Internals · Architecture

How Akson is built.

One daemon per user, a handful of trusted components, and a rule that shapes everything: the path that receives a peer's bytes and the path that executes them never meet without a local decision in between — yours, or a standing policy you configured.

Rust workspace A2A 1.0 + versioned extensions SQLite · envelope-encrypted fields Linux namespaces · seccomp Pre-release

This page is the technical map. The normative source of truth is the design document (§ references below point into it) and the threat model; the state machines drawn here are also model-checked against the code.

The system, drawn

Everything lives in aksond. Peers reach it over pinned mTLS; the operator reaches it over a Unix socket; a model is reachable only through the broker.

remote peer — untrusted another aksond, anywhere operator — trusted akson CLI · MCP · harness aksond HTTPS · A2A 1.0 mutual TLS, pinned unix socket same UID only receive authorize · execute egress · evidence network endpoint mTLS 1.3 · pinned peer cert bounded A2A parser I-JSON caps · fail closed inbox / dedupe durable · inert · replay-safe contract engine CAS head · risk card policy decision risk card → explicit grant one-shot work order single-use · capabilities clean worker sandboxed, from zero output gate recipient · type · size processor broker allowlist · budget · key evidence engine DSSE manifest · SARIF one inherited fd the receive path holds only a &Store — it cannot dial, run, or read files encrypted store tasks · contracts · outbox · audit — durable before effect the only egress for approved plaintext model / processor plaintext, never the key signed result back to the requester
The trusted computing base is the daemon's own components — endpoint, parser, contract engine, issuer, gate, broker, evidence validator, store. The peer, the task content, the worker while running peer work, and the model are all untrusted. (§7.1)

Three structural choices do most of the security work:

One task's life

Twelve steps, in order, with the commit points where they belong. (§7.2)

  1. The network endpoint authenticates the peer's pinned certificate before reading a request body.
  2. Connection, content-length, rate, and parse limits are enforced.
  3. The standard A2A object and the recognized Akson extension are validated.
  4. The encrypted object and its stable digest are stored transactionally.
  5. The A2A response is returned only after the durability commit — a receipt you can hold the sender to.
  6. A human (or a standing policy, for non-disclosing work) evaluates the exact immutable contract revision — the risk card.
  7. Contract acceptance is signed separately from local authorization.
  8. The local issuer creates a one-shot work order for a clean executor.
  9. The executor receives only the selected input manifest and typed capabilities.
  10. Results pass through size, media-type, recipient, and schema gates.
  11. The evidence engine signs statements over the exact inputs, attempt, outputs, and verifier results.
  12. The requester validates the bundle independently and signs its own outcome.

Execution is tracked as an attempt with a small, closed state machine — the same one the TLA+ model checks against the code:

pending claimed running completed claim spawn gate ok failed ambiguous cancelled crash · cancel crash while claimed cancel recovery at boot: claimed | running → ambiguous · pending is untouched
The attempt machine from akson-authority. There is no edge out of ambiguous: after a crash, honesty beats optimism. One honest footnote: task fulfill — the trusted, operator-only path — completes a claimed attempt without entering running, an edge the model does not yet cover. (§10.4)

The two permission domains

Your authority and a peer's grant never share a mechanism — different sockets, different keys, different processes, different rules.

Akson's core principle is that a peer's commands run in a separate, enforced, reduced-authority domain, and your own user-granted authority is never touched. Four mechanisms realize it:

MechanismHow it separates the domains
Grant-derived sandbox spec The sandbox is not a generic jail with holes poked in it — it is derived from the approved grant. The named inputs, the single output directory, and (only if granted) one broker channel are constructed in; nothing else exists.
One-shot work order Approval mints a single-use, nonce-bound order (§12.3). Replaying it is refused by the store's claim transaction; a crash between claim and run resolves to ambiguous, not to a second execution.
Two control sockets, one broker channel The admin socket carries operator authority; the worker socket carries only "submit a result". Brokered model calls travel a third, even narrower channel — the pre-opened socketpair the confined worker inherits as a file descriptor. A compromised worker holding both still cannot approve, send, or pair — those ops are not on any surface it can reach.
Explicit outward grants The two grants that disclose content — processor_use (reach a model) and artifact_export (return files) — must be requested by the task and granted at approval. Auto-approval can never confer them.

