a runtime, not a framework

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 — once mode
$ 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}
the model

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.

you provide

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.

it runs

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.

it ends

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.

model context protocol

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.

01 · consumes

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.

02 · serves

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.

03 · reacts

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.

give it tools — and let it serve its own
# 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
react to a resource changing
# 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 → run
agent-authored workflows

When 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.

deterministic where it can be

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.

humans in the loop, over A2A

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.

durable by MCP

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.

fan out without the model

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.

agentic where it must be

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.

a review loop the agent can write itself
{ "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.
capabilities

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.

no local code

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.

supervision

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.

bounded

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.

security

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.

observability

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.

cloud-native

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.

run shapes

One binary, five shapes

The same loop, the same config — only the lifecycle differs. Each maps cleanly onto a Kubernetes primitive.

once
--mode once
Run an instruction to a terminal status and exit with a cloud-native code. The unit of work is the instruction.
Kubernetes Job
schedule
--mode schedule --cron
Fire on a 5-field UTC cron (or an interval). Hand-rolled, zero-dependency.
CronJob
reactive
--mode reactive --subscribe
Idle cheaply; wake on a pushed MCP resource update, read it, and act. Event-driven, no polling.
Deployment
loop
--mode loop
Self-paced: run, decide when the next iteration is worth doing, sleep, repeat.
Deployment
workflow
--mode workflow --workflow
Drive an explicit graph of steps — branches, loops, fan-out, waits — the agent authored or an operator pinned. Deterministic where it can be, agentic where it must be.
Job / Deployment

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.

agent-to-agent

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.

as a server

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.

as a client

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.

signed agent identity · draft

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.

no shared secret

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.

RFC 9421

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.

Case A · B · C

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.

footprint

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.

first-party deps3 (serde · serde_json · libc)
transportHTTPS everywhere · rustls + ring · bundled roots
runtimeno async runtime · no C toolchain · blocking I/O + threads
binaryone static musl ELF · stripped · on scratch
archamd64 + arm64 · nonroot · read-only rootfs
supply chaincosign-signed · SPDX SBOM attested
the whole image
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
quickstart

Run it

Pull the image, or build from source. Point it at a remote MCP server and a model over HTTPS, and go.

docker
$ 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-6
kubernetes — a one-shot Job
apiVersion: 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
star on github ↗read the docsworkflowsmcp surfaceexamples for Job · CronJob · Deployment in examples/k8s/