feat(orchestrator): agent-initiated ROTATE via handoff reason=manual (S11)
rotate() and TurnDecision now check the agent's own handoff for
meta.reason=="manual" before evaluating occupancy/turn-boundary — per
spec §5.3, that reason is itself the boundary signal ("a coherent unit
finished and the next is independent"), so it bypasses both checks and
releases immediately. Extracted the shared release-and-anchor-certify
tail into Coordinator.finishRelease so the manual path gets the same
anchor safety guarantee as the threshold path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
@@ -910,6 +910,43 @@ a threshold comparison, not attempted this pass.
|
||||
|
||||
`go build ./...`, `go vet ./...`, `go test ./...` all pass.
|
||||
|
||||
## S11 — agent-initiated ROTATE, landed 2026-07-27
|
||||
|
||||
The last of S11's four pieces that fits an in-band signal (milestone and
|
||||
thrash both need transcript/tool-call introspection this repo has no source
|
||||
for — still open). §5.3: *"agent-initiated `ROTATE` → emitted when a coherent
|
||||
unit finishes and the next is independent."* `continuity.Handoff`'s schema
|
||||
already anticipated this — `reasons["manual"]` was valid since B5 landed, but
|
||||
nothing ever checked for it.
|
||||
|
||||
New `handoffReason(worktree)` (`internal/orchestrator/orchestrator.go`) reads
|
||||
`HandoffFile` if present and returns its decoded `meta.reason`, or `""` if
|
||||
absent/invalid — never `"manual"` on a bad read, so a malformed handoff can't
|
||||
accidentally short-circuit rotation. Both `rotate()` and `TurnDecision` check
|
||||
this first: if the agent already wrote a handoff with `reason=manual`, that
|
||||
**is** the boundary signal, so occupancy and the turn-boundary probe are
|
||||
skipped entirely and release proceeds straight away with `reason: "manual"`
|
||||
in the emitted `TaskReleased`. Below that check, the existing
|
||||
threshold/soft/hard logic is unchanged.
|
||||
|
||||
Extracted the release tail (`Release` → anchor certification → `TaskReleased`
|
||||
append) shared between the threshold and manual paths in `TurnDecision` into
|
||||
`Coordinator.finishRelease`, since the manual path needed to reach the exact
|
||||
same anchor-safety logic (never emit a payload with an uncertifiable anchor)
|
||||
without going through occupancy/boundary gating first.
|
||||
|
||||
Covered by `TestTurnDecision/"manual reason bypasses occupancy and turn
|
||||
boundary"` (`internal/orchestrator/rotation_test.go`): occupancy=0,
|
||||
boundary=false (both would refuse/continue under every other path), a
|
||||
`.orchestra-handoff.json` with `reason=manual` written directly to the
|
||||
worktree, and asserts `TurnRotateNow` + `Release` invoked + task state
|
||||
`StateQueued`.
|
||||
|
||||
**Still open from S11:** milestone rotation and thrash detection — both need
|
||||
a source of transcript/tool-call data this repo doesn't have yet.
|
||||
|
||||
`go build ./...`, `go vet ./...`, `go test ./...` all pass.
|
||||
|
||||
### Design consequences (not yet implemented)
|
||||
|
||||
1. **Percentages are a level, not a delta.**
|
||||
|
||||
Reference in New Issue
Block a user