Saphan StudioDocs
Configuration

The cost pricing registry

The engine reads this file and a human writes it: rates, named zeros, and what the ledger does with them.

What it is. The engine normalizes every run's cost onto one USD column, but it never invents a rate. For a token-unit backend (codex, agent-loop, qwen-code), the rate — or the fact that there is no marginal rate at all — is a human declaration, read from one JSON file in the workspace. The engine reads this file. A human writes it. No verb creates it, edits it, or guesses a rate for it.

Where it lives. <workspace>/bridge/registry/pricing.json, workspace-relative. A missing file is not an error — it is the empty table, and every run priced against it books the honest string "unknown" rather than a silent $0.

Shape.

{
  "version": 1,
  "rows": [
    {
      "id": "<a name you choose, unique in this file>",
      "backend": "codex",
      "model": "<the exact --model string a run is dispatched with>",
      "kind": "subscription-covered"
    }
  ]
}

Match is by (backend, model) — the first row that matches wins; there is no fallback to a "default" rate for a backend with no matching row.

The two kinds a row can declare, for a token-unit backend:

  1. A rate row. input_usd_per_1m / output_usd_per_1m — the row prices the run's own token counters at that rate. Use this when the backend genuinely bills per token.
  2. A named zero. kind: "subscription-covered" (for codex on a human-paid subscription account) or kind: "local-energy" (for agent-loop on a self-hosted endpoint, and for qwen-code pointed at a model you run yourself) — the marginal USD for the run is genuinely zero, but that zero is declared and named, never guessed from the seat's auth mode. A named-zero row wins before any rate math. The kinds are backend-specific: a codex row using local-energy (or the reverse) does not win the named-zero branch and stays "unknown".

qwen-code needs a row PER MODEL, and its two model families genuinely differ. One qwen seat legitimately spans both: a local .gguf path the owner serves himself is local-energy, while a served tag reached through an API key is metered and carries real rates. Do not try to express that split on the seat — the seat's billing class is one value for the whole seat, and (backend, model) is what the pricing table is already keyed by:

{"backend": "qwen-code", "model": "qwen3-coder-30b-a3b-q4_k_m.gguf", "kind": "local-energy"},
{"backend": "qwen-code", "model": "qwen3-coder", "input_usd_per_1m": 0.0, "output_usd_per_1m": 0.0}

And for qwen-code a rate row does not yet produce a number. This engine captures no token counters from that backend's output — what it reports is tokens rather than money, and the shape of its streaming output has never been measured here — so a rated run on that backend normalizes to "unknown" naming that gap, deliberately, rather than multiplying real rates by zero unread tokens and booking a confident $0.00. The local-energy row is unaffected and works today: its zero is your declaration, not arithmetic over counters.

One structural exception exists so an unregistered local model cannot masquerade as vendor spend: an openai-compat run against a loopback base URL with no matching row is priced local-energy from the observed loopback fact itself. A non-loopback URL with no row stays honestly unknown.

What changes in the ledger once a row is declared.

  • No row for the model: saphan run and saphan dispatch command print a pre-flight warning naming the model and this file, but the run still spawns — the engine transports, it never refuses over a missing owner declaration. The run books "unknown"; saphan cost <stream> and saphan ledger show report it in their unknown_cost_runs count and name the model — the blind spot leaves a trace in the rollup instead of silently vanishing from the total.
  • A named-zero (or rate) row matches: the warning does not fire, the run books a real, countable figure ("0" for the declared-zero case, with the reason named in its pricing_ref), and the run drops out of the unknown-cost counts.

Declaring a row is the owner's own act, off-engine — write the file by hand, through whatever change-review process your workspace already uses. The full economics — billing classes on seats, cost classes in the ledger, and what the dashboard draws from them — are Cost management.