Agentd
FreeNot checkedModel Context Protocol base library: wire types, version/era negotiation, transports
About
Model Context Protocol base library: wire types, version/era negotiation, transports
README
A minimal, MCP-native, cloud-native AI agent runtime. One small static Rust
binary runs one agent: hand it an instruction and one LLM endpoint, and it
runs the agentic loop — think, call a tool, observe, repeat — until the task
reaches a terminal status or a new event wakes it. Every tool comes from a
remote MCP server over HTTPS (agentd ships none of its own; local execution
is off unless you compile and enable the guarded exec runner), it reacts to
the world through MCP resource subscriptions, speaks A2A to other
agents, and can drive durable DAG workflows. It is built to be a
cloud-native unit of work — drop it into a Job, a CronJob, or a long-lived
A2A Deployment — and when you want to work with it, attach a terminal or a
browser: agentd tui -c agent.yaml.
binary 8.5 MiB static (musl, FROM scratch) · 3.6 MiB download · cold start <1 ms
idle daemon 5.5 MiB RSS · protocols from their own SDKs · HTTPS everywhere · AGPL-3.0
- Why agentd
- How it works
- Install
- Quickstart
- Talk to it — TUI & web UI
- Lifecycle & triggers
- Workflows
- Embedding — the engine in your app
- Composition: serving, subagents, A2A
- Security model
- Operating it
- Scaling out
- Build features
- Footprint (measured)
- Documentation map
Why agentd
- The protocols are not ours to get wrong. MCP is
rmcp, the official Rust
SDK; A2A is a2a-rs, generated from
the specification's protocol buffers. Both run over agentd's own socket, so
request signing, mTLS and the SSRF guard survive the adoption. Everything
small and frozen — the HTTP client, the cron parser, Prometheus text, OTLP
export, the inotify watch — is still hand-rolled on
std+libc. What ships is one 8.5 MiB static binary that starts in under a millisecond, idles at 5.5 MiB on one thread, and lands as a single-layerFROM scratchimage with no shell, no libc, and nothing to CVE-scan but agentd itself. - MCP as the universal interface. agentd has no built-in
fs/http/shelltool library and, in a release binary, executes nothing locally — theexeccontract is mapping-only unless it is both compiled in and enabled (see Security model). Every capability is a remote MCP server you declare with--mcp name=https://…— one protocol out, tools and resources alike. What comes in is A2A; see 5. - Reactivity via resource subscriptions. Instead of polling, an agentd with
a
subscribestart node idles at near-zero CPU and wakes when an MCP resource it subscribed to changes (notify-then-read). An upstream change is the trigger; a workflow can also schedule its own future wakes (loop,schedule). - Two loops, strictly separated. A tiny supervisor owns lifecycle,
triggers, limits, and the kill ladder — and never talks to the LLM. The
reasoning lives in subagent child processes (the same binary, re-exec'd)
the supervisor can always
SIGKILL. A runaway or crashing model is contained by construction; limits are enforced by a process that cannot be prompted. - Composability, three ways. An agentd serves an A2A endpoint
(
a2a.listen), so one agent is just another agent a second one sends messages/commands to. It delegates over A2A (a2a.peers) to remote agents as spec-conformant Tasks. And it nests subagents as an OS process tree with narrowed, per-child context and trust. Agents compose like Unix processes.
How it works
triggers: interval · cron · MCP resource change · A2A request
│
┌────────────────────────────────▼─────────────────────────────────┐
│ supervisor (never talks to the LLM) │
│ config → validate → trifecta gate → mode driver → kill ladder │
│ limits: steps · tokens · deadline · depth · cgroup mem/pids │
└────────────┬─────────────────────────────────────┬───────────────┘
│ spawn (re-exec, narrowed payload) │ serve (optional)
┌────────────▼────────────────┐ ┌────────────▼───────────────┐
│ subagent (agentic loop) │ │ A2A listener over HTTPS │
│ think → tool → observe … │ │ Tasks · streaming · card │
│ or: workflow driver │ │ commands · operator ctl │
└──────┬──────────────┬───────┘ └────────────────────────────┘
│ HTTPS │ HTTPS
┌──────▼──────┐ ┌────▼──────────────┐
│ intelligence│ │ MCP servers │
│ (one LLM │ │ --mcp a=https://… │
│ endpoint, │ │ --mcp b=https://… │
│ failover) │ │ tools+resources │
└─────────────┘ └───────────────────┘
Every network edge is HTTP(S) — the LLM, the MCP servers, the served A2A
endpoint, A2A peers, and operator control — with mTLS and/or bearer auth
(plaintext http:// is loopback-only, for dev). agentd links no vsock or stdio
transport and spawns no tool processes in a release binary (the exec local
runner is a build-and-config opt-in — see Security model);
the one non-TCP transport is a unix domain socket for a co-located A2A peer, where the kernel is the authenticator
— mode 0600 plus an SO_PEERCRED uid check instead of TLS (see
docs/a2a.md).
Install
Installer — detects your architecture, verifies the release SHA256SUMS,
installs to /usr/local/bin (or ~/.local/bin), and never invokes sudo:
$ curl -fsSL https://agentd.dev/install.sh | sh
Release binaries (static musl, amd64 + arm64) if you would rather do it by
hand — install.sh --help lists the pinning and directory options:
$ TAG=$(curl -fsSL https://api.github.com/repos/agentd-dev/source-code/releases/latest | grep -m1 tag_name | cut -d'"' -f4)
$ curl -LO https://github.com/agentd-dev/source-code/releases/download/$TAG/agentd-$TAG-x86_64-unknown-linux-musl.tar.gz
$ tar xzf agentd-$TAG-x86_64-unknown-linux-musl.tar.gz && ./agentd --version
Container image (multi-arch, cosign-signed, single layer):
$ docker run --rm ghcr.io/agentd-dev/agentd:latest --capabilities
From source (Rust stable, 1.96+; no C toolchain — the build is pure Rust and
the crypto provider is ring). Features are compile-time, so --capabilities
tells you what a given binary can actually do:
$ cargo build -p agentd-cli --release
$ cargo build -p agentd-cli --release \
--features "a2a,metrics,cron,otel,hot-reload,config-watch,aauth,oauth,cel,sign,oci,decrypt" # the shipped set
$ cargo build -p agentd-cli --release --features a2a,exec # + the local command runner
exec is deliberately absent from release binaries — running local commands
is opt-in twice over. cel ships: expression guards (when,
until, filter) are how a workflow branches, and a released binary that
refuses them at validation was a cliff every non-trivial config fell off. It is
the one dependency-bearing feature and costs about 1.8 MiB.
Quickstart
# one-shot: instruction + one LLM endpoint + one MCP server, then exit
$ 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/mcp
stdout carries the result; stderr carries JSON-lines telemetry (one structured
event per line, trace-correlated); the exit code maps the terminal status. Bad
config exits 2 in milliseconds, before any LLM round-trip — and
--validate-config checks a config without running anything. The intelligence
endpoint speaks the OpenAI-compatible wire with native tool-calling; a
comma-list of endpoints is a failover order. See
docs/getting-started.md.
Talk to it — TUI & web UI
agentd hosts the state; the clients are thin. One command runs the daemon and a terminal UI together:
$ agentd tui --config agent.yaml # or: agentd ui -c agent.yaml (browser)
agentd · prod-agent chat tasks subagents debug
you › Deploy api-gateway to staging
agent › Deploy checks passed. Rolling out v2.4.1 — 3 pods cycling, ETA 90s.
⣾ read_file · 3s · 1.2k tok
● live http://127.0.0.1:8420 · 1 turns · 33/17 tok
Because the daemon owns the session, several surfaces watch the same one at
once — a terminal at your desk, a browser on another screen, a colleague's
machine paired with a rotating code — and quitting a client leaves the agent
working. Approvals (ask_human) render as answerable rows in every attached
client and survive a restart; a debug mode exposes the live event feed,
per-step run detail and the log tail when you ask for it.
Both clients ship as one npm package — npm i -g @agentd-dev/cli, source under
interface/ — built on a shared thin-client core that the package
also exports, so a third client is a small program. See
docs/interface.md, and
docs/coding-agent.md to set one up as a
pair-programming agent for a repository.
Lifecycle & triggers
agentd has one durable runtime — there are no modes to pick between. A run
is either a one-shot job or a long-lived daemon (lifecycle.run_until),
and what triggers runs is a workflow start node.
# a job (the quickstart): the --instruction sugar expands to a
# `once → agent → finish` workflow; run one turn, map the outcome to an exit
# code, then exit.
$ agentd --instruction "…" --intelligence https://gw.example/v1
Recurring / reactive shapes are workflow start nodes in a
config_version: "1" document (see
docs/modes-and-triggers.md):
config_version: "1"
intelligence: { endpoints: https://gw.example/v1, model: gpt-… }
store: { kind: mcp, mcp: { server: state } } # a daemon needs a durable store
a2a: { listen: https://0.0.0.0:8443,
tls: { cert: …, key: …, client_ca: … } } # the external channel
workflows:
- name: watch
steps:
s: { kind: subscribe, server: queue, uri: "queue://inbox" } # loop|schedule|subscribe|signal|event
do: { kind: agent, depends_on: [s], instruction: "Handle the item." }
f: { kind: finish, depends_on: [do] }
lifecycle: { run_until: drained } # a daemon
A non-loopback a2a.listen is refused at startup unless the endpoint has
client auth — a2a.tls.client_ca (mTLS, and then every caller needs a client
certificate), a2a.bearer, or interface.pairing. --traceparent continues an
upstream W3C trace.
Workflows
agentd runs durable DAG workflows (always compiled — no feature
flag): a declarative graph of steps in the config_version: "1" document,
driven by the same reactor over durable state, so a run survives a restart and
resumes exactly where it died. Deterministic steps (assign / map / filter /
switch / …) cost zero model tokens; agent / think steps run turn
workers:
workflows:
- name: process
steps:
s: { kind: once }
fetch: { kind: agent, depends_on: [s], instruction: "fetch the next item",
output_schema: { type: object, properties: { id: {type: string}, status: {type: string} } } }
route: { kind: switch, depends_on: [fetch], on: "{{steps.fetch.output.status}}",
cases: { pending: [work] }, default: [done] }
work: { kind: mcp.tool, depends_on: [route], server: fs, tool: process,
args: { id: "{{steps.fetch.output.id}}" } }
done: { kind: finish, depends_on: [work] }
- A rich node catalogue:
agent/think(turn workers),mcp.tool(direct MCP), data steps (assign/map/filter/reduce/sort/parse),switchrouting, nested bodies (foreach/batchwith bounded parallelism +ratepacing,iterate,parallel,race,subgraph), orchestration (waiton resource / condition / signal / run / subagent / message / deadline,join, childworkflowruns,subagent,humangates that project A2Ainput-required,a2a.delegate), andfinish. - Variables + templates thread data between steps (
writes/{{vars.…}}/{{steps.x.output}}/CEL:expressions); large step outputs spill to durable artifacts and dereference transparently. - Durable + crash-resumable: every step checkpoints its progress to the configured store before its effect runs — a run restores and resumes exactly where it died (proven by the chaos-matrix e2e), with idempotency keys so at-least-once effects run once. No database is linked — the store is behind MCP tools or HTTP.
- Triggers are start nodes (
once/loop/schedule/subscribe/signal/event/manual/a2a) — the recurring/reactive shapes, durable across restarts. - Bounded by construction: step / token / deadline budgets, concurrency
policies (
queue/drop/replace), and per-node caps — each terminal with a distinctstatus/reason. - Optional CEL (
--features cel):CEL:step conditions, computed values, and data-step element expressions; a non-CEL build fails those closed.
See docs/workflows.md.
Embedding — the engine in your app
agentd is also a library: the binary is a thin shell (agentd-cli) over
the published engine crate (agentd-core, lib name agentd). Any Rust app can
run agentic logic as a function call, with native Rust tools the model
calls alongside MCP tools:
// 1. Register your code as a tool (it joins the model's catalogue — and wins
// name collisions with remote servers; first-party is unstealable).
agentd::tools::register(agentd::tools::CodeTool::new(
"word_count", "Count the words in a text.",
json!({"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}),
|args| Ok(json!({ "words": args["text"].as_str().unwrap_or("").split_whitespace().count() })),
))?;
// 2. One agentic run, as a call — Outcome + token Usage back as plain values.
let intel = IntelClient::from_parts("https://gw.example/v1", token)?;
let (outcome, usage) = run_loop(&intel, &mcp_servers, &LoopInput {
instruction: "Count the words in this review, then summarize it.".into(),
output_contract: Some("JSON: {words, summary}".into()),
model: "my-model".into(), max_steps: 10, max_tokens: 20_000,
deadline: Instant::now() + Duration::from_secs(120),
seed: vec![], cancel: None,
}, &mut NoSelfTools, &log)?;
Workflows embed the same way — author a workflow graph as data, drive() it
with your own executor, and code tools are addressable from tool nodes as the
reserved server code. A compile-guaranteed example ships in-tree:
embedded-agent.rs — the loop
called directly from a host app, with a code-registered tool the model can call
into mid-reasoning. The stock CLI registers nothing
— its no-local-code posture holds by construction. Reusable on their own:
agentd-mcp (MCP client/server + wire) and agentd-net (transports). Recipes,
the embedder obligations (the re-exec dispatch!), and the API-stability tiers:
docs/embedding.md.
Composition: serving, subagents, A2A
Serve your agent over A2A (--features a2a) — set a2a.listen and
peers call SendMessage (natural language → a conversation turn, or a command
DataPart → a registry action like status / workflow.run / config),
GetTask / ListTasks / CancelTask, and SendStreamingMessage (SSE) on the
listener, each resolved to a principal (mTLS / bearer → operator / user /
agent / anonymous) and authorized against a role matrix, and (optionally)
audited:
a2a:
listen: https://0.0.0.0:8443
tls: { cert: tls.crt, key: tls.key, client_ca: clients.crt } # and/or a bearer:
bearer: "{{secret:A2A_BEARER}}"
principals:
- match: { san: "spiffe://team/*" }
role: user
grants: [knowledge.*]
Delegate over A2A — a workflow a2a.delegate step (or a subagent) calls a
declared peer as a spec-conformant Task:
a2a:
peers:
- name: research
endpoint: https://research-agent.internal:8443
Nest subagents — a parent spawns a child by re-exec'ing the same binary
with a narrowed spawn payload (subset of servers, tighter limits, its own
cgroup). The tree is bounded by --max-depth and a spawn-rate token bucket;
every child is one SIGKILL from gone.
Security model
- No local execution by default.
execships as a mapping-only contract: a local runner exists only when the binary was built--features exec(never a release binary) andsecurity.exec.enabledis set. Otherwise the attack surface of a tool call is the remote MCP server's, not the host's. - Rule-of-Two trifecta gate. Tag servers with
--mcp-tags name=untrusted_input,sensitive,egress; a config that wires all three legs into one agent is refused at startup unless you explicitly--allow-trifecta. - Authenticated everything. Outbound: bearer/OAuth 2.1 client-credentials +
bundled webpki roots (+
--tls-cafor private PKI). Inbound: mTLS client CA and/or constant-time bearer; operator verbs (theadmin.*ops) require the operator role — granted by a matchinga2a.principalsrule, by a verifieda2a.bearer, by any client certificatea2a.tls.client_caaccepted that no rule claims (on a listener that sets a bearer or declares no principals), or by a loopback caller when no principals are configured. Discovery stays public, though: the unauthenticated agent card advertises the whole command surface,admin.*included, and only the authenticatedGetExtendedAgentCardnarrows the skills to the ops that caller may actually run. - Hardened served surface. Cross-origin requests are rejected (403 — only
loopback and the origins listed in
interface.originsare admitted); plaintext serving is loopback-only. - Secrets discipline. Tokens come from env or mounted files
(
--intelligence-token-filerotates live) and are never logged; telemetry logs lengths, not contents, unless you opt in with--log-content. - Contained blast radius. Reasoning runs in killable child processes under
optional per-run cgroups (
--cgroup,--cgroup-memory-max,--cgroup-pids-max) with atomiccgroup.killteardown.
See docs/security.md.
AAuth [draft] — signed agent identity
Calling an MCP server protected by AAuth? Build with --features aauth and
agentd gets an Ed25519 identity, an agent token from an Agent Provider,
and signs every MCP request (RFC 9421) — no shared API key, and the server
knows exactly which agent is calling:
$ agentd --instruction "…" --intelligence https://gw.example/v1 \
--mcp secure=https://mcp.secure.example/mcp \
--aauth-provider https://apd.example --aauth-enroll-token '{{secret:ENROLL}}'
The token is fetched, cached, and refreshed automatically; the whole subagent tree signs under one identity. Draft support (Case A end-to-end); ships build-from-source, like CEL. See docs/aauth.md.
Operating it
Exit codes are the contract: 0 completed · 1 crash · 2
config/usage (fails in ms, pre-LLM) · 3 stalled/partial · 4 intelligence
unavailable · 5 refused · 6 required MCP server down · 7 budget/deadline
exhausted · 124 supervisor hard-kill backstop · 137/143 external kills. A
clean drain is always 0, never 143. Policy codes (3/7) can be remapped with --budget-exit-code for
schedulers that treat nonzero as retry-forever.
Telemetry: JSON-lines on stderr (trace-correlated, --log-level), a
--report-file path the schema accepts but the runtime does not write — the
terminal outcome is the proc.exit event and the A2A task artifact —
Prometheus /metrics + /healthz + /readyz via --metrics-addr
(--features metrics), OTLP spans with GenAI semconv via --features otel, a
liveness heartbeat file via --health-file, and the live log ring tailed with
the debug.events command op (needs interface.enabled + interface.debug).
Discovery: agentd --capabilities prints a machine-readable manifest
(runtime: "1", a surfaces{} block pinning the exit_codes and
config_schema contract versions, and, alongside it, exactly what's compiled
and configured in) and exits — feature-detect from this, not the version string.
Control plane: an operator-role principal drives the served endpoint with
the admin.drain / admin.lameduck / admin.pause / admin.resume /
admin.cancel command ops — each a DataPart on an ordinary A2A SendMessage,
not a custom JSON-RPC method. SIGTERM starts a graceful drain
(--drain-timeout < pod grace).
Hot reload (--features hot-reload): SIGHUP — or a ConfigMap volume
swap with --watch-config (--features config-watch) — revalidates and
reapplies the reloadable subset (model, limits, log level, subscriptions,
live MCP server set) at a quiesce boundary, restart-free.
See docs/operations.md and docs/observability.md.
Scaling out
Scale inside one instance first (concurrency.max_runs, limits.max_runs,
agent.max_parallel_turns, parallel on fan-out steps). For a fleet, agentd has
no coordination protocol of its own — ownership lives where it can actually
be arbitrated: partition the subscriptions at the source, or use the queue's own
claim/lease semantics from a workflow step. Prometheus metrics feed the
autoscaler.
See docs/scaling.md.
Build features
The default build is intentionally small; everything else is opt-in at compile
time. A flag whose feature is absent exits 2 loudly — never a silent no-op.
| Feature | What it adds | Extra deps |
|---|---|---|
tls (default) |
rustls + ring + bundled roots — direct https:// everywhere |
rustls stack |
a2a |
the A2A HTTPS listener + outbound delegation peers | — |
cel |
CEL step conditions / computed values / data-step expressions | cel-interpreter (the one exception) |
otel |
hand-rolled OTLP/HTTP trace + log export, GenAI semconv | — |
metrics |
hand-written Prometheus text + health endpoints | — |
aauth |
an Ed25519 agent identity that signs outbound MCP requests | ring (already in-tree) |
exec |
the guarded local command runner (never in a release binary) | — |
cron |
5-field UTC cron scheduling (hand-rolled parser) | — |
oauth |
OAuth 2.1 client-credentials for remote endpoints | — |
hot-reload / config-watch |
SIGHUP / inotify restart-free reconfig | — |
Shipped release feature set:
a2a,metrics,cron,otel,hot-reload,config-watch,aauth,oauth,cel,sign,oci,decrypt.
Footprint (measured)
Measured on the 1.1.0 release build (x86_64, musl, stripped, the shipped feature set):
| Metric | Value |
|---|---|
Binary (static-PIE, runs on scratch) |
8.5 MiB amd64 · 6.3 MiB arm64 |
| Release download (amd64) | 3.6 MiB .tar.gz → 8.5 MiB binary |
Cold start (--version / --capabilities) |
< 1 ms |
| Idle daemon RSS (schedule workflow, file store) | 5.5 MiB, 1 thread |
| Idle daemon CPU | 1 jiffy / 6 s — under 0.2% of a core |
Served request overhead (tools/call, loopback, fresh conn) |
p50 0.26 ms |
| Deterministic workflow steps | ~146k steps/sec (single lane, 0 model tokens) |
Documentation map
- docs/README.md — the task-oriented guide index: getting started · configuration · architecture · mcp · modes & triggers · interface (TUI/web UI) · coding agent · workflows · subagents · intelligence · security · observability · operations · deployment · scaling · use cases
- examples/SAMPLES.md — runnable samples: a coding
agent (
coding-agent.yaml), Docker Compose, KubernetesJob/CronJob/Deploymentmanifests, a systemd unit. - skills/ — an Agent Skill that teaches an AI coding
assistant to install, configure and debug agentd (drop it in
~/.claude/skills/). - SECURITY.md — what counts as a vulnerability here, and how to report one privately.
- CONTRIBUTING.md — build, test and review expectations.
- CHANGELOG.md — release history.
- Website: agentd.dev — the rendered documentation.
License
AGPL-3.0-only — see LICENSE. One crate is deliberately permissive:
agentd-instruction (the Instruction Specification's reference parser,
crates/instruction) is MIT OR Apache-2.0, so other implementations can link
it. Some files are vendored from the
Instruction Specification under their own terms (the schema under CC BY 4.0,
the conformance corpus under Apache-2.0) — see NOTICE. agentd is fully open source; commercial licensing (for proprietary embedding or AGPL-free use) and commercial support are available — contact [email protected].
Installing Agentd
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/agentd-dev/source-codeFAQ
Is Agentd MCP free?
Yes, Agentd MCP is free — one-click install via Unyly at no cost.
Does Agentd need an API key?
No, Agentd runs without API keys or environment variables.
Is Agentd hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Agentd in Claude Desktop, Claude Code or Cursor?
Open Agentd on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Agentd with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
