run

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package run is the run: one supervisor that launches every ready task of a plan store and owns the lifecycle from first dispatch to the root's completion. The chat and the store's other writers are not part of this package; steering reaches a run only through the store, as one more writer among the workers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LandingNote

func LandingNote(landing Landing) string

LandingNote is [landingNote] as a door outside this package reads it: the one sentence a landing answers with, so a headless door can carry the branch the landing named out to its caller in the same words the run's own page holds.

func SeatFor

func SeatFor(role string) string

SeatFor is the role-to-tier table: the crew row a task's role rides.

The run's root and every coordinator are planning work and take the mastermind row; a leaf that does the work itself takes the worker row, which is also where a task born from add or split sits; the review round reads a finished leaf against its acceptance and takes the careful work tier; and a probe is a small disposable unknown and takes the small-work row. Any other word, a role this build has not learned, or a task the store could not name — does the work, so an unknown word falls to the seat every task is born in rather than failing a task on its metadata.

func Start

func Start(ctx context.Context, spec Spec) (Outcome, Summary)

Start is the one door a caller runs a plan through: it puts the run's words on the store's root task, runs the supervisor over the store to one outcome word, and answers what came of it. The context is the run's wall — workers end with it — and nothing here needs a worker of its own: every seat, the root's included, comes from the spec's factory.

IT DOES NOT ANSWER WHILE A WORKER IT STARTED IS ALIVE: the supervisor drains every goroutine it launched before its outcome comes back here, so a caller may close its store — or the process — on the line after this returns and nothing of the run is left to write into it.

func StepsPerTask

func StepsPerTask(ctx context.Context) int

StepsPerTask answers the cap carried by a context the supervisor built, and zero when there is none — zero being the run's word for "no cap".

func WakeClause

func WakeClause(ctx context.Context) string

WakeClause answers the resume clause carried by a context the supervisor built for a wake, and "" for every other worker — the ordinary launch, whose opening carries the trajectory's own resume sentence instead.

func WithSpendBank

func WithSpendBank(ctx context.Context, bank func(float64)) context.Context

WithSpendBank returns a context that reports cumulative spend as a worker banks calls. Workers without this property remain valid and report at return.

func WithStepsPerTask

func WithStepsPerTask(ctx context.Context, steps int) context.Context

WithStepsPerTask returns a context that carries the cap a worker should hold itself to. The supervisor wraps every worker's context with it; a worker that ignores it is uncapped, not broken.

func WithWakeClause

func WithWakeClause(ctx context.Context, clause string) context.Context

WithWakeClause returns a context carrying the resume clause a woken parent's worker opens with — the clause naming every child that landed and what to do with them (internal/run's supervisor composes it). A worker that ignores it is one no wake reached, the way an empty clause is no wake at all.

Types

type BashWorker

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

BashWorker implements Worker by hosting one session agent on the bash belt for one store task. The agent is built the way the session builds a bash-belt task worker today (session.NewBeltWorker, with the task's own id as the agent name — the supervisor's claim trick, so the ownership check the finish command answers is taken against the exact name the task was claimed with); its turn loop runs until the agent ends its turn or the step cap on the context is reached, and then a Report comes back.

EVERY STEP IS RECORDED, and the trajectory is the record — nothing in the worker's transcript is. A step line is appended when the step finishes, and the worker's own ending is one more line, so a person opening the task's folder reads the run the way it happened and how it ended.

THE RESUME CLAUSE IS THE ONLY THING PORTED OF CHECKPOINT REPLAY. A worker opened on a task whose trajectory already has steps opens with the sentence the fresh worker needs — a predecessor was interrupted mid-work, and the effects it left are unannounced facts about the tree — and never replays a recorded command: the file is a record of what ran, not a checkpoint of what to run again.

THE TASK'S SPEND ROW IS WRITTEN HERE, on the model the seat was built on ([recordSpend]): the run worker's session has no graph node to charge through the ordinary plan-spend path, so this is the one writer of a run task's row, and the model it names is the model every call the worker made went out on.

THE FLAG IS THE DOOR'S. CODEAF_TASK_BELT=bash is what makes a run wire this worker at all; the seat's constructor reads the switch once and refuses without it, so with the flag unset not one byte of any prompt, belt or landing changes — nothing constructs this worker.

