Get release news

How it works

Three layers, none inside the agent.

All three layers are on the host, outside the sandbox. The agent gets a sandbox, a proxy and its run’s gates. While the sandbox holds, the rules are out of the agent’s reach.

  1. 3Communication

    A2A messages go in Ed25519-signed envelopes, which the host signs and verifies, to declared peers only. The sandbox does no cryptography and is never given a peer’s real endpoint. There is no discovery mechanism, by design.

    internal/a2a/

  2. 2Identity & governance

    did:key identity, the audit log (signed and hash-chained once the agent has an identity), human gates at the MCP gate proxy, and the per-run / per-day USD ledger.

    internal/identity · audit · mcpgate · spending

  3. 1Runtime & sandbox

    A Firecracker microVM or a two-network Docker sandbox, and a Squid egress allowlist. constle stops the run at max_duration_seconds. memory_mb is the container’s memory limit (Docker) or the microVM’s memory size (Firecracker).

    internal/sandbox/

01What it enforces

Enforced below the model.

Eight capabilities, each with how it works and where it stops.

CapabilityHowWhere it stops
C.01

Sandboxed execution

Firecracker microVM or a two-network Docker sandbox.

isolation: is a minimum. kernel selects Firecracker and fails closed if it’s missing. Declaring external_transfer, delete_records or spawn_subagent raises the floor to kernel. Otherwise Docker runs, and Firecracker only when forced with --backend=firecracker. --accept-isolation=<level> lets an operator knowingly run one run on a weaker boundary. It is named and recorded. Docs ↗
C.02

Network egress

Egress through the proxy meets a Squid allowlist. A raw IP is denied unless that exact address is itself listed.

Name-based dstdomain matching with reverse lookups off. Loopback, link-local, metadata and private ranges refused on the resolved address. CONNECT only to 443. DNS for public addresses is trusted, so a declared name that resolves to an attacker’s public IP gets through. An allowed host’s server can answer for other names it hosts. Traffic to hosts you allow isn’t gated. If an allowed host shares a server with an MCP server, a request can reach that MCP server past the gate. Docs ↗
C.03

Spending limits

Per-run and per-day USD caps you set, on priced calls that pass through the gate.

Spending is a meter, not a bill. It is the usage each priced server reports, multiplied by the price you declare in pricing. The daily ledger is durable and keyed by DID. A priced result with no usage value stops the run. Costs are counted after each call, so a run can pass its cap before it is stopped, and each overlapping run of one identity can pass the daily cap by one call. Traffic through allowed_hosts (direct LLM calls included) is not metered. max_per_month_usd is not enforced, and validate warns about it. Methods listed under methods on a priced server go through unmetered, and error replies aren’t charged. Docs ↗
C.04

Human gates

Named MCP tool calls that pass through the gate wait for a human, at the terminal or through a signed webhook.

A protocol-aware gate proxy pauses matching tools/calls. A webhook decision must be Ed25519-signed by approver_pubkey. A decision that fails verification can only deny. Exact tool-name match only. A terminal approval is unsigned. With no decision, on_timeout decides (default abort). Gates are off until human_gates.enabled: true. A line typed ahead at the terminal can answer a later prompt. Docs ↗
C.05

Audit log

One JSONL file per agent per UTC day, signed and hash-chained when an identity is declared.

With identity.did set, every entry is Ed25519-signed. constle audit verify reports the first edited line and re-verifies recorded gate decisions. An intact chain shows no entry was changed, inserted or reordered. It can’t show that none were cut from the end, and it doesn’t make the account true. Docs ↗
C.06

Cryptographic identity

did:key (the W3C CCG method). The private key never enters the sandbox.

Key at ~/.constle/identities/<name>/, mode 0600. constle run fails closed if the declared DID has no local key. With an identity, identity.owner must match the owner the local identity was created with. It binds nothing else. Docs ↗
C.07

Max duration

The agent is killed when its time is up.

constle stops the run at max_duration_seconds and records terminated_by_limit. Wall-clock only. memory_mb is the container’s memory limit (Docker) or the microVM’s memory size (Firecracker). sandbox.disk_mb is declared, not applied, and validate says so. Docs ↗
C.08

Agent-to-agent messaging

Signed envelopes, to declared peers only.

The host signs and verifies. The sandbox does no cryptography and is never given a peer’s real endpoint. There is no discovery, by design. Replay state lives in the invoking user’s home directory, so an identity listening from several home directories can be replayed once per home directory within ±5 minutes. Docs ↗

02Where each rule is enforced

Enforcement sits on the host, where traffic meets the proxy and the gates.

Constle assumes nothing inside the sandbox is trustworthy. A control that relied on the agent reporting its own behaviour would only work when it wasn’t needed.

ChokepointWhenEnforcesbeforerun startmax_duration_seconds
Sandbox environment constructionbefore the agent existscredentials, sandbox.memory_mb
sandbox.memory_mb is the container’s memory limit (Docker) or the microVM’s memory size (Firecracker), set when the sandbox is created.
Squid egress proxyper runsandbox.network.allowed_hosts
MCP gate proxyper runmcp.servers[].tools, human_gates.*, spending.* metering
A2A gate + host listenerper runa2a.peers authorization, envelope signing and verification
constle on the hostper runStops the run at limits.max_duration_seconds.

The first row acts before the agent starts. The environment is composed and the sandbox is started with it, so there’s no later moment to ask for more.

03The isolation floor

Capabilities set the floor.

Each declared capability needs a minimum isolation level. A declared level may only strengthen that floor, never weaken it. Try it.

What capabilities is not

