Spec: os-sdlc rebuild phase 2 — hook-driven runner tracer-bullet skeleton (ADR-0108/0109) #327
Labels
No labels
P0
P1
P2
P3
bug
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#327
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Spec: os-sdlc rebuild phase 2 — hook-driven runner tracer-bullet skeleton
Governing decisions: ADR-0108 (hook-driven runner), ADR-0109 (SQLite+Sequel state, briefs by path). Design:
docs/os-sdlc-rebuild/(architecture.md, runner.md, dispatch-hook.md, dr-port-map.md). Delta Refinery (~/dev/delta-refinery@4511438) is the template per the port map — its PORT/ADAPT/SKIP verdicts are binding. This spec supersedes the earlier PRD text of this issue; it integrates the accepted findings of the 2026-08-07 Codex second-opinion review (dispatch correlation; honest DB rationale; one stale doc claim). Rejected review items (interface freeze "premature", hook timeout sizing, named recovery CLI) stay rejected as speculative hardening.Problem Statement
The old implement pipeline burned ~20 orchestrator rounds per ticket relaying actions between an LLM and a stepper CLI, with token cost compounding per round. Phase 1 deleted it. There is currently no implement pipeline, and the replacement's load-bearing mechanism — a PostToolUse hook on the Agent tool injecting the next action as additionalContext — has never been run by anyone.
Solution
A walking skeleton with the production interfaces and a two-state toy pipeline: the orchestrator runs
intake <ticket>, dispatches test-writer, the hook fires on the subagent's return, the Runner confirms the return matches the dispatch it is waiting on, records state in SQLite, writes the programmer brief file, and injects the next dispatch; programmer returns the same way; the pipeline reachesdone. One live session proves the channel; every later state is added behind interfaces that never change again.User Stories
Implementation Decisions
New runner lives in the os-sdlc plugin alongside the kept gate CLI; the kept gate entrypoints and their 653-run suite are untouched by this phase.
Dispatch correlation (from the Codex review — a guaranteed breakage point, not hardening). PostToolUse fires on every successful Agent tool call, pipeline-related or not. When the Runner emits a dispatch it records the expected return as a row (a
dispatchestable: ticket, agent name, brief path, status pending/returned, timestamps — this is thehandoffstable ADR-0109 deferred; the correlation requirement earns it now). The hook advances state only when the completed Agent call matches the pending dispatch row (match on the strongest identity the hook payload provides — tool_use_id if present, else agent type — determined empirically during the build and recorded in dispatch-hook.md); any non-matching event, and any event when zero or more-than-one pending dispatch row exists, is a silent no-op: no state change, no rows, no injected action. A lookup that can return the wrong row is a correctness bug; refusing to act on ambiguity is the KISS totality rule, not speculation.StateMachine: ported near-verbatim from DR — constructor validation,
transition!raising on illegal edges,fail!restricted to terminal states,history.Transition table: plain data adapted from DR's LevelConfig shape — parallel hashes plus constructor
validate!, a gate/command column per row, single success terminaldone, no level plural machinery. Skeleton table is two rows: test-writer → programmer → done, with stub gates. Retry routes and their validate! rule land with the first real retry state (next phase) — not built into a table that has no retry row.Runner: one iteration per hook invocation (inverted from DR's blocking loop). Body: confirm the event matches the pending dispatch → execute the current state's gate(s) via
Open3.capture3, route onstatus.exitstatus→ record the transition row → write the next brief file → record the new pending dispatch → emit exactly one action.assert_progress!ported (raise if a matched agent return produces no state change).Persistence: per-project SQLite via Sequel; schema
tickets,transitions(append-only, DB timestamps),dispatches, andretry_counts(added with the first retry state; counters are per-(state, cause), reset only on forward exit, never keyed on object identity — DR wart). The transitions table IS StateMachine#history persisted. Gemssequel+sqlite3are accepted plugin dependencies. The honest justification for the DB is re-entrant process-boundary state plus dispatch correlation, not file-rewrite cost (ADR-0109 rationale amended per review).Runner locates its ticket by the single non-terminal ticket row for the cwd; zero or multiple rows → no-op (see correlation decision). No active-marker file, no fallback instructions, no bespoke timeout handling (rejected per ADR-0108).
Hook: PostToolUse matched on the Agent tool; contract is hook-event JSON on stdin → additionalContext JSON on stdout (or no output for the no-op path). The hook is the completion report. Registered in the plugin's hook manifest;
bin/refresh-pluginsafter landing.Intake:
intake <ticket>creates the ticket row, writes the first brief, records the first pending dispatch, prints the first action.Briefs: markdown files under the ticket's briefs directory in
.sdlc/; dispatch actions carry{agent, brief_path, optional output_file}. Rule: structured multi-writer state → rows; prose with one LLM reader → files referenced by path from rows.Action vocabulary frozen at three shapes:
dispatch {agent, brief_path, optional output_file},ask_human {question, skippable},done {report path}. The freeze is deliberate interface-first design; the live shakedown validates it before anything leans on it.SKILL.md: production interface, written now in the pseudoskill style (vault: howto/writing-pseudocode-style-skills.md — numbered phases, intent bullets with command sub-bullets, declared loop exit, branch per action type). One structural update to the style: there is no report-and-fetch tail call, because the orchestrator never fetches — actions arrive by injection. The skill body is, in essence (snippet encodes the decision; final wording at build time):
No recovery analyst, no free-text route derivation, no JSON brief representation (SKIP verdicts in the port map are binding).
Doc corrections landing with this phase: dispatch-hook.md's "a code loop can't lose its place" idempotency claim (describes the deleted in-process design) rewritten for the re-entrant reality; ADR-0109 amended for the dispatches table + honest rationale.
Testing Decisions
Good tests assert external behavior at the highest seam: given a hook event and a DB state, the emitted action and the resulting rows — never Runner internals.
Timeout.timeout.tests/directory.donesurfacing. It is the only behavior the harness cannot reach.Out of Scope
retry_counts, and their validate! rule (land with the first retry state).Artifact::StepperStateonto the new schema (bridge stays until the tickets table replaces artifact serialization).plugins/os-sdlc/CLAUDE.mdand old planning docs (separate doc pass).Further Notes
docs/os-sdlc-rebuild/dr-port-map.md; confirmed DR warts (object_id retry keys, fail-open verdict parsing, #call/#run mismatch) must not survive the port.bin/refresh-pluginsmust run before a live session can see it.PRD: os-sdlc rebuild phase 2 — hook-driven runner tracer-bullet skeleton (ADR-0108/0109)to Spec: os-sdlc rebuild phase 2 — hook-driven runner tracer-bullet skeleton (ADR-0108/0109)Body replaced with the /to-spec revision: integrates the Codex review's dispatch-correlation requirement (dispatches table, match-or-no-op hook), moves retry machinery to the first retry state, locks the SKILL.md to pseudoskill style, and records rejected review items. Prior PRD text is in the edit history.
Work started on branch sdlc-rebuild-phase2 (hook-driven runner tracer-bullet skeleton).
Skeleton implemented on branch sdlc-rebuild-phase2 (commit
038368a). 682-run suite green (29 new tests at the hook-contract seam incl. correlation no-op paths + state-divergence regression). Passed opus alignment critique, aidd-lint, and code review; fixes applied incl. namespaced subagent_type correlation and loud-stall hook behavior. Remaining before close: merge + the manual live-session shakedown (phase exit criterion).Resolution
Done: Phase 2 tracer-bullet runner skeleton shipped and hardened: SQLite/Sequel runner, two-state skeleton table, PostToolUse hook channel, intake CLI, slim implement SKILL.md; all 8 code-review findings addressed
Evidence: Merged to main (branch sdlc-rebuild-phase2, commits
038368a,c2c5125,b5ef02c); suite 684 runs / 1783 assertions / 0 failuresFollow-ups: Phase 3+ tracked as #328-#334