driver

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package driver makes the agent run-loop a swappable port. The loop, how a run reasons and acts on its way to a goal, used to be hardwired: every run was assembled from one implicit conversation loop and there was no way to plug a different strategy without forking it. A Driver is that strategy as a named, resolvable choice: given the ingredients of a run (a model, a toolset, the governance grant, the safety brake, the capability-scaffolding plan), it builds the executor and the convergence test the goal reconciler drives.

The loop a Driver builds composes WITH the run's governance rather than replacing it: a Driver owns the loop shape only. The dispatch waist, the capability grant, and the safety brakes sit outside the loop and apply to every action it takes, whichever Driver built it. So selecting a different loop never widens authority or escapes a halt.

The default driver is the general-purpose software/automation loop, so the zero-config experience is unchanged; alternate loops are additive registrations, not edits to the default. A run resolves its driver by name from a Registry, fail-closed on an unknown name like every other resource, and the choice is fixed for the run.

Index

Constants

View Source
const NameDefault = "general-software"

NameDefault is the blessed general-purpose loop: the tool-using conversation that advances a goal by calling the model, running the tools it asks for, feeding the results back, and converging when the model finishes. It is the zero-config default, so a run that names no driver gets it.

View Source
const NameSingleShot = "single-shot"

NameSingleShot is a tight responder loop: one model turn, no tools, converge on the answer. It is the alternate loop that proves the Driver boundary: a genuinely different strategy, selected by name, with no edits to the default loop. It suits an archetype that should answer directly rather than work a tool-using investigation (a classifier, a router, a summarizer).

Variables

This section is empty.

Functions

This section is empty.

Types

type Driver

type Driver interface {
	// Name is the driver's stable identifier, resolved from the registry and
	// recorded on the run's spine.
	Name() string
	// Build assembles the loop for one run. It returns the goal.StepExecutor the
	// worker advances and the goal.StopEvaluator the reconciler converges on.
	Build(s Spec) (goal.StepExecutor, goal.StopEvaluator, error)
}

Driver builds the run loop for an agent. Name identifies it in the registry and is recorded on the run; Build assembles the executor and convergence test from a Spec. Build is pure assembly: it wires the loop but does not start it. An implementation must be safe for concurrent use.

type ModelResolver

type ModelResolver func(id string) (llm.Model, error)

ModelResolver turns a model identifier into a model client. The agent wires the provider resolver; a test injects a fake. It is consulted only for a goal that names a model other than the default.

type Registry

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

Registry resolves a Driver by name. It is fail-closed: an unknown name is an error, never a silent fallback to a default, so a run with a misnamed driver stops at assembly rather than running the wrong loop. It is safe for concurrent use.

func Default

func Default() *Registry

Default returns a registry preloaded with the built-in drivers: the general-purpose software loop (the default) and the single-shot responder. A host can register additional drivers onto it.

func NewRegistry

func NewRegistry() *Registry

NewRegistry builds an empty registry.

func (*Registry) Names

func (r *Registry) Names() []string

Names lists the registered driver names in sorted order, for diagnostics and a kube-style listing.

func (*Registry) Register

func (r *Registry) Register(d Driver) error

Register adds a driver under its Name. It refuses an empty name or a duplicate, so a registry can never hold two loops under one name (which would make selection ambiguous).

func (*Registry) Resolve

func (r *Registry) Resolve(name string) (Driver, error)

Resolve returns the driver registered under name, or a Terminal error naming the available drivers when none matches. Resolving the empty name returns the default driver, so a run that names no driver gets the blessed general-purpose loop.

type Router

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

Router drives each goal through the loop and model its spec selects, building and caching one loop per (driver, model) pair. It implements both goal.StepExecutor and goal.StopEvaluator, so a single Router handed to the runtime drives goals of differing loops with the right convergence test for each: a parent on the default loop and a delegated child on a specialist's loop and model. A goal that names no driver or model uses the host defaults, so a single-loop run is unchanged.

The per-goal system prompt and capability grant are applied inside the loop (from the goal), so they vary within one built loop; only the driver and model, which determine which loop object to use, key the cache.

func NewRouter

func NewRouter(cfg RouterConfig) *Router

NewRouter builds a Router. A nil registry uses the built-in Default registry.

func (*Router) Execute

func (r *Router) Execute(ctx context.Context, g resource.Resource) (json.RawMessage, error)

Execute drives one step of the goal through the loop its spec selects.

func (*Router) Met

func (r *Router) Met(ctx context.Context, spec goal.Spec, status goal.Status) (bool, string, error)

Met converges the goal through the loop its spec selects, so a goal driven by an alternate loop is judged by that loop's convergence test.

type RouterConfig

type RouterConfig struct {
	Registry      *Registry
	Base          Spec
	DefaultModel  llm.Model
	DefaultDriver string
	ResolveModel  ModelResolver
}

RouterConfig configures a Router. Base carries the ingredients every loop shares (tools, default system, default grant, sandbox, reporter, brake, fan-out); its Model is ignored, the Router sets the model per goal.

type Spec

type Spec struct {
	// Model is the language model the loop drives.
	Model llm.Model
	// Tools is the capability surface the loop may call. A loop that takes no tools
	// (a single-shot responder) ignores it.
	Tools []mission.Tool
	// System is the standing system instruction framing every turn.
	System string
	// Grant is the capability grant every action is admitted against at the waist.
	// HasGrant distinguishes "no grant bound" (unconstrained) from the zero grant.
	Grant    capability.Grant
	HasGrant bool
	// Sandbox, when set, gates each action on containment sufficiency at the waist.
	Sandbox sandbox.Sandbox
	// Reporter receives the loop's conversational events for live streaming.
	Reporter mission.Reporter
	// Brakes, when set, halts the run from outside the loop on a tripped breaker or
	// the kill-switch. It applies regardless of which loop is built.
	Brakes *brakes.Hook
	// Budget, when set, charges every action against the run's spend pool and refuses
	// one once the ceiling is reached. It applies regardless of which loop is built; a
	// run whose pool has no budget resource is unlimited, so a nil Budget or an
	// unbudgeted run is unconstrained.
	Budget *budget.Hook
	// Fanout, when set, lets the loop delegate sub-goals to concurrent child runs
	// (the model is offered a spawn tool). A loop that does not fan out (a single-shot
	// responder) ignores it. The default is nil: a goal runs as a single conversation.
	Fanout mission.Fanout
	// EventSink, when set, records every governed action's lifecycle (admitted,
	// completed, or rejected) onto the event spine, so the admission decisions are
	// part of the run's recorded and sealed history rather than only the live trace.
	// A nil sink records nothing extra, leaving the live trace unchanged.
	EventSink dispatch.EventSink
	// CompactionBudget, when greater than zero, is the input-token budget past which
	// the loop elides the oldest middle turns to keep a long session within the
	// model's window. Zero leaves compaction at its default.
	CompactionBudget int
	// Plan is the capability-scaffolding for a weaker or more quantized model: how
	// hard the loop should work to keep it reliable. The zero Plan adds nothing, so a
	// capable model runs leanly.
	Plan harness.Plan
}

Spec is the loop-agnostic description of one agent run. Every Driver receives the same Spec and translates it into its own loop shape, so the ingredients (model, tools, governance, observability, scaffolding) stay uniform across loops and only the strategy differs. A zero field means that ingredient is absent: no tools, no grant (unconstrained), no sandbox gate, no observer, no brake, no scaffolding.

Jump to

Keyboard shortcuts

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