It is not a sandbox permission system. Declaring read_file grants nothing and omitting it removes nothing, because the image and mounts decide file access. Capabilities are self-asserted, so an agent that omits external_transfer and declares isolation: none is not raised to kernel and runs on Docker (network isolation). Capabilities naming irreversible actions are reported by validate as needing approval, but that is advice. Only human_gates.require_approval_for gates anything.

capabilities

04Backends

Two backends share one ruleset.

Both render their proxy policy from the same function, so Docker and Firecracker enforce the same allowlist.

DockerFirecracker
BoundaryTwo-network container sandboxmicroVM, with a kernel of its own
Route outNetwork created --internal, with one on-link route and no default routeA default route only to the TAP gateway on the host (a /30, no forwarding or NAT). The per-run nftables inet table accepts only the proxy and gate ports and drops everything else from the tap.
IPv6Off, via explicit --ipv6=falseKernel link-local fe80:: only
Selected whenthe default, or --backend=dockerchosen when the isolation contract needs kernel (isolation: kernel, or a capability whose floor is kernel), or forced with --backend=firecracker
ImageThe image is run as namedA guest root filesystem prepared for that image, images/<image>.ext4. An image with none is refused.

On Docker, the agent’s network is created with the isolated-gateway option, and a Docker Engine that can’t provide it is refused.

05Tool calls

Life of an MCP tool call.

  1. 1

    The agent calls a name

    Constle gives it only CONSTLE_MCP_<ID>_URL, which points to the gate. Constle never passes the real server URL into the sandbox.

  2. 2

    The gate reads the call

    The gate accepts only POST, GET and DELETE, and only for the declared endpoint itself, so a request for any path below it is refused. A body with duplicate or case-colliding keys is refused, and so is a protocol upgrade.

  3. 3

    Tool allowlist

    When a server declares tools, a call to any other tool is refused. An empty list allows none, and no list allows all. Tool calls go through the tool checks, while the handshake, ping, tools/list, notifications and responses pass. Resource, prompt, completion and logging requests pass only if the server’s methods list names them, and any other request the agent sends is refused. What the server sends, its own requests included, is relayed without method checks.

  4. 4

    Human gate

    If gates are on (human_gates.enabled: true) and the tool name is in require_approval_for, the call waits for the terminal or, when a notify webhook URL resolves, a signed decision.

  5. 5

    Forward and meter

    It is forwarded to the declared endpoint only, and the response is metered against pricing. The call that crosses the cap is counted, and the run is stopped.

  6. 6

    Record

    Gate decisions, tool calls and limit hits are written as they happen. Network events are copied from the proxy’s log when the run ends, and the run’s last moments can be missing. With identity.did set, every entry is signed and hash-chained.

06The Agentfile

One YAML file, with no rule language.

A declared protection must never look real when it isn’t. Choose a line.

E enforced at run time V checked at validate D parsed, not acted on I informational

agent.yaml, an exampleits lines labelled

07Host-only

What never enters the sandbox.

~/.constle/identities/<name>/
The agent’s private key, mode 0600.
mcp.servers[].url
Real MCP endpoints. Constle passes the agent only the gate’s address.
a2a.peers[].endpoint
Real peer endpoints. The agent can only name a peer.
your shell’s environment
Nothing from it enters except the variables the Agentfile names under credentials. Those do enter, as environment variables the agent can read.
url_secret_ref
The webhook URL lives in a host env var, not the Agentfile.
~/.constle/webhook-keys/
The approver’s signing key from constle webhook-keygen. Constle never passes it into the sandbox, and you give it to whoever runs the decision endpoint.

08Compared

Four places a rule can live.

A check that runs in the agent’s process, or a rule the model reads in its prompt, is exposed to the same injected text that steers the agent. Constle’s checks run on the host, outside the sandbox.

Four places a rule can live.
reaches the line: it holdsstops short: in parta stub: it doesn’tRules in the system promptA guard library in the agent’s processA hosted gateway in someone else’s cloudConstle: outside the agent, on your machine
Where the rule livesIn the model’s context, as text.In the agent’s own process.On a gateway someone else runs, in their cloud.On your machine, around the sandbox.
Can injected text talk it out of the rule?Yes. A rule written in its prompt is more text it can be argued out of.It is exposed to the same injected text that steers the agent.It depends on what it judges. A destination can’t be argued with, but content can.No. The agent can be fooled into trying anything, but what it’s allowed to do doesn’t change while it runs.
Does it hold if the agent’s process is compromised?No. It lives inside the agent.No. A check inside its own process goes down with it.For the traffic that is sent through it.Yes, while the sandbox holds. On Docker that’s the host kernel, and on Firecracker it’s a kernel of its own.
Does the traffic have to pass it?No. Nothing passes through a prompt.Only the calls the agent makes through it.Only what the agent is configured to send there.It enforces at the proxy and the tool gate, for the traffic that passes them. A request to a declared MCP server can go around the gate inside an allowed host’s tunnel (limitation 2).
What can you check afterwards?What the agent chose to record.Logs written by the process that was steered.The records the gateway keeps, on its side.With an identity, a log signed with the agent’s own key. constle audit verify checks it offline and names any line that was changed.
What it costs youNothing to run.A dependency in your agent’s code.Your agent’s traffic goes through a third party’s servers.Docker or a Firecracker microVM on your own machine. It covers what passes through its proxy and gates, and API keys you declare are passed into the sandbox. The current source and its signed releases aren’t public yet.

Approaches, not products: each column is a place a rule can live.

In-agent guardrails judge content, meaning what the model is asked and what it says, while Constle governs reach: where the agent can connect, which tools it can call, what calls to priced servers may cost, and what gets recorded. Neither replaces the other.