os-sdlc: terminal payload carries a deterministic run summary #552

Closed
opened 2026-09-11 14:17:39 +00:00 by jared · 2 comments
Owner

Context

The user asked, in session d9e1984d, for a table at the end of every /os-sdlc:implement run: dispatched subagents, time, tokens, verdicts, and workflow issues. The orchestrator assembled it by hand twice. It must come from data. The pipeline database already holds per dispatch: agent type, created and completed timestamps, verdict, stalled timestamp, and per gate step a duration_ms (#528). Edge spends hold retry counts. Model and token data are absent from the database; that is a separate ticket (B, filed alongside).
Design agreed with the user on 2026-09-11: Implementation::Summary owns a DispatchSummary, which owns the dispatch models. Objects are named by what they summarize, not by how the orchestrator draws them. No code comments. agent_type is the key, not node, because dispatches carry no node column.

Observed

NextStep#base_terminal_payload (plugins/os-sdlc/lib/os_sdlc/runner/next_step.rb:88-94) emits type, node, reason, session_id, implementation_id, and the ADR-0171 commit record. No run summary.

Reproduce

n/a: feature ticket

Expected

Both terminal payloads (and the escalation payload, which wraps the same base) carry dispatches and totals:
dispatches: [{id:, agent_type:, elapsed_s:, verdict:, stalled:, gate_steps: [{step:, ms:, exit:}]}]
totals: {dispatches:, elapsed_s:, retries:}

Tasks

  • Add lib/os_sdlc/runner/implementation/summary.rb: class OsSdlc::Runner::Implementation::Summary with self.for(implementation) = new(implementation).to_h; initialize stores the implementation and builds DispatchSummary.new(implementation.dispatches); to_h returns { dispatches: @dispatch_summary.to_a, totals: totals }; private totals = { dispatches: @dispatch_summary.count, elapsed_s:, retries: }; elapsed_s = DispatchSummary.seconds_between(implementation.created_at, last_stamp) where last_stamp = completed_at || failed_at || escalated_at; retries = implementation.edge_spends.sum { |spend| spend.spent.to_i }.
  • Add lib/os_sdlc/runner/implementation/dispatch_summary.rb: class DispatchSummary; self.seconds_between(from, to) returns nil unless both, else (to.to_time - from.to_time).round(1); initialize(dispatches) stores dispatches.sort_by(&:id); count; to_a maps each dispatch to { id:, agent_type: dispatch.agent_type, elapsed_s: seconds_between(created_at, completed_at), verdict:, stalled: dispatch.stalled?, gate_steps: } where gate_steps maps step_results.sort_by(&:id) to { step:, ms: duration_ms, exit: exit_status }.
  • Add def summary = Summary.for(self) to Implementation::InstanceMethods next to latest_dispatch (implementation.rb ~line 167).
  • In base_terminal_payload, chain .merge(@row.summary) onto the base hash literal, before the commit_record guard.
  • In skills/implement/SKILL.md, after the ADR-0171 commit paragraph in the implementation_complete section (~line 139), add one unwrapped line: Render \dispatches` as a table: one row per dispatch, followed by the `totals` line. If you observed workflow issues, list them below the table.`
  • Tests: tests/runner/implementation_summary_test.rb (payload shape, totals count, elapsed, retries from one edge spend); tests/runner/dispatch_summary_test.rb (one entry's agent_type, elapsed_s, gate_steps from one recorded step); one case in tests/runner/cli_next_instruction_test.rb asserting the completion payload's dispatches length equals totals.dispatches.
  • Amend ADR-0171 with one paragraph: the terminal payload also carries the run summary; agent_type stands in for node because dispatches have no node column; retries is the sum of edge_spends.spent. Run /os-adr:find on next_step.rb first.
  • Refresh the plugin cache (bin/refresh-plugins) after SKILL.md changes.

Acceptance criteria

  • A toy-map run to completion prints a payload with dispatches rows and a totals hash; the row count equals totals.dispatches.
  • Zeitwerk loads both new files without an explicit require.
  • No code comments in the new classes.
  • Full suite green.

Out of scope

Model and token data (ticket B). A node column on dispatches. A row cap for long runs.

Origin

Session: 5fdd0aa3-8a9c-4c31-a5b3-d575d8e709ab
Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/5fdd0aa3-8a9c-4c31-a5b3-d575d8e709ab.jsonl
Where: plugins/os-sdlc/lib/os_sdlc/runner/next_step.rb NextStep#base_terminal_payload
Filed by: user request, 2026-09-11. Design discussion originated in session d9e1984d.

## Context The user asked, in session d9e1984d, for a table at the end of every `/os-sdlc:implement` run: dispatched subagents, time, tokens, verdicts, and workflow issues. The orchestrator assembled it by hand twice. It must come from data. The pipeline database already holds per dispatch: agent type, created and completed timestamps, verdict, stalled timestamp, and per gate step a `duration_ms` (#528). Edge spends hold retry counts. Model and token data are absent from the database; that is a separate ticket (B, filed alongside). Design agreed with the user on 2026-09-11: `Implementation::Summary` owns a `DispatchSummary`, which owns the dispatch models. Objects are named by what they summarize, not by how the orchestrator draws them. No code comments. `agent_type` is the key, not `node`, because dispatches carry no node column. ## Observed `NextStep#base_terminal_payload` (plugins/os-sdlc/lib/os_sdlc/runner/next_step.rb:88-94) emits type, node, reason, session_id, implementation_id, and the ADR-0171 commit record. No run summary. ## Reproduce n/a: feature ticket ## Expected Both terminal payloads (and the escalation payload, which wraps the same base) carry `dispatches` and `totals`: `dispatches: [{id:, agent_type:, elapsed_s:, verdict:, stalled:, gate_steps: [{step:, ms:, exit:}]}]` `totals: {dispatches:, elapsed_s:, retries:}` ## Tasks - [ ] Add `lib/os_sdlc/runner/implementation/summary.rb`: class `OsSdlc::Runner::Implementation::Summary` with `self.for(implementation) = new(implementation).to_h`; `initialize` stores the implementation and builds `DispatchSummary.new(implementation.dispatches)`; `to_h` returns `{ dispatches: @dispatch_summary.to_a, totals: totals }`; private `totals` = `{ dispatches: @dispatch_summary.count, elapsed_s:, retries: }`; `elapsed_s` = `DispatchSummary.seconds_between(implementation.created_at, last_stamp)` where `last_stamp` = `completed_at || failed_at || escalated_at`; `retries` = `implementation.edge_spends.sum { |spend| spend.spent.to_i }`. - [ ] Add `lib/os_sdlc/runner/implementation/dispatch_summary.rb`: class `DispatchSummary`; `self.seconds_between(from, to)` returns nil unless both, else `(to.to_time - from.to_time).round(1)`; `initialize(dispatches)` stores `dispatches.sort_by(&:id)`; `count`; `to_a` maps each dispatch to `{ id:, agent_type: dispatch.agent_type, elapsed_s: seconds_between(created_at, completed_at), verdict:, stalled: dispatch.stalled?, gate_steps: }` where `gate_steps` maps `step_results.sort_by(&:id)` to `{ step:, ms: duration_ms, exit: exit_status }`. - [ ] Add `def summary = Summary.for(self)` to `Implementation::InstanceMethods` next to `latest_dispatch` (implementation.rb ~line 167). - [ ] In `base_terminal_payload`, chain `.merge(@row.summary)` onto the base hash literal, before the commit_record guard. - [ ] In `skills/implement/SKILL.md`, after the ADR-0171 commit paragraph in the `implementation_complete` section (~line 139), add one unwrapped line: `Render \`dispatches\` as a table: one row per dispatch, followed by the \`totals\` line. If you observed workflow issues, list them below the table.` - [ ] Tests: `tests/runner/implementation_summary_test.rb` (payload shape, totals count, elapsed, retries from one edge spend); `tests/runner/dispatch_summary_test.rb` (one entry's agent_type, elapsed_s, gate_steps from one recorded step); one case in `tests/runner/cli_next_instruction_test.rb` asserting the completion payload's `dispatches` length equals `totals.dispatches`. - [ ] Amend ADR-0171 with one paragraph: the terminal payload also carries the run summary; `agent_type` stands in for node because dispatches have no node column; `retries` is the sum of `edge_spends.spent`. Run `/os-adr:find` on next_step.rb first. - [ ] Refresh the plugin cache (`bin/refresh-plugins`) after SKILL.md changes. ## Acceptance criteria - [ ] A toy-map run to completion prints a payload with `dispatches` rows and a `totals` hash; the row count equals `totals.dispatches`. - [ ] Zeitwerk loads both new files without an explicit require. - [ ] No code comments in the new classes. - [ ] Full suite green. ## Out of scope Model and token data (ticket B). A `node` column on dispatches. A row cap for long runs. ## Origin Session: 5fdd0aa3-8a9c-4c31-a5b3-d575d8e709ab Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/5fdd0aa3-8a9c-4c31-a5b3-d575d8e709ab.jsonl Where: plugins/os-sdlc/lib/os_sdlc/runner/next_step.rb `NextStep#base_terminal_payload` Filed by: user request, 2026-09-11. Design discussion originated in session d9e1984d.
Author
Owner

Work started via /os-sdlc:implement on branch ticket-552 (session 5fdd0aa3).

Work started via /os-sdlc:implement on branch ticket-552 (session 5fdd0aa3).
Author
Owner

Resolution

Done: Terminal payload carries dispatches and totals via Implementation::Summary and DispatchSummary; SKILL.md render line; ADR-0171 amended; history row added.

Evidence: Commit 61995ac on branch ticket-552; full os-sdlc suite 1163 runs, 0 failures, 0 errors; rubocop clean on changed files; live escalation payload from implementation 2 rendered the summary. Pipeline deviations: implementation 1 ended on a stale-gate error (#551), implementation 2 escalated with unexpected_pass, remaining doc tasks and one bug fix (refresh cleared the commit record) finished by hand under the escalation procedure.

Follow-ups: #553 model and token usage from transcripts; #554 behavior-verifier charter red-gate rule

## Resolution **Done:** Terminal payload carries dispatches and totals via Implementation::Summary and DispatchSummary; SKILL.md render line; ADR-0171 amended; history row added. **Evidence:** Commit 61995ac on branch ticket-552; full os-sdlc suite 1163 runs, 0 failures, 0 errors; rubocop clean on changed files; live escalation payload from implementation 2 rendered the summary. Pipeline deviations: implementation 1 ended on a stale-gate error (#551), implementation 2 escalated with unexpected_pass, remaining doc tasks and one bug fix (refresh cleared the commit record) finished by hand under the escalation procedure. **Follow-ups:** #553 model and token usage from transcripts; #554 behavior-verifier charter red-gate rule
jared closed this issue 2026-09-11 14:49:30 +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#552
No description provided.