orchestrate

package
v0.0.1-alpha.2 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

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

View Source
const DefaultGritBin = "grit"

DefaultGritBin is the grit executable name resolved from PATH.

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

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

func ClampSamples(n int) (int, string)

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 makes Available report false.
	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) Claim

func (f *FakeCoordinator) Claim(_ context.Context, agent, _ string, symbols []string) (Claim, error)

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.

func (*FakeCoordinator) Release

func (f *FakeCoordinator) Release(_ context.Context, agent string) error

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.

func (Judge) Evaluate

func (j Judge) Evaluate(ctx context.Context, goal string, history []ports.Message) (Verdict, error)

Evaluate asks the judge whether goal is satisfied by the work in history. An empty goal means no stop condition is configured, so stopping is always allowed and the judge is not consulted.

type MaxMode

type MaxMode struct {
	Sampler Sampler
	Ranker  Ranker
}

MaxMode is best-of-N: sample, rank, return the winner.

func (MaxMode) Run

func (m MaxMode) Run(ctx context.Context, req ports.Request, n int) (Candidate, error)

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

func (NoCoordinator) Claim

func (NoCoordinator) Done

func (NoCoordinator) Release

type Phase

type Phase interface {
	Name() string
	Run(ctx context.Context, st *State) (string, error)
}

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

type PhaseReport struct {
	Name     string
	Attempts int
	Output   string
	Err      error
}

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.

func (Ranker) Rank

func (r Ranker) Rank(ctx context.Context, prompt string, cands []Candidate) (int, string, error)

Rank returns the index of the best candidate and the judge's rationale. Candidates that failed to generate are not offered. An unparseable or out-of-range answer is an error: silently defaulting to candidate 0 would present an unjudged answer as the judged winner.

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").

func (Report) String

func (r Report) String() string

String renders a human-readable summary.

type Result

type Result struct {
	ID     string
	Output string
	Err    error
}

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

func (r Runner) RunAll(ctx context.Context, tasks []Task) ([]Result, error)

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

func (s Sampler) Sample(ctx context.Context, req ports.Request, n int) ([]Candidate, error)

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.

func (*State) Get

func (s *State) Get(key string) (string, bool)

Get reads a hand-off value.

func (*State) Set

func (s *State) Set(key, value string)

Set publishes a hand-off value for later phases.

type Task

type Task struct {
	ID  string
	Run func(ctx context.Context, dir string) (string, error)
}

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.

func (Workflow) Run

func (w Workflow) Run(ctx context.Context, st *State) (Report, error)

Run executes the phases in order, retrying a failing phase up to MaxRetries extra attempts. The first phase to exhaust its budget aborts the workflow; later phases do not run. The returned Report is always populated, including on failure and cancellation.

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.

func (*WorktreeIsolator) Isolate

func (w *WorktreeIsolator) Isolate(ctx context.Context, id string) (string, func() error, error)

Isolate creates a detached worktree for id and returns its directory plus a release func that removes it.

Jump to

Keyboard shortcuts

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