How it works Architecture

Docs How it works

Architecture

Four layers. Three ship today; the fourth doesn't exist yet. Every one of them runs in the host constle process, outside the agent.

On this page

The four layersLink to this section

Layer What it does Where Status
4 · Commerce Agents discovering and paying each other for work. Direction only; see Roadmap. none yet Planned
3 · Communication A2A: Ed25519-signed envelopes, host-side sign and verify, declared peers only. internal/a2a/ Shipped
2 · Identity & governance did:key identity, the signed and hash-chained audit log, human gates, and the per-run / per-day USD ledger. internal/identity, audit, mcpgate, spending Shipped
1 · Runtime & sandbox Firecracker microVM or two-network Docker sandbox, no default route, Squid egress allowlist. internal/sandbox/ Shipped

Layer by layerLink to this section

Layer 1 · Runtime and sandboxLink to this section

A Firecracker microVM or a two-network Docker sandbox, in both cases with no default route. Egress traverses a Squid proxy that allowlists network.allowed_hosts by name, and a wall-clock kill switch stops the agent when limits.max_duration_seconds elapses. Both backends render their proxy policy from the same function, so Docker and Firecracker enforce one ruleset rather than two that drift. Details: Network isolation.

Layer 2 · Identity and governanceLink to this section

A W3C did:key identity whose private key never enters the sandbox (Identity); a JSONL audit log that is Ed25519-signed and hash-chained once a DID is declared (Audit log); human approval gates at the MCP proxy (Human gates); and a per-run and per-day USD ledger metered at the tool-call boundary (Spend caps). The daily ledger is durable across runs and keyed by DID, so renaming an agent does not reset its spend.

Layer 3 · CommunicationLink to this section

Agent-to-agent messages as Ed25519-signed envelopes, exchanged only with peers named in the manifest. The host signs and verifies; the sandbox performs no cryptography and never learns a peer's real endpoint. There is no discovery mechanism, by design: an agent can only talk to what its manifest declared before it started. Details: Agent-to-agent.

Layer 4 · CommerceLink to this section

Planned, with no code in the repository. Agents discovering and paying each other for work, once identity and spending enforcement are solid. A direction, not a scheduled milestone.

Where each rule is enforcedLink to this section

Every enforcement point sits outside the sandbox, at a chokepoint the agent's traffic must physically traverse (spec §2.3):

Chokepoint When Enforces
Sandbox environment construction before the agent exists credentials
Squid egress proxy per run sandbox.network.allowed_hosts
MCP gate proxy per run mcp.servers[].tools, human_gates.*, spending.* metering
A2A gate + host listener per run a2a.peers authorization, envelope signing and verification
Supervisor process per run limits.max_duration_seconds, sandbox.memory_mb

The first row is the only one that acts before the agent exists rather than while it runs: the environment is composed, the sandbox is started with it, and there is no later moment at which an agent could ask for more.

Constle assumes nothing inside the sandbox is trustworthy. A control that depended on the agent truthfully announcing its own behaviour would be reliable only while it was unnecessary. That is why some intuitively useful fields don't exist: there is no human gate on filesystem writes, because writes have no chokepoint outside the sandbox yet (spec §14.3).

Isolation levels and backendsLink to this section

sandbox.isolation is one of none, process, network or kernel. Each declared capability needs a minimum level, and the strongest of them is the capability floor. Omit isolation: and the runtime uses the floor; declare it and it may only be equal or stronger, or constle validate rejects the Agentfile, naming the capabilities that set the floor.

The resolved level is a minimum contract, not a preference. Each backend provides a fixed level:

Docker Firecracker
Provides network: separate process and network namespaces, shared host kernel kernel: a guest kernel behind KVM
Route out Network created --internal; one on-link route, no default route No global address or ::/0 route; a per-run nftables table drops the tap
IPv6 Off, via an explicit --ipv6=false Kernel link-local fe80:: only
Selected when Auto-detected, or --backend=docker Auto-detected, --backend=firecracker, or required by isolation: kernel

So isolation: kernel selects Firecracker, and when Firecracker is unusable on the machine (it needs KVM and root) the run aborts instead of continuing on Docker. The one way to run on a weaker boundary than the host can build is for an operator to name it:

constle run --accept-isolation=network agent.yaml

The run prints ISOLATION DOWNGRADE ACCEPTED and records both the requested and the achieved level in its run_started audit entry. The flag cannot waive the capability-floor refusal, which fires at validation, before any backend is chosen. Full rules: Field reference §6.1 and §8.

Life of an MCP tool callLink to this section

  1. The agent calls a name. It only knows CONSTLE_MCP_<ID>_URL, which points at the gate. The real server URL stays on the host.
  2. The gate reads the call. Only POST, GET and DELETE, and a JSON-RPC body only on POST. A body with duplicate or case-colliding keys, an ambiguous sub-path and a protocol upgrade are all refused.
  3. Tool allowlist. A tool not listed in mcp.servers[].tools is refused at the gate (mcp_tool_blocked).
  4. Human gate. If the tool name is in require_approval_for, the call waits for the terminal or a signed webhook decision.
  5. Forward and meter. The call goes to the declared endpoint only. The response is metered against the server's pricing; over a cap, the run is killed.
  6. Record. Each step lands in the signed, hash-chained audit log.

What never enters the sandboxLink to this section

Stays on the host Why
~/.constle/identities/<name>/ The agent's private key, mode 0600.
mcp.servers[].url Real MCP endpoints. The agent sees only the gate.
a2a.peers[].endpoint Real peer endpoints. The agent can only name a peer.
Your shell's environment Only the variables the Agentfile's credentials names are passed in.
The url_secret_ref value The webhook URL lives in a host environment variable, not the Agentfile.
~/.constle/webhook-keys/ The approver's signing key from constle webhook-keygen. It goes to whoever runs the decision endpoint, never to the agent.

Why the direction of control mattersLink to this section

The agent runs in the sandbox, and the controls run in the host process; the two are not peers. What the agent sees is a set of per-run gate addresses.

A control the agent can reach is a control the agent can be talked into disabling. These controls are not reachable from inside the sandbox: not because the agent is trusted to leave them alone, but because there is no route, no key and no endpoint in there to act on.

A prompt injection that convinces the model to exfiltrate a document does not fail because the model reconsiders. It fails at the network layer, below the model, and the attempt is written to the audit log as a network_blocked event, which is how the operator finds out it happened at all.

Something wrong or unclear? Open an issue. The specifications on these pages are copies of spec/ in constle/constle.