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

TIER reports recorded outcome points per dollar of captured AI use. It associates spend and finished work observed in the same period; it does not establish that the AI spending caused that work.

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.

Current access and retention: Real servers always require an issued key. Root reads everyone, node keys read their dated branch and personal keys read their holder. Named access follows names_visibility; personal keys retain self access. Named cards audit before construction and need no declaration or positive verified spend. Shared/static credentials and server reporting modes are retired. Team, division and company reports expand to their people; name-free monthly comparisons retain the k floor. Writable servers run raw retention at startup and daily (90 days by default, 60–365 days); pruning is permanent. Read-only servers and the synthetic demo do not initiate pruning. See identity access for current setup and trust limits.

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. Real servers always require issued keys. Root sees everyone, node keys see their dated branch and personal keys see self. Team, division and company reports expand to their people. Named reads audit before construction; the person can inspect their own view log. Named branch access follows names_visibility; personal keys retain self access. Declarations and static/shared credentials are removed. Name-free monthly comparisons use the k floor (default five, minimum three), complementary suppression. The synthetic demo uses invented data. See identity access for setup and legal and privacy for policy considerations.

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@v0.7.0, once published), 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. 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. Named reports use issued-key scope and can read live date windows. Name-free comparisons read one closed UTC calendar month, the latest by default on /api/v1/scores; pick another with ?period=YYYY-MM. The synthetic demo defaults to 90 days in the API and 30 days in the dashboard.

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. Local cost reports and the synthetic demo break out these buckets; coaching cards use their own attribution fields. Name-free monthly comparisons do not publish this breakdown. 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. Starting in the right worktree fixes future sessions. A later qualifying PR can also link matching previously unlinked records to its issue in mutable months, including records labelled unattributed:branch-without-issue; closed-month inputs stay frozen. 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

GitHub pull requests count only when merged and when they 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. With --push-capture, qualifying direct commits to the default branch also count at weight 0.5, once per issue per UTC day. Other trackers can send outcomes through the outcomes API.

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 live historical score can still move. Monthly closes freeze activity and census; later quality events, aliases, backfills and repricing do not revise them. Only the paid layer can be revised, and erasure/retention pooling removes identities while conserving totals. The numerator is Σ(weight × quality) with corrective-work scoring off (the default).

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. Name-free monthly comparisons apply a privacy floor. A closed month's census counts rostered, non-bot people with both positive qualifying spend and a positive outcome count on the dated path; a direct push can supply the outcome. Qualifying spend comes from bound uploads, --watch-as server watches or root-mapped vendor-attributed events; shared, legacy and manual spend cannot establish a counted person. Aliases do not lend another subject's evidence. A person counts once across model rows and paths.

Comparisons use the larger of the close's k and the reading server's k (default five, minimum three). Small groups can fold into Other; complementary suppression can withhold siblings or parents to prevent subtraction. One tree decision covers the additive figures; there is no separate spend/points/paid contributor floor. Paid and realtime amounts do not decide which groups publish. A shown group also needs at least two members. See manager-faq.md. Named branch reports and coaching cards instead follow issued-key access and names_visibility; personal keys retain their holder's own figures.