func NewBashWorker

func NewBashWorker(store *plandb.Store, workspace, model string, completer session.Completer) *BashWorker

NewBashWorker builds the seat the run's factory hands each claimed task to. The store is the run's own — the trajectory is appended beside it and the plandb shim is armed against it — the workspace is the run's one working copy every worker of the run shares, and the model is the seat's. The completer is the seat's provider: a test scripts it, a run hands the door's own.

func (*BashWorker) Run

func (w *BashWorker) Run(ctx context.Context, task plandb.Task) (Report, error)

Run hosts one agent's turn loop for the task until the agent ends its turn or the step cap on the context is reached, and returns a Report either way. A turn that ended cleanly reports the agent's own account of the work; a loop the cap stopped reports the steps it took and an error, because a task that ran out of steps did not finish; and a wall or a provider ending the turn ends the task with that reason.

type Landing

type Landing struct {
	Branch  string
	Changed []string
	Refused string
}

Landing is what a run's landing answers: the branch the working copy's work was committed on, the paths that commit carried, and — when the landing refused — the sentence saying why. A landing either names a branch or says what stopped it, so the two are never both empty and never both set.

func Land

func Land(ctx context.Context, store *plandb.Store, workspace, rootID string) (Landing, error)

Land commits a run's working copy onto its branch and writes the answer as a note on the root task, so the run's own page carries where its work went.

THE WORK IS THE COPY'S OWN (session.LandRunTree), because a run's workers edit through bash and leave no ledger: the tree's own status is the record. The commit message is the root task's title — the run's own name for the thing the person asked for.

A REFUSAL IS AN ANSWER, NOT A FAULT. Nothing to land is the ordinary ending of a run that only read, and it is written on the root the same way a landing is. An error is the run having no working copy to land in at all, and then there is no note to write, because there is nothing about this run to say.

type Limit

type Limit string

Limit is which bound a person set ended a run that reached it. The outcome word above is one sentence for both limits and the exit ladder keeps its one rung, so this fact is what says which limit fired, and it is carried beside the word rather than read out of it: set where the run decides the limit was reached ([Supervisor.limitHit]), read where the ending is drawn.

const (
	// LimitTime is the elapsed limit.
	LimitTime Limit = "time"
	// LimitCost is the spend ceiling.
	LimitCost Limit = "cost"
)

type Limits

type Limits struct {
	// CostUSD is what the whole run may spend. Banked calls count while a worker
	// is still working and reconcile with its final Report. When the counter has
	// reached it no new worker starts, work in flight ends, and the run ends on
	// the limit word of the outcome ladder.
	CostUSD float64
	// Elapsed is how long the whole run may remain active. When it passes,
	// workers already in flight are ended and drained, no new worker starts,
	// and the run ends on the same limit word as the cost counter.
	Elapsed time.Duration
	// StepsPerTask is handed to every worker through its context, so the loop
	// a worker hosts can cap itself without the supervisor counting its steps.
	StepsPerTask int
	// StaleAfter is how long a claim may go untouched before a pass takes it
	// over: a claimed task whose owning process has not been seen for this
	// long is released so the ready set offers it again. It is a field here
	// rather than an environment variable because it bounds how long a run
	// waits on a process that may have died, and zero takes the default
	// (defaultStaleAfter) rather than meaning "no stale claim ever".
	StaleAfter time.Duration
	// ReviewRound turns the review round on. When it is set, a work-seat leaf
	// that lands done spawns one check task under its parent — whether the leaf's
	// own `plandb done` wrote the ending or this run did — and a check whose
	// result begins "does not hold" leaves its sentence as a note on the leaf it
	// read AND adds a `fix:` task under that leaf's parent which the run waits on.
	// A `fix:` task is checked in turn, but a finding on one is a note and no
	// second fix task, so a run cannot loop. It is a bool defaulting false so
	// every caller that does not ask for it keeps the run it had — no check
	// tasks, nothing new on the plan.
	ReviewRound bool
}

