agentkit-loop
Runtime-agnostic agent loop orchestration for sessions, turns, tools, and interrupts.
This crate provides:
- Model adapter traits --
ModelAdapter,ModelSession, andModelTurnabstract away the model provider so you can swap between OpenRouter, Anthropic, or a local LLM without changing loop logic. Agentbuilder andLoopDriver-- configure tools, permissions, observers, and compaction, then drive the loop step-by-step.- Interrupt handling -- the loop pauses and yields
LoopStep::Interrupton blocking events (tool approval) and cooperative yields (AwaitingInputat end-of-turn,AfterToolResultbetween tool rounds). The host either resolves the interrupt or just callsnext()again depending on whetherLoopInterrupt::is_blocking()istrue. - Observer hooks -- attach
LoopObserverimplementations to receive streamingAgentEvents (deltas, tool calls, usage, warnings, lifecycle events). - Transcript compaction -- optionally compact the transcript when it grows too large, via the
agentkit-compactionintegration.
Use it as the central coordinator between model providers, tool execution, and application UI or control flow.
Quick start
use ;
use ;
use ;
#
# async
Adding tools and observers
AgentBuilder::add_tool_source accepts any ToolSource. A ToolRegistry
implements ToolSource directly, so you can hand it in by value; call the
method again to federate additional sources (MCP catalogs, plugin loaders,
etc.).
use ;
use ToolRegistry;
;
#
Handling interrupts
When a tool call requires approval the loop yields a blocking interrupt;
AwaitingInput and AfterToolResult are cooperative (use
LoopInterrupt::is_blocking to tell them apart). Resolve any pending
approval and call next() again to resume:
use ;
use ;
# async
OpenTelemetry telemetry
The loop emits GenAI tracing spans without requiring an OpenTelemetry SDK. Enable the optional otel feature when exporting through tracing-opentelemetry; this preserves token counts as signed 64-bit attributes and provider-native finish reasons as string-array attributes. With that feature enabled, usage, finish-reason, and message attributes are written directly to the OpenTelemetry span and are not visible to a plain tracing-subscriber formatting layer.
Message content capture is always off by default and is configured only in code. Input and output limits are independent and bounded:
use ;
let agent = builder
.model
.telemetry
.build?;
AgentKit does not read OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT. MessageCapture::new rejects zero message and byte limits; it never silently clamps them. The exported gen_ai.input.messages and gen_ai.output.messages attributes are OpenTelemetry Array<String> values whose elements are compact valid JSON. Input capture keeps the newest bounded tail in transcript order; output capture keeps the bounded head. Data references are omitted, so inline image/audio/binary data and URLs are neither exported nor dereferenced. An item that exceeds the remaining source-content budget becomes a structured JSON truncation record, including when the configured byte budget is tiny.