atomr-agents-core 0.21.0

Core types for the atomr-agents framework: ids, budgets, context, events, errors.
Documentation

atomr-agents

A native Rust agentic framework built as a layered actor / strategy / harness substrate on top of atomr and atomr-infer. atomr-agents gives you a single mental model — pluggable strategies that resolve under shared budgets, channelled state with first-class checkpointing, tool-call orchestration with parallel dispatch, and durable harness loops — that scales from a one-off chatbot to a multi-tenant production agent platform.

use atomr_agents::prelude::*;

// One Pipeline composes prompt → model → parser like LCEL.
let pipeline = Pipeline::from(prompt)
    .then(model)
    .then(parser)
    .build();

let answer = pipeline.call(input, ctx).await?;

Python parity

The Python facade ships every Rust capability. The native extension atomr_agents._native is split into 28 hierarchical submodules: foundational (errors, core, callable_, strategy, instruction, context, state, observability, cache, parser, registry); tool / skill / memory / retrieval / ingest (tool, skill, memory, embed, retriever, ingest, persona); agent / workflow / harness / org / eval (agent, workflow, harness, org, eval); voice (stt, tts, voice, voice_extras); plus the guest registry. The top-level package re-exports the most-used classes, ships a PEP 561 py.typed marker, and exposes async coroutines / async iterators over pyo3-async-runtimes.

Install

pip install atomr-agents

For an editable workflow against the local checkout:

pip install maturin
maturin develop --features python -m crates/py-bindings/Cargo.toml
pip install -e ".[dev]"

Host-mode async event stream

EventBus.stream() returns an EventStream that implements the Python async iterator protocol. Drive a producer on the same loop and consume events as they fire:

import asyncio
from atomr_agents.observability import EventBus


async def main() -> None:
    bus = EventBus()
    stream = bus.stream()

    bus.emit_tool_invoked("calc", args_hash=0, elapsed_ms=5, ok=True)
    bus.emit_tool_invoked("search", args_hash=1, elapsed_ms=12, ok=True)

    async for ev in stream:
        print(ev.kind, ev.timestamp_ms)
        if ev.kind == "tool_invoked" and ev.tool == "search":
            break


asyncio.run(main())

Async registry publish

Registry.publish_async returns a Python awaitable backed by a tokio future, so version pins land without blocking the event loop:

import asyncio
from atomr_agents.registry import Registry


async def main() -> None:
    registry = Registry()
    record = await registry.publish_async(
        "tool_set", "calc", "0.1.0", {"name": "calc"}
    )
    print(record.kind, record.id, record.version)


asyncio.run(main())

Guest-mode @tool decorator

atomr_agents.guest exposes real decorators wired through _native.guest.register_*_factory. A guest tool is a class with an async def invoke(self, args, ctx) method:

from atomr_agents.guest import tool


@tool(toolset="calc")
class Add:
    name = "add"

    async def invoke(self, args: dict, ctx) -> dict:
        return {"sum": args["a"] + args["b"]}

Mirror decorators are available for the full set of 24 Rust traits: @strategy(kind=...), @persona, @skill, @parser, @scorer, @memory_store, @embedder, @callable_, @retriever, @loader, @splitter, @kv_cache, @long_store, @tracer, @conversation_agent, @diarizer, @vad, @phonemizer, @journal, @repair_model, @persona_reconciler, @inference_client, @ann_index. Each pairs with an atomr_agents.<module>.*_from_factory(key) builder that materialises the registered Python target as a Rust dyn handle.

Host-mode agent runtime

AgentBuilder assembles strategy slots into a runnable AgentRef that implements Callable, so an agent composes with the same decorators and pipeline operators as any other unit:

from atomr_agents.agent import AgentBuilder
from atomr_agents.harness import Harness, iteration_cap, loop_strategy_from_callable
from atomr_agents.workflow import Dag, Step, WorkflowRunner

