factory

package
v0.7.197 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package factory runs an implementer agent on a sidecar and loops it through review and validation until the checks pass or attempts run out.

Index

Constants

View Source
const DefaultImplementTimeout = 30 * time.Minute

DefaultImplementTimeout bounds one implementer turn. A turn writes code and may run the tests several times, so it gets longer than a review.

View Source
const LogDefault = "default"

LogDefault is the RunOptions.Log that puts the run's log at DefaultLogPath.

Variables

View Source
var ErrNothingToCheck = errors.New("nothing to check the implementer's work with")

ErrNothingToCheck means a run has neither review prompts nor validation commands, so its loop would pass whatever the implementer wrote.

Functions

func CommitMessage added in v0.7.197

func CommitMessage(prompt, runID string, o Outcome) string

CommitMessage is the work's commit message: the prompt's first line as the subject, the prompt in full below it, and how the run ended.

func DefaultLogPath added in v0.7.197

func DefaultLogPath(runID string) (string, error)

DefaultLogPath is where a run's log goes when no path is given. The run ID is already the run's start time, so it alone names the file.

func Feedback

func Feedback(checks []Check) string

Feedback renders the failed checks of a round as the implementer's next prompt, or "" when nothing failed. Errored checks are left out: they say nothing about the code, and asking the implementer to fix them would send it chasing infrastructure.

func LoadChecks added in v0.7.197

func LoadChecks(dir string, optional bool, cfg *config.ProjectConfig, noValidate bool) ([]review.Prompt, []config.Command, error)

LoadChecks loads what the implementer's work is checked with: the review prompts in dir and, unless noValidate, cfg's validation commands. With optional, a missing or empty dir means no reviews rather than an error, so validation commands alone may do.

func Members added in v0.7.197

func Members(impl *sidecar.PoolEntry, ids []string) []*sidecar.PoolEntry

Members describes every sidecar in a pool other than the implementer, the ones its tree is relayed to. ids are the pool's, once it is synced.

func Passed

func Passed(checks []Check) bool

Passed reports whether every check passed. An errored check is not a pass: a round where a review could not run has not shown the code is clean.

func PoolName added in v0.7.197

func PoolName(runID string) string

PoolName names a run's sidecar pool. Each run has a pool of its own, rather than one per project reused as `chunk review` does: every member is synced from the run's worktree and gets a baseline commit on top of it, which a later run's sync does not expect. Two runs in one project must not share sidecars either.

func PoolSize added in v0.7.197

func PoolSize(reviewers int) int

PoolSize is how many sidecars a run needs: the implementer, which runs the validation commands too, and one per reviewer.

func ReviewerCount added in v0.7.197

func ReviewerCount(requested int, prompts []review.Prompt) int

ReviewerCount is how many reviewer sidecars a run with prompts needs when asked for requested: one per prompt unless fewer are asked for, and none without prompts.

func ValidationCommands

func ValidationCommands(cmds []config.Command) []config.Command

ValidationCommands picks the configured commands the loop runs: those that check rather than fix, can run remotely, and need no template expanded against the developer's local changes.

Types

type Activity

type Activity struct {
	// Tool is the tool used, or "" for text the implementer wrote.
	Tool string
	// Detail is the tool's target (a file, a command) or the text.
	Detail string
}

Activity is one thing the implementer did, for display.

type Change

type Change struct {
	// Fingerprint identifies the content of the change, so a round in which
	// the implementer changed nothing can be detected.
	Fingerprint string
	// Stat is git's one-line summary, such as "3 files changed, 40 insertions(+)".
	Stat string
}

Change is the implementer's work so far, relative to the baseline.

func (Change) Empty

func (c Change) Empty() bool

Empty reports whether the implementer has changed nothing.

type Check

type Check struct {
	Name      string
	Kind      Kind
	Status    Status
	SidecarID string
	Duration  time.Duration
	// Feedback is what the implementer is told when the check failed.
	Feedback string
	// Error is why the check could not run, for StatusErrored.
	Error string
	// Findings are a review's findings, kept as data for display.
	Findings []review.Finding
	// Prose is a review's prose alongside its findings, kept for display.
	Prose string
	// ExitCode and Output are a validation command's exit code and the tail of
	// its output, kept for display and the run's log.
	ExitCode int
	Output   string
}

