# Quickstart: build one agent > This guide builds a small personal assistant that accepts one request and streams its answer to the terminal. This guide builds a small personal assistant that accepts one request and streams its answer to the terminal. You will create: - one provider client; - one Loop named `assistant`; - one in-memory session store; - one Rig; - one live Session. The example uses OpenRouter because its client can be constructed from a model and API key. looprig also supports local and directly constructed providers. Those choices are covered in [Loop](/docs/loop). ## Prerequisites - Go 1.26.4 or later for the current harness and provider modules - an OpenRouter API key - an OpenRouter model identifier chosen by you The model identifier is configuration, not a looprig default. This keeps the agent under your control and avoids silently changing models when a provider's catalog changes. ## Create the module ```sh mkdir my-agent cd my-agent go mod init example.com/my-agent go get github.com/looprig/core \ github.com/looprig/harness \ github.com/looprig/inference \ github.com/looprig/llm \ github.com/looprig/storage ``` Set your provider configuration: ```sh export OPENROUTER_API_KEY="your-key" export OPENROUTER_MODEL="your-provider-model-id" ``` The API key is passed to the provider client. It is never stored on the model, Loop, Rig, or session history. ## Write the agent Create `main.go`: ```go package main import ( "context" "errors" "fmt" "log" "os" "time" "github.com/looprig/core/content" "github.com/looprig/harness/pkg/event" "github.com/looprig/harness/pkg/loop" "github.com/looprig/harness/pkg/rig" "github.com/looprig/harness/pkg/sessionstore" "github.com/looprig/inference/auth" "github.com/looprig/inference/model" "github.com/looprig/llm" "github.com/looprig/llm/auto" "github.com/looprig/storage/memstore" ) func main() { if err := run(); err != nil { log.Fatal(err) } } func run() error { // Bound the complete agent run so provider or network failures cannot hang // this example forever. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() // Credentials and model choice belong to the application. They are not // compiled into looprig or stored in session history. apiKey := os.Getenv("OPENROUTER_API_KEY") modelName := os.Getenv("OPENROUTER_MODEL") if apiKey == "" || modelName == "" { return errors.New("set OPENROUTER_API_KEY and OPENROUTER_MODEL") } // Model is a secret-free description of what to call and how to speak to it. model := model.CustomModel( model.ProviderName(llm.ProviderOpenRouter), model.APIFormatOpenAI, "https://openrouter.ai/api/v1", modelName, ) // The client owns the credential boundary. auto.New validates the declared // provider and constructs the matching inference.Client. client, err := auto.New(model, auth.APIKey(apiKey)) if err != nil { return err } // A Loop is one immutable agent definition: identity, inference, and behavior. assistant, err := loop.Define( loop.WithName("assistant"), loop.WithInference(client, model), loop.WithSystem("You are a concise personal assistant."), ) if err != nil { return err } // The in-memory backend keeps the first example small. Swap memstore for // fsstore when session history must survive a process restart. store, err := sessionstore.Open(memstore.New()) if err != nil { return err } // A Rig assembles one or more Loops with storage and lifecycle policy. With // one primer, that Loop becomes the active primer automatically. assembly, err := rig.Define( rig.WithLoops(assistant), rig.WithPrimers("assistant"), rig.WithSessionStore(store), ) if err != nil { return err } // A Session is one live execution created from the reusable Rig. session, err := assembly.NewSession(ctx) if err != nil { return err } defer func() { _ = session.Shutdown(ctx) }() // Subscribe before submitting so live token deltas cannot be missed. // Ephemeral events stream progress; enduring events survive restoration. stream, err := session.SubscribeEvents(event.EventFilter{ Ephemeral: event.LoopScope{All: true}, Enduring: event.LoopScope{All: true}, }) if err != nil { return err } defer func() { _ = stream.Close() }() // Submit queues human-authored content on the active primer. Completion and // failure arrive through the event stream, not from Submit itself. _, err = session.Submit(ctx, []content.Block{ &content.TextBlock{Text: "Give me three practical ways to organize my week."}, }) if err != nil { return err } // Keep consuming until this turn reaches a terminal event. for delivery := range stream.Events() { switch ev := delivery.Event.(type) { case event.TokenDelta: // TokenDelta is live-only. Here we print text while the model streams. if chunk, ok := ev.Chunk.(*content.TextChunk); ok { fmt.Print(chunk.Text) } case event.TurnDone: // TurnDone is the durable successful terminal event. fmt.Println() return nil case event.TurnFailed: // Live failures retain their typed provider or runtime cause. if ev.Err != nil { return ev.Err } return errors.New("agent turn failed") case event.TurnInterrupted: // Interruption is distinct from provider or model failure. return errors.New("agent turn was interrupted") } } if err := stream.Err(); err != nil { return err } return errors.New("event stream closed before the turn completed") } ``` Run it: ```sh go run . ``` ## What happened The program assembled the system in the same order looprig uses at every scale: 1. `model.Model` described the provider, wire format, endpoint, and model without carrying a secret. 2. `auto.New` validated that model and bound the API key to an `inference.Client`. 3. `loop.Define` froze the agent's identity, client, model, and instructions. 4. `sessionstore.Open(memstore.New())` created an in-memory durable-state contract for this process. 5. `rig.Define` validated the complete agent topology and storage dependency. 6. `NewSession` created one live execution. 7. `SubscribeEvents` attached a consumer before work was submitted. 8. `Submit` queued the request, while `TokenDelta` and terminal events reported its progress and outcome. The in-memory backend is intentional for the first run. It does not survive a process restart. Replace it with `fsstore` when you want session history to persist on disk. ## Next steps - Customize the agent in [Loop](/docs/loop). - Persist and restore it with [Rig](/docs/rig). - Learn the live API in [Session](/docs/session). - Add tools, workspaces, sandboxing, or more agents in [Build larger systems](/docs/larger-systems). --- # Loop: define one agent > A Loop is the immutable definition of 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](/docs/rig). The Rig binds fresh runtime state for every Session, so the same definition can be reused safely. ## The smallest Loop ```go 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: ```go 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: ```go 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 ```go 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: ```go 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: ```go 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](/docs/larger-systems#confine-commands-with-the-os). ## Tool limits Limit tool activity per turn when an agent can call tools: ```go 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. ```go 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: ```go 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 | Option | Purpose | | --- | --- | | `WithName` | Stable agent identity. Required. | | `WithInference` | Provider-neutral client and secret-free model. Required. | | `WithDisplayName` | Human-facing name. | | `WithDescription` | Human-facing role description. | | `WithSystem` | Base system instructions. | | `WithTools` | Immutable tool definitions. May accumulate across calls. | | `WithAccessGate` | Combined three-state access decision gate for every tool call. Requires `WithPolicyRevision`. | | `WithToolMiddlewares` | Tool execution wrappers. | | `WithToolLimits` | Per-turn iteration, call, and parallelism limits. | | `WithEngine` | Native, Claude, or Codex execution engine. | | `WithDrainTimeout` | Bound shutdown of in-flight Loop work. | | `WithRuntimeContext` | Session-specific instructions or context. | | `WithPolicyRevision` | Stable identity for opaque behavior. | | `WithDelegates` | Names this Loop may delegate to. | | `WithDelegation` | Synchronous or managed delegation style. | | `WithModes` | Predeclared alternative configurations. | | `WithInitialMode` | Mode selected when the Loop starts. | | `WithContextCounter` | Context measurement implementation. | | `WithInferenceCapability` | Transport capability paired with the counter. | | `WithContextObservation` | Context pressure without automatic compaction. | | `WithCompaction` | Explicit 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](/docs/rig). --- # Tools > Tools are capabilities you choose for a Loop. Tools are capabilities you choose for a Loop. The harness defines the contracts. The optional `github.com/looprig/tools` module provides standard implementations. You can use those implementations, write your own, or combine both. ## Use a standard tool Each standard tool is its own definition. Select only what the Loop needs: The read tools take a `loop.ReadGuard`: the narrow read-side policy they enforce themselves. It filters denied paths and caps per-file reads, and is deliberately stdlib-typed so a consumer can derive it from the same sandbox profile that drives OS enforcement. ```go // loop.ReadGuard: DeniedRead(absPath) bool and MaxReadBytes() int64. type readGuard struct{} func (readGuard) DeniedRead(absPath string) bool { return false } func (readGuard) MaxReadBytes() int64 { return 10 << 20 } guard := readGuard{} definition, err := loop.Define( loop.WithName("reviewer"), loop.WithInference(client, model), loop.WithSystem("Review the change and report findings. Do not edit it."), loop.WithTools( tools.ReadFileDefinition(guard), tools.GlobDefinition(guard), tools.GrepDefinition(guard), ), ) ``` This reviewer receives no WriteFile or EditFile tool. Nothing constructs and discards tools behind the scenes. For a coding Loop, add the individual capabilities it needs: ```go loop.WithTools( tools.ReadFileDefinition(guard), tools.WriteFileDefinition(), tools.EditFileDefinition(), tools.GlobDefinition(guard), tools.GrepDefinition(guard), tools.Bash(bash.WithRunner(executor)), tools.TodoDefinition(), tools.AskUserDefinition(), ) ``` File definitions share observations and mutation coordination through the Loop's workspace binding. ReadFile can authorize a safe compare-and-swap edit, and Bash can invalidate those observations after an opaque workspace mutation. ## Permissions are separate from tool selection Adding a tool makes it available to the Loop. An access gate then decides, per prepared tool call, whether each required capability runs automatically, asks a person, or is denied. Install one with `loop.WithAccessGate`; without it every tool call fails closed. The gate evaluates the three access states — `Deny`, `Gated`, `Allow` — for the capabilities a call needs (`command.execute`, `filesystem.read`, `filesystem.write`, `network`). Each capability is routed to an `AccessSource`; a `*sandbox.Profile` satisfies that structural seam directly, so the same profile that drives OS enforcement also decides access: ```go 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, // gate.RuleMatcher: durable workspace rules loop.GateApprover(), // gate.Approver: resolves one combined prompt in the live loop ruleStore, // gate.RuleWriter: persists "always for this workspace" rules executor, // gate.GrantIssuer: *sandbox.Executor mints post-approval grants ) if err != nil { return err } definition, err := loop.Define( // ... loop.WithAccessGate(evaluator), loop.WithPolicyRevision("workspace-policy-v1"), ) ``` A `Gated` capability uses a matching saved rule or produces one combined approval offering exactly three actions: `Approve`, `Approve always for this workspace`, and `Deny`. Use `gate.NewHeadlessEvaluator(bindings, ruleStore, executor)` for a non-interactive run: it never prompts and returns a typed approval-required denial for any unmet `Gated` capability. The `ruleStore` is the single hardened workspace permission file from `tools` (`permission.NewWorkspaceStore`), which is both the rule matcher and writer. Use `github.com/looprig/sandbox` when Bash or another command tool should run behind the looprig OS sandbox. Build a `sandbox.Profile`, create an `ExecutorSet`, and pass the per-Loop executor to Bash with `tools.Bash(bash.WithRunner(executor))`. Because that same `*sandbox.Executor` is the gate's grant issuer, approvals and OS enforcement stay aligned. See [Build larger systems](/docs/larger-systems#confine-commands-with-the-os). ## Create your own tool A tool implements two methods: ```go type Clock struct{} func (Clock) Info(context.Context) (*tool.ToolInfo, error) { return &tool.ToolInfo{ Name: "Clock", Desc: "Return the current UTC time.", Schema: json.RawMessage(`{ "type": "object", "additionalProperties": false }`), }, nil } func (Clock) InvokableRun(ctx context.Context, argsJSON string) (*tool.ToolResult, error) { if err := ctx.Err(); err != nil { return nil, err } var args struct{} if err := json.Unmarshal([]byte(argsJSON), &args); err != nil { return tool.TextResult("error: invalid arguments"), nil } return tool.TextResult(time.Now().UTC().Format(time.RFC3339)), nil } ``` Wrap the concrete tool in an immutable definition: ```go clockDefinition := tool.NewDefinition( "Clock", 0, func(context.Context, tool.Bindings) ([]tool.InvokableTool, error) { return []tool.InvokableTool{Clock{}}, nil }, ) ``` The definition name and `Info().Name` must match. Build a fresh concrete instance for each binding when the tool has mutable state. Use `tool.RequiresWorkspace` when the factory reads `bindings.Workspace`. Use `tool.RequiresDelegateController` only for a delegation control tool. ## Effectful tools Implement `tool.CallPreparer` when a tool can mutate state, execute code, reach a network, spend money, or otherwise needs judgment. Its `PrepareCall` decodes and validates the arguments once, normalizes and canonicalizes the resources involved, and emits one typed `tool.Request` listing the capability requirements the call needs. The tool classifies capabilities; it never decides `Deny`, `Gated`, or `Allow` — that belongs to the access gate. Each requirement's display text should contain the minimum information a person needs to decide, without leaking secrets. Implement `tool.Auditable` for a stable, redacted summary. Never place credentials, file contents, or untrusted response bodies in an audit summary. Tool execution must honor context cancellation. Validate model-supplied JSON before using it, contain paths before filesystem access, and fail closed when permission or safety state is ambiguous. ## Test a tool At minimum, test: - metadata and JSON Schema; - valid execution; - malformed and missing arguments; - context cancellation; - permission request construction for effects; - redacted audit output; - workspace containment for filesystem tools; - concurrency when instances share state. The standard tools module is a reference implementation, not a requirement. Your Rig owns its capabilities. --- # Rig: assemble an agent system > A Rig is the immutable blueprint for an agent system. A Rig is the immutable blueprint for an agent system. It combines one or more [Loops](/docs/loop) with durable session storage, workspace placement, snapshots, delegation limits, gates, and lifecycle policy. The harness provides the parts. You assemble a Rig that matches what you want your agents to do. ```text loop.Definition values + stores + policy | v rig.Define(options...) | v *Rig / \ NewSession RestoreSession | | +--------+-------+ v session.SessionController ``` Define the Rig once and reuse it across concurrent Sessions. Each Session gets its own live Loops, journal, selected access profile, and resolved workspace. ## The smallest Rig ```go store, err := sessionstore.Open(memstore.New()) if err != nil { return err } assembly, err := rig.Define( rig.WithLoops(assistant), rig.WithPrimers("assistant"), rig.WithSessionStore(store), ) if err != nil { return err } ``` This is enough for an agent with no workspace-bound tools. The complete running form is in the [quickstart](/docs/quickstart). Every Rig requires: - at least one Loop; - at least one primer; - one session store. When there is exactly one primer, it becomes active automatically. A Rig with multiple primers must select one with `WithActivePrimer`. ## Loops, primers, and the active primer A **Loop** defines one agent. A **primer** is a Loop that may act as the top-level agent for a Session. The **active primer** is the one that receives a plain `Session.Submit` call. ```go assembly, err := rig.Define( rig.WithLoops(planner, operator), rig.WithPrimers("planner", "operator"), rig.WithActivePrimer("planner"), rig.WithSessionStore(store), ) ``` A Session can switch between declared primers through `SetActiveLoop`. It can also address any live Loop directly through `SubmitToLoop`. Rig validates the complete topology: - every Loop name is present and unique; - every primer names a declared Loop; - the active primer is one of the primers; - every declared delegate exists; - every Loop is reachable from a primer. An invalid or orphaned agent system never reaches runtime. ## Create and restore Sessions Create a new execution: ```go session, err := assembly.NewSession(ctx) ``` Restore an existing execution from its durable session identifier: ```go session, err := assembly.RestoreSession(ctx, sessionID) ``` Both methods return `session.SessionController`. The controller is the trusted runtime surface for submitting work, observing events, answering gates, changing policy, checkpointing a workspace, and shutting down. See [Session](/docs/session). `RestoreSession` compares the stored configuration fingerprint with the current Rig. A changed topology, model, prompt, tool set, policy revision, workspace placement, or other fingerprinted behavior is rejected instead of being silently applied to old history. Build a dedicated restore Rig with `WithAllowConfigMismatch` only when your application has deliberately reviewed and accepted that drift. ## Session storage `WithSessionStore` accepts a `*sessionstore.Store`. That facade records session journals, leases, catalog state, and offloaded content over the neutral `storage.Composite` contract. For a process-local prototype: ```go sessionStore, err := sessionstore.Open(memstore.New()) ``` For history that survives restart, use `fsstore` or `natsstore`, then open the same facade over that backend. See [Choose durable storage](/docs/larger-systems#choose-durable-storage). The session store is required even when your agent has no filesystem workspace. Session durability and workspace durability are related but separate concerns. ## Workspace placement A workspace gives tools a filesystem root and lets the harness capture files as content-addressed snapshots. A Rig may configure at most one placement mode. ### Exclusive workspace ```go rig.WithExclusiveWorkspace(workspaceStore, root, leaser) ``` Every Session uses the same canonical root, such as the checkout currently open in an editor. The harness holds an exclusive root lease, so two Sessions cannot silently mutate the same tree at once. ### Per-session workspaces ```go rig.WithSessionWorkspaces(workspaceStore, baseDir) ``` Each Session receives `baseDir/`. Isolation comes from separate directories, so no root lease is needed. ### Shared workspace ```go rig.WithSharedWorkspace(workspaceStore, root) ``` Sessions, people, and outside tools may all mutate the same root. No root lease is taken. Snapshots are marked fuzzy because the harness cannot guarantee a stable tree while another writer is active. If any Loop declares a tool with `tool.RequiresWorkspace`, the Rig must configure a placement. The definition fails otherwise. The Rig also rejects a workspace that contains its own session or workspace storage. This prevents a journal append from changing the tree while that tree is being captured. ## Snapshots Every configured workspace requires an explicit snapshot policy: ```go rig.WithSnapshots(rig.SnapshotPolicy{ Trigger: rig.SnapshotOnIdle, Priority: rig.SnapshotBestEffort, Timeout: 60 * time.Second, }) ``` Available triggers are: - `SnapshotManual`; - `SnapshotOnIdle`; - `SnapshotOnTurnDone`; - `SnapshotOnStepDone`. `SnapshotBestEffort` reports a failed automatic snapshot but lets agent work continue. `SnapshotRequired` turns snapshot failure into a hard failure. Required snapshots are not allowed with a shared workspace because atomic capture cannot be guaranteed there. The zero trigger defaults to `SnapshotOnIdle`, and a zero timeout defaults to 60 seconds. A snapshot policy without a workspace is rejected. You can also checkpoint explicitly through `SessionController.CheckpointWorkspace`. ## Seed a new Session from a snapshot `WithSeedSnapshot` is the only current per-call `NewSession` option: ```go session, err := assembly.NewSession(ctx, rig.WithSeedSnapshot(ref)) ``` The snapshot is materialized before any Loop starts and becomes the first workspace checkpoint in the new journal. Seeding is valid for a per-session workspace or an empty exclusive root, never a shared root. ## Delegation limits Loop definitions control which agents may delegate to which. The Rig adds system-wide limits: ```go rig.WithDelegationLimits(rig.DelegationLimits{ Depth: 2, Quota: 8, }) ``` - `Depth` bounds nesting below a primer. - `Quota` bounds the total number of child Loops created in one Session. These are backstops, not a replacement for a narrow delegate list on each Loop. ## Gate limits Bound the permission gates a Session may hold open: ```go rig.WithGateCaps(rig.GateCaps{ MaxOpen: 8, MaxTimeout: 15 * time.Minute, }) ``` Command authority itself is fixed for a Session by the access profile its Loops run under, not by a runtime ordinal. A Session is opened with one profile; a different profile requires opening a new Session. See [Build larger systems](/docs/larger-systems#confine-commands-with-the-os) for how a profile drives both the access gate and OS enforcement. ## Configuration fingerprints The Rig automatically fingerprints Loop topology and behavior. Add application configuration that affects behavior but is not already visible to the harness: ```go rig.WithFingerprintFields(rig.ConfigFingerprintFields{ AgentKind: "personal-research-v2", }) ``` Use only secret-free, stable values. Fingerprints are compatibility identities, not storage for API keys or credentials. ## Hustles and compaction A Hustle is a bounded, text-only inference job outside the normal agent turn lane. Context compaction is one consumer of this mechanism. Register Hustles and their required lane limits together: ```go assembly, err := rig.Define( rig.WithLoops(assistant), rig.WithPrimers("assistant"), rig.WithSessionStore(store), rig.WithHustles(compactor), rig.WithHustleLimits(rig.HustleLimits{ BlockingConcurrent: 2, BlockingQueued: 16, BackgroundConcurrent: 2, BackgroundQueued: 32, AuditTimeout: 5 * time.Second, FinalizationTimeout: 5 * time.Second, WorkerDrainTimeout: 10 * time.Second, }), ) ``` Hustle limits are required when any Hustle is registered and rejected when no Hustle uses them. The Rig also verifies that every Loop compaction policy names a compatible blocking Hustle. ## Other lifecycle policy ```go rig.WithOffloadGC(rig.OffloadGCPolicy{ Interval: time.Hour, Timeout: time.Minute, }) ``` This runs periodic garbage collection for orphaned session offload blobs. Both values must be positive. ```go rig.WithForeignBuilders(liveBuilder, restoredBuilder) ``` This supplies the construction seams required by Loops that use a foreign Claude or Codex engine. Both builders are required together. ## Rig option reference | Option | Requirement | Purpose | | --- | --- | --- | | `WithLoops` | Required | Add immutable Loop definitions. Accumulates across calls. | | `WithPrimers` | Required | Declare Loops eligible to receive top-level input. | | `WithActivePrimer` | Required with multiple primers | Select the initial active primer. | | `WithSessionStore` | Required, once | Supply durable session storage. | | `WithExclusiveWorkspace` | Optional placement | Use one exclusively leased root. | | `WithSessionWorkspaces` | Optional placement | Create one directory per Session. | | `WithSharedWorkspace` | Optional placement | Use a root shared with other writers. | | `WithSnapshots` | Required with placement | Configure automatic workspace snapshots. | | `WithDelegationLimits` | Optional, once | Bound child depth and total child count. | | `WithGateCaps` | Optional, once | Bound open gates and gate timeouts. | | `WithFingerprintFields` | Optional, once | Add stable application behavior to restore compatibility. | | `WithHustles` | Optional | Register immutable Hustle definitions. Accumulates across calls. | | `WithHustleLimits` | Required with Hustles | Bound execution lanes, queues, audit, finalization, and drain. | | `WithForeignBuilders` | Required for foreign engines | Build new and restored foreign Loops. | | `WithOffloadGC` | Optional, once | Collect orphaned offload blobs periodically. | | `WithAllowConfigMismatch` | Restore-only opt-in | Accept reviewed configuration drift. | `rig.Define` validates and freezes the entire assembly. Definition, workspace, snapshot, persistence-overlap, and lifecycle failures retain typed error identity for `errors.As`. Next, learn how to [drive a live Session](/docs/session). --- # Session: run and control a Rig > A Session is one live execution created by a Rig. A Session is one live execution created by a [Rig](/docs/rig). It owns the runtime state that should not be shared between executions: - live Loop actors; - active turns and tool calls; - durable event history; - permission gates; - the resolved workspace and its checkpoints. The Rig is reusable. The Session is the running thing. ## Create or restore ```go session, err := assembly.NewSession(ctx) ``` `NewSession` returns a `session.SessionController` with a newly generated session identifier. ```go session, err := assembly.RestoreSession(ctx, sessionID) ``` `RestoreSession` rebuilds live runtime state from the durable journal. It does not create a second history. It resumes the existing session identity after checking that the current Rig is compatible with the stored configuration. The caller owns the returned controller and must shut it down: ```go defer func() { shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = session.Shutdown(shutdownCtx) }() ``` ## Subscribe before submitting Attach an event consumer before starting work so you do not miss live streaming events: ```go stream, err := session.SubscribeEvents(event.EventFilter{ Ephemeral: event.LoopScope{All: true}, Enduring: event.LoopScope{All: true}, }) if err != nil { return err } defer stream.Close() ``` An event filter has separate scopes for two classes: - **Ephemeral events** report live progress such as token deltas and tool-call lifecycle. They are not journaled. - **Enduring events** report accepted input, turns, steps, gates, configuration changes, workspace transitions, and terminal outcomes. They survive restore. `event.LoopScope{All: true}` selects every Loop. To follow only specific Loops, populate the `Loops` map with their identifiers. Consume the channel continuously. A subscription is bounded so one stalled consumer cannot block the Session. After the channel closes, call `stream.Err()` to distinguish an intentional close from a forced loss. ## Submit input Send human-authored input to the active primer: ```go inputID, err := session.Submit(ctx, []content.Block{ &content.TextBlock{Text: "Plan my week around these three goals."}, }) ``` `Submit` is fire-and-forget. A nil error means the input was sent to the Loop, not that the turn succeeded. The returned `inputID` is the correlation ID carried by the resulting reply events. The event stream reports whether the input was queued, started, folded into other input, rejected, cancelled, completed, failed, or interrupted. To address a specific live Loop instead of the active primer: ```go inputID, err := session.SubmitToLoop(ctx, loopID, blocks) ``` This is useful for interfaces that focus different agents within one Session. It does not change which primer is active. ## Content blocks Input is an ordered slice of `content.Block` values. The shared `core/content` package defines: - `TextBlock`; - `ImageBlock`; - `AudioBlock`; - `DocumentBlock`; - `ThinkingBlock`; - `ToolUseBlock`; - `ToolResultBlock`. Ordinary user input usually contains text, images, audio, or documents. Declare matching model capabilities with `inference.WithImages` and other model options where appropriate. Provider codecs reject unsupported shapes rather than silently dropping them. ## Read events ```go for delivery := range stream.Events() { switch ev := delivery.Event.(type) { case event.TokenDelta: if text, ok := ev.Chunk.(*content.TextChunk); ok { fmt.Print(text.Text) } case event.GateOpened: // Present ev.Gate to a person or a policy resolver. case event.TurnDone: // ev.Message is the complete assistant response. case event.TurnFailed: // ev.Err is the typed live failure cause when available. case event.TurnInterrupted: // The turn ended through cancellation. } } if err := stream.Err(); err != nil { return err } ``` Each `event.Delivery` contains: - `Event`, a member of the sealed event union; - `JournalSeq`, which is zero for ephemeral events and the durable sequence for enduring events. Use concrete event types rather than parsing event names or error strings. ## Answer permission gates The access gate may decide that a tool call needs human approval. The Session emits `event.GateOpened` and pauses the affected work without blocking the rest of the process. A permission gate carries one combined request listing the command and every capability that still needs approval. A permission gate is answered with exactly one of three actions. Approve the call once: ```go err := session.RespondGate(ctx, gate.GateResponse{ GateID: opened.Gate.ID, Action: string(gate.ApprovalApprove), Source: gate.ResponseSource{Kind: gate.ResponseFromUser}, }) ``` Approve it and persist the displayed reusable allow rules for this workspace: ```go err := session.RespondGate(ctx, gate.GateResponse{ GateID: opened.Gate.ID, Action: string(gate.ApprovalApproveAlwaysWorkspace), Source: gate.ResponseSource{Kind: gate.ResponseFromUser}, }) ``` Deny it: ```go err := session.RespondGate(ctx, gate.GateResponse{ GateID: opened.Gate.ID, Action: string(gate.ApprovalDeny), Source: gate.ResponseSource{Kind: gate.ResponseFromUser}, }) ``` The three exact actions are `gate.ApprovalApprove`, `gate.ApprovalApproveAlwaysWorkspace`, and `gate.ApprovalDeny`. There is no session or user-global approval scope: `Approve` writes nothing, and `Approve always for this workspace` atomically appends the reusable rules to the single workspace permission file. A saved deny always beats a saved allow. Gates are durable. A restorable gate can remain open across process restart and be answered after `RestoreSession` reconstructs it. ## Interrupt work ```go interrupted, err := session.Interrupt(ctx) ``` This requests cancellation of every in-flight turn in the Session and reports whether any running work was actually interrupted. The durable event stream reports the resulting terminal outcomes. For one Loop subtree, obtain its trusted controller and call `Interrupt`: ```go controller, ok := session.LoopController(loopID) if ok { err = controller.Interrupt(ctx) } ``` ## Select and change Loops Inspect the current primer: ```go active := session.ActiveLoop() fmt.Println(active.ID(), active.Mode(), active.Model().Name) ``` Switch the active primer by live Loop identifier: ```go err := session.SetActiveLoop(ctx, anotherPrimerID) ``` Change one Loop's declared mode: ```go controller, ok := session.LoopController(loopID) if !ok { return errors.New("loop not found") } err := controller.SetMode(ctx, "deep-review") ``` Or change its model and effort atomically: ```go err := controller.Change( ctx, loop.ChangeModel(newModel), loop.ChangeEffort(model.EffortHigh), ) ``` Changes apply at a turn boundary and produce enduring events. ## Compact context Request manual compaction on the active Loop: ```go compactID, err := session.Compact(ctx) ``` Or target one Loop: ```go compactID, err := session.CompactToLoop(ctx, loopID) ``` The returned ID correlates durable compaction outcomes. Compaction is supported only for native Loops configured with an explicit compaction policy and a compatible Hustle registered on the Rig. ## Checkpoint and restore the workspace Create an explicit content-addressed snapshot: ```go ref, err := session.CheckpointWorkspace(ctx) ``` Restore the current Session workspace to an earlier reference: ```go err := session.RestoreWorkspace(ctx, ref) ``` Both operations require a configured workspace placement. Workspace restoration is a control-plane action and should be exposed only to trusted callers. ## Public interfaces The ordinary data plane is: ```go type Session interface { SessionID() uuid.UUID ActiveLoop() loop.Handle Loop(uuid.UUID) (loop.Handle, bool) Submit(context.Context, []content.Block) (uuid.UUID, error) SubmitToLoop(context.Context, uuid.UUID, []content.Block) (uuid.UUID, error) Compact(context.Context) (uuid.UUID, error) CompactToLoop(context.Context, uuid.UUID) (uuid.UUID, error) SubscribeEvents(event.EventFilter) (event.Subscription, error) RespondGate(context.Context, gate.GateResponse) error Interrupt(context.Context) (bool, error) } ``` The trusted control plane adds: ```go type SessionController interface { Session SetActiveLoop(context.Context, uuid.UUID) error LoopController(uuid.UUID) (loop.Controller, bool) CheckpointWorkspace(context.Context) (workspacestore.Ref, error) RestoreWorkspace(context.Context, workspacestore.Ref) error Shutdown(context.Context) error } ``` Prefer the narrower `session.Session` interface in code that does not need trusted policy or lifecycle operations. Next, [add persistence, tools, multiple agents, and deployment surfaces](/docs/larger-systems). --- # Build larger systems > The quickstart is already a complete looprig system. The [quickstart](/docs/quickstart) is already a complete looprig system. Larger systems use the same composition order and add only the capabilities they need: ```text model and client | v one or more Loops | v Rig + storage + workspace + policy | v Sessions + your interface ``` This guide shows the main additions independently. You can combine them in one application without adopting all of them. ## Choose durable storage The quickstart uses `memstore`, which disappears with the process. Use `fsstore` when one process should keep its history and snapshots on local disk: ```go backend, err := fsstore.Open(fsstore.Options{ Root: storageRoot, }) if err != nil { return err } defer func() { _ = backend.Close() }() sessionStore, err := sessionstore.Open(backend.Backend()) if err != nil { return err } workspaceStore, err := workspacestore.Open(backend.Blobs) if err != nil { return err } ``` `sessionStore` persists journals, leases, catalog state, and offloaded content. `workspaceStore` uses the same blob backend for content-addressed workspace snapshots. They are separate facades even when they share one backend. Keep the storage root outside the workspace being captured: ```text /srv/my-agent/state session and snapshot storage /srv/my-agent/workspaces agent workspaces ``` The Rig rejects overlapping persistence and workspace paths because writing a journal must not change the tree being snapshotted. For multiple processes or hosts, use JetStream through `natsstore`: ```go backend, err := natsstore.Open(ctx, natsstore.Options{ URL: "nats://nats.internal:4222", }) if err != nil { return err } defer func() { _ = backend.Close(context.Background()) }() sessionStore, err := sessionstore.Open(backend.Backend()) ``` Set exactly one of `URL` or `EmbeddedDir`. Embedded mode owns an in-process NATS server and requires an absolute directory. The caller must ensure that only one embedded store opens that directory at a time. `rclonestore` implements the blob contract through an rclone remote. It is useful for snapshot or offload data, but it does not provide the ledger, lease, and KV contracts required by `sessionstore`. Combine it with providers for those contracts through `storage.NewComposite` when you intentionally want a hybrid backend. | Backend | Best fit | Contracts | | --- | --- | --- | | `memstore` | tests and process-local prototypes | ledger, leases, KV, blobs | | `fsstore` | one host with local durable state | ledger, leases, KV, blobs | | `natsstore` | multiple processes or hosts | ledger, leases, KV, blobs | | `rclonestore` | remote snapshot and offload blobs | blobs only | Your application owns each backend's lifecycle. Close it only after every Session using it has shut down. ## Give an agent a workspace Workspace-bound tools need four pieces: 1. tool definitions on the Loop; 2. an access profile and its OS executor; 3. an access gate on the Loop; 4. a workspace placement and snapshot policy on the Rig. Call `sandbox.Init()` as the first statement in `main` (see below), then build a read guard, an access profile, its executor, and one gate for the Loop: ```go // A read guard is the narrow read-side policy the file tools enforce // themselves: loop.ReadGuard is DeniedRead(absPath) bool and MaxReadBytes() int64. type readGuard struct{} func (readGuard) DeniedRead(absPath string) bool { return false } func (readGuard) MaxReadBytes() int64 { return 10 << 20 } guard := readGuard{} // A sandbox profile chooses each capability's access state directly. There are // no named modes or presets: the consumer sets every field. profile, err := sandbox.NewProfile(sandbox.ProfileConfig{ WorkspaceRoot: workspaceRoot, WorkspaceRead: sandbox.Allow, WorkspaceWrite: sandbox.Allow, HostRead: sandbox.Deny, HostWrite: sandbox.Deny, Network: sandbox.Deny, Command: sandbox.Gated, Home: sandbox.IsolatedHome, Isolation: sandbox.Sandboxed, }) if err != nil { return err } // An ExecutorSet memoizes one OS-enforcing executor per opaque key, each with // its own isolated HOME and grant identity beneath the scratch root. executors, err := sandbox.NewExecutorSet( profile, sandbox.WithScratchRoot(scratchRoot), sandbox.WithMaxExecutors(8), ) if err != nil { return err } defer func() { _ = executors.Close() }() executor, err := executors.For("workspace-assistant") if err != nil { return err } // The single hardened workspace permission file is the gate's rule store. ruleStore, diagnostics, err := permission.NewWorkspaceStore(permission.Config{ Path: permissionFilePath, // one explicit absolute path; never discovered }) if err != nil { return err } _ = diagnostics // surface manual-rule diagnostics to the operator // The gate applies Deny/Gated/Allow per capability. *sandbox.Profile is the // AccessSource; the *sandbox.Executor mints post-approval grants. 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, loop.GateApprover(), ruleStore, executor, ) if err != nil { return err } assistant, err := loop.Define( loop.WithName("workspace-assistant"), loop.WithInference(client, model), loop.WithSystem("Help with files in the assigned workspace."), loop.WithTools( tools.ReadFileDefinition(guard), tools.WriteFileDefinition(), tools.EditFileDefinition(), tools.Bash(bash.WithRunner(executor)), ), loop.WithAccessGate(evaluator), loop.WithPolicyRevision("workspace-policy-v1"), ) ``` The Loop receives only the definitions listed above. `tools.Bash` supplies command execution behind the sandbox executor. The model declaration must include `model.WithTools()` so the provider codec knows tool calls are supported. The read guard is enforced inside the file tools. The access gate decides whether each proposed tool call is automatically allowed, shown to a person, or denied. Without an access gate, tool execution fails closed. Now place the workspace on the Rig: ```go assembly, err := rig.Define( rig.WithLoops(assistant), rig.WithPrimers("workspace-assistant"), rig.WithSessionStore(sessionStore), rig.WithExclusiveWorkspace( workspaceStore, workspaceRoot, backend.Leaser, ), rig.WithSnapshots(rig.SnapshotPolicy{ Trigger: rig.SnapshotOnTurnDone, Priority: rig.SnapshotBestEffort, Timeout: 60 * time.Second, }), ) ``` Use exclusive placement for one canonical checkout, per-session placement for an isolated directory per Session, or shared placement when outside writers must touch the same tree. [Rig](/docs/rig#workspace-placement) describes the tradeoffs. ## Confine commands with the OS The access gate answers whether a command may run. The `sandbox` module limits what the resulting process can read, write, execute, and reach over the network. The two layers share one `sandbox.Profile`: it is the gate's `AccessSource` and the source of truth for OS enforcement, so a decision and its enforcement cannot drift. Call `sandbox.Init()` as the first statement in `main`. This lets the module perform platform setup (a re-executed Linux confinement helper) before the application starts goroutines. On other platforms it is a no-op: ```go func main() { sandbox.Init() if err := run(); err != nil { log.Fatal(err) } } ``` A profile chooses an access state — `Deny`, `Gated`, or `Allow` — for each capability directly. There are no reusable named modes or presets; the consumer sets every field. The workspace example above builds one such profile. A product typically defines a small set of named profiles from these fields. CodeRig, for instance, exposes three: | Capability | ReadOnly | Trusted | Unconfined | | --- | --- | --- | --- | | Workspace read | `Allow` | `Allow` | `Allow` | | Workspace write | `Deny` | `Allow` | `Allow` | | Host read | `Deny` | `Allow` | `Allow` | | Host write | `Deny` | `Gated` | `Allow` | | Network | `Deny` | `Allow` | `Allow` | | Command execution | `Gated` | `Allow` | `Allow` | | Command HOME | `IsolatedHome` | `IsolatedHome` | `RealHome` | | Isolation | `Sandboxed` | `Sandboxed` | `Unconfined` | `Sandboxed` execution compiles the profile into the strongest available OS boundary — Seatbelt on macOS, namespaces/Landlock/seccomp/nftables/cgroups on Linux — and construction reports the guarantees actually achieved, failing closed when a required one is unavailable. A `Gated` capability stays OS-blocked until a per-spawn grant opens the exact approved delta. `Unconfined` runs with the invoking user's authority, requires every filesystem and network field to be `Allow`, and requires `AckUnconfined`. Restrict a role below the selected profile with `sandbox.Restrict(base, ceiling)`, which returns the component-wise, immutable intersection and never widens `base`. Target-scoped Bash network access is enforced by a loopback egress proxy the sandbox owns; the child reaches only that listener and direct remote egress is denied. ## Add more agents Define each role as an ordinary Loop. Give only the parent a delegate list: ```go 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, }), loop.WithAccessGate(plannerGate), loop.WithPolicyRevision("planner-delegation-v1"), ) researcher, err := loop.Define( loop.WithName("researcher"), loop.WithInference(client, model), loop.WithSystem("Research one focused question and return evidence."), ) assembly, err := rig.Define( rig.WithLoops(planner, researcher), rig.WithPrimers("planner"), rig.WithSessionStore(sessionStore), rig.WithDelegationLimits(rig.DelegationLimits{ Depth: 2, Quota: 8, }), ) ``` The harness injects one scoped Subagent tool into `planner`. Do not add that tool yourself. The frozen delegate list controls which Loop definitions the parent can reach, while Rig limits bound total depth and child count. The parent model must declare `model.WithTools()` because the injected Subagent definition is model-facing. The Subagent tool still passes through the parent's access gate (`plannerGate`), constructed as shown earlier. With no matching saved rule, each delegate call produces one approval; a saved allow rule can approve known delegate calls, and a deny rule refuses delegation outright. ## Expose Sessions over HTTP The `serve` package turns a Rig into a small HTTP and server-sent events surface. It separates live Session control from durable catalog reads: ```go catalog := sessionStore.OpenCatalog() reads := catalogreader.New(catalog, sessionStore) handler := serve.Handler(assembly, reads) server, err := serve.Server("127.0.0.1:8080", handler) if err != nil { return err } go func() { if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("looprig server stopped: %v", err) } }() ``` The surface can create and restore Sessions, submit input, interrupt work, answer gates, stream events, and read session status or journal history. The returned value is a standard `*http.Server`; your application owns startup and graceful shutdown: ```go shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = server.Shutdown(shutdownCtx) ``` A loopback bind may omit authentication for local use. A public bind is refused unless the handler has `serve.WithAuth` or the caller explicitly acknowledges an authenticating external proxy with `serve.WithInsecurePublicBind`. ## Add an interface The harness does not choose how people interact with a Session. A consumer can use the Session interface directly from a CLI, desktop application, service, chat integration, scheduled job, or another Go package. The `tui` module supplies an interactive terminal UI and accepts an adapter through its `tui.Agent` interface. The `serve` package supplies HTTP for remote or multi-process interfaces. Both are optional presentation layers over the same Session behavior. ## Coordinate durable workflows The `flow` module is a sibling workflow engine for durable, Pregel-style graphs. Use it when work has explicit vertices, messages, checkpoints, and resumable control flow. Keep agent execution in the harness and workflow coordination in flow, then connect them through application-owned tasks when that separation is useful. Flow is not required to run a Rig, and a Rig does not become more durable merely because it is placed inside a workflow. Each module owns a different boundary. ## Production checklist Before exposing a system to other users or machines, decide explicitly: - which model catalog and credentials the application owns; - which Loops are primers and which may be delegated to; - which tools exist and how every tool call is permitted or denied; - which access profile each Loop's command tools run under; - where journals, leases, KV state, blobs, and workspaces live; - whether snapshots are best effort or required; - how Sessions are restored after process failure; - which callers receive the data plane or trusted control plane; - how HTTP authentication, transport security, and graceful shutdown work; - which policy and fingerprint revisions change when behavior changes. The shape stays the same as the quickstart. The application continues to own its agents, policy, data, and deployment while looprig supplies the runtime machinery. --- # Package overview > looprig is a collection of small Go modules, not one all-inclusive framework. looprig is a collection of small Go modules, not one all-inclusive framework. Applications import the boundaries they need and own the composition between them. ```text your application / / | \ \ v v v v v provider clients harness storage sandbox presentation llm module backends tui / serve | v core + inference + storage contracts flow is a sibling engine for durable workflow graphs ``` The harness stays provider-neutral and backend-neutral. Provider implementations live in `llm`. Durable backend implementations live in the storage modules. The application chooses and wires both. ## Modules | Module | What it owns | Import it when | | --- | --- | --- | | `core` | shared content blocks, identifiers, logging contracts | exchanging content or IDs across looprig modules | | `inference` | provider-neutral invocation contracts, with focused model, stream, codec, context-counting, auth, routing, transport, and failure packages | declaring models or implementing an inference client | | `llm` | provider clients and wire codecs | calling OpenRouter, LM Studio, Bedrock, Google, Phala, Chutes, or another supported provider | | `harness` | Loops, Rigs, Sessions, tool contracts, gates, journals, workspaces, delegation, serving | building and running an agent system | | `tools` | optional standard file, command, web, interaction, skill, and permission implementations | adding selected standard capabilities to a Loop | | `storage` | neutral ledger, lease, KV, and blob contracts plus in-memory implementations | implementing a backend or running in memory | | `fsstore` | filesystem implementations of all four storage contracts | persisting one-host systems on local disk | | `natsstore` | JetStream implementations of all four storage contracts | sharing durable state across processes or hosts | | `rclonestore` | blob storage through rclone | keeping snapshots or offloads on an rclone-supported remote | | `sandbox` | access profiles, OS-level command enforcement, and honest guarantee reports | giving agents command execution with an operating-system boundary | | `flow` | durable graph execution, messages, checkpoints, ingress, and control plane | coordinating explicit resumable workflows | | `tui` | reusable interactive terminal presentation | building a terminal interface over a Session adapter | | `coderig` | a complete coding Rig | studying a production composition or running CodeRig | | `tests` | cross-module integration and compatibility tests | validating development across the repository set | `coderig` and `tests` are useful references, but consumer applications do not need to import them. ## Start with these imports A text-only agent like the quickstart needs: ```text github.com/looprig/core github.com/looprig/harness github.com/looprig/inference github.com/looprig/llm github.com/looprig/storage ``` Add one durable backend when state must survive restart: ```text github.com/looprig/fsstore ``` or: ```text github.com/looprig/natsstore ``` Add `github.com/looprig/tools` for standard tools. Add `github.com/looprig/sandbox` when a Loop can spawn confined commands: its `Profile` drives both the access gate and OS enforcement. Add `github.com/looprig/tui` or the Harness `serve` package only when you want those presentation surfaces. ## Harness package map Most consumer code uses a small part of the harness module: | Package | Consumer role | | --- | --- | | `pkg/loop` | define one agent's identity, inference, instructions, tools, permissions, modes, and delegates | | `pkg/rig` | validate and assemble Loops with storage, workspaces, snapshots, limits, and lifecycle policy | | `pkg/session` | submit work, observe events, answer gates, interrupt, compact, checkpoint, and control one live execution | | `pkg/event` | consume typed ephemeral and enduring runtime events | | `pkg/tool` | define tool contracts, bindings, prepared requests, capability requirements, and command runners | | `pkg/gate` | evaluate the three access states and answer durable permission gates | | `pkg/sessionstore` | open durable Session journals, offloads, leases, and catalog state over `storage.Composite` | | `pkg/workspacestore` | capture and materialize content-addressed workspace snapshots over `storage.Blobs` | | `pkg/serve` | expose live and durable Session operations over HTTP and server-sent events | | `pkg/serve/catalogreader` | connect the durable session catalog to the HTTP read plane | | `pkg/hustle` | define bounded inference jobs outside the normal turn lane, including compaction work | | `pkg/foreignloop` | integrate supported foreign Claude or Codex execution engines | The normal construction path is: ```text loop.Define | v rig.Define | +-------------------+ v v NewSession RestoreSession | | +---------+---------+ v session.Session ``` Read [Loop](/docs/loop), [Rig](/docs/rig), and [Session](/docs/session) in that order when you need to understand those boundaries in detail. ## Inference packages `model.Model` from `github.com/looprig/inference/model` describes a model without secrets. `inference.Client` performs provider calls. Keeping them separate lets a model declaration participate in validation and fingerprints without putting credentials into durable state. The `llm` module contains concrete clients and an `auto` package for providers that can be constructed from a model plus API key. Providers with extra security or credential requirements expose direct constructors. Applications own their model catalog. Declare capabilities such as tool or image support on each model, and keep credentials on the client side of the boundary. ## Storage packages The `storage` module defines four independent contracts: - `Ledger` for ordered append-only records; - `Leaser` for ownership and coordination; - `KV` for revisioned keyed state; - `Blobs` for content-addressed or named binary objects. `storage.Composite` bundles one implementation of each contract because their method names overlap and cannot be implemented by one ordinary Go interface. The harness uses them through two facades: ```text storage.Composite --> sessionstore.Store storage.Blobs --> workspacestore.Store ``` This means Session history and workspace snapshots can share a backend or use different ones. [Build larger systems](/docs/larger-systems#choose-durable-storage) shows the standard choices. ## Harness and flow The harness and flow solve different problems: | Harness | Flow | | --- | --- | | runs model-driven Loops | runs explicit graph vertices | | records agent turns, steps, gates, and tool activity | records graph messages and checkpoints | | supports delegation chosen by an agent | supports routing chosen by graph structure | | exposes Session control | exposes workflow ingress and control | Use the harness alone for an agent or multi-agent system. Use flow alone for a durable graph without agents. Combine them in your application when a workflow vertex should start or resume agent work. ## Presentation is optional The main harness boundary is a Go `session.Session`. Nothing requires a specific interface. You can drive it from: - a one-shot command; - a terminal or desktop application; - an HTTP service; - a scheduled process; - a chat integration; - a workflow task; - another Go library. Use the `tui` module when its terminal adapter fits. Use `pkg/serve` when its HTTP surface fits. Otherwise keep the Session interface and build the interaction model your product needs. ## What the application owns Across every module, your application remains the composition root. It owns: - model selection and provider credentials; - prompts, tools, permissions, and policy revisions; - Loop topology and delegation limits; - storage placement and retention; - workspace placement and the access profile command tools run under; - interfaces, authentication, and deployment; - restore compatibility and configuration migrations. looprig supplies the reusable machinery and validates the boundaries. The Rig you assemble remains yours to run, change, and extend. --- # looprig Glossary > This glossary defines the shared language used across looprig's code, documentation, and public interfaces. This glossary defines the shared language used across looprig's code, documentation, and public interfaces. It focuses on terms that have a specific meaning inside the project. Capitalized terms such as **Loop**, **Rig**, and **Session** name public runtime concepts. Lowercase terms such as **harness** or **flow** usually name a module or a general idea. Where an item is not implemented yet, its definition is marked **planned**. ## Core Mental Model **Agent.** A model-driven program that can receive input, maintain context, use tools, and produce actions or answers within defined boundaries. **Agent harness.** The machinery that turns model capability into a usable agent system. It coordinates context, tools, permissions, events, storage, recovery, and interfaces around one or more agents. **Agent loop.** The execution cycle in which a model receives context, produces output or requests a tool, receives the tool result, and continues until the Turn ends. A looprig **Loop** is the owned definition and runtime boundary around that cycle. **Agentic system.** The complete system built from agents and the machinery around them. It may include Loops, deterministic tasks, workflows, people, storage, external services, and user interfaces. **looprig.** The overall open source project and ecosystem. It includes the harness, workflow engine, inference stack, storage backends, the sandbox, standard tools, interfaces, and complete Rigs built from those parts. **harness.** The runtime module that provides Loops, Rigs, Sessions, tools, gates, journals, workspaces, delegates, and lifecycle control. The harness provides the parts. A user assembles a Rig. **Loop.** The immutable definition of one agent. A Loop selects the agent's identity, model, instructions, tools, permissions, modes, context policy, and delegation behavior. **Rig.** The agent system a user configures, owns, and runs. A Rig assembles one or more Loops with storage, workspace, access, lifecycle, and delegation policy. looprig is the project. A Rig is what someone builds with it. **Session.** One running or restorable instance of a Rig. A Session owns the live execution state, event history, active Loop, selected access profile, workspace, and pending gates for a body of work. **Turn.** One unit of interaction submitted to a Loop, including the model and tool activity required to complete it. **Step.** One model execution step within a Turn. A step may produce content, request tools, or end the Turn. **Tool.** A named capability a Loop can ask to use, such as reading a file, running a command, searching, or calling an application API. Tools are selected individually and remain under the runtime access gate and OS confinement. **Workflow.** Durable coordination across tasks, agents, people, and external systems. In looprig, workflows are built with the `flow` module and are separate from the model-driven execution inside a Loop. ## Foundational AI Terms **GPU.** Graphics processing unit. GPUs provide the highly parallel computation used to train and run many neural networks. **JEPA.** Joint Embedding Predictive Architecture. A family of neural network architectures that learns by predicting representations rather than directly reconstructing every detail of an input. **Large language model or LLM.** A neural network trained across large amounts of language and other data. LLMs are one form of neural network, not the name for all machine intelligence. **Machine intelligence.** The knowledge and reasoning capability produced by machine learning models. looprig supplies machinery around this capability; it does not create the underlying intelligence. **Multimodal model.** A model that can process or produce more than one kind of content, such as text, images, audio, or documents. **Neural network.** A learned computational system made from connected layers of parameters. Neural networks include LLMs, multimodal models, JEPA systems, and other model families. **RAG.** Retrieval-augmented generation. A pattern that retrieves relevant external information and supplies it to a model as context for a response. **TPU.** Tensor processing unit. A specialized processor built to accelerate the tensor computations used by machine learning models. ## Agents and Runtime **Actor.** A runtime component that owns its mutable state and processes commands serially. Native Loops use this model to keep concurrent activity from creating conflicting state transitions. **Active Loop.** The Loop currently selected to receive top-level user input in a Session. **Agency.** The identity of the actor responsible for an action, such as a person, Loop, delegate, policy, or system component. **Cause.** Correlation information that records what caused an event or action. It lets later work be traced back to its originating Turn, tool call, delegate, or control request. **Context.** The messages, instructions, tool results, summaries, and other content available to a model for an inference request. **Context compaction.** Replacing older context with a smaller durable summary so a Session can continue within the model's context limit. **Context counter.** A component that estimates or exactly counts how many tokens a complete inference request will consume. **Context observation.** A measurement of current context use, including token count, capacity, and occupancy. **Context occupancy.** The proportion of a model's available context window currently in use. It can be used to decide when compaction should begin. **Context window.** The maximum amount of input and generated content a model can consider in one request. **Controller.** A public control surface for changing live state, such as interrupting work, changing a mode, or selecting the active Loop. **Command.** A typed request to change runtime state, such as submitting user input, interrupting a Turn, resolving a Gate, or starting compaction. Commands express intent; Events record what happened. **Coordinates.** Stable identifiers that locate an action or event within its Rig, Session, Loop, Turn, Step, and related execution scope. **Delegate.** A Loop invoked by another Loop to perform bounded work. The parent retains responsibility for the overall task. **Delegation style.** The way a parent may use a Delegate. Synchronous-only delegation returns one bounded result, while managed delegation permits a longer-lived child Loop under Session control. **Delegation depth.** The maximum number of parent-to-child delegation levels allowed in a Session. **Delegation quota.** The maximum amount of delegate work allowed by Rig policy. **Foreign agent.** An agent runtime built outside the native looprig harness, such as Codex or Claude Code, that is adapted into looprig's session and event model. **Foreign Loop.** The looprig representation of a foreign agent. It normalizes the foreign runtime's input, output, tool activity, permissions, and session identity without pretending it is a native Loop. **Foreign session ID.** The session identifier assigned by a foreign agent. It is preserved alongside looprig's own Session identity so either side can resume the same work. **Handle.** The public data-plane surface used to interact with a running Loop or Session without exposing its internal runtime implementation. **Hustle.** Auxiliary, managed model work performed for a Session, such as context compaction or a future classifier. A Hustle is not a user-facing Loop and has its own purpose, limits, inference binding, run identity, and outcome. **Human in the loop.** A design in which work can pause for a person's judgment when authority, ambiguity, or consequences require it. **Inference binding.** The resolved model and inference client assigned to a Loop or Hustle at runtime. **Loop definition.** The immutable recipe used to construct a Loop. Binding the definition resolves its runtime dependencies without changing the recipe. **Loop engine.** The execution mechanism behind a Loop. It may be the native harness engine or a foreign engine such as Claude Code or Codex. **Meta-harness.** A harness that can host and coordinate other agent harnesses. looprig is being built to operate native Loops and foreign agents through the same owned Session, policy, event, storage, workflow, and client model. **Native Loop.** A Loop executed directly by the looprig harness from explicit model, context, tool, permission, and lifecycle parts. **Orchestration.** Coordinating several tasks or actors into one body of work. Loops orchestrate model and tool activity; Rigs coordinate agents; `flow` coordinates durable workflows. **Loop mode.** A named behavioral setting exposed by a Loop. Modes let a user change an approved part of its behavior without replacing the Loop definition. **Memory.** Information an agent can reuse beyond the immediate input. In looprig, this may come from Session history, compacted context, workspace files, or an application-provided store. Memory is not one hidden global subsystem. **Primer.** A Loop that is eligible to receive top-level user input. A Rig can contain several Primers and selects one as the initial active Loop. The active Loop may change during the Session. **Provenance.** Information about where instructions, content, configuration, or actions came from. Provenance supports auditing and policy decisions. **Runtime context.** Session and environment information supplied to a Loop at execution time, separate from its immutable definition. **Quiescence.** The state in which a Session and its participating Loops have no active work. The Event hub federates those activity signals so a caller can reliably wait for the whole Session to become idle. **Single-flight.** An execution rule that permits only one active Turn in a given Loop at a time. **Subagent.** General language for an agent working under another agent. In the harness, managed subagent work is represented through delegation to another Loop. **Transcript.** The ordered human-readable projection of a Session's conversation and tool activity. It is derived from typed runtime history rather than serving as the source of truth for restoration. **System prompt.** The highest-level model instructions selected by a Loop definition. It guides model behavior but does not replace runtime permissions or OS confinement. ## Models, Inference, and Content **API format.** The wire-level request and response shape used to communicate with a model endpoint. OpenAI, Anthropic, and Gemini formats are supported as native formats. A provider and an API format are not the same thing. **Capability.** A declared model feature such as tool use, images, audio, thinking, or structured output. Capabilities let the runtime validate a request before sending it. **Chunk.** One incremental piece of a streaming model response. **Codec.** The component that translates neutral inference requests and responses to and from a specific API format. **Content block.** One typed unit of content in a message. Current block types include text, image, audio, document, thinking, tool use, and tool result. **Conversation.** An ordered collection of messages supplied as model context. **Delta.** A streaming update that adds to or changes the response accumulated so far. **Inference.** Running a model against a request to produce a response. The `inference` module defines the provider-neutral contract for this operation. **Inference client.** An implementation of the neutral invoke and stream contract. It sends requests through a selected transport and API codec. **Message.** A role and an ordered collection of content blocks exchanged in a conversation. **Model.** A secret-free description of the model to invoke, its provider origin, API format, endpoint selection, and capabilities. Credentials are resolved outside the model value. **Model origin.** Provenance describing which provider or route supplied a model, used for validation and policy decisions. **Model SDK.** A library focused on calling one or more model APIs. It commonly normalizes generation, streaming, tools, and structured output, but does not by itself provide the complete durable agent harness. **Multimodal.** Able to work with more than text, including images, audio, and documents. **Provider.** The service or runtime that makes a model available. The `llm` module owns provider-specific authentication, validation, and routing policy. **Router.** A component that directs neutral inference requests to the correct API implementation and transport. **Streaming.** Delivering model output incrementally as chunks instead of waiting for the complete response. **Structured output.** Model output constrained to a declared data shape, usually a JSON schema, so an application can validate and consume it reliably. **Thinking block.** Typed model reasoning content when a provider makes that content available. Its handling depends on provider and Loop policy. **Token usage.** Normalized input, output, cache, and reasoning token counts reported for an inference request. **Transport.** The connection-bound mechanism that carries an inference request, usually HTTP, independently from the request's API format. **Wire format.** The concrete bytes and framing exchanged with an external model API, including JSON, Server-Sent Events, or newline-delimited JSON. **Attestation.** Cryptographic evidence about the software and environment running a confidential inference service. **Confidential inference.** Model inference designed to protect requests and responses from the surrounding service infrastructure through attestation, trusted execution, or end-to-end encryption. **SigV4.** AWS Signature Version 4. The request-signing authentication method used for services such as Amazon Bedrock. **TEE.** Trusted execution environment. Hardware-backed isolation used to run code and handle data with evidence about the executing environment. ## Tools, Permissions, and Safety **Approval scope.** The boundary within which a granted permission remains valid, such as one action, one Turn, one Session, or another explicitly defined scope. **Auditable tool.** A tool that reports the security-relevant operation it is about to perform so the runtime can record and evaluate it. **Classifier.** A component that labels input for policy use. Planned classifiers include detection of prompt injection and other untrusted content. **Access profile.** The complete, immutable set of access states a Session's commands run under: a `Deny`/`Gated`/`Allow` value for each capability (workspace read/write, host read/write, network, command execution) plus HOME and isolation choices. A profile is the sandbox's `AccessSource` for the gate and the source of truth for OS enforcement, so a decision and its enforcement cannot drift. It is selected before a Session opens and fixed for that Session. **Access state.** One of the three per-capability decisions in an access profile: `Deny` (reject without asking; no saved rule overrides it), `Gated` (use a matching saved rule or ask once), or `Allow` (proceed). **Confinement.** Whether a command runs inside an OS sandbox (`Sandboxed`) or directly with the invoking user's authority (`Unconfined`). It is a property of the executor, independent of the per-capability access states. **Effect.** What resolving a Gate does to execution. A response may resume parked work, initiate follow-up work, or apply a control-plane action. **Fail closed.** Refusing an operation when the required permission or safety guarantee cannot be established. The system does not silently fall back to a less safe mode. **Gate.** A durable pause that requires input from a person or policy before work can continue. Gates are used for permissions, questions, and resumable control decisions. **Gate criticality.** Whether an unresolved Gate must survive restoration or may be abandoned when a Session is restored. **Gate control.** The live operation used to answer, reject, or otherwise resolve a pending Gate. **Gate resolver.** The Loop-level or Session-level owner responsible for processing a Gate response. **Gate route.** The Gate, Loop, and tool execution identifiers needed to deliver a response to the correct resolver. **Grant token.** An authenticated token issued by the sandbox authority to permit a bounded escalation that untrusted code cannot forge. **Guardrail.** A check or policy applied around model input, output, tool use, or workflow execution. Broader integrated guardrails are planned. **Permission.** Runtime authority for a proposed tool or system action. A model requesting an action does not itself grant permission to perform it. **Call preparer.** The preparation boundary a tool owns. It decodes and validates untrusted arguments once, normalizes and canonicalizes the resources involved, and emits one typed request listing the capability requirements the call needs. Tools classify capabilities; the access gate decides `Deny`, `Gated`, or `Allow`. **Prompt injection.** Untrusted content that attempts to redirect an agent, override its instructions, obtain secrets, or cause unsafe actions. **Read guard.** An in-process check that decides whether a native tool may read a path. It complements OS confinement for subprocesses. **Sandbox.** OS-level enforcement around spawned commands. The `sandbox` module can restrict filesystem, process, network, environment, and resource access. **Sandbox guarantee.** A report of what the current host actually enforced. The sandbox reports achieved guarantees, not only the policy that was requested. **Restrict.** The operation that intersects a base access profile with a ceiling, component-wise, without ever widening the base. It is how a role such as a reviewer is held below the Session's selected profile. **Grant.** A short-lived, single-spawn capability token the sandbox executor mints only after the gate approves a `Gated` requirement. It binds the exact command, working directory, profile fingerprint, and expiry, and is never a durable permission record. **Secret brokerage.** **Planned.** Resolving secret references only inside an authorized tool or transport so raw secret values do not enter model context. **Tool definition.** The reusable, unbound description of a tool, including its name, input schema, and factory. **Tool binding.** A tool definition connected to the runtime dependencies and policy required for one Loop. **Tool call.** One model request to invoke a named tool with structured input. **Tool middleware.** A wrapper that can observe or constrain tool invocation without changing the tool's business logic. **Tool result.** The typed output of a tool call returned to the Loop. It may contain content for the model and separate runtime metadata. **Unconfined.** An explicitly acknowledged mode with no OS sandbox boundary. It is never treated as a safe fallback. **Untrusted content.** Content whose instructions must not automatically gain authority, including web pages, files, tool output, and messages from external systems. **Workspace permit.** The runtime authority for a tool to perform a declared operation against the Session workspace. ## Events, Durability, and Recovery **Ambiguous append.** A storage write whose acknowledgement was lost, leaving the caller unsure whether the record committed. `AppendDefinite` resolves this by reading the Ledger state before deciding whether to retry. **Append-only.** A persistence rule that allows new records to be added but does not rewrite committed history. **Blob.** An immutable byte object addressed by a key. Workspace snapshots use Blobs for durable content. **CAS.** Compare-and-swap. A write succeeds only if the stored revision or sequence still matches the caller's expected value. **Checkpoint.** A durable record of execution state from which work can resume. Sessions checkpoint workspaces, while `flow` checkpoints graph state and the execution frontier. **Configuration fingerprint.** A stable digest of the Rig configuration that affects restoration. It prevents a Session from silently resuming under an incompatible definition. **Content-addressed.** Stored under a key derived from the content itself, usually a cryptographic digest. Identical content can be reused and corruption can be detected. **Enduring event.** An event persisted in the Session Journal because it changes durable state or is required to reconstruct what happened. **Ephemeral event.** A live-only event, such as a streaming token delta, that is useful to clients but is not written to the Journal. **Epoch.** A strictly increasing ownership generation assigned by a Lease. A new owner receives a newer epoch than every prior owner. **Event.** A typed fact emitted by the harness about Rig, Session, Loop, Turn, Step, tool, gate, workspace, delegate, or lifecycle activity. **Event hub.** The Session-level fan-in that publishes live events, tracks subscribers, and supports quiescence without becoming the durable Journal. **Fencing token.** The epoch used to prevent a stale process from continuing as the accepted owner after another process has taken the Lease. The Session Journal records its writer epoch as an opening fence. **Journal.** The ordered, durable history of enduring Session events. **Journal sequence.** The monotonic position of an enduring event in the Journal. Ephemeral events do not receive one. **KV.** A revisioned key-value storage primitive whose writes use CAS. **Lease.** Time-bounded, single-writer ownership of a named resource. Leases and epochs prevent two runtimes from safely claiming the same Session writer. **Ledger.** The neutral append-only record log defined by `storage`. A Session Journal is persisted on a Ledger. **Restore.** Reconstructing a Session and its workspace from durable events, snapshots, and compatible Rig configuration. **Snapshot.** An immutable capture of workspace contents. Snapshots can be taken manually or by policy when a Step, Turn, or idle transition completes. **Snapshot policy.** The rule that decides when workspace snapshots are taken and whether a snapshot failure is best-effort or must fail the operation. **Snapshot priority.** Whether a snapshot is best-effort or required. A required snapshot failure fails the surrounding operation instead of being reported only as an observation. **Storage backend.** A concrete implementation of the neutral Ledger, Leaser, KV, or Blobs contracts. **Workspace.** The filesystem state assigned to a Session. It is where tools read and change working files. **Exclusive workspace.** A workspace whose root is leased so only one Session runtime can own it at a time. **Per-session workspace.** A separate workspace root created for each Session. It isolates working files between Sessions. **Shared workspace.** A workspace root intentionally used without exclusive per-Session placement. Required snapshots are not permitted for this placement because another actor may change the same files. **Workspace coordinator.** The runtime boundary that validates and observes tool operations against a workspace. **Workspace observation.** Recorded information about files a tool read, created, changed, or removed. **Workspace store.** The harness component that captures and restores content-addressed workspace snapshots through `storage.Blobs`. ## Durable Workflows **Bulk Synchronous Parallel.** The execution model used by `flow`. All vertices in a super-step read the same frozen state, run in parallel, then cross a barrier before their reducers are applied in stable order. **Conditional edge.** A graph connection whose destination is chosen from the current state or task result. **Control plane.** The component that coordinates workflow execution across workers. The term also describes session operations that change live state, such as gates and interrupts. **Edge.** A directed connection that determines which graph vertex may run after another vertex completes. **Flow.** A durable workflow defined and run through the `flow` module. In ordinary prose, we prefer **workflow** unless referring to the module or public type. **Frontier.** The set of graph vertices eligible to execute in the next super-step. **Graph.** A typed workflow definition containing vertices, edges, shared state, and stable identifiers. **Graph run.** One execution of a compiled Graph, identified independently so it can be inspected, interrupted, checkpointed, and resumed. **Graph state.** The shared typed state carried through a workflow. Tasks read selected input from it and reducers fold task output back into it. **Idempotency key.** A stable identifier for a side effect. External systems can use it to avoid performing the same operation twice after a retry or recovery. **Ingress.** The boundary that accepts a request to start, resume, or otherwise control a workflow run. **Interruption.** An intentional durable pause that returns control to the caller while preserving enough state to resume later. **Manifest.** A description of a registered Graph and the task kinds it needs, used to make workflow definitions discoverable to a control plane. **Reducer.** A function that folds one task's output into Graph state after the parallel execution barrier. **Resume payload.** New information supplied when interrupted work continues, such as a person's answer or an external system's result. **Selector.** A function that derives one task's typed input from the frozen Graph state. **Super-step.** One parallel round of eligible workflow vertices, followed by a barrier and deterministic state reduction. **Task.** A reusable unit of workflow work with typed input and output. A Task can be deterministic, call an agent, wait for a person, or interact with an external system. **Vertex.** A stable position in a Graph that binds a Task to selectors, reducers, and routing. **Workflow checkpoint.** The durable Graph state, completed work, frontier, and execution metadata needed to recover a Graph run. ## Interfaces and Protocols **ACP.** Agent Client Protocol. **Planned for the general integration.** ACP will let looprig expose native Rigs to compatible clients and host compatible foreign agents through a shared protocol. Existing Codex and Claude Code adapters prove the foreign Loop boundary but are not the complete ACP implementation. **BFF.** Backend for frontend. A service surface shaped for a particular user interface while delegating runtime behavior to the harness. **CLI.** Command-line interface. A text command surface intended for terminals, scripts, and automation. **Client.** A user-facing interface that observes and controls a Session, such as a terminal, web, desktop, or mobile application. This is distinct from an inference client, which calls a model API. **Control plane operation.** A request that changes live execution, such as submitting input, resolving a Gate, interrupting work, changing a mode, or restoring a Session. **HTTP API.** The harness service interface for Session history and control. Its live event stream uses SSE. **JSON-RPC.** A request and response protocol encoded in JSON. It is used by several external agent and tool protocols. **Live plane.** The stream of events produced while a Session is running. It contains both enduring and ephemeral events. **History-to-live join.** The handoff a client performs from durable Journal history to the live event stream without losing or duplicating enduring events. **MCP.** Model Context Protocol. **Planned.** MCP integration will connect external tools and context servers to Loops while preserving looprig's authentication, permission, gate, provenance, and audit boundaries. **NDJSON or JSONL.** Newline-delimited JSON, where each line is one complete JSON value. It is useful for streaming structured records between processes. **Read plane.** The durable history surface used to inspect a Session from its Journal, whether or not that Session is currently running. **SSE.** Server-Sent Events. A one-way HTTP stream used to deliver live Session events to clients. **TUI.** Terminal user interface. looprig's interactive terminal presentation layer is separate from the harness runtime. ## Evaluation and Observability **Evaluation.** A repeatable test of an agent or workflow against defined cases and metrics. **Golden set.** A maintained collection of representative evaluation cases and expected properties used to detect regressions. **Hook.** A lifecycle callback that observes runtime or workflow activity without owning the underlying state transition. **LLM judge.** A model used as an evaluation metric to score output against a rubric. Its score is evidence, not an infallible ground truth. **Metric.** A rule that converts an evaluation result into a score or pass/fail decision. **OpenTelemetry.** A vendor-neutral standard for traces, metrics, and logs. Complete correlated instrumentation across looprig is planned; existing events and hooks provide part of the required foundation. **Score.** A metric's structured evaluation of one result, including its value, reasoning, and threshold outcome. **Span.** One timed operation inside a distributed trace, such as an inference, tool call, Gate, delegate run, or workflow task. **Trace.** Correlated observability data that follows work across components and services. ## Modules and Repositories **`tui`.** The interactive terminal presentation module. It provides the TUI surface without making the harness depend on terminal libraries. **`client`.** **Planned.** The shared client module for web, desktop, mobile, and framework-specific interfaces over the harness HTTP and SSE contract. **`coderig`.** A complete coding Rig built from looprig modules. It demonstrates how an application owns model policy, Loop definitions, tools, prompts, delegation, storage, and its composition root. **`core`.** The smallest shared vocabulary, including typed content, UUIDs, stream accumulation, and structured logging helpers. **`flow`.** The durable, replayable workflow engine for typed graphs, parallel tasks, retries, checkpoints, interruptions, and resume. **`foreignloop`.** The harness boundary and adapters that translate external agent runtimes into looprig's Loop, Session, permission, and Event vocabulary. **`fsstore`.** The local-filesystem implementation of all four `storage` primitives. **`inference`.** The provider-neutral model contract, content mapping, codecs, streaming, routing, transport, usage, and context counting layer. **`llm`.** The provider policy and integration layer built on `inference`. It adds authentication, provider selection, model validation, and concrete model services. **`memstore`.** The in-memory reference backend for the four `storage` primitives. It is useful for tests and short-lived local execution. **`natsstore`.** The distributed implementation of all four `storage` primitives over NATS JetStream. It can use an embedded or remote server. **`rclonestore`.** A `storage.Blobs` implementation that uses the external `rclone` program to reach local or cloud remotes without linking its dependency tree into looprig. **`sandbox`.** The standalone module that owns the access profile and its OS enforcement for commands. It compiles a profile into the strongest available OS boundary, mints post-approval grant tokens, and reports the guarantees achieved by the current platform. **`storage`.** The dependency-light persistence contract module. It defines Ledger, Leaser, KV, and Blobs plus reference and conformance utilities. **`storetest`.** The shared conformance suite that checks whether a storage backend obeys the neutral storage contracts. **`serve`.** The harness package that projects Session history, live events, input, Gates, interrupts, and restore over an HTTP and SSE API. **`sessionstore`.** The harness persistence facade that stores and reconstructs Session Journals over the neutral `storage` contracts. **`tests`.** The cross-repository integration module that verifies looprig modules work together through their public contracts. **`tools`.** Optional standard tool implementations. Consumers can select each tool independently, combine them with custom tools, or use none of them. ## Architecture and Engineering Language **Composition root.** The application location where concrete models, stores, Loops, tools, policies, interfaces, and other dependencies are selected and wired together. The user's application remains the final composition root. **Conformance suite.** Reusable tests that every implementation of a public contract must pass. **Contract hub.** A small, stable module that defines shared interfaces and types without depending on their concrete implementations. **Data plane.** The surface used for ordinary runtime work, such as submitting input, receiving output, and invoking tools, rather than changing control policy. **Dependency inversion.** Defining a small contract at the layer that needs it, then supplying concrete behavior from the outside. This keeps the harness from depending directly on every backend or integration. **Dependency direction.** The rule that lower, more stable contract modules do not import higher-level products or concrete integrations. Dependencies point toward shared contracts. **Durability.** The ability to preserve and recover meaningful state after a crash, restart, disconnection, pause, or move to another machine. **Leaf module.** A narrowly scoped module with few dependencies and no reason to import the wider looprig runtime. **Module.** An independently versioned Go module and repository boundary, such as `harness`, `flow`, or `storage`. **Package.** A Go source boundary inside a module. Packages group closely related types and behavior. **Public seam.** An intentional interface between replaceable parts of the system, such as inference, storage, the sandbox, or foreign agent execution. **Reference backend.** A small implementation used to demonstrate and test the expected behavior of a contract. **Reference oracle.** A simple implementation whose behavior is used as the expected result when testing more complex implementations. **Replay.** Reconstructing state deterministically from durable history or checkpoints without repeating already committed side effects. **Structural coupling.** Satisfying a small Go interface by method shape rather than importing the package that owns a concrete implementation. **SDK.** Software development kit. A library surface intended to be embedded in another application, as distinct from a finished agent product. **stdlib-first.** A project preference for the Go standard library at stable contract layers, adding third-party dependencies only where they provide clear value and can be contained. ## Mission and Brand **Federated knowledge.** Knowledge and reasoning made broadly available through open, composable systems instead of remaining trapped in a few people, institutions, models, or vendors. Here, federated describes access and ownership, not one specific distributed-learning technique. **Industrial Revolution.** The historical transformation in which engines, machine tools, factories, and infrastructure converted new sources of physical power into large-scale automation. **Intellectual Revolution.** Our name for the shift from knowledge and reasoning concentrated in a few people toward intelligence that can be made widely available through neural networks and the machinery built around them. **Lathe.** The mission metaphor for looprig. GPUs and TPUs provide computing power, neural networks turn it into intelligence, and looprig gives that intelligence the precision and control needed to produce useful systems. The power source will keep changing. We intend looprig to remain the adaptable machine that puts it to work. **Steam engine.** The mission metaphor for a foundational source of power. In the current era, GPUs and TPUs supply computation in the way steam engines once supplied mechanical power. Neural networks turn that computation into intellectual capability. **Loop in the logo.** The open infinity form represents intelligence and its continuing potential. **Rig in the logo.** The triangle represents the structure that contains, directs, and makes that potential reliable. **Rig in the name.** A rig is assembled equipment that harnesses power for a purpose. Sailing rigs harness wind, drilling rigs put mechanical power to work, and test rigs make systems controlled and repeatable. A looprig applies that same idea to an agent Loop. **Electric Lime, `#D4F84D`.** The energy color. It represents the active power of intelligence moving through the Rig. **Light Sky Blue, `#A2D2FF`.** The stability color. It represents trust, reliability, clarity, and calm control around that power. **The rig that runs the loop.** A compact expression of the project name: the Loop is the continuing intelligence, and the Rig is the owned machinery that makes it useful.