Files
orchestra/internal/herdr/backend.go
T
kami 7f12c7fc37 v3 workflow: intent, phases, review, submission, enforcement, burn-in
The v3 stack, previously an uncommitted working tree, plus this session's two
units and the burn-in instrument. This commit is the burn-in build identity:
coordinator and worker must both report this revision before a task is created.

Workflow (earlier sessions, uncommitted until now): human decision events and
reduction, source cursors and reconcile-before-launch, turn-boundary
reconciliation, internal/agentctx as the single renderer, ace-fca phases with
sealed artifacts, the trajectory gate, bounded grilling, independent review,
task pr enforcement, and human review reflection.

Capability restrictions at the agent boundary: an authz.Agent surface at
GatedWrite may ask and may not act. It also fixes two bugs the unit exposed --
gated surfaces could not reach the two endpoints written for them, and
RequestHumanDecision would block an unowned task while rejecting a question
from the session that did own it.

Turn-boundary reconcile-failure escalation: a streak of consecutive failures
asks the session to hand off, fenced on the lease epoch, with reconcile_failure
as a real handoff reason. The worker was dropping the coordinator's verdict on
the floor; it now acts on it.

Burn-in: herdr.WriteLaunchContext dumps the exact agentctx.Build result to
<worktree>/.orchestra/launch.md at every launch, local and federated. BURNIN.md
is the runbook. deploy/build.sh stamps both binaries from one commit.

go build, go vet and go test ./... pass, 20 packages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 18:31:20 +04:00

79 lines
2.7 KiB
Go

package herdr
import (
"context"
"time"
)
// Backend is the machine-local terminal/process seam used by a federation
// worker. Herdr remains the default implementation; tmux is a deliberately
// smaller alternative for Claude Code hosts that do not run herdr.
//
// The interface deals only in local session operations. Git checkout and
// lease ownership stay with orchestra-worker regardless of the backend.
type Backend interface {
Kind() string
Check(context.Context) error
Worktree(context.Context, string, string, string) (string, error)
StartAgent(context.Context, string, string, string, string, string) (Session, error)
Prompt(context.Context, string, string, time.Duration) error
Kill(context.Context, Session) error
AgentStatus(context.Context, Session) (string, error)
PaneCapture(context.Context, Session, string) (string, error)
SendText(context.Context, Session, string) error
SendKeys(context.Context, Session, []string) error
ReleaseAgent(context.Context, Session, string) error
}
// Kind identifies the existing JSON-RPC backend.
func (c *Client) Kind() string { return "herdr" }
// Check verifies the live protocol rather than treating an open socket as a
// healthy execution backend.
func (c *Client) Check(ctx context.Context) error { return c.CheckProtocol(ctx, "17") }
func (c *Client) Kill(ctx context.Context, s Session) error {
return c.Call(ctx, "pane.close", map[string]any{"pane_id": s.PaneID}, nil)
}
func (c *Client) AgentStatus(ctx context.Context, s Session) (string, error) {
var result map[string]any
if err := c.Call(ctx, "agent.get", map[string]any{"target": s.PaneID}, &result); err != nil {
return "", err
}
return statusFromAgentResult(result), nil
}
func (c *Client) PaneCapture(ctx context.Context, s Session, source string) (string, error) {
if source == "" {
source = "recent"
}
var result struct {
Read struct {
Text string `json:"text"`
} `json:"read"`
}
if err := c.Call(ctx, "pane.read", map[string]any{"pane_id": s.PaneID, "source": source}, &result); err != nil {
return "", err
}
return result.Read.Text, nil
}
func (c *Client) SendText(ctx context.Context, s Session, text string) error {
return c.Call(ctx, "pane.send_text", map[string]any{"pane_id": s.PaneID, "text": text}, nil)
}
func (c *Client) SendKeys(ctx context.Context, s Session, keys []string) error {
return c.Call(ctx, "pane.send_keys", map[string]any{"pane_id": s.PaneID, "keys": keys}, nil)
}
func (c *Client) ReleaseAgent(ctx context.Context, s Session, harness string) error {
return c.Call(ctx, "pane.release_agent", map[string]any{
"pane_id": s.PaneID,
"source": "herdr:" + harness,
"agent": agentForSession(s, harness),
}, nil)
}
var _ Backend = (*Client)(nil)