Documentation
¶
Overview ¶
Package orchestrator owns the agent loop: it triages incoming reports, claims work under a fenced database lease, and delegates it.
Index ¶
- Variables
- func OwnerForArea(ctx context.Context, db *sql.DB, area string) (string, error)
- type Brain
- type Claim
- type Config
- type Memory
- type Notifier
- type Orchestrator
- func (o *Orchestrator) ClaimFor(ctx context.Context, agentSlug, model string, areas []string) (*Claim, error)
- func (o *Orchestrator) Heartbeat(ctx context.Context, c *Claim) error
- func (o *Orchestrator) Implement(ctx context.Context, c *Claim, persona string, areas []string, workdir string)
- func (o *Orchestrator) RouteOne(ctx context.Context) (bool, error)
- func (o *Orchestrator) Run(ctx context.Context)
- func (o *Orchestrator) SetBrain(b Brain)
- func (o *Orchestrator) SetNotifier(n Notifier)
- func (o *Orchestrator) TriageOne(ctx context.Context) (bool, error)
Constants ¶
This section is empty.
Variables ¶
var ErrNoWork = errors.New("no claimable work")
ErrNoWork means the queue is empty or everything is taken.
Functions ¶
func OwnerForArea ¶ added in v0.1.1
OwnerForArea returns the slug of the agent whose declared areas cover `area`, or "" when nobody's do.
This is the deterministic half of routing — the same `area = ANY(areas)` predicate the claim statement (claimSQL) matches on and RouteOne's unroutable filter tests. It lives here, next to RouteOne, so exactly one place decides what "an agent owns this area" means. The wizard's plan-import pass calls this instead of growing its own matcher, because two matchers WILL drift, and then the board's assignments disagree with what claim actually hands out.
Two deliberate differences from the runtime predicates, both because this answers "who OWNS this surface" rather than "who can run right now":
- Disabled agents are eligible. The wizard assigns issues to a fleet it generated seconds ago, and generated agents land disabled until a human enables them. Filtering on enabled here would send every plan-derived issue to triage on the one run where assignment matters most. Enabled agents still win ties — an owner that can act beats one that cannot.
- Generalists (agents with no declared areas) do NOT match. In the claim statement a generalist takes anything, but that is claiming, not owning: quietly handing unowned work to whoever looks nearest is how an agent ends up editing a surface it does not own. No declared owner means "".
The slug tiebreak keeps the answer stable across a resumed run, so a re-run assigns the same owner it assigned before.
Types ¶
type Brain ¶
type Brain interface {
Recall(ctx context.Context, agentSlug, query string, limit int) ([]Memory, error)
// Writable returns the namespace this agent owns. An agent may read several
// namespaces but writes to exactly one — its own.
Writable(ctx context.Context, agentSlug string) (string, error)
Retain(ctx context.Context, ns, content, sourceKind, sourceRef string, importance float64) (string, error)
}
Brain is the seam to per-agent memory.
Without this the agents had brains that were never read from and never written to: builder_memories stayed empty run after run, so every agent rediscovered the same codebase facts and repeated the same dead ends at full model price. Recall feeds the prompt; Retain records what the run learned.
type Claim ¶
type Claim struct {
IssueID string
Number int64
Title string
Body string
Area string
Type string
Priority string
Attempt int
RunID string
Token string // the fencing token
Agent string
Model string
// MaxBudgetUSD and MaxTurns come from the agent's settings and are handed to
// the in-session guards, so the settings UI is the single source of truth.
MaxBudgetUSD float64
MaxTurns int
}
Claim is a won lease on an issue.
type Config ¶
type Config struct {
TriageModel string
TriageTimeout time.Duration
ImplementModel string
ImplementTO time.Duration
// DailyBudgetUSD across the whole fleet. Hitting it aborts and reports; it
// never degrades to a cheaper model, because escalating spend on one issue
// means the issue is mis-specified, not underfunded.
DailyBudgetUSD float64
PollInterval time.Duration
// LeaseTTL bounds how long a claim survives without a heartbeat, so a
// crashed runner's issue returns to the queue instead of being stuck.
LeaseTTL time.Duration
// OpenPR pushes the agent's branch and opens a pull request when a run
// succeeds. OFF unless the operator sets BUILDER_OPEN_PR=1.
//
// Default-off because a push is the one irreversible thing the loop does:
// it reaches a remote, can start CI, can notify reviewers, and on a public
// repository survives deletion. Everything else the agent produces lives in
// a local worktree the operator can discard. Turning this on is the
// operator's decision to make once, deliberately — never a default, and
// never inferred from gh happening to be logged in.
OpenPR bool
// PRBase is the target branch. Empty means ask the remote (DefaultBase).
PRBase string
// PRRemote defaults to origin.
PRRemote string
}
Config bounds what the loop is allowed to do. Every field has a ceiling because an unattended fleet's failure mode is spending money, not crashing.
func DefaultConfig ¶
func DefaultConfig() Config
type Memory ¶
Memory is one recalled fact. Mirrors brain.Memory's shape without importing it, so the orchestrator keeps no hard dependency on the brain package.
type Notifier ¶
type Notifier interface {
NotifyDecision(ctx context.Context, userID, issueID string, issueNumber int64, agent, question string)
}
Notifier is the seam to the realtime push. An interface rather than a concrete type so the orchestrator does not depend on the notify package — and so a deployment without notifications still runs the loop.
type Orchestrator ¶
type Orchestrator struct {
// contains filtered or unexported fields
}
func (*Orchestrator) ClaimFor ¶
func (o *Orchestrator) ClaimFor(ctx context.Context, agentSlug, model string, areas []string) (*Claim, error)
ClaimFor attempts to claim work for one agent.
func (*Orchestrator) Heartbeat ¶
func (o *Orchestrator) Heartbeat(ctx context.Context, c *Claim) error
Heartbeat extends the lease. Fenced: a zombie runner whose lease was reaped and re-claimed by someone else updates zero rows and learns it has lost.
func (*Orchestrator) Implement ¶
func (o *Orchestrator) Implement( ctx context.Context, c *Claim, persona string, areas []string, workdir string, )
Implement runs one claimed issue end to end.
The agent's verdict is treated as a HINT. Every fact acted on — files touched, lines changed, whether anything changed at all — is derived from git by the runner. An agent that believes it edited three files and actually edited thirty is precisely what the caps exist to catch, and asking the agent how much it changed would defeat them.
func (*Orchestrator) RouteOne ¶
func (o *Orchestrator) RouteOne(ctx context.Context) (bool, error)
RouteOne assigns the oldest ready, unassigned, unclaimable issue.
"Unclaimable" is the precise condition worth routing: an issue whose area no enabled agent covers. Work that some agent's areas already match needs no help — it will be claimed on this same tick.
func (*Orchestrator) Run ¶
func (o *Orchestrator) Run(ctx context.Context)
Run polls until the context is cancelled.
Opt-in only: nothing starts this unless BUILDER_RUNNER=1. A blueprint that began spending on model calls the moment someone ran `togo serve` would be indefensible.
func (*Orchestrator) SetBrain ¶
func (o *Orchestrator) SetBrain(b Brain)
func (*Orchestrator) SetNotifier ¶
func (o *Orchestrator) SetNotifier(n Notifier)