orchestrator

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package orchestrator owns the agent loop: it triages incoming reports, claims work under a fenced database lease, and delegates it.

Index

Constants

This section is empty.

Variables

View Source
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

func OwnerForArea(ctx context.Context, db *sql.DB, area string) (string, error)

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

type Memory struct {
	Content    string
	SourceKind string
	SourceRef  string
	Score      float64
}

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 New

func New(db *sql.DB, log *slog.Logger, cfg Config) *Orchestrator

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)

func (*Orchestrator) TriageOne

func (o *Orchestrator) TriageOne(ctx context.Context) (bool, error)

TriageOne classifies the oldest untriaged issue. Returns false when there is nothing to do, so the caller can back off.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL