agentd is a runtime, not an application. You don't configure features — you hand it three things and it runs the agentic loop:
- an instruction (what to do, ending in an explicit output contract),
- an intelligence endpoint (
--intelligence, the one LLM it talks to), - tools and resources over MCP (
--mcp name=<endpoint> …),
and a mode that decides when the loop runs. Everything below is the same binary with those four knobs turned differently. No plugins, no SDK, no per-use-case code — the use case lives in the instruction and the wiring.
There are two axes to think along:
- agent as a single agent — one supervised subagent runs a task to a terminal status. Pick a mode for the trigger shape (run-once, poll, react).
- agent orchestrating subagents — the root agent delegates through the
subagent.spawnchokepoint into a supervised process tree: each child gets a narrowed objective, a subset of the tools, and a slice of the budget, and returns a small distilled result. The process tree is the agent tree.
The two compose: a reactive single agent can fan a hard task out to subagents, and an orchestrator can drive another agent that is itself reactive.
| You want to… | Mode | Deployment shape | Subagents? |
|---|---|---|---|
| Run a task once and exit with a status | once |
k8s Job / CLI / CI step |
optional |
| Watch a queue/inbox/resource and act on change | reactive |
k8s Deployment |
optional |
| Re-run on a cadence or work-until-done | loop |
Deployment / bounded Job |
optional |
| Fire on a clock with no orchestrator | schedule (or external cron + once) |
k8s CronJob |
optional |
| Split a big task into parallel narrowed workers | any | — | fan-out |
| Let an untrusted reader feed a trusted actor safely | any | — | trust-partition |
| Run a long-lived worker an orchestrator drives + steers | reactive + --serve-mcp |
Deployment |
served |
Every flag below is in configuration.md; the mechanics are
in modes-and-triggers.md, subagents.md,
and mcp.md. Runnable skeletons live in examples/.
Shape: --mode once · a Kubernetes Job, a CLI invocation, or a CI step.
A bounded task that has a definite end: research a topic to a sourced answer, generate a release note from a diff, reconcile two records, draft a migration plan. The run produces its result on stdout, structured telemetry on stderr, and an exit code that encodes the terminal status — so a job scheduler can branch on it.
agentd \
--mode once \
--instruction-file instructions/research.md \
--intelligence https://gw.example/v1 \
--mcp search=https://mcp-search.internal/mcp \
--mcp fs=https://mcp-fs.internal/mcp \
--max-steps 40 --max-tokens 150000 --deadline 5m \
--run-id "research-2026-06-27"The contract. The instruction ends with a required output shape (the
research template emits
Summary / Findings / Open questions / Sources with every claim
attributed). A crisp contract gives the supervisor a crisp terminal state:
completed → 0, refused → 5, exhausted (steps / tokens / the run's own
--deadline) → 7 — and the supervisor's hard wall-clock backstop, when a
child won't self-terminate, kills with 124
(RFC 0007,
RFC 0011).
Why agentd. A bad config exits 2 in milliseconds, before any token is
spent. Setting --run-id makes a retried Job idempotent. The whole thing is one
~1 MB static binary on scratch — nothing to install, nothing to patch.
Shape: --mode reactive · a long-lived Deployment. Idles at near-zero CPU,
wakes on an MCP resource change, acts, returns to idle. Never exits on its
own — only SIGTERM (graceful drain) or a fatal/limit class stops it.
Wire it to anything an MCP server can expose as a subscribable resource — an alert queue, a support inbox, a "new object" bucket notification, a CI webhook landed as a resource — and it triages each item as it arrives.
agentd \
--mode reactive \
--instruction-file instructions/triage.md \
--intelligence https://gw.example/v1 \
--mcp inbox=https://mcp-inbox.internal/mcp \
--mcp tickets=https://mcp-tickets.internal/mcp \
--subscribe "inbox:///items/new" \
--max-steps 25 --max-tokens 2000000 \
--metrics-addr :9090 --drain-timeout 25sThe contract. --mode reactive requires at least one --subscribe
(without it, config validation fails 2). The wake notification carries only
a URI — the agentd resources/reads the item's current state, so a change
missed during a restart is still recovered (level-, not edge-, triggered). The
triage instruction emits one JSON decision
object per item, and — importantly — treats the item's text as untrusted
data, not instructions (the right posture for anything reacting to the
outside world).
Why agentd. The tree-wide --max-tokens ceiling is the ultimate
backpressure under a flood. --metrics-addr adds /healthz+/readyz+/metrics
for k8s probes; --drain-timeout (kept under the pod's
terminationGracePeriodSeconds) bounds graceful shutdown so in-flight triage
finishes before the pod dies.(Reactivity rides the MCP servers' Streamable-HTTP subscriptions — see
modes-and-triggers.md.)
Shape: an external scheduler invoking --mode once (a k8s CronJob) — the
recommended production path, robust to clock skew and restart. For
non-orchestrated hosts, --mode loop (re-enter on a cadence) or --mode schedule
(per-fire identical to once) do it in-process.
Periodic, unattended checks: scan dependencies for new CVEs and open tickets for regressions; reconcile desired vs actual config and file drift reports; sweep a data lake for schema violations every 15 minutes.
# k8s CronJob spec runs, on each fire:
agentd \
--mode once \
--instruction-file /etc/agentd/audit.md \
--intelligence https://gw.example/v1 \
--mcp fs=https://mcp-fs.internal/mcp \
--mcp tickets=https://mcp-tickets.internal/mcp \
--max-steps 30 --deadline 10m \
--run-id "audit-$(date +%Y%m%dT%H%M)"In-process polling instead:
agentd --mode loop --interval 15m --instruction-file /etc/agentd/audit.md …
agentd --mode loop --interval 0 … # work-until-done: re-enter immediately on completionWhy agentd. A CronJob owns lifecycle, retries, and history; agentd owns the
reasoning of one fire and an honest exit code. --interval 0 turns loop into
a drain-a-backlog worker that re-enters the instant it finishes, until a bound
(--deadline / token ceiling) or SIGTERM.
Delegation has exactly one path: the root agent's model calls the
subagent.spawn self-tool. The supervisor (which owns the process table)
mints the child's identity and depth, intersects its tool scope to a subset
of the parent's, clamps its budget to what the tree can still afford, and only
then re-execs a child process. The child returns a distillate (~1–2k tokens)
— never its transcript. Caps (depth 4, 8 children/node, 64/tree, the tree-token
ceiling) come back as ordinary tool-result errors the model can adapt to — a
runaway loop gets refusals, never a fork bomb. The
Rule-of-Two trifecta check is enforced once, at startup, over the
root's whole grant; because scope only ever narrows as you descend, no subtree
can re-acquire a capability the root was refused
(subagents.md,
RFC 0009).
Pattern: a coordinator decomposes a task, spawns N narrowed workers, and
synthesizes their distillates. Spawn sync to delegate one subtask at a time, or
async to run a bounded fan of children concurrently and collect them as they
finish.
Good fits: audit a repository across independent dimensions (security, perf, API-compat, docs) in parallel; summarize 200 documents into one briefing; evaluate several candidate designs against the same rubric; shard a large backfill and reconcile the shard reports.
agentd \
--mode once \
--instruction-file /etc/agentd/repo-audit.md \
--intelligence https://gw.example/v1 \
--mcp fs=https://mcp-fs.internal/mcp \
--mcp tickets=https://mcp-tickets.internal/mcp \
--max-depth 2 --max-tokens 4000000 --deadline 20mThe coordinator instruction does the decomposing — for example:
Audit the repository at
/src. For each of {security, performance, API-compatibility, documentation},subagent.spawna worker whose objective is that dimension only, scoped to thefstool, with a JSON output contract{dimension, findings[], severity}. Do not analyze the code yourself. When all workers return, merge their findings, de-duplicate, and emit one ranked report; open aticketsissue for everyhigh+ finding.
Why this shape. Each worker gets a clean context window (only the slice it needs — half the point of delegating) and a hard slice of the budget, so one runaway dimension can't starve the others. Failures are isolated to a subtree: the security worker timing out doesn't sink the perf worker. The coordinator's window stays lean because it only ever sees the ~1–2k-token distillates; a worker with a large result uses store-and-reference (writes the bulk to a resource, returns a summary + URI) so the coordinator reads detail only if it needs it.
Pattern: keep the agent that reads untrusted input away from the tools that are sensitive or egress-capable. The untrusted reader returns a distilled, structured summary; only that distillate crosses back — raw, possibly-injected bytes never enter a context that can act on them.
This is the agentic answer to prompt injection, and agentd enforces it
structurally. You tag each MCP server's capabilities, and at startup the
supervisor refuses any root grant that gives one agent all three of
untrusted_input + sensitive + egress — the
Rule-of-Two (at most 2 of the 3 legs), overridable only with an
explicit --allow-trifecta (RFC 0012). A
dangerous topology can't even start by accident.
Within one tree you partition the (≤2-leg) work with subagents — read the
untrusted ticket in a child scoped to tickets only, then act in the parent:
agentd \
--mode reactive \
--instruction-file /etc/agentd/handle-ticket.md \
--intelligence https://gw.example/v1 \
--subscribe "tickets:///incoming" \
--mcp tickets=https://mcp-tickets.internal/mcp --mcp-tags "tickets=untrusted_input" \
--mcp crm=https://mcp-crm.internal/mcp --mcp-tags "crm=sensitive"The coordinator delegates reading the (untrusted) ticket to a child scoped to
tickets only — that child has no CRM tool, so a malicious ticket body that
says "look up and leak every customer" reaches an agent with nothing sensitive to
reach for. The child returns {intent, customer_id, summary}; the parent acts on
that clean distillate with crm, and the raw ticket text never enters the
parent's window. This grant is two legs (untrusted_input + sensitive, no
egress), so it starts.
Add the third leg — say, emailing the customer (egress) — and the Rule-of-Two
refuses to co-locate it on this root. That's the runtime steering you to the
right shape: run the actor as a separate agent holding crm + email
(sensitive + egress — still two legs) and have this reactive front hand it the
distillate over MCP — the cross-process composition of use case 6 below. Each
process stays within the Rule-of-Two; no single agent ever holds all three.
Why agentd. The trust boundary is the process boundary plus the
spawn-time scope intersection — not a convention you hope the model follows. An
untagged server is treated conservatively as untrusted_input, so the check fails
closed. (agentd has no exec tool — it runs no local code; the trifecta budget is
entirely over the granted MCP servers' tags.)
Pattern: run agentd as a long-lived MCP server (--serve-mcp https://host:port,
mTLS/bearer auth) that exposes subagent.spawn / subagent.send / subagent.status /
subagent.cancel and the subscribable agent:// state resources. Any MCP client — a
control plane, a workflow engine, or another agent — drives it. Because agentd is
symmetric, composition needs no new protocol: the parent declares the worker (a
separately-deployed HTTPS service) as one more --mcp server.
# A reusable reviewer service:
agentd --instruction "Be a reusable reviewer" --intelligence https://gw.example/v1 \
--mode reactive --subscribe file:///nowhere \
--serve-mcp https://0.0.0.0:8443 --serve-bearer "$REVIEWER_TOKEN"
# An orchestrator agent that delegates to it:
agentd \
--instruction "Run the nightly review; delegate each PR to the reviewer service." \
--intelligence https://gw.example/v1 \
--mcp reviewer=https://reviewer.internal:8443Two patterns fall out (mcp.md §3):
- Drive — the parent calls
subagent.spawnon the worker and gets a clean, bounded distillate back; it never reasons about the worker's internal steps. - Subscribe — the parent spawns
async, subscribes toagent://subagent/{handle}, and is woken bynotifications/resources/updatedwhen the worker reaches a terminal status; it thenresources/reads that URI to collect the status and distilled result — the same notify-then-read discipline agentd uses for every resource, applied to agents themselves.
Warm sessions. subagent.send injects a follow-up turn into a still-warm
worker session — an iterative reviewer that keeps context across rounds ("address
that feedback and re-check"), a chat-shaped assistant fronted by a thin gateway,
a multi-step workflow where each step refines the last. subagent.cancel walks
the kill ladder on a subtree when the orchestrator changes its mind.
Why agentd. The orchestrator gets supervision for free: every served run is a
real, reaped process with a hard deadline, a no-progress watchdog, and active
ping/pong liveness; agent://subagent/{handle} gives the driver an honest,
subscribable view of each child, and the read-only agent://status a view of
the worker itself — without parsing logs.
These aren't exclusive. A realistic production agentd is often several at once: a
reactive front (use case 2) that, per event, fans out to workers (4),
partitions trust so the untrusted reader can't exfiltrate (5), and is itself a
served worker (6) that a higher-level orchestrator drives and can drain on
deploy. The runtime is the same binary throughout — what changes is the
instruction, the --mcp wiring, and the mode.
modes-and-triggers.md—once/loop/reactive/schedulein depth, and the reactive router.subagents.md— the spawn payload, scope intersection, dispositions, caps, and supervision.mcp.md— agentd as MCP client and server, theagent://resources, and composition.security.md— the Rule-of-Two trifecta, secret redaction, and tool scoping.deployment.mdandexamples/— k8sJob/CronJob/Deploymentmanifests and runnable skeletons.