For local use, tierd score --repo <path> shows captured cost and its issue-attributed share; add --outcomes for personal TIER. The dashboard demo uses invented data. See quickstart for real-server setup.

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. For a live named report, 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. For a name-free comparison the window is a closed 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 an organization server; name-free comparisons require a closed month and enough counted contributors. Start in the current UTC month with a fresh database. Backfill and ship only that month so startup cannot freeze older outcomes before their matching spend and roster arrive. Writable serve closes due months at startup and every minute, 24 hours after month end. Finish roster import, shipping and identity mapping before then; later uploads cannot revise frozen costs or census. This sequence cannot bootstrap complete historical reports; use offline score --outcomes for past work. For branch reports, use the first full month after the roster takes effect. 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> --since "$(date -u +%Y-%m-01)" 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 and issue bound keys. Start tierd serve. Bootstrap root on the host and import your developer into the team map. Issue a personal or upload key for that developer. This walkthrough starts no watcher: repeat step 6 for new activity before the month closes. For a separate live watcher, see bound watcher setup.

  6. Load this month's spend. In a second terminal, run tierd ship --server http://127.0.0.1:8080 --repo <path> --developer <login> --bound-token @<key-file> --since "$(date -u +%Y-%m-01)". Use the same current-month start as step 4 so outcomes are not loaded from months that could close before their matching spend arrives. --server is the address the server from step 5 listens on, and <path> is your checkout folder. <key-file> holds the issued personal or upload key. ship reads Claude Code sessions only, unless you add --codex-rollout for Codex, --opencode for Opencode or --muse for Muse Code. 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. Check identity. Use the same developer identifier for a separate watcher's --watch-as, ship --developer and the issued key's subject, preferably your GitHub login from step 4. Bound uploads use the key's subject; the identity is not necessarily your OS login. If historical spend uses a different identifier, root can join it with an alias after checking that both identifiers belong to the same person. See README § Identity mapping. An alias corrects live reads and future closes; it does not rewrite closed months or turn legacy spend into qualifying bound evidence. Check before you join historical records. If that historical identifier is your current OS login ($USER), inspect it with

    printf 'Authorization: Bearer %s\n' "$(cut -f4 ~/.tier/root-credential.tsv)" |
      curl --fail-with-body -H @- "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

    printf 'Authorization: Bearer %s\n' "$(cut -f4 ~/.tier/root-credential.tsv)" |
      curl --fail-with-body -H @- -X DELETE "http://127.0.0.1:8080/api/v1/developer_alias/$USER"
    

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

    printf 'Authorization: Bearer %s\n' "$(cut -f4 ~/.tier/root-credential.tsv)" |
      curl -f -H @- -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 the historical identifier checked above and <github-login> is your GitHub login. Use the issued root key for these setup requests, saved in ~/.tier/root-credential.tsv by the root bootstrap. 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 in live reads and future closes; closed monthly figures stay frozen. The team-server form is in README § Identity mapping.

  8. Open the dashboard. Open http://127.0.0.1:8080 and sign in with your issued key. To read the latest closed monthly comparison as JSON, use root:

    printf 'Authorization: Bearer %s\n' "$(cut -f4 ~/.tier/root-credential.tsv)" |
      curl -f -H @- http://127.0.0.1:8080/api/v1/scores
    

    A monthly comparison requires a completed close; without one it returns 404.

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 reprice raw history; it does not revise monthly closes. 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. Names containing nemotron-ultra or deepseek-r1-full select the large self-hosted class; names containing phi-4-mini, embeddings or reranker select the small class. Otherwise, a parameter count in the name (such as 70b) selects a self-hosted size-class rate. If neither rule matches, it gets the flat self-hosted-medium rate of $0.50 per million tokens. For either guess path, 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. Bound uploads use the issued key's subject; --watch-as sets the Claude Code watcher's subject. Other local capture can use the computer login or a configured developer identifier. Outcomes use the PR author's GitHub login. Root can map identifiers belonging to one person through the alias API so cost and outcomes join in live reads and future closes. Monthly closes retain their frozen identities, activity and census; aliases do not rewrite them. The monthly quality block reports whole-company cost-only and outcome-only counts when that population is published; it does not list unjoined names. Named reports remain scoped by the holder's issued key.

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 repository and issue in a 14-day window ending at the latest merge that reuses that issue among the outcomes being scored. Reused issues share that window, so an older merge can be flagged too. 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

Use your first settled window as your baseline, and read later windows against it. The rubric deliberately defines no absolute good or bad band.

GET /api/v1/scores?period=YYYY-MM returns a monthly comparison manifest. Read disclosures.horizon.window_predates_cost_capture and disclosures.horizon.cost_coverage_safe_since for the frozen cost horizon; choose a closed UTC month beginning at or after the safe date. An unknown or unrecorded horizon does not establish complete capture. disclosures.partial_input and origin_evidence disclose retention and capture loss.

Shown rows are in comparison.Nodes and comparison.Other, with figures only when Disposition is shown; comparison_withheld signals that some figures were withheld or folded. Hidden rows are omitted from the public comparison. Monthly comparison routes reject arbitrary since/until windows. A month without a close returns 404. Named reports and coaching cards have their own response shapes; the synthetic demo retains its data_quality block.

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 introducing named or group reporting; it covers works-council consultation and data protection impact assessments (DPIAs).

On real data, root reads everyone and node keys read their dated branch when names_visibility permits; personal keys retain their own figures and card. Each card read is audited before construction. Shared/static credentials and tokenless real-server access are retired. Host/database owners remain trusted with retained data. The synthetic demo exposes invented rows only.

Interfaces