Documentation
¶
Overview ¶
Package orchestrate implements subagents, the Go-native Workflow engine, and goal-driven stopping (ADR-0006, ADR-0002).
Subagents fan out in parallel, each optionally in its own isolated working directory (a git worktree in production, a fake in tests) behind the Isolator port. Results merge back in input order so a fan-out is reproducible regardless of completion timing.
Index ¶
- Constants
- func ClampSamples(n int) (int, string)
- type Candidate
- type Claim
- type Coordinator
- type FakeCoordinator
- func (f *FakeCoordinator) Available() bool
- func (f *FakeCoordinator) Claim(_ context.Context, agent, _ string, symbols []string) (Claim, error)
- func (f *FakeCoordinator) Done(_ context.Context, agent string) error
- func (f *FakeCoordinator) Holder(symbol string) string
- func (f *FakeCoordinator) Merged(agent string) bool
- func (f *FakeCoordinator) Release(_ context.Context, agent string) error
- type GritCoordinator
- type Isolator
- type Judge
- type MaxMode
- type NoCoordinator
- type Phase
- type PhaseReport
- type Ranker
- type Report
- type Result
- type Runner
- type Sampler
- type State
- type Task
- type Verdict
- type Workflow
- type WorktreeIsolator
Constants ¶
const DefaultGritBin = "grit"
DefaultGritBin is the grit executable name resolved from PATH.
const DefaultSamples = 3
DefaultSamples is the N used when the caller names none. Three is enough for a judge to have a real choice without tripling cost for no reason.
const MaxSamples = 8
MaxSamples caps N. Each sample is a full generation the user pays for, so the ceiling is explicit rather than left to whatever number gets typed.
Variables ¶
This section is empty.
Functions ¶
func ClampSamples ¶
ClampSamples resolves a requested N to the effective one, returning a non-empty note when the request was changed. A note means the user must be told: silently running fewer samples than asked for would misrepresent what they got.
Types ¶
type Candidate ¶
type Candidate struct {
// Index is the candidate's stable position in the sample set.
Index int
// Text is the generated answer.
Text string
// Rationale is the judge's reason for choosing this candidate. Set only on
// the winner returned by MaxMode.Run.
Rationale string
// Err is this generation's own failure.
Err error
}
Candidate is one sampled answer. Err records that this generation failed, in which case Text is empty and the candidate is not offered to the judge — siblings are unaffected.
type Claim ¶
type Claim struct {
// Granted reports whether the agent may proceed.
Granted bool
// Dir is the worktree the agent should work in (granted claims only).
Dir string
// BlockedSymbol and BlockedBy name why a claim was refused, so the report can
// say who holds what rather than just "blocked".
BlockedSymbol string
BlockedBy string
}
Claim is the outcome of a claim attempt.
type Coordinator ¶
type Coordinator interface {
// Available reports whether coordination can be used at all.
Available() bool
// Claim locks symbols for agent. A refusal (someone else holds a symbol) is
// reported in the Claim, not as an error; an error means coordination itself
// failed.
Claim(ctx context.Context, agent, intent string, symbols []string) (Claim, error)
// Done merges the agent's work and releases its locks.
Done(ctx context.Context, agent string) error
// Release frees the agent's locks without claiming its work was merged. It is
// the failure path: a crashed agent must not hold locks forever.
Release(ctx context.Context, agent string) error
}
Coordinator locks the code symbols an agent intends to edit, before it edits them, so parallel agents cannot produce conflicting changes (change 0012).
The model is grit's: claim → work → done. Claiming AST symbols rather than files means two agents editing different functions in the same file both proceed — the case raw git fails on. Conflicts are prevented at claim time instead of detected at merge time.
The port exists because the reference implementation (grit) is an external Rust binary. OpenPlus is a cgo-free Go binary and must run without it, so the core depends on this interface and an absent tool is a reportable state rather than a build dependency.
type FakeCoordinator ¶
type FakeCoordinator struct {
Unavailable bool
// ClaimErr, when set, makes Claim fail — the hard-error path, distinct from a
// refused claim.
ClaimErr error
// contains filtered or unexported fields
}
FakeCoordinator is an in-memory Coordinator for tests: symbol locking without a binary, a repository, or a filesystem.
func NewFakeCoordinator ¶
func NewFakeCoordinator() *FakeCoordinator
func (*FakeCoordinator) Available ¶
func (f *FakeCoordinator) Available() bool
func (*FakeCoordinator) Done ¶
func (f *FakeCoordinator) Done(_ context.Context, agent string) error
func (*FakeCoordinator) Holder ¶
func (f *FakeCoordinator) Holder(symbol string) string
Holder reports which agent holds a symbol, or "" if none. Test inspection.
func (*FakeCoordinator) Merged ¶
func (f *FakeCoordinator) Merged(agent string) bool
Merged reports whether Done was called for an agent. Test inspection.
type GritCoordinator ¶
type GritCoordinator struct {
// RepoRoot is the repository grit coordinates.
RepoRoot string
// Bin overrides the executable name (tests, or a pinned install path).
Bin string
}
GritCoordinator is the Coordinator adapter for grit (https://github.com/rtk-ai/grit): AST-symbol locking, isolated worktrees, and serialized rebase+merge.
grit is a Rust binary, so it is shelled out to rather than imported — OpenPlus stays a cgo-free Go binary (ADR-0001) and runs with grit absent. Callers must check Available before using a coordinated path.
The adapter parses as little as possible: decisions come from exit status, and stderr is surfaced verbatim. grit's CLI is young, so a flag change should degrade to a reported error rather than a confident misparse.
func (*GritCoordinator) Available ¶
func (g *GritCoordinator) Available() bool
Available reports whether the grit binary resolves.
func (*GritCoordinator) Claim ¶
func (g *GritCoordinator) Claim(ctx context.Context, agent, intent string, symbols []string) (Claim, error)
Claim locks symbols for agent via `grit claim`.
A refusal (another agent holds a symbol) comes back as a non-granted Claim, not an error: being blocked is a normal outcome of coordination, while an error means coordination itself is broken. Conflating them would make "wait your turn" look like a malfunction.
func (*GritCoordinator) Done ¶
func (g *GritCoordinator) Done(ctx context.Context, agent string) error
Done runs `grit done`, which auto-commits, rebases, merges, and releases locks.
func (*GritCoordinator) Release ¶
func (g *GritCoordinator) Release(ctx context.Context, agent string) error
Release frees an agent's locks without merging. It is the failure path, so it is best-effort: a release that itself fails must not mask the original problem, but a stuck lock is worth reporting.
type Isolator ¶
type Isolator interface {
Isolate(ctx context.Context, id string) (dir string, release func() error, err error)
}
Isolator provides an isolated working directory for one subagent. The returned release func tears the isolation down and is always called, even when the task fails.
type Judge ¶
type Judge struct {
// Provider is the judge's model backend — independent of the agent's.
Provider ports.Provider
// Model is the judge model id ("<provider>/<model>").
Model string
}
Judge evaluates a goal / stop condition with an independent model (ADR-0006, orchestration spec "Goal / stop condition"). The agent must clear the judge before it is allowed to stop.
type MaxMode ¶
MaxMode is best-of-N: sample, rank, return the winner.
func (MaxMode) Run ¶
Run samples n candidates and returns the single best, carrying the judge's rationale. A ranking failure is an error rather than a fallback to candidate 0: without a verdict there is no "best", and presenting an arbitrary answer as the judged winner would be a lie.
n == 1 skips the judge — there is nothing to compare.
type NoCoordinator ¶
type NoCoordinator struct{}
NoCoordinator is the unconfigured coordinator: always unavailable. It exists so the uncoordinated path holds a real object rather than forcing a nil check at every call site.
func (NoCoordinator) Available ¶
func (NoCoordinator) Available() bool
type Phase ¶
Phase is one step of a workflow (ADR-0006). Run receives the shared State so a phase can read prior hand-off values and publish its own.
type PhaseReport ¶
PhaseReport records one phase's outcome.
type Ranker ¶
type Ranker struct {
// Provider is the judge's model backend — independent of the sampler's.
Provider ports.Provider
// Model is the judge model id ("<provider>/<model>").
Model string
}
Ranker asks a judge model to pick the best candidate. It is a sibling of Judge: an independent model that renders a verdict and nothing more.
type Report ¶
type Report struct {
OK bool
Phases []PhaseReport
FailedPhase string
}
Report is the workflow's structured result. On failure it stops at the phase that exhausted its retry budget, so the report shows exactly how far the workflow got (ADR-0006: "the workflow fails with a report").
type Result ¶
Result is one task's outcome. Err is the task's own failure (or an isolation failure) and never aborts sibling tasks.
type Runner ¶
type Runner struct {
// Isolator isolates each task's working directory. Nil runs in place.
Isolator Isolator
// MaxParallel bounds concurrency. Zero defaults to GOMAXPROCS.
MaxParallel int
}
Runner fans tasks out across goroutines, bounded by MaxParallel.
func (Runner) RunAll ¶
RunAll runs every task, at most MaxParallel at a time, and returns results in input order. A task's error is captured in its Result rather than aborting the fan-out: one failing subagent must not lose the others' work. RunAll itself only errors on a programming fault (a task with no Run func).
type Sampler ¶
type Sampler struct {
// Provider is the generating model backend.
Provider ports.Provider
// Runner supplies the bounded fan-out. Its Isolator is deliberately unused:
// generations do not touch the filesystem, so isolation buys nothing.
Runner Runner
}
Sampler produces N independent answers to the same request.
func (Sampler) Sample ¶
Sample runs n tool-free generations of req concurrently, bounded by the Runner's MaxParallel, and returns them in stable index order regardless of completion timing. A generation's failure is recorded on its candidate rather than aborting the set: N-1 usable answers still beat none.
type State ¶
type State struct {
// Last is the output of the most recently completed phase.
Last string
// contains filtered or unexported fields
}
State is the structured hand-off between phases: a string-keyed bag plus the previous phase's output.
type Task ¶
Task is one unit of subagent work. Run receives the isolated directory (empty when no Isolator is configured, meaning "work in place").
type Verdict ¶
type Verdict struct {
// Met reports whether the goal is satisfied. Only a clear approval sets
// this true — anything ambiguous keeps the agent working.
Met bool
// Feedback is the judge's reasoning, fed back to the agent when Met is
// false so the next turn knows what is missing.
Feedback string
}
Verdict is the judge's decision about whether the agent may stop.
type Workflow ¶
type Workflow struct {
Phases []Phase
// MaxRetries is the number of *additional* attempts per phase after the
// first. Zero means a phase runs once.
MaxRetries int
}
Workflow is an ordered set of phases with a bounded retry budget per phase (ADR-0006). JS (goja) compatibility is deferred behind this same shape.
type WorktreeIsolator ¶
type WorktreeIsolator struct {
// RepoRoot is the primary checkout the worktrees branch from.
RepoRoot string
// BaseDir is where worktree directories are created. Empty uses the OS temp
// directory.
BaseDir string
// Ref is the commit-ish each worktree checks out. Empty uses HEAD.
Ref string
// contains filtered or unexported fields
}
WorktreeIsolator isolates each subagent in its own git worktree (ADR-0006, orchestration spec: "each runs in its own git worktree and results merge back deterministically"). Edits in one worktree cannot disturb the primary checkout or a sibling subagent.