Capabilities · 8. What it costs

The daemon measures context tokens. It does not know what a token costs you, it has no price table, and there is no monetary field anywhere in the model. Of the six capabilities in this family, one is shipped and unconditional, one is shipped and advisory, and four are behind flags that default off. If you arrived looking for a bill, this chapter is going to disappoint you honestly rather than dishonestly.

This chapter is explanation. It says why cost is modelled the way it is and where each measurement stops being trustworthy. Route tables, response fields and console panels are reference and already exist.

8.0 The standing rail, and the six capabilities

The rail that governs this whole family, and that the rest of the documentation set applies too: cost attribution is a supporting field, never a headline. A token-burn figure tells you where context went. It is not an invoice line, it is not a price, and a number produced by the coarse attribution path in 8.2 is not itemised enough to bill from. Anyone who puts one of these numbers in front of a client as a charge has crossed a line the design deliberately drew.

#CapabilityStatusWhat it gives you
8.1Session cost lensAnalyser and CLI SHIPPED; daemon storage and read behind CORECRUXD_FEATURE_COST_LENS, default offMeasured context tokens per turn for one coding session, from your own transcript
8.2Cost attributed to work and plansSHIPPED as a read-time joinA credit of measured burn to work items and ExecPlans, precise or coarse, and it tells you which
8.3Per-passport token accountingSHIPPED, advisory onlyA running per-identity call and token count for this process
8.4Token-savings holdoutFLAG CRUX_OUTPUT_HOLDOUT, default 0.0, which is offA measured rather than asserted answer to "did shaping actually save tokens"
8.5Quota buckets and the credit meterFLAG CORECRUXD_QUOTA, default off; FLAG CORECRUXD_CREDIT_METER, default offRefusal before execution, and a debit on a metered call
8.6Agent Workbench Pro surfacesFLAG CORECRUXD_ENABLED_PRO_SERVICES, empty by default, so these routes return 402Twelve entitlement-gated surfaces for fleet leads

Grounding for each capability is in that capability's closing line rather than in a chapter-level list.

8.1 Measuring what a session actually cost, without sending it anywhere

Status the analyser and CLI are SHIPPED and unflagged. Daemon storage and the read endpoint are behind CORECRUXD_FEATURE_COST_LENS, default off. Reached through corecruxctl session cost; POST and GET /v1/cost/report; Console → Meters › Token Burn. Who it is for operators.

What it does. The analyser parses your own agent transcript on your own machine, takes the transcript's recorded message.usage as ground truth, apportions that measured total across the blocks that carried it, and reports context tokens per turn with threshold-based levers for reducing it. Storing a report in the daemon and reading it back is behind CORECRUXD_FEATURE_COST_LENS, which is off by default; with it off, both cost routes return 404 and the report journal is never armed. The daemon never sees the transcript.

Why it works this way. The tempting design is to estimate cost inside the daemon from what it observes passing through, and it is wrong in both directions: the daemon does not see the whole conversation, and an estimate presented next to real numbers gets read as a real number. Taking the transcript's own usage record as ground truth and using estimation only to apportion that measured total means every figure reconciles exactly to something the model provider actually reported. The residual is forced into a session-prefix bucket rather than being quietly dropped, so the buckets add up. The cost is that the lens needs a transcript file, which makes it client-side by construction, which is also why the daemon never has to be trusted with your conversation.

What changes for you.

  • As an operator: you can answer "why is this session expensive" with an apportionment that sums to the measured total, rather than with a guess, and you can do it without uploading anything.
  • As an agent: nothing directly. This measures you; it does not constrain you.

What it does not do.

  • There is no currency in the model. No price table, no rate card, no monetary field. "Cost" means carried context tokens. The daemon cannot tell you what a session cost in money because it does not know and is never told.
  • It enforces nothing. The cost lens is bound to no quota, no budget and no billing. Nothing in it blocks, throttles or charges.
  • The transcript format it parses is undocumented by its vendor and drifts, so parsing is deliberately fail-soft. A report that looks thin may be a parse gap rather than a cheap session.
  • The apportionment estimate and the token estimates used by MCP budgets are different numbers computed for different purposes. Do not quote one as the other.

Turn it on. Set CORECRUXD_FEATURE_COST_LENS=1 if you want reports stored in and served by the daemon. The read path then requires a token_budget on every call, one of the few surfaces where it is mandatory rather than advisory.

Where the detail lives. The measurement model, the levers and the flag's exact off-behaviour: Daemon 15.2. The routes: API 8.6. Crate contract: crux-cost/lib.rs:8.

8.2 Crediting burn to the work it paid for

Status SHIPPED, as a read-time join rather than a stored binding. Reached through GET /v1/work, in the token_burn field; Console → Work › ExecPlans. Who it is for operators.