Limits bound a run from the outside. Every field is optional: a CostUSD or Elapsed of zero (or less) sets no corresponding run limit, a StepsPerTask of zero hands the worker no cap, and ReviewRound's false is the run every caller had before it.

type Outcome

type Outcome string

The outcome words are the exit ladder's own (cmd/codeaf/envelope.go, the Short column every headless verb prints beside its exit codes), so a caller of this package and a reader of the process's exit code say the same thing about the same ending. The fifth rung — needed an answer — has no home here yet: nothing in the loop asks a question.

const (
	OutcomeDone       Outcome = "done"
	OutcomeCannotRun  Outcome = "could not be run at all"
	OutcomeIncomplete Outcome = "ran and did not finish"
	OutcomeLimit      Outcome = "a limit you set stopped it"
)

type Report

type Report struct {
	Result  string
	Steps   int
	USD     float64
	Waiting bool
}

Report is what a worker hands back when its task ends well. Result is the task's own account of itself and lands in the store verbatim; Steps and USD feed the run's counters, and USD in particular feeds the shared cost counter the Limits govern.

WAITING IS NOT A RESULT. A worker that called `plandb wait` has not finished its task: it parked it, the store released its claim, and it is owed a wake when a dependency or a child moves. Such a worker comes home with Waiting set and no Result, and the supervisor leaves the task open rather than writing a completion.

type Seats

type Seats struct {
	Work  string
	Plan  string
	Check string
}

Seats are the run's two resolved seats, as the door that opened the run named them: the work seat every leaf rides and the plan seat every planning task rides. They are the door's own answer — the flag, the environment and the crew it climbed (config.ResolveSeats) — carried so a task launched after the door sits in the seat the person named rather than one the profile happens to hold.

AN EMPTY SEAT IS THE DOOR HAVING NAMED NOTHING, and it falls the way an empty tier always fell: the profile's row for the task's tier, and the worker row beneath that.

Only the work and plan seats climb the ladder on their own; the check seat is the door's own three way answer (config.CheckSeat), carried here as Check: the check flag the person typed, else the plan flag they typed, else empty. A check rides the careful work tier (SeatFor), and an EMPTY Check is the door having named no check model, which falls the way an empty tier always fell: the profile's careful row, the crew's checker. The small row a probe rides has no flag on any door and is the profile's, read below.

type Spec

type Spec struct {
	// Store is the run's plan, opened by the caller and shared with the run's
	// other writers. The root task it was seeded with is the run itself.
	Store *plandb.Store
	// Workspace is the run's own working copy, carried for the worker seat
	// and the landing that follow this loop.
	Workspace string
	// Title and Brief are the run's own words. The store writes a root task's
	// title nowhere but its own open, so the title is the caller's to seed
	// there; the brief is Start's to put down — on a root opened without a
	// description it becomes the root task's description, which is the
	// assignment the root worker reads.
	Title string
	Brief string
	// Slots bounds how many workers run at once, and Limits bound the run's
	// cost and its per-task steps. Both pass through to the supervisor as
	// given.
	Slots  int
	Limits Limits
	// Factory makes the worker for every task the run dispatches. Start holds
	// no seat of its own: the root's worker comes from here like the rest.
	Factory WorkerFactory
	// OnSpend observes the reconciled cumulative run spend whenever it rises.
	OnSpend func(float64)
}

Spec is what a caller hands Start: the plan store the run lives in, the working copy its workers share, the run's own words, and the factory that resolves every task — the root's included — into the seat that runs it.

type Step