The sandbox, layer by layer

Defense in depth, each layer independent, all of it probed by akson doctor and refused rather than degraded. (§13.1)

LayerWhat it removes
User / mount / PID / net / IPC / UTS namespaces The host's users, filesystem, processes, network interfaces, and IPC. The worker sees a private world with only its task directory in it.
Default-deny seccomp Every syscall not on the reviewed allowlist — including socket() and connect(), so there is no network path even inside the net namespace. no_new_privs is set.
Strict adapter profile For a production adapter (AKSON_WORKER_EXEC), the process-creation family too: no fork, clone, or vfork. A shell reached via execve cannot spawn a single command.
cgroups v2 Unbounded memory and process count (hard ceilings per run). A delegated subtree is required; without one, task run refuses (503 no-confinement). Wall-clock is a separate daemon-enforced timer; there is no CPU quota yet.
Landlock Filesystem reach beyond the constructed paths — applied by the worker entrypoint where the kernel ABI supports the rules. Best-effort depth behind the mount namespace, not part of the fail-closed required set.
Private /proc, fd allowlist, empty environment Sight of host processes; inherited descriptors beyond the explicit allowlist (the broker channel is the notable member); ambient credentials in env. No home, no D-Bus, no SSH agent, no keyring, no Docker socket, no cloud metadata.
Fails closed

If the host cannot provide the required set — unprivileged user namespaces, the delegated cgroup subtree, seccomp — execution refuses rather than running a peer's task unconfined. akson doctor names the missing capability. The worker does get a read-only runtime (/usr, /lib*) and a private, task-scoped output directory — what it never gets is your workspace, home, credentials, or a socket. (§13.1)

The broker: processor calls are effects

Sending plaintext to a model discloses data and costs money — so every call is a durable sub-attempt with the same crash honesty as execution itself.

The confined worker inherits exactly one already-connected file descriptor. It writes a typed request; the daemon makes the real HTTPS call. On the daemon's side of that descriptor, the broker enforces:

the sub-attempt machine, per call
prepared ──> dispatching ──> completed | failed | ambiguous | cancelled

durable-before-wire: "dispatching" commits before the first byte leaves.
a crash mid-call resolves to ambiguous — possible disclosure and cost are
reported to the operator, and never silently retried.
Honest limits

The broker bounds operations, bytes, destination, and time — not money. max_cost_microusd is recorded, not enforced: pricing is per-provider and depends on token usage the broker does not parse. The operation count and size caps bound the blast radius; a true cost ceiling needs per-provider usage accounting and is tracked as follow-up work in the threat model.

Evidence and outcomes

A result is not "some bytes arrived" — it is a signed claim the requester re-verifies from scratch before recording anything.

When the performer delivers, two things travel: the output bytes, and a canonical result manifest naming each output with its role, media type, size, and SHA-256 — signed as a DSSE envelope (§14.1–14.2). Findings use plain SARIF, validated against the profile before they are emitted, so a model answering with malformed or oversized findings fails closed on the performer's side.

The requester's daemon then re-verifies the claim independently: the delivery must match an outstanding request, verify under the performer's pinned task-result key, and every output the manifest names must re-hash to the digest it signs. One missing output, one altered byte, one extra part — the whole delivery is refused before anything is recorded (T8). Only then does the requester sign its own outcome, a durable disposition grounded in the verified bundle (§14.5). Two honest limits, tracked in the threat model: the manifest is not yet cross-checked against the contract's declared deliverables, and referenced in-toto evidence bytes are not delivered or independently verified in v1.

Before any renderable artifact leaves the performer, it is checked inert: SVG, HTML, Markdown, Mermaid, and Graphviz outputs are refused if they carry scripts, event handlers, script-bearing data URIs, external fetches, or DOCTYPE/ENTITY declarations (§11, T4). That is defense in depth for the supported renderable types — not a warranty for arbitrary media or a vulnerable viewer.

Authentic ≠ correct

Verification establishes who produced the bytes and that nobody altered them. It does not establish that the content is right, honest, or safe. Treat a verified result as attributable input — the guide says how.

The wire

Standard A2A 1.0 where a standard exists; a small, versioned extension surface where Akson needs stronger promises. (§3, §10)

