agent

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 59 Imported by: 0

Documentation

Overview

Package agent runs an agent step's LLM tool-calling conversation: it resolves the agent's model/connection, compiles the step's granted tools, drives the request/tool-execute/append loop (see runAgentConversation), and enforces required: true tools via the provider's tool_choice. It also runs a task's fix: agent (RunFix) on the same conversation machinery.

Index

Constants

View Source
const RuleKey = "rule"

RuleKey is the key of a refusal result naming the rule that made it.

Variables

View Source
var ErrRateLimited = errors.New("rate limit reached for this export; try again shortly")

ErrRateLimited is a call past an export's rate:.

Functions

func AnswerFlags

func AnswerFlags(ctx context.Context, pipeline string) string

AnswerFlags finish a printed answer command; with no WithAnswerDB, as under a daemon, the read commands' default database is the run's.

func CheckExportedCommands

func CheckExportedCommands(export config.MCPExport) error

CheckExportedCommands refuses an export whose command would put a caller's value into the shell as syntax. The caller is not the pipeline's author, so a value that reaches sh -c bare is a command the caller chose.

func Preflight

func Preflight(ctx context.Context, cfg *config.Config, agentNames []string, settings *config.Preflight) []config.Problem

Preflight checks every model and MCP server the named agents need, and returns a problem per target that failed. An empty result means the job's dependencies are all live.

Only what this job needs: a pipeline with ten agents whose job uses two checks two. Distinct (endpoint, model) pairs are checked once even when several agents share one.

func RecordOutcome

func RecordOutcome(ctx context.Context, name string, up Upstream)

RecordOutcome notes what the step named name decided. A later visit of the same step replaces the earlier one: a revise loop's reader wants the verdict that just sent it back, not the one from two passes ago.

func RenderUpstream

func RenderUpstream(name string, level config.FromLevel, up Upstream) string

RenderUpstream formats one step's outcome at the demanded level, or "" when there is nothing to say.

Absence is deliberately not an error. A reader that runs BEFORE its sender — the writer at the top of a revise loop, whose critic has not judged anything yet — gets nothing on the first pass and the critic's verdict on every pass after. That is the same "routed entry only" behaviour, arrived at by asking whether the sender has run rather than by inspecting the route.

func ResetProbeCache

func ResetProbeCache()

ResetProbeCache clears everything preflight has verified AND every source pin it (or a mid-run cascade) selected. Tests use it to stay independent of each other; a test about the pin's own lifetime wants only the first half, since wiping the pin is what such a test is trying to observe.

Every caller must be a serial test: this is process-wide state for all agents, so a `t.Parallel()` test that reached it would clear another's.

func RunFix

func RunFix(
	ctx context.Context, cfg *config.Config, jobName string, stepIndex int,
	rt config.ResolvedTask, st StepStore, failureOutput, workspaceDir string,
) error

RunFix invokes a failed task's fix: agent. It reuses the normal agent-invocation resolution (tool grant, dials, attempts, max_turns) by projecting the FixSpec onto an agent Step, then injects the parent task as a zero-arg rerun tool — the task's own run: command (never its fix:, so a rerun can't recurse), exposed under the task's name — and seeds the conversation with the captured failure output. It does no merkle/store recording: the enclosing task step records the overall outcome, and the task's re-run (not the model's word) is the verdict.

The fix conversation's run_shell/custom tools and the injected rerun tool all execute under rt.Image — the failing task's own image, never the fix agent's own Agent.Image (config.validateFixAgentImages rejects a fix agent that sets one, at LoadConfig time, precisely because it can never take effect here). The premise of the fix loop is reproducing and resolving the exact failure the task hit; running under a different image than the one that produced (and re-verifies) the failure would make the loop incoherent.

func RunHook

func RunHook(ctx context.Context, cfg *config.Config, jobName string, step config.Step, bw workspace.BuildWorkspace, st StepStore, newRunner RunnerFactory) error

RunHook runs an agent step as a hook: it resolves, materializes, runs the conversation, evaluates its assert, and captures declared outputs exactly like a plan step, but records no merkle node or job_run — the enclosing step/job records the aggregate outcome (the same no-record contract as RunFix). A returned error is already outcome-marked where appropriate (see runAgentConversation), so the caller's hook classification works unchanged.

The assert runs here for the same reason a task hook's does (see internal/pipeline's executeTask -> runAssertedTask): config.validateAsserts deliberately walks hook steps, so an assert on an on_failure: agent is checked and rejected at load like any other. Evaluating it at load and then never running it made that promise a lie — the hook reported success on a mismatch its own assert existed to catch.