Check is the outcome of one review or validation command in one round. A review is treated like any other command run on a sidecar: it passes or fails, and a failure carries feedback for the implementer.

func FromCommand

func FromCommand(name, sidecarID string, exitCode int, output string, d time.Duration, runErr error) Check

FromCommand converts one validation command's run into a check. runErr is a failure to run the command at all, as opposed to it exiting non-zero.

func FromReview

func FromReview(r review.Result) Check

FromReview converts one review result, run with structured findings, into a check. A review fails only on findings worth changing (high or medium), the same bar the session loop uses, so the implementer is not sent round after round to polish style remarks. Lower findings are kept for display. A review that could not run errored.

type Event

type Event struct {
	Kind   EventKind
	Round  int
	Prompt string
	Turn   Turn
	Change Change
	Checks []Check
}

Event reports progress through the loop.

type EventKind

type EventKind int

EventKind identifies a step of the loop for display.

const (
	EventImplementing EventKind = iota // a turn started; Round, Prompt
	EventImplemented                   // a turn ended; Round, Turn
	EventCollected                     // the work was collected; Round, Change
	EventChecking                      // checks started; Round
	EventChecked                       // checks ended; Round, Checks
)

Event kinds, in the order a round emits them.

type Implementer

type Implementer struct {
	Exec       review.Execer
	Entry      *sidecar.PoolEntry
	Credential review.Credential
	BaseURL    string
	Model      string
	Timeout    time.Duration
	OnActivity func(Activity)
	// contains filtered or unexported fields
}

Implementer runs Claude Code on a sidecar to write code. Every turn resumes the same session, so feedback arrives with the context of the work so far.

func (*Implementer) Run

func (im *Implementer) Run(ctx context.Context, prompt string) (Turn, error)

Run sends prompt to the implementer and waits for its turn to end.

type Kind

type Kind string

Kind is what produced a check: a review prompt or a validation command.

const (
	KindReview   Kind = "review"
	KindValidate Kind = "validate"
)

Check kinds.

type Loop

type Loop struct {
	// Attempts is the most rounds to check. Each round after the first starts
	// with an implementer turn fixing the previous round's failures.
	Attempts int
	OnEvent  func(Event)
}

Loop drives the implementer through rounds of review and validation.

func (Loop) Run

func (l Loop) Run(ctx context.Context, steps Steps, prompt string) (Outcome, error)

Run implements prompt, then checks the work and feeds failures back until the checks pass, the implementer stops changing anything, or attempts run out. The returned error is for a step that could not run, not for checks that failed; the outcome so far is returned with it.

type Outcome

type Outcome struct {
	Result Result
	// Rounds is how many rounds were checked.
	Rounds int
	Change Change
	// Checks are the last round's checks.
	Checks []Check
}

Outcome is the end state of a loop.

type Relay

type Relay struct {
	// contains filtered or unexported fields
}

Relay carries the implementer's workspace to the reviewers through the run's worktree on this machine. Sidecars cannot reach each other, so the implementer's files are pulled into the worktree with rsync and pushed from there to each reviewer the same way the developer's own tree is synced.

A pull mirrors the implementer's files into the worktree with --delete, so the directory must be one chunk owns. It leaves every .git alone, nested ones included: the worktree keeps its own, and the implementer's git config and hooks never reach this machine, where git will run on the files.

func NewRelay

func NewRelay(client *circleci.Client, dir string, status iostream.StatusFunc) *Relay

NewRelay relays through dir, the run's worktree.

func (*Relay) Pull

func (r *Relay) Pull(ctx context.Context, sidecarID, repoPath string) error

Pull mirrors the workspace at repoPath on sidecarID into the worktree.

func (*Relay) Push

func (r *Relay) Push(ctx context.Context, entries []*sidecar.PoolEntry) error

Push mirrors the worktree to each entry's workspace, in parallel. It attempts every entry and reports all that failed.

type Report added in v0.7.197

type Report struct {
	RunID string
	// Worktree is the run's worktree. It is kept once the implementer has
	// started, and removed, with its branch, if the run failed before that.
	Worktree Worktree
	// Started reports whether the implementer started.
	Started bool
	// Committed reports whether the work was committed on Worktree.Branch.
	Committed bool
	Outcome   Outcome
	// KeptSidecars are the sidecars left running with KeepSidecars.
	KeptSidecars []string
	// Log is the path of the run's log, if it kept one.
	Log string
}

