agentd
A small, cloud-native AI agent runtime. Give it an instruction and tools from MCP — it runs the agentic loop, calls tools, reads resources, and self-corrects, as a one-shot, a daemon, a reactive service, or an agent-authored workflow.
MCP-native to the core, over HTTPS: tools come only from remote MCP servers, agentd is an MCP server, it reacts to resource subscriptions, and it speaks A2A to other agents. It runs no code of its own. One static binary — supervised, bounded, observable.
$ agentd \
--instruction "triage new GitHub issues and label them" \
--mcp github=https://mcp-github.internal/mcp \
--intelligence https://gateway.internal/v1 \
--model claude-sonnet-4-6
{"event":"mcp.connect","server":"github","proto":"2025-11-25"}
{"event":"loop.start","tools":11,"servers":1,"run_id":"19f0…"}
{"event":"tool.call","tool":"list_issues"}
{"event":"tool.call","tool":"add_labels","args":{"labels":["bug"]}}
{"event":"run.exit","status":"completed","steps":4,"exit_code":0}An instruction, some tools, one loop
agentd is deliberately small. You give it three things; it does one thing well and tells you exactly what happened.
An instruction + MCP servers + a model
The task in plain language, the remote MCP servers whose tools it may use, and an OpenAI-compatible intelligence endpoint over HTTPS. Capabilities are exactly what you wire — no built-in tool zoo, no local execution.
The ReAct loop, supervised
Think → call a tool over MCP → observe the result → repeat, until it has an answer or hits a budget. The loop lives inside a subagent process; a supervisor with no model owns its lifecycle.
A terminal status + a trace
A completed / partial / refused / budget-exceeded outcome, mapped to an exit code — or it stays alive as a reactive daemon. Either way, every step is on the event stream.
MCP-native, three ways
The Model Context Protocol is not an integration in agentd — it is the substrate. Tools, composition, and reactivity all ride one protocol, over Streamable HTTP.
Tools come from MCP
Every tool the agent can call is served by a remote MCP server you declare with --mcp name=https://host/mcp. agentd connects over Streamable HTTP, negotiates the protocol version, discovers the tools, and offers exactly that set to the model. It spawns no process and runs no local code.
agentd is an MCP server
With --serve-mcp https://host:port (mTLS or bearer) it speaks MCP back: a peer calls subagent.spawn (sync · async · detach · warm), subagent.send/status/cancel, and reads agent:// resources. One agent orchestrates others over the same wire.
Reactive on resources
--subscribe <uri> and agentd idles until a server pushes notifications/resources/updated over SSE — then it reads the resource and runs, optionally only when a condition holds. Event-driven agents, no polling, no glue.
# the agent's toolset = the union of its remote MCP servers
$ agentd --instruction "reconcile the inbox" \
--mcp fs=https://mcp-fs.internal/mcp \
--mcp gh=https://mcp-github.internal/mcp \
--serve-mcp https://0.0.0.0:8443 \
--serve-client-ca /tls/clients.pem # ← agentd is now an mTLS MCP server# wake on every change to the watched resource
$ agentd --mode reactive \
--subscribe inbox:///items/new \
--mcp inbox=https://mcp-inbox.internal/mcp \
--instruction "classify each new item and route it"
{"event":"trigger.armed","kind":"reactive","subscriptions":1}
{"event":"resource.updated","uri":"inbox:///items/new"} # ← push → runWhen one loop isn't the right shape
Some work is a graph, not a single reasoning loop. agentd lets the agent build one itself — like LangGraph, but the agent authors and drives the graph, and agentd supervises every node.
Twelve node kinds
agent, tool (with $from data flow), assign, infer (schema-checked structured extraction), branch, foreach, parallel, join, wait, human, subgraph, halt — plus writes_mode reducers (append/merge/union). A tool/branch-only path spends zero model tokens.
Ask a person mid-workflow
A human node flips the served A2A task to input-required with the question as its status message; the person answers with a plain SendMessage carrying the task id — the spec's own multi-turn shape — and the workflow resumes on replied.
Crash-resume, fork, time-travel
A checkpoint policy writes every superstep to ANY MCP server speaking a 3-tool profile. --workflow-resume recovers a SIGKILLed run with its blackboard and budget intact; @seq under a new run id is a fork. No database linked — the store lives behind MCP.
Process arrays at scale
A tool returns 500 items? foreach maps a body over each — and parallel runs different bodies at once — on up to 8 shared lanes, deterministically, without feeding anything through the LLM.
Authored, run, or delegated
The agent calls workflow.define / workflow.run mid-reasoning, an operator pins one with --mode workflow, or a parent hands a whole workflow to a supervised subagent. Layered termination — budget, token pool, deadline, loop + progress guards — every stop with a reason.
{ "start": "draft",
"nodes": {
"draft": { "kind": "agent", "instruction": "draft the release note", "writes": "doc",
"edges": { "ok": "judge", "error": "fail" } },
"judge": { "kind": "branch", "cases": [], "default": "revise",
"semantic": { "prompt": "Is it ready to publish?", "reads": ["doc"],
"choices": { "yes": "publish", "no": "revise" } } },
"revise": { "kind": "agent", "instruction": "revise it", "reads": ["doc"],
"writes": "doc", "edges": { "ok": "judge", "error": "fail" } },
"publish":{ "kind": "halt", "status": "completed", "result_from": "doc" },
"fail": { "kind": "halt", "status": "crashed" } } }
# feature-gated (--features workflow); optional CEL for expression predicates.Small surface, serious guarantees
agentd is minimal where it can be and uncompromising where it must be — no local execution, supervision, budgets, authenticated control, and observability are not add-ons.
It runs nothing of its own
agentd ships zero tools and executes no code — every capability comes from a remote MCP server you declare. There is no shell, no exec, no plugin. A prompt-injected agent has nothing to break into; the blast radius is exactly the servers you wired.
Two-loop, no orphans
A supervisor that never reasons owns lifecycle; the agentic loop runs only inside subagent processes. Dead/stuck detection, a bounded kill ladder, PR_SET_PDEATHSIG, and a restart governor mean a crashed or wedged agent never leaks.
Budgets, by construction
Every run is capped by steps, tokens, and a wall-clock deadline; a subagent tree rolls token usage up to one ceiling. Exceed it and the subtree is drained — the agent can spend, but only what you granted.
Authenticated identity + Rule of Two
Trust is a verified mTLS cert or a constant-time bearer — never the transport. Tools are tagged untrusted-input / sensitive / egress; granting one agent all three lethal-trifecta legs is refused at startup unless you override it. Scope narrows monotonically down the tree; secrets are redacted everywhere.
Everything is auditable
One JSON-lines event stream with run_id + agent_path tree correlation, W3C trace-context propagation, and dependency-free OTLP export. /healthz, /readyz, /metrics for k8s — all opt-in, all off by default.
Built for the cluster
Terminal statuses map to a documented exit-code contract a podFailurePolicy branches on; SIGTERM drains gracefully to exit 0, not a 143 failure. cgroup-v2 teardown, horizontal sharding + work-claim leases, SIGHUP hot-reload. One static binary, nothing to patch.
One binary, five shapes
The same loop, the same config — only the lifecycle differs. Each maps cleanly onto a Kubernetes primitive.
And within a run, an agent can spawn subagents (sync · async · detach · warm), delegate a whole workflow to a child, or delegate over A2A to another agent entirely. Depth and breadth are bounded; the whole tree is one reaping domain. Operators drive a running instance over the same HTTPS surface — a2a.Drain / Pause / Cancel, authenticated, never a plaintext control plane.
A first-class agent in the mesh
agentd speaks the Agent2Agent protocol both ways — a served run is an A2A Task — so it interoperates with any conformant A2A peer, not just other agentds.
Your agent, callable
A peer sends SendMessage / SendStreamingMessage, polls GetTask, and streams status + artifact updates over SSE — the A2A spec's JSON-RPC binding, verbatim. mTLS/bearer-gated.
It delegates outward
Declare a peer with --a2a-peer and the agent can hand an objective to another A2A agent mid-reasoning, streaming-first with graceful recovery. One protocol for the whole mesh.
The full method surface — SendMessage · SendStreamingMessage · GetTask · CancelTask · ListTasks · SubscribeToTask — with spec-exact semantics: blocking by default (opt into returnImmediately), spec error codes (TaskNotFound, TaskNotCancelable, UnsupportedOperation), and terminality signalled by the task state + stream close. A run's lifecycle is the Task lifecycle — no adapter layer, no second state machine.
AAuth — your agent, provably itself
Calling an MCP server protected by AAuth? agentd gets an Ed25519 identity, a short-lived token from an Agent Provider, and signs every MCP request — no shared API key, and the server knows exactly which agent is calling.
Keys, not API keys
agentd holds an Ed25519 key, enrolls once with an Agent Provider, and fetches a short-lived agent token — cached and refreshed automatically. Nothing to rotate by hand; no long-lived secret to leak.
Every request signed
Each MCP POST — and the --intelligence dial — carries HTTP Message Signatures over @method / @authority / @path. The server (or model gateway) verifies and knows the caller by signature, not source IP; the whole subagent tree signs under one identity.
Reacts to what the server asks
The server wants the agent (Case A)? It's already signed. An opaque AAuth-Access token (Case B)? Adopted and replayed. The human behind it (Case C)? agentd runs the Person Server exchange and presents the user token — all inside one request, bounded.
Turn it on with just --aauth-provider — the feature is in the release binary and image. In steady state the human is never in the loop; they enable the agent once and it signs every call. Draft support — all three access modes end-to-end (identity, resource-managed, and user-scoped Person-Server consent), plus federated enrollment (per-pod projected tokens, no shared secret). It ships free: the one crypto dependency, ring, is the same one rustls already links — zero new crate.
Minimalism is the moat
Three first-party dependencies. The only other code in the build is rustls + ring for the HTTPS transport — no async runtime, no framework, no C toolchain. It links statically and ships on an empty base.
FROM scratch COPY agentd /agentd # one static musl binary USER 65532:65532 # nonroot ENTRYPOINT ["/agentd"] # no shell · no libc · no package manager · nothing to attack or patch # HTTPS by default (rustls + bundled roots) — dial https:// with no CA bundle # opt-in k8s probes: --metrics-addr :9090 → /healthz /readyz /metrics # opt out of TLS (--no-default-features) for a loopback sidecar posture
Run it
Pull the image, or build from source. Point it at a remote MCP server and a model over HTTPS, and go.
$ docker run --rm ghcr.io/agentd-dev/agentd \
--instruction "summarize /data/report.txt and write a digest" \
--mcp fs=https://mcp-fs.internal/mcp \
--intelligence https://gateway.internal/v1 \
--model claude-sonnet-4-6apiVersion: batch/v1
kind: Job
metadata: { name: agentd-digest }
spec:
template:
spec:
restartPolicy: Never
containers:
- name: agentd
image: ghcr.io/agentd-dev/agentd:latest
args: ["--mcp", "fs=https://mcp-fs.internal/mcp"]
env:
- { name: INSTRUCTION, value: "digest the report" }
- { name: AGENT_INTELLIGENCE, value: "https://gw/v1" }
# podFailurePolicy maps agentd's exit codes → retriable vs terminal