Sign in Book a demo 134

Configuration

Config file format, agent overrides, capture settings, storage, and merge rules.

TokenJam reads its config from ~/.config/tj/config.toml (created by tj init). All settings can be overridden via the CLI, the REST API, or the web UI. The file is the source of truth and is regenerated when you save.

Example

# ~/.config/tj/config.toml (generated by tj init)

[defaults.budget]
daily_usd = 10.00

[agents.my-email-agent]
description = "Personal email management agent"

  [agents.my-email-agent.budget]
  daily_usd   = 5.00
  session_usd = 1.00

  [[agents.my-email-agent.sensitive_actions]]
  name     = "send_email"
  severity = "critical"

  [agents.my-email-agent.drift]
  enabled           = true
  baseline_sessions = 10
  token_threshold   = 2.0

[capture]
prompts      = true     # default
tool_inputs  = true     # default
completions  = false    # default
tool_outputs = false    # default

[storage]
path           = "~/.config/tj/telemetry.duckdb"
retention_days = 90

Merge rules

Budget limits merge per-field: each agent inherits [defaults.budget] unless overridden. Setting daily_usd on one agent doesn’t reset session_usd to nothing.

Plan tier and framing

TokenJam knows two distinct budget concepts. Don’t conflate them.

  • [defaults.budget] / [agents.<id>.budget] — per-agent alert thresholds (daily_usd, session_usd) checked on every span.
  • [budget.<provider>] — per-provider config: your declared plan tier, and an optional cycle ceiling read by the budget-projection analyzer and by a budget_cap proxy policy.

tj init prompts for your plan tier and writes it to [budget.<provider>] plan. You can set it non-interactively with tj init --plan <tier>, or edit it by hand:

[budget.anthropic]
plan = "max_5x"    # api | pro | max_5x | max_20x | plus | team | enterprise | local
# usd = 200        # optional cycle ceiling: budget-projection and budget_cap policies

[budget.openai]
plan = "plus"      # api | plus | team | enterprise

The plan tier drives how dollar figures are rendered everywhere — the CLI, the REST API, and the web UI all read one shared framing rule rather than deciding on their own:

Plan tierHow figures are shown
API (api)Dollar costs verbatim.
Subscription (pro, max_5x, max_20x, plus, team, enterprise)Token-share framing — ”% of cycle” and “implied API value”, never a spend claim.
Local (local)Tokens only. No dollars.
UnknownDollars suppressed with a hint to re-run tj init --reconfigure.

This is why a Max-plan user sees quota-share language while an API user sees dollars: the plan tier, not the command, decides the framing.

Capture settings

Two of the four flags default on. prompts and tool_inputs are recorded unless you turn them off, because several analyzers go dark without them: cache-recommend and trim never fire, reuse never reaches its prompt-prefix mode, script and verbosity fall back to tool names alone, and tj optimize --validate has nothing to replay. Most of those are already skipped for a window an interactive coding agent dominates. If that is all you run, what turning the flags off actually costs you is tj optimize --validate, which has no prompt to replay without prompts, and the exact form of Shipped’s repeat-edit figure, which falls back to counting on path alone without tool_inputs. completions and tool_outputs default off, because completion text is the largest payload to store and no analyzer’s detection depends on it. One deliverable does: Reuse renders its cluster skeleton from the planning call’s completion text, so without completions the clusters and numbers still appear and the skeleton is replaced by a hint.

Everything captured stays in the local DuckDB on your machine.

FlagDefaultWhat it records
promptsonThe user/system prompt sent to the model.
tool_inputsonThe arguments of each tool call.
completionsoffThe model’s response text.
tool_outputsoffThe raw return value of each tool call.

When capture.* is false, only metadata (tokens, cost, latency, span structure) is stored.

Custom model rate overrides

TokenJam ships a packaged model rate table (tokenjam/pricing/models.toml, USD per million tokens) that prices most public models with zero configuration. These are model token rates only — the numbers used to turn token counts into a cost estimate. They are not a product plan.