# Strategy slots come from in-process factories or Python guests.
builder = AgentBuilder("research-agent", "gpt-4o-mini")
builder.with_instructions(instructions)
builder.with_tools(tool_strategy)
builder.with_memory(memory_strategy)
builder.with_skills(skill_strategy)
builder.with_inference(inference_client)
agent_ref = builder.build()
result = await agent_ref.run_turn("What's the GDP of France?")

# The agent is itself a Callable — drop it into a workflow.
dag = Dag("plan")
dag.add_step("plan", Step.invoke(agent_ref.as_callable()))
runner = WorkflowRunner("research-wf", dag.build())
await runner.run({"user": "..."})

Where things live

The hierarchical layout is reflected in the Python facade — every submodule has a one-to-one .py mirror under atomr_agents/:

from atomr_agents.errors import RegistryError
from atomr_agents.core import TokenBudget, AgentId
from atomr_agents.agent import AgentSpec, AgentBudgets
from atomr_agents.tool import ToolDescriptor, ToolCallParser
from atomr_agents.observability import EventBus, RunTreeBuilder
from atomr_agents.registry import Registry

The top-level package keeps the 0.2.x convenience names — so from atomr_agents import EventBus, Registry still works.

Runtime coverage

AgentRef.run_turn, Harness.run, WorkflowRunner.run, and Conversation are all callable from Python. The Rust runtimes are type-erased through BoxedAgent (in crates/agent) and Box<dyn LoopStrategy> / Box<dyn TerminationStrategy> (in crates/harness); the blanket impl Trait for Box<dyn Trait> impls live in their respective trait crates so the composition contract holds regardless of whether a strategy is monomorphic or boxed. See docs/python-api.md for the full module map and async-surface table.

Why an agentic framework, in Rust, on actors

Agentic systems don't fail because the models aren't good enough — they fail because the substrate underneath them treats context, composition, and persistence as afterthoughts. Glue-code retry policies, opaque memory, hand-rolled tool loops, brittle handoff between agents — that's where 3 a.m. pages come from.

Composition is the unit of work. A real agent is a Pipeline of prompts, models, parsers, and tools — each with its own retry, fallback, timeout, cache, and trace policy. atomr-agents makes every component a Callable with the same composition surface, so with_retry, with_fallbacks, and with_config apply uniformly to prompts, models, retrievers, and parsers alike.

State is channelled, durable, and forkable. Long-running agents need more than chat history. They need typed channels with reducers (AppendMessages, MergeMap, LastWriteWins, MaxByTimestamp), per-super-step checkpoints keyed by (workflow, run, step), and fork-with-edit so an operator can branch a divergent run from any prior state. atomr-agents ships LangGraph's state model verbatim in atomr's actor idiom.

Tool calls are parallel and provider-agnostic. When a model emits five tool calls in one turn, atomr-agents fans them into a JoinSet and aggregates in original order. The streaming tool_call_delta parser handles OpenAI and Anthropic deltas natively; new providers plug in behind the same Provider enum. Per-call deltas are also surfaced as Event::ToolCallStreamed so tracers and UIs see tool intent in real time, distinct from the post-call Event::ToolInvoked. RichTool returns ToolReturn::{Content, ContentAndArtifact, Command} so a tool can also drive graph control flow.

Provider runtimes are opt-in feature flags. Enable provider-anthropic, provider-openai, or provider-gemini on the umbrella to pull the corresponding atomr-infer-runtime-* crate and re-export its *Config / *Pricing / *Runner via atomr_agents::agent::providers::{anthropic, openai, gemini}. Cost reports include cached_tokens (Anthropic prompt-cache, OpenAI cached input) and reasoning_tokens (o1-style) automatically.

Granular efficiency. Rust gives us deterministic resource use, zero-cost abstractions, and ownership-as-concurrency-safety. Strategy trait generics monomorphize the per-turn pipeline; Box<dyn> opt-in exists for config-driven loading. The whole 26-crate workspace builds under cargo check --workspace in seconds and ships zero runtime overhead beyond what the actor crate already pays.

