> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beacon.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# beacon token-usage

> Report token usage and attribution from Beacon runtime logs

## Command overview

`beacon token-usage` reads Beacon runtime events from the JSONL log and summarizes token usage, cost, users, models, sessions, runs, harnesses, and repositories.

```bash title="Command syntax" theme={null}
beacon token-usage [flags]
```

Use this command when you need a local report before opening the dashboard, sending diagnostics, or validating token attribution for a specific agent session.

<Note>
  In Beacon `v0.0.70`, token reporting moved from `beacon endpoint tokens` to the top-level `beacon token-usage` command. Update scripts, CI jobs, and runbooks that still invoke the old endpoint subcommand.
</Note>

## How token attribution works

Beacon normalizes token telemetry from every runtime into one canonical [`gen_ai.usage`](/telemetry-schema/normalization#usage-metadata) representation, then aggregates it locally from the runtime JSONL log. There are no per-harness token fields, so a report over mixed runtimes uses the same vocabulary throughout.

A few rules shape how the numbers read:

* **Counts are kept disjoint.** `input_tokens`, `output_tokens`, `cache_read`, and `cache_creation` do not overlap, so a total is `input + output + cache_read + cache_creation` without double-counting. Codex reports input inclusive of both cache reads and cache writes, so Beacon subtracts both before storing uncached input.
* **Cost is runtime-reported only.** `cost_usd` reflects what the runtime emits. Beacon never derives cost from local pricing tables, so cost appears only for runtimes that report it.
* **Attribution depends on the metadata a runtime attaches.** Model, harness, repository, and run grouping work whenever those fields are present. Per-session grouping (`--session`, the session step tree) requires a session or conversation id on the usage signal. Per-user grouping uses the event user directly or a matching session context event from a local hook.
* **Duplicate channels are de-duplicated.** When a runtime emits the same usage on more than one signal (Claude Code reports it on both logs and metrics), Beacon counts it once.

### Per-runtime coverage

Token coverage is not uniform, and the reason is almost always the runtime rather tha Beacon: most agent runtimes do not report usage on the surface Beacon collects from. A turn with no token attribution is a turn whose runtime never reported any.

**Collected and verified against captured payloads:**

| Runtime          | Tokens                                               | Cost                          | Per-session              | Per-user                                |
| ---------------- | ---------------------------------------------------- | ----------------------------- | ------------------------ | --------------------------------------- |
| Claude Code      | input, output, cache read, cache creation, reasoning | Reported (`cost_usd`)         | Yes                      | When local user context is present      |
| Codex CLI        | input, output, cache read, cache creation, reasoning | Not emitted by Codex          | Yes, from the turn trace | Yes, from matching SessionStart context |
| Codex Desktop    | input, output, cache read, cache creation, reasoning | Not emitted by Codex          | Yes, from the turn trace | When local user context is present      |
| Claude Cowork    | input, output, cache read, cache creation            | Reported when Cowork emits it | Yes                      | When the OTLP stream carries it         |
| Cline            | input, output, cache read, cache creation            | Reported (`cost_usd`)         | Yes, per task            | Yes                                     |
| OpenCode         | input, output, cache read, cache creation, reasoning | Reported (`cost_usd`)         | Yes                      | Yes                                     |
| Pi               | input, output, cache read, cache creation, reasoning | Reported (`cost_usd`)         | Yes                      | Yes                                     |
| Oh My Pi         | input, output, cache read, cache creation, reasoning | Reported (`cost_usd`)         | Yes                      | Yes                                     |
| Prime Agent      | input, output, cache read, cache creation, reasoning | Reported (`cost_usd`)         | Yes                      | Yes                                     |
| fx (Vercel Labs) | input, output, cache read, cache creation            | Reported (`cost_usd`)         | Yes                      | Yes                                     |
| OpenClaw Gateway | input, output, cache read, cache creation            | Not reported on the hook      | Yes, per model response  | Yes                                     |

OpenClaw reports usage on its `llm_output` hook, which is a *conversation* hook -- OpenClaw withholds those from every non-bundled plugin until `plugins.entries.beacon-endpoint.hooks.allowConversationAccess` is set in its config. An OpenClaw endpoint collecting prompts, tools, commands and files while reporting no tokens at all is almost always that key, and `beacon endpoint hooks status --harness openclaw` says so. OpenClaw does compute a per-turn price, from an operator-configured cost table rather than from a provider report, so Beacon does not record it as a reported cost. See [OpenClaw Gateway](/runtimes/openclaw-gateway).

Cline reports usage once per task rather than per model call, because Beacon's rollups sum `gen_ai.usage` across events and reporting at both granularities would count every token twice. fx reports cumulative totals, which Beacon differences into per-turn deltas for the same reason.

**Collected through the generic OpenTelemetry path, not yet verified against captured payloads:**

| Runtime            | Status                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Gemini CLI         | Usage is captured when the runtime emits the OTel GenAI semconv names (`gen_ai.usage.*` attributes, or a `gen_ai.client.token.usage` metric split by `token_type`). The generic path is tested; these runtimes' own payloads have not been captured and pinned.                                                                                                                                                                                                      |
| GitHub Copilot CLI | As above                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| VS Code Copilot    | As above                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| Factory Droid      | As above                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| Grok Bot           | Reaches Beacon only through Cursor's server-side OpenTelemetry export, so usage arrives only if that export carries the semconv names                                                                                                                                                                                                                                                                                                                                |
| goose              | goose's `chat` spans carry the full `gen_ai.usage.*` set, cache reads and writes included, under the exact semconv names -- that much is read out of the runtime rather than hoped for. What is conditional is whether it arrives: usage reaches Beacon only over OTLP, so a goose endpoint with hooks installed and `beacon endpoint install --harness goose` never run is correctly silent rather than faulty. goose reports no cost. See [goose](/runtimes/goose) |

This table records what Beacon is built to read, not what your machine actually produced. A runtime can be listed here and still contribute nothing, because it was never run, or because its build reports no usage.

## Context occupancy

Some runtimes report how full the model's context window was without reporting what a turn cost. That is a different measurement from spend, and it is recorded in a different place: `gen_ai.context`, holding `used_tokens` and `limit_tokens`.

The distinction is not cosmetic. Everything in `gen_ai.usage` is **additive** -- every total on this page sums it. Occupancy is a level at one moment: a session whose window was 92% full ten times did not spend ten windows. So context is never summed, never added to a total, and never counted as a runtime reporting its spend. It feeds the utilization view, where a reported window also replaces the model table's default, because a reported limit reflects the tier a call actually ran under.

| Runtime   | Reported on  | Fields read                             |
| --------- | ------------ | --------------------------------------- |
| Cursor    | `preCompact` | `context_tokens`, `context_window_size` |
| Qwen Code | `Stop`       | `input_tokens`, `context_limit`         |

Both runtimes also report the ratio of the two (`context_usage_percent`, `context_usage`). It is derivable from the pair, so it stays in `raw` rather than being stored a second time in different units.

A runtime that reports occupancy and no spend still shows as `not_instrumented` in the coverage report below. That is deliberate: it has not told Beacon what it cost.

**Not collected:**

| Runtime                                                             | Why                                                                                                                                                                                                                                          |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cursor                                                              | No hook payload carries a token count. Cursor does report context occupancy on `preCompact`, which is recorded separately -- see [Context occupancy](#context-occupancy) below                                                               |
| Antigravity CLI, Grok Build, Hermes Agent, Devin CLI, Devin Desktop | No token counts on the hook payloads Beacon receives                                                                                                                                                                                         |
| Muse Code                                                           | Usage arrives on `PostLLMCall` and in Muse's own session log, neither of which Beacon reads. See [Muse Code](/runtimes/muse-code)                                                                                                            |
| Qwen Code                                                           | The `Stop` payload carries session-level context counters, not per-call usage. Those counters are recorded as context occupancy rather than spend -- see [Context occupancy](#context-occupancy) below. See [Qwen Code](/runtimes/qwen-code) |
| OpenHands                                                           | None of the six hook payloads carries token counts, and OpenHands exposes no other hook that would. See [OpenHands](/runtimes/openhands)                                                                                                     |
| Kiro                                                                | No Kiro hook payload carries token counts. See [Kiro](/runtimes/kiro)                                                                                                                                                                        |
| Claude.ai                                                           | The adapter parses counts, but no recorded claude.ai stream has carried a usage object. See [Claude.ai](/runtimes/claude-web#limits)                                                                                                         |
| ChatGPT                                                             | The chat stream reports no token counts                                                                                                                                                                                                      |
| Devin Cloud                                                         | Devin bills ACUs rather than tokens. The count is preserved in the Devin raw block; there is no token or USD equivalent to normalize it into                                                                                                 |

Legacy metric-only Codex rows written before the turn trace existed remain visible in historical totals, but cannot be assigned to a session or user. See the [Codex CLI](/runtimes/codex-cli#token-usage-and-cost) and [Claude Code](/runtimes/claude-code) runtime pages for details.

### Cost estimates are separate

Beacon does not turn Codex token counts into `cost_usd`. A future report may show a separately labeled estimate with the pricing source, effective date, model, and service tier. Unknown models must remain unknown rather than reading as zero cost, and reasoning tokens must not be charged a second time because they are already included in output.

## Checking coverage

A usage report answers "what did I spend". It cannot answer "is this all of it", and that is the question to settle before treating a number as a bill. A runtime whose telemetry never arrives looks exactly like a runtime nobody used: both are simply absent from every rollup, so a total can omit an entire agent and still look complete.

```bash title="Check which runtimes are contributing token telemetry" theme={null}
beacon token-usage --coverage
```

```
Token coverage over 4812 events: 3 runtime(s) reporting usage, 1 silent

RUNTIME      STATUS            INSTALLED  EVENTS  USAGE EVENTS  TOKENS   NOTE
codex_cli    silent            yes        412     0             0        per-turn usage trace; Codex emits no cost
claude_code  covered           yes        3106    288           4271883  OTLP token and cost telemetry
cline        covered           yes        190     12            283740   plugin reports usage once per task
cursor       not_instrumented  yes        1104    0             0        Cursor hook payloads carry no token counts
```

The status column is the report:

| Status             | Meaning                                                                                                                                                                                                                     |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `covered`          | The runtime reported usage in the window. Nothing to do                                                                                                                                                                     |
| `silent`           | The runtime produced events but no usage, and Beacon is built to read usage from it. **This is the one to investigate** — a stale runtime build, a hook that stopped firing, or an OTLP export that is no longer configured |
| `inactive`         | Beacon configured this runtime, but it produced no events at all in the window. Usually means it was not used; can also mean its hooks or OTLP export stopped firing                                                        |
| `not_instrumented` | The runtime ran, and Beacon cannot read token usage from it. Expected, not a fault. The note column says why                                                                                                                |

The `not_instrumented` distinction is what makes the report worth reading. Without it Cursor would appear as a problem on every run, and a column that always shows a problem is a column people stop reading.

The `INSTALLED` column means "Beacon configured this runtime", not "this product is on the machine". Only configuration Beacon wrote counts — a hook command, a managed plugin marker, or a local OTLP endpoint it set. That is the population where silence is informative: Beacon wired it up, so it should be reporting. A runtime present but configured by hand would not be reporting through Beacon either way, so it appears only if it actually emits events.

Coverage applies the `--since`, `--until` and `--harness` scopes, and deliberately ignores `--session`, `--model`, `--repository` and `--run-id`. Those filters select usage-bearing events almost by definition — an event carries no model unless it carried a model call — so a coverage report computed over them would find every runtime covered and prove nothing.

Add `--json` for the machine-readable form, which carries the same per-runtime rows plus `silent`, `covered` and `total_events` counts. A `silent` count above zero is the alertable condition.

The local dashboard serves the same report at `GET /api/tokens/coverage`, with the same body as `--coverage --json`. It honors `since`, `until` and `harness` and ignores the rest, for the reason the command does. Both read one join and one expectation table, so the two cannot reach different conclusions about the same log.

## Examples

Show token usage from the default per-user endpoint log:

```bash title="Show local token usage" theme={null}
beacon token-usage
```

Print a machine-readable report:

```bash title="Print JSON" theme={null}
beacon token-usage --json
```

Filter to one session and include per-step detail:

```bash title="Filter by session" theme={null}
beacon token-usage --session claude-session-123
```

Group usage into hourly time buckets:

```bash title="Bucket usage over time" theme={null}
beacon token-usage --bucket 1h
```

Inspect a CI runtime log and filter to a run:

```bash title="Inspect a CI run" theme={null}
beacon token-usage --log-path ./beacon-runtime.jsonl --run-id github/123456 --json
```

## Filters

Filters can be combined to narrow the report to a time range, model, agent harness, repository, session, or CI run.

| Flag                         | Description                                                                    |
| ---------------------------- | ------------------------------------------------------------------------------ |
| `--since <timestamp>`        | Only include events at or after this RFC3339 timestamp                         |
| `--until <timestamp>`        | Only include events at or before this RFC3339 timestamp                        |
| `--session <id>`             | Filter to one session and include per-step detail                              |
| `--model <name>`             | Filter by model name                                                           |
| `--harness <name>`           | Filter by harness name                                                         |
| `--repository <path-or-url>` | Filter by repository                                                           |
| `--run-id <id>`              | Filter by CI run ID. Bare run IDs and composite provider/run keys are accepted |
| `--bucket <duration>`        | Time-series bucket size, such as `1h` or `15m`                                 |
| `--top <count>`              | Limit each grouping to the top N entries. `0` keeps all entries                |

## Runtime log selection

By default, the command reads the per-user endpoint runtime log. Use `--system` for a system-mode endpoint, or `--log-path` when reading a copied runtime log or a CI session artifact.

| Flag                | Description                                                  |
| ------------------- | ------------------------------------------------------------ |
| `--user`            | Use per-user endpoint paths. Enabled by default              |
| `--system`          | Use system endpoint paths                                    |
| `--log-path <path>` | Runtime JSONL log path. In CI, point this at the session log |
| `--json`            | Print the token usage report as JSON                         |

## Related

<Columns cols={2}>
  <Card title="Endpoint dashboard" icon="chart-line" href="/cli/dashboard">
    Explore endpoint runtime logs locally in a browser.
  </Card>

  <Card title="Endpoint inventory" icon="clipboard-list" href="/cli/endpoint-inventory">
    See configured, detected, hook, and observed endpoint inventory.
  </Card>
</Columns>