func UnquotedToolRefs

func UnquotedToolRefs(run string) []string

UnquotedToolRefs reports each action in a custom tool's run: that prints a .args value without shellquote — the model-authored half of a tool reaching sh -c as syntax.

func WithAnswerDB

func WithAnswerDB(ctx context.Context, db string) context.Context

WithAnswerDB carries a local run's own state file to the printed commands, since the read commands default to a daemon's.

func WithAnswerSeeds

func WithAnswerSeeds(ctx context.Context, seeds []AnswerSeed) context.Context

WithAnswerSeeds attaches pre-seeded answers to a run's context.

func WithExportWho

func WithExportWho(ctx context.Context, who ExportWho) context.Context

WithExportWho carries who is calling to the rules checking the call.

func WithOutcomes

func WithOutcomes(ctx context.Context) context.Context

WithOutcomes installs a fresh outcome register on ctx. Called once per job run; a run without one simply delivers nothing, which is what every pipeline that declares no from: should see.

func WithRunBoundary

func WithRunBoundary(ctx context.Context) context.Context

WithRunBoundary stamps a context with the moment its run started, which is the instant every pin read inside that run is judged against.

The lifetime still rides on the pin and is still applied wherever one is read — hanging it off a writer would leave it exactly as skippable as reconsiderPin. What this fixes is WHICH clock the read consults. Reading at wall-clock meant a job of six agent steps could cross the lifetime between steps 4 and 5: the pin vanished mid-job and the remaining steps resolved to a primary that was still down, each paying a full attempts: cycle before cascading and re-pinning. Nothing renews mid-job, because renewPinIf is reachable only from Preflight — once per job, and switched off entirely by --no-preflight, which is the configuration the lifetime exists to serve.

Judging against the run's own start makes the release land at a boundary where nothing is half-done, which is what docs/agents.md has always promised: a run decides before it starts, and one job sees a settled source throughout. internal/pipeline stamps this once per job run, beside the OpenRouter session, so concurrent jobs each carry their own.

Types

type AnswerSeed

type AnswerSeed struct {
	Match  string
	Answer string
}

AnswerSeed is one --answer: a substring of a question, and the answer to give when a question contains it.

Matched on the question's TEXT rather than on a position or an id, because neither exists when the flag is written: an id is minted by the run that is about to start, and which question a model asks first is the model's choice. The text is the only part of a question the pipeline author can predict.

func ParseAnswerSeed

func ParseAnswerSeed(raw string) (AnswerSeed, error)

ParseAnswerSeed parses one --answer value, written `substring=answer`.

Split on the FIRST `=`, so an answer may contain one; a match may not, which is the right way round — the match is a fragment of a question somebody wrote, and an answer is arbitrary text.

type ExportHooks

type ExportHooks struct {
	// Before may refuse a call — a rate limit — and the refusal reaches the caller as the tool's error result.
	Before func(ctx context.Context, tool string) error
	// After is told every call that ran: the tool, the NAMES of the arguments it was sent (never their values, which can hold anything a caller pasted), whether it succeeded, and, when a rule refused it, that rule's name.
	After func(ctx context.Context, tool string, argNames []string, ok bool, rule string)
}

ExportHooks are what the server that mounts an export decides about each call.

type ExportServer

type ExportServer struct {
	// Handler serves the MCP streamable HTTP protocol, statelessly: every request is answered from the tools alone.
	Handler http.Handler
	// Tools are the names a caller sees, in the order they are offered.
	Tools []string
	// contains filtered or unexported fields
}

ExportServer is one export, built and connected to its upstreams.

func NewExportServer

func NewExportServer(ctx context.Context, cfg *config.Config, export config.MCPExport, hooks ExportHooks) (*ExportServer, error)

NewExportServer builds an export's tools under cfg and serves them. ctx bounds the upstream connections, which live until Close.

func (*ExportServer) Close

func (e *ExportServer) Close() error

Close ends the upstream connections.

type ExportWho

type ExportWho = exprlang.RuleWho

ExportWho is the person an export's call is made as. Zero for a call that is not a person's, an agent step's own tool among them.

type PreparedStep

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

PreparedStep is an agent step resolved and materialized, with its memory: loaded — everything internal/pipeline needs to key it — but whose conversation has not started. Pipeline hashes it, asks the step cache, and records its node; Run is the conversation and nothing around it.

func PrepareStep

func PrepareStep(ctx context.Context, cfg *config.Config, step config.Step, bw workspace.BuildWorkspace, st StepStore, newRunner RunnerFactory) (*PreparedStep, error)

