Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Turn on the dashboard

Add two lines to your config, or run one command with no credentials. The setting, the ports, the container, and the demo.

How-to guideLast updated

On this page

The dashboard is part of the binary, so turning it on is a setting rather than an install. This page covers the setting, the two ports it interacts with, the container, and the no-credentials path if you want to look before you commit.

See it first, with no configuration

tokentriage demo boots the proxy and the dashboard against a synthetic ledger, points the proxy at built-in mock upstreams, and drives a trickle of requests through the pricing pipeline so rows arrive while you watch. No API key is involved, and the temp directory is removed when you stop it.

$ tokentriage demo
demo: temp ledger at D:\tt-tmp\tokentriage-demo-35440665, removed on exit
tokentriage dev · mode: ledger
DEMO — synthetic data (watermarked)
proxy: http://127.0.0.1:4215   (point your app's base_url here)
dashboard: http://127.0.0.1:4216/ui/
ledger: D:\tt-tmp\tokentriage-demo-35440665\decisions.jsonl
next:
  $env:ANTHROPIC_BASE_URL = "http://127.0.0.1:4215"
  tokentriage wrap -- <your app>        (Anthropic clients; no env changes)
  tokentriage doctor                    (check the setup)

Three things in that capture are the machine’s rather than yours. It came from a source build, so a release prints its version where this says dev. It ran on Windows with a redirected temp directory, so your paths and your shell’s export line will differ. And the ports are the ones this run happened to get.

Its ledger is synthetic and every dollar tile carries a diagonal DEMO DATA watermark, so nothing you see there can be mistaken for your own traffic. It writes to a temp directory and does not touch a ledger you already have.

Turn it on in a config file

Two lines:

dashboard:
  enabled: true            # default: false

Then validate and start. Validation decodes the block strictly, so an unknown key under dashboard: is an error rather than a setting that quietly does nothing:

$ tokentriage validate --config tokentriage.yaml
mode:      ledger
listen:    127.0.0.1:8787   admin: 127.0.0.1:9090
upstreams: 1
  - openai       openai    https://api.openai.com
tiers:     1 (cheapest -> most capable)
  0. frontier   -> openai/gpt-4o-mini
rules:     0
OK: configuration is valid

The boot banner prints the dashboard URL when the admin listener binds:

$ tokentriage run --config tokentriage.yaml
tokentriage dev · mode: ledger
proxy: http://127.0.0.1:8787   (point your app's base_url here)
dashboard: http://127.0.0.1:9090/ui/
ledger: D:/tt-tmp/ttdocs/state/decisions.jsonl
next:
  $env:ANTHROPIC_BASE_URL = "http://127.0.0.1:8787"
  tokentriage wrap -- <your app>        (Anthropic clients; no env changes)
  tokentriage doctor                    (check the setup)
  tokentriage demo                      (no traffic yet? the whole journey on synthetic data)

The ledger path is the one this config named; yours is whatever ledger.decision_log.path says, defaulting to $HOME/.tokentriage/decisions.jsonl.

Give it its own index path

The dashboard keeps a SQLite read model derived from your ledger. Its path defaults to ~/.tokentriage/index/decisions.sqlite, which is shared by every instance that does not set one:

dashboard:
  enabled: true
  index:
    path: /var/lib/tokentriage/index/decisions.sqlite

Set it per instance. Two instances with different ledgers and one default index path will contend over the same file, and a large or unexpected index at that path can stall a boot while it is opened and caught up before the listener comes up. If a start hangs after replica identity resolved and before the banner, that is where it is.

The read model is derived and disposable — deleting it costs you nothing but the rebuild. The read model covers when to do that and what index rebuild does.

The two ports

The dashboard is served from the admin listener, never the data plane.

Default Serves
Data plane 127.0.0.1:8787 the proxy (server.listen)
Admin plane 127.0.0.1:9090 /ui/, /tt/api/v1, /health, /metrics (server.admin)

