agentd is one binary that runs one agent. An external scheduler starts,
stops, replicates, and watches it; the binary itself owns no control plane
(RFC 0011 §1). This page is a set of deployment recipes for the v1 target:
- Standalone CLI — one-shot
- Long-lived reactive daemon
- Container — minimal scratch/distroless image
- Scheduled by an external orchestrator (Kubernetes)
The same supervisor loop backs every shape; they differ only in the exit
predicate — and therefore in which exit codes are reachable (RFC 0011 §7). A
unit of work (once) goes empty-and-final and exits; a reactive daemon
(reactive) idles on a subscription stream and exits only on signal or a fatal
class.
Build status. The runtime is implemented — config precedence + validate-at-startup (exit
2), the agentic loop, the supervisor + subagent process tree, the MCP client, all five run modes (once/loop/reactive/schedule/workflow), reactive routing, and the served self-MCP all ship. The examples below describe real behaviour.
Every flag and env var on this page is taken verbatim from
crates/agentd/src/config.rs (agentd --help).
If a flag is not in --help, it does not exist. See
configuration.md for the complete flag/env reference,
the config-file schema, and the reloadable-vs-restart-only partition.
Precedence, top wins: built-in default < config file < env var < CLI flag.
The config file (--config/AGENT_CONFIG) is live (RFC 0017) — a local JSON
document for structural config (MCP inventory, subscriptions, limits, the
intelligence endpoint list + headers). Everything else is env-settable;
secrets are env/flag/mounted-file only, never inline in the config file
(RFC 0011 §3.2; the file may carry {{secret:NAME}} / {{secret-file:PATH}}
references).
| Concern | Env | Flag |
|---|---|---|
| Instruction | INSTRUCTION |
--instruction <TEXT> / --instruction-file <PATH> |
| Config file | AGENT_CONFIG |
--config <PATH> |
| Intelligence list | AGENT_INTELLIGENCE |
--intelligence https://… (loopback http:// for dev; comma-list = failover) |
| Intelligence creds | AGENT_INTELLIGENCE_TOKEN / …_FILE, …_<N> / …_<N>_FILE |
--intelligence-token <T> / --intelligence-token-file <PATH> |
| Model / swap policy | AGENT_MODEL / AGENT_MODEL_SWAP |
--model <NAME> / --model-swap finish-on-old│restart-turn |
| MCP server | — | --mcp name=<endpoint> (repeatable; remote Streamable HTTP) |
| Serve self-MCP | AGENT_SERVE_MCP |
--serve-mcp https://host:port + --serve-cert/--serve-key/--serve-client-ca or --serve-bearer (serve-https feat.) |
| A2A peer | AGENT_A2A_PEER |
--a2a-peer name=https://endpoint (repeatable; a2a feat.) |
| Run-graph | AGENT_WORKFLOW |
--workflow <FILE> with --mode workflow (workflow feat.) |
| Mode | AGENT_MODE |
--mode once│loop│reactive│schedule│workflow |
| Subscriptions | — | --subscribe <uri> / --continue <uri> (repeatable; reactive) |
| Interval / cron | AGENT_CRON |
--interval <dur> / --cron <5-field> (cron feat.) |
| Sharding | AGENT_SHARD, AGENT_SHARD_TIMER |
--shard K/N (cluster feat.) |
| Work-claim | AGENT_CLAIM_TTL, AGENT_CLAIM_RENEW_FRACTION |
--claim <uri>=<srv>[:tool], --claim-ttl, --claim-renew-fraction (cluster feat.) |
| Standby | AGENT_STANDBY, AGENT_ASSIGN_FROM, AGENT_WARM_INTEL |
--standby, --assign-from <srv>:<uri> (cluster feat.) |
| Max steps | AGENT_MAX_STEPS |
--max-steps <N> (default 50) |
| Max tokens | AGENT_MAX_TOKENS |
--max-tokens <N> (default 200000) |
| Deadline | AGENT_DEADLINE |
--deadline <dur> (default 600s) |
| Max depth | — | --max-depth <N> (default 4) |
| Run ID | AGENT_RUN_ID |
--run-id <ID> (idempotency key) |
| Log level | AGENT_LOG_LEVEL |
--log-level trace│debug│info│warn│error |
| Log content | AGENT_LOG_CONTENT |
--log-content |
| Drain timeout | AGENT_DRAIN_TIMEOUT |
--drain-timeout <dur> (default 25s) |
| Health file | — | --health-file <PATH> |
| Metrics/probes | AGENT_METRICS_ADDR |
--metrics-addr host:port (metrics feat.) |
| Per-run cgroup | AGENT_CGROUP / …_MEMORY_MAX / …_PIDS_MAX |
--cgroup auto│PATH, --cgroup-memory-max, --cgroup-pids-max |
| Report / events | AGENT_REPORT_FILE, AGENT_EVENTS_RING |
--report-file <PATH>, --events-ring <N> |
| Hot reload | AGENT_WATCH_CONFIG |
--watch-config (config-watch feat.) + SIGHUP (hot-reload feat.) |
Durations accept ms/s/m/h or a bare integer (seconds): 600s, 5m,
2h, 250ms, 30. Each intelligence list element must be https://host/… (or a
loopback http:// for a same-host dev gateway); the same https-only rule holds
for --mcp, --serve-mcp, and --a2a-peer. Config is validated before any side
effect — a typo'd flag, a feature-gated flag in a build without its feature, or an
unresolvable secret reference exits 2 in milliseconds, not after an LLM round-trip.
Scope markers. Reactivity rides the MCP servers' Streamable-HTTP subscriptions; self-MCP serving is over HTTP(S) with mTLS/bearer auth (loopback
http://for dev); MCP tasks/sampling/roots are deferred (RFC 0013). Theclusterwork-claim:resourcestyle is a stub (:toolis the working style), andAGENT_WARM_INTELis forward-compat only — seeconfiguration.md§13. Items below are tagged where they do not ship.
The default mode (--mode once, the default). Run an instruction to a terminal
status, emit the result on stdout, write telemetry to stderr, exit with
a code from the exit-code table.
agentd \
--instruction "Summarise today's open incidents and post a digest." \
--intelligence https://gw.example/v1 \
--model my-model \
--mcp incidents=https://mcp-incidents.internal/mcp \
--mcp slack=https://mcp-slack.internal/mcp \
--deadline 5m \
--max-steps 40stdout carries the agent's final result; stderr carries one NDJSON event per
line. The canonical fields are
ts level event run_id agent_id agent_path comp pid … (RFC 0010):
{"ts":"2026-06-25T18:30:01.412Z","level":"info","event":"proc.start","run_id":"0197f3c4a01abcd","agent_id":"sup","agent_path":"0","comp":"supervisor","pid":4711,"version":"1.0.0","mode":"once","mcp_servers":2,"subscribe":0}Because stdout is the result and stderr is telemetry, you compose with ordinary shell tooling:
agentd --instruction "$(cat task.md)" --intelligence https://gw.example/v1 \
2> >(jq -c 'select(.level=="error")') \
| tee result.txtRead the instruction from a file (handy for ConfigMap/Secret projection) with
--instruction-file, or set INSTRUCTION in the environment. The intelligence
token is never logged — pass it via AGENT_INTELLIGENCE_TOKEN or
--intelligence-token, not on a shared command line where it lands in ps.
Idempotent retries. A bare run mints a random run_id per process. For a
unit of work that a scheduler may retry, pin a stable key so backing MCP
services can dedupe the side effect (RFC 0011 §6):
agentd --run-id "nightly-digest-2026-06-25" \
--instruction "$(cat task.md)" --intelligence https://gw.example/v1 --mcp …The key rides in the _meta of every outbound MCP tools/call; a backing
service that honours idempotency keys collapses a retried effect to one. agentd
itself writes nothing durable except its log streams, so a re-run is safe by
construction.
--mode reactive idles cheaply and wakes on MCP resource subscription
updates (RFC 0008). It exits only on a signal or a fatal class — never on an
individual reaction failing.
agentd \
--mode reactive \
--instruction "When a ticket is filed, triage it and assign an owner." \
--intelligence https://gw.example/v1 \
--model my-model \
--mcp tickets=https://mcp-tickets.internal/mcp \
--subscribe "tickets://queue/inbound" \
--drain-timeout 25s \
--health-file /run/agent/health--mode reactive requires at least one --subscribe <uri> (validated;
omitting it exits 2). On restart the daemon re-reads config, re-handshakes
MCP, re-subscribes every declared subscription, and does a mandatory
read-after-subscribe so a change that happened while it was down is still acted
on (RFC 0003 §3.11). No persistence layer — a restart is a cold start that
reconciles.
Reactivity rides the MCP servers' Streamable-HTTP subscriptions: the client subscribes and processes pushed
notifications/resources/updatedover HTTP/SSE.
On SIGTERM/SIGINT the daemon flips a one-way DRAINING latch and runs a
bounded drain (RFC 0011 §4, ladder in RFC 0003 §3.5):
- Disarm triggers — stop routing new resource updates; reject new
subagent.spawn; flip readiness to not-ready. - Wind down in-flight subagents at turn boundaries (cooperative cancel).
- Ladder the stragglers —
SIGTERM→ ~5s grace →SIGKILL→ reap. - Flush logs and
exit(0).
A clean drain exits 0, not 143 — a rolled Deployment looks like a
clean shutdown in dashboards, not a failure. A second SIGTERM/SIGINT
forces immediate SIGKILL of all process groups.
The whole drain is bounded by AGENT_DRAIN_TIMEOUT (default 25s). This MUST
be smaller than the orchestrator's shutdown grace — see the
footgun below.
# /etc/systemd/system/agent-triage.service
[Unit]
Description=agent ticket triage (reactive)
After=network.target
[Service]
Environment=AGENT_INTELLIGENCE=https://gw.example/v1
Environment=AGENT_INTELLIGENCE_TOKEN=
EnvironmentFile=/etc/agentd/triage.env
ExecStart=/usr/local/bin/agentd \
--mode reactive \
--instruction-file /etc/agentd/triage.txt \
--mcp tickets=https://mcp-tickets.internal/mcp \
--subscribe tickets://queue/inbound \
--drain-timeout 25s
# Give the drain room: must exceed AGENT_DRAIN_TIMEOUT.
TimeoutStopSec=30
KillSignal=SIGTERM
Restart=on-failure
[Install]
WantedBy=multi-user.targetTimeoutStopSec is systemd's analogue of terminationGracePeriodSeconds: keep
it larger than --drain-timeout.
agentd is std + libc, statically linkable, with no async runtime, no C
toolchain, and no built-in tools — so the image is tiny (~1.3 MB on
scratch). The recommended entrypoint is agentd itself: it sets
PR_SET_CHILD_SUBREAPER and reaps orphans, acting as a tini-class init for its
own process tree (RFC 0003 §3.1). You do not need an external tini.
The published image (Dockerfile at the repo root) ships the dependency-free
cloud-native feature set by default —
FEATURES="metrics,serve-mcp,cron,otel,cluster,hot-reload,config-watch". Every
one is hand-rolled and adds no dependency (serde/serde_json + libc only, 3
deps; no async runtime, no TLS, no C toolchain), so the binary stays the
minimalism target. What each adds:
| Feature | Adds |
|---|---|
metrics |
The /metrics + /healthz + /readyz HTTP probe surface (--metrics-addr) — so k8s liveness/readiness probes work against a shell-less scratch image. |
serve-mcp |
agentd serving its own MCP (--serve-mcp) so other agents compose with it; also the substrate for events/a2a/the capacity surface. |
cron |
UTC 5-field cron scheduling for --mode schedule (--cron). |
otel |
OTLP-over-HTTP/JSON span export + GenAI semconv (hand-rolled, no protobuf/opentelemetry deps). |
cluster |
Horizontal scaling: --shard K/N partitioning, work-claim leases (--claim), standby pools (--standby/--assign-from), the autoscaling signal set, and the agent://capacity read surface. |
hot-reload |
SIGHUP-triggered, validate-first reload of the reloadable config subset at a reactive quiesce boundary. |
config-watch |
The inotify file-watch reload trigger (--watch-config) — a ConfigMap volume swap reloads in place. Implies hot-reload. |
Build a narrower (or wider) surface with --build-arg FEATURES=…. tls is in the
default set (it is the transport — every network surface is HTTPS); the served
self-MCP is serve-https, and events/a2a ride it. --no-default-features drops
TLS for the loopback-http://-to-a-sidecar posture.
# syntax=docker/dockerfile:1
# Static musl binary on scratch — the dependency-free cloud-native feature set.
FROM rust:1-alpine AS build
ARG FEATURES="serve-https,metrics,cron,otel,cluster,hot-reload,config-watch,workflow"
RUN apk add --no-cache musl-dev
WORKDIR /src
COPY . .
# Alpine's host target IS <arch>-unknown-linux-musl, so the release binary is
# static; one Dockerfile yields native-static amd64 AND arm64 via buildx.
RUN if [ -n "$FEATURES" ]; then \
cargo build --release --locked -p agentd-cli --features "$FEATURES"; \
else \
cargo build --release --locked -p agentd-cli; \
fi
# scratch: nothing but the binary. (Swap for gcr.io/distroless/static if you
# want a CA bundle + /etc/passwd without managing them yourself.)
FROM scratch
COPY --from=build /src/target/release/agentd /agent
# Non-root by uid (scratch has no /etc/passwd; the kernel uses the number).
USER 65532:65532
# MCP servers are remote HTTP endpoints (--mcp name=https://…), deployed as their
# own services — nothing MCP-related is bundled into the agentd image.
ENTRYPOINT ["/agentd"]Build-arg, not flag.
FEATURESselects what the binary can do; it is a compile-time choice, not a runtime flag. A runtime flag that needs an unbuilt feature exits2— e.g.--shard 2/8on an image built withoutcluster.
The default build links tls (rustls + bundled roots), so agentd dials https://
directly. Two postures:
- Direct HTTPS (default):
--intelligence https://…(and--mcp name=https://…) reach real endpoints over TLS; agentd holds the trust roots. - Sidecar TLS termination: build
--no-default-featuresand point agentd at a same-host sidecar over loopback —--intelligence http://127.0.0.1:PORT/…— which terminates TLS + provider auth. A non-loopbackhttp://is rejected.
# Direct HTTPS (default build):
agentd --intelligence https://gw.example/v1 --instruction-file /etc/task.txt \
--mcp fs=https://mcp-fs.internal/mcpThis keeps the default image at scratch-size with no certificate management in the agentd process.
Two options, both live:
--metrics-addr host:port(metricsfeature, in the default image) serves/healthz+/readyz+/metricsover HTTP. This is the right choice for the scratch image, which has no shell to run an exec probe: point the k8s liveness probe at/healthzand readiness at/readyz. The bare:portform binds all IPv4 interfaces so the kubelet reaches it at the pod IP. (See the K8s probes below.)--health-file <PATH>— agentd heartbeats it while the reactor is live, so an exec-style probe cantestits freshness. Useful where you do not want an HTTP listener at all.
/healthz returns 200 while the supervisor tick is fresh and 503 once it
goes stale; /readyz flips to not-ready on drain so the pod leaves rotation. An
idle reactive agentd is healthy — liveness tracks the supervisor, not whether work
is flowing.
Self-MCP scope.
--serve-mcplets other agents compose with this one over HTTP(S) (serve-httpsfeature), with trust minted per request by mTLS or a bearer token — never by the transport; a non-loopback bind must authenticate. The same listener carries the management transport and (with--features a2a) the A2A method surface — see the management transport below.
The orchestrator (a K8s operator, Knative, Nomad, a bare-metal supervisor) is
not part of this project (RFC 0011 §1). agentd just honours a contract:
config from env/flags, signal-driven drain, and a public exit-code table a
podFailurePolicy can branch on. Below are the three shapes; runnable manifests
live in examples/.
This table is a stable, machine-actionable API — author podFailurePolicy
against it (RFC 0011 §5; constants in
crates/agentd/src/exit.rs):
| Code | Meaning | Scheduler hint |
|---|---|---|
0 |
success — one-shot done / clean bound / clean SIGTERM drain | Complete |
1 |
generic / unspecified failure | retriable |
2 |
config / usage error (validation failed) | non-retriable → FailJob |
3 |
partial result (useful output, some sub-tasks failed) | policy |
4 |
intelligence endpoint unreachable / auth after retries | retriable |
5 |
agentd ran correctly but the task cannot be done / refused | non-retriable |
6 |
a required MCP server failed to connect / handshake / died | retriable |
7 |
budget exceeded (steps / tokens / deadline / tree) | policy |
124 |
supervisor hard-kill backstop (a child that won't self-terminate; a self-detected --deadline is 7) |
— |
137 |
killed by SIGKILL (OOM / kubelet) — OS-set |
raise memory limit |
143 |
killed by SIGTERM without clean drain — OS-set |
distinguishes ungraceful from 0 |
agentd never exit(137)/exit(143) itself — the kernel sets those when it
kills the process. A clean drain returns 0.
AGENT_DRAIN_TIMEOUT(default 25s) MUST be<terminationGracePeriodSeconds(default 30s).
If your drain budget is >= the pod's grace period, the kubelet sends
SIGKILL before agentd finishes draining — you lose the clean exit (it
becomes 137/143), in-flight subagents are not wound down at turn boundaries,
and a rolled Deployment shows failures instead of clean 0s. Always keep the
internal budget the smaller number, with headroom for the kill-ladder rung
plus the log flush.
agentd validates this where it can: a drain_timeout >= 30s (the K8s default
grace) emits a loud warning at startup (RFC 0011 §3.3). Set both explicitly and
keep the gap:
spec:
terminationGracePeriodSeconds: 30 # kubelet grace
containers:
- name: agent
args: ["--drain-timeout", "25s", …] # < 30s, with headroom--mode once; the Job runs to a terminal status and exits. Use
podFailurePolicy to turn the exit-code table into retry decisions:
apiVersion: batch/v1
kind: Job
metadata:
name: agent-digest
spec:
backoffLimit: 3
podFailurePolicy:
rules:
# Config / usage error and a deterministic refusal are operator bugs —
# never retry them.
- action: FailJob
onExitCodes: { operator: In, values: [2, 5] }
template:
spec:
restartPolicy: Never
terminationGracePeriodSeconds: 30
containers:
- name: agent
image: ghcr.io/example/agent:1.0.0
args:
- --mode=once
- --instruction-file=/etc/agentd/task.txt
- --intelligence=https://gw.example/v1
- --drain-timeout=25s
env:
- { name: AGENT_INTELLIGENCE_TOKEN, valueFrom: { secretKeyRef: { name: intel, key: token } } }
- { name: AGENT_RUN_ID, value: "digest-2026-06-25" } # stable → idempotent retriesPin AGENT_RUN_ID to a stable per-unit-of-work value (e.g. derived from the
Job name) so retries dedupe through your MCP backing services.
Prefer an external CronJob firing --mode once per tick over the internal
--mode schedule/--interval — it is more robust, observable, and 12-factor
(RFC 0011 §9). agentd's internal scheduler is a standalone convenience, not a
calendar (no DST/missed-tick catch-up; UTC).
apiVersion: batch/v1
kind: CronJob
metadata:
name: agent-nightly
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: Never
terminationGracePeriodSeconds: 30
containers:
- name: agent
image: ghcr.io/example/agent:1.0.0
args:
- --mode=once
- --instruction-file=/etc/agentd/nightly.txt
- --intelligence=https://gw.example/v1
- --drain-timeout=25s--mode reactive. A long-lived Pod that idles on subscriptions and survives
rolls cleanly because a clean drain exits 0.
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-triage
spec:
replicas: 1
selector: { matchLabels: { app: agent-triage } }
template:
metadata: { labels: { app: agent-triage } }
spec:
terminationGracePeriodSeconds: 30 # > --drain-timeout
containers:
- name: agent
image: ghcr.io/example/agent:1.0.0
args:
- --mode=reactive
- --instruction-file=/etc/agentd/triage.txt
- --intelligence=https://gw.example/v1
- --subscribe=tickets://queue/inbound
- --drain-timeout=25s
- --health-file=/run/agent/health
livenessProbe:
# The reactor heartbeats the health file; a wedged reactor goes stale.
exec: { command: ["/bin/sh", "-c", "test $(( $(date +%s) - $(stat -c %Y /run/agent/health) )) -lt 30"] }
periodSeconds: 10
# If built/served with the HTTP health surface (RFC 0010), use instead:
# httpGet: { path: /healthz, port: 8080 }
resources:
limits: { memory: "512Mi" } # 137 on OOM → raise thisNote the liveness probe targets the supervisor reactor, not the agentic
work — a subagent legitimately busy on a long tool call must not flip pod
liveness (RFC 0003 §3.4, RFC 0010). Set resources.limits.memory deliberately:
aggregate subtree memory is a cgroup/pod concern, not enforced in-binary, so an
OOM surfaces as 137 and means "raise the limit" (RFC 0003 §3.8).
To process a shared workload across N replicas without duplicating it, run a
sharded fleet (RFC 0019; needs an image built with --features cluster). The
idiom maps a StatefulSet pod ordinal → AGENT_SHARD=K/N: each pod owns shard
K of N, and replicas is N. The ordinal is in the pod's hostname
(agent-shard-0, -1, …), so the container derives K from it; agentctl
injects AGENT_SHARD the same way. Shard identity is restart-only — a
reload never changes it (a re-shard is a rolling restart).
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: agent-shard
spec:
serviceName: agent-shard
replicas: 8 # == N (the shard count)
selector: { matchLabels: { app: agent-shard } }
template:
metadata: { labels: { app: agent-shard } }
spec:
terminationGracePeriodSeconds: 30 # > --drain-timeout
containers:
- name: agent
image: ghcr.io/agentd-dev/agentd:1.0.0 # built with `cluster`
# Derive K from the StatefulSet ordinal (the trailing -N of the hostname)
# and export AGENT_SHARD=K/8 before exec'ing agent.
command: ["/bin/sh", "-c"] # use a shell image, or bake this into an entrypoint
args:
- |
K="${HOSTNAME##*-}"
exec /agentd --mode reactive \
--instruction-file /etc/agentd/task.txt \
--intelligence https://gw.example/v1 \
--subscribe tickets://queue/inbound \
--metrics-addr :9090 --drain-timeout 25s
env:
- name: AGENT_SHARD
value: "0/8" # overwritten below from the ordinal…
# …or, on the scratch image (no shell), set AGENT_SHARD directly per
# pod via a small admission/operator that reads the ordinal. The shard
# gate is the only sharding wiring agentd needs.Because shard ownership is a pure FNV-1a gate over the resource URI/key, the
replicas are share-nothing: each independently subscribes to the full set and
processes only the items hashing to its shard. For at-most-once processing of
an item that more than one replica could see, layer work-claim on top
(--claim <uri>=<coord-server>, configuration.md §13) so a
lease — not just the hash — decides ownership. AGENT_SHARD_TIMER (shard0 |
keyed) controls timer firing for a sharded schedule/loop fleet.
A reactive daemon can apply a new structural config without a restart
(RFC 0017 §5; see configuration.md §11 for the
reloadable-vs-restart-only partition). Mount the config file from a ConfigMap and
either send SIGHUP or run --watch-config:
--watch-config(config-watchfeature) arms aninotifywatch on the config file's directory. Akubectl applyof the ConfigMap is an atomic volume-symlink swap, which the watch sees — agentd re-reads, validates, and applies the reloadable subset (model, the intelligence endpoint list, limits,subscribe,log_level,mcp_serversre-handshake) in place. An invalid candidate keeps the running config — nothing is half-applied. A diff that touches a restart-only field (mode, run-id, serve-mcp, drain, shard, claim/standby, continue topology) is refused withreason="restart_required"(roll the pod).SIGHUP(hot-reloadfeature) is the portable trigger if you would rather signal than watch:kubectl exec … -- kill -HUP 1, or an operator that signals after editing the ConfigMap.
spec:
template:
spec:
containers:
- name: agent
image: ghcr.io/agentd-dev/agentd:1.0.0 # built with config-watch
args:
- --mode=reactive
- --config=/etc/agentd/config.json # mounted from the ConfigMap
- --watch-config # reload on a ConfigMap update
- --instruction-file=/etc/agentd/task.txt
- --metrics-addr=:9090
- --drain-timeout=25s
volumeMounts:
- { name: config, mountPath: /etc/agentd, readOnly: true }
volumes:
- name: config
configMap: { name: agent-config } # holds config.json (+ task.txt)Secrets never live in the ConfigMap: the file carries only structural config and
{{secret:NAME}} / {{secret-file:PATH}} references, resolved from env vars or
mounted Secret files at load/reload (configuration.md §12).
The same --serve-mcp listener that exposes the self-MCP also carries the
management transport — status, subagent introspection, the operator control
family (a2a.Drain/LameDuck/Pause/Resume/Cancel), and (with --features a2a) the A2A method surface — over HTTP(S). Trust is minted per request by
mutual TLS (--serve-cert/--serve-key/--serve-client-ca) or a bearer
token (--serve-bearer), never by the transport; a non-loopback bind that
configures neither is a startup error (no open control plane). The controller (the
thing that issues management RPCs, signals reloads, and reads agent:// resources —
e.g. agentctl) is external and not part of agentd; it presents a client cert
or bearer and agentd honours the authenticated-identity contract.
See examples/k8s/ for the manifests above as standalone
files:
examples/k8s/job-once.yaml— one-shotJobwithpodFailurePolicyexamples/k8s/cronjob-schedule.yaml— scheduledCronJobexamples/k8s/deployment-reactive.yaml— reactiveDeploymentwith HTTP probesexamples/docker/Dockerfile— the static-on-scratch imageexamples/systemd-agentd.service— reactive systemd unit
docs/configuration.md: the complete flag/env reference, the config-file schema (§12), the reloadable-vs-restart-only partition (§11), and theclustersharding/claim/standby surface (§13).- RFC 0011 — cloud-native contract: config precedence, signals, the exit-code table, idempotency.
- RFC 0003 — process supervision & recovery: the kill ladder, reaping, restart governor, rebuild + reconcile.
- RFC 0008 — modes & reactive routing: the exit predicates per mode, read-after-subscribe.
- RFC 0017 — declarative config & hot reload:
the config file,
--validate-config/--config-schema, SIGHUP/--watch-config. - RFC 0018 — intelligence transport resilience:
the endpoint list, per-endpoint creds,
--model-swap. - RFC 0019 — horizontal scaling: sharding, work-claim leases, standby pools.
docs/design/PLAN.md: current build status.