PrepareStep resolves, materializes and loads the memory of an agent step. The caller must Close what it returns.

func (*PreparedStep) Capture

func (s *PreparedStep) Capture(ctx context.Context) error

Capture files the step's declared outputs. After a successful Run only: the spill directory is removed first when it would otherwise ride into an artifact (see removeSpillDirIfCaptured).

func (*PreparedStep) Close

func (s *PreparedStep) Close()

Close releases everything PrepareStep opened, the step's directory last.

func (*PreparedStep) MemoryDigest

func (s *PreparedStep) MemoryDigest() string

MemoryDigest covers what the step's memory: scope holds as it starts, or "" when it declares no memory:. Part of the key: the same message with different memory is a different step (see applyStepMemory).

func (*PreparedStep) Primary

func (s *PreparedStep) Primary() config.ResolvedInvocation

Primary is the invocation as CONFIGURED, before any preflight failover — the one to key on, so which source serves a run is availability rather than content and a fallback firing cannot invalidate a cache entry.

func (*PreparedStep) Run

func (s *PreparedStep) Run(ctx context.Context, run StepRun) (StepResult, error)

Run runs the conversation and checks its assert:. Its usage and transcript are saved on every outcome, before the error is returned, so a step that FAILED still records what it spent and said — a failed agent step is often the expensive one, and its transcript is the one that gets read. Both writes are best-effort and detached (see saveAgentUsage, saveAgentTranscript), and both reference run.NodeHash, which is why the caller records that node before calling this.

func (*PreparedStep) Step

func (s *PreparedStep) Step() config.Step

Step is the resolved step — the copy to key on. When message_files: named a run-time artifact, its loaded text is in Messages here (see prepareStepPrompt) while the step the caller holds still has none; keying on the caller's copy would collide every such pipeline onto one node regardless of what the file said.

type RunnerFactory

type RunnerFactory func(ctx context.Context, spec shell.RunnerSpec) (shell.Runner, error)

RunnerFactory builds the runner a step's run_shell, custom tools and containerized file tools all execute through.

It is a parameter rather than a call to shell.NewRunner because placing a step is internal/pipeline's knowledge and not this package's: pipeline is what turns a step's tags: into a machine, and the dependency graph deliberately keeps internal/venue out of here. A nil factory runs on this machine, which is what every caller with no placement to apply passes.

type StepResult

type StepResult struct {
	Record   map[string]any
	Response string
	Verdict  string
	Note     string
}

StepResult is what a conversation produced. Record is what its node records whatever the outcome, and Response is set whenever the conversation ran at all. Verdict and Note are set only on success: a failed run emitted no clean verdict, and internal/pipeline routes it via to["failure"] instead.

type StepRun

type StepRun struct {
	JobName  string
	Index    int
	Name     string
	NodeHash string
}

StepRun is where a conversation belongs: the job and plan position it runs at, the name it is known by, and the node internal/pipeline recorded for it — which its usage and transcript rows reference.

type StepStore

type StepStore interface {
	store.Usage
	store.Questions
	store.Memories
	TranscriptStore
}

StepStore is the part of the state database an agent step's conversation touches: its spend in the usage ledger, the questions it parks for a person, what its memory: scope keeps, and its transcript. Not the node it is recorded under, nor the skip index — internal/pipeline owns both for every step kind, and this type having no way to write either is what keeps it so.

type TranscriptStore

type TranscriptStore interface {
	SaveNodeTranscript(ctx context.Context, hash, transcript string) error
}

TranscriptStore files a conversation's transcript under the node internal/pipeline recorded for it. The one store.Cache method an agent step needs, declared alone because the rest of that facet is pipeline's.

type Upstream

type Upstream struct {
	Verdict  string
	Note     string
	Response string
}

Upstream is one step's outcome as a later step may see it.

func LookupOutcome

func LookupOutcome(ctx context.Context, name string) (Upstream, bool)

LookupOutcome returns what the named step decided this run, if it has run.

Directories

Path Synopsis
Package llmclient builds the model.LLM an agent step talks to and the HTTP transports under it: retries, tool-call argument repair, OpenRouter's caching levers, and the choice of which fallback source to try next.
Package llmclient builds the model.LLM an agent step talks to and the HTTP transports under it: retries, tool-call argument repair, OpenRouter's caching levers, and the choice of which fallback source to try next.
Package tree is how a file tool reaches the step's directory: directly, or through the image's own userland in the step's container.
Package tree is how a file tool reaches the step's directory: directly, or through the image's own userland in the step's container.

Jump to

Keyboard shortcuts

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