delegate

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package delegate is what codeaf needs of the programs it carries and can hand a whole task to — senior-dev first (docs/design/delegate/PROTOCOL.md): what one IS (a Go value in the build's list, internal/delegate/builtin), the command line every one of them answers (`codeaf <name> …`), the records a running one writes on its stdout and the one reader over them, the model API that is its only road to a model, the log of the conversation it holds over that road, and the launch of it as a child process that streams, stops on SIGTERM and ends with one terminal record.

"DELEGATE" IS A WORKING TITLE. Everything a person reads names the program itself — `/senior-dev`, `codeaf senior-dev`, its own manual page — and only code says delegate, where a later rename is one package move.

A PROGRAM CODEAF CARRIES IS STILL A PROGRAM APART. It is compiled into this binary, but it runs as a child process of it (`codeaf <name> run --json …`), so a crash in its engine cannot take the chat down, and it reaches a model only through the API codeaf serves it for that one run, so it never holds a key. What runs one AS A WORKER of a run — the live step, the trajectory, the spend bank — is internal/run's; nothing here knows what a task is.

THIS PACKAGE IS A LEAF ON PURPOSE. The session door lists the programs and checks a name; the run engine seats one; the command line runs one; none of them may import the others, so what they share lives here and imports none of them.

Index

Constants

View Source
const (
	ActionStage = "stage"
	ActionStep  = "step"
	ActionEnd   = "end"
)

The kinds of line the log holds: a stage record, a step record, and the terminal record's status and message.

View Source
const (
	// LandsTree is a program that edits files in the folder it is given and
	// leaves its changes there: in a git repository on a branch codeaf cut for
	// the run and left checked out, with what it left uncommitted committed
	// onto that branch when it ends (internal/session's programfolder.go).
	LandsTree = "tree"
	// LandsText is a program that changes nothing in the folder and puts its
	// answer in the terminal record's deliverable: codeaf folds the text into
	// the conversation the way a quick task's answer arrives.
	LandsText = "text"
)

The two things a program can leave behind.

View Source
const (
	EnvModelAPI   = "CODEAF_MODEL_API"
	EnvModelToken = "CODEAF_MODEL_TOKEN"
)

The model API's two names in a program's environment: the OpenAI-style base URL codeaf serves this one run, and the token that opens it and nothing else. They are the ONLY road to a model a program has.

View Source
const (
	// An autonomous senior-dev run with no person watching must stop on its own.
	DefaultSeniorDevCostUSD = 10.0
	// An autonomous senior-dev run with no person watching must stop on its own.
	DefaultSeniorDevHours = 3.0
)
View Source
const (
	// RecordHello is the first line a program writes: the protocol it speaks,
	// its name, and the stages it will move through, in order.
	RecordHello    = "hello"
	RecordStage    = "stage"
	RecordStep     = "step"
	RecordTerminal = "terminal"
)

The record types.

View Source
const (
	StatusPass    = "pass"
	StatusFail    = "fail"
	StatusBudget  = "budget-exhausted"
	StatusCrashed = "crashed"
)

The terminal statuses. The set is closed and it is senior-dev's, because senior-dev's projection of an ending onto four words was already the right one: the work stands, it does not, a ceiling stopped it, or the program itself broke.

View Source
const ActionsFile = "delegate-actions.jsonl"

ActionsFile is the log's name inside a task's record folder.

View Source
const ConversationFile = "delegate-conversation.jsonl"

ConversationFile is the log's name inside a task's record folder.

View Source
const DefaultGrace = 15 * time.Second

DefaultGrace is how long a SIGTERM has to work before SIGKILL follows. It is the job registry's own two seconds plus what a program that has to write a terminal record and close a database needs: senior-dev ships its frozen tree on the way out, and a grace that cut that short would lose the one record the whole protocol exists for.

View Source
const GuideMax = 400

GuideMax is the most bytes a program's Delegate.Guide may take. It is a paragraph a model reads on every turn of every conversation that carries the program, so it is held to what a model needs to choose the program and brief it, and the program's manual page carries the rest.

View Source
const HoldEnv = "CODEAF_PROGRAM_HOLD_FD"

HoldEnv names, in a program's environment, the descriptor its host's hold on the program's folder was handed to it on (Launch.Hold).

THE HOLD OUTLIVES A HOST THAT DIES. The hold is a flock, and a flock belongs to the open file, not to the process: shared with the program, it is let go only when the program has gone too. A host killed outright once let the next codeaf take its run's copy, commit what was in it and remove it, while the program — still inside the grace it is given to stop ([watchHost]) — was restoring its candidate and committing its last edits there.

View Source
const MainThread = "main"

MainThread is the thread a call belongs to when the program gave it no other: its one long conversation.

View Source
const ProgramFile = "delegate-program.json"

ProgramFile names, inside a task's record folder, which program the run handed its task to and the stages it said it would move through (its `hello`). The worker writes it when the hello arrives and again, whole, when the program's process is gone — then whether or not a hello ever came, so a program that died early still has its clock; the task page reads it to say whose conversation it is drawing, after the run as well as during.

View Source
const ProtocolVersion = 2

ProtocolVersion is the version `hello` carries. Both ends are this package, so it moves only when a record changes meaning, and a mismatch means the two processes are two builds.

AN OPTIONAL FIELD ADDED TO A RECORD IS NOT A NEW MEANING. A stage's `data` and a step's `tool`, `step` and `exit` arrived inside version 2: a reader that predates them ignores them as it ignores every field it does not know, and a program that does not send them is read exactly as before.

View Source
const StageDataCap = 1024

StageDataCap is the most bytes a stage record's data may take, in JSON. It is a curated copy of what the program already knows about the phase — an attempt number, a count, a verdict of its own checks — for a page to say in words, and never the program's whole account of itself, which stays on its stderr. A reader drops data past it rather than cut it, because half an object is not an object; the program is expected to have curated to it, and senior-dev does (internal/seniordev/app's stage_data.go).

Variables

View Source
var ErrHelp = flag.ErrHelp

ErrHelp is Parse's answer when the line asked for help and got it.

View Source
var ErrNoTerminal = errors.New("the program exited without a terminal record")

ErrNoTerminal is the error a launch answers when the program exited without a terminal record and was not stopped by the caller: the run did not finish in the protocol's terms, whatever the exit code said.

Functions

func AnsweredModels

func AnsweredModels(dir string) ([]string, error)

AnsweredModels is the distinct model set that actually answered this run, in first-answer order. An unfinished, refused or failed call credits nobody.

func AppendAction

func AppendAction(dir string, action Action) error

AppendAction writes one line to the log in dir, capped, in one write, making the folder when it is not there.

func AppendTurn

func AppendTurn(dir string, turn Turn) error

AppendTurn writes one turn to the log in dir, capped, in one write, making the folder when it is not there.

func ChildArgs

func ChildArgs(program Delegate, workspace, brief string, ceilings Ceilings, facts RunFacts) []string

ChildArgs is the line a host starts a program's process with, after codeaf's own executable: the name, the default command, --json, the folder, the ceilings that are set, and the brief after `--`, so no word of it can be read as a flag. Parse reads it back to the same invocation.

AN UNSET CEILING IS NOT ON THE LINE. A program handed `--max-cost 0` might read it as a ceiling of nothing; one handed no flag reads no ceiling.

The facts codeaf read about the run put the program's own flags on the line after codeaf's: Delegate.PlainFolder for a folder with no git history, and Delegate.CrewFlags for the conversation's crew.

func ChildEnv

func ChildEnv(api ModelAPI) []string

ChildEnv is the environment a program's process starts in: this process's, with every provider key and model redirection codeaf knows of taken out, and the model API's two names set.

NO PROVIDER KEY IS INHERITED BY A PROGRAM. The child itself receives only the loopback model token; the model's shell strips that token as well. A redirection left here would let a program reach a model outside the API, the one road codeaf can meter and show a person.

func ExitWord

func ExitWord(exit *int) string

ExitWord is how a command came out, in the words every program's page uses: `passes` for an exit of 0, `fails · exit N` for any other, and nothing for an action that ran no command or learned no exit.

func Help

func Help(program Delegate, out io.Writer)

Help writes a program's help: what it is, its commands, and the flags every command takes.

EVERY LINE FITS EIGHTY CELLS, the width codeaf's own help pages are held to (cmd/codeaf's helpwidth law); the build's list holds every carried program's pages to it (internal/delegate/builtin).

func KnownStatus

func KnownStatus(status string) bool

KnownStatus answers whether a terminal status is one of the four.

func RunChild

func RunChild(ctx context.Context, inv *Invocation, stdout io.Writer) string

RunChild runs a parsed invocation as the child of a host: its records go to stdout as JSON lines and its models come from the environment. It answers the status of the ending it wrote, and the caller turns that into the exit code.

EXACTLY ONE TERMINAL, ON EVERY PATH. A body that returns without writing one gets one written for it here — the context's end, the error it returned, or the plain fact that it said nothing — because a host reads a missing terminal as work that did not finish and says only that, and the reason the body knew would be lost.

AND IT ENDS WHEN ITS HOST DOES, however the host went ([watchHost]).

func WriteProgram

func WriteProgram(dir string, record ProgramRecord) error

WriteProgram writes the record, whole, making the folder when it is not there.

Types

type Action

type Action struct {
	// At is when codeaf received the record.
	At   time.Time `json:"at"`
	Kind string    `json:"kind"`
	// Stage, Status and Data are a stage's; Status is the ending's too.
	Stage  string          `json:"stage,omitempty"`
	Status string          `json:"status,omitempty"`
	Data   json.RawMessage `json:"data,omitempty"`
	// Tool, Step, Command, Observation and Exit are a step's.
	Tool        string `json:"tool,omitempty"`
	Step        string `json:"step,omitempty"`
	Command     string `json:"command,omitempty"`
	Observation string `json:"observation,omitempty"`
	Exit        *int   `json:"exit,omitempty"`
	// Added and Removed are a step's lines added and removed, when the program
	// counted them.
	Added   *int `json:"added,omitempty"`
	Removed *int `json:"removed,omitempty"`
	// Message is the ending's one sentence.
	Message string `json:"message,omitempty"`
}

Action is one line of the log.

func EndAction

func EndAction(at time.Time, t Terminal) Action

EndAction is the terminal record as the log's last line: its status and its sentence. The rest of the record is the result's, which the run keeps whole.

func ReadActions

func ReadActions(dir string, n int) ([]Action, error)

ReadActions reads the log in dir in the order it was written, and the last n lines of it (n <= 0 for all). A log that is not there is no actions and no error — a run from before the log existed, or one whose program has said nothing yet — and a line that does not parse is skipped, because a log cut mid-write is still a log.

func StageAction

func StageAction(at time.Time, record StageRecord) Action

StageAction is a stage record as a line of the log, received at at.

func StepAction

func StepAction(at time.Time, record StepRecord) Action

StepAction is a step record as a line of the log, received at at.

type ActionReader

type ActionReader func(Action) (Shown, bool)

ActionReader reads a program's action log for its page: each line, in the order the log holds them, as the words a person reads (Shown), and false for a line the page leaves out. A reader may remember the lines before — a program can only tell its model's second nudge from a retry of the first by what came earlier — so one reader reads one log, from its first line.

type Body

type Body func(ctx context.Context, host Host, args []string) error

Body is a command's work. It runs to its ending and reports through the host — the ending included, as one Host.Terminal — and answers an error only for a failure it could not put into that record itself. args is what the flags left on the line: the brief's words.

type Ceilings

type Ceilings struct {
	CostUSD float64
	Hours   float64
}

Ceilings are a run's limits. Zero is none.

func (Ceilings) Elapsed

func (c Ceilings) Elapsed() time.Duration

Elapsed is the hours as a duration, zero for none.

func (Ceilings) SeniorDev

func (c Ceilings) SeniorDev() Ceilings

SeniorDev fills absent conversation limits and caps larger ones at defaults.

func (Ceilings) SeniorDevDefaults

func (c Ceilings) SeniorDevDefaults() Ceilings

SeniorDevDefaults fills omitted shell limits while preserving explicit flags.

func (Ceilings) Summary

func (c Ceilings) Summary() string

Summary says the two ceilings as the person sees them at either start door.

func (Ceilings) TimeWord

func (c Ceilings) TimeWord() string

TimeWord spells the wall ceiling without padded zero units.

type Command

type Command struct {
	Name string
	// Usage is the shape of the line after the command's name, for its help:
	// `[flags] -- <brief>`.
	Usage   string
	Summary string
	// Bind declares the command's own flags on fs and answers its body, which
	// reads them once the line has been parsed. It is called once per
	// invocation, so the values live in the closure and never in package
	// state. codeaf's shared flags (--dir, --max-cost, --max-hours, --json) are
	// already on fs; a command may not declare them again.
	Bind func(fs *flag.FlagSet) Body
}

Command is one verb a program answers to: `codeaf <name> <command> [flags] -- <brief>`.

type Crew

type Crew struct {
	// Brain is the planning seat: the model the crew thinks hardest with.
	Brain string
	// Hands is the working seat: the model the crew does the work with.
	Hands string
	// Light is the cheap seat: summaries, and whatever needs no depth.
	Light string
	// Asked is the models the person asked this run to work with, in their
	// words' order, already resolved to ids. When it is set it is the working
	// seat in place of Hands, and a program may not swap any of it for another:
	// one it cannot use is a refusal, said before anything is spent.
	Asked []string
	// Effort is how hard the working seat is asked to think, a rung of
	// internal/effort: the one the conversation chose for this hand-off, or
	// the one written on the person's working seat. Empty leaves it to the
	// program's own default.
	Effort string
}

Crew is the models a conversation's crew seats, by what each is for, as ids on the service codeaf's model API speaks for (`vendor/model`), with no effort suffix. An empty field is a seat the crew leaves unset.

func (Crew) IsZero

func (c Crew) IsZero() bool

IsZero says the crew names no model and no effort, so no flag is owed for it.

type Delegate

type Delegate struct {
	// Name is one lowercase word with single hyphens: the chat command
	// (`/<name> <brief>`), the command line's verb (`codeaf <name>`) and the
	// word every row says out loud.
	Name string
	// Summary is one sentence saying what it does, in a person's words: the
	// command row's tail and its line in `codeaf --help`.
	Summary string
	// Guide is the program describing itself to the model that hands it work:
	// what it is for, what its brief must hold, and what it needs of its
	// folder. The conversation prints it under the program's name, where the
	// model reads which programs it can name in `via`, and says nothing about
	// the program of its own.
	//
	// THE PROGRAM OWNS WHAT IS TRUE OF IT, AND CODEAF OWNS WHAT IS TRUE OF
	// EVERY PROGRAM. The folder a program works in, where its work is left and
	// the fact that nobody can be asked anything are codeaf's mechanics, stated
	// once beside the list; a guide that restated them would be one more copy
	// to drift. A second program brings its own guide, and the conversation's
	// page never has to learn its name.
	//
	// IT RIDES EVERY REQUEST OF EVERY TURN, because the paragraph is part of
	// the conversation's fixed prefix (internal/session's prefixbudget_test.go
	// weighs it), so it is one paragraph of at most [GuideMax] bytes.
	Guide string
	// Lands is LandsTree or LandsText. Empty reads as LandsTree, because a
	// program that edits a tree is the one this was built for.
	Lands string
	// PlainFolder is the flags the default command takes to work in a folder
	// with no git history, which codeaf puts on the line itself when the folder
	// it hands a tree program is one ([ChildArgs]). Empty is a program that
	// needs no flag for it, or cannot work there and says so in its ending.
	//
	// CODEAF DECIDES, BECAUSE CODEAF KNOWS. The folder is the one the task was
	// proposed on, and whether the program works there on a branch of its own
	// is read by codeaf before the program starts: a folder with no history, or
	// in a repository at the home folder, is worked in without git, and the
	// program is told so on its line. The program says only how it is told, so
	// codeaf never has to learn its flag's name.
	PlainFolder []string
	// Notes is the folder, relative to the folder it works in, where the
	// program keeps its own records while it works: its copy of the brief, its
	// checklist, its session's database and its whole conversation with its
	// model. Empty is a program that keeps nothing there.
	//
	// THE NOTES ARE MOVED OUT OF THE FOLDER. A program works in the person's
	// folder itself, so its records were left there when it ended — 46 files
	// for one senior-dev run, a database and the full conversation among them —
	// where a `git add -A` would commit them and the next run would read them
	// as its own. codeaf moves the folder this names into the run's own record
	// folder when the run ends, unless it was there before the run began.
	Notes string
	// CrewFlags is the flags the default command takes to use the models of
	// the conversation's crew ([Crew]), which codeaf puts on the line of every
	// run it starts from a conversation. Nil is a program that picks its own
	// models whatever the crew says.
	//
	// THE PERSON'S CREW IS THE DEFAULT, AND THE PROGRAM SAYS HOW IT HEARS IT.
	// A person who set which models do the thinking and the typing expects a
	// program they hand work to to use them too, rather than a list of its
	// own they never chose; codeaf knows the crew and nothing of the program's
	// flags, so the program turns the one into the other.
	CrewFlags func(Crew) []string
	// StageWords is the word a person reads for each stage the program reports
	// (its `stage` record), keyed by the stage's own name. The task's row and
	// the line over its conversation show the word, never the name: a program's
	// stages are its machinery — senior-dev's say `agent-runtime` and
	// `router-cancellation` — and this house draws no machinery vocabulary.
	// A stage with no word leaves the word shown before it standing, so a
	// program's inner phases need not each be named. Nil shows every stage by
	// its own name, for a program that has not said.
	StageWords map[string]string
	// Present is the program's own vocabulary for its task's page: it makes a
	// reader ([ActionReader]) that turns each line of its action log
	// ([Action]) — a stage, a step or its ending — into the words a person
	// reads, under the word for the step of its process it served ([Shown]),
	// and answers false for a line the page leaves out. Nil reads every line
	// plainly ([Delegate.Reader]).
	//
	// THE PROGRAM KNOWS WHAT ITS RECORDS MEAN, AND CODEAF KNOWS HOW A PAGE IS
	// DRAWN. A stage named `submit` with `patch_files` in its data is
	// senior-dev's machinery; that it reads `handed in its work · 4 files` is
	// senior-dev's to say, once, beside the words it gives its stages. The page
	// draws whatever a program says here, and the same step word leads the
	// task's row while the program is in that step.
	Present func() ActionReader
	// Default is the command a bare brief runs: `/<name> <brief>` in the chat
	// and `codeaf <name> <brief>` in a shell. It names one of Commands.
	Default string
	// Commands is the program's own verbs, each with its own flags. codeaf owns
	// the dispatch and the flags every program shares; the program owns these.
	Commands []Command
	// Page is the name of its page in the chat's manual (internal/manual/chat):
	// what it does, how to ask it, what a run costs, where the work lands. It is
	// compiled in with the rest of the manual, so the manual law's own gates
	// hold it to that.
	Page string
}

Delegate is one program this build carries. It is a value in the build's list (internal/delegate/builtin), never a file on the machine: there is nothing to install, and no version of it that differs from the codeaf it ships in.

func (Delegate) Command

func (d Delegate) Command(name string) (Command, bool)

Command finds one of the program's commands by name.

func (Delegate) LandsTree

func (d Delegate) LandsTree() bool

LandsTree answers whether this program's work is a tree to land, which is the reading of an empty Lands too.

func (Delegate) Reader

func (d Delegate) Reader() ActionReader

Reader is a fresh reader of this program's action log: through the program's own vocabulary when it has one (Delegate.Present), and plainly otherwise — a stage as its name and status, a step as its command with its step id as the step's word and its exit as the outcome, the ending as its sentence. A line with no words is left out, and every line shown keeps the moment codeaf received it.

func (Delegate) Validate

func (d Delegate) Validate() error

Validate names the first thing wrong with a program's definition in a sentence the person who wrote it can act on. The build's own test runs it on every program the list carries (internal/delegate/builtin), so a definition that could not run never reaches a person.

type Emitter

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

Emitter writes a program's records on its stdout: one JSON object per line, each written whole under one lock, so two goroutines of the program can never interleave half a line of each.

THE TERMINAL IS WRITTEN AT MOST ONCE. A second is dropped here rather than sent for the reader to drop, so a program's own "and one more for luck" on its way out cannot become the record a person reads.

func NewEmitter

func NewEmitter(w io.Writer) *Emitter

NewEmitter writes to w, which for a running program is its stdout.

func (*Emitter) Ended

func (e *Emitter) Ended() bool

Ended answers whether the terminal has been written.

func (*Emitter) Err

func (e *Emitter) Err() error

Err is the first write that failed, if any. A program whose stdout is gone has nobody left to tell; the error is kept so its ending can say so.

func (*Emitter) Hello

func (e *Emitter) Hello(name string, stages []string) error

Hello writes the first record.

func (*Emitter) Stage

func (e *Emitter) Stage(stage StageRecord) error

Stage writes a phase change, with its data when it is an object the reader will keep (StageDataCap) and without it otherwise, so the record a program writes is the record that arrives.

func (*Emitter) Step

func (e *Emitter) Step(step StepRecord) error

Step writes one finished action, capped the way the reader caps it, so what the program meant to say is what arrives. The optional fields are written only when they say something.

func (*Emitter) Terminal

func (e *Emitter) Terminal(end Ending) error

Terminal writes the result, once.

type Ending

type Ending struct {
	// Status is StatusPass, StatusFail, StatusBudget or StatusCrashed.
	Status string
	// Message is one sentence saying why.
	Message string
	// CostUSD is the program's own reading of what it spent, zero for none.
	// codeaf's model API meters every call itself; this figure is kept for the
	// record and never trusted over that one.
	CostUSD float64
	// Reason is the longer reason, when there is one.
	Reason string
	// Claim is what the program's model said it did, and Observed is what the
	// program itself verified. They are two witnesses and stay two fields.
	Claim    string
	Observed string
	// Deliverable is the answer text of a program that lands text.
	Deliverable string
	// Extra is any other data the program wants on the record. It never
	// overrides a field above.
	Extra map[string]any
}

Ending is a program's result as it writes it: the terminal record, in fields rather than a map, so a program cannot misspell the one record the whole protocol exists for.

type Hello

type Hello struct {
	Protocol int      `json:"protocol"`
	Delegate string   `json:"delegate"`
	Stages   []string `json:"stages,omitempty"`
}

Hello is the first record: who is speaking, in which protocol, and the stages it will move through, which is what lets a page draw the whole track before the program has reached the end of it.

type Host

type Host interface {
	// Workspace is the folder the program works in, absolute.
	Workspace() string
	// Ceilings are the limits codeaf set for this run. The program keeps them
	// itself so it can end cleanly, and codeaf enforces them whatever it does.
	Ceilings() Ceilings
	// Hello, Stage, Step and Terminal are the records (protocol.go). Hello
	// comes first and Terminal last, once.
	Hello(stages []string)
	Stage(stage StageRecord)
	Step(step StepRecord)
	Terminal(end Ending)
	// Models is this run's model API.
	Models() ModelAPI
}

Host is what a running program asks codeaf for. Its body is handed one and reports through it: the records go to codeaf, and the models come from it.

type Invocation

type Invocation struct {
	Program Delegate
	Command Command
	// Workspace is --dir, absolute; the current folder when it was not given.
	Workspace string
	// Ceilings are --max-cost and --max-hours.
	Ceilings Ceilings
	// JSON is --json: the records on stdout instead of readable lines. A child
	// of a host always writes records, so for it the flag only says so aloud.
	JSON bool
	// Args is what the flags left: the brief's words.
	Args []string
	// Line is the arguments exactly as given after the name, so a host can hand
	// its child the same line it was handed.
	Line []string
	// ExplicitFlags records values the caller actually wrote, so a host can
	// distinguish a model pin from a program's default after parsing.
	ExplicitFlags map[string]string
	// contains filtered or unexported fields
}

Invocation is one `codeaf <name> …` line, parsed.

func Parse

func Parse(program Delegate, line []string, out io.Writer) (*Invocation, error)

Parse reads the arguments after `codeaf <name>`. The first word picks a command when it names one; otherwise the program's default command runs on the whole line, so `codeaf senior-dev fix the flaky test` is its `run`. Help (`-h`, `--help`, or `help` as the first word) is written to out and answered as ErrHelp.

func (*Invocation) Brief

func (inv *Invocation) Brief() string

Brief is the brief's words, joined.

type Launch

type Launch struct {
	// Name is the program's name, for the errors this launch writes.
	Name string
	// Bin and Args are the process: codeaf's own executable and the program's
	// line ([ChildArgs]).
	Bin  string
	Args []string
	// Env is the child's whole environment ([ChildEnv]). Nil inherits this
	// process's, which only a test wants: it would hand a program every key.
	Env []string
	// Dir is the folder the process starts in.
	Dir string
	// StderrPath is the file the program's stderr is appended to. Empty
	// discards it, which no real caller wants: stderr is where a program says
	// why it could not start.
	StderrPath string
	// Grace overrides DefaultGrace, for a test that must not wait fifteen
	// seconds for a process that ignores SIGTERM.
	Grace time.Duration
	// Hold is the file the run's hold on its folder is taken on
	// (session.ProgramFolder.Hold), which the program's process is handed as
	// well ([HoldEnv]); nil hands it nothing.
	Hold *os.File
}

Launch is one run of one program.

type ModelAPI

type ModelAPI struct {
	BaseURL string
	Token   string
}

ModelAPI is the model API codeaf serves one run: an OpenAI-style base URL and the bearer token that opens it. A program in codeaf's own tree builds its route through internal/provider, the one package codeaf's funnel law lets spell a model route; a program outside it appends the route the way every OpenAI client does.

func ModelAPIFromEnv

func ModelAPIFromEnv() (ModelAPI, bool)

ModelAPIFromEnv reads the API from this process's environment; ok is false outside a run, which is how `codeaf <name>` tells a child of a host from a person at a shell.

func (ModelAPI) Authorize

func (m ModelAPI) Authorize(req *http.Request)

Authorize puts the token on a request the program sends to the API.

func (ModelAPI) Ready

func (m ModelAPI) Ready() bool

Ready answers whether there is an API to call.

type ProgramRecord

type ProgramRecord struct {
	Name   string   `json:"name"`
	Stages []string `json:"stages,omitempty"`
	// CeilingUSD is the dollar ceiling the run handed the program, zero for
	// none. It is written here because the run works it out when it starts and
	// keeps it nowhere a page could read it afterwards, and a page that shows
	// the spend without the ceiling beside it leaves out half the reading.
	CeilingUSD float64 `json:"ceiling_usd,omitempty"`
	// StartedAt and EndedAt are the program's own clock: the instant codeaf
	// started its process and the instant that process was gone, written by
	// whoever ran it (the run's worker, or the shell verb). They are the ONE
	// record of how long the program itself ran, because every other pair of
	// times near a run brackets something else — the store is seeded before
	// the copy is cut, and the row settles after the landing. EndedAt is zero
	// while the program runs, and both are zero in a record written before
	// they existed, which a reader draws as no time rather than a wrong one.
	StartedAt time.Time `json:"started_at,omitzero"`
	EndedAt   time.Time `json:"ended_at,omitzero"`
	// Models and Effort are the models the program said it works on and how
	// hard it asks them to think ([StageRecord.Models]), so the task's page can
	// name what a run was launched on instead of leaving a person to remember
	// the flags. Both are empty until the program says, and stay empty for a
	// program that never does.
	Models []string `json:"models,omitempty"`
	Effort string   `json:"effort,omitempty"`
}

ProgramRecord is ProgramFile's content.

func ReadProgram

func ReadProgram(dir string) (ProgramRecord, bool)

ReadProgram reads the record; ok is false for a run that handed its task to no program, or whose program has neither said hello nor ended yet.

func (*ProgramRecord) Heard

func (r *ProgramRecord) Heard(stage StageRecord) bool

Heard keeps the models a stage record names (StageRecord.Models) and reports whether it changed the record, so a sink writes the record again only when there is something new in it.

type Reading

type Reading struct {
	Hello      *Hello
	LastStage  string
	LastStatus string
	Steps      int
	Terminal   *Terminal
	Ignored    int
}

Reading is what a reader saw, for the record the launch keeps: the last stage, how many steps, whether a terminal arrived, and how many lines were not the protocol's (dropped, not failed). What the run spent is not here: the model API metered it call by call, and a reading of the program's stdout is not where money is learned.

func Read

func Read(r io.Reader, sink Sink) (Reading, error)

Read consumes r to its end, telling sink each record, and answers what it saw. It returns when the stream closes, which for a pipe is when the program exits or closes stdout; an error is only a read failure on the stream itself.

type Result

type Result struct {
	Reading Reading
	// ExitCode is the process's own, -1 when it was ended by a signal or never
	// ran. The verdict is NOT read from it (§3): a program that failed its task
	// exits zero with a terminal saying `fail`.
	ExitCode int
	// Stopped is true when the caller's context ended the program: SIGTERM,
	// and SIGKILL when the grace passed. The reading may still hold a terminal
	// the program wrote inside the grace.
	Stopped bool
	// Killed is true when SIGKILL was needed.
	Killed bool
	// Elapsed is the process's wall time.
	Elapsed time.Duration
}

Result is what one launch came to.

func Run

func Run(ctx context.Context, launch Launch, sink Sink) (Result, error)

Run starts the program and reads it to its end. It returns when the process has exited and stdout is drained, so nothing of the child outlives the call.

A CONTEXT THAT ENDS ENDS THE PROGRAM, in the order the protocol promises: SIGTERM to the group, the grace, SIGKILL. The stdout reader keeps reading through the grace, so a terminal written on the way out is the reading's terminal. The error answered is the context's own, so a run supervisor that reads `context.Canceled` off a worker knows its own ending cut the task.

func (Result) ExitedAt

func (r Result) ExitedAt(started, returned time.Time) time.Time

ExitedAt is the instant the program's process was gone: the launch's own measure of the process's life laid on the instant the caller started it, and never later than returned, the instant the launch gave its answer back.

THE PROGRAM'S WALL TIME IS ITS PROCESS'S, NOT THE DRAIN'S. A launch returns only once stdout is drained, and a helper the program left holding stdout can keep that drain open for the whole grace after the program itself exited. A conversation's run and a shell run both end the program's clock here, so the same program reads the same time on every surface.

type RunFacts

type RunFacts struct {
	// Plain says the folder has no git history.
	Plain bool
	// Crew is the conversation's crew; zero for a run no conversation started.
	Crew Crew
}

RunFacts is what codeaf read about a run before it started the program, each of which puts the program's own flags for it on the line (ChildArgs).

type Said

type Said struct {
	// Role is "system", "user" or "tool", as the program sent it.
	Role string `json:"role"`
	Tool string `json:"tool,omitempty"`
	Text string `json:"text"`
}

Said is one message a program sent: whose it is and its words. A tool's result carries the tool it answers.

type Shown

type Shown struct {
	At time.Time `json:"at"`
	// Step is the word for the part of the program's process the action
	// served, printed once at the head of each run of actions in it. Empty is
	// an action inside whatever step is under way.
	Step string `json:"step,omitempty"`
	// Text is the action in words: `read internal/auth/middleware.go`.
	Text string `json:"text"`
	// Outcome is how it came out, in a word or two: `passes`, `fails · exit 2`,
	// `4 files`. Empty when there is nothing to say.
	Outcome string `json:"outcome,omitempty"`
	// Detail is the whole of the step as the log kept it — the command or
	// argument, and what came back — which the page opens under the action's
	// one line when it is clicked. Empty for a line with nothing more to show.
	Detail string `json:"detail,omitempty"`
	// Lines says the action changed a file and counted how: Added and Removed
	// are its lines added and removed, drawn as `+N,-M` in the diff's own
	// colours beside the action. False for every other action.
	Lines   bool `json:"lines,omitempty"`
	Added   int  `json:"added,omitempty"`
	Removed int  `json:"removed,omitempty"`
	// Steer marks the program steering its own model — a nudge, a last turn, a
	// retry after a dropped call, a correction — rather than working through it.
	Steer bool `json:"steer,omitempty"`
	// Memory marks the action that says the program compacted its memory, and
	// Model names the model an action says it moved to, with Reason why. The
	// page reads both beside the conversation log, which says the same two
	// things from the model's side, so one compaction or one switch is drawn
	// once.
	Memory bool   `json:"memory,omitempty"`
	Model  string `json:"model,omitempty"`
	Reason string `json:"reason,omitempty"`
}

Shown is one action as a person reads it on its program's page: the step it belongs under, the words, and how it came out. A program's own vocabulary makes it (Delegate.Present); a program with none is read plainly (Delegate.Reader).

type Sink

type Sink interface {
	// Hello is the program's first record, told once.
	Hello(h Hello)
	// Stage is a phase change: the live step, and one line of the program's
	// action log. Its data is already held to [StageDataCap].
	Stage(record StageRecord)
	// Step is one finished action: command and the observation head, both
	// already capped, and the tool, step and exit the program said.
	Step(record StepRecord)
	// Terminal is the result. It is told at most once; a second terminal on
	// the stream is ignored, because the contract says exactly one and the
	// first is the one the program wrote on purpose.
	Terminal(t Terminal)
}

Sink is what a reader tells as the stream arrives. Every method is called on the reader's goroutine, in stream order, and none may block on the program: a sink that waits on the child is a deadlock with a pipe in the middle.

type StageRecord

type StageRecord struct {
	Stage  string `json:"stage"`
	Status string `json:"status"`
	// Data is a JSON object of at most [StageDataCap] bytes, or nothing. It is
	// OPTIONAL AND ADDITIVE: a reader of version 2 that predates it reads the
	// record without it.
	Data json.RawMessage `json:"data,omitempty"`
}

StageRecord is one `stage` record: the phase the program moved to, how it stands in it, and the small copy of what it knows about it.

func (StageRecord) Models

func (s StageRecord) Models() ([]string, string, bool)

Models is the models a stage record's data says the program works on — its `models`, a list of model ids, and its `effort`, the rung it asks them to think at — and false for a record that names none.

THE PROGRAM SAYS WHICH MODELS IT RUNS ON, because only the program knows. The flags codeaf puts on its line are a request: the program folds in its own defaults, a person's own flags on a shell line, and drops a model it cannot use, and a page that named the request would name models the run never touched. Any stage may carry the pair; senior-dev's `bootstrap` does.

type StepRecord

type StepRecord struct {
	// Command is the action on one line, `<tool>: <what it was about>`.
	Command string `json:"command"`
	// Observation is the head of what came back.
	Observation string `json:"observation,omitempty"`
	// Tool is the tool's own name.
	Tool string `json:"tool,omitempty"`
	// Step is the program's own id for the part of its process the action
	// served (senior-dev's are app.Steps). It is the program's word, drawn
	// through the program's own vocabulary ([Delegate.Present]).
	Step string `json:"step,omitempty"`
	// Exit is a command's exit code, present only for an action that ran a
	// command and learned how it exited — which is why it is a pointer: a
	// command that exited 0 and an action that ran none are two facts.
	Exit *int `json:"exit,omitempty"`
	// Added and Removed are the lines an action that changed a file added and
	// removed, present only when the program counted them.
	Added   *int `json:"added,omitempty"`
	Removed *int `json:"removed,omitempty"`
}

StepRecord is one `step` record: one finished action, what was run and the head of what came back, and — each optional, each absent from a program that does not say it — the tool that ran it, the step of the program's own process it served, and a command's exit code.

type Terminal

type Terminal struct {
	Status  string                     `json:"status"`
	Message string                     `json:"message"`
	Data    map[string]json.RawMessage `json:"data"`
}

Terminal is the one record that is the result. Data is kept whole so the landing note can read the optional keys, in the protocol's spelling and in senior-dev's own, through the accessors below rather than by every caller knowing both.

func (Terminal) Claim

func (t Terminal) Claim() string

Claim is what the program's model said it did: `claim` in the protocol, `submission_reason` in senior-dev's record.

func (Terminal) CostUSD

func (t Terminal) CostUSD() (float64, bool)

CostUSD is the final total, and false when the record did not carry one.

func (Terminal) Deliverable

func (t Terminal) Deliverable() string

Deliverable is the answer text of a delegate that lands text.

func (Terminal) HandedIn

func (t Terminal) HandedIn() bool

HandedIn says the program handed in a change of its own — senior-dev's `submitted` — whatever its own check of that change then said, and false when the record does not say.

A CHANGE HANDED IN IS FINISHED WORK, AND A PROGRAM'S OWN CHECK OF IT IS A LEAD, NOT A VERDICT. senior-dev ends `fail` when its guess at the project's build and tests exits non-zero, and in a fortnight of real runs every such ending came from the guess — a CI line cut in half, `python3 -m pytest` on a machine with no pytest — and none from a change that broke the project. Read as unfinished work, each one woke the conversation to "fix" work that was never broken; the host reads a handed-in change as finished instead (internal/run's DelegateWorker), with what the check said kept beside it.

func (Terminal) Observed

func (t Terminal) Observed() string

Observed is what the program itself verified: `observed` in the protocol. senior-dev spells its observation as its own inner status and a count of failing verification commands, which read here as one sentence so the landing note can keep the claim and the observation apart.

func (Terminal) Reason

func (t Terminal) Reason() string

Reason is the longer reason when there is one.

func (Terminal) Verdict

func (t Terminal) Verdict() string

Verdict is the program's own word for how its work stood when it ended — senior-dev's inner status (`pass`, `pass-unverified`, `fail`) — beside the protocol's status word, and "" when the record carried none.

type ToolUse

type ToolUse struct {
	Name string `json:"name"`
	Args string `json:"args,omitempty"`
}

ToolUse is one tool a model asked the program to run, with its arguments on one line.

type Turn

type Turn struct {
	Seq int `json:"seq"`
	// Thread tells conversations apart when a program holds more than one at
	// once (a summary of its own history, a helper agent): the call's cache key
	// or its own id, MainThread when it gave none.
	Thread  string    `json:"thread,omitempty"`
	Started time.Time `json:"started"`
	Ended   time.Time `json:"ended,omitempty"`
	// Model is the model the program asked for; Served is the one that
	// answered, when codeaf's router answered with another.
	Model  string `json:"model"`
	Served string `json:"served,omitempty"`
	// Sent is what the program sent that the thread's previous call did not:
	// its brief first, then its tools' results and its own words. Restarted is
	// true when the program rewrote its history instead of adding to it (a
	// compaction), so Sent is then everything it sent.
	Sent      []Said `json:"sent,omitempty"`
	Restarted bool   `json:"restarted,omitempty"`
	// Reply is the model's text, and Calls the tools it asked the program to run.
	Reply string    `json:"reply,omitempty"`
	Calls []ToolUse `json:"calls,omitempty"`
	// The call's size and price, as the funnel metered them.
	TokensIn  int     `json:"tokens_in,omitempty"`
	TokensOut int     `json:"tokens_out,omitempty"`
	Cached    int     `json:"cached,omitempty"`
	CostUSD   float64 `json:"cost_usd,omitempty"`
	// Refused is codeaf's own refusal — the ceiling, a run that has ended — set
	// when the call never reached a model. Failed is the model's side failing.
	Refused string `json:"refused,omitempty"`
	Failed  string `json:"failed,omitempty"`
}

Turn is one model call a program made through its model API. A call is written twice under one Seq — when it starts, with no Ended, and when it ends — and a reader keeps the later, which is how the page shows a call in flight without a second file.

func ReadTurns

func ReadTurns(dir string, n int) ([]Turn, error)

ReadTurns reads the log in dir: every call once, in the order they started, each as its latest record says, and the last n of them (n <= 0 for all). A log that is not there is no turns and no error, because a run that has not called a model yet has said nothing; a line that does not parse is skipped, because a log cut mid-write is still a log.

func (Turn) InFlight

func (t Turn) InFlight() bool

InFlight answers whether the call has not come back yet.

Directories

Path Synopsis
Package builtin is the list of programs this build carries — the one place a program becomes part of codeaf (internal/delegate).
Package builtin is the list of programs this build carries — the one place a program becomes part of codeaf (internal/delegate).

Jump to

Keyboard shortcuts

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