What it does. Cost reports are keyed by session; work items and ExecPlans are not. This capability joins the two at read time, so a work item shows the measured burn attributable to it. When a report names the plans it worked on, the credit is precise and the burn is split across the named plans. When it does not, the join falls back to overlapping the session's active window against each plan's activity window, which credits every plan that was live at the same time.

Why it works this way. The alternative is to tag burn at write time, which produces a stored number that is correct on the day it is written and progressively wrong afterwards as plans are renamed, split and reassigned. Recomputing at read time means the answer always reflects the current facts, at the cost of doing the work on every read. More importantly, the module records its own measured coarseness in source rather than presenting both join methods as equivalent, and the response tells you which method produced the number in front of you. A join that admits it is a join is usable; one that hides it is not.

What changes for you.

  • As an operator: a work item carries a burn figure, and a method telling you whether it was linked or inferred from a time window. Read the second before you rely on the first.
  • As an agent: naming the ExecPlans you are working on in your cost report is what moves the attribution from coarse to precise.

What it does not do.

  • A window-method number is a rough indication and never an itemised bill. Multi-day sessions overlap many plans, and the window method credits all of them. The module's own measurement of how badly is recorded in the source and is not flattering.
  • It is a join, not a binding. Nothing is stored linking a report to a plan, so the number can change when the underlying facts change. That is the intended behaviour.
  • It carries the same currency limit as 8.1: these are tokens, not money.

Where the detail lives. The two join methods, the split rule and the module's own coarseness measurement: Daemon 15.2. The work board response: API 6.1. Join implementation: cost_attribution.rs:6.

8.3 A running count per identity, and what that count is worth

Status SHIPPED and unconditional, but advisory only. Reached through MCP session_token_usage; GET /v1/agents/{passport}/usage, GET /v1/mcp/tools/usage; Console → Meters › Average Token Usage. Who it is for agents.

What it does. Every MCP tool call increments a per-passport accumulator of calls, estimated input tokens, estimated output tokens and any declared budget. An agent reads its own accumulator back as used, limit and percentage. An optional advisory limit can be configured. Per-agent and per-tool usage endpoints expose the same data to an operator.

Why it works this way. This deliberately does not live on the persisted session document. Writing a token count to the journal on every tool call would flood the durable record with observability noise and make the store's growth a function of chatter rather than of decisions. Keeping the accumulator in process memory, alongside the trace ring, makes it cheap enough to run unconditionally, and running unconditionally is what makes it useful: an agent can always ask how much it has spent this process without an operator having enabled anything. The price of that choice is stated in the next block and is significant.

What changes for you.

  • As an agent: you can self-throttle. Reading your own accumulator mid-run is how you decide to narrow a query rather than discovering after the fact that you did not.
  • As an operator: you get a per-identity and per-tool usage view without turning anything on, and you should read it as a shape rather than as a ledger.

What it does not do.

  • Nothing enforces the limit. The configured budget is advisory. Exceeding it does not refuse a call. If you want refusal before execution, that is 8.5.
  • It is process-local and resets on restart. These are not cumulative lifetime figures. A restarted daemon reports zero.
  • It self-labels its estimator. The token figures are estimates derived from argument and result size, not measured usage from a model provider. They are the right order of magnitude and the wrong number to invoice from.

Where the detail lives. How a token is counted here, and why it differs from 8.1: Daemon 11.13. The tool and its response shape: API 13.12. Accumulator design note: token_accounting.rs:6.

8.4 Measuring the saving instead of asserting it

Status FLAG CRUX_OUTPUT_HOLDOUT, default 0.0, which means off. It is not present in config.example.env. Reached through MCP token_savings. Who it is for operators.

What it does. Behind CRUX_OUTPUT_HOLDOUT, default off, a deterministic fraction of live traffic runs unshaped as a control group, and the saving from shaping is reported as the difference between the two arms rather than as a comparison against a counterfactual nobody ran. Assignment is deterministic on the request key, so the same request always lands in the same arm and the split is reproducible without a random number generator.

Why it works this way. The standard way to report a token saving is to compute what the unshaped response would have been and subtract, which produces a number that is generous by construction because the counterfactual is estimated by the same code that is being evaluated. Holding out real traffic costs you the shaping benefit on that slice and buys you a number that survives someone else checking it. The determinism matters for a second reason: it keeps the benchmark harness free of clocks and randomness, so a reported figure can be reproduced rather than merely repeated.

What changes for you.

  • As an operator: the savings figure you quote has a control arm behind it. If you have never set this variable, you have no control arm, and the honest statement is that you have not measured.
  • As an agent: a fraction of your calls run without shaping. That is the cost of the measurement, and it is the fraction you chose.

What it does not do.

  • It can report a negative net, and that is the feature working. Reversible pointer budgeting trades tokens for recall on purpose, so a configuration tuned for recall will show shaping costing tokens rather than saving them. A measurement instrument that could only produce favourable numbers would not be one.
  • It measures output shaping. It does not measure end-to-end spend, and it is not connected to 8.1.
  • It is off by default and absent from the example configuration, so assume it is off unless you set it.

