TIER docs

Understanding TIER

TIER links recorded AI spend to merged software changes. This guide explains the number in plain language, shows where its inputs come from, and takes you from installation to a first report.

TIER (Token Impact & Efficiency Ratio) is explained here in three layers. Each layer stands on its own, so read only as far as you need.

  1. The short answer — what TIER is and why a team would want it, in plain words.
  2. How the meter works — where the numbers come from. Technical terms are defined where they first appear.
  3. The full method, page by page — how to run it on real data, then each rule in brief with a link to the page that defines it.

Layer 1 — The short answer

The bill from an AI coding tool tells you how much you spent. It does not tell you what the spending produced. TIER measures that second thing: how much finished work you got for each dollar of AI use.

The number is outcome points per $1,000. Higher means more finished work for the same money.

Compare a team with its own history, not with other teams. The kind of work a team takes on moves the number, and the number cannot tell the kind of work apart from the skill of the people doing it.

What it is for. TIER is built for coaching, not for evaluating developers. Per-person figures are not accurate enough to judge an individual. The positioning is grade the work, not the worker, by policy, not by mechanism. Points follow the size of the finished work, not how hard it was: someone who spends a lot of AI effort on a small fix to a hard problem scores lower than someone making routine changes of the same size. A low number describes the work, not the developer's ability.

v0.7.0 access and retention: Real-data developer mode requires a nonempty admin API token, a named --admin-viewer operator and at least one active permitted viewer credential: a legacy subject-bound viewer credential or an eligible personal/node key for a current mapped person. Coaching cards use positive verified spend and either a configured subject viewer credential or a live declared-viewer check; each view is logged before construction, and logging failure denies the read. Shared reads remain name-free. Writable developer mode starts retention at startup and daily; once armed, it continues in team/division mode. The window defaults to 90 days and allows 60–365 days; pruning is permanent. Read-only serving and the synthetic demo do not initiate age-prunes. Team/division reports use sealed calendar months and the k floor. The synthetic tierd demo keeps its startup exemption, invents its own data in a private temporary database and cannot load yours. The separate real-data per-issue report remains future work. See developer access and upload identity and the quickstart for setup, trust limits and sealing.

Solo users can read their own local Claude Code, Codex CLI, Opencode (Z.ai coding plan) and Muse Code cost table and issue-attributed spend share with tierd score. tierd score --outcomes joins that spend to only the GitHub token owner's merged PRs for a personal TIER; it persists no report data and shows no team band. tierd demo provides the dashboard on invented data.

Who sees what. TIER has three reporting modes. Its server will not start until someone picks one, because there is no default. Whichever mode you pick, read legal-and-privacy.md first. In some countries, Germany, France and the Netherlands among them, even reporting by group can require consulting employee representatives.

One caveat. The number is an estimate built from real records, not an exact reading. Spend is recorded when it happens, but work only counts when it merges, which comes later. So a period that ended very recently reads too low, and a period that starts before TIER began recording spend reads too high. Layer 2 explains both.

Historical self-measurement; original inputs and run settings are not publicly reproducible. The maintainer reported 221 points per $1,000 across 152 merged PRs, trailing 30 days to 2026-07-21, and 72.1% of spend unlinked. It was computed from backfilled history; no quality events were recorded for it, so it is size-weighted only. It illustrates the dashboard, not independent validation. Unlinked spend remains in the denominator.

The maintainer has exercised TIER's CI and revert rules in manual test runs; the project's automated webhook tests exercise the same rules. The inspected historical 221 figure has no recorded quality-event receipt.

Claude costs are derived from deduplicated Claude Code usage logs. Their completeness against independent provider accounting remains unvalidated, including for the subscription routes used here. Missing usage would understate those costs. Codex costs use logged cumulative counters; those counters do not independently establish complete capture. If the logs undercount, costs are understated and TIER scores read high. If Claude's logs undercount more than Codex's, Claude looks relatively cheaper.

Try it. Install TIER with Go 1.27.3 or newer (go install github.com/tiermetric/tier/cmd/tierd@latest), or download a release; quickstart.md §1 covers both. Then run tierd demo and open http://127.0.0.1:8080 in a browser. The demo fills the dashboard with invented developers and spend, and picks the developer mode for you. To measure your own work, see From install to a first real score.

Next: How the meter works.


Layer 2 — How the meter works

The score is a ratio:

TIER = outcome points ÷ (list-price cost of the AI use / 1,000)

