Cap what an AI agent can spend
A budget cap the agent can talk its way around is not a cap. Constle meters priced MCP tool calls at the gate, outside the agent, and stops the run when a cap is crossed. Read the scope first: it is narrower than "everything the agent spends".
On this page
- What you'll get
- 1. Declare what a tool call costs
- 2. Set a per-run cap
- 3. Add a per-day cap
- 4. Validate and read the warnings
- 5. Run, and check what was charged
- How much can a run overshoot?
- What this does not cover
- FAQ
- Does Constle count LLM tokens?
- Can the agent reset the daily cap?
- What does the agent see when a cap trips?
- Why are prices strings and not numbers?
Source release coming soon
The source and installers are not public yet, so there is nothing to install today. This guide documents Constle as it behaves now. More in Project status.
What is metered, and what is not
Constle meters MCP tool calls to servers that declare a pricing block, and nothing else. Calls the agent makes directly to an LLM API over allowed_hosts are not metered, because Constle never decrypts that traffic. A cap declared without a priced MCP server measures nothing, and constle validate says so. This is limitation 3.
What you'll getLink to this section
- A per-run cap: crossing it kills the run and records
spending_limit_reached. - A per-day cap that holds across runs, keyed by the agent's identity so a rename cannot reset it, and refuses to start a run once the day's budget is spent.
- An optional early warning in the audit log, and a ledger that records what was actually charged.
1. Declare what a tool call costsLink to this section
mcp:
servers:
- id: web-search
url: "https://mcp-search.example.com/mcp"
tools: ["search"]
pricing:
meters:
- usage_path: "result.usage.input_tokens"
usd_per_unit: "0.00000300"
- usage_path: "result.usage.output_tokens"
usd_per_unit: "0.00001500"The gate reads each tools/call response from a priced server and charges it. usage_path is an exact dot path into the JSON-RPC response (a digit segment indexes an array); usd_per_unit is a decimal string with at most 8 decimal places. A response's cost is the sum over all its meters. You declare the prices; nothing is guessed per provider.
Pricing is server-wide. A response that lacks a declared usage value is a metering failure, and it kills the run: a server that could leave out its usage could zero its own bill. To mix free and priced tools from one upstream, declare its URL twice under two ids with disjoint tools lists.
2. Set a per-run capLink to this section
spending:
max_per_run_usd: "0.50"When the run's metered total exceeds the cap, the gate stops forwarding calls and the run is killed through the same path as max_duration_seconds, with a spending_limit_reached event naming the cap. Amounts are exact decimal strings; "0" is rejected as ambiguous, so omit a field to leave a cap unset.
3. Add a per-day capLink to this section
identity:
name: research-agent
did: did:key:z6Mk...4doK # from `constle identity create research-agent`; required for the daily cap
spending:
max_per_run_usd: "0.50"
max_per_day_usd: "5.00"
alerts:
warn_at_pct_of_daily: 80 # a one-time warning in the audit log, never a blockThe daily ledger lives in ~/.constle/spending/<did>/, under a file lock, so concurrent runs of the same identity share it, and it is keyed by the DID because a name can be changed. A run whose day is already spent is refused before the sandbox starts, recorded as spending_limit_reached with action: run_refused. An unreadable ledger is an error, never read as $0. Creating the identity: Give each AI agent a verifiable identity.
max_per_month_usd parses and is never enforced; declaring it prints a warning.
4. Validate and read the warningsLink to this section
constle validate agent.yamlFor an agent with these caps, the priced web-search server and allowed_hosts: [api.groq.com], validation reminds you of the scope:
⚠️ warning: spending limits are enforced ONLY for priced MCP servers (web-search);
traffic to network.allowed_hosts (api.groq.com)
is NOT metered and does not count toward the limits.Without any priced server, it says the caps are declared but NOT enforced. Every case: Spend caps and metering.
5. Run, and check what was chargedLink to this section
constle run agent.yaml
grep spending_limit_reached ~/.constle/logs/research-agent-$(date -u +%F).jsonlHow much can a run overshoot?Link to this section
Metering is after the fact: a response's cost is known only once it has arrived. The call that crosses the cap is still incurred and still recorded; enforcement stops what comes next. A run's priced calls go to the server one at a time, so one run overshoots by at most the one call that crossed. Concurrent runs of one identity can each overshoot the daily cap by one call. Set caps with one expensive call of headroom.
What this does not coverLink to this section
| Spending | Covered? |
|---|---|
| Priced MCP tool calls, through the gate | Yes, per run and per day |
Direct calls to an LLM API through allowed_hosts |
No: never decrypted, so never metered |
| A priced MCP server's response that omits its usage value | The run is killed (fail closed) |
| Anything per month | No: max_per_month_usd is declared only |
| Calls answered with a JSON-RPC error, or failed transports | Not charged |
For model API spend, use a usage limit on the key with your provider if it offers one, keep the key's scope small, and set limits.max_duration_seconds so a looping agent stops on time. The reason Constle does not meter that traffic is a privacy choice, explained in Spend caps and metering.
FAQLink to this section
Does Constle count LLM tokens?Link to this section
Only when they are billed through a priced MCP server, where the gate reads the usage values you point it at. Tokens the agent spends by calling an LLM API directly are not counted, because Constle does not intercept the agent's TLS.
Can the agent reset the daily cap?Link to this section
Not from inside the sandbox: the ledger is on the host. It is keyed by the DID, so renaming the agent in the Agentfile does not reset it either.
What does the agent see when a cap trips?Link to this section
The gate stops forwarding calls as soon as the cap is crossed, and the run is then killed. Later runs of the same identity that day are refused before they start if the daily cap is spent.
Why are prices strings and not numbers?Link to this section
A YAML float cannot represent decimal money exactly, and a rounding error in a spending cap is a security bug. Inside the runtime all money is integer micro-cents.