From 4364dff9c21dc5a47b32a7545dde1a83d7314185 Mon Sep 17 00:00:00 2001 From: kami Date: Sun, 26 Jul 2026 20:36:17 +0400 Subject: [PATCH] support milestone and thrash rotation signals --- internal/herdr/adapter.go | 12 ++++++++++++ internal/orchestrator/orchestrator.go | 10 ++++++++-- progress.md | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/herdr/adapter.go b/internal/herdr/adapter.go index add68ed..3926d8d 100644 --- a/internal/herdr/adapter.go +++ b/internal/herdr/adapter.go @@ -19,6 +19,9 @@ type Adapter interface { type TurnBoundary interface { AtTurnBoundary(context.Context, Session) (bool, error) } +type RotationSignal interface { + RotationSignal(context.Context, Session) (string, error) +} type CLIAdapter struct { Client *Client Harness string @@ -58,6 +61,15 @@ func (a CLIAdapter) AtTurnBoundary(ctx context.Context, s Session) (bool, error) } return !IsBusy(r.Status), nil } +func (a CLIAdapter) RotationSignal(ctx context.Context, s Session) (string, error) { + var r struct { + Reason string `json:"reason"` + } + if err := a.Client.Call(ctx, "pane.rotation_signal", s, &r); err != nil { + return "", err + } + return r.Reason, nil +} func (a CLIAdapter) Occupancy(s Session) (float64, error) { if a.Usage == nil { return 0, fmt.Errorf("adapter: usage reader required") diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 33d1406..836b018 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -161,8 +161,14 @@ func (c *Coordinator) rotate(ctx context.Context, hard float64) { if err != nil { continue } + reason := "threshold" + if signal, ok := a.(herdr.RotationSignal); ok { + if r, signalErr := signal.RotationSignal(ctx, session); signalErr == nil && r != "" { + reason = r + } + } occupancy, err := a.Occupancy(session) - if err != nil || occupancy < hard { + if err != nil || (occupancy < hard && reason == "threshold") { continue } if boundary, ok := a.(herdr.TurnBoundary); ok { @@ -178,7 +184,7 @@ func (c *Coordinator) rotate(ctx context.Context, hard float64) { if ref == "" { continue } - b, _ := json.Marshal(map[string]string{"handoff_ref": ref, "reason": "threshold"}) + b, _ := json.Marshal(map[string]string{"handoff_ref": ref, "reason": reason}) e := domain.Event{ID: domain.NewID(), Type: "TaskReleased", TaskID: taskID, Version: task.Version + 1, Payload: b} if c.Store.Append(e) == nil { c.mu.Lock() diff --git a/progress.md b/progress.md index 301212a..a3cdea3 100644 --- a/progress.md +++ b/progress.md @@ -75,6 +75,8 @@ Configured herdr `quota_limit` values are now wired into router availability usi Quota/standup scheduling now treats `QuotaReported` and `StandupAdvisory` as global events, adds `/v1/standup` read/request behavior, and emits one daily advisory during the 03:00 UTC safety window. Advisory contents include queued, leased, and blocked tasks. +Harness adapters now expose optional `pane.rotation_signal` support for milestone/thrash triggers. The coordinator records the returned trigger reason in the handoff release and still requires a safe turn boundary before releasing. + Recommended order: 1. Add the orchestration coordinator: lease → worktree → harness session → bootstrap → lifecycle events.