agentd is a small, dependency-light Rust binary that runs one agent. You
give it an instruction and a way to reach an LLM, and it runs an agentic loop —
think, call tools, observe, repeat — until the job is done or a new event wakes
it. Every task tool it can call comes from an MCP server; agentd ships none of its
own and runs no local code. It reaches exactly one LLM endpoint, the
intelligence. And it reacts to the world through MCP resource
subscriptions — a resource changing upstream is what triggers a run.
This page gets you from a checkout to a first end-to-end run, then shows the same
instruction in loop and reactive modes. For the full knob list see
configuration.md; for how triggers and modes work in depth
see modes-and-triggers.md. The architecture is in
RFC 0001.
Build status. The agent runtime is fully implemented — config validation, the agentic loop, the supervisor + subagent process tree, the MCP client, the intelligence client, and all five run modes. The examples on this page run as written.
agentd is a single Cargo crate in a workspace. The default build is dependency-light: no async runtime, no TLS, no C/C++ toolchain.
$ git clone <repo> agent && cd agent
$ cargo build -p agentd-cli --release
Compiling agentd v1.0.0
Finished `release` profile [optimized] target(s)
$ ./target/release/agentd --version
agentd 1.0.0The result is one static binary that starts fast, idles cheaply, and drops
into a container or a VM. The same binary is also the subagent: when a parent
spawns a child, it re-execs argv[0] in subagent mode — there is no second
artifact to ship.
The default build links tls (the https:// transport with bundled roots) — the
only transport agentd uses. Turn on the rest only when you need them (each is gated
so it never weighs down a minimal build):
$ cargo build -p agentd-cli --release # default: tls (https)
$ cargo build -p agentd-cli --release --features serve-https,a2a # served self-MCP + A2A
$ cargo build -p agentd-cli --release --features serve-https,cluster,workflowTo keep TLS out of the binary entirely, terminate it at a same-host sidecar and
point agentd at it over a loopback http:// endpoint (--no-default-features).
The binary needs nothing but libc (or build fully static for FROM scratch).
A minimal image is just the binary — MCP servers are remote HTTP endpoints, so
they are not bundled into the agentd image:
FROM rust:1-bookworm AS build
WORKDIR /src
COPY . .
RUN cargo build -p agentd-cli --release
FROM debian:bookworm-slim
COPY --from=build /src/target/release/agentd /usr/local/bin/agentd
ENTRYPOINT ["agentd"]All configuration is env-settable (12-factor), so the container takes its instruction, intelligence endpoint, and MCP servers entirely from the environment — see configuration.md.
Two loops, deliberately separated:
agentd (main process) = SUPERVISOR ── never talks to the LLM
• parse + validate config (exits 2 on bad config, before any side effect)
• connect declared MCP servers (as a CLIENT) ── this is where ALL tools come from
• arm the trigger: once | loop | reactive | schedule
• subscribe to MCP resources; idle in recv_timeout until something happens
• spawn + supervise subagent child processes; reap, kill, restart
│ spawn (OS process tree)
▼
subagent (child process) = the AGENTIC LOOP ── where intelligence lives
think → call MCP tool → observe → … → terminal status, return a result
(may spawn its own children → agents nest as a process tree)
Three facts are the whole design:
- The supervisor never reasons. It owns lifecycle, triggers, the process
tree, and limits. It has no LLM dependency, so it stays tiny and robust; a
runaway or crashing model is always isolated in a child the supervisor can
SIGKILL. - MCP is the only tool source. agentd ships no
fs/http/shelltool library and runs no local code. Want a capability? Connect an MCP server with--mcp. Its only built-in tools are its self/control primitives (spawn a subagent, subscribe, run a graph). - One intelligence endpoint. A single LLM endpoint named by a URI in
--intelligence—https://(or a loopbackhttp://for a same-host dev gateway). This is the LLM wire, not MCP; the two are different channels.
Output discipline: stdout carries the agent's result; stderr carries JSON-lines telemetry. This holds for a one-shot run and is the convention every example below relies on.
The default mode is once: run the instruction to a terminal status, print the
result on stdout, exit. Here we give the agent a filesystem MCP server and ask it
to do something with a file.
$ agentd \
--instruction "Read /data/report.md and write a 3-bullet summary to /data/summary.md" \
--intelligence https://gw.example/v1 \
--mcp fs=https://mcp-fs.internal/mcpThree things are wired here:
--instruction— the task. (Use--instruction-file <path>to read it from a file, or set theINSTRUCTIONenv var.)--intelligence https://gw.example/v1— the LLM endpoint. A direct provider is--intelligence https://api.openai.com/v1/...with--intelligence-token; a same-host gateway sidecar is--intelligence http://127.0.0.1:4000/v1(loopbackhttp://is the only plaintext allowed). Any other scheme — or a non-loopbackhttp://— is rejected at startup with exit 2.--mcp fs=https://mcp-fs.internal/mcp— declare an MCP server namedfs. The value isname=<endpoint>; agentd connects to that remote Streamable-HTTP MCP endpoint (it spawns no process) and discovers its tools viatools/list. Repeat--mcpfor more servers; declare per-server auth headers in the config file.
On stderr you get one JSON object per line. The run threads a
proc.start, the loop's tool calls, and a terminal proc.exit — all stamped
with the same run_id, agent_id, agent_path, and comp correlation tuple:
agent_path is the cheap subtree-query trick: it is the agent's position in the
process tree (0 = supervisor, 0.1 = first child), so filtering logs by an
agent_path prefix selects a whole subtree with no backend join. Secrets never
appear — the intelligence token prints as *** and is kept out of every log line
and the model transcript.
On stdout you get just the distilled result:
Wrote /data/summary.md (3 bullets). Source: /data/report.md (1,840 words).The exit code is the agent's terminal status mapped to a number, so a script or an external scheduler can branch on it:
| Terminal status | Exit code |
|---|---|
completed |
0 |
| partial result usable | 3 |
| intelligence unreachable / auth failed | 4 |
refused |
5 |
| a required MCP server is down | 6 |
budget hit (exhausted_steps/exhausted_tokens/deadline) |
7 |
| supervisor hard-kill backstop (a child that won't self-terminate) | 124 |
| bad config (validation) | 2 |
Every run is bounded by limits you can tune — --max-steps (default 50),
--max-tokens (default 200000), and --deadline (default 600s) — so a confused
or runaway loop can never burn unbounded cost. See
configuration.md for the full list.
The runtime is fully implemented and runs the command above end to end:
--help and --version exit 0; invalid config exits 2 in milliseconds with
an agentd: … message on stderr; valid config parses, logs proc.start, runs
the agentic loop, and exits on the agent's terminal status (see the exit-code
table above).
loop re-enters the agent on a timer or after each completion — the shape for a
polling or continuously-working agent. It is the same supervisor and same
inner loop as once; only the exit predicate differs. It stops on a bound (max
iterations / wall-clock deadline / tree-wide token ceiling) or a SIGTERM.
$ agentd \
--instruction "Check /data/inbox for new files; process each into /data/done" \
--intelligence https://gw.example/v1 \
--mcp fs=https://mcp-fs.internal/mcp \
--mode loop \
--interval 5m \
--deadline 24h--interval 5msets the re-entry cadence: re-run every 5 minutes.--interval 0re-enters immediately on completion (work-until-done) instead of polling.--deadline 24hcaps the daemon's lifetime; the token ceiling (--max-tokens) and aSIGTERMare the other ways it stops.
A healthy idle loop (nothing to do) backs off exponentially rather than spinning
hot. This is a Deployment-shaped or Job-with-deadline-shaped workload.
reactive is the signature mode: the agentd idles at near-zero CPU and wakes
when an MCP resource it subscribed to changes. Instead of polling on a timer,
you subscribe to concrete resource URIs; an upstream change is the trigger.
$ agentd \
--instruction "When a file appears in the inbox, process it into /data/done" \
--intelligence https://gw.example/v1 \
--mcp fs=https://mcp-fs.internal/mcp \
--mode reactive \
--subscribe "file:///data/inbox"--mode reactiverequires at least one--subscribe(validated at startup; omitting it exits 2).--subscribeis repeatable, one concrete resource URI each.- The supervisor issues MCP
resources/subscribefor each URI (gated on the server advertisingresources.subscribe), then idles inrecv_timeout. When the server emitsnotifications/resources/updated{uri}, the reactive router maps it to exactly one action — spawn a fresh subagent for the event, or continue a warm session — and the agentd wakes, re-reads current state, and works.
Two facts worth knowing up front, both detailed in modes-and-triggers.md:
- Notify-then-read. The update notification carries only the
{uri}— no diff, no payload. The agentd re-reads the resource on wake to learn what changed. Bursts are debounced and coalesced (newest-wins) per route. - You can only subscribe to concrete URIs, not templates. To react to "any
new row," enumerate concrete URIs via
resources/listand subscribe per-URI.
An agentd can even subscribe itself to a resource mid-reasoning (via the
subscribe self-tool) to schedule its own future wake — the capability the
runtime is built around.
Scope notes. Reactivity rides the MCP servers' Streamable-HTTP subscriptions; serving agentd's own MCP (
--serve-mcp) is over HTTP(S) with mTLS/bearer auth (loopbackhttp://for dev). Subagent spawning defaults to synchronous;{async}/{detach}dispositions also ship. Agent-authored cyclic workflows ship under--features workflow(workflows.md). MCP tasks/sampling/roots are deferred (RFC 0013).
- configuration.md — every flag and env var, precedence
(
default < config file < env < flag), limits, secrets, exit codes. - modes-and-triggers.md — the five modes as exit
predicates, reactive routing (exactly-one-owner, spawn-vs-continue,
debounce/coalesce), self-subscribe, and internal
schedule/cron. - RFC 0001 — the architecture front door; sub-RFCs 0002–0013 cover each mechanism in depth.
- docs/design/PLAN.md — the design plan and milestone history for the loop, MCP client, and intelligence client.
{"ts":"2026-06-25T11:18:02.796Z","level":"info","event":"proc.start","run_id":"19efe80512c1a9184","agent_id":"sup","agent_path":"0","comp":"supervisor","pid":1741188,"version":"1.0.0","mode":"once","mcp_servers":1,"subscribe":0} {"ts":"...","level":"info","event":"mcp.connect","run_id":"19efe80512c1a9184","agent_id":"sup","agent_path":"0","comp":"mcp","server":"fs"} {"ts":"...","level":"info","event":"tool.call","run_id":"19efe80512c1a9184","agent_id":"a1","agent_path":"0.1","comp":"agent","server":"fs","tool":"read_file"} {"ts":"...","level":"info","event":"tool.call","run_id":"19efe80512c1a9184","agent_id":"a1","agent_path":"0.1","comp":"agent","server":"fs","tool":"write_file"} {"ts":"...","level":"info","event":"proc.exit","run_id":"19efe80512c1a9184","agent_id":"sup","agent_path":"0","comp":"supervisor","status":"completed","code":0}