the manual / sec 02

Loop: define one agent

A Loop is the immutable definition of one agent. It answers six questions:

  1. What is this agent called?
  2. Which model and provider client does it use?
  3. What instructions does it follow?
  4. Which tools can it call?
  5. Which access gate controls those tools?
  6. Which other agents may it delegate to?

You define a Loop once and hand it to a Rig. The Rig binds fresh runtime state for every Session, so the same definition can be reused safely.

The smallest Loop

assistant, err := loop.Define(
	loop.WithName("assistant"),
	loop.WithInference(client, model),
	loop.WithSystem("Help me plan my work. Be concise and practical."),
)
if err != nil {
	return err
}

WithName and WithInference are required. The system prompt is optional, but most useful agents define one.

The name is more than a display label. It is the stable identity used by Rig topology, delegation, attribution, fingerprints, and restoration. Choose a name that can remain stable across versions of your agent.

Models and clients are separate

A model.Model from github.com/looprig/inference/model is a secret-free description of a model:

  • provider name;
  • API format;
  • endpoint;
  • provider-specific model identifier;
  • declared capabilities and context limits.

An inference.Client performs calls and owns the authentication boundary. API keys belong on the client, never on the model or Loop.

For an OpenAI-compatible model through OpenRouter:

model := model.CustomModel(
	model.ProviderName(llm.ProviderOpenRouter),
	model.APIFormatOpenAI,
	"https://openrouter.ai/api/v1",
	modelName,
	model.WithTools(),
)
client, err := auto.New(model, auth.APIKey(apiKey))

For a local model served by LM Studio:

model := model.CustomModel(
	model.ProviderName(llm.ProviderLMStudio),
	model.APIFormatOpenAI,
	"http://localhost:1234/v1",
	modelName,
	model.WithTools(),
)
client, err := auto.New(model, "")

auto.New supports providers it can construct from a model and API key alone. Bedrock requires AWS SigV4 credentials, and Phala requires an attestation policy, so their provider packages expose direct constructors instead.

Your application owns its model catalog. looprig validates model declarations but does not silently select or replace models for you.

Instructions and presentation

agent, err := loop.Define(
	loop.WithName("weekly-planner"),
	loop.WithDisplayName("Weekly Planner"),
	loop.WithDescription("Turns goals and constraints into a realistic weekly plan."),
	loop.WithInference(client, model),
	loop.WithSystem(systemPrompt),
)
  • WithName is the stable machine identity.
  • WithDisplayName is the human-facing label.
  • WithDescription helps interfaces and parent agents explain the role.
  • WithSystem defines the base instructions sent on every inference request.

Keep identity and behavior separate. A display label can change without renaming the agent in durable topology.

Tools and permissions

Tools are declared as tool.Definition values. A definition is immutable, but it builds fresh tool instances when a Loop is bound into a Session.

The optional github.com/looprig/tools module provides standard definitions. Add each capability explicitly:

loop.WithTools(
	tools.ReadFileDefinition(readGuard),
	tools.WriteFileDefinition(),
	tools.EditFileDefinition(),
	tools.Bash(bash.WithRunner(executor)),
)

The file tools require a workspace. A Rig containing them must configure one of its workspace placements or rig.Define will fail.

Tool execution fails secure when no access gate is wired. To let tools run, install one with loop.WithAccessGate. The gate applies the three access states — Deny, Gated, Allow — to the capabilities each prepared call needs, routing them to an AccessSource. A *sandbox.Profile satisfies that seam directly:

evaluator, err := gate.NewInteractiveEvaluator(
	[]gate.AccessBinding{
		{Kind: "command.execute", Source: profile},
		{Kind: "filesystem.read", Source: profile},
		{Kind: "filesystem.write", Source: profile},
		{Kind: "network", Source: profile},
	},
	ruleStore,           // durable workspace rules (permission.NewWorkspaceStore)
	loop.GateApprover(), // resolves one combined prompt in the live loop
	ruleStore,
	executor,            // *sandbox.Executor mints post-approval grants
)
if err != nil {
	return err
}

agent, err := loop.Define(
	loop.WithName("workspace-assistant"),
	loop.WithInference(client, model),
	loop.WithSystem(systemPrompt),
	loop.WithTools(
		tools.ReadFileDefinition(readGuard),
		tools.WriteFileDefinition(),
		tools.EditFileDefinition(),
		tools.Bash(bash.WithRunner(executor)),
	),
	loop.WithAccessGate(evaluator),
	loop.WithPolicyRevision("workspace-policy-v1"),
)