Turn it on. Set CRUX_OUTPUT_HOLDOUT to a fraction such as 0.1. Anything at 0.0 or unset means no control arm.

Where the detail lives. What the number means, including the case where it argues against the product: Daemon 11.12. The tool: API 13.10. Control assignment and paired savings: holdout.rs:10.

8.5 Refusing before you spend, and debiting when you do

Status FLAG CORECRUXD_QUOTA, default off; FLAG CORECRUXD_CREDIT_METER, default off. Neither is live on a stock daemon. Reached through GET /v1/quota, POST /v1/credits/spend; quota middleware on every route. Who it is for operators.

What it does. Behind CORECRUXD_QUOTA, default off, a token bucket per identity and per hosted surface refuses over-budget requests with 429 and a Retry-After, and it does so before any metered execution or credit spend. Behind CORECRUXD_CREDIT_METER, also default off, a metered call debits a credit balance. With quota off the middleware is transparent, and with the hosted-surface list empty everything is unlimited local compute regardless.

Why it works this way. The ordering is the design. Putting the bucket in front of execution means a refused request has consumed nothing and debited nothing, so a caller hammering a limit cannot run up a balance while being told no. The same reasoning explains the meter's most surprising behaviour: if its lock is poisoned, every metered request fails closed with a 500 until the daemon is restarted, rather than proceeding un-metered. Serving work you cannot account for is treated as worse than serving nothing, which is the correct trade for anything that debits, and it is the one failure in this chapter you are likely to meet at three in the morning.

What changes for you.

  • As an operator: if you have not set these, you have no enforcement. Turning quota on gives you refusal at the edge; turning the meter on gives you a debit trail. Uniform 500s on metered routes with nothing else wrong means a poisoned meter, and restart is the fix.
  • As an agent: a 429 here carries Retry-After and quota headers. It is a wait instruction, not a permission error, and it did not cost you anything.

What it does not do.

  • Billing is not live. Both flags default off, and there is no currency in this family; the meter debits credits, not money.
  • Quota with an empty hosted-surface list enforces nothing even when the flag is on. Both settings are required.
  • It is not connected to the cost lens. The quota bucket counts requests against a surface; 8.1 measures context tokens from a transcript. Neither feeds the other.

Turn it on. Set CORECRUXD_QUOTA=1 and populate CORECRUXD_QUOTA_HOSTED_SURFACES. The meter is a separate flag.

Where the detail lives. Bucket, headers and meter routes: API 8.7. The poisoned-mutex failure and what to do about it: API 8.3. Gating and ordering: quota.rs:27.

8.6 Entitlement, and why a correct caller still gets a 402

Status FLAG CORECRUXD_ENABLED_PRO_SERVICES, empty by default, so every one of these routes returns 402. Reached through /v1/workbench/*; Console → System › Workbench. Who it is for operators and agents.

What it does. Twelve surfaces aimed at fleet leads, including an agent brief, a budgeted context pack, an impact preflight, a command ledger, audit triage, a reasoning timeline and policy simulation, sit behind a dual gate. A caller passes the first gate with admin scope or with the tenant-scoped capability for that specific surface. Even then, the capability must be listed in CORECRUXD_ENABLED_PRO_SERVICES or the route returns 402 with pro_service_not_enabled. The variable is empty by default, so on a stock daemon all twelve return 402.

Why it works this way. Entitlement and permission are separated deliberately, and the 402 is what that separation looks like from the outside. Permission answers "may this caller do this"; entitlement answers "is this deployment licensed to offer it at all". Collapsing them into one check produces a 403 that sends an operator hunting through scope configuration for a problem that lives in the deployment's product posture. A distinct status code and a distinct reason code make the two failures tell different stories, which is worth the confusion of a caller who has correct scopes and is still refused.

What changes for you.

  • As an operator: enabling a Pro surface is a deliberate, per-capability list entry, not a side effect of granting a scope. Nothing appears because someone got admin rights.
  • As an agent: a 402 with pro_service_not_enabled is not fixable by presenting a better token. It is a deployment answer, and the correct response is to stop rather than to retry with different credentials.

What it does not do.

  • It does not enable anything by default. Empty is the shipped value.
  • Passing the capability gate is not sufficient; both gates apply, in that order.
  • These are Community Edition boundaries in places. Some adjacent surfaces return 501 rather than 402, which is a different statement: not built here, rather than not enabled here.

Turn it on. List the specific capabilities in CORECRUXD_ENABLED_PRO_SERVICES. There is no wildcard.

Where the detail lives. The dual gate and the 402 contract: API 0.12. The twelve surfaces: API 2.5. Gate implementation: workbench.rs:812.

Sources

Grounding for each capability is carried in that capability's closing line, next to the claim it supports, rather than gathered here. Every source link resolves against origin/main of the Crux repository.