Skip to content

story-112: A stage can check its own outputs - #155

Merged
jerodw merged 2 commits into
mainfrom
story/story-112
Sep 7, 2026
Merged

story-112: A stage can check its own outputs#155
jerodw merged 2 commits into
mainfrom
story/story-112

Conversation

@jerodw

@jerodw jerodw commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Story

A stage can check its own outputs.

A stage found out it had left a required output unwritten only after its turn ended, when the coordinator checked and re-entered it — a whole agent invocation to produce one JSON file. In nine stories that happened six times. The check was already right; what was missing was that the stage could not run it.

The stage's prompt said it about as forcefully as prose can — "Ending your turn is how this stage ends; there is no later invocation to write them in" — and the stage ignored it anyway. Making the paragraph louder was the move this story exists to replace.

Two halves

A command a stage may run, at any point in its turn. It exits zero when every required output is present, was written by this invocation, and satisfies its declared schema, and non-zero naming what is not. The prompt is careful about its standing: running it is optional, nothing requires it, the coordinator makes the same check after the turn ends and that check is what decides. What running it buys is finding out while there is still time to act.

A Stop hook that catches what the command would have caught, registered beside the existing Bash guard and passed to a workflow stage invocation. Three properties, none of them accidental:

  • It blocks at most once per turn. When the payload reports a stop hook is already active it emits no decision whatever the outputs look like, so a stage that genuinely cannot write the file ends its turn and the existing self-route handles it exactly as before. There is no loop.
  • It fails open, the Bash guard's bias: unreadable stdin, a malformed payload, an absent baseline, an unreadable state.json and an unloadable workflow each yield no decision rather than a block. A defect in the hook stops no run.
  • It decides nothing. The coordinator's required-output, freshness and schema checks remain the authority and read nothing the hook wrote. A stage that reports itself complete and is not is still re-entered.

An invocation naming no run directory — the planner, the workflow selector, the Inspector — is given the declaration it had before, with no Stop entry in it, so none of them meets this at all.

One derivation, which was the risk worth managing

The brief warned that two spellings of "was this written freshly and does it validate" would agree today and drift, and that the drift would be silent in the worst direction: a stage told it was complete, re-entered anyway.

orchestration/output_check.py is the single answer. The coordinator imports it, the context assembler imports it to render the command into the prompt, and scripts/l5-check is a thin entry point over it. The freshness half is the subtle one — an existence check would pass on a previous attempt's artifact and tell the stage it was done, which is the exact failure this exists to prevent — and it comes from the same baseline the coordinator uses.

Evidence

  • Suite: 5274 passed, 1 skipped (exit 0)
  • Clean clone with the story committed: exit 0
  • Verification passed, no blocking issues
  • 0 retries, 0 self-routes, no correction pass, $39.58
  • 67 files changed, +1974 / −71

Notes for review

The one unverified note is a non-issue. The claim-support check flagged a story-063 sentence inside a paragraph this run touched; the sentence is not a claim this run made.

What this does not fix. A stage that ends without writing an artifact and without running the check is unchanged in the case where the hook fails open or has already blocked once. That is deliberate: the fallback is the coordinator's re-entry, which is what happens today, and story-108's split budget means it no longer costs the failure allowance.

🤖 Generated with Claude Code

https://claude.ai/code/session_019WkxmJ5ArLEaC2RDPGkbRU

@jerodw
jerodw merged commit 8f3e8f2 into main Sep 7, 2026
3 checks passed
@jerodw
jerodw deleted the story/story-112 branch September 7, 2026 22:35
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.

1 participant