constle docs constle docs v0.5.0pre-1.0

Known limitations

Five gaps, all documented and deliberate rather than discovered later. 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.

On this page

Important

Read these before you rely on anything else in these docs.

Known limitations: five places a field looks stronger than the runtime is1 · Human gates match tool names by exact string, nothing elseA gate fires only on a byte-exact match to the MCP tool name: no semantic, prefix orwildcard matching. An unmatched entry warns loudly at validate and run time rather thanfailing silently.pkg/manifest/manifest.go · cmd/constle/gates.go2 · max_per_month_usd is parsed but not enforcedAccepted and validated as a decimal, but nothing enforces it and no monthly ledger exists.Only max_per_run_usd and max_per_day_usd are enforced (the daily cap durably, across runs).pkg/manifest/manifest.go3 · Traffic through allowed_hosts isn’t metered for spendingCost is metered only at the MCP gate. Plain HTTPS to an allowed host, including direct LLMAPI calls, is allowlisted and logged but not counted toward any cap. Deliberate: metering itwould mean TLS-intercepting the agent.pkg/manifest/manifest.go · internal/mcpgate/metering.go4 · A2A replay state is per machine, not sharedSeen message IDs persist under ~/.constle/a2a/replay/<did>/ across restarts and fail closed,but aren’t replicated: one identity listening on several machines can be replayed once permachine within the ±5-minute window.internal/a2a/envelope.go · internal/a2a/replay_store.go5 · sandbox.network.egress is declared but has no consumerThe field parses and defaults to restricted, then nothing reads it. All egress enforcementcomes from network.allowed_hosts. Treat allowed_hosts as the entire network policy; egressis documentation.cmd/constle/main.go · issue #16 Known limitations: five places a field looks stronger than the runtime is1 · Human gates match toolnames by exact string,nothing elseA gate fires only on a byte-exact match tothe MCP tool name: no semantic, prefix orwildcard matching. An unmatched entry warnsloudly at validate and run time rather thanfailing silently.pkg/manifest/manifest.go ·cmd/constle/gates.go2 · max_per_month_usd isparsed but not enforcedAccepted and validated as a decimal, butnothing enforces it and no monthly ledgerexists. Only max_per_run_usd andmax_per_day_usd are enforced (the daily capdurably, across runs).pkg/manifest/manifest.go3 · Traffic throughallowed_hosts isn’t meteredfor spendingCost is metered only at the MCP gate. PlainHTTPS to an allowed host, including directLLM API calls, is allowlisted and logged butnot counted toward any cap. Deliberate:metering it would mean TLS-intercepting theagent.pkg/manifest/manifest.go ·internal/mcpgate/metering.go4 · A2A replay state is permachine, not sharedSeen message IDs persist under~/.constle/a2a/replay/<did>/ across restartsand fail closed, but aren’t replicated: oneidentity listening on several machines canbe replayed once per machine within the±5-minute window.internal/a2a/envelope.go ·internal/a2a/replay_store.go5 · sandbox.network.egress isdeclared but has no consumerThe field parses and defaults to restricted,then nothing reads it. All egressenforcement comes fromnetwork.allowed_hosts. Treat allowed_hostsas the entire network policy; egress isdocumentation.cmd/constle/main.go · issue #16
Figure · Known limitations · each card links to its section below
Text description

1. Human gates match tool names by exact string, nothing else. A gate fires only on a byte-exact match to the MCP tool name: no semantic, prefix or wildcard matching. An unmatched entry warns loudly at validate and run time rather than failing silently. pkg/manifest/manifest.go · cmd/constle/gates.go

2. max_per_month_usd is parsed but not enforced. Accepted and validated as a decimal, but nothing enforces it and no monthly ledger exists. Only max_per_run_usd and max_per_day_usd are enforced (the daily cap durably, across runs). pkg/manifest/manifest.go

3. Traffic through allowed_hosts isn’t metered for spending. Cost is metered only at the MCP gate. Plain HTTPS to an allowed host, including direct LLM API calls, is allowlisted and logged but not counted toward any cap. Deliberate: metering it would mean TLS-intercepting the agent. pkg/manifest/manifest.go · internal/mcpgate/metering.go

4. A2A replay state is per machine, not shared. Seen message IDs persist under ~/.constle/a2a/replay/<did>/ across restarts and fail closed, but aren’t replicated: one identity listening on several machines can be replayed once per machine within the ±5-minute window. internal/a2a/envelope.go · internal/a2a/replay_store.go

5. sandbox.network.egress is declared but has no consumer. The field parses and defaults to restricted, then nothing reads it. All egress enforcement comes from network.allowed_hosts. Treat allowed_hosts as the entire network policy; egress is documentation. cmd/constle/main.go · issue #16

1. Human gates match MCP tool names by exact string, and nothing elseLink to this section

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 enforcedLink to this section

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 spendingLink to this section

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. A2A replay state is per machine, not shared between machinesLink to this section

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 is durable: every accepted id is persisted under ~/.constle/a2a/replay/<did>/, so the check spans process restarts and concurrent runs of the same identity, and fails closed (a retryable 503) if that state cannot be read or written. What it does not span is machines: the state lives in the invoking user's home and is not replicated anywhere.

What this means: if you run the same identity as a listener on more than one machine, an envelope captured in flight can be replayed once per machine, provided each replay lands inside the 5-minute timestamp window. One listening machine per identity, the normal deployment, has no such exposure.

Why this differs from the README's short list

The README's one-line summary of this limitation still calls the replay guard "in-memory and per-run". The detailed section, the A2A specification and internal/a2a/replay_store.go all describe the durable, per-machine store above, and that is what the code does.

Source: internal/a2a/envelope.go (replayGuard), internal/a2a/replay_store.go.

5. sandbox.network.egress is declared but has no consumerLink to this section

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.