From 26011ed33cb6cd574636ac431ba993cad5904503 Mon Sep 17 00:00:00 2001 From: kami Date: Sun, 26 Jul 2026 20:32:01 +0400 Subject: [PATCH] coordinate rotation at harness turn boundaries --- internal/herdr/adapter.go | 15 +++++++++++++++ internal/orchestrator/orchestrator.go | 6 ++++++ progress.md | 2 ++ 3 files changed, 23 insertions(+) diff --git a/internal/herdr/adapter.go b/internal/herdr/adapter.go index 9fa10f8..add68ed 100644 --- a/internal/herdr/adapter.go +++ b/internal/herdr/adapter.go @@ -13,6 +13,12 @@ type Adapter interface { Kill(context.Context, Session) error Occupancy(Session) (float64, error) } + +// TurnBoundary is optional so older herdr deployments remain usable. A true +// result means the current harness turn has ended and handoff is safe. +type TurnBoundary interface { + AtTurnBoundary(context.Context, Session) (bool, error) +} type CLIAdapter struct { Client *Client Harness string @@ -43,6 +49,15 @@ func (a CLIAdapter) Release(ctx context.Context, s Session) (string, error) { func (a CLIAdapter) Kill(ctx context.Context, s Session) error { return a.Client.Call(ctx, "pane.kill", s, nil) } +func (a CLIAdapter) AtTurnBoundary(ctx context.Context, s Session) (bool, error) { + var r struct { + Status string `json:"status"` + } + if err := a.Client.Call(ctx, "pane.status", s, &r); err != nil { + return false, err + } + return !IsBusy(r.Status), 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 4dbe4d0..33d1406 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -165,6 +165,12 @@ func (c *Coordinator) rotate(ctx context.Context, hard float64) { if err != nil || occupancy < hard { continue } + if boundary, ok := a.(herdr.TurnBoundary); ok { + atBoundary, boundaryErr := boundary.AtTurnBoundary(ctx, session) + if boundaryErr == nil && !atBoundary { + continue + } + } ref, err := a.Release(ctx, session) if err != nil { continue diff --git a/progress.md b/progress.md index 513a9be..27c2565 100644 --- a/progress.md +++ b/progress.md @@ -67,6 +67,8 @@ Quota reporting now has strict payload validation, and `router.QuotaAvailability Notification delivery now supports Telegram and ntfy fan-out for completion, failure, block, and approval events, with event cursors, bounded polling, and notify-only surface policy preserved. Configure `ORCHESTRA_TELEGRAM_BOT_TOKEN`/`ORCHESTRA_TELEGRAM_CHAT_ID` or `ORCHESTRA_NTFY_TOPIC` to enable it. +Rotation now honors an optional herdr turn-boundary probe (`pane.status`) before hard-threshold release. Adapters without the optional capability retain occupancy-based fallback behavior. + Recommended order: 1. Add the orchestration coordinator: lease → worktree → harness session → bootstrap → lifecycle events.