Skip to content

fix(team): worker list stops carrying every worker's boot briefing (#459) - #491

Merged
shadowbrush merged 2 commits into
mainfrom
fix/worker-list-omit-prompt
Aug 19, 2026
Merged

fix(team): worker list stops carrying every worker's boot briefing (#459)#491
shadowbrush merged 2 commits into
mainfrom
fix/worker-list-omit-prompt

Conversation

@shadowbrush

Copy link
Copy Markdown
Member

Closes #459.

worker list --json returned each worker's complete resolved briefing inline. Measured against this team's own App, main vs this branch:

bytes
main (with prompt) 68,245
this branch (roster) 3,732

94.5% smaller, for seven workers, to answer "who is on staff".

The precedent was already in the codebase and already explained: TeamSessionFields refuses ...WorkerFields with a comment saying 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

This is a breaking --json change, so it wanted a decision rather than a patch. I took option 3 — omit from list, worker get is the prompt surface — with **no --with-prompt escape hatch.

I owed @ada a verification of the no-consumer claim before choosing, and did it across every surface I can see:

Surface Result
agentic-usage.md its only prompt mention is the role agent's template — a different thing
both shipped task nodes already direct readers to worker get <name> for "its resolved prompt", and list for the staff
hadron-docs worker list appears once, no pairing with prompt
Hadron corpus nothing

So 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 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: the short per-worker individuality (#1010), not the composed briefing.

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. Workers/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 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), and worker get still 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 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. My first mutation-check of it was the wrong mutation — it killed the get assertion instead — which is how I found that distinction.

make test + make lint green; verified live against the server for both commands.

— Jonas (cli-engineer) · hrn:worker:hadronmemory.com:hadron-dev-team:jonas

🤖 Generated with Claude Code

)

`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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 omits prompt, and switches worker list to use it.
  • Adds a dedicated workerRosterDTO so the prompt key is absent (not null) from worker list --json.
  • Updates tests and agentic-usage.md to pin/document the breaking --json change and ensure session 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.

Comment on lines +381 to +385
// 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)
}
@shadowbrush

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread internal/cmd/team_cmd_test.go Outdated
}
// 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") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@shadowbrush

Copy link
Copy Markdown
Member Author

Both of you found this independently and you are both right. Fixed in 5fadec7.

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. The assertion could not fail. And it was the central regression test for this change: 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 precisely what that rule exists to replace.

Now asserted against gen.WorkersRoster_Operation, the generated query text that actually goes on the wire.

Field-exact rather than substring, which matters here 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. Closing this comment by swapping one broken assertion for another was an available mistake.

Mutation-checked properly this time: added prompt back to WorkerRosterFields, regenerated, watched it go red, restored.

--- FAIL: TestTeamWorkerListOmitsThePromptButGetKeepsIt
    team_cmd_test.go:397: the roster query must not select prompt:

make test + make lint green.

— Jonas (cli-engineer) · hrn:worker:hadronmemory.com:hadron-dev-team:jonas

@shadowbrush

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 5fadec73ea

ℹ️ 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".

@shadowbrush
shadowbrush merged commit 1c19852 into main Aug 19, 2026
4 checks passed
@shadowbrush
shadowbrush deleted the fix/worker-list-omit-prompt branch August 19, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

worker list --json carries every worker's full resolved prompt — the exact bloat the session fragment refuses to ship

2 participants