Report is what a run left behind.

func Run added in v0.7.197

func Run(ctx context.Context, opts RunOptions) (rep Report, err error)

Run makes the run's worktree and sidecars, runs the loop, and commits the implementer's work on the run's branch. A failure before the loop starts says which step failed; one in the loop is returned as Loop.Run returns it, with the report so far. The work is committed either way once the implementer has started, so it is not lost with the sidecars.

type Result

type Result string

Result is why the loop stopped.

const (
	// ResultPassed means every check passed.
	ResultPassed Result = "passed"
	// ResultExhausted means checks still failed when attempts ran out.
	ResultExhausted Result = "exhausted"
	// ResultStuck means the implementer changed nothing in response to
	// feedback, so another round would review the same code again.
	ResultStuck Result = "stuck"
	// ResultNoChange means the implementer's work is empty: it changed
	// nothing, or reverted everything it had changed.
	ResultNoChange Result = "no_change"
)

Loop results.

type ReviewerTree added in v0.7.197

type ReviewerTree struct {
	Round     int
	SidecarID string
	// Fingerprint is the reviewer's change; Want is the implementer's.
	Fingerprint string
	Want        string
	// Err is why the reviewer's fingerprint could not be read.
	Err error
}

ReviewerTree compares the change one reviewer is about to review with the implementer's, by fingerprint.

func (ReviewerTree) Matches added in v0.7.197

func (t ReviewerTree) Matches() bool

Matches reports whether the reviewer has the implementer's change.

type RunOptions added in v0.7.197

type RunOptions struct {
	// Root is the developer's repository root. The run's worktree is made from
	// it, and the pool's state is kept in it, where the dashboard finds it.
	Root string
	// Prompt is what the implementer is asked to do.
	Prompt string
	// Attempts is the most rounds to check.
	Attempts int
	// Reviewers is how many reviewer sidecars to run; see ReviewerCount.
	Reviewers int
	Prompts   []review.Prompt
	Commands  []config.Command

	Client *circleci.Client
	OrgID  string
	Image  string
	// KeepSidecars leaves the sidecars running when the run ends.
	KeepSidecars bool

	Credential       review.Credential
	BaseURL          string
	Model            string
	ImplementTimeout time.Duration
	ReviewTimeout    time.Duration

	// Exec runs commands on the sidecars; review.ClientExec when nil.
	Exec review.Execer

	// Log is where to keep a plain-text log of the run, with its full context
	// whatever a display of it filters out: a path, LogDefault for
	// DefaultLogPath, or "" for none.
	Log string
	// Verbose adds the review prompts, the output of commands that passed,
	// and a check each round that every reviewer has the implementer's change
	// to the log. It implies a log.
	Verbose bool

	// Status reports progress, warnings included. It must be set.
	Status iostream.StatusFunc
	// The rest report what the run is doing, and may be nil. OnStart is
	// called once the run's worktree exists.
	OnStart          func(runID string, wt Worktree)
	OnEvent          func(Event)
	OnActivity       func(Activity)
	OnReviewProgress func(review.ProgressEvent)
	OnCheck          func(Check)
}

RunOptions is one factory run. Everything that needs a person, such as picking an org or finding a credential, is settled before it.

type Sidecars

type Sidecars struct {
	Exec        review.Execer
	Implementer *Implementer
	// Acquire and Release hand out reviewer sidecars: the pool's own, with the
	// implementer's member already checked out.
	Acquire   func(context.Context) (*sidecar.PoolEntry, error)
	Release   func(*sidecar.PoolEntry)
	Reviewers []*sidecar.PoolEntry
	Relay     *Relay
	Prompts   []review.Prompt
	Review    review.Options
	Commands  []config.Command
	// OnCheck is called as each validation command finishes. Reviews report
	// their progress through Review.ProgressFn.
	OnCheck func(Check)
	// OnReviewerTree, when set, is called each round for every reviewer, in
	// Reviewers order, with whether the change it is about to review is the
	// implementer's. It is a canary for the relay; a mismatch does not stop the
	// round.
	OnReviewerTree func(ReviewerTree)
	// contains filtered or unexported fields
}