What's in the box

Crate What it does
atomr-agents Umbrella facade re-exporting the public surface, feature-flag-driven
atomr-agents-core Ids, budgets (token / time / money / iteration), AgentContext, RunId, structured Event taxonomy, error types
atomr-agents-callable Callable trait, CallableHandle, Pipeline builder (then / fan_out / assign), decorators (with_retry / with_fallbacks / with_config / with_timeout / Branch / Lambda)
atomr-agents-strategy Strategy trait family (ToolStrategy, MemoryStrategy, SkillStrategy, RoutingStrategy, PolicyStrategy, LoopStrategy, TerminationStrategy) + combinators
atomr-agents-context ContextAssembler — priority-merge under a TokenBudget
atomr-agents-observability EventBus, RunTree builder, Tracer trait, StdoutTracer / JsonlTracer / LangSmithTracer
atomr-agents-state StateSchema + 5 reducers, RunState, Checkpointer trait + InMemoryCheckpointer, fork-with-edit; SQLite/Postgres backend stubs behind features
atomr-agents-tool Tool / RichTool traits, ToolDescriptor, ToolSet + ToolSetRegistry, PermissionSpec, provider-aware ToolCallParser (OpenAI / Anthropic), HandoffTool
atomr-agents-skill Skill, SkillSet, Static / Keyword skill strategies
atomr-agents-memory MemoryStore (short-term) + LongStore (long-term, namespace-tupled), RecencyMemoryStrategy / SummarizingMemoryStrategy / ChainedMemoryStrategy, WriteMemoryTool / UpdateMemoryTool / RecallMemoryTool
atomr-agents-embed Embedder trait, MockEmbedder, AnnIndex + InMemoryAnnIndex, EmbeddingToolStrategy
atomr-agents-retriever Retriever zoo: Bm25 / Vector / MultiQuery / ContextualCompression / ParentDocument / Ensemble (RRF) / SelfQuery / EmbeddingsFilter / TimeWeighted
atomr-agents-ingest Loader (text / md / json / csv) + splitters (Recursive / MarkdownHeader / Code / Token / Semantic) + CachedEmbedder + IngestPipeline
atomr-agents-persona All five structural strategies (Static, BigFive, Mbti, Jungian, Composite) + emphasis strategies (Static, AudienceAdaptive, TaskAdaptive, MoodState, GoalConditioned)
atomr-agents-instruction ComposedInstructionStrategy<P, T, B>, ChatPromptTemplate, MessagesPlaceholder, FewShotChatTemplate, LengthBasedSelector / SemanticSimilaritySelector
atomr-agents-agent Agent<I, T, Ms, Sk> actor + per-turn pipeline, tool-call loop with parallel dispatch, AgentMiddleware (logging / retry / rate-limit / redaction / tool-error-recovery), InferenceClient adapter for any ModelRunner
atomr-agents-workflow DAG primitives, WorkflowRunner, StatefulRunner (channelled state), Interruptible (interrupt() + interrupt_before / _after + Command::{Continue, Resume, Update, Goto}), Subgraph, dispatch_fan_out (Send-API analogue)
atomr-agents-harness Harness<L, T> actor, LoopStrategy / TerminationStrategy, durable iteration log; Harness is itself a Callable
atomr-agents-org Org / Department / Team, OrgRoutingStrategy impls (RoundRobin / LoadAware / CapabilityMatch), Policy::narrow, NamespacedMemory (read-cascade + write-gating), swarm_loop helper
atomr-agents-registry Versioned artifact registry with (kind, id, version) keys + publish_gated for eval-regression blocking
atomr-agents-eval EvalSuite, Scorer (Contains / Equality / Regex / LlmJudgeScorer / RubricScorer / PairwiseScorer), RegressionGate, AnnotationQueue
atomr-agents-cache LlmCache trait + InMemoryLlmCache + SemanticLlmCache (cosine match on prompt embedding); SQLite/Redis backend stubs behind features
atomr-agents-parser Parser<T> trait, JsonParser / JsonSchemaParser / SchemaParser<T> / EnumParser / CommaListParser / XmlParser / YamlParser, OutputFixingParser, RetryWithErrorParser, StreamingPartialJsonParser
atomr-agents-stt-core SpeechToText / StreamingSession traits, Capabilities (advertised per backend via a pub const), AudioInput / Transcript / StreamEvent, MockSpeechToText
atomr-agents-stt-remote-core Shared HTTP / WebSocket plumbing for cloud STT backends: reqwest client builder, tokio-tungstenite connect helper, SecretRef (env / literal / file), retry / rate-limit / timeout config
atomr-agents-stt-audio symphonia-based decoder, rubato resampler, and (feature mic) cpal-based MicCaptureSession with backpressure-aware mpsc producer
atomr-agents-stt-runtime-openai OpenAI Whisper / gpt-4o-transcribe REST batch backend
atomr-agents-stt-runtime-deepgram Deepgram REST + WebSocket backend; speaker-count diarization, partial results, VAD endpointing
atomr-agents-stt-runtime-assemblyai AssemblyAI REST upload + Universal-Streaming WebSocket; named-speaker diarization
atomr-agents-stt-runtime-whisper Local whisper.cpp via whisper-rs (gated behind the whisper-cpp feature). Optional download-models helper fetches ggml weights
atomr-agents-stt-diarize-sherpa Diarizer trait, MockDiarizer, sherpa-onnx-backed SherpaDiarizer (gated behind sherpa-onnx), apply_to_transcript stitching
atomr-agents-stt-voice VoiceSession (Live vs TurnBased { silence_ms }), Vad trait + EnergyVad/SileroVad, pump_mic_to_stream glue
atomr-agents-stt-tool TranscribeTool (a Tool the model can call) and voice_input_skill(stt) -> (Skill, DynTool) for declarative agent integration
atomr-agents-agent-sdk-core Provider-neutral Claude Agent SDK contract: AgentSdkConfig (mirrors ClaudeAgentOptions), normalized AgentSdkMessage / ResultSummary / AgentSdkEvent, the pluggable AgentSdkBackend / AgentSdkSession trait seam, and a deterministic MockBackend (network-free, no claude CLI)
atomr-agents-agent-sdk-harness Wraps Anthropic's programmable Claude Code agent (claude-agent-sdk) as a Callable: slash commands, subagents, hooks, MCP, permission modes, sessions, custom system prompts; session registry under a TOCTOU-safe quota, SpendLedger credit tracking, .claude/ projection; behind actor, the interactive session is an atomr_core::actor::Actor; behind sandbox, per-session isolated microVM workspaces (Pattern C)
atomr-agents-agent-sdk-harness-web Axum REST + SSE companion over an AgentSdkHarness (/run, /sessions…, /events, /healthz)
atomr-agents-sandbox-core Backend-agnostic microVM sandbox contract: SandboxBackend / SandboxHandle traits, the 5 SandboxProfile toolchains, ResourceBudget (2 GB / 2 vCPU Rust floor), SandboxEvent, and a deterministic MockBackend so the whole surface is testable with no Docker / KVM
atomr-agents-sandbox-harness Sandbox orchestration: pluggable backend, live-sandbox registry, TOCTOU-safe concurrency quota, BestFitScheduler bin-packing, warm SnapshotPool, SandboxEvent broadcast; ephemeral one-shot + persistent registry paths; itself a Callable
atomr-agents-sandbox-tool The execute_in_sandbox Tool — runs untrusted Python / Bash / JS / Rust in an ephemeral sandbox, applying the Rust floor, returning { exec_id, exit_code, success, stdout, stderr, timed_out }
atomr-agents-sandbox-backend-docker Docker "insecure dev mode" backend (bollard): one long-lived container per sandbox, tar-based file I/O confined to /workspace, commit-based snapshot/fork. Not a security boundary — that's Firecracker's job
atomr-agents-sandbox-proto Host↔guest wire protocol: length-prefixed postcard frames (u32 LE + body, 64 MiB cap) over AF_VSOCK; keeps the in-VM guest a tiny static binary
atomr-agents-sandbox-guest-agent In-VM PID-1 daemon serving the protocol: per-language exec, /workspace-confined file I/O, best-effort init; lean static musl build
atomr-agents-sandbox-harness-web Axum REST + SSE companion over a SandboxHarness (/run, /sandboxes…, /events, /healthz)
atomr-agents-py-bindings atomr_agents._native PyO3 module — 28 hierarchical submodules exposing every framework capability to Python (callable composition, strategies, instruction templates, memory + retriever zoo + ingest, agent / workflow / harness runtimes via BoxedAgent, eval, tracers, voice + conversation, 24 guest-trait decorators)
atomr-agents-cli atomr-agents binary with eval / registry / harness / serve (Studio-style read+resume inspector) subcommands
atomr-agents-testkit Stub crate today. For tests, depend on atomr-infer-testkit (re-exports MockRunner / MockScript) directly — that's what crates/agent tests use.

