On the API key runtime the classifier now hands claude its prompt as one
stream-json user message (`--input-format stream-json`) with two text
blocks: the PR's shared context, carrying `cache_control: ephemeral`, and
the classification's question.
As plain stdin text the whole prompt was one block, and claude puts its
only message breakpoint on the last block, after the question. The cache
writes an entry only where a breakpoint is, so no entry ended at the
shared-context/question boundary. Every classification wrote the whole
prompt and none read it (production: cacheRead ~0, cacheCreation ~input).
The system-prompt prefix (~270 tokens) is under the minimum cacheable
length. Captures against a local listener with a dummy key (claude
2.1.223) show the client breakpoint survives on the shared block, and that
claude adds none there on its own.
The PR stays in the user role: moving it into the system prompt would give
untrusted PR text the system prompt's authority.
`Model::ask` takes a `Prompt { shared, question }`. The ACP runtime joins
it back into the same text it sent before.
Refs #5245
1.8 KiB
swarm-review-classifier
The review classifier's try-out service, in the swarm-review-classifier
container on the host that sets deploy.review-classifier.enable. The
design is docs/swarm/review-classifier.md.
swarm-controller reads a PR (its head commit, its commits and its diff) and sends it, with the operator's classifications, as a request on the queue. This service asks a small model one classification at a time and answers with the result table and the overall verdict. It writes nothing to the forge and holds no forge credential.
What a run is
- The shared context first: a system prompt, the PR's commits with their full
messages, and the diff with lockfiles collapsed as
hive-forge diffdoes. Every classification's prompt starts with the same bytes, andclaudegets that part as a content block of its own, with a cache breakpoint at its end. - One
claude --printper classification, with no session, no tools (--tools ""), no MCP server, its own empty directory andANTHROPIC_API_KEYin its environment only. - An answer is a JSON object with a verdict, a certainty and a reason; the
classifier truncates a reason over 500 characters to 499 plus
…. A failed run, a timeout or an answer that doesn't validate leaves that row without a verdict, and it doesn't count. - A run with no API key, or with no classification answered, fails as a whole: the caller sees an error, never a verdict.
Credentials
The container holds one credential: its own store certificate, which nspawn hands in at start without a copy on disk. With it the service reads its queue client secret at start and the API key at every run, so a key written or removed in the store takes effect on the next run. Its store role reads exactly those two paths.