Open source · Apache 2.0 · v0.5.0

The agent runs inside. The rules don't.

Constle enforces network, spend, approvals and identity from outside the agent, so a compromised agent can't switch them off.

curl -fsSL https://constle.dev/install | sh

Checks the release's SHA-256 before unpacking. How releases are verified

Render

01Product

Watch it say no.

One run: an allowed host, a refused one, a gated payment, a log you can verify.

~/agents — constle runA Run
$ constle run invoice-processor.yaml
constle v0.5.0
  → parsing invoice-processor.yaml
  ✓ Agentfile valid
     network:   restricted → api.groq.com
     spending:  run≤$0.50 · day≤$5.00
  ✓ backend: firecracker
  ✓ sandbox started (run_id: 76935e132f9be8e9)
  ┌─ agent output ──────────────────────────
  │ https://api.groq.com/        CONNECT allowed
  │ https://evil.example.com/    CONNECT refused 403
  └─────────────────────────────────────────
⏸  human gate: agent "invoice-processor" wants to call
   MCP tool "pay_invoice" on server "accounting"
   subject: sha256:4b3f…e91a
   approve? [a]pprove / [d]eny (timeout 300s → abort):
✓ run finished    exit=0
  audit log: ~/.constle/logs/invoice-processor-2026-08-08.jsonl
invoice-processor.yamlB Agentfile
identity:
  name: invoice-processor
  did: did:key:z6Mk...4doK
sandbox:
  isolation: kernel
  network:
    allowed_hosts:  # the whole policy
      - api.groq.com
spending:
  max_per_run_usd: "0.50"
  max_per_day_usd: "5.00"
human_gates:
  enabled: true
  require_approval_for: [pay_invoice]
  approver_pubkey: did:key:z6Mk...Ap7w
  notify:
    - channel: webhook
      url_secret_ref: HUMAN_GATE_WEBHOOK_URL
  on_timeout: abort
constle audit verifyC Audit
invoice-processor-2026-08-08.jsonl
#timeeventdetailsig
0112:04:31.102run_startedfirecracker✓
0212:04:31.870network_allowedapi.groq.com · 200✓
0312:04:32.114network_blockedevil.example.com · 403✓
0412:04:33.006gate_triggeredpay_invoice✓
0512:04:41.552gate_approveddecided_by: webhook✓
0612:04:41.790tool_callaccounting/pay_invoice✓
0712:04:42.231run_finishedexit=0✓

✓ 7 entries · all signatures valid · hash chain intact

human gate · signed decisionD Approve
  1. POST$HUMAN_GATE_WEBHOOK_URL2xx
    { "request_id": "hg_1864a3f2c07d9e10",
      "tool_call": { "name": "pay_invoice", … },
      "subject_digest": "sha256:4b3f…e91a" }
  2. GET…/hg_1864a3f2c07d9e10/decision200
    { "decision": "approved",
      "signature": "z3xQb…c7f1" }
  • signature · approver_pubkey
  • request_id matches this gate
  • subject_digest matches the call
  • decision == "approved"

forwarded · gate_approved · decided_by: webhook

02What it enforces

Enforced below the model.

Nine capabilities. Eight ship today. Open any tile for the mechanism and where it stops.

C.01 Shipped

Sandboxed execution

Firecracker microVM or a two-network Docker sandbox. No default gateway.

Mechanism · where it stops
How
Backend auto-detected, or forced with --backend. isolation: is a minimum: kernel selects Firecracker and fails closed if it's missing.
Stops at
--accept-isolation=<level> lets an operator knowingly run one run on a weaker boundary. It is named and recorded.

C.02 Shipped

Network egress

Every packet goes through a Squid allowlist. Raw IPs are denied.


        
Mechanism · where it stops
How
Name-based dstdomain matching with reverse lookups off. Loopback, link-local, metadata and private ranges refused on the resolved address. CONNECT only to 443.
Stops at
DNS for public addresses is trusted: a declared name that resolves to an attacker's public IP gets through.

C.03 Shipped

Spending limits

Hard per-run and per-day USD caps, metered at the MCP gate.