Read it as "outcome points per $1,000." The list-price cost is what the AI use would cost at the vendors' published per-token prices, not what you paid. TIER computes the ratio per developer. A team's or division's score is its members' points added together, divided by their costs added together. It is not an average of individual scores. In developer mode the API covers the last 90 days and the dashboard the last 30 by default, and both can be changed. In team and division mode both read one whole sealed calendar month, the latest by default; pick another in the dashboard's month picker or with ?period=YYYY-MM.

Both halves come from records that already exist: AI session logs, and merged pull requests. Neither comes from surveys or timesheets. The main human judgment in the formula is the size label on a pull request, covered below (a revert's reason is also read from keywords in its human-written message).

The bottom half: dollars

TIER reads the session files your AI coding tool already writes to disk. For Claude Code, these are JSONL files (one JSON record per line) under ~/.claude/projects/. Each AI reply in them records its token counts and the model's name. A token is a fragment of text a few characters long, and it is the unit AI vendors bill by. Codex CLI, Opencode and Meta's Muse Code have their own local readers, each switched on by a flag. Other tools can be captured through an optional proxy. README § Capturing tokens lists which tools are supported and how well tested each one is.

Tokens become dollars through a reference price table built into the program. It holds the vendors' published prices in US dollars per million tokens, under one version number for the whole table. That choice is deliberate. An invoice bundles discounts, credits and negotiated rates, so identical work would cost different amounts at different discount levels. A list price makes the same work cost the same. The table changes only when you install a new TIER release or supply your own table. On a flat-rate subscription, the figure is what your usage would have cost at list price, not what you paid. If you record what you actually paid, TIER reports the ratio between the two as a separate number. See pricing-philosophy.md for why, and reference-price-table.md for the table itself.

Spend is linked to a piece of work through the branch the AI session was on. TIER looks for the issue number (the ticket number in your tracker) in the branch name, such as feature/42-login or fix/TIER-99-crash (only with TIER configured as a tracker key, for example --tracker-keys TIER), or in a commit on that branch made around the same time. Spend it cannot link lands in an unattributed bucket. It stays in the bottom half of the fraction, so it still lowers the number. It is reported as its own line alongside the score, and withheld with the totals when the anonymity rule described below hides them. The bucket's label says why the link failed:

Bucket What happened
unattributed:main recorded on main or master, which carry no issue number
unattributed:branch-without-issue recorded on a named branch with no issue number in its name
unattributed:detached-head no branch was recorded (a detached HEAD, or none at all)
unattributed from a source that never sees a branch: the proxy, or a vendor's billing feed

unattributed:main can have a technical cause rather than a missing ticket. Here is one. A developer starts an AI session in the main copy of the repository, then has it work in a second working copy (a git "worktree") checked out on a feature branch. In one installation's session files, examined on 2026-09-26, Claude Code recorded the branch where the session started and did not record a later cd into another worktree. Other installations and Claude Code versions have not been checked. Helper agents that Claude Code runs in worktrees of their own can land on main the same way; Attribution in Layer 3 explains how.

The remedy is to start the session inside the worktree, on a branch whose name carries the issue number. A worktree on a branch without one moves the spend to unattributed:branch-without-issue instead. The fix applies to future sessions only: spend already recorded keeps the label it was given. tierd doctor --repo . reports how much of your spend it could link, so you can check whether the fix worked.

The top half: outcome points

Only merged pull requests count, and only those that name an issue, either in the branch name or in a line such as closes #42 in the description. A merged pull request with no issue reference is skipped, because its work cannot be joined to its spend. "Merged" means the merge was recorded (by GitHub, or through TIER's outcomes API for other trackers). It does not mean deployed. Unmerged work adds cost and no points.

Each merged pull request earns points from its size label, on a fixed scale of five sizes:

Label Weight
xs 0.5
s 1
m 3
l 5
xl 8

A person, or your own automation, puts the label on the pull request in GitHub. A pull request with no recognised label is sized from its diff instead, on the same five weights.

The weights are locked. You can rename the labels in the config file, but TIER refuses any weight not on this scale. The lock keeps your own history comparable: a weight change would silently rescale every earlier period. Fixed weights do not establish shared calibration; cross-installation comparison is not supported; compare a team with its own history.

Each pull request's weight is then multiplied by a quality multiplier between 0.1 and 1.0 when it comes from GitHub (an outcome sent through the outcomes API carries the quality its sender set, from 0 to 1). It is 1.0 unless something goes wrong after the merge and GitHub's webhook reports it; without the webhook nothing lowers it after the merge. A failing GitHub Actions run (the automated build and tests) on the merge commit within 48 hours lowers it to 0.7, unless a re-run of the same check soon passes. A revert within 60 days lowers it to 0.8 when the revert was a business decision, or to 0.1 when it was a code problem. TIER tells the two apart from keywords in the revert's human-written commit message, and treats a revert as a code problem unless the message gives only business reasons. The worst of these wins. Other PR-head SHAs are not scored. In v0.6.1 (#1230), only push-triggered runs on the default branch with a recorded merge SHA change quality; matched scheduled and dispatched runs are audit-only, and merge_group runs are dropped. The matcher still enforces no lower event-time bound; a matching push-triggered failure timestamped before merge can count. A revert can arrive weeks after the merge, so a past period's score can still move. The numerator is Σ(weight × quality).

Three things that will bite you

1. Task mix moves the number, and the number cannot separate it from skill. A quarter spent building new features and a quarter spent chasing production bugs will not score the same. The number cannot say how much of the gap comes from the work and how much from the people. Compare a team to its own history, not to another team's number.

2. Small groups are anonymized, by design. In the team and division modes, TIER groups developers by the team (or division) the operator assigns each one to. A group with fewer counted people than the anonymity floor (five by default, never below three) is folded into one catch-all row named other, along with developers who have no team. Since #856 the count is of people, not IDs: an ID counts only if it is on the roster during the window (after its aliases are joined, see Attribution), is not a bot, and has activity a collector, the proxy or a pull request captured in the window. Each figure also needs its own floor: a group's cost is shown only if enough counted people in it have captured spend, its points only if enough have merged work, and its paid spend only if enough have paid spend; the fidelity split (coverage_pct) needs enough people behind each part of it. manager-faq.md has the full rule and how to bring uncounted people back. Folding drops nothing: the folded spend and points stay in other and in the totals. If other is itself below the floor, it is withheld along with the totals, because subtracting the named rows from the totals would reveal it, and TIER says so rather than showing an empty page. If you are evaluating TIER on your own, use tierd score --repo <path> for your own Claude Code cost table and the issue-attributed share of that spend; it gives no TIER number on your own real data. Real-data developer mode requires #1370 subject-bound credentials; team/division reports withhold a cohort of one and publish only sealed calendar months. Follow quickstart.md §7 for sealing and the k floor.

3. The two halves must cover the same stretch of work. Both halves are counted over the same calendar dates. TIER does not pair each merged pull request with the exact spend that produced it. Spend is recorded when it happens, and points arrive later, at merge. Two things follow:

On one real multi-repo installation, measured on 2026-07-26, the API's default 90-day window gave a number about twice as high as the trailing 30 days, which roughly matched the period whose spend had been recorded. TIER warns about this second case, with a dashboard banner and a tierd doctor check, but it still computes the number. There is no fixed safe length. In developer mode, the rule of thumb is a window several times longer than your usual time from first AI use to merge, starting after the cost horizon. In team and division mode the window is a sealed month: read months that start on or after the cost horizon; reading an earlier one, the dashboard banner names the first such month. See interpreting-the-number.md.

Next: The full method, page by page. Before you rely on any score, read interpreting-the-number.md, which covers windows, skew and when a score is too early to trust.


Layer 3 — The full method, page by page

Layer 2 gave the formula, the weights and the multiplier values. For each step, this layer says what the program does, gives each rule's key numbers in brief and links the page that owns it, so each rule has one copy to keep correct. For a code-grounded account of what the tool does and does not do today, read how-it-works.md alongside it.

From install to a first real score

Solo users can stop after step 3 for their own local cost table and the issue-attributed share of that spend. tierd score --repo . --outcomes also computes a local personal TIER score by joining that spend to the GitHub token owner’s merged PRs; it persists nothing and shows no team band. Run tierd demo for the dashboard on invented data. The remaining steps load a team/division server; reports also require a sealed month and enough contributors. See the quickstart for the full setup.

  1. Install. Go 1.27.3 or newer, or a release download: quickstart.md §1.

  2. Check capture. In your repository's folder, run tierd doctor --repo .. It reports what TIER can read on this machine and how much of your spend it could link to an issue.

  3. See the cost side. tierd score --repo . prints cost per issue from your local session files. It needs no server and no GitHub access. It shows cost only by default; add --outcomes for personal TIER. In v0.7.0 it reads Claude Code, Codex CLI, Opencode (Z.ai coding plan) and Muse Code logs by default. Server capture in steps 5 and 6 still needs the collector flags named in step 6.

  4. Load merged work. tierd backfill --repo <owner>/<name> --token @<file> reads your merged pull requests from GitHub. <owner>/<name> is the repository as it appears in its GitHub URL. <file> is a file holding a GitHub token that can read the repository's pull requests; README § What you need before you start shows how to create one.

  5. Serve. tierd serve --aggregation team --watch-repo <path>, where <path> is the folder of your local checkout. Leave it running. No token is needed because none was set; without one, the server accepts connections only from this machine, unless something forwards that port, such as an SSH tunnel or a reverse proxy.

  6. Load past spend. In a second terminal, run tierd ship --server http://127.0.0.1:8080 --repo <path>. --watch-repo only picks up new activity, so without this step the score divides up to 90 days of merged work by only the spend in session files written to after step 5, and reads too high. ship sends the last 90 days of session files still on your disk. --server is the address the server from step 5 listens on, and <path> is the same checkout folder. It needs no --api-token, because the server has none set. ship reads Claude Code sessions only, unless you add --codex-rollout for Codex, --opencode for Opencode or --muse for Muse Code; add the same flag to the step 5 serve command. Without it, a run on a repository where you used only one of those tools finds nothing and stops with an error that blames the --repo path.

  7. Join your two names. Spend from steps 5 and 6 is recorded under your computer's login name, which echo $USER prints. Merged work from step 4 is recorded under your GitHub login, which your GitHub profile page shows, or which gh api user -q .login prints if you have the GitHub CLI. If the two are the same, skip this step. If they differ, you appear twice in the scores: once with spend and no merged work, once with merged work and no spend.

    Check before you join them. Inspect your computer login with curl --fail-with-body "http://127.0.0.1:8080/api/v1/developer/$USER/export". The sum of cost_micro in token_events must be above 0, and outcomes must be null or empty (no merged work). If you have jq, add | jq '{spend: ([.token_events[]?.cost_micro] | add), outcomes: (.outcomes // [] | length)}' to the command to print both. If the export fails, spend is absent, or any outcome is present, stop here. Merged work may belong to somebody whose GitHub login matches your computer login. The join moves both spend and merged work, so it would put that person's work into your score. The check covers only merged work loaded so far. After the join, the same export also lists your own merged work, so before any later backfill, check that every pull request in its outcomes is yours. If one is not, undo the join with curl --fail-with-body -X DELETE "http://127.0.0.1:8080/api/v1/developer_alias/$USER".

    If the check passes, tell the server they are one person:

    curl -f -X POST http://127.0.0.1:8080/api/v1/developer_alias \
      -H "Content-Type: application/json" \
      -d '{"alias":"<login-name>","canonical":"<github-login>"}'
    

    <login-name> is what echo $USER printed and <github-login> is your GitHub login. No token header is needed, because the server has none set. The server answers 201 Created with an empty body, so the command prints nothing when it works. The join also applies to spend and work already loaded. The team-server form is in README § Identity mapping.

  8. Open the dashboard. Open http://127.0.0.1:8080, or read the same scores as JSON with curl -f http://127.0.0.1:8080/api/v1/scores.

Everything is stored in one SQLite database file on the machine running the server, ~/.tier/tier.db by default. For a shared team server, with tokens, the GitHub webhook, and spend sent in from each developer's laptop, see README § Run it for a team.

Capture — how spend gets measured

TIER is JSONL-first. The primary path reads the session files the tool already writes locally. On that path no proxy sits in front of your model provider and no request is intercepted. An optional reverse proxy exists for tools that write no usable local log, and open-weights and self-hosted models have their own capture path. On a team, each developer runs tierd ship on their own machine. It reads the local session files and sends the cost records to the team's server; this is the laptop-shipper setup.

Pricing — how tokens become dollars

ComputeCost, the function that turns an event's token counts into dollars, prices each event against the table in internal/store/prices.yaml. That file is compiled into the program and is the single source of truth. It is versioned as one unit: any price change bumps the table's single version number. Each cost is fixed when it is recorded and stamped with the version that priced it, so upgrading TIER does not reprice history. tierd reprice is the explicit, audited way to do that, and a window whose costs span several versions is flagged.

Before lookup, a model name is lowercased and a trailing date stamp (plus -preview or -latest) is removed. Dated snapshots of one model therefore share a price, while differently named versions keep their own. A model not in the table is priced by a guess. If its name carries a parameter count (such as 70b), it gets a self-hosted size-class rate. Otherwise it gets the flat self-hosted-medium rate of $0.50 per million tokens. Either way the server writes a one-time warning per model name to its log and counts the event in the tier_unknown_model_events_total metric. Open-weights models are priced by model and by the host serving them, because the cost belongs to the host, not the weights.

Attribution — how spend is tied to work

Spend is joined to an issue through the issue reference carried by the session's branch. Merged work is joined through the pull request's branch name or a closes #N line in its body. Cost is attributed to your shipping identity: the developer name stamped on your cost records, which is your computer's login name unless you override it. Outcomes are attributed to the PR author's GitHub login. When those differ, they must be mapped to one developer (an alias, recorded through the API), or cost and outcomes land in separate rows and cannot be paired. The mapping is applied when scores are read, so it corrects past windows too. Developers who appear on only one side are reported in the response's unjoined_developers note, by name in developer mode and as a count otherwise. The anonymity floor counts the IDs mapped to one person once, and only if that person also meets the rest of the rule in Three things that will bite you (item 2), so load the alias map before relying on it.

Claude Code can run helper agents in worktrees of their own, on branch names it makes up (worktree-agent- followed by a code). Those names carry no issue number, so TIER uses the last branch a person named earlier in the same session file. If that branch was main, the helper's spend lands on main too.

Scoring — how outcomes become points

The canonical rubric defines the fixed {0.5, 1, 3, 5, 8} size scale, the label taxonomy, the fallback for a pull request with no recognised label, and the quality multiplier derived from quality events. The fallback sizes a pull request from its diff: lines changed plus ten for each file changed, bucketed onto the same five weights.

Two checks decide whether a score has enough behind it to read. A row is only treated as sound once it has at least 3 outcomes and at least $5 of list-price spend; below that it is still shown, marked as below the evidence floor. A merged pull request is flagged when its developer recorded fewer than 1,000 tokens against its issue in the 14 days before the merge: its points still count, but its developer's row is marked as not sound, because TIER cannot tell whether the work used AI it never saw, was badly attributed, or was genuinely done without AI. Direct commits to the main branch, with no pull request, count only if the operator turns on --push-capture, and then at the smallest weight, 0.5.

Reading the result

There is no good or bad value. The rubric deliberately defines no absolute band, so your first settled window is your baseline, and later windows are read against it.

Two notes in the data_quality block of the GET /api/v1/scores response say when a score needs care, and the dashboard shows each as a notice. window_predates_cost_capture is true when the window starts before the cost horizon. In developer mode, start it at cost_coverage_safe_since instead (the dashboard's From date, or ?since= on the API). In team and division mode the server reports whole sealed months and refuses ?since=, so there is no cost_coverage_safe_since; pick a later sealed month that begins at or after the cost horizon, in the dashboard's month picker or with ?period=YYYY-MM on the API. A month that is not sealed yet answers 404; when the month will become sealable, the answer gives the time it becomes eligible (sealable_at), and a month before the earliest sealable one, or one the sealer skipped, answers without it. kanon_suppressed says the anonymity floor withheld the other row and the totals.

The limits, stated plainly

TIER can be gamed. The known ways are documented: label inflation, splitting pull requests, picking cheap work, and several failure modes that are structural rather than deliberate. Read adversarial-analysis.md before the score is used anywhere it could affect how people are treated.

Measuring developers individually is regulated in some jurisdictions. Two separate kinds of rule apply. Works-council co-determination rules, such as Germany's §87 BetrVG, can require the works council's agreement before a monitoring tool is introduced. GDPR Article 22 restricts decisions about a person made solely by automated processing. Read legal-and-privacy.md before you pick a reporting mode, whichever you pick. developer aggregation carries the heaviest obligations, but that page also lists works-council consultation and a data protection impact assessment (DPIA) for team mode in several countries.

In developer mode (subject-bound credentials required on real data), anyone who can read the server sees every invented developer's row. That means anyone who holds one of its tokens (the administrator token or the read-only viewer token), or, when no token is set, anyone on the same computer, plus anyone reaching it through something that forwards its port, such as an SSH tunnel or a reverse proxy.

Interfaces