Both default to loopback. That default is what makes auth.mode: off valid out of the box: a non-loopback admin bind with authentication off is a validation error rather than a warning. Before you move the admin listener off loopback, read sign in to the dashboard and Operate’s hardening guide, which owns the network stance for the admin plane.

An admin-plane bind failure is not fatal. The proxy keeps serving and the banner says so:

dashboard unavailable: listen tcp 127.0.0.1:9090: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. — proxy traffic unaffected (run: tokentriage doctor)
tokentriage dev · mode: ledger
proxy: http://127.0.0.1:8787   (point your app's base_url here)

The sentence in the middle is the operating system’s, so Linux and macOS say “address already in use” where that Windows capture says something longer. What matters is the part after it, and the missing dashboard: line below.

A data-plane bind failure, by contrast, stops the process. The asymmetry is intentional: the proxy is the product, and losing the read plane should not take traffic down with it.

In a container

Two facts about the published image surprise people, and they compound.

The baked-in config has no dashboard: block, so the image serves no UI. Port 9090 is published and answers /health and /metrics, but /ui/ is not there until you mount a config that turns it on. Install TokenTriage covers the image itself and the /data volume it writes to.

A container config binds 0.0.0.0, and that changes which authentication modes are legal. A loopback bind inside a container is unreachable from outside it, so the admin listener must bind a routable address, and an unauthenticated dashboard on one is rejected at validation rather than warned about.

The repository’s Compose profiles do this for you. deploy/compose/compose.demo.yaml brings up a token-authenticated demo dashboard against an in-network mock upstream with no provider key on the machine, and its config is the worked example of the two facts above. The UI shell and /tt/api/v1/capabilities are served before authentication so the demo label is visible without a token; reading any data needs one.

Publishing 9090 to a host interface publishes the admin plane. Keep it on an internal network or behind your own proxy; production deployment owns the topology and hardening owns the network stance.

Everything under `dashboard:` and its default

Every field below is optional. The values are the ones a two-line block resolves to, read from GET /config/running on a running instance.

Key Default What it does
enabled false serve /ui/ and /tt/api/v1 on the admin listener
auth.mode off off, token or users
auth.session_ttl 12h how long a minted session lasts
state_dir ~/.tokentriage/dash users, token hashes, saved views, audit log, event ring
index.path ~/.tokentriage/index/decisions.sqlite the read model
stream.client_queue 1024 per-client bounded queue on the live stream
stream.max_clients 32 live-stream client cap
evidence.max_age 168h how fresh shadow evidence must be to satisfy the promote gate
query.deadline 10s per-query time budget
query.row_cap 1000 maximum rows a single query returns
query.max_concurrent 2 concurrent heavy queries
capture.request false store request bodies
capture.response false store response bodies
capture.ttl 720h how long captured bodies survive
capture.path ~/.tokentriage/content where captured bodies live
api_limits.read_per_min 600 management-API read budget per principal
api_limits.write_per_min 120 management-API write budget per principal
docs_public false serve the API reference without authentication
demo false synthetic watermarked mode; tokentriage demo sets it

index.enabled also decodes, and today it does not do what its name says: the read model is opened whenever the dashboard is composed, and setting it to false neither skips the index nor changes the capability the server advertises. Measured on a run --config instance with index.enabled: false, which still created the SQLite file and still reported the read model as available. Treat the read model as always on when the dashboard is on. This is filed.

What can go wrong

The banner prints no dashboard line. Either dashboard.enabled is false, or the admin listener could not bind. Both cases print a reason; tokentriage doctor reports the state without stopping anything.

Validation rejects auth.mode: off. The admin listener is not on loopback. That is the container case above, and the fix is to pick an authentication mode rather than to move the bind back.

The process starts but /ui/ shows “UI not built”. The binary was built from source without the web assets. Release binaries embed them; a source build needs make ui before go build.

A start hangs before the banner. Look at the index path, as above.