factory

package
v0.7.196 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 17 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.

Variables

This section is empty.

Functions

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 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 Provision

func Provision(ctx context.Context, client *circleci.Client, orgID, image, repoPath string, reviewers int, status iostream.StatusFunc) (impl *sidecar.PoolEntry, revs []*sidecar.PoolEntry, err error)

Provision creates the implementer's sidecar and one per reviewer, in parallel, and waits until each accepts connections. On error, any sidecar it created is deleted.

func Teardown

func Teardown(client *circleci.Client, entries []*sidecar.PoolEntry) error

Teardown deletes sidecars, ignoring nil entries and ones already gone. It returns the deletes that failed, so the caller can name the sidecars left running.

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
}

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 a workspace from one sidecar to others through a staging copy on this machine. Sidecars cannot reach each other, so the implementer's tree is pulled here with rsync and pushed from here to each reviewer the same way the developer's own tree is synced.

The staging copy is a temporary directory the relay creates and owns: a pull mirrors the sidecar into it with --delete, so it must never be a directory anyone else keeps files in.

func NewRelay

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

NewRelay creates a relay with a fresh staging directory. Close removes it.

func (*Relay) Close

func (r *Relay) Close() error

Close removes the staging copy.

func (*Relay) Dir

func (r *Relay) Dir() string

Dir is the staging copy: the workspace as of the last Pull.

func (*Relay) Pull

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

Pull mirrors the workspace at repoPath on sidecarID into the staging copy.

func (*Relay) Push

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

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

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 Sidecars

type Sidecars struct {
	Client      *circleci.Client
	Exec        review.Execer
	Implementer *Implementer
	Reviewers   []*sidecar.PoolEntry
	Relay       *Relay
	Prompts     []review.Prompt
	Review      review.Options
	Commands    []config.Command
	Status      iostream.StatusFunc
	// OnCheck is called as each validation command finishes. Reviews report
	// their progress through Review.ProgressFn.
	OnCheck func(Check)
	// contains filtered or unexported fields
}

Sidecars runs the loop's steps on real sidecars: the implementer writes code on its own sidecar, which also runs validation, and each review runs on a reviewer sidecar the implementer's tree is relayed to.

func (*Sidecars) Check

func (s *Sidecars) Check(ctx context.Context, _ 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, workDir string) error

Prepare syncs the developer's tree at workDir to the implementer and commits it as the baseline the implementer's work is measured against.

func (*Sidecars) WritePatch

func (s *Sidecars) WritePatch(ctx context.Context, path string) (Change, error)

WritePatch collects the implementer's work and writes it, relative to the baseline, to path as a patch the developer can git apply to the tree they started from. It collects first because it also runs after a turn that failed, whose new files are not yet marked intent-to-add. Nothing is written when the change is empty.

The diff runs on the implementer's sidecar, not on a local copy: the implementer controls its .git/config and .gitattributes, whose filters and diff drivers git would otherwise run on the developer's machine.

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.

Jump to

Keyboard shortcuts

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