Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Agentd

FreeNot checked

Model Context Protocol base library: wire types, version/era negotiation, transports

GitHubEmbed

About

Model Context Protocol base library: wire types, version/era negotiation, transports

README

A minimal, MCP-native, cloud-native AI agent runtime. One small static Rust binary runs one agent: hand it an instruction and one LLM endpoint, and it runs the agentic loop — think, call a tool, observe, repeat — until the task reaches a terminal status or a new event wakes it. Every tool comes from a remote MCP server over HTTPS (agentd ships none of its own; local execution is off unless you compile and enable the guarded exec runner), it reacts to the world through MCP resource subscriptions, speaks A2A to other agents, and can drive durable DAG workflows. It is built to be a cloud-native unit of work — drop it into a Job, a CronJob, or a long-lived A2A Deployment — and when you want to work with it, attach a terminal or a browser: agentd tui -c agent.yaml.

binary 8.5 MiB static (musl, FROM scratch) · 3.6 MiB download · cold start <1 ms
idle daemon 5.5 MiB RSS · protocols from their own SDKs · HTTPS everywhere · AGPL-3.0

Why agentd

  1. The protocols are not ours to get wrong. MCP is rmcp, the official Rust SDK; A2A is a2a-rs, generated from the specification's protocol buffers. Both run over agentd's own socket, so request signing, mTLS and the SSRF guard survive the adoption. Everything small and frozen — the HTTP client, the cron parser, Prometheus text, OTLP export, the inotify watch — is still hand-rolled on std + libc. What ships is one 8.5 MiB static binary that starts in under a millisecond, idles at 5.5 MiB on one thread, and lands as a single-layer FROM scratch image with no shell, no libc, and nothing to CVE-scan but agentd itself.
  2. MCP as the universal interface. agentd has no built-in fs/http/shell tool library and, in a release binary, executes nothing locally — the exec contract is mapping-only unless it is both compiled in and enabled (see Security model). Every capability is a remote MCP server you declare with --mcp name=https://… — one protocol out, tools and resources alike. What comes in is A2A; see 5.
  3. Reactivity via resource subscriptions. Instead of polling, an agentd with a subscribe start node idles at near-zero CPU and wakes when an MCP resource it subscribed to changes (notify-then-read). An upstream change is the trigger; a workflow can also schedule its own future wakes (loop, schedule).
  4. Two loops, strictly separated. A tiny supervisor owns lifecycle, triggers, limits, and the kill ladder — and never talks to the LLM. The reasoning lives in subagent child processes (the same binary, re-exec'd) the supervisor can always SIGKILL. A runaway or crashing model is contained by construction; limits are enforced by a process that cannot be prompted.
  5. Composability, three ways. An agentd serves an A2A endpoint (a2a.listen), so one agent is just another agent a second one sends messages/commands to. It delegates over A2A (a2a.peers) to remote agents as spec-conformant Tasks. And it nests subagents as an OS process tree with narrowed, per-child context and trust. Agents compose like Unix processes.

How it works

              triggers: interval · cron · MCP resource change · A2A request
                                        │
       ┌────────────────────────────────▼─────────────────────────────────┐
       │  supervisor (never talks to the LLM)                             │
       │  config → validate → trifecta gate → mode driver → kill ladder   │
       │  limits: steps · tokens · deadline · depth · cgroup mem/pids     │
       └────────────┬─────────────────────────────────────┬───────────────┘
                    │ spawn (re-exec, narrowed payload)   │ serve (optional)
       ┌────────────▼────────────────┐       ┌────────────▼───────────────┐
       │  subagent (agentic loop)    │       │  A2A listener over HTTPS   │
       │  think → tool → observe …   │       │  Tasks · streaming · card  │
       │  or: workflow driver        │       │  commands · operator ctl   │
       └──────┬──────────────┬───────┘       └────────────────────────────┘
              │ HTTPS        │ HTTPS
       ┌──────▼──────┐  ┌────▼──────────────┐
       │ intelligence│  │ MCP servers       │
       │ (one LLM    │  │ --mcp a=https://… │
       │  endpoint,  │  │ --mcp b=https://… │
       │  failover)  │  │  tools+resources  │
       └─────────────┘  └───────────────────┘

Every network edge is HTTP(S) — the LLM, the MCP servers, the served A2A endpoint, A2A peers, and operator control — with mTLS and/or bearer auth (plaintext http:// is loopback-only, for dev). agentd links no vsock or stdio transport and spawns no tool processes in a release binary (the exec local runner is a build-and-config opt-in — see Security model); the one non-TCP transport is a unix domain socket for a co-located A2A peer, where the kernel is the authenticator — mode 0600 plus an SO_PEERCRED uid check instead of TLS (see docs/a2a.md).

Install

Installer — detects your architecture, verifies the release SHA256SUMS, installs to /usr/local/bin (or ~/.local/bin), and never invokes sudo:

$ curl -fsSL https://agentd.dev/install.sh | sh

Release binaries (static musl, amd64 + arm64) if you would rather do it by hand — install.sh --help lists the pinning and directory options:

$ TAG=$(curl -fsSL https://api.github.com/repos/agentd-dev/source-code/releases/latest | grep -m1 tag_name | cut -d'"' -f4)
$ curl -LO https://github.com/agentd-dev/source-code/releases/download/$TAG/agentd-$TAG-x86_64-unknown-linux-musl.tar.gz
$ tar xzf agentd-$TAG-x86_64-unknown-linux-musl.tar.gz && ./agentd --version

Container image (multi-arch, cosign-signed, single layer):

$ docker run --rm ghcr.io/agentd-dev/agentd:latest --capabilities

From source (Rust stable, 1.96+; no C toolchain — the build is pure Rust and the crypto provider is ring). Features are compile-time, so --capabilities tells you what a given binary can actually do:

$ cargo build -p agentd-cli --release
$ cargo build -p agentd-cli --release \
    --features "a2a,metrics,cron,otel,hot-reload,config-watch,aauth,oauth,cel,sign,oci,decrypt"   # the shipped set
$ cargo build -p agentd-cli --release --features a2a,exec   # + the local command runner

exec is deliberately absent from release binaries — running local commands is opt-in twice over. cel ships: expression guards (when, until, filter) are how a workflow branches, and a released binary that refuses them at validation was a cliff every non-trivial config fell off. It is the one dependency-bearing feature and costs about 1.8 MiB.

Quickstart

# one-shot: instruction + one LLM endpoint + one MCP server, then exit
$ agentd \
    --instruction "Read /data/report.md and write a 3-bullet summary to /data/summary.md" \
    --intelligence https://gw.example/v1 \
    --mcp fs=https://mcp-fs.internal/mcp

stdout carries the result; stderr carries JSON-lines telemetry (one structured event per line, trace-correlated); the exit code maps the terminal status. Bad config exits 2 in milliseconds, before any LLM round-trip — and --validate-config checks a config without running anything. The intelligence endpoint speaks the OpenAI-compatible wire with native tool-calling; a comma-list of endpoints is a failover order. See docs/getting-started.md.

Talk to it — TUI & web UI

agentd hosts the state; the clients are thin. One command runs the daemon and a terminal UI together:

$ agentd tui --config agent.yaml     # or: agentd ui -c agent.yaml (browser)
agentd · prod-agent                              chat  tasks  subagents  debug
you › Deploy api-gateway to staging
agent › Deploy checks passed. Rolling out v2.4.1 — 3 pods cycling, ETA 90s.
⣾ read_file · 3s · 1.2k tok
● live http://127.0.0.1:8420 · 1 turns · 33/17 tok

Because the daemon owns the session, several surfaces watch the same one at once — a terminal at your desk, a browser on another screen, a colleague's machine paired with a rotating code — and quitting a client leaves the agent working. Approvals (ask_human) render as answerable rows in every attached client and survive a restart; a debug mode exposes the live event feed, per-step run detail and the log tail when you ask for it.

Both clients ship as one npm package — npm i -g @agentd-dev/cli, source under interface/ — built on a shared thin-client core that the package also exports, so a third client is a small program. See docs/interface.md, and docs/coding-agent.md to set one up as a pair-programming agent for a repository.

Lifecycle & triggers

agentd has one durable runtime — there are no modes to pick between. A run is either a one-shot job or a long-lived daemon (lifecycle.run_until), and what triggers runs is a workflow start node.

# a job (the quickstart): the --instruction sugar expands to a
# `once → agent → finish` workflow; run one turn, map the outcome to an exit
# code, then exit.
$ agentd --instruction "…" --intelligence https://gw.example/v1

Recurring / reactive shapes are workflow start nodes in a config_version: "1" document (see docs/modes-and-triggers.md):

config_version: "1"
intelligence: { endpoints: https://gw.example/v1, model: gpt-… }
store: { kind: mcp, mcp: { server: state } }         # a daemon needs a durable store
a2a:   { listen: https://0.0.0.0:8443,
         tls: { cert: …, key: …, client_ca: … } }    # the external channel
workflows:
  - name: watch
    steps:
      s:  { kind: subscribe, server: queue, uri: "queue://inbox" }  # loop|schedule|subscribe|signal|event
      do: { kind: agent, depends_on: [s], instruction: "Handle the item." }
      f:  { kind: finish, depends_on: [do] }
lifecycle: { run_until: drained }                    # a daemon

A non-loopback a2a.listen is refused at startup unless the endpoint has client auth — a2a.tls.client_ca (mTLS, and then every caller needs a client certificate), a2a.bearer, or interface.pairing. --traceparent continues an upstream W3C trace.

Workflows

agentd runs durable DAG workflows (always compiled — no feature flag): a declarative graph of steps in the config_version: "1" document, driven by the same reactor over durable state, so a run survives a restart and resumes exactly where it died. Deterministic steps (assign / map / filter / switch / …) cost zero model tokens; agent / think steps run turn workers:

workflows:
  - name: process
    steps:
      s:     { kind: once }
      fetch: { kind: agent, depends_on: [s], instruction: "fetch the next item",
               output_schema: { type: object, properties: { id: {type: string}, status: {type: string} } } }
      route: { kind: switch, depends_on: [fetch], on: "{{steps.fetch.output.status}}",
               cases: { pending: [work] }, default: [done] }
      work:  { kind: mcp.tool, depends_on: [route], server: fs, tool: process,
               args: { id: "{{steps.fetch.output.id}}" } }
      done:  { kind: finish, depends_on: [work] }
  • A rich node catalogue: agent / think (turn workers), mcp.tool (direct MCP), data steps (assign / map / filter / reduce / sort / parse), switch routing, nested bodies (foreach / batch with bounded parallelism + rate pacing, iterate, parallel, race, subgraph), orchestration (wait on resource / condition / signal / run / subagent / message / deadline, join, child workflow runs, subagent, human gates that project A2A input-required, a2a.delegate), and finish.
  • Variables + templates thread data between steps (writes / {{vars.…}} / {{steps.x.output}} / CEL: expressions); large step outputs spill to durable artifacts and dereference transparently.
  • Durable + crash-resumable: every step checkpoints its progress to the configured store before its effect runs — a run restores and resumes exactly where it died (proven by the chaos-matrix e2e), with idempotency keys so at-least-once effects run once. No database is linked — the store is behind MCP tools or HTTP.
  • Triggers are start nodes (once / loop / schedule / subscribe / signal / event / manual / a2a) — the recurring/reactive shapes, durable across restarts.
  • Bounded by construction: step / token / deadline budgets, concurrency policies (queue / drop / replace), and per-node caps — each terminal with a distinct status / reason.
  • Optional CEL (--features cel): CEL: step conditions, computed values, and data-step element expressions; a non-CEL build fails those closed.

See docs/workflows.md.

Embedding — the engine in your app

agentd is also a library: the binary is a thin shell (agentd-cli) over the published engine crate (agentd-core, lib name agentd). Any Rust app can run agentic logic as a function call, with native Rust tools the model calls alongside MCP tools:

// 1. Register your code as a tool (it joins the model's catalogue — and wins
//    name collisions with remote servers; first-party is unstealable).
agentd::tools::register(agentd::tools::CodeTool::new(
    "word_count", "Count the words in a text.",
    json!({"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}),
    |args| Ok(json!({ "words": args["text"].as_str().unwrap_or("").split_whitespace().count() })),
))?;

// 2. One agentic run, as a call — Outcome + token Usage back as plain values.
let intel = IntelClient::from_parts("https://gw.example/v1", token)?;
let (outcome, usage) = run_loop(&intel, &mcp_servers, &LoopInput {
    instruction: "Count the words in this review, then summarize it.".into(),
    output_contract: Some("JSON: {words, summary}".into()),
    model: "my-model".into(), max_steps: 10, max_tokens: 20_000,
    deadline: Instant::now() + Duration::from_secs(120),
    seed: vec![], cancel: None,
}, &mut NoSelfTools, &log)?;

Workflows embed the same way — author a workflow graph as data, drive() it with your own executor, and code tools are addressable from tool nodes as the reserved server code. A compile-guaranteed example ships in-tree: embedded-agent.rs — the loop called directly from a host app, with a code-registered tool the model can call into mid-reasoning. The stock CLI registers nothing — its no-local-code posture holds by construction. Reusable on their own: agentd-mcp (MCP client/server + wire) and agentd-net (transports). Recipes, the embedder obligations (the re-exec dispatch!), and the API-stability tiers: docs/embedding.md.

Composition: serving, subagents, A2A

Serve your agent over A2A (--features a2a) — set a2a.listen and peers call SendMessage (natural language → a conversation turn, or a command DataPart → a registry action like status / workflow.run / config), GetTask / ListTasks / CancelTask, and SendStreamingMessage (SSE) on the listener, each resolved to a principal (mTLS / bearer → operator / user / agent / anonymous) and authorized against a role matrix, and (optionally) audited:

a2a:
  listen: https://0.0.0.0:8443
  tls: { cert: tls.crt, key: tls.key, client_ca: clients.crt }   # and/or a bearer:
  bearer: "{{secret:A2A_BEARER}}"
  principals:
    - match: { san: "spiffe://team/*" }
      role: user
      grants: [knowledge.*]

Delegate over A2A — a workflow a2a.delegate step (or a subagent) calls a declared peer as a spec-conformant Task:

a2a:
  peers:
    - name: research
      endpoint: https://research-agent.internal:8443

Nest subagents — a parent spawns a child by re-exec'ing the same binary with a narrowed spawn payload (subset of servers, tighter limits, its own cgroup). The tree is bounded by --max-depth and a spawn-rate token bucket; every child is one SIGKILL from gone.

Security model

  • No local execution by default. exec ships as a mapping-only contract: a local runner exists only when the binary was built --features exec (never a release binary) and security.exec.enabled is set. Otherwise the attack surface of a tool call is the remote MCP server's, not the host's.
  • Rule-of-Two trifecta gate. Tag servers with --mcp-tags name=untrusted_input,sensitive,egress; a config that wires all three legs into one agent is refused at startup unless you explicitly --allow-trifecta.
  • Authenticated everything. Outbound: bearer/OAuth 2.1 client-credentials + bundled webpki roots (+ --tls-ca for private PKI). Inbound: mTLS client CA and/or constant-time bearer; operator verbs (the admin.* ops) require the operator role — granted by a matching a2a.principals rule, by a verified a2a.bearer, by any client certificate a2a.tls.client_ca accepted that no rule claims (on a listener that sets a bearer or declares no principals), or by a loopback caller when no principals are configured. Discovery stays public, though: the unauthenticated agent card advertises the whole command surface, admin.* included, and only the authenticated GetExtendedAgentCard narrows the skills to the ops that caller may actually run.
  • Hardened served surface. Cross-origin requests are rejected (403 — only loopback and the origins listed in interface.origins are admitted); plaintext serving is loopback-only.
  • Secrets discipline. Tokens come from env or mounted files (--intelligence-token-file rotates live) and are never logged; telemetry logs lengths, not contents, unless you opt in with --log-content.
  • Contained blast radius. Reasoning runs in killable child processes under optional per-run cgroups (--cgroup, --cgroup-memory-max, --cgroup-pids-max) with atomic cgroup.kill teardown.

See docs/security.md.

AAuth [draft] — signed agent identity

Calling an MCP server protected by AAuth? Build with --features aauth and agentd gets an Ed25519 identity, an agent token from an Agent Provider, and signs every MCP request (RFC 9421) — no shared API key, and the server knows exactly which agent is calling:

$ agentd --instruction "…" --intelligence https://gw.example/v1 \
    --mcp secure=https://mcp.secure.example/mcp \
    --aauth-provider https://apd.example --aauth-enroll-token '{{secret:ENROLL}}'

The token is fetched, cached, and refreshed automatically; the whole subagent tree signs under one identity. Draft support (Case A end-to-end); ships build-from-source, like CEL. See docs/aauth.md.

Operating it

Exit codes are the contract: 0 completed · 1 crash · 2 config/usage (fails in ms, pre-LLM) · 3 stalled/partial · 4 intelligence unavailable · 5 refused · 6 required MCP server down · 7 budget/deadline exhausted · 124 supervisor hard-kill backstop · 137/143 external kills. A clean drain is always 0, never 143. Policy codes (3/7) can be remapped with --budget-exit-code for schedulers that treat nonzero as retry-forever.

Telemetry: JSON-lines on stderr (trace-correlated, --log-level), a --report-file path the schema accepts but the runtime does not write — the terminal outcome is the proc.exit event and the A2A task artifact — Prometheus /metrics + /healthz + /readyz via --metrics-addr (--features metrics), OTLP spans with GenAI semconv via --features otel, a liveness heartbeat file via --health-file, and the live log ring tailed with the debug.events command op (needs interface.enabled + interface.debug).

Discovery: agentd --capabilities prints a machine-readable manifest (runtime: "1", a surfaces{} block pinning the exit_codes and config_schema contract versions, and, alongside it, exactly what's compiled and configured in) and exits — feature-detect from this, not the version string.

Control plane: an operator-role principal drives the served endpoint with the admin.drain / admin.lameduck / admin.pause / admin.resume / admin.cancel command ops — each a DataPart on an ordinary A2A SendMessage, not a custom JSON-RPC method. SIGTERM starts a graceful drain (--drain-timeout < pod grace).

Hot reload (--features hot-reload): SIGHUP — or a ConfigMap volume swap with --watch-config (--features config-watch) — revalidates and reapplies the reloadable subset (model, limits, log level, subscriptions, live MCP server set) at a quiesce boundary, restart-free.

See docs/operations.md and docs/observability.md.

Scaling out

Scale inside one instance first (concurrency.max_runs, limits.max_runs, agent.max_parallel_turns, parallel on fan-out steps). For a fleet, agentd has no coordination protocol of its own — ownership lives where it can actually be arbitrated: partition the subscriptions at the source, or use the queue's own claim/lease semantics from a workflow step. Prometheus metrics feed the autoscaler.

See docs/scaling.md.

Build features

The default build is intentionally small; everything else is opt-in at compile time. A flag whose feature is absent exits 2 loudly — never a silent no-op.

Feature What it adds Extra deps
tls (default) rustls + ring + bundled roots — direct https:// everywhere rustls stack
a2a the A2A HTTPS listener + outbound delegation peers
cel CEL step conditions / computed values / data-step expressions cel-interpreter (the one exception)
otel hand-rolled OTLP/HTTP trace + log export, GenAI semconv
metrics hand-written Prometheus text + health endpoints
aauth an Ed25519 agent identity that signs outbound MCP requests ring (already in-tree)
exec the guarded local command runner (never in a release binary)
cron 5-field UTC cron scheduling (hand-rolled parser)
oauth OAuth 2.1 client-credentials for remote endpoints
hot-reload / config-watch SIGHUP / inotify restart-free reconfig

Shipped release feature set: a2a,metrics,cron,otel,hot-reload,config-watch,aauth,oauth,cel,sign,oci,decrypt.

Footprint (measured)

Measured on the 1.1.0 release build (x86_64, musl, stripped, the shipped feature set):

Metric Value
Binary (static-PIE, runs on scratch) 8.5 MiB amd64 · 6.3 MiB arm64
Release download (amd64) 3.6 MiB .tar.gz → 8.5 MiB binary
Cold start (--version / --capabilities) < 1 ms
Idle daemon RSS (schedule workflow, file store) 5.5 MiB, 1 thread
Idle daemon CPU 1 jiffy / 6 s — under 0.2% of a core
Served request overhead (tools/call, loopback, fresh conn) p50 0.26 ms
Deterministic workflow steps ~146k steps/sec (single lane, 0 model tokens)

Documentation map

License

AGPL-3.0-only — see LICENSE. One crate is deliberately permissive: agentd-instruction (the Instruction Specification's reference parser, crates/instruction) is MIT OR Apache-2.0, so other implementations can link it. Some files are vendored from the Instruction Specification under their own terms (the schema under CC BY 4.0, the conformance corpus under Apache-2.0) — see NOTICE. agentd is fully open source; commercial licensing (for proprietary embedding or AGPL-free use) and commercial support are available — contact [email protected].

from github.com/agentd-dev/source-code

Installing Agentd

This server has no published package — it is built from source. Open the repository and follow its README.

▸ github.com/agentd-dev/source-code

FAQ

Is Agentd MCP free?

Yes, Agentd MCP is free — one-click install via Unyly at no cost.

Does Agentd need an API key?

No, Agentd runs without API keys or environment variables.

Is Agentd hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Agentd in Claude Desktop, Claude Code or Cursor?

Open Agentd on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Agentd with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All development MCPs