A Gated capability uses a matching saved rule or produces one combined approval offering exactly Approve, Approve always for this workspace, and Deny. Allow proceeds without asking; Deny rejects and no saved rule can override it. Use gate.NewHeadlessEvaluator for a non-interactive run.

WithPolicyRevision is required when a Loop captures behavior that cannot be fingerprinted automatically, including an access gate, runtime context provider, or middleware. Change the revision whenever that behavior changes so a restored Session cannot silently adopt different policy.

For commands, pair the access gate with the sandbox module. The gate answers whether a call may run. The sandbox controls what the resulting process can touch, and the same *sandbox.Executor is the gate’s grant issuer. See Build larger systems.

Tool limits

Limit tool activity per turn when an agent can call tools:

loop.WithToolLimits(loop.ToolLimits{
	Iterations: 12,
	Calls:      40,
	Parallel:   4,
})
  • Iterations bounds model and tool cycles in one turn.
  • Calls bounds total tool calls in one turn.
  • Parallel bounds calls that may execute concurrently.

Zero values select harness defaults. Negative values are rejected.

Multiple modes

A Mode is a predeclared alternative model, effort, tool set, tool limits, or set of additional instructions for the same agent identity.

agent, err := loop.Define(
	loop.WithName("assistant"),
	loop.WithInference(client, fastModel),
	loop.WithSystem(baseSystem),
	loop.WithModes(
		loop.Mode{
			Name:         "deep-review",
			Model:        reasoningModel,
			Effort:       model.EffortHigh,
			Instructions: "Review assumptions and identify hidden risks.",
		},
	),
	loop.WithInitialMode("deep-review"),
)

At runtime, a trusted caller can select a declared mode through loop.Controller.SetMode. It can also change the model or effort atomically with loop.Controller.Change. Runtime changes apply at a turn boundary.

Delegation

A Loop may name the agents it is allowed to create or address:

planner, err := loop.Define(
	loop.WithName("planner"),
	loop.WithInference(client, model),
	loop.WithSystem("Plan the work and delegate focused research."),
	loop.WithDelegates("researcher"),
	loop.WithDelegation(loop.Delegation{Style: loop.DelegationManaged}),
)

researcher, err := loop.Define(
	loop.WithName("researcher"),
	loop.WithInference(client, model),
	loop.WithSystem("Investigate one question and return evidence."),
)

The Rig must contain both definitions. It rejects missing delegates and Loops that are unreachable from every primer.

Do not add the Subagent tool yourself. When a Loop declares delegates, the harness injects exactly one scoped Subagent tool from the frozen delegate list. The parent cannot use it to reach an undeclared agent.

  • DelegationSyncOnly supports a direct request and response.
  • DelegationManaged also supports sending follow-up work, waiting, checking status, and interrupting a child.

The Rig adds global depth and quota limits as a second boundary.

Context observation and compaction

Context management is explicit. A Loop can either observe context pressure or automatically compact it.

Both paths require:

  • WithContextCounter;
  • WithInferenceCapability;
  • either WithContextObservation or WithCompaction.

Automatic compaction also names a registered hustle.Definition. A Hustle is a bounded, text-only inference job executed outside the normal turn lane. The Rig registers Hustles and their concurrency, queue, audit, and drain limits.

The harness supplies no compaction thresholds or timeouts. Applications choose them because model context limits, latency, cost, and summary quality are product policy.

Loop option reference

OptionPurpose
WithNameStable agent identity. Required.
WithInferenceProvider-neutral client and secret-free model. Required.
WithDisplayNameHuman-facing name.
WithDescriptionHuman-facing role description.
WithSystemBase system instructions.
WithToolsImmutable tool definitions. May accumulate across calls.
WithAccessGateCombined three-state access decision gate for every tool call. Requires WithPolicyRevision.
WithToolMiddlewaresTool execution wrappers.
WithToolLimitsPer-turn iteration, call, and parallelism limits.
WithEngineNative, Claude, or Codex execution engine.
WithDrainTimeoutBound shutdown of in-flight Loop work.
WithRuntimeContextSession-specific instructions or context.
WithPolicyRevisionStable identity for opaque behavior.
WithDelegatesNames this Loop may delegate to.
WithDelegationSynchronous or managed delegation style.
WithModesPredeclared alternative configurations.
WithInitialModeMode selected when the Loop starts.
WithContextCounterContext measurement implementation.
WithInferenceCapabilityTransport capability paired with the counter.
WithContextObservationContext pressure without automatic compaction.
WithCompactionExplicit automatic compaction policy.

loop.Define validates and freezes the complete definition. It returns typed errors for missing identity or inference, invalid tools, conflicting context policy, invalid modes, duplicate singleton options, and unsafe opaque policy.

Next, assemble your Loops into a Rig.