Show os-sdlc step failure reasons to the orchestrator and in the report #806

Open
opened 2026-09-28 19:16:03 +00:00 by jared · 2 comments
Owner

Problem

When a step fails, the orchestrator does not see why. The dispatch payload carries only node, agent and instructions. The reason goes only to the next agent's brief. The final report shows fail with no reason. The user cannot find repeated failures, information gaps between agents, or bad choices that a rule or heuristic could prevent.

Reproduce

n/a: feature ticket.

Approach

The reasons already exist:

adapter/db.rb:197-198        objections.objection, objections.expected_action
command/objection_intake.rb  implementation.add_objection(node:, raised_by_dispatch_id:, ...)
model/objection.rb:16-24     Objection#brief_lines
parser/brief/red_tests.rb    Brief::RedTests.parse(output)  # ADR-0211; becomes TestReport in #808
presenter/brief/red_test_line.rb

A review agent failure renders the objections that the failed dispatch raised. A gate failure such as suite-check renders TestReport.parse(output).lines(limit: 15) from step_results.output. TestReport comes from #808.

What the user sees:

✗ test-reviewer (629) failed
  Objection: test asserts on private method `#parse_row`
  Expected:  drive the test through public `Importer#call`

✗ suite-check (634) failed
  ImporterTest#test_skips_blank_rows [test/importer_test.rb:42]
  Expected 2 rows, got 3.

The dispatch presenter from #805 adds a failure: key when the step before this one failed. CompletionIntake already tells the orchestrator to relay next_step exactly as given, so the relay carries the block.

Required work:

  1. Add a failure presenter that renders the objections raised by the failed dispatch, or TestReport lines for a gate failure, capped at 15 lines. The behavior-verifier does not write objections rows: its fail handoff carries CORRECTION: and GAP: items (implementation 64, dispatch 665). The presenter must render those too; trace how they reach the next test-writer brief and read them from the same source.
  2. Add a failure: key to the dispatch payload when the previous step failed.
  3. Add a failures: list to Implementation::Summary#to_h, one entry for each failed dispatch, with its id, agent and first reason line.
  4. In skills/implement/SKILL.md, tell the orchestrator: after you dispatch, show the failure block to the user. In the report step, show the failures list under the table — check: present failures in plugins/os-sdlc/skills/implement/SKILL.md

Rejected: a new dispatches.reason column — objections and step_results.output already hold the reasons.
Rejected: show the failure before the dispatch — it slows the run for information the user reads later.

Acceptance criteria

  1. Given a review dispatch that failed with two objections, when the next dispatch payload is built, then failure names the failed dispatch id and agent and lists both objections with their expected actions.
  2. Given a suite-check dispatch that failed with Minitest output, when the next dispatch payload is built, then failure lists the failed test names and messages and no backtrace lines.
  3. Given gate output with more than 15 useful lines, when the failure presenter renders it, then it shows no more than 15 lines.
  4. Given a step that passed, when the next dispatch payload is built, then the payload has no failure key.
  5. Given a completed implementation with two failed dispatches, when implementation-report runs, then failures has two entries with id, agent and first reason line.

Owner sketch

module OsSdlc
  class Implementation
    class Failure
      def self.for(dispatch)
      def to_h
    end
  end
end

Non-goals

A concise lint report presenter. The raw gate_results fact for the programmer node.

Blocked by

#805: move NextStep and CompletionIntake payload rendering into presenter/.
#808: extract TestReport and LintReport.

