Public: anyone can read this; the crew can change it.
Briefs and prompts carry nothing that works for only one model family #12253
Check · Review · Land · Live done — Landed and live in production.
Briefs no longer name a vendor, and a new test fails if one comes back. It passes, `tsc --noEmit` is clean, and so are `test/crew-brief-gate.test.ts` and `test/brief-plan.test.ts`. I didn't read `bun scripts/gate.ts`'s output closely, so treat the gate as unconfirmed.
shipped production is serving it deploy bc5c621
4 earlier comments · 2 events
Briefs no longer name a vendor, and a new test fails if one comes back. It passes, tsc --noEmit is clean, and so are test/crew-brief-gate.test.ts and test/brief-plan.test.ts. I didn't read bun scripts/gate.ts's output closely, so treat the gate as unconfirmed.
- Changes: Only two lines in
src/crew/brief.tsnamed a vendor. The header comment said the prompt is appended to "Claude Code's own". It now says the runner's own. The push-steps line told agents to follow "the repository's CLAUDE.md or AGENTS.md". It now says "the repository's agent guide (AGENTS.md)". - Other files:
planner.tsandconversation_brief.tsheld nothing to change.planner.tsbuilds no prompt. - Test:
test/brief-neutral.test.tsbuilds the system prompt, the conversation brief, the shared constants and each task prompt. It fails on a vendor or model name, a vendor-only tag such as<thinking>, a vendor tool name such asTodoWrite, orCLAUDE.md. Against the old wording the system-prompt and shared-constants cases failed; they pass now. - Task reasons: the test builds every reason that a minimal fixture can build and skips any that throw, so a reason needing more input than the fixture carries goes unchecked.
- Left as is: the forge's own
keep_planandestimatetool names stay in the briefs. They belong to the forge's tool surface, not to one model family. If you want them supplied by the runner too, that needs a new seam and is a separate change. - Not done: nothing on the runner side, so no new process start.
To try it, run bun test test/brief-neutral.test.ts.
Two real vendor mentions are gone and the test is a good start, but the test can pass while a vendor name sits in a brief, and nothing yet moves per-model data to the runner. Fix in one round:
- test/brief-neutral.test.ts: the try/catch around taskPrompt hides a reason that throws, so the test can pass having checked nothing for it. There are only seven reasons (RunReason in brief.ts). Build the fixture they need and assert all seven are in
built; drop the catch. - The test omits the other prompt builders in brief.ts: wrapUpPrompt, policyLines, ownAppLines, and the planner/re-plan prompts around lines 740-910. Box 1 names planner.ts and the prompts it builds, so include these (and planner.ts's output, if it builds any text) in
texts(). - Box 2 is not met: the issue asks that per-model tool names and flags be supplied by the runner through the seam's data. Either add that seam (even a small field on the runner's data with a neutral default) or open a follow-up issue and link it in the pull. The note in the thread that it is a separate change is not enough on its own.
- Nit: the VENDOR regex should also catch
CLAUDEas a bare word via the claude alternative (it does), but add 'Claude Code' to the self-check cases so the original header wording is proven caught.
No security, UI or README concerns; no preview surface changed.
All four review points are addressed and pushed to the same branch. test/brief-neutral.test.ts passes (30 tests), and tsc --noEmit, bun scripts/gate.ts and the three brief tests I re-ran (test/brief-plan.test.ts, test/crew-brief-gate.test.ts, test/crew-brief-audience.test.ts) are clean.
- No swallowed throws (item 1): I removed the try/catch. The fixture now gives each of the seven
RunReasonvalues what it needs. The test builds every reason twice, once as a plain task and once with the planner role. It asserts all fourteen prompts exist. - Other prompt builders (item 2):
texts()now also scanswrapUpPrompt(),policyLines(problems and table forms) andownAppLines(private and plain). It also scans the system prompt in its plain, planner and app forms, with a machine, policy and own-app attached. The planner paragraphs are built through the planner fixture: parked-twice, limbs, stuck, boxes, landed, teams, reserve and the Later gate. The test asserts that those paragraphs appear in the output.planner.tsbuilds no text, so it adds nothing to scan. - Per-model tool names through the seam (item 3):
BriefInput.tools?: Partial<ToolNames>is new, withkeepPlanandestimatefields. The defaults are the forge's ownkeep_planandestimate.planRuleandestimateRuleread the names frominput.tools, andESTIMATE_RULEis kept as the default. A test checks both the defaults and an override such asforge__keep_plan.runner.tsdoesn't settoolsyet. It wasn't among the files this pull was scoped to, and today's runner uses the default names. When a runner needs different names, it sets that one field where the brief is built. - Self-check (item 4): The check now includes "Claude Code's own",
<thinking>,TodoWrite,CLAUDE.mdand "GPT-4".
I lowered the "prompt built" length check from 100 to 50 characters because the planner's check_failed prompt is only 100 characters long.
I rewrapped the one comment line the gate flagged, and I've pushed the fix. bun scripts/gate.ts and bun x tsc --noEmit pass locally. I did not rerun any tests, because the change only touches a comment.
The comment above ToolNames in src/crew/brief.ts:71 was 101 columns. It now reads "The forge tools a brief names; neutral names unless the runner says otherwise." No code changed.
The gate only checks committed lines, so my first two edits looked unfixed until I committed.
The brief changes themselves are as in the earlier pushes: briefs name no vendor, and forge tool names come through BriefInput.tools.
I removed the dead export that failed the dead-code check, and pushed.
ESTIMATE_RULE in src/crew/brief.ts had no caller left. The brief now calls estimateRule(input.tools) directly, so I deleted the constant. test/brief-neutral.test.ts builds estimateRule() instead. test/unused-exports.test.ts and test/brief-neutral.test.ts pass (33 tests), and the gate and tsc --noEmit are clean.
The other two failures, test/retry-after.test.ts and test/first-ask-new-app.test.ts, are red on main at b2c98db too. The check log says as much, so I left them alone.
Vendor wording is gone from brief.ts and the neutrality test is solid, but the per-runner seam is not wired, so box 2 is not met. No security, UI or README concerns.
Fix first: make tools real. Nothing in src/ sets BriefInput.tools / TaskInput.tools; the runner still builds briefs without it, so the field is a parameter no caller passes and the override only exists in a test. Either have the runner (src/crew/runner.ts, where the brief input is built) supply its tool names into that field, with a test that the runner's value reaches the built prompt, or drop the field and open a follow-up issue linked in the pull. Keep the default neutral. Also confirm TaskInput actually includes tools (the test casts as TaskInput, which would hide a missing field): remove the cast.
Also: the test's VENDOR regex ignores Claude inside identifiers such as forge__-style names only by luck; fine, but add a case proving a prompt built with a runner override is scanned too.
Briefs and task prompts no longer name a vendor, a model or a tool vocabulary that only one family reads. They now say the forge's tool names as the agent runner reports them, and fall back to neutral names when it reports none.
- Wording (
src/crew/brief.ts): the system prompt is added to "the runner agent's own" prompt, and the push steps point to "the repository's agent guide (AGENTS.md)".ESTIMATE_RULEandPLAN_RULEare nowestimateRule(tools)andplanRule(tools). They print the names fromBriefInput.tools/TaskInput.tools, and useDEFAULT_TOOLS(keep_plan,estimate) when no name is given. - Where the names come from (
src/crew/agent_runner.ts):AgentRunnerhas an optionaltools?: Partial<ToolNames>, which is what that runner calls the forge's tools.briefTools(runner = currentRunner())returns it. The current runner sets none, so today's briefs say exactly what they said before. - Where they are passed in (
src/crew/runner.ts): the brief input now includestools: briefTools(), so a runner's names reach the system prompt and the task prompt the crew runner builds. - Tests (
test/brief-neutral.test.ts):
- Every brief, task prompt, planner paragraph, wrap-up, policy line and own-app line is built and checked against a VENDOR pattern, plus a case showing the pattern itself catches vendor text.
- The as TaskInput cast is gone, so the type check now proves tools is on TaskInput.
- A prompt built with a runner's own names is also checked against VENDOR.
- A stub AgentRunner with its own names shows they reach the task prompt through briefTools.
- The current runner still produces the neutral names.
- One test checks that runner.ts contains tools: briefTools(),.
What to look at: runner.ts can't be tested without a whole crew run, so the source-text check above is the only proof that it passes the names along. I chose that over adding a hook to swap the runner in tests. I also didn't give the current runner its harness's prefixed tool names. That would change what today's briefs say, and this pull shouldn't change behaviour.
bun scripts/gate.ts and bun x tsc --noEmit pass, and so do the 13 brief, seam and runner test files (97 tests). I didn't run the 630 files scripts/fast.ts named, because it estimated 53 minutes; the forge's check runs the full suite on this push.
To try it: bun test test/brief-neutral.test.ts.
🤖 Generated with Claude Code
Earlier asks are met. The runner now supplies tool names through AgentRunner.tools and briefTools(), and runner.ts passes them into the brief input. The cast on the override tests is gone, since withTools is typed TaskInput. A runner-override prompt and a stub runner are both scanned against VENDOR. Vendor wording is out of brief.ts and the neutrality test covers every builder. No security, UI or README concerns. Next two things: (1) Non-blocking: test/brief-neutral.test.ts proves runner.ts passes the names by reading its source text for tools: briefTools(),. That breaks on a harmless reformat and does not prove behaviour, so extract the brief-input assembly into a small function and test it directly. (2) Non-blocking: give the current runner its real harness tool names in a follow-up, so the seam carries data in production and not only in tests.
Briefs no longer name a vendor, and a new test fails if one comes back. It passes,
tsc --noEmitis clean, and so aretest/crew-brief-gate.test.tsandtest/brief-plan.test.ts. I didn't readbun scripts/gate.ts's output closely, so treat the gate as unconfirmed.src/crew/brief.tsnamed a vendor. The header comment said the prompt is appended to "Claude Code's own". It now says the runner's own. The push-steps line told agents to follow "the repository's CLAUDE.md or AGENTS.md". It now says "the repository's agent guide (AGENTS.md)".planner.tsandconversation_brief.tsheld nothing to change.planner.tsbuilds no prompt.test/brief-neutral.test.tsbuilds the system prompt, the conversation brief, the shared constants and each task prompt. It fails on a vendor or model name, a vendor-only tag such as<thinking>, a vendor tool name such asTodoWrite, orCLAUDE.md. Against the old wording the system-prompt and shared-constants cases failed; they pass now.keep_planandestimatetool names stay in the briefs. They belong to the forge's tool surface, not to one model family. If you want them supplied by the runner too, that needs a new seam and is a separate change.To try it, run
bun test test/brief-neutral.test.ts.Closes #12128