agentd-core 2.2.0

Minimal, MCP-native agent runtime as a library: the agentic loop, supervisor, workflows, and code-registered tools (the agentd engine)
Documentation
[package]
name = "agentd-core"
version = "2.2.0"
edition = "2024"
rust-version = "1.96"
description = "Minimal, MCP-native agent runtime as a library: the agentic loop, supervisor, workflows, and code-registered tools (the agentd engine)"
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/agentd-dev/source-code"
keywords = ["agent", "mcp", "a2a", "llm", "workflow"]
categories = ["development-tools", "network-programming"]

[lib]
name = "agentd"
path = "src/lib.rs"

# Capability surface is compile-time. The default build stays lean — no async
# runtime, no C/C++ toolchain, single-digit first-party crates — but as of v2.0.0
# HTTPS is the primary transport for both intelligence and MCP, so `tls` is ON by
# default (pure-Rust rustls+ring, no cmake/C build dep). Everything else heavier is
# feature-gated and OFF by default. See rfcs/0002 §dependency-budget.
[features]
default = ["tls"]
# Direct https:// intelligence/MCP (the default transport). rustls with the `ring`
# provider (no cmake/C build dep) + bundled webpki roots — so a scratch container
# has trust anchors with no system CA bundle. On by default; opt out with
# `--no-default-features` to reach https only via a TLS-terminating sidecar.
tls = ["net/tls", "mcp/tls"]
# A2A (Agent2Agent, RFC 0029): agentd's channel to other agents and to its own
# display clients. The protocol itself is `a2a-rs` — an implementation of the
# published A2A specification, so method dispatch, the typed request/response
# shapes, the streaming events, the error mapping and the agent card come from
# the spec's own model rather than from our reading of it. agentd supplies the
# ports (`a2a::ports`): identity, the role matrix, and the durable tasks the
# reactor owns. The listener is axum over tokio-rustls, which is where mTLS
# client identity is read, so this rides `tls`.
a2a = [
    "tls",
    "dep:a2a-rs",
    "dep:buffa",
    "dep:buffa-types",
    "dep:tokio",
    "dep:axum",
    "dep:tokio-rustls",
    "dep:hyper",
    "dep:hyper-util",
    "dep:tower",
    "dep:futures-util",
    "dep:async-trait",
    "dep:tokio-stream",
]
# Optional cron-expression scheduling (internal time event source).
# Hand-rolled 5-field UTC parser — no dep (the minimalism moat, rfcs/0002).
cron = []
# OAuth 2.1 client-credentials (M2M) auth for remote intelligence/MCP endpoints
# (RFC 0006 §auth): fetch+cache a bearer token from a token endpoint, refresh on
# expiry. Dependency-free — reuses the hand-rolled HTTP client + serde_json + the
# sec::secret resolver; the form encoder is hand-rolled (no url/form_urlencoded
# crate). Rides `tls` for an https token endpoint (the common case).
oauth = []
# The GUARDED local command runner behind the `exec` tool (RFC 0028 §exec).
# Default-OFF and cuts against the "no local execution" posture (RFC 0012), so it
# is opt-in at BUILD time (this feature) AND at RUN time (security.exec.enabled)
# — without both, `exec` is a mapping-only contract (delegate off-box). Dep-free
# (std::process). Never in the default binary.
exec = []
# Hand-written Prometheus text on the opt-in HTTP/socket surface (no dep).
metrics = []
# Restart-free reconfiguration (RFC 0017 §5): a SIGHUP-triggered, validate-first,
# quiesce-and-reapply hot reload of the reloadable config subset (model/limits/
# log_level/subscribe/mcp_servers), gated at a reactive quiesce boundary.
# Dependency-free — raw `libc::sigaction` + the existing self-pipe (no
# signal-hook), reusing the one `Config::load` validation pipeline. SIGHUP keeps
# its default disposition when this feature is OFF. `mcp_servers` is RELOADABLE
# (RFC 0017 §5.1/§5.3 step 4): a validated reload re-handshakes the MCP server set
# live — removed servers stop+reap via the stdio shutdown ladder, added servers
# spawn+handshake+subscribe (read-after-subscribe), changed servers remove-then-
# add. The supervisor's name-keyed `servers`/`owner`/claim wiring makes the live
# re-handshake safe (a remove/add never shifts another server's identity).
hot-reload = []
# The inotify file-watch reload trigger (RFC 0017 §5.2): a `--watch-config`-armed
# raw `inotify` watch on the config file's PARENT DIRECTORY that sets the SAME
# RELOAD latch SIGHUP does, so a Kubernetes ConfigMap volume swap (an atomic
# directory-symlink rename) reloads with no signal plumbing. Implies `hot-reload`
# — the watch is useless without the reload routine it funnels into. Off by
# default (SIGHUP is the portable, dependency-free default). Dependency-free: raw
# `libc` inotify syscalls (no `notify`/`inotify` crate — the minimalism moat).
config-watch = ["hot-reload"]
# OTLP span export + GenAI semconv. Hand-rolled OTLP-over-HTTP/JSON over the
# existing HTTP client + serde_json — no opentelemetry/protobuf deps, so
# `--features otel` stays dependency-free (3 deps).
otel = []
# Agent-authored cyclic workflows (pivot Phase 7): a serde-only graph the model
# self-authors (workflow.define/run/patch self-tools) or the operator pins
# (--mode workflow --workflow <file>), driven by a thin walker reusing the ReAct
# `Session`/`Budget`/`Router` — nodes Agent/Tool/Branch/Wait/Subgraph/Halt, a
# blackboard, two-tier conditions (json-pointer preds + one semantic branch), and
# four-layer cycle termination with distinct graph statuses. Dependency-free (serde
# + serde_json::Value::pointer only — the minimalism moat). Default OFF: an absent
# build is byte-for-byte today; the degenerate single-Agent-node workflow reproduces
# today's behaviour. The ENGINE is transport-free (self-tools + the driver need no
# listener); the `agent://workflow` served resource lights up only when a serving
# feature is also built (its read arm lives behind `serve-mcp`).
workflow = []
# Built-in mock intelligence + mock MCP server (the hidden `--internal-mock-llm`
# / `--internal-mock-mcp` re-exec modes) that the test + conformance suites drive.
# Test scaffolding, never a shippable capability — so it's OFF in release and the
# production binary carries none of it. Debug builds enable it automatically (the
# `debug_assertions` cfg on the modules), so `cargo test` and the conformance
# harness need no extra flag; an explicit `--release` test build can opt in here.
internal-mocks = []
# CEL (Common Expression Language) for workflows: `{"op":"cel"}` predicates,
# computed `assign.expr`, `infer.check` value constraints, and reactive `cel`
# wake conditions — hermetic, non-Turing-complete, terminating expressions the
# model can safely author. THE one gated exception to the zero-dep moat: the
# default build carries none of it; a non-cel build REJECTS graphs using CEL at
# define time (fail-closed, clear message). Rides `workflow`.
cel = ["dep:cel-interpreter"]
# AAuth [DRAFT] — agent-side auth for calling AAuth-protected MCP servers
# (RFC 0023): an Ed25519 agent identity, an Agent-Provider token client
# (enroll + agent-token cache/refresh), and RFC 9421 HTTP Message Signatures on
# every outbound MCP request. Rides `tls` (the token/enroll dials are https).
# `ring` is ALREADY in the tree (rustls's crypto provider), so this adds a
# direct edge, not a new crate — the crypto exception, like `cel` is the
# expression exception. OFF by default; a non-aauth binary carries none of it.
aauth = ["tls", "dep:ring"]

