BriefComposer sends the predecessor report twice when an enrich role name equals handoff.from #690

Closed
opened 2026-09-24 17:11:26 +00:00 by jared · 5 comments
Owner

Problem

brief_composer.rb compose builds { node:, instructions:, brief:, handoff: handoff_json }.merge(enriched_facts). handoff_json carries the immediate predecessor's content. An enrich entry that is a role name resolves through latest_handoff_content(from_agent_type:), which returns the same row on first entry. Two nodes always enter from the role they enrich: seam-designer enriches change-auditor (poodr-implementation.yaml:26, poodr-ticket-implementation.yaml:33) and test-writer enriches seam-designer (poodr-implementation.yaml:43, poodr-ticket-implementation.yaml:50). Role reports measure median 1,587 chars, max 8,497, so the duplicate can cost up to 8.5k of a 10,000-unit hook cap. The enrich entry is still needed on re-entry (for example test-reviewer fail routes back to test-writer, where handoff.from is test-reviewer).

Reproduce

n/a: design audit finding, not a runtime defect.

Approach

In BriefComposer#compose, when handoff.from equals a name in the node's enrich list, send handoff as { id, from } only. The enrich key keeps the content because charters read the report by role name. On re-entry (test-reviewer fail to test-writer) handoff.from is not an enriched name, so its content stays. Do this in the composer so HookPayload measures the reduced size.
Required work:

  1. Red test: compose with handoff.from equal to an enriched role name returns handoff with keys id and from only, and the enrich key carries the content.
  2. Red test: compose with handoff.from not in the enrich list returns handoff with id, from, and content unchanged.
  3. Composer change in plugins/os-sdlc/lib/os_sdlc/pipeline/brief_composer.rb.

Acceptance criteria

  1. When handoff.from equals a name in the node enrich list, BriefComposer#compose returns handoff with keys id and from only, and the enrich key for that name carries the content.
  2. When handoff.from is not in the enrich list, compose returns handoff with id, from, and content unchanged.
  3. When there is no handoff, compose returns handoff nil as today.

Owner sketch

plugins/os-sdlc/lib/os_sdlc/pipeline/brief_composer.rb (compose, handoff_json). Tests: plugins/os-sdlc/tests/brief_composer_test.rb or the existing test file that covers BriefComposer.

Evidence

#676 audit note from the seam-designer and test-writer rows plus the handoff-path check of brief_composer.rb:17-36.

Origin

• Trigger: #676 enrich-fact audit, run in session 9434289b
• Improvised this session: none
• Chain: oversized or duplicated brief facts <- enrich lists chosen per node without a per-role byte audit <- DESIGN (map enrich model)
• Root candidate: this ticket
• Where: the map/charter/composer paths named in the draft
• Parent: #676
• Session: 9434289b-c48f-4a8a-8add-0d82068e2c3f
• Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/9434289b-c48f-4a8a-8add-0d82068e2c3f.jsonl

Skeptic verdict: CREATE (os-backlog:ticket-skeptic, session 9434289b, citations verified).

## Problem brief_composer.rb compose builds { node:, instructions:, brief:, handoff: handoff_json }.merge(enriched_facts). handoff_json carries the immediate predecessor's content. An enrich entry that is a role name resolves through latest_handoff_content(from_agent_type:), which returns the same row on first entry. Two nodes always enter from the role they enrich: seam-designer enriches change-auditor (poodr-implementation.yaml:26, poodr-ticket-implementation.yaml:33) and test-writer enriches seam-designer (poodr-implementation.yaml:43, poodr-ticket-implementation.yaml:50). Role reports measure median 1,587 chars, max 8,497, so the duplicate can cost up to 8.5k of a 10,000-unit hook cap. The enrich entry is still needed on re-entry (for example test-reviewer fail routes back to test-writer, where handoff.from is test-reviewer). ## Reproduce n/a: design audit finding, not a runtime defect. ## Approach In BriefComposer#compose, when handoff.from equals a name in the node's enrich list, send handoff as { id, from } only. The enrich key keeps the content because charters read the report by role name. On re-entry (test-reviewer fail to test-writer) handoff.from is not an enriched name, so its content stays. Do this in the composer so HookPayload measures the reduced size. Required work: 1. Red test: compose with handoff.from equal to an enriched role name returns handoff with keys id and from only, and the enrich key carries the content. 2. Red test: compose with handoff.from not in the enrich list returns handoff with id, from, and content unchanged. 3. Composer change in plugins/os-sdlc/lib/os_sdlc/pipeline/brief_composer.rb. ## Acceptance criteria 1. When handoff.from equals a name in the node enrich list, BriefComposer#compose returns handoff with keys id and from only, and the enrich key for that name carries the content. 2. When handoff.from is not in the enrich list, compose returns handoff with id, from, and content unchanged. 3. When there is no handoff, compose returns handoff nil as today. ## Owner sketch plugins/os-sdlc/lib/os_sdlc/pipeline/brief_composer.rb (compose, handoff_json). Tests: plugins/os-sdlc/tests/brief_composer_test.rb or the existing test file that covers BriefComposer. ## Evidence #676 audit note from the seam-designer and test-writer rows plus the handoff-path check of brief_composer.rb:17-36. ## Origin • Trigger: #676 enrich-fact audit, run in session 9434289b • Improvised this session: none • Chain: oversized or duplicated brief facts <- enrich lists chosen per node without a per-role byte audit <- DESIGN (map enrich model) • Root candidate: this ticket • Where: the map/charter/composer paths named in the draft • Parent: #676 • Session: 9434289b-c48f-4a8a-8add-0d82068e2c3f • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/9434289b-c48f-4a8a-8add-0d82068e2c3f.jsonl Skeptic verdict: CREATE (os-backlog:ticket-skeptic, session 9434289b, citations verified).
Author
Owner