Mechanism · where it stops
How
Each priced server's response is metered against its declared pricing. The daily ledger is durable and keyed by DID. A missing usage value kills the run.
Stops at
Traffic through allowed_hosts (direct LLM calls included) is not metered. max_per_month_usd is not enforced.

C.04 Shipped

Human gates

Named MCP tool calls wait for a human. Terminal or signed webhook.

pay_invoice waiting…
Mechanism · where it stops
How
A protocol-aware gate proxy pauses matching tools/calls. A webhook decision must be Ed25519-signed by approver_pubkey, and can only deny if anything fails to verify.
Stops at
Exact tool-name match only. A terminal approval is unsigned. With no decision, on_timeout decides (default abort).

C.05 Shipped

Audit log

JSONL per agent per UTC day. Signed and hash-chained.


        
Mechanism · where it stops
How
With identity.did set, every entry is Ed25519-signed. constle audit verify reports the first edited line and re-verifies recorded gate decisions.
Stops at
An intact chain proves the runtime's account is unaltered, not that it is true.

C.06 Shipped

Cryptographic identity

W3C did:key. The private key never enters the sandbox.


        
Mechanism · where it stops
How
Key at ~/.constle/identities/<name>/, mode 0600. constle run fails closed if the declared DID has no local key.
Stops at
identity.owner is a label checked for equality, not a cryptographic binding to a person.

C.07 Shipped

Max duration

The agent is killed when its time is up.

Mechanism · where it stops
How
The host supervisor stops the sandbox at limits.max_duration_seconds and records terminated_by_limit.
Stops at
Wall-clock only. Memory is capped by sandbox.memory_mb; sandbox.disk_mb is declared, not applied.

C.08 Shipped

Agent-to-agent messaging

Signed envelopes, to declared peers only.


        
Mechanism · where it stops
How
The host signs and verifies. The sandbox does no cryptography and never sees a peer's real endpoint. There is no discovery, by design.
Stops at
Replay state is per machine: one identity listening on several machines can be replayed once per machine within ±5 minutes.

C.09 Not built

Agent commerce

Agents discovering and paying each other. Exploring.

03Network isolation

Nowhere else to send a packet.

No default route, IPv4 or IPv6. The only next hop is the proxy. Pick a destination.

request
CONNECT api.groq.com:443
result
TCP_TUNNEL/200
audit
network_allowed
why
Listed in allowed_hosts. A tunnel may only go to 443.
AWhy the proxy can't be skipped

On Docker the agent's network is created --internal: one on-link route and no default route. IPv6 is off with an explicit --ipv6=false, so this doesn't depend on the host's Docker defaults. DNS doesn't resolve inside the sandbox either. The Firecracker guest gets only a link-local fe80:: address, and a per-run nftables inet table covers both address families.

BWhy raw IPs lose

The allowlist is a dstdomain ACL with reverse lookups off. An IP literal is admitted only if that same literal was listed, never by resolving back to a name. A PTR record is written by whoever owns the address, not by whoever wrote your allowlist.

CWhere it stops: DNS

For public addresses the proxy trusts DNS. If a declared hostname resolves to a public address an attacker controls, the allowlist lets it through. DNS can't aim a declared name back inside: internal destinations are refused on the resolved address.

The full network model

04Architecture

Rules live outside the agent.

Every layer runs in the host constle process. Keys, real MCP URLs and peer endpoints never enter the sandbox.

Layer 2 · Identity & governance

did:key identity, the signed hash-chained audit log, human gates, and the per-run / per-day USD ledger.

internal/identity · audit · mcpgate · spending

Chokepoints, isolation levels and backends

05The Agentfile

One YAML file. Every field labelled.

A declared protection must never look real when it isn't. Tap a line.

Full field reference on docs.constle.dev

06Quickstart

Sixty seconds to a sandboxed agent.

Verified end to end on Linux + Docker against v0.5.0.

curl -fsSL https://constle.dev/install | sh

or build it (Go 1.26+)

git clone https://github.com/constle/constle && cd constle && go build -o constle ./cmd/constle

