# The other analyzers

Budget Projection, Resend, Relearn, Verbosity, Deadweight, Stream Usage and Placement, with what each looks for and how to run it.

---

Nine of the fifteen registered analyzers have a page of their own. The six that do not are below, together with `placement`, which the command accepts without it being a registered analyzer at all. Each section says what the analyzer looks for, what it puts in the report, and how to run it on its own.

`tj optimize` runs the fifteen registered analyzers, minus the ones that do not apply to your workload. That gate is unconditional: naming an analyzer on the command line does not override it, so a skipped name produces nothing whether you asked for it or not. [When an analyzer does not run](#when-an-analyzer-does-not-run) lists both skip sets, and several of the seven below are in them. Every name below except `placement` is one of those fifteen. `placement` is the sixteenth name the command accepts, and the last section explains what it does instead.

## Budget Projection

```bash
tj optimize budget-projection
```

Reads your spend so far in the current billing cycle and projects the cycle total against any ceiling you configured under `[budget.<provider>]`. Per provider, and silent for a provider with no ceiling set: with nothing to project against, there is no claim to make.

The finding carries `provider`, `budget_usd` and `cycle_start_day`, so the figure always names which configured ceiling it is projecting against. Alongside those sit the cycle bounds, days into and remaining in the cycle, the daily and monthly run rate, `projected_cycle_total`, `projected_overage_usd`, and an `exhaustion_date` when the run rate reaches the ceiling before the cycle ends.

The query behind the run rate is the one analyzer query that is not scoped to a persona. A `[budget.<provider>]` ceiling applies to the whole provider account, so every call counts against it whoever made it. Like the per-agent values, it stops nothing on its own; it is a number tj reads, never one the provider is told about. Narrowing the numerator to one persona while comparing it to a whole-account ceiling would put two different populations into one percentage.

This ceiling is a monthly spend forecast. It is a separate thing from the per-agent daily and per-session alert thresholds under `[defaults.budget]` and `[agents.<id>.budget]`, which fire an alert when they are crossed and stop nothing.

## Resend

```bash
tj optimize resend
```

Context your agent already sent in an earlier turn, sent again. Measured as a token share, independent of whether prompt caching was ever turned on:

```
prompt_size(turn) = input_tokens + cache_tokens
repeat_share      = 1 - (max(prompt_size) / sum(prompt_size))
```

aggregated token-weighted across sessions. For a session whose prompt only grows turn over turn, `sum - max` is exactly its repeated portion and the figure is exact. A mid-session compaction breaks that: the prompt resets and rebuilds, so the rebuild is counted against a `max` taken before the reset and the figure can read higher than the content actually re-sent.

The window needs at least 3 sessions and at least 6 LLM turns in total before the analyzer will report a repeat-share. Below either floor it records why and stops. Its caveat is explicit about the one way the number misleads:

> This is a structural token-share, not a savings claim [...] It is measured independent of whether caching is enabled. This means it can read high even when every re-sent byte was already a cheap cache read. Review sessions before restructuring.

Cache adoption is a different question and a different analyzer. See [Cache](/docs/optimize-cache).

## Relearn

```bash
tj optimize relearn
```

A relearn is a blocker your agents keep re-hitting across sessions that nobody ever wrote down. A wrong-directory Read, an Edit issued before the Read it needed, a stale-read race, a domain-blocked WebFetch. Each one costs a few recovery turns, every time, in sessions nobody was watching.

The analyzer builds each session's story, extracts the steps whose tool errored, and clusters the failures into signatures. Known families match by pattern against the raw error text; the residue is normalized by stripping paths, ids, numbers and timestamps, then passed through a bounded local pass that recovers a title, a root cause and a proposed fix. Clusters already codified in a reachable `CLAUDE.md` or `learnings.md` are dropped, since writing down something already written down buys nothing.

A cluster has to recur across at least 3 distinct sessions to be proposed. The horizon is tj's own archive. On-disk transcripts get rotated by your agent runtime on its own schedule, so a detector reading only those could never accumulate months of recurrence.

Each surviving cluster gets a token estimate built from occurrences times a grounded per-turn cost, a delivery mechanism, and a scope: project-level when the contributing sessions sit in one repo, user-global when they span several.

## Verbosity

```bash
tj optimize verbosity
```

The only analyzer that looks at the output side. It is [skipped](#when-an-analyzer-does-not-run) for a window an interactive coding agent dominates. It flags `(agent, model)` cohorts and sessions whose output runs long against a like-for-like baseline.

The baseline is a per-task-shape median. A session's task shape is the ordered tuple of its `(tool_name, arg_shape)` tool calls, the same signature the Script analyzer builds, so a session is compared against other sessions doing the shape of work it was doing. A session is a candidate at 2x its cohort median. The output-to-input ratio is carried as a descriptive field and is deliberately not the flag, because a long answer to a short prompt is not evidence of anything.

This is the least-grounded analyzer in the registry and it is framed that way. Its recoverable figure is output tokens above the cohort median priced at output rates, tagged as a soft upper bound, and its caveat says:

> Predicted high-verbosity output. Review before constraining a response. Output length is not waste: a terse answer can drop information the task needed. This is a candidate to look at, never a claim you are wasting tokens. Measure a brevity constraint before applying it.

The remedy is surfaced and never applied. A blanket "be concise" line pasted into an always-loaded file applies terseness pressure to every future task, including the ones that legitimately need a long answer. Putting a number on a brevity constraint means measuring it. [`--validate`](/docs/optimize-validate) does that against your own recorded calls, and it accepts `downsize` only, so verbosity is not covered yet. Until it is, apply a constraint to one cohort and compare the output tokens before and after.

## Deadweight

```bash
tj optimize deadweight
```

Two things you pay for on every turn without using them. This one is [skipped](#when-an-analyzer-does-not-run) for an SDK-dominated window.

The first is unused MCP servers. The analyzer enumerates the servers configured for a session from your project `.mcp.json`, `.claude/settings*.json` and global `~/.claude.json`, all read-only, then counts how often each server's tools were actually invoked across the window. A server that appears in at least one session and has nothing firing anywhere in the trailing 20 days is unused, and its tool schemas are being injected into context for no return.

Per-call sizes are measured rather than assumed. A probe starts each configured server, asks it for its tools, and counts the serialized schemas. A server that cannot be measured is excluded from every priced figure instead of being billed a default.

One caveat is load-bearing. When a session's transcript shows a deferred tool listing naming a server's tools, the full schemas were not loaded that turn; only a short name-and-description line per tool appeared. Those calls are priced at the measured size of that listing, taken from the same `tools/list` response as the full schema, so neither lane is a guess.

The second output is a table of the always-injected context tax: what shows up verbatim in a session's first turn, per source, per session. Session-start hook output, rules files, `CLAUDE.md`, and the schema-injection line per configured server. Every figure there is estimated and the table is report-only. Whether a source's content is ever referenced downstream is out of scope for this pass, so a row in that table is a size, not a verdict.

## Stream Usage

```bash
tj optimize stream-usage
```

A data-quality finding, not a cost one, and [skipped](#when-an-analyzer-does-not-run) for a window an interactive coding agent dominates. A streamed response reports its token usage in a final payload. On Anthropic that is the trailing `message_delta`; on OpenAI-compatible APIs it is an extra trailing chunk emitted only when the request carried `stream_options={"include_usage": true}`. Two ordinary things stop it arriving: the caller never opted in, or the client disconnected before the stream finished.

Either way the call lands with no token counts, which looks exactly like a call that cost nothing. Your spend total reads low and nothing says so.

The finding reports `streams_observed`, `streams_missing_usage`, and a per-`(provider, model, agent)` breakdown with the peer medians each estimate was built from. A call site with no completed stream to learn from reports `None` for both the undercounted tokens and the undercounted dollars, never zero.

This is not recoverable waste and is never treated as any. The provider already billed the money. Fixing the opt-in makes the figure correct, not smaller, which is why the finding carries no overspend field and stays out of the recoverable-waste total.

The signature is recorded when the stream is watched, not inferred afterwards. Every path that watches a stream stamps the streaming attributes on the span, and this analyzer only reads them.

## Placement

```bash
tj optimize placement
```

`placement` is the sixteenth name `tj optimize` accepts and it is not a registered analyzer. Asking for it runs `downsize` and surfaces the batch-placement card from that run. The check lives inside the downsize lane because detection needs the span table.

It sizes the workloads whose shape matches a provider batch endpoint. For an interactive-coding-agent window the card is [skipped](#when-an-analyzer-does-not-run) and `downsize` runs without it, so an absent card there is not a finding of no candidates. Detection is structural and conservative: a workload group qualifies only when its session start times are cadence-regular and no session in the group has a human turn after its first model call. Either condition failing means not a candidate.

The card never proposes the switch as a configuration flip. Adopting a batch endpoint means submitting work and polling for it later, which is a change to your own application and not a setting. The check stays advise-only.

## When an analyzer does not run

Some of these are skipped for some workloads, on purpose. A skipped analyzer is dropped before dispatch and runs no query, instead of rendering an empty row. The engine records a reason per name, and they are not all the same reason.

For a window an interactive coding agent dominates:

| Names | Why |
|---|---|
| `cache`, `cache-recommend`, `trim`, `stream-usage` | out of reach. The harness builds the request and owns the prompt template, so the only remedy sits on the other side of a line the user cannot cross |
| `verbosity` | the detection is sound and the remedy is not one this product recommends. Buying tokens by pasting "be concise" into an always-loaded file makes the agent terser on every future task, off a finding scoped to one cohort |
| `script`, `reuse` | measured against a real corpus of coding sessions, their detection did not hold up. Script's whole-sequence signature found no clusters; Reuse's clustering had no content signal there. Both are marked for re-enabling once the detection is rebuilt |

For an SDK-dominated window, `deadweight`, `subagent` and `summarize` are skipped because their population does not exist there. Deadweight reads project and global agent-harness config; `subagent` needs a subagent id no SDK span carries; Summarize prices agent instruction files, not the source a prompt template lives in. Each would render a permanently empty card.

A window tj cannot classify skips nothing, so an unclassified window never silently loses a finding.

`placement` rides on the same gate from the other side. It is not dispatched as an analyzer, so `downsize` reads the same list and leaves the batch-placement card out for an interactive-coding-agent window. `downsize` itself still runs.

The gate runs after tj has validated the names you asked for and before anything is dispatched, so a typo still errors and a skipped name still runs no query. Naming it explicitly changes nothing. The report records which analyzers actually ran, so a reader can tell the two cases apart.

An analyzer skipped this way produced no finding because it never ran. That is a different thing from a finding of zero, which means it ran and found nothing.

## See also

- [How the analyzers work](/docs/optimize-overview) — the registry, ranking, and plan-tier rendering
- [Validating a finding](/docs/optimize-validate) — turning an estimate into a measured delta
- [Shipped](/docs/optimize-shipped) — what a session left behind, rather than what it cost