Plus a Python facade — pip install atomr-agents — that exposes the host-mode Registry / EventBus and the guest-mode @tool / @strategy / @persona decorators.

Untrusted code execution — the microVM sandbox. Seven sandbox-* crates give an agent secure, instant-boot compute to run model-authored Python / Bash / JS / Rust. A backend-agnostic contract (SandboxBackend / SandboxHandle) sits under a quota'd orchestration harness (bin-packing scheduler + warm snapshot pool), the execute_in_sandbox tool, an AF_VSOCK host↔guest protocol, an in-VM PID-1 guest agent, a REST/SSE web companion, and the atomr_agents.sandbox Python facade. Backends escalate by isolation strength — deterministic mock (CI) → Docker "insecure dev mode" → Firecracker microVM (the real boundary) → Tier-3 gRPC cluster. Full write-up in docs/sandbox-architecture.md.

Programmable Claude Code — the Agent SDK harness. Three agent-sdk-* crates wrap Anthropic's claude-agent-sdk (the programmable form of Claude Code) as a Callable, exposing Claude Code's full harness — slash commands, subagents, hooks, MCP, permission modes, sessions, custom system prompts, and the built-in tool set — billed against your Anthropic API credits via the bundled claude CLI. A provider-neutral contract (AgentSdkConfig + AgentSdkBackend / AgentSdkSession, normalized message/event schema) sits under the orchestration harness (credit-tracking SpendLedger, .claude/ projection, session registry), a REST/SSE web companion, and the atomr_agents.agent_sdk Python facade — whose interactive session is exposed into the atomr actor model. Behind the sandbox feature, Pattern C gives each session its own isolated microVM workspace: the agent's exec/file is routed into the sandbox (host Bash/Write/Edit disabled) and the workspace is discarded — or snapshotted — on close, containing the bypassPermissions default for untrusted work. The contract is deliberately provider-neutral: because the harness only sees a normalized schema behind the backend trait, other vendors that mirror Anthropic's Agent SDK structure (with small option/message differences) plug in by supplying a thin adapter — no harness changes. Full write-up, including the generalization recipe, in docs/agent-sdk-harness.md.