type Step struct {
	Kind string `json:"kind"`
	// Step is the step's number, counted from one over the task across its wakes.
	Step int `json:"step"`
	// Command is what the worker asked the belt to run, as the model spelled
	// it — the command a resumed worker must not repeat blind, and the one
	// address the record has for what this step was.
	Command string `json:"command"`
	// Observation is the head of what came back, cut the way the belt cuts.
	Observation string `json:"observation,omitempty"`
	// FullOutput names the file the whole output was filed in, set only when
	// the step's output was cut — the belt files it beside the worker's
	// transcript, and this is where the next reader finds it.
	FullOutput string `json:"full_output,omitempty"`
	// Writes are the plandb verbs the command ran: the store writes the step
	// made, read off the command's own words, because the verbs are the words
	// the store was addressed by.
	Writes []string `json:"writes,omitempty"`
	// Children are the ids of the tasks this step created under the task, read
	// off the store after the command rather than parsed out of its output.
	Children []string `json:"children,omitempty"`

	// NotRun is established by the engine when the ended tool event is an
	// answer the harness wrote rather than a call the belt ran. The requested
	// command and the answer remain in the record; a surface reads this fact and
	// never the answer's words. False also preserves records written before the
	// field existed.
	NotRun bool `json:"not_run,omitempty"`
	// Refused narrows NotRun to the one kind a person wants to see: AN ACTION THE
	// WORKER ATTEMPTED AND A DOOR REFUSED ([session.Event.Refused]). A NotRun
	// step without it is a correction about the form of the worker's reply, where
	// nothing was attempted on the world. The engine records the difference here
	// because the event is where an attempted action is known; a surface cannot
	// recover it from the record afterwards.
	Refused bool `json:"refused,omitempty"`

	// ExitCode is the command's own exit status when the belt ran one and the
	// recorder knew it: zero for a step that ended, the non-zero code for one
	// that failed. It is a pointer so a step written before this field existed,
	// or one whose exit is unknown, decodes to nil and is never read as a zero a
	// real exit could equal. A holds verdict rests only on a recorded zero exit.
	ExitCode *int `json:"exit_code,omitempty"`

	// The ending line's fields. Steps is the run's whole step count, Result is
	// the worker's own account of the work, and Reason is why the loop ended —
	// a turn that ended, a step cap, a wall.
	Steps  int    `json:"steps,omitempty"`
	Result string `json:"result,omitempty"`
	Reason string `json:"reason,omitempty"`

	// ExitsRecorded is stamped true by a build that records each command's
	// exit, on the OPENING line it writes before any step and on the ending
	// line; bashworker.go sets it at both. A reader uses it to tell a record
	// that observed no run, or was cut off before its ending, from one written
	// before exits were recorded: the first refuses a holds verdict that never
	// ran its checks, the second falls back to reading.
	ExitsRecorded bool `json:"exits_recorded,omitempty"`
}

Step is one line of a task's trajectory. The step lines carry the command, the observation head, the path of the whole output when the belt filed one, the plandb verbs the command ran, and the ids of the children it created; the ending line carries what the worker said last, how many steps it took and why its loop ended. The two shapes share the type and never share a line: Kind says which one a line is.

func Trajectory

func Trajectory(storeDir, id string) ([]Step, error)

Trajectory reads one task's recorded steps back, in the order they were appended. A task that has never run has no file and answers with no steps and no error — the resume road reads it as "no predecessor left anything behind". A line that will not parse is skipped, not reported: a run interrupted mid-append left a half-written last line, and the record is about effects, not about the byte the process died on.

type Summary

type Summary struct {
	Outcome Outcome
	// Result is the root's own result: what the run's last worker reported
	// when the tree finished whole, and empty whenever it did not.
	Result string
	// Limit is which bound a person set ended the run, and empty on every
	// run that did not end on one. The outcome word is the same sentence for
	// both limits; this is what tells them apart.
	Limit Limit
	// Cut is every task the run's own ending cut mid-flight, by store id: its
	// wall, its spend ceiling, or a person's stop ended the context their
	// workers ran under. A task that failed on its own before the ending is
	// not here. This is the fact a surface draws those rows with, so a row the
	// person's bound took down is never read as a fault; it is carried typed
	// and never parsed out of a stored error sentence.
	Cut     []string
	Nodes   int
	Steps   int
	USD     float64
	Seconds float64
}

Summary is what a run came to, in the figures a headless caller prints beside its exit code: the outcome word off the same ladder the envelope speaks, the root's result where a deliverable goes, the run's size — every worker launched, every step its workers reported — what they cost, and the wall the run took.

type Supervisor

type Supervisor struct {
	// Owner is the process that holds this run's claims: "<hostname>:<pid>",
	// so each claim names the process answerable for it and a claim nobody
	// touches reads stale. It is set from the running process when the
	// supervisor is built; a test sets it to stand in for a second process
	// sharing one store.
	Owner string
	// contains filtered or unexported fields
}

