Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Overview

Freeze one agent's model, instructions, tools, limits, modes, and delegation policy.

developer

A loop.Definition is one agent’s immutable design-time contract. It carries the inference client and model, system text, declared tool factories, access policy, limits, context policy, output schema, modes, and delegate names. It contains no conversation, goroutine, lease, or session ID. A Rig owns the graph of definitions and binds them when it creates a session.

How it works

The public lifecycle has three deliberately different values:

ValueCreated byOwnsMay mutate live state?
loop.Definitionloop.Define(opts ...loop.Option)validated, frozen policyno
loop.BoundDefinitionDefinition.Bind(ctx, tool.Bindings)fresh tool instances and resolved modesno; read-only view
loop.Handle / loop.Controllera session runtimelive loop identity and actor commandsController only, at turn boundaries

Binding is a separate step because tool factories receive session and loop IDs. The definition can therefore be reused by several sessions without sharing tool instances or mutable slices. The public package does not expose the internal loopruntime.Loop; use session.Session and session.SessionController for conversation, events, persistence, and shutdown.

%%{init: {"theme":"dark"}}%%
flowchart LR
    O[loop.Option values] --> D[loop.Define]
    D -->|validated| DF[immutable loop.Definition]
    DF -->|Bind(ctx, tool.Bindings)| B[loop.BoundDefinition]
    B --> H[live loop Handle]
    H --> C[loop.Controller]
    C --> T[turn-boundary changes]

Configure a Loop

The smallest definition has a name and a valid provider-neutral model. The inference client is an inference.Client; the model is model.Model.

assistant, err := loop.Define(
	loop.WithName("assistant"),
	loop.WithInference(client, selectedModel), // client is an inference.Client.
	loop.WithSystem("You help the user inspect and change a project."),
	loop.WithTools(readFile, grep, editFile),  // tool.Definition values.
)
if err != nil {
	var definitionErr *loop.DefinitionError
	if errors.As(err, &definitionErr) {
		log.Printf("definition rejected: %s", definitionErr.Kind)
	}
	return fmt.Errorf("define loop: %w", err)
}

Every option is applied once for singleton settings. WithTools, WithToolMiddlewares, WithDelegates, and WithModes are additive. A gated or middleware-enabled loop must include a nonempty WithPolicyRevision; this opaque revision gives restore a stable identity for function-valued policy. Context counting requires a counter, an inference capability, and exactly one of observation or compaction; see Context Observation for the policy split and Compaction Policy.

Lifecycle

The Rig checks that delegate names exist and that every registered loop is reachable from a primer. A session then binds each definition, creates a live loop actor, and routes input through a Turn. A Turn may contain several conceptual Steps. A controller change is validated and committed at a turn boundary; it does not mutate the immutable definition or retroactively change a running step.

Public boundary

The sealed BoundDefinition interface exposes read-only accessors such as Model, EffectiveSystem, Tools, Mode, ContextCounter, and RuntimeIdentity. It returns defensive copies for models, slices, and output schemas. Handle exposes only ID, Mode, and Model; Controller embeds it and adds SetMode, Change, and subtree-scoped Interrupt.

Do not type-assert a bound value to an internal runtime or construct an actor directly. That would bypass session ownership, durable events, and shutdown ordering.

Source and proof

← back to documentation