A Rust runtime for long-running AI agents. The main idea is that Linux is the harness.
This repo contains:
agent-core: the runtime kernel — a serializable agent IR with anInferoperation, interpreters, providers, hydration, context GC, cost accounting, approvals, tracingagent: CLI for oneshot and persistent agents, this follows the Unix philosophy of "do one thing well", all it does is run an agentic loopagentd: process supervisor for named, long-running sessions — start/stop/resume, turn delivery, systemd unit generationagent-sdk: Rust SDK for embedding the agent loop — typed tools, structured output, streaming events, replayagent-oauth: support for using codex/claude-code subscription auth. Tokens live in~/.local/share/agent/auth.jsonby default; setAGENT_AUTH_FILE(absolute path) to point at a different store — e.g. a per-context work token via direnv, kept separate from your personal one
This was originally written in Haskell in my private monorepo of projects. The Rust port is now the reference implementation, including the agentd supervisor.
Between the Haskell prototype and this Rust port, the implementation changed a lot, but the core ideas have remained.
Traditional computing is:
eval(structured_data)
Modern ML is:
infer(unstructured_data)
An agent alternates between the two. It infers from context, evaluates effects against the environment, reads the result, then infers again.
agent-core makes that structure explicit with a free monad over OpF:
pub enum OpF<S, A> {
Infer { model, prompt, next }, // LLM call: infer(unstructured)
Eval { command, next }, // process call: eval(structured), currently $SHELL -c
Emit { event, next }, // trace
Par { ops, next }, // parallel effects
Pure(A),
}The CLI's actual runtime is the serializable AgentIR, which carries the same Infer/Eval/Emit core and adds Retrieve (a ranked, query-based read over registered context sources), Store (a create/update/delete write to a registered sink), and Tool (typed in-process native tools, recorded and replayed like every other effect). Par runs for real in the IR: a dynamic-width map whose branches execute concurrently, with deterministic effect ids and order-independent replay. See ARCHITECTURE.md for the longer version and docs/MEMORY.md for the retrieval/memory design.
All effects are available to agent programs, including Infer.
So a multi-agent system is not a special framework layer. It is just an agent program that emits multiple Infer calls, maybe with different models, prompts, budgets, or context windows. The outer agent is the orchestrator.
This is the SICP meta-circular idea applied to agents. eval calling eval collapses the interpreter/object-language boundary, Infer calling Infer does the same thing for agents.
This is not just a design stance anymore; it is implemented, measured, and guided. The agent loop exposes an infer tool so the model can dispatch a sub-inference directly, passing bulky tool output by reference (context_refs) instead of copying it. The recorded evals show delegation pays where you'd expect: generation-heavy work measured ~2.7x cheaper in scripted mechanics (1.4-2.3x in the behavioral rounds) via output-rate arbitrage, and pass-by-reference makes delegation-of-reading viable where by-copy delegation costs more than doing it yourself. Models don't discover the mechanism on their own, so the runtime ships operations guidance describing when to delegate — with it, models delegated exactly where the economics pay and nowhere else; without it, they never delegated at all. Details and recorded runs: evals/infer-infer/, evals/delegation/, docs/GUIDANCE.md.
The durable history is an append-only record (checkpoints, traces, replay all
depend on that). What the model sees per turn is a managed window over it:
agent-core models context reads as queries over registered hydration
sources, hydrates passively before each turn, and garbage-collects the
outbound window under budget pressure with five strategies — stack (default),
ring, mark-sweep, semantic, generational — plus eviction markers and a
progress ledger so the model knows what was dropped and where it is (see
docs/GC.md). This is similar to RLM.
There are really only 2 ways to lookup content for context: temporally via chat history, and semantically via similarity search; these operations work on any unstructured text. Similarly, there are 2 times during an agentic turn that an agent can build context: it can be injected passively into the LLM prompt, or the agent can actively use a tool call to find more context. This gives us a neat 2x2 matrix for the design space.
| Passive | Active | |
|---|---|---|
| Temporal | recent messages/history | Retrieve (kind = Temporal) |
| Semantic | RAG/static workspace | Retrieve (kind = Semantic) |
Passive sources run before the model sees a turn, like traditional RAG or appending chat messages.
Active sources are available when the agent decides it needs them: the loop exposes a recall tool that compiles onto the Retrieve effect. Writes mirror this — a remember tool compiles onto the Store effect, and the runtime writes session checkpoints passively at turn completion through the same sink interface.
The Rust CLI runs an agent loop as a normal process. It can take one prompt, read NUL-terminated turns from stdin, or read NUL-terminated turns from a FIFO path. After each turn it can write checkpoints. The protocol is pipes and files.
# One-shot prompt
agent --model openai/gpt-4o-mini "inspect this repo and summarize it"
# NUL-framed stdin session
printf 'go build the thing\0\0' | agent --session --checkpoint-dir .agent-checkpoints
# FIFO-driven session
mkfifo .agent.fifo
agent --fifo .agent.fifo --checkpoint-dir .agent-checkpoints &
printf 'run cargo test\0' > .agent.fifo
# ACP server mode: any Agent Client Protocol frontend (e.g. Paseo) can
# spawn this and drive sessions with streaming, permission prompts, and
# session resume — see docs/ACP.md
agent --acpThe agentd supervisor wraps this with named sessions:
agentd start myagent --model openrouter/auto
agentd send myagent "go build the thing"
agentd logs myagent
agentd status
agentd stop myagent
agentd resume myagent # restart from the latest checkpoint
agentd set-model myagent openai/gpt-4o-mini
agentd gen-systemd myagent # emit a systemd user unitIt is a thin CLI over a conventional directory layout (~/.local/share/agentd/<name>/ with a canonical agent.md spec, a FIFO, a pid file, and checkpoints) — no daemon, no broker, no registry database; the filesystem is the API. Turn delivery is correlated by turn id, send --timeout leaves the turn running and attach re-attaches to it later. See docs/SUPERVISOR.md.
This allows us to use all the regular Linux tooling for managing agents: systemd, kubernetes, docker/podman. Feel free to sandbox your agent with bwrap or nix or whatever you want.
Prebuilt static musl binaries of agent (x86_64 and aarch64 Linux) are attached to GitHub Releases as agent-<tag>-<target>.tar.gz with a combined SHA256SUMS:
v=v0.2.0
target=x86_64-unknown-linux-musl # or aarch64-unknown-linux-musl
curl -LO "https://github.com/bsima/agentd/releases/download/$v/agent-$v-$target.tar.gz"
curl -LO "https://github.com/bsima/agentd/releases/download/$v/SHA256SUMS"
sha256sum -c --ignore-missing SHA256SUMS
tar xzf "agent-$v-$target.tar.gz"
install -m 755 "agent-$v-$target/agent" ~/.local/bin/Or build from source with cargo (the agentd supervisor binary is source-only for now):
cargo install --git https://github.com/bsima/agentd agent
cargo install --git https://github.com/bsima/agentd agentdBuild and test:
cargo test
cargo build --releaseConfigure a model registry:
mkdir -p ~/.config/agent
cp -n examples/models.yaml ~/.config/agent/models.yamlInstalled from a release tarball instead of a source checkout? The same
file ships alongside the binary as models.yaml.example:
mkdir -p ~/.config/agent
cp -n models.yaml.example ~/.config/agent/models.yamlDo not overwrite an existing ~/.config/agent/models.yaml; it is runtime configuration and may contain local aliases used by deployed services.
Set the provider key:
export OPENROUTER_API_KEY=...Run a one-shot prompt:
cargo run -p agent -- --model openrouter/auto "say hello"You can also run a markdown file as the prompt:
cargo run -p agent -- ./task.md
cat input.json | cargo run -p agent -- ./task.mdMarkdown prompts may include YAML frontmatter for fields the CLI applies directly: provider, model, max_iterations, and system_prompt.
---
model: openrouter/auto
max_iterations: 8
system_prompt: ./system.md
---
Inspect this repo and summarize it.system_prompt may be inline text or a path resolved relative to the markdown file.
You can also skip the registry and pass a raw model id.
Then the CLI uses OPENROUTER_BASE_URL or https://openrouter.ai/api/v1, and AGENT_API_KEY or OPENROUTER_API_KEY.
Useful execution controls:
agent --eval-timeout-seconds 10 --eval-max-output-bytes 65536 --eval-env clean "inspect this repo"By default (--eval-env inherit), shell commands issued by the model inherit
the parent environment minus known credential variables —
ANTHROPIC_AUTH_TOKEN and anything ending in _API_KEY — so the model cannot
read the key the agent runs on. Working credentials like GITHUB_TOKEN are
not stripped. Use --eval-env inherit-full if your commands genuinely need
the provider keys, or --eval-env clean for an empty environment.
To put a human in the loop, --require-shell-approval gates every shell
command: the run pauses durably (surviving process restarts) until someone
resolves it with agent approvals --approve/--deny. A denial is a typed
value the model reads and reacts to, not a crash.
Replay recorded Infer and Eval results without an API key or shell execution:
agent --replay-trace ~/.local/share/agent/traces/<run-id>.jsonl --model ignored "same prompt"Inspect what a run cost, from its trace:
agent cost --trace ~/.local/share/agent/traces/<run-id>.jsonlThe default interpreter gives the model direct shell execution. The sane default is a disposable workspace with only the files and credentials needed for the task.
A minimal container pattern:
cargo build --release
mkdir -p .agent-home/.config/agent .agent-work
cp -R ./your-project .agent-work/project
podman run --rm -it \
-e SHELL=/bin/sh \
-e OPENROUTER_API_KEY \
-v "$PWD/target/release/agent:/usr/local/bin/agent:ro" \
-v "$PWD/.agent-home:/home/agent" \
-v "$PWD/examples/models.yaml:/home/agent/.config/agent/models.yaml:ro" \
-v "$PWD/.agent-work:/work" \
-w /work/project \
docker.io/library/rust:1 \
agent --model openrouter/auto "inspect this project"For real use, prefer a purpose-built image with agent, the allowed toolchain, and no ambient secrets.
Add network only when the task needs it.
Mount source read-only unless the agent is supposed to edit it.
Keep traces and checkpoints outside your main home directory if command output may contain secrets.
See ARCHITECTURE.md for the effect algebra, hydration model, session model, and interpreter story. See ROADMAP.md for milestone status.
Design docs for the subsystems:
- docs/AGENT_IR.md — the serializable IR, effect ids, replay,
Par - docs/GC.md — context GC: strategies, invariants, eviction markers, the progress ledger
- docs/MEMORY.md and docs/PROVIDERS.md — retrieval/memory design and the provider-author contract
- docs/GUIDANCE.md — the runtime operations guidance shipped to models
- docs/SUPERVISOR.md — the
agentdsupervisor - docs/TRACE_SCHEMA.md — the versioned public trace event schema
- docs/OTEL.md — OpenTelemetry export
Implemented and tested, at v0.2.0:
- the serializable AgentIR runtime (the CLI's only runtime; the closure-based
Oplayer remains a library builder/test API), with validation, canonical-form hashing, stable effect ids, mid-turn checkpoints, and deterministic replay (including replay of failures) - bounded shell-backed
Eval(timeouts, output caps, env policy with credential stripping) plus direct-exec argvEvalfor typed tool calls Retrieve/Storehydration effects with a file-backed memory backend (--memory-dir), optional embedding-based semantic retrieval, and model-facingremember/recalltools- concurrent
Par(dynamic-width map; deterministic ids, order-independent replay) - context GC: five strategies (
stackdefault), hard guards, eviction markers, escalation, and the progress ledger — behaviorally evaluated on recorded sessions (see evals/gc/) - model-visible sub-inference (
infertool) with pass-by-referencecontext_refsand trace lineage - runtime operations guidance: per-tool descriptions plus a capability-keyed, budget-aware prompt fragment (
--no-runtime-guidanceto opt out) - human-in-the-loop approvals: durable pauses resolved by
agent approvals, in-process hooks in the SDK - cost accounting: per-call and per-run token/cost rollups in traces,
agent costto inspect them - output contracts (
--output-schema): JSON Schema-constrained final answers with bounded repair turns - structured traces with a versioned public event schema, plus optional OpenTelemetry export
- the
agentdsupervisor: named sessions, turn delivery with re-attachment, spec-file config, systemd unit generation - the
agent-sdkcrate: embed the loop with typed native tools, structured output, streaming public events, and replay
Active development:
- behavioral evals: GC strategy validation on recorded sessions continues; guidance A/Bs at realistic budgets
- sandboxing: documented container patterns exist, first-class sandbox-runner integration does not yet
- PromptIR optimization passes (structure and provenance are shipped; optimization is future)
- distributed interpretation, multi-VM campaigns (future)
The design comes from Omni/Agent/Op.hs, a Haskell prototype that proved the free monad Op abstraction in production use. The Rust port started as a translation and has since become the reference implementation.
The meta-circular Infer-emitting-Infer pattern has direct precedent in the SICP meta-circular evaluator.
MIT