# Measurements

Nothing is estimated and nothing is recomputed by hand. pi already reports tokens
and cost; the library **collects and attributes** them, and adds the one thing pi
does not measure - time - plus aggregation per subagent.

```typescript
type Usage = {
	// time, measured here on a monotonic clock
	wallMs: number;        // spawn to close, waiting included
	busyMs: number;        // time actually spent working: the sum of the asks
	turns: number;
	toolCalls: number;     // as pi counted them in the transcript

	// tokens and cost, reported by pi, never reconstructed
	input: number;
	output: number;
	cacheRead: number;
	cacheWrite: number;
	cost: number;
	contextTokens?: number;
};
```

```typescript
subagent.usage;   // cumulative since spawn
result.usage;     // this turn only
```

## Where the numbers come from

- pi's `getSessionStats()` is the source of truth for tokens and cost, and for
  the context occupancy worth showing for a persistent agent. `src/session.ts`
  reads it around each turn and hands the turn's share back with the turn.
- Time is measured around each turn: `busyMs` is the sum of the `ask` calls,
  `wallMs` runs from spawn to close.

On a `"task"` subagent the two times are nearly equal. On a `"workflow"` one, the
**gap between them is the information**: waiting versus useful work.

## The rules

- **`getSessionStats()` is cumulative over the session.** A turn's usage is
  therefore the *difference* between two snapshots, taken before and after the
  turn. That is what gives both a cumulative `subagent.usage` and a per-turn
  `result.usage` without ever recounting a token. Adding up the usage of the
  turn's own messages would not do: it agrees on an ordinary turn, but a
  compaction's summary request emits no message, and pi compacts inside a turn.
- **Counters are clamped at zero.** Compaction can walk the totals backwards, and
  a negative usage means nothing.
- **A fan-out aggregates, it does not average**: total tokens, total cost,
  `wallMs` is the duration of the fan-out, `busyMs` the sum of the branches. The
  ratio of the two is the real parallelism, and that is what is worth seeing.
- **A failure counts.** A subagent that crashed after 12k tokens cost 12k tokens.
  Its `Usage` is filled in even when `ok` is false.
- **A refused turn does not.** Asking a subagent that has been stopped returns
  at once without reaching the session: the `Result` is a failure with
  `turns: 0`. A run called off halfway reports the turns it ran, not the ones it
  was about to.
- **Never estimate tokens by counting characters.** If the provider does not
  report them, the field is `0` and we say so - and what a provider reports
  changes under you: one measured here reported no tokens at all, reports them
  today, and still reports no cost. So a line with no `$` figure means "not
  reported", never "free", and a table's cost column says `not reported`.
  Tokens wait for a turn to end, since pi's counters are read then: before
  that a line gives none, and after it `↑0 ↓0` is a zero pi reported.
- **Tool calls are pi's count too.** `getSessionStats().toolCalls` counts the
  calls in the transcript, compacted ones included, so the session takes a
  turn's `toolCalls` as a delta like its tokens. It is recorded for every
  subagent. An agent whose definition says `mustCallTool: true` fails a turn
  that called none, and the displays say `called no tool` of it; see
  [Agents](agents.md#an-agent-that-must-read-before-it-answers).
- **`input` counts every request, not every turn.** pi sends the whole prompt on
  each round trip, so one turn of a hundred tool calls sends a 14k context a
  hundred times and the counter reads millions. That number is right; the turn
  is the problem, and `timeoutMs` is what ends it.

## Reading it

```typescript
import { formatUsage, sumUsage } from "@ai-for-dev/combo";

formatUsage(result.usage);
// 3 turns 12.4s ↑12k ↓2.1k R8k $0.0412 ctx:34k
```

Three functions do the arithmetic, and nothing else does:

- `deltaUsage(before, after)` is one turn, the difference between two cumulative
  snapshots, clamped at zero.
- `accumulate(total, turn)` is a subagent's life so far: counters and busy time
  add up, its own `wallMs` stands, and the context is the turn's reading rather
  than a sum.
- `sumUsage(parts, wallMs)` is several subagents together, which is how a
  workflow reports its total; the wall time comes from outside because a
  fan-out's is not the sum of its branches. A workflow gets both from its
  pool's `trail`, which records every turn and keeps the clock.

`usage.json` writes a `Usage` per subagent and one for the whole run, with the
subagent count beside it, so what the file holds is the type above and nothing
hand-picked from it.

## A delegated run is a tree

A subagent that spawns children of its own is still one subagent among others:
it has its own `Usage`, and so does each child. What links them is the id of
whoever asked, carried on the `spawn` event and kept on the snapshot:

```
✓ explorer#1      1 turn 2.1s ↑8.0k ↓412 $0.0180
✓   scout#1       1 turn 9.4s ↑14k ↓1.1k $0.0402
✓   scout#2       1 turn 8.8s ↑13k ↓980 $0.0377
total            3 turns 20.3s ↑35k ↓2.5k $0.0959
```

Two things this is built around:

- **The total is the tree, never the root.** An explorer whose own turn cost
  eighteen cents while its scouts cost eighty is a cheap agent and an expensive
  run. `summaryTable` and `usage.json` both sum every row.
- **The link is an id, never a name.** Two explorers running at once share a
  name; nothing infers a parent afterwards, and a child whose parent is not in
  the list reads as a root rather than disappearing from the total.

The link reaches a tool through `SpawnOptions.customTools` in its function form:
the id is minted by `spawn`, so a tool that will spawn children is built from it
rather than before it.

```typescript
customTools: (parentId) => [delegateTool({ agents, holder: explorer, parentId })],
```

A flow run adds the same numbers per visit, per node and per life: pi's
counters across each visit, and a resumed run's lives summed. See
[Measuring a run](flows.md#measuring-a-run).

Comparing these numbers across models, over repeated runs, is what
[Experiments](experiments.md) is for - same collection, one table.

## Reference

- [`usage`](../reference/api/usage.md) - `Usage`, `deltaUsage`, `sumUsage`, `formatUsage`, `compact`.
- [`measure/export`](../reference/api/measure/export.md) - `usageReport`, the `usage.json` document.