Origin

  • Trigger: user sees fail rows in the implementation summary table with no reason
  • Improvised this session: none
  • Chain: user cannot see failure reason ← dispatch payload has no failure field ← DESIGN (pipeline/next_step.rb:82-86, presenter/implementation/dispatch_summary.rb:30-35)
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/pipeline/next_step.rb, plugins/os-sdlc/lib/os_sdlc/presenter/implementation/summary.rb
  • Session: b505a812-db25-4f85-8d52-ae7cda49eaab
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/b505a812-db25-4f85-8d52-ae7cda49eaab.jsonl
## Problem When a step fails, the orchestrator does not see why. The dispatch payload carries only `node`, `agent` and `instructions`. The reason goes only to the next agent's brief. The final report shows `fail` with no reason. The user cannot find repeated failures, information gaps between agents, or bad choices that a rule or heuristic could prevent. ## Reproduce n/a: feature ticket. ## Approach The reasons already exist: ``` adapter/db.rb:197-198 objections.objection, objections.expected_action command/objection_intake.rb implementation.add_objection(node:, raised_by_dispatch_id:, ...) model/objection.rb:16-24 Objection#brief_lines parser/brief/red_tests.rb Brief::RedTests.parse(output) # ADR-0211; becomes TestReport in #808 presenter/brief/red_test_line.rb ``` A review agent failure renders the objections that the failed dispatch raised. A gate failure such as suite-check renders `TestReport.parse(output).lines(limit: 15)` from `step_results.output`. `TestReport` comes from #808. What the user sees: ``` ✗ test-reviewer (629) failed Objection: test asserts on private method `#parse_row` Expected: drive the test through public `Importer#call` ✗ suite-check (634) failed ImporterTest#test_skips_blank_rows [test/importer_test.rb:42] Expected 2 rows, got 3. ``` The dispatch presenter from #805 adds a `failure:` key when the step before this one failed. `CompletionIntake` already tells the orchestrator to relay `next_step` exactly as given, so the relay carries the block. Required work: 1. Add a failure presenter that renders the objections raised by the failed dispatch, or `TestReport` lines for a gate failure, capped at 15 lines. The behavior-verifier does not write `objections` rows: its fail handoff carries `CORRECTION:` and `GAP:` items (implementation 64, dispatch 665). The presenter must render those too; trace how they reach the next test-writer brief and read them from the same source. 2. Add a `failure:` key to the dispatch payload when the previous step failed. 3. Add a `failures:` list to `Implementation::Summary#to_h`, one entry for each failed dispatch, with its id, agent and first reason line. 4. In `skills/implement/SKILL.md`, tell the orchestrator: after you dispatch, show the `failure` block to the user. In the report step, show the `failures` list under the table — check: present `failures` in `plugins/os-sdlc/skills/implement/SKILL.md` Rejected: a new `dispatches.reason` column — `objections` and `step_results.output` already hold the reasons. Rejected: show the failure before the dispatch — it slows the run for information the user reads later. ## Acceptance criteria 1. Given a review dispatch that failed with two objections, when the next dispatch payload is built, then `failure` names the failed dispatch id and agent and lists both objections with their expected actions. 2. Given a suite-check dispatch that failed with Minitest output, when the next dispatch payload is built, then `failure` lists the failed test names and messages and no backtrace lines. 3. Given gate output with more than 15 useful lines, when the failure presenter renders it, then it shows no more than 15 lines. 4. Given a step that passed, when the next dispatch payload is built, then the payload has no `failure` key. 5. Given a completed implementation with two failed dispatches, when `implementation-report` runs, then `failures` has two entries with id, agent and first reason line. ## Owner sketch ```ruby module OsSdlc class Implementation class Failure def self.for(dispatch) def to_h end end end ``` ## Non-goals A concise lint report presenter. The raw `gate_results` fact for the programmer node. ## Blocked by #805: move `NextStep` and `CompletionIntake` payload rendering into `presenter/`. #808: extract `TestReport` and `LintReport`. ## Origin - Trigger: user sees `fail` rows in the implementation summary table with no reason - Improvised this session: none - Chain: user cannot see failure reason ← dispatch payload has no failure field ← DESIGN (`pipeline/next_step.rb:82-86`, `presenter/implementation/dispatch_summary.rb:30-35`) - Root candidate: this ticket - Where: `plugins/os-sdlc/lib/os_sdlc/pipeline/next_step.rb`, `plugins/os-sdlc/lib/os_sdlc/presenter/implementation/summary.rb` - Session: b505a812-db25-4f85-8d52-ae7cda49eaab - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/b505a812-db25-4f85-8d52-ae7cda49eaab.jsonl
Author
Owner

Triage 2026-09-29: still valid, not a duplicate. No Failure presenter or failure: payload key exists on main. Fully specified (required work, 5 acceptance criteria, owner sketch). Blocked by #805 (open, payload rendering into presenters). Other blocker #808 is closed and merged (5dcc15c7, TestReport/LintReport). Labels: waiting (named blocker #805), enhancement, create. Move to ready-for-agent when #805 merges.

Triage 2026-09-29: still valid, not a duplicate. No Failure presenter or failure: payload key exists on main. Fully specified (required work, 5 acceptance criteria, owner sketch). Blocked by #805 (open, payload rendering into presenters). Other blocker #808 is closed and merged (5dcc15c7, TestReport/LintReport). Labels: waiting (named blocker #805), enhancement, create. Move to ready-for-agent when #805 merges.
Author
Owner

Blocker #805 closed (17d051b7); ready for agent.

Blocker #805 closed (17d051b7); ready for agent.
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#806
No description provided.