runner

package
v0.14.1 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package runner runs a delegated role through a command-line harness the operator named, with the same brief, inputs and output contract the host's own sub-agent gets (itd-2609201916056194, spc-2609221533057881).

A role's route is roles.<role>.runner in the layered configuration (config.go): host, the default, or a runner this machine enabled under runner.<name>. The shipped runners are the claude CLI in print mode with the bare flag (claude.go) and opencode's run mode (opencode.go). Each is started as a process (proc.go): the argv is a vector and never a shell line, the binary is resolved on PATH and refused inside the repository, the environment is the parent's scrubbed of every git repository-selection and config-injection variable, stdin is the null device, output is bounded, and a run past its time is killed with the process group it leads.

The fallback is decided in one place (dispatch.go): an absent, refusing, failing or unparsable runner, or an answer the contract's validator refuses, hands the role to the host session, or with no host session to the host the operator configured (runner.fallback_host), and writes one receipt naming the role, the runner asked for, the reason and the route that ran. Tally counts those receipts per runner and per role for the run's summary.

The package never prints. It starts processes only through Dispatch and the adapters' Run, and never logs a harness in: a harness's credential is its own, read from its own environment or configuration, and abcd never puts one on a command line, in an error or in a receipt.

Index

Constants

View Source
const (
	Host     = "host"
	Claude   = "claude"
	OpenCode = "opencode"
)

The routes a role may name. Host is the default.

View Source
const DefaultTimeout = 60 * time.Minute

DefaultTimeout bounds one run when the request names no timeout.

Variables

View Source
var (
	ErrNotOnPath  = errors.New("not on PATH")
	ErrRefused    = errors.New("refused before it is run")
	ErrNotStarted = errors.New("could not be started")
)

The launch stages a Failure can name through errors.Is: the program was not found, it was found and refused, or it was admitted and could not start. Every other failure (a non-zero exit, a run past its time, an overrun bound) is none of them.

Functions

This section is empty.

Types

type Answer

type Answer struct {
	// Text is the harness's final message.
	Text string
	// Model is the model the harness reports, or the one it was asked for when
	// it reports none.
	Model string
	// SessionID is the harness's own session id, "" when it reports none.
	SessionID string
}

Answer is the one answer shape every adapter parses its harness's events into. The contract's own answer is the receipt file; this is what the harness said about the run.

type ClaudeCLI

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

ClaudeCLI is the claude CLI runner.

func (*ClaudeCLI) Name

func (*ClaudeCLI) Name() string

Name is the route's name.

func (*ClaudeCLI) Run

func (c *ClaudeCLI) Run(ctx context.Context, req Request) (Answer, []byte, error)

Run runs the role through the claude CLI.

type Command added in v0.13.0

type Command struct {
	// Name is the program's fixed name, looked up on PATH; a name carrying a
	// path separator is refused, so no caller can point Exec at a file.
	Name string
	// Args is the argument vector, never a shell line.
	Args []string
	// Dir is the working directory the program runs in; empty runs it at the
	// file-system root, never in the caller's directory, which for abcd is
	// usually a project whose settings a vendor binary must not read.
	Dir string
	// Guards are the folders a program resolving inside is refused from,
	// lexically or through a link: repository content is never run. A caller
	// running in or for a project passes it here; Exec cannot know it.
	Guards []string
	// Timeout bounds the run; past it the program's group is killed. A
	// command without one is never run.
	Timeout time.Duration
	// MaxStdout and MaxStderr bound each stream; past either the run fails.
	MaxStdout, MaxStderr int
}

Command is one program found on PATH that another package runs under this file's discipline: the one launch primitive abcd has for a vendor binary, so a second copy of the admission, the group kill and the bounds is never written. Zero output bounds take a harness's own.

type Config

type Config struct {

	// Diagnostics are the non-fatal reports the read produced, one line each,
	// for a front door to print on stderr.
	Diagnostics []string
	// contains filtered or unexported fields
}

Config is the runner configuration one invocation read.

func Load

func Load(r layered.Roots) (*Config, error)

Load reads and validates the runner configuration. A fault is an error naming the file and the key; none falls through to a default.

func (*Config) FallbackHost

func (c *Config) FallbackHost() string

FallbackHost is the runner that runs a role when there is no host session, "" when none is configured.

func (*Config) Quota added in v0.14.0

func (c *Config) Quota(ctx context.Context, name string) (Quota, bool, error)

Quota asks the runner the machine enabled under name for its remaining quota. The host, and a name the machine has not enabled, report none.

func (*Config) RouteFor

func (c *Config) RouteFor(role string) Route

RouteFor returns where role runs: its configured route, or the host from the bundled default.

func (*Config) Runner

