RFC 0015: Management & control surface — vsock serving, the operator MCP profile, the capabilities manifest
⚠ SUPERSEDED — transport (target-vision pivot, 2026-07-02). The unix/vsock transport this RFC specifies is replaced by HTTPS (mTLS/bearer auth; loopback
http://for dev); operator control is unified into thea2a.*method family. The contracts (methods/resources/semantics) still hold — only the transport does not. See../docs/design/00-target-vision-pivot.md.
Status: Proposed (agentctl control-plane track) Author: Andrii Tsok Date: 2026-06-27 Part of: the agentd rewrite — control-plane track (RFC 0014); extends the self-MCP server & control protocol (RFC 0005)
A2A alignment (RFC 0020). RFC 0020 (A2A-over-vsock) builds two surfaces here into the agent mesh, with no new source of truth: (1) the capabilities manifest (§5.2) is the A2A Agent Card — the on-node gateway projects the manifest into A2A's
agent.jsonschema; one builder, one document (RFC 0020 §3/§5). (2) the vsock serving transport (§3) also carries the A2A server profile (featurea2a) alongside this management/self-MCP profile — same listener, same codec (RFC 0004), same one trust domain (§3.3) where the vsock peer is the node-agent (RFC 0020 §2/§4). agentd carries none of A2A's HTTP/SSE/OAuth/webhook machinery; that lives in the gateway. See RFC 0020.
1. Problem / Context
RFC 0014 splits the world: agentd is the data plane (one static binary that
runs the bounded agentic loop), agentctl is the control plane (a CLI, a
kubectl agent[s] plugin, and a Kubernetes operator that provisions, scales, and
observes a fleet). The umbrella's load-bearing move is to make vsock
bidirectional: agentd already dials out over vsock for intelligence (RFC
0006); if it can also serve its self-MCP over vsock, an agentctl node-agent
DaemonSet manages every local agentd pod host-side — control and telemetry —
and the pod can run with no cluster networking at all (vsock-out for the
model, vsock-in for management). That is the strongest isolation posture agentd
can offer and the natural backend for kubectl agent.
This RFC lands the first sub-RFC of that track — the one the rest depend on (RFC 0014 §7: "RFC 0015 first — it unlocks the transport + manifest the rest depend on"). It specifies three things and nothing more:
- A new self-MCP transport:
--serve-mcp vsock:PORT. The exact same NDJSON self-MCP server (RFC 0005), now reachable over a vsock listener, so the host-side node-agent is the management peer. - The operator profile of the self-MCP — a superset of the existing work
profile (RFC 0005 §3.2/§3.3): a few new
agent://resources and a small set of operator-facing tools (drain,lame-duck,pause/resume, instancecancel), all served over the same transports, with no new protocol. - The capabilities manifest — the shared spine RFC 0014 §5 sketched and
explicitly delegated here ("the manifest's exact schema is owned by RFC 0015 §
capabilities"). One machine-readable document, emitted as a one-shot
(
agentd --capabilities) and as a live resource (agent://capabilities), that tells agentctl what an instance is, what it can do, and which contract versions it speaks.
The discipline is RFC 0014 §3 verbatim: agentd exposes primitives; agentctl
owns policy. Everything here is a primitive (a resource to read, a tool to
call, a transport to bind). The Kubernetes-facing translation — CRDs, the
reconcile loop, kubectl agent tree's rendering, RBAC, dashboards — lives
entirely in agentctl and is a non-goal of this RFC (§9). And the minimalism
moat holds: every surface here is feature-gated and dependency-free. The
default build stays serde + serde_json + libc; the only new code is a vsock
listener (already behind the vsock feature for the client, RFC 0006) and a
handful of resources/tools that are pure supervisor-state reads. A surface that
would pull an async runtime, a Kubernetes client, or a TLS/gRPC stack is wrong by
construction (RFC 0014 §3.3) and belongs in agentctl.
2. Decision
-
--serve-mcp vsock:PORTserves the existing self-MCP over vsock, in addition to today'sstdio(always) andunix:PATH(RFC 0005 §3.6). It is a blocking, thread-per-connection listener — the unix-server design lifted verbatim, only the socket type changes — gated by theserve-mcpANDvsockfeatures (vsockalready exists for the intelligence client). The vsock peer is the node-agent: one trust domain, exactly as the unix socket trusts whoever holds filesystem permission to it (RFC 0012 §3.8). No auth, no session model — the transport is the boundary (§7). -
The operator profile is a superset of the work profile, gated by reachability, not a flag. The self-MCP served over
vsock/unixexposes, in addition to the RFC 0005 work surface, the resourcesagent://capabilities,agent://inventory, andagent://events(the last owned by RFC 0016), and the toolsdrain,lame-duck,pause,resume, and an instance-levelcancel.attachis not a new tool — it issubagent.sendinto a warm session (RFC 0005); agentctl labels itkubectl agent attach. These operator tools are listed only on the management transports (vsock/unix), never on stdio served into a parent agent (a peer subagent must not drain its supervisor); §3.4. -
The capabilities manifest is the contract spine.
agentd --capabilitiesprints the manifest JSON to stdout and exits0(one-shot, side-effect free, before any MCP connect);agent://capabilitiesreturns the same document as a live, subscribable resource. Its schema (§5) carriescontract_version,agent_version,build_features,identity,mode,model, anintelligencesummary,mcp_servers+ trifecta tags,limits, and asurfacesblock declaring which control-plane contracts this binary actually serves — so agentctl degrades gracefully (RFC 0014 §7). -
Instance identity comes from the Kubernetes downward API, env-only. agentd reads pod name / uid / node / namespace from operator-injected env vars (
valueFrom.fieldRef) and surfaces them incapabilities.identityandagent://status. agentd never calls the kube API — no client, no in-cluster config, no service-account token read. Env in, nothing else (§6). -
Capability absence is not an error. An operator tool or resource that a binary was not built with (or whose surface is off) is simply not listed — never an error, never a stub (the RFC 0005 §3.2 / RFC 0012 §3.6 "absent, not present-but-erroring" rule). agentctl discovers presence from the manifest's
surfacesblock and fromtools/list/resources/list, and drives only what it sees (§8). -
Freeze and version what agentctl couples to. The manifest schema, the operator tool names, and the operator resource URIs are a public API (RFC 0014 §3.4): they carry
contract_version, change additively within a major, and a breaking change bumps the major. agentctl negotiates oncontract_versionand refuses an instance whose major it does not understand.
These decisions are final for the RFC 0015 surface. Each defers to its owner where it touches another RFC (noted inline).
3. Mechanism — --serve-mcp vsock:PORT
3.1 The flag and config surface
--serve-mcp already accepts unix:PATH (RFC 0005 §3.6) and is plumbed through
Config::serve_mcp: Option<ServeAddr> (RFC 0011 §3.1). This RFC adds a third
variant. The flag stays a single value (one served address per process); the
config table row is unchanged but the value space grows:
| Concern | Env | Flag | Value space |
|---|---|---|---|
| Serve self-MCP | AGENT_SERVE_MCP | --serve-mcp | unix:PATH | vsock:PORT | vsock:CID:PORT |
// config.rs — extends RFC 0005 §3.6 ServeTarget
enum ServeTarget {
Stdio, // always, implicit
Unix(PathBuf), // --serve-mcp unix:PATH
Vsock { cid: u32, port: u32 }, // --serve-mcp vsock:[CID:]PORT (feature = "vsock")
}
vsock:PORTbinds withcid = VMADDR_CID_ANY(the guest accepts from any peer CID — in practice its host, since vsock is point-to-point guest↔host). This is the normal pod form: the node-agent connects from the host CID (VMADDR_CID_HOST = 2).vsock:CID:PORTis accepted for symmetry with the intelligence URI grammar (RFC 0006 §2:vsock:<cid>:<port>) and for test rigs that bind a specific CID; the operator normally omits the CID.
Validation (RFC 0011 §3.3, exit 2 before any side effect): --serve-mcp vsock:… on a binary built without the vsock feature is a config error
(scheme unsupported: vsock requires the 'vsock' build feature), symmetric to
RFC 0011's intelligence.scheme_supported() check for https. Port 0 or a
non-numeric port is exit 2. As today, a process serving the self-MCP on
stdio cannot also print a once-mode result on stdout (RFC 0005 §3.6); a
vsock/unix served address has no such conflict and may coexist with any mode.
3.2 The listener — blocking, thread-per-connection, identical to unix
The vsock server is the unix server (RFC 0005 §3.6) with the socket type swapped.
There is no new concurrency model, no async runtime, no new framing. It reuses
net/vsock.rs — already in the tree for the intelligence client (RFC 0006 §3,
the vsock feature) — for the listening half:
// net/vsock.rs [feature = "vsock"] — server half (mirrors net/unixsock.rs)
fn serve_vsock(cid: u32, port: u32, sup: SupervisorHandle) -> io::Result<()> {
let listener = VsockListener::bind(&VsockAddr::new(cid, port))?; // AF_VSOCK, SOCK_STREAM
for conn in listener.incoming() {
let stream = conn?; // VsockStream: Read + Write, TcpStream-shaped
let sup = sup.clone();
thread::spawn(move || {
// SAME PeerConn as the unix path: NDJSON framing (read_line/write_line, RFC 0005),
// one reader thread, tagged events onto the supervisor's merged mpsc (RFC 0002).
serve_peer(FramedNdjson::new(stream), PeerOrigin::Management, sup);
});
}
Ok(())
}
Concretely:
- Same framing. NDJSON (
read_line/write_line, RFC 0005 §3.6) — one compact JSON object per line,\n-terminated, UTF-8. vsock is a reliable byte stream (SOCK_STREAM), so the unix-socket framing applies unchanged. The MCP wire and codec are RFC 0004's; this RFC adds no protocol bytes. - Same threading. One reader thread per accepted connection forwards tagged
events onto the supervisor's merged
mpsc(the thread-per-fd model, RFC 0002). For the high-fan-in case the RFC 0005 §3.6 escape hatch (mio/libc::pollbehindserve-mcp) applies identically; in practice the node-agent holds one long-lived management connection per pod, so fan-in is trivial. - Same
PeerConn/PeerCaps. A vsock peerinitializes, negotiates capabilities, and gets the sameagentserverInfo(RFC 0005 §3.1). The only difference from a unix peer isPeerOrigin::Management(set for vsock and unix;PeerOrigin::Stdiofor the stdio path) — the tag that gates the operator tools (§3.4). - No new dependency.
vsock::VsockListenerlives in the samevsockcrate RFC 0006 already vendors for the client; the default build and the cloud-native image set (novsock) are byte-for-byte unchanged. This is the minimalism moat (RFC 0014 §3.3): the vsock-serving image is a feature-gated build, not the default.
3.3 Trust domain — the vsock peer IS the node-agent
vsock is point-to-point between a guest VM and its host; there is no routing, no
multi-tenant fabric. The peer that connects to the guest's vsock:PORT is the
host — i.e. the agentctl node-agent DaemonSet running on that node (RFC 0014
§2). This is one trust domain, structurally identical to the unix-socket
model (RFC 0012 §3.8): the unix socket trusts whoever holds filesystem permission;
the vsock port trusts whoever owns the host side of the VM boundary. In both cases
the transport is the access control — there is no in-band auth, no token, no
session-is-authn confusion (RFC 0012 §2 decision: no auth as core).
Who provisions the device is not agentd's concern (RFC 0014 §6): the node-agent sets up the vsock CID/port and the host-side model service; agentd only uses the CID/port it is given (RFC 0006 makes the same statement for the client). agentd binds, accepts, and serves. The operator's job is to ensure only the trusted node-agent can reach that vsock port — exactly the posture a unix socket's mode/owner gives on the filesystem.
Cluster-network serving (TCP / Streamable HTTP, RFC 0013) remains a deferred
alternative transport for the same surface, with the same operator tools — not a
precondition, and explicitly out of this RFC because it needs the auth/hardening
story RFC 0012 §3.8 defers. vsock needs none of that hardening precisely
because it is not a network — there is no Origin to validate, no DNS to
rebind, no bearer token to pass through.
3.4 The operator profile is gated by PeerOrigin, not a global flag
The self-MCP tools/list is already per-caller gated (RFC 0005 §3.2: a
caller's scope narrows the available set; tools/list_changed fires on change).
This RFC reuses that exact mechanism. The operator tools (§4) and the operator
resources (§5) are listed iff the connecting peer's PeerOrigin is
Management (vsock or unix) — i.e. iff the peer is reaching agentd over a
management transport:
| Peer origin | Transport | Work tools (RFC 0005) | Operator tools (§4) | Operator resources (§5) |
|---|---|---|---|---|
Stdio | stdin/stdout (spawned by a parent agent / harness) | yes | no | capabilities only |
Management | unix:PATH / vsock:PORT | yes | yes | yes |
This is the critical containment: a peer subagent spawned over stdio (RFC 0005
§3.6) must never be able to drain or lame-duck its own supervisor. The
operator tools are a management capability, scoped to the management transport,
which is itself scoped to the node-agent trust domain (§3.3). A child agent that
holds a subagent.spawn-style relationship to a parent reaches the parent over
stdio and sees only the work surface. The gate is structural (peer origin), not a
runtime check inside each tool.
agent://capabilities is the one operator-profile resource also visible on
stdio — it is pure read-only self-description and is harmless to expose to a
parent (indeed a parent agent wants to read a child's manifest before spawning).
Everything else operator-facing requires PeerOrigin::Management.
4. Mechanism — the operator tools
All operator tools are normal MCP self-tools (RFC 0005 §3.2): JSON-Schema
inputSchema, called via tools/call, returning a CallToolResult whose
structuredContent carries the machine-readable answer. A refusal (already
draining, unknown handle) is isError:true inside a successful result so the
caller adapts (RFC 0005 §3.2 distinction); a malformed call (unknown tool, bad
params) is a JSON-RPC error (-32601/-32602). None of these tools invent new
lifecycle machinery — they pull the same levers SIGTERM and the control
protocol already own.
| Tool | Purpose | Underlying machinery |
|---|---|---|
drain | trigger graceful drain; return in-flight count + ETA | identical to SIGTERM → the RFC 0011 §4.2 drain state machine |
lame-duck | flip /readyz to NotReady without exiting | readiness flag only (RFC 0010 §3.7); no drain, no exit |
pause / resume | suspend / resume the tree at turn boundaries | fan-out ctrl/pause / ctrl/resume (RFC 0005 §4.3) |
cancel | cancel a run / subtree by handle | wraps subagent.cancel (RFC 0005 §3.2) at instance scope |
(attach) | interactive steering of a warm session | is subagent.send (RFC 0005) — not a new tool |
4.1 drain — the same machinery as SIGTERM
drain enters the exact drain choreography SIGTERM/SIGINT trigger (RFC 0011
§4.2): flip the one-way DRAINING latch → disarm triggers → flip readiness to
NotReady (RFC 0010 §3.7) → reject new subagent.spawn with -32000 "shutting down" → wind down in-flight subagents at turn boundaries → ladder stragglers →
flush → exit 0 (a clean drain returns 0, not 143 — RFC 0011 §5). It is the
programmatic equivalent of the kubelet sending SIGTERM, so agentctl can drain
a pod over the management transport without a kill, and the pod still exits a
clean 0 for dashboards (RFC 0011 §5 / RFC 0016).
It returns immediately (does not block until exit) with a snapshot:
{ "name":"drain",
"inputSchema":{ "type":"object",
"properties":{
"deadline_ms":{"type":"integer"} // optional override; clamped to <= AGENT_DRAIN_TIMEOUT
},
"additionalProperties":false }}
// CallToolResult.structuredContent
{ "draining": true,
"in_flight": 3, // root subagents still winding down
"eta_ms": 18400, // min(remaining drain budget, observed wind-down)
"drain_timeout_ms": 25000, // the bound (RFC 0011 §4.2)
"started_at":"2026-06-27T10:00:00.123Z" }
drain is idempotent and monotonic: the latch is one-way (RFC 0011 §4.2), so a
second drain (or a SIGTERM arriving after a drain tool call) just returns the
current snapshot — it does not map to the second-signal FORCE path. Force
remains the second signal (RFC 0011 §4.3); there is deliberately no force tool
in v1 (agentctl that wants force sends a second SIGTERM, or lets the pod grace
expire — the kubelet SIGKILL). A drain call after drain has begun is a no-op
that re-reports.
4.2 lame-duck — NotReady without exiting (the rolling-update primitive)
lame-duck flips readiness to NotReady (RFC 0010 §3.7 — proc.ready → not-ready,
/readyz → 503, agent_ready gauge → 0) without entering drain and
without exiting. The supervisor keeps running, keeps serving in-flight work,
keeps liveness green — it just advertises "don't send me new work." This is the
node-drain / rolling-update primitive: agentctl flips a pod lame-duck, waits
for in-flight work to bleed off (watching agent://inventory or
agent_active_subagents), then drains or deletes it. Unlike drain, it is
reversible:
{ "name":"lame-duck",
"inputSchema":{ "type":"object",
"properties":{ "ready":{"type":"boolean","default":false} }, // false => NotReady; true => back to Ready
"additionalProperties":false }}
// structuredContent
{ "ready": false, "since":"2026-06-27T10:00:00.123Z", "in_flight": 3 }
lame-duck{ready:false} is the only sanctioned way to make a reactive daemon
NotReady while alive. It does not disarm subscriptions or unsubscribe — a
reactive daemon keeps reconciling, it just signals the orchestrator's readiness
gate. (Whether agentctl also stops routing reactive events is its policy: it can
pause subscriptions with pause, §4.3.) lame-duck{ready:true} restores
readiness iff the underlying readiness conditions still hold (MCP connected, subs
reconciled — RFC 0010 §3.7); if they do not, the call returns isError:true with
the unmet condition, and readiness stays as the supervisor computes it. Readiness
is a computed state with a lame-duck override toward NotReady; the tool can
never assert Ready over a genuinely-not-ready supervisor.
4.3 pause / resume — tree-wide turn-boundary suspension
pause fans ctrl/pause (RFC 0005 §4.3) to every in-flight root subagent; each
loop suspends at its next turn boundary (RFC 0007). resume fans ctrl/resume.
This is the existing per-child control message lifted to an instance-wide
operation — useful for live debugging (kubectl agent pause to inspect state
without teardown) and for holding a tree while the host model service is swapped
(RFC 0018). Paused is not drain and not lame-duck: the tree is frozen but
intact, readiness is unchanged unless the operator also lame-ducks.
// pause / resume — no params
{ "name":"pause", "inputSchema":{ "type":"object","additionalProperties":false } }
{ "name":"resume","inputSchema":{ "type":"object","additionalProperties":false } }
// structuredContent (both)
{ "paused": true, "affected": 3 } // count of subtrees that took the message
Pause is reported in agent://inventory (§5.3, per-node paused:true) and via the
agent_paused gauge (§5.5). A paused instance still answers ping, still serves
the management transport, still bumps the liveness heartbeat (RFC 0010 §3.7 — the
supervisor reactor is not paused, only the agentic loops are), so pausing does not
trip liveness.
4.4 cancel — instance-level cancel by handle (wraps subagent.cancel)
cancel is the management-transport, instance-scoped wrapper over the existing
subagent.cancel tool (RFC 0005 §3.2): cancel a run or a subtree by handle.
"Instance-level" means the caller does not need a prior parent↔child relationship
to the target — the node-agent addresses any handle in this instance's tree by
its agent://subagent/{handle} identity. It opens the graceful rung of the kill
ladder (ctrl/cancel → grace → killpg(SIGTERM) → killpg(SIGKILL), RFC 0003);
the supervisor remains the source of truth for depth/handles.
{ "name":"cancel",
"inputSchema":{ "type":"object",
"properties":{
"handle":{"type":"string"}, // e.g. "0.2" — omit/"0" => the whole run (root subtree)
"reason":{"type":"string"} // surfaced in logs + ctrl/cancel (RFC 0005 §4.3)
},
"required":["handle"],
"additionalProperties":false }}
// structuredContent
{ "handle":"0.2", "cancelling": true, "subtree_size": 4 }
An unknown handle ⇒ isError:true ("no such handle: 0.2.9"), not a JSON-RPC
error — it is an observation, and a racing reap may have already removed it.
cancel{handle:"0"} (or omitted) cancels the root subtree, i.e. the whole run,
without exiting the supervisor — distinct from drain, which also exits. This
gives agentctl kubectl agent cancel <run> (kill the work, keep the pod for the
next reactive trigger) separately from kubectl agent drain <pod> (kill the work
and exit).
4.5 attach is subagent.send — note it, do not reinvent
Interactive steering of a warm session (kubectl agent attach) is not a new
tool. It is the existing subagent.send (RFC 0005 §3.2), which injects an
instruction/event into a warm subagent session via the ctrl/inject control
message (RFC 0005 §4.3). agentctl's attach opens a management connection, calls
subagent.send{handle, event}, and streams the resulting agent://events
(RFC 0016) and agent://subagent/{handle} updated notifications back to the
operator's terminal. agentd adds nothing: the warm-session steering primitive
already exists, and reinventing it would fork the control surface. This RFC only
names the mapping so the umbrella's kubectl agent attach row (RFC 0014 §4) has
a concrete backing.
5. Mechanism — the operator resources & the capabilities manifest
5.1 New agent:// resources (superset of RFC 0005 §3.3)
The operator profile adds three resources to the RFC 0005 §3.3 tree. All are
readable and (where it makes sense) subscribable, emitting
notifications/resources/updated{uri} on transition per the RFC 0005 §3.4 rule —
notify-then-read, URI only, no payload.
| URI | Body (on resources/read) | Subscribable | Owner |
|---|---|---|---|
agent://capabilities | the capabilities manifest (§5.2) | yes (re-read on hot reload / model swap) | this RFC |
agent://inventory | the live subagent tree (§5.3) | yes (on any node spawn/exit/transition) | this RFC |
agent://status | instance status incl. downward-API identity (§5.4) | yes | extends RFC 0005 agent://run/* |
agent://events | live structured event stream | n/a (a stream) | RFC 0016 — referenced, not defined here |
agent://events is the operator log/telemetry stream and is specified by
RFC 0016, not here; this RFC only declares that it is part of the operator
profile and is listed on the management transport. Do not read its schema into
this RFC.
5.2 agent://capabilities and agentd --capabilities — the manifest
The capabilities manifest is the shared spine of the whole control-plane track (RFC 0014 §5, which delegates the exact schema here). It is emitted two ways from the same builder, so the one-shot and the live resource never drift:
- One-shot:
agentd --capabilitiesbuilds the manifest, writes it as JSON to stdout, and exits0. It is side-effect free and runs before any MCP connect, LLM call, or socket bind (the RFC 0011 §3.3 discipline): it reflects static config + build features + downward-API env, with the live fields (intelligence.healthy, counts) reported as their pre-connect/unknown values. This is how agentctl probes a binary/image at admission time without starting a run. - Live resource:
agent://capabilitiesreturns the same document with the live fields populated, and emitsupdatedwhen anything in it changes — notably on hot reload (RFC 0017) and model/endpoint hot-swap (RFC 0018), so a subscribed agentctl re-reads the current capability set without polling.
Full schema (jsonc; every field is part of the frozen contract — §6 of RFC 0014):
{
"contract_version": "1.0", // the agentctl<->agentd contract major.minor (RFC 0014 §3.4)
"agent_version": "2.1.0", // env!("CARGO_PKG_VERSION")
"build_features": ["metrics","serve-mcp","vsock","cron","otel"], // compiled-in cargo features
"identity": { // from the k8s downward API env (§6); fields absent if unset
"run_id": "01J8Z3K2QN7…", // AGENT_RUN_ID / minted ULID (RFC 0011 §6)
"instance": "agent-pod-abc", // metadata.name via AGENT_POD_NAME
"uid": "f3c1…-…-…", // metadata.uid via AGENT_POD_UID
"node": "node-3", // spec.nodeName via AGENT_NODE_NAME
"namespace": "agents" // metadata.namespace via AGENT_POD_NAMESPACE
},
"mode": "reactive", // once | loop | reactive | schedule (RFC 0008)
"model": "claude-opus-4", // AGENT_MODEL (the configured model id)
"intelligence": { // summary only; resilience detail is RFC 0018
"transport": "vsock", // unix | https | vsock (RFC 0006)
"endpoints": 2, // configured endpoint count (RFC 0018 multi-endpoint)
"healthy": true // last-known reachability; "unknown" pre-connect (one-shot)
},
"intelligence_summary": { // coarse capability hints for placement (no secrets, RFC 0012)
"toolmode": "native", // native tool-calling vs JSON-action fallback (RFC 0006)
"max_context_hint": 200000 // operator-declared context window hint, if configured
},
"mcp_servers": [ // operator-declared client servers (RFC 0004), tags from RFC 0012 §3.1
{ "name":"fs", "tags":["untrusted_input"] },
{ "name":"vault", "tags":["sensitive"] },
{ "name":"mail", "tags":["egress"] }
],
"exec_enabled": false, // --enable-exec (RFC 0012 §3.6); a trifecta leg if true
"allow_trifecta": false, // --allow-trifecta override active (RFC 0012 §3.2)
"limits": { // the bounding box (RFC 0007/0009/0003)
"max_depth": 4, "max_children": 8, "max_total_subagents": 64,
"max_steps": 200, "max_tokens": 2000000, "tree_token_budget": 2000000,
"deadline_ms": 1800000, "drain_timeout_ms": 25000
},
"surfaces": { // which control-plane contracts THIS binary actually serves
"management": "vsock:5005", // false | "vsock:PORT" | "unix:PATH" — the served self-MCP (§3)
"operator_tools": ["drain","lame-duck","pause","resume","cancel"], // present on the mgmt transport (§4)
"metrics": ":9090", // false | the /metrics addr (RFC 0010 / RFC 0016)
"metrics_schema": "1.0", // frozen metrics schema version (RFC 0016)
"events": true, // agent://events served (RFC 0016)
"hot_reload": true, // SIGHUP / file-watch reload (RFC 0017)
"config_validate": true, // agentd --validate-config available (RFC 0017)
"exit_codes": "RFC-0011-§5" // the exit-code table version this binary honours
}
}
Decisions baked into the schema:
surfacesis the graceful-degradation contract (RFC 0014 §7). A binary built withoutserve-mcpreports"management": false; without the metrics surface,"metrics": false; an instance with none of the control-plane surfaces reports them allfalse/absent and agentctl manages it with liveness + exit codes + logs only, exactly as today. agentctl readssurfacesand drives only what is declared.surfaces.operator_toolsis the authoritative list of operator tools this build exposes on the management transport — it mirrors whattools/listreturns to aManagementpeer (§3.4), so agentctl can plan akubectl agentsubcommand set from the manifest alone, before it opens a management connection.- No secrets, ever (RFC 0012 §3.7): the manifest carries no tokens, no
credentials, no resolved
{{secret:NAME}}values —intelligence.transportandendpointsare structural, never the endpoint URL with embedded creds. TheSecretnewtype has noSerialize(RFC 0012 §3.7), so it cannot reach the builder. contract_versionis the field agentctl negotiates on (RFC 0014 §3.4). It is the agentctl↔agentd contract version, distinct fromagent_version(the binary release) and frommetrics_schema(RFC 0016, versioned independently and surfaced here so a scraper can branch).
5.3 agent://inventory — the live subagent tree (backs kubectl agent tree)
inventory is the instance-local view of the running subagent tree: per-node
status, depth, and usage. It is the read model behind kubectl agent tree and
kubectl agent describe (RFC 0014 §4). It is instance-local — agentd reports
its tree, never a fleet view; cross-instance aggregation is agentctl's job
(RFC 0014 §6). The supervisor already holds every field (it is the source of truth
for the tree — RFC 0003); inventory is a pure projection of supervisor state, so
it costs nothing beyond serialization.
// resources/read agent://inventory
{ "contents":[{ "uri":"agent://inventory","mimeType":"application/json","text":
"{ … the object below, stringified … }" }]}
{
"run_id": "01J8Z3K2QN7…",
"mode": "reactive",
"draining": false, "paused": false, "ready": true, // instance-level lifecycle flags (§4)
"totals": { "active": 3, "total_spawned": 11, "depth": 2,
"tokens_in": 84120, "tokens_out": 31044, "steps": 218 },
"nodes": [ // one entry per live tree node (RFC 0003/0005)
{ "handle":"0", "depth":0, "status":"working", "paused":false,
"scope_summary":["fs","vault","mail"],
"usage":{ "tokens_in":40100,"tokens_out":15022,"steps":92 },
"last_event_ms":120 },
{ "handle":"0.2", "depth":1, "status":"working", "paused":false,
"scope_summary":["fs"],
"usage":{ "tokens_in":22010,"tokens_out":8011,"steps":61 },
"last_event_ms":340 },
{ "handle":"0.3", "depth":1, "status":"stalled", "paused":false,
"scope_summary":["mail"],
"usage":{ "tokens_in":22010,"tokens_out":8011,"steps":65 },
"last_event_ms":9400 } // high last_event_ms => Detector B watching (RFC 0003)
]
}
statusis the per-node status the supervisor already tracks (working / stalled / paused / a terminal status from RFC 0007 §3.4 once reached). This RFC introduces no new status vocabulary — the terminal-status set is owned by RFC 0007 §3.4 and consumed verbatim.inventoryis subscribable: it emitsupdatedon any node spawn, exit, or status transition (the same closed transition set as RFC 0005 §3.4 plus spawn/reap). A subscribed agentctl re-reads to refreshkubectl agent treewithout polling. For a deep tree, the same RFC 0005 §3.3 "list vs read" cap/prefix-summarize discipline applies —nodes[]is capped and summarized beyond a depth/breadth threshold rather than unbounded.- It is read-only. Mutations go through the operator tools (§4), never by writing a resource (MCP resources are not writable here, RFC 0004).
5.4 agent://status — instance status + identity
agent://status extends the RFC 0005 agent://run/{run_id} body with the
downward-API identity block (§6) and the instance lifecycle flags, so agentctl has
one canonical status read per instance:
{
"identity": { "run_id":"01J…","instance":"agent-pod-abc","uid":"f3c1…",
"node":"node-3","namespace":"agents" }, // §6 — same block as the manifest
"mode":"reactive", "ready":true, "draining":false, "paused":false,
"started_at":"2026-06-27T09:58:00Z", "uptime_ms":124000,
"subscriptions_active":4, "warm_sessions":1,
"intelligence":{ "transport":"vsock","healthy":true }, // detail in RFC 0018
"exit_disposition": null // set once terminal (RFC 0011 §5 code)
}
This is the identity-bearing read: the identity block is the one place the
downward-API env surfaces in live state (the manifest carries the same block for
the static probe). agentctl correlates inventory/events/metrics to a pod via
identity.uid + identity.instance.
5.5 Operator-surface metrics
The metrics schema is frozen and owned by RFC 0016 (and extends RFC 0010 §3.8).
This RFC adds only the gauges its new lifecycle states need, named in the RFC 0010
convention (agent_*, low-cardinality labels — never agent_path/handle in a
label, RFC 0010):
agent_draining(0/1) — drain latched (draintool or SIGTERM, §4.1).agent_paused(0/1) — tree paused (§4.3).agent_lame_duck(0/1) — readiness overridden NotReady while alive (§4.2).agent_ready(0/1),agent_active_subagents,agent_tree_depth— already defined by RFC 0010 §3.8; reused, not redefined.
These three new gauges are registered in the existing hand-written Prometheus
table (RFC 0010 §3.8 — no metrics crate, plain text). Their canonical names/labels
are ratified by RFC 0016 (the freeze authority); listed here so the operator tools'
state is observable. agentctl reads them via the /metrics surface declared in
surfaces.metrics.
5.6 The work-claim coordination contract (work.*) — frozen
Resolves RFC 0019 §12 ("
work.*tool names are a frozen contract — confirm ownership") and the matching item in §10 below. RFC 0019 §3 defines the claim lifecycle agentd honours; this subsection is the single frozen authority for the tool names, argument shapes, and the_metaconvention so a fleet and its coordination server agree. The umbrella (RFC 0014 §3.4) ratifies it here, in RFC 0015, because it is a control-plane contract agentctl negotiates on — even though agentd calls these tools (it does not serve them; cf. §4, which is tools agentd serves). agentctl, or the source MCP server, provideswork.*; agentd is a participant, exactly as RFC 0011 §6 makes it a participant in the idempotency contract.
Direction & discovery. Unlike the operator tools (§4, served by agentd on the
management transport), work.* are tools agentd calls on a coordination MCP
server (the source server, or a thin claim/lease server the operator runs) over the
ordinary MCP client codec (RFC 0004). Their JSON-Schemas are the server's, found via
tools/list. The names and _meta keys below are frozen; the server owns the
result schema.
The four tools.
// work.claim — atomically lease one item. Atomicity is the SERVER's contract;
// it is the single serializing point that makes RFC 0008 exactly-one-owner hold
// across replicas (RFC 0019 §3.1).
{ "name":"work.claim", "arguments":{ "item": "<uri|id>", "ttl_ms": 30000 } }
// → { "granted": true, "lease_id": "<opaque>", "expires_in_ms": 30000 }
// | { "granted": false, "held_by": "<opaque>" } // another replica owns it
{ "name":"work.renew", "arguments":{ "lease_id": "<opaque>", "ttl_ms": 30000 } }
{ "name":"work.ack", "arguments":{ "lease_id": "<opaque>" } } // work done; side effect committed
{ "name":"work.release","arguments":{ "lease_id": "<opaque>", "reason": "<string>" } } // relinquish; re-claimable now
The _meta convention (frozen). agentd stamps these onto the work.*
tools/call (the agentd/* namespace is reserved for this contract):
_meta key | Value | Purpose |
|---|---|---|
agent/claim_key | the item-derived RUN_ID (below) | the dedupe key the server keys the side effect + ack on |
agent/instance | identity.instance (RFC 0015 §6) | observability / held_by attribution |
agent/shard | "K/N" or absent (RFC 0019 §4) | server-side observability only |
traceparent | W3C trace-context (RFC 0010) | span propagation |
Two styles (per-route claim.style). tool (default): the server
advertises the four work.* tools above. resource: the server models items as
resources carrying a lease field; work.claim degenerates to a conditional
(compare-and-set) tools/call the server exposes, observed after a
resources/read. The lifecycle (CLAIMED → ack/release/expire) is identical; only
the wire shape differs.
Implementation status (resource style). The
toolstyle is implemented + frozen (the fourwork.*names +_metaabove). Theresourcestyle is deliberately deferred — its CAS wire shape is NOT yet frozen. Unliketool, where the names are pinned, the resource variant's compare-and-set call (the exact tool name +expect_lease/new_leaseargument shape) is left unfrozen until a concrete resource-lease coordination server exists to validate it against — freezing a CAS contract speculatively risks a wrong shape that could double-grant, the one thing the convention must never do. agentd's claim client therefore dispatches aclaim.style=resourceroute to a loud error (and startup validation rejects it), never a silent misbehaviour; the path slots in behind the existingClaimOutcomeonce the CAS shape is frozen against a real consumer.
The claim key narrows RUN_ID for claim routes (extension to RFC 0011 §6).
RFC 0011 §6.1 mints RUN_ID as a per-process value. For a claim route this would
break redelivery dedup (two claimers of the same redelivered item would mint two
keys). So for claim routes only, agent/claim_key == RUN_ID == derive(item)
— a stable, item-derived key (default: a deterministic hex digest of the FNV-1a
of item_uri + route_id, reusing the RFC 0019 §4.1 hash; overridable to the item's
own id via claim.key). This is an additive, route-scoped narrowing of
RFC 0011 §6.1, not a conflict: non-claim routes keep per-process RUN_ID unchanged.
The same key rides every downstream side-effect tools/call (RFC 0011 §6.2), so the
durable effect dedupes identically whether the first claimer or a post-expiry second
claimer does the work.
Validation & exit codes (frozen, per RFC 0011 §5 / RFC 0019 §3.6). A claim
route whose coordination server is not a declared --mcp server fails at startup,
exit 2 (EXIT_USAGE). A declared server that is down at the tools/list
handshake is exit 6 (EXIT_MCP) (retriable — a sidecar may be racing up); a server
that is up but lacks work.claim/work.ack is exit 2 (an operator wired a
non-claim server). These are post-handshake checks run before the first reactive
wake.
Capabilities advertisement. A cluster build that has wired the claim path
advertises surfaces.claim: { "styles": ["tool","resource"] } in the manifest
(§5.2); a build without it omits the key (capability-absence-not-error, §2.5).
agentctl places a claim route only on instances advertising claim.
6. Mechanism — instance identity from the downward API (env-only)
agentd surfaces pod identity (identity block in §5.2/§5.4) by reading
operator-injected environment variables, set from the Kubernetes downward API
via valueFrom.fieldRef. agentd never calls the kube API — there is no kube
client, no in-cluster config, no service-account token read; that would pull a
dependency and a cluster coupling the minimalism moat forbids (RFC 0014 §3.3) and
belongs in agentctl. Env in, manifest/status out.
The operator's pod spec injects (conventional names, fixed by this RFC so agentctl and agentd agree — the one naming contract here):
# agentctl's pod template (lives in agentctl, shown for the contract)
env:
- name: AGENT_POD_NAME # -> identity.instance
valueFrom: { fieldRef: { fieldPath: metadata.name } }
- name: AGENT_POD_UID # -> identity.uid
valueFrom: { fieldRef: { fieldPath: metadata.uid } }
- name: AGENT_POD_NAMESPACE # -> identity.namespace
valueFrom: { fieldRef: { fieldPath: metadata.namespace } }
- name: AGENT_NODE_NAME # -> identity.node
valueFrom: { fieldRef: { fieldPath: spec.nodeName } }
# AGENT_POD_GRACE_SECONDS (RFC 0011 §3.3, drain-vs-grace) rides the same downward-API convention
// identity.rs — pure env read, no syscalls beyond getenv, no validation side effects
struct Identity {
run_id: Ulid, // RFC 0011 §6 (always present — minted if unset)
instance: Option<String>, // AGENT_POD_NAME
uid: Option<String>, // AGENT_POD_UID
namespace: Option<String>, // AGENT_POD_NAMESPACE
node: Option<String>, // AGENT_NODE_NAME
}
fn from_env() -> Identity { /* getenv each; absent => None; emit nothing durable */ }
Rules:
- Every field is optional. Outside Kubernetes (bare-metal, local CLI) the env
vars are simply unset and the
identityfields are absent/null. The manifest andagent://statusare valid withrun_idalone (always present, RFC 0011 §6). No env var here is required; their absence is never a config error. - Identity is descriptive, never load-bearing. agentd uses these for
correlation/labelling only; it makes no decision based on
node/namespace(no placement, no scheduling — that is agentctl, RFC 0014 §6). This keeps the values pure metadata. - Shared with RFC 0011 §3.3.
AGENT_POD_GRACE_SECONDS(theterminationGracePeriodSecondshint for drain-vs-grace validation) is the same downward-API convention; this RFC and RFC 0011 §10 settle on theAGENT_POD_*/AGENT_NODE_NAMEfamily as the one naming contract (§10). - No secrets via the downward API. Only the four identity fields above and the
grace hint; downward-API
secretKeyReffor credentials is the RFC 0012 §3.7 env-secret path, never folded intoidentity.
7. Trust boundary & who may call operator tools
The access-control model is structural, transport-scoped, and identical in shape to RFC 0012 §3.8 — there is no in-band auth in v1 (RFC 0012 §2: no auth as core). The rule is one sentence:
Whoever can reach the management transport may call the operator tools.
Concretely:
- unix socket (
--serve-mcp unix:PATH): access control is filesystem permission on the socket — the operator sets the socket mode/owner so only the node-agent (or a co-located admin) can connect (RFC 0012 §3.8). Anyone who canconnect(2)the socket is, by construction, trusted to drain/cancel this instance. - vsock (
--serve-mcp vsock:PORT): access control is the VM boundary — the peer is the host, i.e. the node-agent (§3.3). One trust domain. The operator ensures only the trusted node-agent can reach the guest vsock port, exactly as it ensures only trusted principals can open the unix socket. - stdio: a peer reaching agentd over stdio is a parent agent / harness that spawned it (RFC 0005 §3.6); it gets the work profile only — operator tools are not listed to it (§3.4). A subagent cannot drain its supervisor.
This is honest minimalism (RFC 0012 §3.8): agentd ships no network-exposed
control surface it cannot secure. The management surface is reachable only over a
unix socket (filesystem perms) or vsock (VM boundary) — both confinement
properties of the deployment, not in-band tokens agentd would have to validate.
A network-exposed (TCP/HTTP) management transport, with the auth model and
Origin/session hardening it requires (RFC 0012 §3.8), is deferred to RFC 0013
and out of scope here.
The capability gate (§3.4) and the trust boundary compose: operator tools are
(a) listed only to a Management peer, and (b) a Management peer exists only on
a transport whose reachability the deployment controls. There is no path by which
an untrusted network principal, or a spawned subagent, calls drain.
8. Edge cases & failure semantics
Capability absence is not an error. A binary built without a feature simply
does not list the corresponding tool/resource and reports it false/absent in
surfaces (§2.5, RFC 0012 §3.6 rule). Examples, all by-design, never errors:
serve-mcpnot compiled in →--serve-mcpis rejected at config (exit 2, §3.1); the running default build hassurfaces.management: false; agentctl manages it by liveness + exit codes + logs only (RFC 0014 §7).vsocknot compiled in →--serve-mcp vsock:…is exit 2 (§3.1);unix:still works. Avsock-built binary running with--serve-mcp unix:…reports"management":"unix:/run/agentd.sock"and is fully manageable host-side over the unix socket — vsock is one management transport, not the only one.- The metrics/events/hot-reload surfaces off → reported
falseinsurfaces; agentctl reads the manifest and does not attempt those operations.
Unknown operator tool / resource on a binary without it. A tools/call for a
tool not in this build's tools/list is a JSON-RPC -32601 (method/tool not
found) — the standard MCP error for an unknown tool (RFC 0005 §3.2), not a
custom error and not a crash. agentctl should never call it: it learns the set
from surfaces.operator_tools + tools/list first (§5.2). A resources/read of
an unlisted agent:// URI is -32002 with data.uri (RFC 0005 §3.3).
drain after drain / drain racing SIGTERM. Idempotent and monotonic
(§4.1): the DRAINING latch is one-way (RFC 0011 §4.2). A second drain, or a
SIGTERM after a drain tool call, re-reports the snapshot; neither maps to FORCE.
FORCE remains the second signal (RFC 0011 §4.3). If the management connection
drops mid-drain, drain proceeds regardless — it is supervisor state, not
connection state (RFC 0011 §4.4: SIGKILL safety is a property of state).
lame-duck{ready:true} over a genuinely-not-ready supervisor. Refused as
isError:true with the unmet readiness condition (§4.2). The override can only
push toward NotReady; it can never assert Ready over a supervisor whose computed
readiness (MCP connected + subs reconciled, RFC 0010 §3.7) is false.
cancel of a vanished handle. isError:true ("no such handle"), not a
JSON-RPC error — a racing reap may have already removed the node (RFC 0003).
cancel{handle:"0"} cancels the run without exiting; drain cancels and exits —
agentctl picks per intent (§4.4).
Operator tool on stdio. Not listed (§3.4); a tools/call for it is -32601.
A spawned subagent therefore cannot discover or invoke drain/lame-duck/etc.
against its parent.
--capabilities before connectivity. agentd --capabilities never connects,
never binds, never spawns; it exits 0 with the static+env view (§5.2). Live
fields (intelligence.healthy, node counts) read as their unknown/zero values.
This is the admission-time probe and must not have side effects (RFC 0011 §3.3).
Management transport down vs supervisor down. A dropped management connection
is not a liveness signal — liveness is the supervisor heartbeat (RFC 0010 §3.7),
independent of any served connection. agentctl reconnecting after a node-agent
restart sees the same instance (correlated by identity.uid), re-initializes,
and re-subscribes; agentd holds no per-connection durable state (RFC 0011 §7), so
reconnect is a clean re-read.
Contract-version mismatch. agentctl reads contract_version first; if it does
not understand the major, it refuses to drive the instance (RFC 0014 §3.4) and
falls back to liveness + exit-code + log management. agentd never rejects an
agentctl on version grounds — it serves the manifest and lets the client decide; a
newer agentctl drives an older agentd within the same major additively.
drain vs grace. A drain tool call honours the same AGENT_DRAIN_TIMEOUT
< terminationGracePeriodSeconds invariant (RFC 0011 §3.3): an optional
deadline_ms is clamped to AGENT_DRAIN_TIMEOUT, never above it, so a tool call
can never push drain past the pod grace and lose the clean 0 exit.
9. Non-goals (these stay in agentctl)
Per RFC 0014 §6, restated for this surface:
- No Kubernetes anything in agentd. No CRDs, no operator reconcile loop, no
kubectlplugin, no RBAC, no leader election, no in-cluster client. agentd reads four downward-API env vars (§6) and nothing else from the cluster. - No fleet view, no cross-instance aggregation.
agent://inventoryis instance-local;kubectl agent treeacross a fleet is agentctl stitching many per-instance inventories (RFC 0014 §6). - No scheduling/placement decisions.
identity.node/namespaceare descriptive metadata; agentd makes no placement decision (RFC 0014 §6). - No network-exposed (TCP/HTTP) management transport, no auth model. Deferred with the self-MCP-over-HTTP hardening to RFC 0013 (RFC 0012 §3.8). v1 management is vsock + unix only.
- No new protocol. The operator profile is MCP (RFC 0004/0005) — more resources and tools on the existing wire. No bespoke management RPC (RFC 0014 §3.2).
- No
forcetool, no dashboards, no alert rules. Force is the second signal (RFC 0011 §4.3); dashboards/alerts are agentctl over the RFC 0016 metrics. - Provisioning the vsock device / host model service — the node-agent's job (RFC 0014 §6); agentd only binds the CID/port it is given (§3.3).
agentd's contribution is to make each of these cheap to build by exposing the right primitive — never to implement them.
10. Open items (for the umbrella author to reconcile)
- Downward-API env naming — single source of truth. This RFC fixes
AGENT_POD_NAME/AGENT_POD_UID/AGENT_POD_NAMESPACE/AGENT_NODE_NAME(and reusesAGENT_POD_GRACE_SECONDSfrom RFC 0011 §3.3/§10). RFC 0011 §10 flagged the grace-hint name as an unsettled documentation convention; this RFC proposes the wholeAGENT_POD_*/AGENT_NODE_NAMEfamily as the one contract. The umbrella (RFC 0014) should ratify this family once so agentctl's pod template and agentd'sidentity.rscannot drift. surfacesschema co-ownership with RFC 0016/0017/0018. Thesurfacesblock (§5.2) names fields owned by sibling RFCs (metrics_schema→ RFC 0016,hot_reload/config_validate→ RFC 0017, intelligence detail → RFC 0018). This RFC defines the block and its keys; the sibling RFCs own the truth of each flag. The umbrella should confirmsurfacesis the single manifest location for all cross-RFC capability advertisement, so no sibling invents a parallel one.contract_versionbump policy across sub-RFCs. Addingagent://events(RFC 0016) or hot-reload fields (RFC 0017) to the manifest is additive within1.x; the umbrella should own the rule that any sub-RFC adding asurfaceskey bumps the minor (not the major), and pin which changes are major.- vsock CID convention for multi-pod-per-VM. §3.1 binds
VMADDR_CID_ANYper process, assuming one agentd per guest network namespace. If a future deployment runs multiple agentd pods sharing one VM/CID, port allocation becomes the node-agent's responsibility (it already provisions the device, §3.3); worth a one-line confirmation in the umbrella that port-per-pod is the node-agent's job, not agentd's. pausesemantics vs reactive subscriptions. §4.3 pauses agentic loops but leaves subscriptions armed (events still arrive, just not processed untilresume). Whether agentctl'skubectl agent pauseshould also imply pausing subscription routing is a policy call for agentctl; the umbrella should note that agentd offers both levers (pausefor loops,lame-duckfor readiness) and the composition is agentctl's.
11. References
- RFC 0003 — process supervision & recovery: the kill ladder
cancel/drainopen, the subagent treeinventoryprojects, hierarchical usage accounting. - RFC 0004 — MCP client subset & codec: the wire/codec the operator profile speaks; resources are read-only, mutations are tools.
- RFC 0005 — self-MCP server & control protocol: the surface this RFC profiles;
subagent.*/subscribe/resource.read, theagent://resource tree, the unix-listener design vsock mirrors,subagent.send(=attach), thectrl/pause/resume/cancel/injectcontrol messages. - RFC 0006 — intelligence transport & wire: the
vsockfeature andVsockStream(client) this RFC reuses for the listening half; thevsock:<cid>:<port>URI grammar. - RFC 0007 — agentic loop & terminal status: the terminal-status vocabulary
(§3.4)
inventory.statusconsumes verbatim; turn-boundary checkpointspause/cancelact on. - RFC 0008 — execution modes & reactive routing:
modein the manifest; reactive readinesslame-duckoverrides. - RFC 0009 — subagent process model: depth-minting and the spawn chokepoint
cancelandinventoryreference. - RFC 0010 — observability, health & telemetry:
/readyz//healthzand the readiness flaglame-duck/drainflip; theagent_*metric naming convention; liveness = supervisor heartbeat (independent of the management connection). - RFC 0011 — cloud-native contract: the SIGTERM drain choreography
drainreuses (§4.2), the exit-code table a clean drain returns0against (§5), theRUN_IDidentity (§6), and the downward-API grace-hint convention (§3.3/§10). - RFC 0012 — security posture: the transport-is-the-boundary access model
(§3.8) vsock extends; trifecta tags surfaced in the manifest; the
Secretnewtype that keeps credentials out of the manifest; capability-absence-not-error. - RFC 0013 — deferred v2 surface: Streamable HTTP serving + an auth model for a network-exposed management transport (out of scope here).
- RFC 0014 — control-plane (agentctl) contract: the umbrella this slots under; the primitives-not-policy principle (§3), the capabilities-manifest spine (§5, whose schema this RFC owns), and the contract-versioning/freeze rule (§3.4).
- RFC 0016 — telemetry & lifecycle contract: owns
agent://events, the frozen metrics schema (metrics_schemainsurfaces), and the exit-code-table freeze this manifest cites — referenced, not redefined here. - RFC 0017 — declarative config & hot reload: owns
hot_reload/config_validateinsurfacesand theagent://capabilitiesupdated-on-reload behaviour. - RFC 0018 — intelligence transport resilience: owns the
intelligenceendpoint/health detail summarized in the manifest and the model/endpoint hot-swap that triggers acapabilitiesupdated.