Quick start (Rust)

The umbrella crate is published on crates.io as atomr-agents:

[dependencies]
atomr-agents = { version = "0.2", features = ["agent", "harness", "eval"] }
atomr-infer  = { version = "0.6", features = ["openai"] }   # or any provider

Or, to pull a provider runtime through the umbrella so Agent / LocalRunnerClient / OpenAiRunner come from one crate:

atomr-agents = { version = "0.2", features = ["agent", "provider-openai"] }
# or features = ["agent", "provider-anthropic"], ["agent", "provider-gemini"]

A minimal agent against MockRunner (good for tests; swap for any ModelRunner in production):

use std::sync::Arc;
use atomr_agents::prelude::*;
use atomr_agents::agent::{Agent, AgentBudgets, InferenceClient, LocalRunnerClient, Provider};
use atomr_agents::tool::{StaticToolStrategy, DynTool};
use atomr_agents::memory::{InMemoryStore, RecencyMemoryStrategy};
use atomr_agents::skill::StaticSkillStrategy;
use atomr_agents::persona::StaticPersonaStrategy;
use atomr_agents::instruction::{
    ComposedInstructionStrategy, StaticBehaviorStrategy, StaticTaskStrategy,
};
use atomr_agents::observability::EventBus;
use atomr_infer_testkit::{MockRunner, MockScript};