func (c *Config) Runner(name string) (RunnerConfig, bool)

Runner returns the runner this machine enabled under name.

type Counts

type Counts struct {
	Total    int            `json:"total"`
	ByRunner map[string]int `json:"by_runner"`
	ByRole   map[string]int `json:"by_role"`
}

Counts is the fallback intel a run's summary reports.

func Tally

func Tally(rs []FallbackReceipt) Counts

Tally counts fallback receipts per runner asked for and per role. A run with none reports zero, with empty maps rather than none.

type Dispatcher

type Dispatcher struct {
	// Config is the runner configuration read at the lane's start.
	Config *Config
	// HostSession is true when a host session drives the loop and can take a
	// role handed back to it.
	HostSession bool
	// Attended is true when the person is at the terminal with no host session
	// (the plain-Terminal interviews, spc-2610030911534855): a role routed to a
	// runner runs there although no runner.fallback_host is configured, and a
	// runner that does not answer stops the run, its fallback receipt naming
	// none as the route that ran, rather than leaving the role nowhere to land.
	// A role on the host still needs a host session or a configured host.
	Attended bool
	// Validate is the contract's validator: the same check the host sub-agent's
	// answer passes. It is handed the runner that ran the role, so a caller
	// whose validator also records the answer (the loop's receipt verifier)
	// can name the route that ran.
	Validate func(runner string, req Request, ans Answer) error
	// Transcripts is where every runner's transcript lands.
	Transcripts TranscriptStore
	// Record appends one fallback receipt to the run's state.
	Record func(FallbackReceipt) error
	// Prepare, when set, readies req's files for the runner about to run it,
	// before each runner starts: the routed one and a fallback host alike. A
	// *Failure it returns is that runner's failure, recorded and fallen back
	// on as a launch's would be, with nothing launched; any other error ends
	// the dispatch.
	Prepare func(runner string, req Request) error
	// Now is the clock the receipts are stamped with; time.Now when nil.
	Now func() time.Time
}

Dispatcher runs roles by their routes.

func (*Dispatcher) Dispatch

func (d *Dispatcher) Dispatch(ctx context.Context, req Request) (Outcome, error)

Dispatch runs req by its role's route.

type Failure

type Failure struct {
	Runner string
	Reason Reason
	Detail string
	// contains filtered or unexported fields
}

Failure is a runner's failure. Detail is written by abcd, never copied from the harness's output, so it cannot carry anything the harness printed, a credential it echoed included.

func (*Failure) Error

func (f *Failure) Error() string

func (*Failure) Unwrap added in v0.13.0

func (f *Failure) Unwrap() error

Unwrap is the launch stage that failed, nil when the failure is not one.

type FallbackReceipt

type FallbackReceipt struct {
	At     time.Time `json:"at"`
	Role   string    `json:"role"`
	Asked  string    `json:"asked"`
	Reason Reason    `json:"reason"`
	// Detail is abcd's own account of the reason; it never carries the
	// harness's output.
	Detail string `json:"detail"`
	Ran    string `json:"ran"`
}

FallbackReceipt is one fallback: the role, the runner asked for, the reason, and the route that ran instead.

type HistoryStore

type HistoryStore struct {
	RepoRoot string
	RootSHA  string
}

HistoryStore is the production store: the repository's lane of the user-level transcript store, keyed on its root commit.

func (HistoryStore) Store

func (h HistoryStore) Store(runner string, req Request, ans Answer, raw []byte) error

Store captures raw as a native transcript produced by the runner, recorded under the harness's own session id when it reported one and the request's otherwise, with the role as its agent type.

type OpenCodeCLI

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

OpenCodeCLI is the opencode runner.

func (*OpenCodeCLI) Name

func (*OpenCodeCLI) Name() string

Name is the route's name.

func (*OpenCodeCLI) Run

func (o *OpenCodeCLI) Run(ctx context.Context, req Request) (Answer, []byte, error)

Run runs the role through opencode.

type Outcome

type Outcome struct {
	// Handoff is true when the host session runs the role, exactly as it does
	// with no runner configured: the caller hands the host the brief and
	// awaits its receipt.
	Handoff bool
	// Answer is the runner's parsed answer when a runner ran the role.
	Answer *Answer
	// Fallback is the receipt this dispatch recorded, nil when none.
	Fallback *FallbackReceipt
	// Receipt is the role's receipt block.
	Receipt RoleReceipt
}

Outcome is one dispatch's result.

type Output added in v0.13.0

type Output struct {
	Stdout, Stderr []byte
}

Output is what an Exec run wrote, each stream up to its bound.

func Exec added in v0.13.0

func Exec(ctx context.Context, c Command) (Output, error)

