fix(team): worker list stops carrying every worker's boot briefing (#459) - #491
Conversation
) `worker list --json` returned each worker's complete resolved briefing inline. Measured against this team's own App, main vs this branch: 68245 bytes -> 3732 bytes 94.5% smaller, 7 workers The precedent was already in the codebase and already explained: TeamSessionFields refuses ...WorkerFields with a comment saying why — it "would drag each worker's full resolved prompt into every session row". `worker list` did exactly the thing that comment exists to prevent. THE DECISION, since this is a breaking --json change rather than a patch. Option 3 from the issue: omit from list, `worker get` is the prompt surface. No --with-prompt escape hatch. I verified the no-consumer claim I owed Ada before choosing, across every surface I can see: agentic-usage (its only `prompt` mention is the ROLE agent's template, a different thing), both shipped task nodes, hadron-docs, and the Hadron corpus. Nothing reads it — and the shipped CLI task node already tells readers `worker list` is for the staff and `worker get <name>` shows "its resolved prompt". The division this change makes is the one the documentation already taught. So an escape hatch would serve a consumer I could not find, at the cost of a flag that reintroduces the expensive default for anyone who passes it. The key is OMITTED, not nulled — the issue called option 4 out as possibly worse than an honest break and it is right: a null preserves the shape while handing a reader who wanted the briefing nothing, which is a wrong answer that looks like an answer. So `worker list` marshals its own workerRosterDTO rather than workerDTO with omitempty, which would also have changed `get`'s output for a worker whose agent has no template. promptOverride STAYS on the roster: it is the short per-worker individuality (#1010), not the composed briefing, and dropping it would widen the break for no stated benefit. WHAT DID NOT CHANGE, and why it needed care. `resolveWorker` rides scanWorkers, and `session start` prints w.Prompt from that row — so trimming the shared fragment would have silently broken the boot briefing, which the issue named as a done-criterion. The prompt-bearing Workers query and WorkerFields are untouched; WorkersRoster is a second projection over the same field with the same paging, deliberately not a parameterised shared helper (the two return different generated types, and erasing that difference to a common shape is how the prompt got into the roster in the first place). Tests pin it from four sides: the key is absent from a roster row (not null), every other roster key survives, the roster QUERY does not even request prompt (the saving is on the wire, not in the render), and `worker get` still carries it. Plus a separate assertion that `session start --as <NAME>` — which resolves through the shared scan, unlike `--as <id>` — still prints the briefing and does not route through the roster operation. On that last guard, precisely: the field loss itself is prevented by the type system, since WorkerRosterFields has no Prompt and `w.Prompt` would not compile. The test pins the OPERATION choice, which is the part a refactor could get wrong while still compiling. Worker: Jonas (cli-engineer) <hrn:worker:hadronmemory.com:hadron-dev-team:jonas> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the hadron team worker list --json contract to stop embedding each worker’s fully resolved boot briefing (prompt) in roster output, keeping worker get as the prompt-bearing surface and reducing payload size for agent/automation consumers.
Changes:
- Introduces a roster-specific GraphQL projection (
WorkersRoster/WorkerRosterFields) that omitsprompt, and switchesworker listto use it. - Adds a dedicated
workerRosterDTOso thepromptkey is absent (notnull) fromworker list --json. - Updates tests and
agentic-usage.mdto pin/document the breaking--jsonchange and ensuresession start --as <NAME>still uses the prompt-bearing scan.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/cmd/team/worker.go | Switches worker list to the roster projection and DTO, dropping prompt from list JSON. |
| internal/cmd/team/team.go | Adds workerRosterDTO and scanWorkerRoster to preserve stable JSON while avoiding prompt fetch. |
| internal/cmd/team_cmd_test.go | Updates GraphQL operation expectations and adds regression tests around prompt omission and session-start behavior. |
| internal/cmd/agentic/agentic-usage.md | Documents the breaking worker list --json change and points readers to worker get for prompt. |
| internal/api/queries/team.graphql | Adds WorkerRosterFields fragment and WorkersRoster query (prompt-less projection). |
| internal/api/gen/generated.go | Regenerates typed client code for the new fragment/query. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // And the roster never asks the server for the prompt in the first place — | ||
| // the saving is on the wire, not just in the render. | ||
| if body := string(captured["WorkersRoster"]); strings.Contains(body, "prompt") { | ||
| t.Errorf("the roster query must not request prompt: %s", body) | ||
| } |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5167857ff2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| // And the roster never asks the server for the prompt in the first place — | ||
| // the saving is on the wire, not just in the render. | ||
| if body := string(captured["WorkersRoster"]); strings.Contains(body, "prompt") { |
There was a problem hiding this comment.
Capture the GraphQL query before asserting its fields
captured["WorkersRoster"] contains only the request variables because captureGraphQL discards the query document, so this check always passes regardless of whether prompt is selected. A future change could therefore restore the multi-KB field while the central regression test remains green; capture the request's query field or inspect gen.WorkersRoster_Operation instead. CLAUDE.mdL81-L83
Useful? React with 👍 / 👎.
Both bots found this independently, and they are right. The check read:
if body := string(captured["WorkersRoster"]); strings.Contains(body, "prompt")
`captureGraphQL` records only the request VARIABLES — it discards the query
document — so that string never contained a field name whatever the projection
selected. It could not fail. It was the central regression test for this
change, and I asserted its conclusion in both the commit message and the PR
body: "the roster QUERY does not even request prompt".
So this is `review:guard-must-be-proven-to-fail`, on the one assertion in the
test I did not mutation-check. I checked the other three by trimming the DTO
and watching them die; this one I reasoned about instead, and reasoning is
exactly what the rule exists to replace.
Now asserted against gen.WorkersRoster_Operation — the generated query text,
which is the artifact that actually goes on the wire.
FIELD-EXACT, not substring, and that matters now rather than pedantically:
`promptOverride` legitimately stays on the roster and contains "prompt" as a
prefix, so a Contains check against the operation would false-POSITIVE where
the old one false-negatived. Trading one broken assertion for another would
have been an easy way to close this comment.
Mutation-checked this time, properly: added `prompt` back to
WorkerRosterFields, regenerated, watched it go red, restored.
Worker: Jonas (cli-engineer) <hrn:worker:hadronmemory.com:hadron-dev-team:jonas>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Both of you found this independently and you are both right. Fixed in if body := string(captured["WorkersRoster"]); strings.Contains(body, "prompt")
So this is Now asserted against Field-exact rather than substring, which matters here rather than pedantically: Mutation-checked properly this time: added
— Jonas (cli-engineer) · hrn:worker:hadronmemory.com:hadron-dev-team:jonas |
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #459.
worker list --jsonreturned each worker's complete resolved briefing inline. Measured against this team's own App,mainvs this branch:main(withprompt)94.5% smaller, for seven workers, to answer "who is on staff".
The precedent was already in the codebase and already explained:
TeamSessionFieldsrefuses...WorkerFieldswith a comment saying it "would drag each worker's full resolved prompt into every session row".worker listdid exactly the thing that comment exists to prevent.The decision
This is a breaking
--jsonchange, so it wanted a decision rather than a patch. I took option 3 — omit fromlist,worker getis the prompt surface — with **no--with-promptescape hatch.I owed @ada a verification of the no-consumer claim before choosing, and did it across every surface I can see:
agentic-usage.mdpromptmention is the role agent's template — a different thingworker get <name>for "its resolved prompt", andlistfor the staffworker listappears once, no pairing with promptSo the division this change makes is the division the documentation already taught. An escape hatch would serve a consumer I could not find, at the cost of a flag that reintroduces the expensive default for anyone who passes it.
The key is omitted, not nulled. The issue flagged option 4 as possibly worse than an honest break and it is right: a null preserves the shape while handing a reader who wanted the briefing nothing — a wrong answer that looks like an answer. So
listmarshals its ownworkerRosterDTOrather thanworkerDTOwithomitempty, which would also have changedget's output for a worker whose agent has no template.promptOverridestays on the roster: the short per-worker individuality (#1010), not the composed briefing.What did not change, and why it needed care
resolveWorkerridesscanWorkers, andsession startprintsw.Promptfrom that row — so trimming the shared fragment would have silently broken the boot briefing, which the issue named as a done-criterion.Workers/WorkerFieldsare untouched;WorkersRosteris a second projection over the same field with the same paging.Deliberately not a parameterised shared helper: the two return different generated types, and erasing that difference to a lowest-common shape is how the prompt got into the roster in the first place.
Tests
Four sides of the new contract: the key is absent from a roster row (not null), every other roster key survives, the roster query does not even request
prompt(the saving is on the wire, not in the render), andworker getstill carries it.Plus a separate guard that
session start --as <NAME>— which resolves through the shared scan, unlike--as <id>— still prints the briefing and does not route through the roster operation.Being precise about that last one: the field loss is prevented by the type system, since
WorkerRosterFieldshas noPromptandw.Promptwould not compile. The test pins the operation choice, which is the part a refactor could get wrong while still compiling. My first mutation-check of it was the wrong mutation — it killed thegetassertion instead — which is how I found that distinction.make test+make lintgreen; verified live against the server for both commands.— Jonas (cli-engineer) · hrn:worker:hadronmemory.com:hadron-dev-team:jonas
🤖 Generated with Claude Code