Sidecars runs the loop's steps on a pool of sidecars: the implementer writes code on the member it holds for the whole run, which also runs validation, and each review runs on another member the implementer's tree is relayed to.

func (*Sidecars) Check

func (s *Sidecars) Check(ctx context.Context, round int) ([]Check, error)

Check relays the implementer's tree to the reviewers, then runs the reviews there and validation on the implementer at the same time.

func (*Sidecars) Collect

func (s *Sidecars) Collect(ctx context.Context) (Change, error)

Collect describes the implementer's work so far.

func (*Sidecars) Implement

func (s *Sidecars) Implement(ctx context.Context, prompt string) (Turn, error)

Implement runs one implementer turn.

func (*Sidecars) Prepare

func (s *Sidecars) Prepare(ctx context.Context) error

Prepare commits the tree the pool synced to every member, the worktree's, as the baseline the implementer's work is measured against. Reviewers get one too: they are sent the implementer's files but never its .git, so `git diff HEAD` on a reviewer needs a commit of the same tree to diff against.

func (*Sidecars) Pull added in v0.7.197

func (s *Sidecars) Pull(ctx context.Context) error

Pull brings the implementer's work so far into the worktree. Check does it every round; this is for the way out, after a turn that failed or a round that stopped short of checking.

type Status

type Status string

Status is how a check came out.

const (
	// StatusPassed means the check ran and found nothing to fix.
	StatusPassed Status = "passed"
	// StatusFailed means the check ran and found something the implementer
	// must fix: review findings, or a validation command exiting non-zero.
	StatusFailed Status = "failed"
	// StatusErrored means the check could not run, such as a review that timed
	// out. It says nothing about the code, so it is not fed back.
	StatusErrored Status = "errored"
)

Check statuses.

type Steps

type Steps interface {
	// Implement runs one implementer turn with prompt.
	Implement(ctx context.Context, prompt string) (Turn, error)
	// Collect describes the implementer's work so far.
	Collect(ctx context.Context) (Change, error)
	// Check reviews and validates the implementer's work so far.
	Check(ctx context.Context, round int) ([]Check, error)
}

Steps are the operations one round of the loop is made of. Sidecars implements them against real sidecars; tests implement them in memory.

type Turn

type Turn struct {
	Summary  string
	Duration time.Duration
	CostUSD  float64
}

Turn is the outcome of one implementer turn.

type Worktree added in v0.7.197

type Worktree struct {
	Path   string
	Branch string
	// Baseline is the commit the branch starts from: the developer's HEAD, or
	// their uncommitted work committed on top of it.
	Baseline string
	// Head is the developer's HEAD when the run started. It differs from
	// Baseline when their uncommitted work was committed as the baseline.
	Head string
}

Worktree is a run's local checkout: a git worktree of the developer's repository on a branch of its own. The implementer's work is pulled into it each round, so the developer's own checkout is never touched and they can look at the work while the run goes.

func CreateWorktree added in v0.7.197

func CreateWorktree(ctx context.Context, root, path, runID string) (Worktree, error)

CreateWorktree checks out a new worktree of the repository at root into path, on the branch chunk/factory/<runID>. It starts from the developer's files as they are now: uncommitted work, untracked files included, is committed on the branch first, so it is the baseline and not part of the change under review.

func (Worktree) Commit added in v0.7.197

func (w Worktree) Commit(ctx context.Context, message string) (string, error)

Commit commits everything in the worktree to its branch and returns the commit, or "" when nothing changed. The worktree is left in place for the developer to carry on in. Hooks and signing are skipped: the run cannot answer a prompt, and the work has already been checked.

func (Worktree) Remove added in v0.7.197

func (w Worktree) Remove(ctx context.Context, root string) error

Remove deletes the worktree and its branch, for a run that ended before the implementer did anything. Nothing is lost: the baseline is the developer's own files, still in their checkout.

func (Worktree) Stat added in v0.7.197

func (w Worktree) Stat(ctx context.Context) (string, error)

Stat is git's one-line summary of the branch's work since the baseline, such as "3 files changed, 40 insertions(+)", or "" when there is none.

Jump to

Keyboard shortcuts

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