Known limitations¶
These are known, deliberate, and load-bearing to read before you trust anything above. Each one is a case where a manifest field looks stronger than the runtime currently is, and each is stated in the code at the point where it matters.
1. Human gates match MCP tool names by exact string, and nothing else¶
human_gates.require_approval_for gates a call when an entry is a byte-exact, case-sensitive match for the params.name of a tools/call request on a server declared under mcp.servers. The tool name is the only protocol-level identifier the gate proxy sees, and exact match is the only mapping that is deterministic and auditable — there is no semantic matching, no prefix matching, no wildcards.
What this means for you: an entry like external_transfer gates nothing unless an MCP server actually exposes a tool named exactly external_transfer. Constle warns about every unmatched entry at both validate and run time, so an unenforceable gate is loud rather than silent — but it is still unenforceable. Human gates also do not apply to plain HTTPS traffic through allowed_hosts; the gate proxy only sees MCP.
Source: pkg/manifest/manifest.go (HumanGates.RequireApprovalFor, "MAPPING CONTRACT"), cmd/constle/gates.go.
2. max_per_month_usd is parsed but not enforced¶
The field is accepted by the parser and validated as a decimal amount. Nothing enforces it. Declaring it produces an explicit warning and no monthly ledger exists. max_per_run_usd and max_per_day_usd are enforced (the daily one durably, across runs, keyed by DID).
Source: pkg/manifest/manifest.go (Spending.MaxPerMonthUSD).
3. Traffic through allowed_hosts is not metered for spending¶
Cost is metered only at the MCP gate proxy, against the pricing block a server declares. Ordinary HTTPS to a host in network.allowed_hosts — including every direct call to an LLM API — is allowlisted, logged, and not counted toward any spending cap.
This is a deliberate privacy trade-off, not an oversight: metering that traffic would require Constle to TLS-intercept the agent's connections and read their contents, and Constle refuses to do that. The consequence is real and you should size it: an agent that spends money over allowed_hosts rather than through a priced MCP server has no spending enforcement at all. That is exactly the case the quickstart's example hits, and why it prints NOT ENFORCED.
Source: pkg/manifest/manifest.go (Spending, "Enforcement scope"), internal/mcpgate/metering.go.
4. The A2A replay guard is in-memory and per-run¶
The A2A listener rejects duplicate msg_ids and envelopes whose timestamp drifts more than ±5 minutes from the local clock. The set of seen message IDs lives in process memory and does not survive a constle restart.
What this means: an envelope captured during one run can be replayed against a later run, provided the replay lands inside the 5-minute timestamp window. Durable, cross-run replay state is out of scope for this version. The mitigation available today is the timestamp window itself — keep runs of the same agent separated by more than 5 minutes if replay across runs is in your threat model.
Source: internal/a2a/envelope.go (replayGuard).
5. sandbox.network.egress is declared but has no consumer¶
The field parses, validates, and defaults to restricted — and then nothing reads it. All egress enforcement is derived solely from network.allowed_hosts, which becomes the Squid dstdomain allowlist. An empty list denies everything.
So egress: open and egress: none both parse cleanly, change nothing about what the agent can reach, and still render as restricted in the run summary. This is the one gap in this list that is a declared policy which looks real and is not, which is precisely what the warnings in items 1 and 2 exist to prevent elsewhere. Fixing it means deciding what egress: open should do, not just what it should print — until that decision is made, the display label is deliberately not derived from the field, because deriving it would make the output honest about a value the runtime still ignores.
Until then: treat allowed_hosts as the entire network policy. It is. An empty or absent allowed_hosts is your "deny all"; egress is documentation.
Source: cmd/constle/main.go (renderRunSummary, "KNOWN GAP"), recorded in #16.