[dependencies]
# The A2A protocol itself (feature `a2a`): domain types generated from the
# specification's protocol buffers, the JSON-RPC method dispatch, the streaming
# events and the error mapping. agentd implements its ports; it does not
# re-implement the protocol.
#
# `jsonrpc-client` is deliberately NOT enabled: agentd's outbound A2A builds the
# spec's types over its own credentialed transport (`a2a::peer`), because
# a2a-rs's client is reqwest with a static bearer and would drop request
# signing, mTLS and the SSRF guard. Leaving it off keeps reqwest and four other
# crates out of the tree.
a2a-rs = { version = "0.6", optional = true, default-features = false, features = ["jsonrpc-server"] }
# a2a-rs's protobuf runtime, pinned to the version it builds its domain types
# with. agentd constructs those types directly rather than hand-writing their
# JSON, so `MessageField`/`Struct`/`Timestamp` have to be the same types — a
# mismatch is a compile error, which is the point.
buffa = { version = "0.3.0", optional = true, features = ["json"] }
buffa-types = { version = "0.3.0", optional = true, features = ["json"] }
# The async runtime + HTTP server the A2A listener runs on (feature `a2a`).
# a2a-rs's server adapter is an axum `Router`, so agentd serves it directly and
# merges its own routes onto the same listener.
tokio = { version = "1", optional = true, features = ["rt-multi-thread", "net", "time", "sync", "macros"] }
axum = { version = "0.8", optional = true, default-features = false, features = ["http1", "json", "tokio"] }
tokio-rustls = { version = "0.26", optional = true, default-features = false, features = ["ring", "tls12"] }
hyper = { version = "1", optional = true, features = ["http1", "server"] }
hyper-util = { version = "0.1", optional = true, features = ["server", "tokio"] }
tower = { version = "0.5", optional = true, default-features = false }
# a2a-rs's ports are `#[async_trait]`; implementing them needs the same macro.
async-trait = { version = "0.1", optional = true }
# A channel receiver as a `Stream`, for the observation feed's SSE response.
tokio-stream = { version = "0.1", optional = true }
futures-util = { version = "0.3", optional = true, default-features = false }
cel-interpreter = { version = "0.10.0", optional = true, features = ["json"] }
# AAuth [DRAFT] crypto (feature `aauth`): Ed25519 keygen/sign + SHA-256. Pinned
# to the SAME ring rustls already resolves (no new crate in the graph). The
# `less-safe-getrandom-custom-or-rdrand` avoidance is default; std rng is used.
ring = { version = "0.17", optional = true }
# The reusable MCP base library (workspace crate): wire types + the version/era
# model + the modern dialect helpers + the JSON-RPC codec. In-tree.
mcp = { package = "agentd-mcp", path = "../mcp", version = "0.3.1" }
# Shared transport primitives (workspace crate): the HTTP/1.1+SSE client, unix +
# feature-gated tls/vsock connects, SSRF guard. Owns the rustls/webpki deps
# (agentd's `tls` feature maps to `net/tls`).
net = { package = "agentd-net", path = "../net", version = "0.4.0" }
# The one non-negotiable dependency: JSON is the MCP + LLM wire format.
# Keep all wire types in one module (json/, wire/) so a swap to miniserde
# stays mechanical (rfcs/0002).
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# The reusable `net`/`mcp` crates keep their `vsock` features (agentd no longer
# enables them — the pivot removed agentd's unix/vsock use). No direct `vsock` dep.
# --- feature: cron --- hand-rolled in triggers/timer.rs; no dependency.

# Unix-only: signals (sigaction), process control (setpgid/killpg/waitpid),
# prctl (PR_SET_CHILD_SUBREAPER / PR_SET_PDEATHSIG), setrlimit. Raw libc —
# no signal-hook, no nix in the default build (rfcs/0002, rfcs/0003).
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[dev-dependencies]
tempfile = "3"

# Unix-only test helper: send SIGTERM to a spawned agent to exercise the
# graceful-drain → exit-0 contract for the long-lived (loop/schedule/reactive)
# modes. `libc` is already a runtime dependency on Unix; this adds no new crate.
[target.'cfg(unix)'.dev-dependencies]
libc = "0.2"