feat(orchestrator): soft occupancy threshold requests handoff early (S11 partial)

Coordinator.Soft (default 0.55, ORCHESTRA_OCCUPANCY_SOFT) makes rotate()
and TurnDecision request a handoff advisory-only once occupancy crosses
the soft threshold, well before Hard forces a release/rotation.

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 23:40:46 +04:00
parent 8b4955a687
commit 84d09ce114
5 changed files with 116 additions and 4 deletions
+28
View File
@@ -882,6 +882,34 @@ and `TaskAmended` in `store.apply` now populate/update all four fields
as amendable. Covered by `TestTaskAmendedAppliesAllFields`
(`internal/store/store_test.go`).
## S11 — partial fix, 2026-07-27 (soft threshold)
Of S11's four missing pieces (soft ~55% threshold, milestone rotation, thrash
detection, agent-initiated `ROTATE`), only the first is landed here.
`Coordinator` gained a `Soft float64` field (default 0.55 via `soft()` when
unset, configurable through `ORCHESTRA_OCCUPANCY_SOFT`). Both `rotate()` (the
periodic ticker) and `TurnDecision` (the synchronous per-turn path) now check
occupancy against `Soft` before `Hard`: at or above soft but below hard, they
call the adapter's `HandoffRequester.RequestHandoff` once (same
"ask, don't invent" convention as the hard-threshold path,
`Session.HandoffRequested` guarding re-prompts) and — for `TurnDecision` —
return `prepare_handoff` without requiring a turn boundary, since this is
advisory: the agent keeps working, the task stays leased. Only once occupancy
clears `Hard` does the existing boundary-check/release path run. Covered by
`TestTurnDecision/"prepare_handoff at soft threshold, below hard, without a
turn boundary"` (internal/orchestrator/rotation_test.go), asserting a
handoff request fires and the task remains `StateLeased`.
**Still open from S11:** milestone rotation, thrash detection (N failed test
runs / same file M times / identical tool calls as a circuit breaker with
`reason=thrash` and populated `dead_ends`), and agent-initiated `ROTATE`. All
three need either transcript/tool-call introspection this repo doesn't yet
have a source for, or an explicit in-band signal from the agent — bigger than
a threshold comparison, not attempted this pass.
`go build ./...`, `go vet ./...`, `go test ./...` all pass.
### Design consequences (not yet implemented)
1. **Percentages are a level, not a delta.**