Budgets and cost limits
Price tool and model calls, cap spend per agent, namespace, user and session, and read spend with maqpna budgets, costs and session cost.
flowchart LR
A[Pricing table<br/>per tool call, per 1k tokens] --> B[Gateway meters<br/>every governed call]
C[Budgets<br/>agent, namespace, user, session] --> D{Spend over the limit?}
B --> D
D -- no --> E[Call goes on to policy<br/>and the MCP server or model]
D -- yes, onExceed deny --> F[-32001 budget_exceeded<br/>or HTTP 402 for models]
D -- yes, require_approval --> G[Held for a person]
D -- yes, alert --> H[Allowed, alert logged]
B --> I[maqpna budgets list,<br/>costs summary, costs focus]
Goal#
Stop an agent from spending more than you allow. You will set a price for tool calls, give an agent a daily budget, watch the gateway deny the call that would exceed it, and read spend and cost per agent, namespace, user and session.
The gateway meters every governed call it allows: tool calls at a price per call, model calls at a price per 1,000 input and output tokens. Budgets are checked before every tool call and model call against what has been spent so far in the window.
Prerequisites#
- A local MAQPNA (
maqpna dev up) or a cluster with MAQPNA installed. eval "$(maqpna dev env)"(local) or a context with theviewer,auditororadminrole (cluster), so the read commands reach the gateway.
Steps#
1. Price the calls#
Costs come from a pricing table. Without one, every call costs $0.0000, which is what a fresh
maqpna dev up shows. The format, from examples/pricing.json:
{
"currency": "USD",
"defaultToolCallUSD": 0.0001,
"toolCalls": {
"github/*": 0.0002,
"github/search_*": 0.0005,
"kubernetes/delete_*": 0.001
},
"models": {
"claude-sonnet": {"inputPer1kUSD": 0.003, "outputPer1kUSD": 0.015},
"local-llama": {"inputPer1kUSD": 0.0, "outputPer1kUSD": 0.0}
},
"defaultModel": {"inputPer1kUSD": 0.003, "outputPer1kUSD": 0.015},
"sandboxPerSecondUSD": {"gvisor": 0.00001, "kata": 0.00003, "confidential": 0.00008}
}
Tool-call keys are server/tool globs. The gateway reads the file named by its pricingFile setting.
The *USD field names are historical: amounts are in currency, and budgets must use the same currency.
2. Set budgets#
Budgets are set at four levels. Zero or missing means unlimited.
| Level | Where | Keys |
|---|---|---|
| Agent | Agent.spec.budget |
perSessionUSD, perDayUSD, perMonthUSD, currency |
| Agent model limits | Agent.spec.model |
tokensPerMinute, maxTokensPerRequest |
| Namespace and user | BudgetPolicy (cluster-wide), or Helm budgets.namespaces / budgets.users |
perDayUSD, perMonthUSD, maxConcurrentSessions; "*" is the default for keys without their own entry |
| What happens | BudgetPolicy.spec.onExceed, Helm budgets.onExceed |
deny (default), require_approval (tool calls are held for a person; model calls are denied) or alert (logged and counted only) |
A BudgetPolicy, from config/samples/budgetpolicy-default.yaml:
apiVersion: maqpna.com/v1alpha1
kind: BudgetPolicy
metadata:
name: default
spec:
namespaces:
team-a: {perDayUSD: "200", perMonthUSD: "3000", maxConcurrentSessions: 20}
"*": {perDayUSD: "50"}
users:
"*": {perDayUSD: "25"}
onExceed: deny # deny | require_approval | alert
alertAtPercent: [80, 100]
An agent's own limit:
apiVersion: maqpna.com/v1alpha1
kind: Agent
metadata:
name: coder
namespace: team-a
spec:
budget:
perSessionUSD: "2"
perDayUSD: "20"
model:
tokensPerMinute: 20000
maxTokensPerRequest: 4096
# ...
Apply them with maqpna apply -f. The operator renders all levels into the gateway's budgets file
(ConfigMap maqpna-budgets), which the gateway reloads without a restart. Day and month windows are
calendar windows in the gateway's budgetTimezone (default UTC).
3. Try it on a local MAQPNA#
The local gateway reads the same budgets document directly. Create a pricing table and a budget that allows
three calls a day for agent coder in namespace dev:
cat > .maqpna/pricing.json <<'EOF'
{"currency": "USD", "defaultToolCallUSD": 0.01}
EOF
cat > .maqpna/budgets.json <<'EOF'
{"agents": {"dev/coder": {"perDayUSD": 0.03, "alertAtPercent": [80, 100]}}}
EOF
Agent keys are <namespace>/<agent>. Add "pricingFile" and "budgetsFile" (absolute paths) to
.maqpna/gateway.json, then restart the gateway, because maqpna dev up has no flag for them:
pkill -f "maqpna-gateway -config $PWD/.maqpna"
(set -a; . .maqpna/dev.env; nohup maqpna-gateway -config "$PWD/.maqpna/gateway.json" \
> .maqpna/logs/maqpna-gateway.log 2>&1 &)
4. Spend the budget#
Make four calls:
for i in 1 2 3 4; do
maqpna dev run -- maqpna call --server echo --tool get_time >/dev/null
done
The fourth is denied before it reaches the MCP server:
maqpna dev: dev-c6ad40ee: 1 allow, cost $0.0100 (maqpna dev timeline --last)
maqpna dev: dev-932622fb: 1 allow, cost $0.0100 (maqpna dev timeline --last)
maqpna dev: dev-ed6bf31b: 1 allow, cost $0.0100 (maqpna dev timeline --last)
error -32001: denied by policy budget rule agent-day: budget_exceeded:agent/day
data: {"domain":"maqpna.com","policy":"budget","reason":"budget_exceeded:agent/day","rule":"agent-day"}
maqpna dev: dev-9a205562: 1 deny, cost $0.0000 (maqpna dev timeline --last)
The reason is budget_exceeded:<scope>/<window>, for example budget_exceeded:namespace/month. A model
call over budget answers HTTP 402 with the error type maqpna_budget_exceeded. A model call over
tokensPerMinute answers HTTP 429 maqpna_rate_limited with Retry-After.
5. Read spend#
Budgets and their state:
maqpna budgets list
SCOPE KEY WINDOW PERIOD SPENT USD LIMIT USD EXCEEDED
agent dev/coder day 2026-10-03 0.03 0.03 true
--exceeded lists only the exceeded ones. Running totals since the gateway started:
maqpna costs summary
SCOPE KEY COST USD TOOL CALLS MODEL CALLS IN TOKENS OUT TOKENS
total * 0.03 5 0 0 0
namespace dev 0.03 4 0 0 0
namespace support 0 1 0 0 0
agent dev/coder 0.03 4 0 0 0
agent support/coder 0 1 0 0 0
user you@localhost 0.03 5 0 0 0
Totals for a window:
maqpna costs summary --scope agent --window day
day window 2026-10-03 (2026-10-03T00:00:00Z to 2026-10-04T00:00:00Z, UTC)
SCOPE KEY COST USD TOOL CALLS MODEL CALLS IN TOKENS OUT TOKENS
agent dev/coder 0.03 4 0 0 0
agent support/coder 0 1 0 0 0
One session's cost: maqpna session cost SESSION (or maqpna costs summary --session SESSION). A session
whose calls were all denied prints session dev-9a205562: no metered cost.
6. Export for FinOps#
maqpna costs focus writes FOCUS, the FinOps cost-export format (version 1.2), as CSV or JSON, one row
per charge period (default 1 hour), from the gateway or offline from a ledger file:
maqpna costs focus --since 2026-10-01T00:00:00Z --out costs.csv
maqpna costs focus --ledger .maqpna/audit.jsonl --format json
Each row carries BilledCost, ChargeDescription (for example MCP tool call echo/get_time),
ResourceId (ns/dev/agent/coder/session/dev-932622fb) and MAQPNA columns x_MaqpnaAgent,
x_MaqpnaSession, x_MaqpnaUser. For signed usage reports used in billing, see
Usage metering and billing.
Verify#
maqpna budgets list --exceededis empty in normal operation.- The denied call is in the ledger with
rule: budget/agent-dayandreason: budget_exceeded:agent/day:maqpna audit tail --decision denyin a cluster,maqpna dev timeline --session Slocally. - With
alertAtPercent, the gateway logs abudget.thresholdevent when spend crosses each percentage.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
Every cost is $0.0000 |
No pricing table is loaded | Set pricingFile (see step 1) |
| A budget never triggers | The key does not match: agents are <namespace>/<agent>; or the limit is in another currency |
Check maqpna budgets list; set budgetCurrency to the pricing table's currency (maqpna doctor checks this as budget-currency) |
Calls are denied with budget_exceeded after a restart |
Spend is restored from the gateway's finops state file, or rebuilt from the audit ledger | Expected. Raise the limit or wait for the next window |
| Model calls fail with HTTP 429 | tokensPerMinute reached for the session and route |
Wait for Retry-After, or raise the limit on the Agent |
The local gateway does not pick up pricingFile |
Pricing is read at start; only the budgets file is reloaded | Restart the gateway |
Next steps#
- Hold, rather than deny, calls over budget: set
onExceed: require_approvaland see Human approvals. - Bill tenants from signed usage reports: Usage metering and billing.
- Stop a runaway agent at once: Kill switch and revocations.
- Command reference:
maqpna budgets list,maqpna costs summary,maqpna costs focus,maqpna session cost.