fix(herdr): close Phase 4 item 2 — actually ask the agent for a handoff

Release already validated and uploaded a §6.1 handoff, but nothing ever
told the agent the .orchestra-handoff.json convention existed, so the
file it waited on never got written. rotate() now prompts the agent
once via a new optional herdr.HandoffRequester capability
(CLIAdapter.RequestHandoff) when the file is missing, and defers
Release until it appears, mirroring the .orchestra-report.md/B3 ask
pattern rather than inventing a handoff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
kami
2026-07-27 22:10:22 +04:00
parent 62bb17e05d
commit 3fe3aee5b7
6 changed files with 161 additions and 6 deletions
+25 -4
View File
@@ -717,7 +717,28 @@ cooperation):
record the deviation" as the alternative to half-implementing that. Taking
that option rather than bolting on an undesigned notification path.
**Still open from Phase 4**: item 2 (handoff production / stop-hook write of
`.orchestra-handoff.json` for non-Claude harnesses is untouched; Claude's
own stop-hook convention exists per B3/B5 but nothing yet drives Codex/
opencode to write one).
**Phase 4 item 2 — closed 2026-07-27.** The remaining gap after B5 was not
"which harness" (Release was always harness-agnostic) — it was that *nothing,
for any harness*, ever told the agent the `.orchestra-handoff.json` convention
existed. `Coordinator.rotate` (internal/orchestrator/orchestrator.go) now
checks whether the resolved adapter implements a new optional
`herdr.HandoffRequester` capability; if `HandoffFile` isn't present in the
worktree yet, it calls `RequestHandoff` once (`herdr.Session.HandoffRequested`
guards against re-prompting every tick) and skips `Release` for that tick,
leaving the lease intact — exactly the same "ask, don't invent" shape as B3's
`.orchestra-report.md` convention. `CLIAdapter.RequestHandoff`
(internal/herdr/adapter.go) sends a prompt naming the exact §6.1 JSON shape
(`meta.id`, `anchor.git_sha`/`branch`/`dirty[].{path,sha256}`) and explicitly
tells the agent not to fabricate the SHA/hashes. Once the file exists, the
existing Release path (validate → scratch-commit dirty files → upload → CAS
→ `pane.release_agent`) is unchanged. Covered by
`TestRotationRequestsHandoffBeforeReleasing`
(internal/orchestrator/rotation_test.go), which asserts `Release` is never
called while the file is absent and fires once it's written. `go build`,
`go vet`, `go test ./...` all pass.
Not attempted here (separate, deployment-level question, not a code gap):
whether Codex's/opencode's own turn-boundary mechanism actually surfaces this
in-pane prompt to the agent before it exits the way Claude Code's Stop hook
does — that's Phase 2 item 4 territory (native Face B per harness), not
Phase 4.