feat(prompt): capability-gated assembly + the session transcript line (#421, #410) - #438
Merged
Conversation
…ranscript line (#421, #410) ## What changed The root system prompt is no longer one frozen string. `src/prompt_text.zig` holds it as 16 capability-scoped segments; `prompts.zig` owns the `segments` table that gives each one a `Gate`, the comptime `main_system_prompt` those segments still concatenate to (an `Agent` struct default, so it has to stay comptime), and `composeBase()`, which drops every segment whose capability this process does not have. `startup.buildSystemPrompt` now starts from `prompts.baseForSession(arena)` instead of the constant. Gates are read from the two predicates dispatch already refuses a hallucinated call with (`no_local_tools.blocks` + `tool_gates.blocks`), so the prompt cannot disagree with the catalog. What the gates remove today: - `--no-local-tools` (#330) drops the read_file/edit_file/write_file/codedb/bash paragraph, the `.graff/traces` paragraph (its instruction is "read and analyze it", and the trace is a host file), the `gh issue create` paragraph, and the batching note's three tool examples: 1930 of 6777 chars, 28.5%; - `subagent`/`workflow` absent drops the fan-out paragraph (413 chars); - `todo_write` absent drops both todo paragraphs (197); - `note_constraint` absent drops the record-a-rejection paragraph (388). Two prompt-doctrine lines from the prime-agent analysis are adopted where they had a home: "never invent a tool, a parameter, or a wrapper API" joins the always-on intro, and "run the target project through its OWN environment" joins the verify-your-work paragraph. #410 adds one line naming this session's durable transcript. It composes inside `setSystemPrompts`, not at the call site, so a persona swap or a `set_system_prompt` cannot drop it; `buildRootAgent` settles `session_name` before the funnel runs. `prompt_snapshot_tests.zig` pins all of it: the full-capability prompt against an inline golden, and a capability matrix asserting that an absent capability contributes ZERO text (exact length AND the dropped segment's bytes being unfindable), plus the already-gated MCP/skill/optional-tool/goal paths. ## Why - Problem/failure mode: every session paid for every instruction. An embedder running `--no-local-tools` was told to `read_file` before editing and to prefer `codedb` over `bash grep` for tools the provider was never told exist - tokens on every turn, and an invitation to call something that cannot work. - Reason for this approach: the segment table is the single source of both the comptime constant and the runtime composition, so the two cannot drift or reorder; and `composeBase` returns the constant itself at full capability, so the common path allocates nothing and is byte-identical to what shipped. - Constraints or trade-offs: the transcript line says JSON, not JSONL as the issue assumed - `.graff/sessions/<name>.session.json` is one object, the JSONL files are the traces - and it does not claim to hold what compaction discarded, because compaction rewrites the retained history in place. A wrong format or provenance claim costs the model a turn discovering otherwise. - Rejected alternatives: gating the git/PR/commit guidance too. An embedder still reaches a sandbox where git may run, and "never discard the user's work" is the wrong instruction to make optional. Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
This was referenced Aug 6, 2026
Closed
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #421 and #410 together (both are buildSystemPrompt work), from the prime-agent adoption batch.
#421 — capability-conditional assembly
The root prompt is now 16 capability-scoped segments (
prompt_text.zig), composed by gates that read the same predicates dispatch uses to refuse a hallucinated call (no_local_tools.blocks+tool_gates.blocks) — the prompt structurally cannot disagree with the tool catalog the provider was sent. At full capability the compose returns the comptime constant itself: zero allocation, byte-identical to what shipped.Savings:
--no-local-toolsembedder sessions drop 28.5% of the base prompt (~482 tokens/turn); the floor config drops 43.2%. Full trim table with per-segment justification in the commit. Deliberately NOT trimmed: the destructive-git rail (an embedder still reaches a sandbox where git may run). The matrix test earned its keep immediately — it caught tool-name examples leaking through an otherwise-gated note, which got split so the instruction survives and only its illustration goes. Both prime doctrine lines adopted into the always-on intro.#410 — with a premise correction
The issue said JSONL and assumed the transcript preserves what compaction discards. Neither is true in graff:
.graff/sessions/<name>.session.jsonis one JSON object whosemessagesarray is rewritten in place after compaction. The line shipped says what is TRUE (single object, lags the live turn, resume artifact not an archive), and a test asserts the string "JSONL" is absent — a wrong provenance claim would cost the model turns. Composes inside the #326setSystemPromptsfunnel so persona swaps can't drop it; suppressed for subagents and--no-local-tools.Experiment
prompt_snapshot_tests.zig: a full-capability inline golden of the entire prompt (drift becomes a conscious choice), a capability matrix asserting absent capabilities contribute zero bytes (checked two ways, failing segment named), zero-alloc fast-path check, transcript-line exactness, and verified on the wire against a live mock request.Gates
Tests 994 → 1003 (+9 exactly); all 9 golden eval files PASS — with the reason no eval moved documented (the request estimator floors above the prompt-size term); fmt clean; no
**; all files ≤600 (startup.zig untouched at 600).examples/prepare_graff_tournament.pyupdated — its old prompt scrape was approximate, the new per-segment read reproduces the shipped prompt exactly.Follow-ups flagged, not taken
sub_system_prompthas the same residue (subagents under--no-local-toolsstill see gated tool examples) — needs a runtime compose at spawn sites./trace, a user-only REPL command — Capability-conditional system prompt assembly #421's failure class but a wording fix; left to avoid churning the golden twice.