rig-compose
Composable agent kernel: stateless skills, transport-agnostic tools, registry-driven agents, signal-routing coordinator. Companion crate for rig.
Overview
rig-compose is a domain-neutral composition layer for Rig-shaped agent systems. It provides the kernel traits and data structures for stateless skills, side-effectful tools, shared registries, generic agents, deterministic signal routing, workflows, in-process agent delegation, and optional YAML manifest loading.
The crate does not call an LLM itself and does not depend on rig-core; instead it defines the small tool and skill surfaces that companion crates such as rig-mcp and rig-resources plug into.
Why it exists
Rig supplies provider-agnostic model, embedding, vector-store, and tool traits. rig-compose fills a different gap: it organizes domain skills and tools into repeatable agent workflows without forcing those workflows to know whether a tool is local, delegated to another in-process agent, or surfaced through MCP.
This keeps downstream systems from reimplementing the same coordination pieces: Skill, Tool, ToolRegistry, SkillRegistry, GenericAgent, CoordinatorAgent, DelegateTool, and the budget guards used to meter finite work.
Status
- Crate version:
0.2.0. - Rust edition: 2024.
- MSRV: 1.88.
- Runtime stance: runtime-agnostic library;
tokiois used only as a dev-dependency for tests and examples. - Current Unreleased work adds the
budgetmodule, drop-safeTokenReservationrefunds,KernelError::ToolNotApplicablefor soft tool failures, provider-neutral tool-call normalization/dispatch helpers, dispatch hooks, provider-neutral context packing primitives, and bounded tool-result envelopes.
The crate-local maturity plan lives in ROADMAP.md. Cross-crate
coordination lives in
rig-contributions/docs/roadmap.md.
Feature flags
| Feature | Default | Enables | Checked by just check |
|---|---|---|---|
| none | yes | Core agent, skill, tool, registry, workflow, delegate, coordinator, context, instruction, and budget APIs. | cargo clippy --all-targets, cargo test --all-targets |
manifest |
no | YAML-backed portable agent manifest types and materialization helpers from src/manifest.rs. Pulls serde_yaml. |
cargo clippy --all-targets --features manifest, cargo test --all-targets --features manifest, docs and examples with all features |
Key Types
- src/skill.rs:
Skill,SkillId, andSkillOutcome. Skills are stateless and decide whether they apply to anInvestigationContext. - src/tool.rs:
Tool,ToolSchema,ToolName,LocalTool,ToolResultEnvelope, andToolResultEnvelopeConfig. Tools are the side-effectful async boundary; envelopes provide deterministic bounding metadata for large tool results. - src/registry.rs:
ToolRegistry,SkillRegistry, andKernelError. Registries hold shared tools and skills;ToolRegistry::scopedproduces per-agent tool views. - src/agent.rs:
Agent,AgentId,AgentStepResult,GenericAgent, andGenericAgentBuilder. Generic agents run registered skill chains over mutable investigation context. - src/context.rs:
InvestigationContext,Signal,Evidence, andNextActionfor skill-chain state, plusContextItem,ContextPack,ContextPackConfig, andContextSourceKindfor provider-neutral context-window planning. - src/delegate.rs:
DelegateExecutor,DelegateRegistry,DelegateTool,DelegateName, andInProcessAgentDelegate. This is the model-driven agent-to-agent delegation path. - src/coordinator.rs:
CoordinatorAgent,CoordinatorBuilder, andRoutingRule. This is deterministic first-match routing for fixed topologies. - src/budget.rs:
BudgetGuard,TokenBudget,AtomicBudget,AtomicTokenBudget,TokenReservation,TokenRefund, andBudgetError. These meter rows, dispatch slots, and prompt-token reservations. - src/normalizer.rs:
ToolCallNormalizer,LfmNormalizer,StructuredToolCallNormalizer,ToolInvocation, anddispatch_tool_invocations. These normalize LFM/MLX text markers, OpenAI Responsesfunction_calloutput, and OpenAI Chat Completionstool_callsinto the same dispatchable shape. - src/workflow.rs:
Workflow, the async workflow composition trait. - src/instructions.rs:
Instructions, a serializable instruction bundle with examples, response schema, and metadata. - src/manifest.rs:
AgentManifest,ModelSpec,ToolSpec,DelegateSpec, and materialization helpers, gated behindmanifest.
The crate-level architecture rule is simple: Skill is pure decision logic, Tool is the side-effect boundary, registries own lookup, and agents compose registered pieces without hard-coding concrete implementations.
Integration With Rig
rig-compose does not pin rig-core in Cargo.toml. It is intentionally Rig-shaped rather than Rig-bound: other crates can adapt Rig tools, MCP tools, local closures, or in-process delegates into the same Tool and Skill flow.
The main companion integration points are:
rig-mcpimplements remote MCP access by adapting MCP endpoints intorig_compose::Toolvalues.rig-resourcesprovides reusable skills and tools that implementrig-composetraits.- Downstream systems can use
BudgetGuardandTokenBudgetto enforce compute budgets around agent dispatch and LLM token use.
Usage
The minimal runnable example is examples/basic_agent.rs. It registers one stateless skill, builds a GenericAgent, and runs that agent against an InvestigationContext.
use Arc;
use async_trait;
use ;
;
# async
The budget behavior is covered by the unit tests in src/budget.rs, including reservation reconciliation and drop-time refunds.
Tool-Call Normalization
Local OpenAI-compatible servers sometimes emit tool intent as text instead of
provider-native structured tool calls. rig-compose normalizes both paths into
one kernel shape:
raw model output or provider JSON
-> ToolInvocation { name, args }
-> ToolRegistry
-> tool result
-> next model turn / final answer
Supported normalizers today:
LfmNormalizer: parses LiquidAI LFM markers such as<|tool_call_start|>[get_weather(city='Berlin')]<|tool_call_end|>.StructuredToolCallNormalizer::normalize_openai_responses: parses OpenAI Responsesfunction_calloutput items or full response objects.StructuredToolCallNormalizer::normalize_openai_chat_completions: parses OpenAI Chat Completionstool_callsor full chat completion responses.
All of these produce ToolInvocation values that can be dispatched through
dispatch_tool_invocations(&ToolRegistry, &[ToolInvocation]).
The intended agent loop is: normalize the first model response, dispatch any
invocations through the registry, pass the tool results back to the model, then
let the model produce the final answer. rig-compose owns the provider-neutral
normalization and dispatch pieces; callers decide how to encode tool results for
their provider or local server.
Use dispatch_tool_invocations_with_hooks when callers need policy,
accounting, or tracing around dispatch. ToolDispatchHook can continue an
invocation, skip it with a synthetic result, or terminate the loop before the
tool runs. Hooks receive only kernel shapes (ToolInvocation and
ToolInvocationResult), keeping provider, MCP, memory, approval, and telemetry
implementations downstream.
DispatchBudgetHook is the first concrete hook. It gates each normalized tool
invocation on a BudgetGuard, terminates dispatch when the budget denies the
reservation, and releases the reservation after success, skip, or dispatch
error.
Context Planning
ContextItem is the provider-neutral shape for one piece of context that may
enter a model window. It records the source kind, stable source id, rank, score,
prompt-ready text, character estimate, provenance, and metadata without tying
the kernel to memory, MCP, files, resource stores, or provider SDKs.
ContextPack takes ranked context items and a ContextPackConfig, selects what
fits in a bounded character window, and records omitted items with explicit
ContextOmissionReason values. This is the kernel-level generalization of the
memory candidate/context-pack work in rig-memvid: memory cards, tool results,
resource lookups, files, and reasoning workspace notes can all project into the
same packable shape.
use ;
let memory = new
.with_rank
.with_score;
let tool_result = new
.with_rank;
let pack = pack;
assert!;
Harness Prototype
examples/tool_loop_harness.rs shows the first
deterministic harness shape around that loop. It records the task, first model
output, normalized invocations, dispatched tool results, final answer, and
passed assertions. This is intentionally an example rather than a new crate: it
lets the platform prove the run-record vocabulary before extracting a dedicated
rig-harness layer.
Validation
Canonical validation is just check.
That recipe runs formatter checks, clippy and tests for default features and manifest, rustdoc with -D warnings -D rustdoc::broken_intra_doc_links, and cargo build --examples --all-features.
Gotchas
DelegateToolandCoordinatorAgentsolve different routing problems. UseDelegateToolwhen the model should decide whether to call another agent; useCoordinatorAgentfor fixed host-driven signal routing.KernelError::ToolNotApplicableis a soft failure for tools that cannot apply to the current context. Callers may treat it as a no-op, asrig-resourcesdoes for missing graph entities.TokenReservationrefunds its estimate on drop unlessAtomicTokenBudget::record_usagedisarms it. Keep the handle alive until actual usage is recorded.- The library is runtime-agnostic. Do not add runtime dependencies to
[dependencies]; tests and examples usetokiofrom[dev-dependencies].
Ecosystem
These companion crates are maintained as separate repositories. Together they form a small stack around the upstream Rig project: rig-compose provides the kernel surface, rig-resources contributes reusable skills and tools, rig-mcp moves tools across MCP, and rig-memvid connects Rig agents to persistent .mv2 memory.
flowchart TD
rig["rig / rig-core"]
compose["rig-compose 0.2.x"]
resources["rig-resources 0.1.x"]
mcp["rig-mcp 0.1.x"]
memvid["rig-memvid 0.1.x"]
compose -. "Rig-shaped kernel; no direct rig-core dep" .-> rig
resources -- "rig-compose = 0.2; features: security, graph, full" --> compose
mcp -- "rig-compose = 0.2; rmcp stdio bridge" --> compose
memvid -- "rig-core = 0.36.0; features: lex, vec, api_embed, temporal, encryption" --> rig
Pinned Rig-facing dependencies from the current manifests:
| Crate | Direct Rig-facing dependency | Notes |
|---|---|---|
rig-compose |
none | Defines a Rig-shaped kernel surface without depending on rig-core. |
rig-resources |
rig-compose = 0.2 |
Provides reusable skills, resource tools, and security helpers. |
rig-mcp |
rig-compose = 0.2 |
Bridges rig-compose tools over MCP stdio and loopback transports. |
rig-memvid |
rig-core = 0.36.0 |
Implements Rig vector-store and prompt-hook flows over Memvid. |
The concrete multi-crate workflow tested today is the MCP loopback path: a rig_compose::ToolRegistry is exposed through rig_mcp::LoopbackTransport, remote schemas are wrapped as rig_mcp::McpTool, and the wrapped tools are registered back into another ToolRegistry. That proves a local rig-compose tool and an MCP-adapted tool are indistinguishable to callers. The backing test is mcp_tool_indistinguishable_from_local in rig-mcp/src/transport.rs.
License
Licensed under either Apache-2.0 or MIT, at your option.