let runner = MockRunner::new(MockScript::from_text(["the answer is ", "42"]));
let inference: Arc<dyn InferenceClient> =
    Arc::new(LocalRunnerClient::new(runner, Provider::OpenAi));

let agent = Agent {
    id: AgentId::from("a-1"),
    model: "mock".into(),
    instructions: ComposedInstructionStrategy::new(
        StaticPersonaStrategy::new("You are a helpful assistant."),
        StaticTaskStrategy("Answer arithmetic questions.".into()),
        StaticBehaviorStrategy("Reply tersely.".into()),
    ),
    tools: StaticToolStrategy::new(Vec::<DynTool>::new()),
    memory: RecencyMemoryStrategy::new(Arc::new(InMemoryStore::new()), 5, 30),
    skills: StaticSkillStrategy::new(vec![]),
    inference,
    bus: EventBus::new(),
    max_tool_iterations: 3,
};

let r = agent
    .run_turn("what's 1+2".into(), AgentBudgets::default())
    .await?;
println!("{}", r.text);

Add tools, switch the MockRunner to a real ModelRunner (OpenAI, Anthropic, vLLM, …), and the same code runs unchanged.

Quick start (Python)

pip install atomr-agents
from atomr_agents import EventBus, Registry

bus = EventBus()
bus.subscribe(lambda ev: print(ev.kind, ev.timestamp_ms))

registry = Registry()
registry.publish("tool_set", "ts", "0.1.0", {"tools": ["calc"]})
print(registry.latest("tool_set", "ts"))

See docs/python.md for the full host/guest model and the subinterpreter-pool dispatcher pattern inherited from atomr's pycore.

Documentation map

docs/index.md is the full documentation hub. The map below links everything from this README.

Core framework

Subsystems & harnesses

  • docs/sandbox-architecture.md — microVM sandbox: untrusted-code execution, backend tiers (mock → Docker → Firecracker → cluster), vsock protocol, guest agent
  • docs/coding-cli-harness.md — wraps local AI coding CLIs (Claude Code, Codex, Antigravity) as callables; headless + interactive (xterm.js) modes
  • docs/agent-sdk-harness.md — wraps Anthropic's programmable Claude Code agent (claude-agent-sdk) as a Callable (slash commands, subagents, hooks, MCP, sessions); the Python interactive session is exposed into the atomr actor model; includes the recipe for generalizing to other Agent-SDK-shaped providers
  • docs/stt-harness.md — agentic streaming speech-to-text, diarization, editable transcript review UI
  • docs/meetings-harness.md — attendees, notes, actions, tiered summaries over a diarized transcript
  • docs/avatar-harness.md — real-time embodied agent: perception → cognition → TTS → 60 Hz LiveLink sync to a UE5 MetaHuman
  • docs/deep-research-harness.md — multi-step, multi-source, citation-bearing research with pluggable topologies
  • docs/agent-host/index.md — long-lived on-disk runtime (SOUL / RULES / MEMORY / USER / SKILL.md) giving an agent persistent identity, skills, hooks, schedules, channels

Python

  • docs/python.md — Python bindings + subinterpreter-pool guest mode
  • docs/python-api.md — Python API reference: submodule map, async surfaces, 0.2 → 0.3 migration

Migration & AI-assisted coding

License

Apache-2.0.