7f12c7fc37
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>
79 lines
2.7 KiB
Go
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)
|