---
slug: "omp-throughput"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/kajeagentspi/omp-throughput@main/README.md"
repo: "https://github.com/kajeagentspi/omp-throughput"
source_file: "README.md"
branch: "main"
---
# omp-throughput

A live throughput (tokens-per-second) meter for [omp](https://oh-my-pi). It renders a small,
permanent panel above the editor that shows how fast your model is streaming, a session-level
TPS history, and one row per running task subagent.

- **Zero configuration.** Drop it in and it runs.
- **Zero disk I/O.** All state lives in process memory and is discarded when the process exits.
- **O(1) stats.** Mean and p95 are maintained incrementally (a P² streaming sketch), not recomputed per frame.
- **Task-subagent aware.** One row per in-flight worker, tagged with its agent type (`scout` / `task` / `designer` / `reviewer` / `librarian` / `sonic`).

> **Works on vanilla pi too.** The extension is written against the vanilla pi extension API,
> so in [pi](https://github.com/earendilworks/pi-coding-agent) the **main-agent** throughput
> (gauge, sparkline, μ, p95) works fully. The task-subagent rows and agent badges are
> omp-specific (the `task` tool and agent roster are not in vanilla pi), so they simply do not
> appear there — nothing breaks.

---

## What it looks like

The panel has one header line (the session summary) and one row per active participant.

**Idle** — a fresh session, nothing streamed yet:

![omp-throughput idle](https://raw.githubusercontent.com/kajeagentspi/omp-throughput/main/docs/img/throughput-idle.png)

**Main agent streaming** — the header carries live aggregates and the `main` row shows a live gauge:

![omp-throughput main streaming](https://raw.githubusercontent.com/kajeagentspi/omp-throughput/main/docs/img/throughput-main.png)

**Task subagents running** — once the main agent has completed at least one response the header
carries the session fingerprint (sparkline of the last 12 completed responses + mean μ + p95),
and each worker gets a row with its agent-type badge and live gauge:

![omp-throughput subagents running](https://raw.githubusercontent.com/kajeagentspi/omp-throughput/main/docs/img/throughput-subagents.png)

**On vanilla pi** — main-agent panel only (no subagent rows or badges):

![omp-throughput on vanilla pi](https://raw.githubusercontent.com/kajeagentspi/omp-throughput/main/docs/img/throughput-pi.png)

### Row anatomy

```
 ⠴ IndexPools     task      zai/glm-5.2:max   ▕█████████▏ 42 tps · 780 tok
 ↑ ↑              ↑         ↑                 ↑            ↑
 │ │              │         │                 │            └─ tokens for this message/session
 │ │              │         │                 └─ live TPS gauge (fills relative to the fastest row)
 │ │              │         └─ model + thinking level
 │ │              └─ agent-type badge (scout / task / design / review / libr / sonic)
 │ └─ worker name (the task name)
 └─ phase icon: ⠴ streaming · ◇ in a tool · ✓ done · · waiting
```

### Header anatomy

```
 Throughput ··········█▁ 75 tps · μ 77 · p95 80 · 3 active · 2 streaming · 90 tps total · 1.7k tok
           ↑                      ↑    ↑    ↑    ↑           ↑              ↑              ↑
           sparkline (last 12)    last  mean p95  active count streaming cnt  sum of live TPS  sum of tokens
```

The sparkline / μ / p95 group appears only once the main agent has completed at least one response.

---

## Install

**omp** (recommended):

```bash
omp install omp-throughput
```

Restart omp (or run `/reload`) and confirm:

```bash
omp plugin list      # → omp-throughput, enabled
omp plugin doctor    # → plugin:omp-throughput  ok
```

**Vanilla pi:**

```bash
pi install npm:omp-throughput
```

Confirm with `pi list`. The extension requires `@earendil-works/pi-coding-agent` >= 3.0.0 as an optional peer dependency (both omp and pi already provide it).

**From source / dev:**

```bash
git clone git@github.com:kajeagentspi/omp-throughput.git
omp plugin link ./omp-throughput     # omp — live symlink for development
pi install ./omp-throughput          # vanilla pi
```

---

## How it works

- **In-memory registry.** All sessions (main + workers) register in a single `Map` stored on
  `globalThis[Symbol.for("omp.throughput.registry.v1")]`. Because task subagents run in-process
  with the main, they share this registry. Nothing is written to disk.
- **Main stats are incremental.** Each completed main response records its TPS into:
  - a 12-sample ring (the sparkline),
  - a running sum + count (the mean μ),
  - a **P² streaming quantile** sketch (the p95).
  Stats update once per completed message — never per render frame — so the 200 ms UI tick is O(1).
- **Agent badges** are read from the `task` tool's own input: a `tool_call` listener reads
  `input.tasks[].name` + `input.tasks[].agent` and maps each worker name to its agent type.
- **Pruning.** Completed workers stay visible for 3 s, then drop off; stale workers (>10 min) are removed.

### Why a P² sketch for p95?

An exact p95 needs the full sorted history. The P² algorithm (Jain & Chlamtac, 1985) estimates
a quantile from a stream in O(1) time and constant memory using five markers. This implementation
bootstraps from the first 64 samples (exact p95 over the buffer) and then streams, giving:

- N ≤ 64: exact (0% error),
- N ≥ 100: typically < 5%,
- N ≥ 1000: typically < 1.5%.

---

## Files

```
omp-throughput/
├── extensions/
│   └── throughput.ts     # the extension (single file, ~700 lines)
├── docs/
│   ├── img/              # README screenshots
│   └── render_shots.py   # regenerates the screenshots
├── LICENSE
├── package.json
└── README.md
```

No build step is required — omp loads the TypeScript extension directly via Bun.

## Notes

- The panel renders every 200 ms while anything is active. When fully idle it still shows the header
  line (with the session sparkline once there is history).
- If you ever see the same row twice or a doubled total, restart the window — an `[omp-tp-anomaly]`
  line will appear in the omp debug log with the registry contents.