CLI

  • run <agentfile>run an agent in a sandbox

    --backend=docker|firecracker forces a backend. --accept-isolation=none|process|network accepts a weaker boundary for this run, and says so.

  • validate <agentfile>check without running

    Prints the resolved isolation and warns about every declared control that won't be enforced.

  • initscaffold a starter Agentfile

    Writes agent.yaml with sane defaults in the current directory.

  • identity create <name>make an agent DID

    Ed25519 keypair as did:key. --owner=<email> binds a label. identity show prints the DID and key location.

  • webhook-keygen <name>approver keypair for gates

    Not an agent identity: it authenticates the human approving. Paste the DID into human_gates.approver_pubkey.

  • audit verify <logfile>signatures + hash chain

    Re-verifies recorded gate decisions too. --agentfile pins both trust anchors; --did and --approver-pubkey pin them one at a time.

  • ps · stop <run-id>list and stop runs

    Lists running and recent Constle-managed agents; stops one by run ID.

  • versionprint the version

    Bare constle shows the startup screen; --no-animation or CONSTLE_ANIMATION=never skips it.

Full quickstart & CLI reference

07Honest by default

Pre-1.0. Five known gaps.

Each one is a place where a field looks stronger than the runtime is. Stated here, and in the code.

  1. 1Human gates match tool names exactly

    An entry gates a call only when it byte-exactly matches an MCP tool's params.name. No wildcards, no semantics. Gates don't apply to plain HTTPS via allowed_hosts. Unmatched entries are warned at validate and run.

    pkg/manifest/manifest.go · cmd/constle/gates.go

  2. 2max_per_month_usd isn't enforced

    Parsed and validated, then ignored with a warning. Per-run and per-day caps are enforced; the daily one durably, keyed by DID.

    pkg/manifest/manifest.go

  3. 3allowed_hosts traffic isn't metered

    Only the MCP gate meters cost. Metering direct LLM calls would mean TLS-intercepting the agent, and Constle refuses to. An agent that spends over allowed_hosts has no spend enforcement.

    internal/mcpgate/metering.go

  4. 4A2A replay state is per machine

    Seen message IDs persist under ~/.constle/a2a/replay/<did>/ across restarts, and fail closed. They aren't shared between machines, so one identity listening on several machines can be replayed once per machine within the ±5-minute window.

    internal/a2a/envelope.go · replay_store.go

  5. 5sandbox.network.egress has no consumer

    It parses and defaults to restricted, and nothing reads it. allowed_hosts is the entire network policy; an empty list denies everything.

    cmd/constle/main.go · issue #16

What Constle is not

  • An agent frameworkIt governs the environment, not the logic. LangGraph, CrewAI or hand-rolled code run inside unchanged.
  • A cloud providerIt installs on your infrastructure, any cloud or on-premise.
  • A monitoring overlayIsolation stops exfiltration even if the model is fully compromised.
  • FinishedThe gaps above are listed up front rather than found later.
  • A closed platformApache 2.0. The Agentfile format is open and independently auditable.

08Open source

Built in the open, in milestones.

Early and solo-maintained. Every issue and PR gets read.

Shipped

  • Firecracker & Docker sandboxes, proxy-only egress
  • did:key identity, signed hash-chained audit
  • Human gates with an abort-on-timeout default
  • Per-run and per-day spend enforcement
  • Signed A2A to declared peers
  • CLI: run, validate, ps, stop, init, identity, audit verify, webhook-keygen

In progress

  • Deeper conformance testing
  • A field-by-field spec of what each key enforces versus declares
  • Hardening the paths that touch real infrastructure

Exploring

  • Agent-to-agent commerce, once identity and spending are solid. A direction, not a date.

Contribute

git clone https://github.com/constle/constle && cd constle && go test ./...

Most useful now: bug reports with a failing test, a gVisor backend, example agents, spec-vs-parser gaps.

CONTRIBUTING.md

Verify a release

The release workflow signs checksums.txt with cosign keyless, pinned to that workflow. The installer checks the SHA-256 every time, and the signature when cosign is on your PATH.

Verification commands

Report a vulnerability

Please don't open a public issue. Use GitHub Security Advisories; disclosure is coordinated.

Report privately

09Start

Contain your first agent.

Install the CLI, validate the example Agentfile, run it in a sandbox.

curl -fsSL https://constle.dev/install | sh