Inspect the resolved table any time:

tj pricing list                    # every (provider, model) with input/output/cache rates
tj pricing list --model claude     # filter by substring
tj pricing list --json

The source column tells you whether each row came from the packaged table (packaged) or a local override (override).

The packaged table can’t know your situation — negotiated rates, open-weight models hosted elsewhere, or a self-hosted endpoint. Override rates locally, no PR and no package edit needed. There are two places to put an override, and two ways to key it.

Where overrides live (later wins):

  1. A [pricing] section in your main config (tj.toml / .tj/config.toml / ~/.config/tj/config.toml) — the project-local home.
  2. A standalone file at ~/.config/tj/pricing.toml, or any path in the TJ_PRICING_FILE environment variable.

The project-local [pricing] section wins over the standalone file, which wins over the packaged table.

Two ways to key a rate:

Provider-keyed corrects a rate for a specific provider and model:

[pricing.anthropic]
"claude-haiku-4-5" = { input_per_mtok = 0.80, output_per_mtok = 4.00, cache_read_per_mtok = 0.08, cache_write_per_mtok = 1.00 }

Model-keyed pins a rate to a bare model name regardless of which provider TokenJam inferred. Reach for this when a model’s provider resolves to unknown (open-weight or unattributed traffic). It lives under a reserved models section:

[pricing.models]
"llama-3.3-70b"    = { input_per_mtok = 0.59, output_per_mtok = 0.79 }
"claude-haiku-4-5" = { input_per_mtok = 0.50, output_per_mtok = 2.50 }  # your negotiated rate

In the standalone ~/.config/tj/pricing.toml, the reserved section is simply [models] and provider-keyed entries stay at the top level:

[models]
"llama-3.3-70b" = { input_per_mtok = 0.59, output_per_mtok = 0.79 }

[anthropic]
"claude-haiku-4-5" = { input_per_mtok = 0.50, output_per_mtok = 2.50 }

Only input_per_mtok and output_per_mtok are required; the cache rates default to 0.0. Dated variants (claude-haiku-4-5-20251001) are priced off the base-name entry automatically.

Lookup order (first match wins): model-keyed override, then provider-keyed override, then the packaged table, then a $0.50 input / $2.00 output flat default (logged once) when nothing matches.

The rate table is cached for the process lifetime, so restart the daemon (tj stop then tj serve) to pick up an edit.

Local API auth

tj serve’s read routes are unauthenticated by default, on the assumption that 127.0.0.1 is your own machine. To require a key on them:

[api]
host = "127.0.0.1"
port = 7391

[api.auth]
enabled = true
api_key = "a-secret-you-choose"

With enabled = true, every read route wants Authorization: Bearer <api_key>. Lens keeps working, because tj serve injects the key into the dashboard HTML it serves and the page sends it on every call.

That bootstrap is why this setting is not a way to expose tj serve beyond your own machine. The dashboard route is not itself behind the key, so anyone who can reach the port can load the page and read the key out of it. It raises the bar for a script pointed straight at /api/v1/*, and it is not a boundary. To reach the dashboard from elsewhere, tunnel to it or front it with something that authenticates, and leave the listener on loopback.

The ingest routes are separate and always take the ingest secret, and the write endpoints Lens calls carry a per-process token of their own that this setting never affects.

Storage

DuckDB is the only datastore. The file lives at [storage].path and is opened read-write by tj serve and read-only by the MCP server and CLI. retention_days controls automatic pruning of old spans.

Discovery order

tj looks for config in this order:

  1. --config <path> CLI flag
  2. TJ_CONFIG environment variable
  3. ./tj.config.toml (current directory)
  4. ~/.config/tj/config.toml (default, written by tj init)

The first match wins.

Verification

Run tj doctor to check that your config is well-formed and matches what the daemon is using:

tj doctor

doctor reports missing fields, invalid types, conflicting overrides, and whether the daemon picked up your most recent edits.