From cfa61d2e7c447e23fd56a24605caf546a7c6a3ee Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 19:33:06 +0000 Subject: [PATCH 1/4] Make a dispatched stage's output file the completion signal Issue #8: on harnesses that background subagent dispatch, the orchestrator reported the verify/explore stages as still running while their output files were already on disk, stalling the run until a human nudged it. The spec never said how a stage dispatch completes. A stage's output file on disk is now the authoritative completion signal, independent of dispatch mode (blocking/background) and site (single or parallel fan-out); a terminal-marker format also requires its VERDICT line, and an overwrite-in-place re-dispatch compares a content hash, not mtime/size. A hard rule pins that the orchestrator never reports a dispatched stage as still running without checking disk first. Rebased onto current main; completion clause trimmed to fit the 500-line SKILL.md cap. Closes #8. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014weQJCVrR4rakMvnSgMVm4 --- .claude/skills/shepherd/SKILL.md | 12 ++++++++++++ tests/test_orchestrator_contract.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index 68a76c0..493a8b2 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -128,6 +128,16 @@ Method line omitted. For stage key `K` with assignment `S`: > {role.standing} (reviewer and final-reviewer roles only; omit the line otherwise). > **Write:** `{role.writes}` in this format: {role.format}. +4. Completion signal: dispatch prefers blocking; when backgrounded, or fanning reviewers out in + parallel, `{role.writes}` on disk is the completion signal everywhere it's dispatched. File + presence is the floor; a terminal-marker format (a ledger verdict line, `VERDICT:`) isn't done + until the file carries it too — a parallel round completes only once every reviewer's file is + present **and** verdicted. An overwrite-in-place re-dispatch (architect revision, a + `success_criteria` re-run) starts with the file already present: record its content hash in + `_progress.md` at dispatch time and treat it done once a fresh hash differs — never mtime/size. + Check output files on disk before any status claim, every turn, not only at resume; no + idle-polling loop. + If the dispatched agent has no write access, it returns the artifact verbatim as its final message and the orchestrator persists it to `{role.writes}` **unchanged** — a mechanical relay, not authorship; the no-judgment-files rule is not violated. Note the relay in `_progress.md`. @@ -465,6 +475,8 @@ from step 5 (Inner loop). on-disk marker is the only approval signal. - Triage has no gate; iterate the design with the human before the gate — chat is never the record. - Verify runs on every run; the claim ledger is never empty. +- Never report a dispatched stage as still running, and never end a turn waiting on one, without + first checking its output file on disk — present and complete means done: read it and proceed. - Blindness per the role table's "do NOT read" column; judgment files are pasted, never granted; the repository itself is never blinded. - shepherd never commits `.shepherd/` paths. Run data stays ignored via the run's diff --git a/tests/test_orchestrator_contract.py b/tests/test_orchestrator_contract.py index f2431cc..664cbfe 100644 --- a/tests/test_orchestrator_contract.py +++ b/tests/test_orchestrator_contract.py @@ -198,6 +198,15 @@ def test_orchestrator_dispatches_reviewers_in_parallel(): assert "never overwrite an earlier round's files" in ORCH +def test_dispatched_stage_completion_is_disk_based(): + # A dispatched stage's output file on disk is the completion signal, independent of + # dispatch mode (background vs blocking) and dispatch site (single or parallel fan-out) — + # the orchestrator must never claim it's still waiting without checking disk first. + assert "completion signal" in ORCH + assert "output file on disk" in ORCH + assert "Never report a dispatched stage as still running" in ORCH + + def test_dispatched_stages_run_non_interactively(): assert "non-interactively" in ORCH assert "record open questions in your output file" in ORCH From 05feee1991f6ea7049e9eafaa5b616aa0015261d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 19:33:34 +0000 Subject: [PATCH 2/4] Cover the silent-stall case on top of the disk-completion signal Stacked on #12. #12 handles the file-is-on-disk-but-unnoticed case (missed notification). This adds the other direction: when the output file never appears because the dispatched agent died rather than running slowly, an absent file after several turns or a human check-in is a stall to surface and offer to re-dispatch, not another "still running" repeat. Extends #12's contract test with the new phrase. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014weQJCVrR4rakMvnSgMVm4 --- .claude/skills/shepherd/SKILL.md | 2 ++ tests/test_orchestrator_contract.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index 493a8b2..be48835 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -477,6 +477,8 @@ from step 5 (Inner loop). - Verify runs on every run; the claim ledger is never empty. - Never report a dispatched stage as still running, and never end a turn waiting on one, without first checking its output file on disk — present and complete means done: read it and proceed. + An absent file after several turns or a direct human check-in isn't "still running" either — + say so and offer to re-dispatch. - Blindness per the role table's "do NOT read" column; judgment files are pasted, never granted; the repository itself is never blinded. - shepherd never commits `.shepherd/` paths. Run data stays ignored via the run's diff --git a/tests/test_orchestrator_contract.py b/tests/test_orchestrator_contract.py index 664cbfe..240c0e0 100644 --- a/tests/test_orchestrator_contract.py +++ b/tests/test_orchestrator_contract.py @@ -201,10 +201,12 @@ def test_orchestrator_dispatches_reviewers_in_parallel(): def test_dispatched_stage_completion_is_disk_based(): # A dispatched stage's output file on disk is the completion signal, independent of # dispatch mode (background vs blocking) and dispatch site (single or parallel fan-out) — - # the orchestrator must never claim it's still waiting without checking disk first. + # the orchestrator must never claim it's still waiting without checking disk first. An + # absent file after a while is a stall to surface, not another "still running" repeat. assert "completion signal" in ORCH assert "output file on disk" in ORCH assert "Never report a dispatched stage as still running" in ORCH + assert "isn't \"still running\" either" in ORCH def test_dispatched_stages_run_non_interactively(): From 417182c768a7e3c06af28aeb1ae292e0558d6d62 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Tue, 21 Jul 2026 14:02:00 +0200 Subject: [PATCH 3/4] refactor: clarify output file handling and status reporting in SKILL.md and tests --- .claude/skills/shepherd/SKILL.md | 6 ++++-- tests/test_orchestrator_contract.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index be48835..ac02f5e 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -477,8 +477,10 @@ from step 5 (Inner loop). - Verify runs on every run; the claim ledger is never empty. - Never report a dispatched stage as still running, and never end a turn waiting on one, without first checking its output file on disk — present and complete means done: read it and proceed. - An absent file after several turns or a direct human check-in isn't "still running" either — - say so and offer to re-dispatch. + An absent or incomplete output proves neither liveness nor failure: if dispatch status exists, + report running only when explicitly active and stalled only when terminal; without a liveness + signal, report `status unknown; output not present` and offer to wait or re-dispatch — never infer + status from turn count or a human check-in. - Blindness per the role table's "do NOT read" column; judgment files are pasted, never granted; the repository itself is never blinded. - shepherd never commits `.shepherd/` paths. Run data stays ignored via the run's diff --git a/tests/test_orchestrator_contract.py b/tests/test_orchestrator_contract.py index 240c0e0..8f6592c 100644 --- a/tests/test_orchestrator_contract.py +++ b/tests/test_orchestrator_contract.py @@ -201,12 +201,15 @@ def test_orchestrator_dispatches_reviewers_in_parallel(): def test_dispatched_stage_completion_is_disk_based(): # A dispatched stage's output file on disk is the completion signal, independent of # dispatch mode (background vs blocking) and dispatch site (single or parallel fan-out) — - # the orchestrator must never claim it's still waiting without checking disk first. An - # absent file after a while is a stall to surface, not another "still running" repeat. + # the orchestrator must never claim it's still waiting without checking disk first. When + # output is absent, it reports only observable liveness and never guesses from turn count. assert "completion signal" in ORCH assert "output file on disk" in ORCH assert "Never report a dispatched stage as still running" in ORCH - assert "isn't \"still running\" either" in ORCH + assert "report running only when explicitly active" in ORCH + assert "stalled only when terminal" in ORCH + assert "status unknown; output not present" in ORCH + assert "never infer" in ORCH and "turn count or a human check-in" in ORCH def test_dispatched_stages_run_non_interactively(): From ea8a29a709f8ffc786930bf765674bef7b55e9ba Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Tue, 21 Jul 2026 14:14:53 +0200 Subject: [PATCH 4/4] Make the absent-file rule concrete instead of hedged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The silent-stall rule leaned on "if dispatch status exists ... report running only when explicitly active and stalled only when terminal" — as vague as the "after several turns" trigger it replaced (per @Pijukatel's review). Replace it with the honest, observable rule: an absent output file means only "not done" — report status unknown, offer to wait or re-dispatch, and never fabricate "still running"/"stalled" from turn count or a human check-in. Nets the skill from 500 to 499 lines and drops two now-redundant contract asserts. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/shepherd/SKILL.md | 7 +++---- tests/test_orchestrator_contract.py | 8 +++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index ac02f5e..1ccaed2 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -477,10 +477,9 @@ from step 5 (Inner loop). - Verify runs on every run; the claim ledger is never empty. - Never report a dispatched stage as still running, and never end a turn waiting on one, without first checking its output file on disk — present and complete means done: read it and proceed. - An absent or incomplete output proves neither liveness nor failure: if dispatch status exists, - report running only when explicitly active and stalled only when terminal; without a liveness - signal, report `status unknown; output not present` and offer to wait or re-dispatch — never infer - status from turn count or a human check-in. + An absent output file tells you only that the stage isn't done — not whether it's still + working or has died: report `status unknown; output not present` and offer to wait or + re-dispatch. Never infer "still running" or "stalled" from turn count or a human check-in. - Blindness per the role table's "do NOT read" column; judgment files are pasted, never granted; the repository itself is never blinded. - shepherd never commits `.shepherd/` paths. Run data stays ignored via the run's diff --git a/tests/test_orchestrator_contract.py b/tests/test_orchestrator_contract.py index 8f6592c..9d0b636 100644 --- a/tests/test_orchestrator_contract.py +++ b/tests/test_orchestrator_contract.py @@ -201,15 +201,13 @@ def test_orchestrator_dispatches_reviewers_in_parallel(): def test_dispatched_stage_completion_is_disk_based(): # A dispatched stage's output file on disk is the completion signal, independent of # dispatch mode (background vs blocking) and dispatch site (single or parallel fan-out) — - # the orchestrator must never claim it's still waiting without checking disk first. When - # output is absent, it reports only observable liveness and never guesses from turn count. + # the orchestrator must never claim it's still waiting without checking disk first. When the + # file is absent it reports the unknown honestly and never fabricates a status from turn count. assert "completion signal" in ORCH assert "output file on disk" in ORCH assert "Never report a dispatched stage as still running" in ORCH - assert "report running only when explicitly active" in ORCH - assert "stalled only when terminal" in ORCH assert "status unknown; output not present" in ORCH - assert "never infer" in ORCH and "turn count or a human check-in" in ORCH + assert 'Never infer "still running" or "stalled" from turn count or a human check-in' in ORCH def test_dispatched_stages_run_non_interactively():