Supervisor is the launch loop over one plan store. It claims ready leaves as the task's own agent — the name its finish command answers to — and records the process that holds the claim beside it, so a run is owned per process: a claim a dead process left behind is released and taken over by the next one. It starts a worker for each claim under a slot bound and writes every worker's ending back into the store. The root task is the supervisor's own: no worker completes it, and it is completed once every other task in the store is terminal.

func NewSupervisor

func NewSupervisor(store *plandb.Store, workspace string, slots int, limits Limits, factory WorkerFactory) *Supervisor

NewSupervisor builds a run over store. The workspace is the run's own working copy, carried for the worker seat and the landing that follow this loop; slots bounds how many workers run at once (a slot count below one runs one at a time, which keeps a misconfigured run alive rather than dead); limits bound the run's cost and, per task, its steps.

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context) Outcome

Run drives the run to one of the outcome words and returns it. Each pass reads the ready set, claims every ready leaf whose ancestors are not cancelled, and starts one worker per claim, bounded by slots. A pass runs when a worker returns and on the idle timer, so a task added through the store while the loop waits is launched without waiting for anything else.

NO WORKER OUTLIVES ITS RUN. The word this answers is the caller's licence to close the store, land the working copy or drop the process, so every road out of the loop ends the workers still in flight through their contexts and waits for each of them before answering — [Supervisor.drain], on all four roads. The road that needs it most is the tree's completion: the run's own row is written the moment its last descendant lands, and a coordinator still in its turn is a worker the completed tree left behind, with a spend row and a trajectory ending still to write.

The run is over when the root task is terminal, when the cost limit has been crossed and every worker already launched has come home, or when the context ends. A run that ends on anything but the root's own completion leaves the store as it stands — an open root is a run a later pass can pick up — which is why the limit and the context end no store writes of their own.

type Worker

type Worker interface {
	Run(ctx context.Context, task plandb.Task) (Report, error)
}

Worker is one task's executor. The supervisor never talks to a model itself: it launches a Worker per claimed task, hands it the task as the store recorded it, and writes the answer back under the name the task was claimed with. A Worker that cannot finish returns an error and the task fails; a Worker that finishes returns a Report and its result is written.

type WorkerFactory

type WorkerFactory func(task plandb.Task) Worker

WorkerFactory is how the supervisor makes a Worker. The factory sees the task before the worker does, so a real factory resolves whatever the task carries — its role, its kind — into the seat that runs it. It is called once per launch, on the supervisor's own goroutine, and may return nil to say no seat exists for this task; the task then fails rather than hangs.

func CrewFactory

func CrewFactory(store *plandb.Store, workspace, profileDir string, seats Seats, completerFor func(model string) session.Completer) WorkerFactory

CrewFactory is the run's WorkerFactory: it seats each task in the model its role's seat names — the door's resolved seat where the door named one, the profile's tier row otherwise — and builds the bash-belt worker the run hosts it in already on that model.

THE ROLE IS READ FROM THE STORE, NOT FROM THE TASK HANDED OVER. SeatFor is handed plandb.Store.RoleOf's answer, so a leaf that split mid-work seats as a planner for its coordinating turns without a word on the task being rewritten, and the seat follows the shape as it stands at this launch.

THE PROFILE ANSWERS THE MODEL, through config.TierSeatAt, the same read a conversation and the settings sheet make; the seat it answers carries the rung it came from, but only the model travels here, because a worker is built at a path that has no surface to print the rung on. completerFor builds the seat's provider from the resolved model — a run hands the door's own, and a test records which model it was asked for.

A TIER WITH NO MODEL FALLS TO THE WORKER ROW, and a worker row that is empty too is a task no model can run: the factory answers a worker that refuses rather than one built on an empty model, and its error names the tier, because the row a person has to go and fill is the one the message says. Empty here is not the same as never held: config.TierSeatAt answers this build's default for a tier key the profile has never held and only a row CLEARED on purpose reads empty, so a fallback means somebody emptied a row rather than that the profile is old.

Jump to

Keyboard shortcuts

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