Files
xarmian 4532eecffd feat(decision): conventions ask about the item without its comment trail, 12 questions a call (TASK-3119 U2a) (#1871)
* chore(eval): TASK-3119 U2 gates in one command; decision.BuildCommentState

-gates runs three passes through the production question path:
A  U0's population on the U2a item state (links in, trail out), gated on
   precision not dropping below U0's on any convention that fired;
B  comment-borne breaks, each comment asked alone with its item's title
   and collection, gated on precision >= 0.9;
C  a measurement of 12+ questions in one call (MaxPerCall 12).

BuildCommentState is the comment subject's one serialisation, shared by
the harness now and the runner in U2b. Comments are clipped at the body
bound (16k runes), not the trail's 2k.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* feat(decision): the conventions set asks about the item without its comment trail, 12 questions a call (TASK-3119 U2a)

The conventions state carried the last 10 comments, clipped at 2,000 runes
each: on docapp that was most of every call's input (15.7k tokens for one
write across 3 calls), 720 items had a clipped comment, and a break past the
clip was invisible anyway. Comments get their own subject in U2b.

- QuestionSet.NoTrail leaves the trail out; the conventions set sets it. The
  state is exactly what the U2 gates measured (recent_trail [], links in):
  precision held at 100% on every convention that fires, recall rose (1286
  40->80%, 2693 70->90%), 0 truncated states, ~954 input tokens per call.
- MaxPerCall 5 -> 12, from measure C: up to 13 questions in one call gave
  identical precision and recall, 0 of 116 answers moved by more than 0.2.
- A comment write no longer changes this set's state, so it costs no call.
- Every conventions answer computed before this reads not-current until the
  item's next write (the state bytes changed): a fleet-wide re-ask, by design.
- CLAUDE.md: the registered sets (it still said attention was the only one,
  since U1a) and the per-set state.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-08 00:01:26 -04:00

39 lines
1.5 KiB
Go

package decision
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
)
// TASK-3119 U2: a comment is asked about on its own (the "comment subject"),
// with its item's title and collection for context, instead of riding in
// every item state as part of a trail that was clipped at 2,000 runes per
// comment. This is that state's ONE serialisation, shared by the eval
// harness (which measured it before it shipped) and the runner.
// maxCommentSubjectRunes bounds the comment. It is the body's bound, not the
// trail's: a comment asked about alone has the request to itself. Census at
// the time (the :7777 DB): 20 of 23,618 comments exceed it.
const maxCommentSubjectRunes = maxStateBodyRunes
// CommentState is the state a comment subject is asked about. Like
// ItemState, part of the hash: a change here re-asks every comment.
type CommentState struct {
Title string `json:"title"`
Collection string `json:"collection"`
Comment string `json:"comment"`
}
// BuildCommentState builds and serialises one comment's state.
func BuildCommentState(itemTitle, collectionSlug, body string) (BuiltState, error) {
clipped, truncated := clipRunes(body, maxCommentSubjectRunes)
b, err := json.Marshal(CommentState{Title: itemTitle, Collection: collectionSlug, Comment: clipped})
if err != nil {
return BuiltState{}, fmt.Errorf("decision comment state: marshal: %w", err)
}
sum := sha256.Sum256(b)
return BuiltState{Bytes: b, Hash: hex.EncodeToString(sum[:]), Truncated: truncated}, nil
}