SurfaceProfile
Protocol A2A 1.0 objects over HTTP+JSON — today the SendMessage surface; the wider profiled operation set (GetTask, ListTasks, CancelTask) is specified but not yet implemented. Akson extensions ride the declared extension mechanism: signed task contracts, durable passive delivery, portable evidence.
Transport TLS 1.3 only, mutual authentication, peer certificates pinned by digest at pairing — no CA chain for peers, no DNS trust. No session resumption, no tickets, no 0-RTT. (§9.1)
Delivery Durable-before-response: the A2A reply is returned only after the store commit, and every request carries an idempotency commitment over its covered values — an exact replay returns the original response byte-identically; a changed value is a Conflict. (§9.2)
Encoding Strict I-JSON with hard byte, depth, and node caps; duplicate-key and unsafe-integer rejection; digests computed over original bytes; JCS (RFC 8785) where canonical bytes are needed. Every parser gate fails closed, backed by fuzz targets and hostile-input suites. (§11.1)
Signing Ed25519 keys, purpose-bound (a proposal key cannot sign a result), exchanged and pinned at pairing; DSSE envelopes for contracts and results; JWS for the agent card.

The control plane

The CLI, the MCP server, and any client you write all speak one tiny protocol to the daemon: one JSON line in, one JSON line out, over a Unix socket.

any host
printf '{"op":"who_am_i"}\n' | socat - UNIX-CONNECT:"$XDG_RUNTIME_DIR/akson/admin.sock"
# {"outcome":"ok","result":{"issuer":"orgB","agent":"bob", …}}

Every exchange has that shape; akson whoami is exactly this request. The daemon checks two things before running any op: the connecting process's UID (via SO_PEERCRED) must equal its own, and the op must be allowed on the socket it arrived at:

SocketSurface
admin.sock Authority-bearing operator ops: pair, approve, run, fulfil, deliver, send, configure processors.
worker.sock The narrow surface the worker uses to submit its result. Nothing else — a compromised worker cannot approve a contract or send a task, because those ops do not exist on its socket. (Brokered processor calls do not travel here at all: the confined worker uses the pre-opened broker fd it inherited.)

The framing, problem shapes, and operation tables: spec/control-protocol.md.

The crate map

A workspace of small crates, each owning one concern — the boundaries in the code mirror the boundaries in the design.

CrateOwns
aksondThe daemon: receive pipeline, decision surface, issuing, delivery, outcomes.
akson-cliThe operator CLI — a thin client of the control socket.
akson-mcpThe MCP server exposing the daemon to an agent harness as tools.
akson-transportA2A HTTP+JSON server/client on the pinned mTLS 1.3 profile with the delivery extension.
akson-protoPinned A2A types, JSON mapping, profile validation.
akson-extAkson extension schemas, I-JSON, JCS canonicalization, DSSE envelopes, golden vectors.
akson-pairingInvitations, the consume-once bootstrap ledger, pending-to-active pairing, re-pair and removal.
akson-contractContract validation, the revision chain, decisions, risk-card projection.
akson-authorityLocal policy, the capability vector, one-shot work orders, the attempt machine.
akson-sandboxThe Linux isolation launcher: namespaces, seccomp, cgroups v2, Landlock; fail-closed probing.
akson-workerThe clean-worker protocol and the output gate, including inertness checks.
akson-brokerThe processor broker: durable sub-attempts; the only egress path for approved plaintext.
akson-evidenceResult manifests, in-toto statements, the SARIF profile, evidence validation.
akson-cryptoKey lifecycle, purpose binding, thumbprints, JWS — thin wrappers over reviewed libraries.
akson-storeEncrypted SQLite state, outbox/inbox, replay tombstones, the audit chain, state-generation checkpoint.
akson-adapter-sdkThe adapter contract, fixtures, and the §16.3 conformance harness.
adapters/*OpenAI-compatible, Anthropic, and Gemini worker adapters, built on the SDK, run under the strict profile.

Beyond the runtime crates: proof/ holds the TLA+ models, and its conformance harness is itself a workspace member so plain cargo test --workspace runs it (see Proofs); xcheck/ is the independent Python cross-checker (deliberately not sharing the workspace); fuzz/ carries the contract, I-JSON, and SARIF fuzz targets; spec/ the golden vectors, control protocol, and ADRs.