Second-opinion review (Codex, session 9434289b): RISK MEDIUM. seam-designer.md:11 and test-writer.md:18 tell the role to read the fact by name. A marker value leaves the key present but empty. Prefer the other option: keep the enrich key and drop the duplicate from the handoff field, or dedupe by content in HookPayload only when the cap would trip.

Second-opinion review (Codex, session 9434289b): RISK MEDIUM. seam-designer.md:11 and test-writer.md:18 tell the role to read the fact by name. A marker value leaves the key present but empty. Prefer the other option: keep the enrich key and drop the duplicate from the handoff field, or dedupe by content in HookPayload only when the cap would trip.
Author
Owner

Shape correction before implementation: keep the content under the enrich key (charters read it by name at seam-designer.md:11 and test-writer.md:18) and reduce the handoff field to { id, from } when handoff.from equals an enriched role name. Do not replace the enrich value with a marker.

Shape correction before implementation: keep the content under the enrich key (charters read it by name at seam-designer.md:11 and test-writer.md:18) and reduce the handoff field to { id, from } when handoff.from equals an enriched role name. Do not replace the enrich value with a marker.
Author
Owner

Decision (session 9434289b): drop handoff.content, keep the enrich key. Rule: when handoff.from equals a name in the node's enrich list, the composer sends handoff as { id, from } only. Reason: charters read the report by role name; handoff.from is still needed to say which node routed here; on re-entry (test-reviewer fail to test-writer) handoff.from is not an enriched name so its content, the objections, stays. Do it in BriefComposer#compose so the HookPayload cap sees the reduced size.

Decision (session 9434289b): drop handoff.content, keep the enrich key. Rule: when handoff.from equals a name in the node's enrich list, the composer sends handoff as { id, from } only. Reason: charters read the report by role name; handoff.from is still needed to say which node routed here; on re-entry (test-reviewer fail to test-writer) handoff.from is not an enriched name so its content, the objections, stays. Do it in BriefComposer#compose so the HookPayload cap sees the reduced size.
Author
Owner

Work started on branch ticket-690 via /os-sdlc:implement, session 9434289b.

Work started on branch ticket-690 via /os-sdlc:implement, session 9434289b.
Author
Owner

Resolution

Done: BriefComposer#handoff_json returns { id, from } when handoff.from is a name in the node enrich list; the enrich key carries the content. Handoffs from non-enriched roles and nil handoffs are unchanged. Three tests in tests/pipeline/brief_composer_test.rb.

Evidence: os-sdlc implementation 26, 8 dispatches all pass, 0 retries, 514s; suite gate green (40,504 ms); branch ticket-690, 2 files.

Follow-ups: none

## Resolution **Done:** BriefComposer#handoff_json returns { id, from } when handoff.from is a name in the node enrich list; the enrich key carries the content. Handoffs from non-enriched roles and nil handoffs are unchanged. Three tests in tests/pipeline/brief_composer_test.rb. **Evidence:** os-sdlc implementation 26, 8 dispatches all pass, 0 retries, 514s; suite gate green (40,504 ms); branch ticket-690, 2 files. **Follow-ups:** none
jared closed this issue 2026-09-24 19:05:54 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#690
No description provided.