Exec admits c.Name exactly as a harness is admitted (admit) and runs it exactly as a harness is run (run): the scrubbed environment, the null device on stdin, the bounds, and the process group killed through this handle at the timeout or when ctx ends. A failure is a *Failure, whose errors.Is names the stage for ErrNotOnPath, ErrRefused and ErrNotStarted; the output is returned whenever the program produced some.

type Quota added in v0.14.0

type Quota struct {
	Remaining int `json:"remaining"`
}

Quota is what a runner reports of its budget before a run starts (itd-2609201925079472 criterion 7): the agent runs it can still start in its current window. A runner whose harness counts its budget in another unit converts it, or reports none.

type QuotaReporter added in v0.14.0

type QuotaReporter interface {
	Quota(ctx context.Context) (Quota, error)
}

QuotaReporter is a Runner that reports its remaining quota. Neither shipped runner is one: the claude CLI and opencode expose no remaining quota a launch can read before it starts, so a run routed to either names it and skips the budget check out loud.

type Reason

type Reason string

Reason is why a runner did not answer: the kinds the fallback records.

const (
	// ReasonAbsent: the runner is not enabled on this machine, or its binary
	// is not on PATH or is refused.
	ReasonAbsent Reason = "absent"
	// ReasonRefused: the harness ran and reported that it refused or erred.
	ReasonRefused Reason = "refused"
	// ReasonFailed: the harness could not start, exited non-zero, ran out of
	// time or overran its output bound.
	ReasonFailed Reason = "failed"
	// ReasonUnparsable: the harness's output is not its structured events.
	ReasonUnparsable Reason = "unparsable"
	// ReasonInvalid: the answer failed the contract's validator.
	ReasonInvalid Reason = "invalid"
	// ReasonRateLimited: the harness reported a rate-limit response and did
	// not finish. It is never fallen back on (dispatch.go): every route spends
	// the budget the run's window paces, so the caller ends the window
	// (itd-2609201925079472 criterion 8).
	ReasonRateLimited Reason = "rate-limited"
)

type Request

type Request struct {
	// Role is the agent the run plays: an agent in the roster, or the
	// implementer.
	Role string
	// Brief is the absolute path of the brief the role follows; it states the
	// task and the output contract.
	Brief string
	// Receipt is the absolute path the contract says the answer is written to.
	Receipt string
	// Dir is the absolute path of the working tree the role runs in.
	Dir string
	// Checkout is the absolute path of the checkout the run belongs to when
	// Dir is a worktree of it (a lane's worktree lives outside it); a program
	// inside it is repository content and is never run, as one inside Dir is
	// not. Empty when Dir is the checkout itself.
	Checkout string
	// Tools are the tools the role's contract grants, granted without a
	// prompt; none granted when empty.
	Tools []string
	// SessionID names the run's transcript record when the harness reports no
	// session of its own.
	SessionID string
	// Timeout bounds the run; DefaultTimeout when zero.
	Timeout time.Duration
}

Request is what a role is handed: the same the host sub-agent is handed.

type RoleReceipt

type RoleReceipt struct {
	Role  string `json:"role"`
	Brief string `json:"brief"`
	// Contract is the receipt path the contract names.
	Contract string      `json:"contract"`
	Route    RouteRecord `json:"route"`
}

RoleReceipt is a role run's receipt block. Everything but Route is the request's, so two runs of one request differ in their route alone.

type Route

type Route struct {
	Runner string
	Layer  layered.Layer
	Origin string
}

Route is where a role runs and the layer that said so.

type RouteRecord

type RouteRecord struct {
	// Asked is the route the configuration named: host or a runner.
	Asked string `json:"asked"`
	// Ran is the route that ran the role: host, a runner, or none.
	Ran string `json:"ran"`
	// Model is the model the runner that ran it reported; "" on the host,
	// whose own receipt reports it.
	Model string `json:"model"`
}

RouteRecord is the route a role asked for and the one that ran it.

type Runner

type Runner interface {
	// Name is the route's name, as roles.<role>.runner spells it.
	Name() string
	// Run runs the role and returns the parsed answer and the transcript, the
	// harness's raw event stream. A failure is a *Failure; the transcript is
	// returned whenever the harness produced one, failure or not.
	Run(ctx context.Context, req Request) (Answer, []byte, error)
}

Runner is one route a role can run through.

type RunnerConfig

type RunnerConfig struct {
	Name string
	// Model is the admitted <provider>/<model> route, "" for the harness's own
	// default.
	Model  string
	Origin string
}

RunnerConfig is one runner this machine enabled.

type TranscriptStore

type TranscriptStore interface {
	Store(runner string, req Request, ans Answer, raw []byte) error
}

TranscriptStore keeps one runner's transcript of one role's run.

Jump to

Keyboard shortcuts

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