{"id":"turn-speed","repo":"Drenalol/paseo-turn-speed","url":"https://github.com/Drenalol/paseo-turn-speed","package":"paseo-turn-speed","npm":{"package":"paseo-turn-speed","version":"1.0.1","integrity":"sha512-9eTN/UfGilPqJLc91o9wacBiNiJbgJH0jYUo5QE6SnAx4BfOKGhww9s9Lt5GBqOgAaynJkzJNaQbwowKaJX+3Q==","publishedAt":"2026-10-15T10:14:30.079Z","downloadsLast30Days":0},"name":"turn-speed","description":"Live decode tok/s, prefill and token breakdown for every agent turn","categories":["monitoring","productivity"],"platforms":[],"caveats":["Turns are observed live; history before a plugin reload or reconnect is not reconstructed","Exact numbers need provider usage; OpenCode/MLX models need options.includeUsage: true in the provider config","Without provider usage every number is a character estimate marked with an approximation sign"],"images":["https://raw.githubusercontent.com/Drenalol/paseo-turn-speed/533070ca5d38b4b094acd6cfbeedf4baa989351d/images/desktop-pill-live.png","https://raw.githubusercontent.com/Drenalol/paseo-turn-speed/533070ca5d38b4b094acd6cfbeedf4baa989351d/images/desktop-pill-waiting.png","https://raw.githubusercontent.com/Drenalol/paseo-turn-speed/533070ca5d38b4b094acd6cfbeedf4baa989351d/images/desktop-popover.png","https://raw.githubusercontent.com/Drenalol/paseo-turn-speed/533070ca5d38b4b094acd6cfbeedf4baa989351d/images/mobile-popover.png"],"themes":[],"health":{"manifestValid":true,"hasReadme":true,"hasLicense":true,"hasTests":true,"hasTypecheckScript":true,"updatedRecently":true},"scannedAt":"2026-10-15T12:34:58.214Z","addedAt":"2026-10-15T10:20:53Z","npmSecurity":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-15T12:14:24.405Z","version":"1.0.1","integrity":"sha512-9eTN/UfGilPqJLc91o9wacBiNiJbgJH0jYUo5QE6SnAx4BfOKGhww9s9Lt5GBqOgAaynJkzJNaQbwowKaJX+3Q=="},"version":"1.0.1","security":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-15T12:14:24.405Z","commit":"533070ca5d38b4b094acd6cfbeedf4baa989351d"},"author":"Bogdan Yanysh <acidlol@live.ru>","license":"MIT","paseoVersionRequirement":">=0.11.0","descriptionNodes":[{"type":"text","text":"Live decode tok/s, prefill and token breakdown for every agent turn"}],"caveatNodes":[[{"type":"text","text":"Turns are observed live; history before a plugin reload or reconnect is not reconstructed"}],[{"type":"text","text":"Exact numbers need provider usage; OpenCode/MLX models need options.includeUsage: true in the provider config"}],[{"type":"text","text":"Without provider usage every number is a character estimate marked with an approximation sign"}]],"manifest":{"id":"turn-speed","description":"Live decode tok/s, prefill/TTFT, and a token breakdown for every agent turn, right in the composer.","requirements":{"paseo":">=0.11.0"}},"repoMeta":{"stars":0,"defaultBranch":"main","pushedAt":"2026-10-15T10:27:21Z"},"owner":{"login":"Drenalol","avatarUrl":"https://avatars.githubusercontent.com/u/4239087?v=4"},"installNotesHtml":"<pre><code class=\"language-bash\">paseo plugin add github:Drenalol/paseo-turn-speed\n</code></pre>","readmeText":"# turn-speed — live token-rate meter for Paseo agents\n\nA composer pill per agent that shows how fast the model is actually decoding:\n`⚡ 138 tok/s · 0.8k` while a turn runs, `139 tok/s · 1.2k tok` after it ends,\nwith a popover breakdown (prefill/TTFT, in/cached/out tokens, steps, tool wait,\ncontext occupancy). Numbers measured from real provider usage are exact;\nbefore the first usage event they are character estimates marked with `≈`.\n\n## Install\n\n```bash\npaseo plugin add github:Drenalol/paseo-turn-speed\n# or, from npm:\npaseo plugin add npm:paseo-turn-speed\n```\n\nThen open Settings → Plugins → Turn speed to tune it. See\n[Requirements](#requirements) for the one provider setting that makes the\nnumbers exact.\n\n## Screenshots\n\nDesktop — the composer pill while a turn decodes, and the paused variant\n(the model is waiting for your answer):\n\n<img src=\"images/desktop-pill-live.png\" alt=\"Live pill: 74.4 tok/s\" width=\"200\"> <img src=\"images/desktop-pill-waiting.png\" alt=\"Waiting pill: 43.6 tok/s\" width=\"200\">\n\nThe popover behind the pill — decode and whole-turn rates, prefill/TTFT,\ntoken totals, steps, tool wait and context occupancy:\n\n<img src=\"images/desktop-popover.png\" alt=\"Turn breakdown popover\" width=\"420\">\n\nThe same popover on mobile, with the `pill shows` basis chips:\n\n<img src=\"images/mobile-popover.png\" alt=\"Mobile popover\" width=\"300\">\n\n## Requirements\n\n- Paseo **0.11.0+** (daemon and app).\n- Node.js with npm on the daemon host (for the one-time `npm install` here; Paseo itself\n  needs no npm to run the plugin).\n\n## Make it work\n\n```bash\ncd /path/to/paseo-turn-speed\nnpm install                  # dev dependencies for typechecking only\nnpm run typecheck\npaseo plugin install \"$(pwd)\"\npaseo plugin reload turn-speed   # after every source edit\npaseo plugin logs turn-speed\n```\n\nThen open the Paseo app: every agent with a workspace gets a pill in the composer\ntrack (next to Tasks/Subagents). Settings → Plugins → Turn speed holds the options.\n\n### The one setting that decides accuracy: `includeUsage`\n\nThe plugin reads real token counts from the `usage` the provider reports. For\nOpenCode-backed models (including local MLX servers) the provider only forwards\nusage when it is enabled in the OpenCode config, e.g. in `opencode.json`:\n\n```json\n{\n  \"provider\": {\n    \"mlx\": {\n      \"options\": { \"includeUsage\": true }\n    }\n  }\n}\n```\n\nWithout it the plugin still works: every number is estimated from streamed\ncharacters (`≈`) and the popover shows a hint about this setting.\n\nAlso useful: a `limit.context` value for the model (the model catalog or the\nprovider config) — it feeds the `context 34k / 131k` line in the popover.\n\n## How the numbers are computed\n\n- **TTFT / prefill** — from turn start to the first streamed character. Reported\n  separately, never mixed into tok/s.\n- **decode tok/s** — output tokens from usage deltas divided by decode windows\n  only. Tool waits, thinking pauses longer than 2 s, and the prefill of the next\n  model call are excluded. This is why the pill shows ~139 tok/s on short\n  answers instead of 6.\n- **Whole-turn tok/s** — optional alternative basis in settings; it includes\n  tool time and is always lower. Time spent waiting for you is excluded.\n- **Waiting for you** — when the model asks a question or waits for a permission\n  or plan approval it is asleep until you answer. Those intervals are taken from\n  the provider's permission/question requests (and restored from agent snapshots\n  after reloads and reconnects), and are removed from decode windows, tool-wait\n  time and the whole-turn rate. `turn time` stays wall-clock; the popover shows a\n  separate `waiting for you` row and the pill switches to a ⏸ icon meanwhile.\n- **Live number inside a model call** — real tokens only arrive at the end of a\n  call, so the live value estimates tokens from characters over a 1/2/5 s\n  smoothing window and is marked `≈` until the call's usage lands.\n- **Implausible rates are dropped** — a step implying a decode faster than the\n  configured limit (default 400 tok/s) cannot have happened on your hardware;\n  it is almost always the previous (interrupted) message's usage arriving late.\n  Such deltas are absorbed into the baseline instead of inflating the rate, and\n  a note lands in the app log (`turn-speed: step dropped as implausible`).\n- **Auto-calibration** — at every step end the observed `chars/token` ratio for\n  the model is blended into a stored per-model calibration (EMA 0.7/0.3, clamped\n  to 2..8, persisted in plugin settings). After 2–3 steps the live estimate\n  tracks the real one closely. Settings → Turn speed shows and can override it.\n\n## Settings (Settings → Plugins → Turn speed)\n\n| Option | Default | Effect |\n| --- | --- | --- |\n| Show pills for idle agents | off | otherwise pills appear only around observed turns |\n| Speed basis | Live decode | what the pill shows: live estimate, decode-window average, or whole-turn rate |\n| Token format | 1.2k | compact or grouped digits |\n| Live smoothing window | 2 s | averaging window of the live tok/s |\n| Manual chars per token | off (auto-calibrated) | override the live character estimate |\n| Implausible rate limit | 400 tok/s | steps implying a faster decode are dropped as misattributed usage |\n\n## Development\n\n```bash\nnpm run verify      # typecheck + unit tests + client-bundle check\npaseo plugin reload turn-speed\npaseo plugin ls     # status of every installed plugin\n```\n\nReleasing a new version (npm + registry flow, with the gotchas): see\n[RELEASE.md](RELEASE.md).\n\n`npm run check:client` compiles the client entry with the same options Paseo's\ncompiler uses and rejects output the mobile app's JS engine (Hermes) cannot\nparse — notably anonymous class expressions, which is exactly what a `class`\nbecomes after bundling. Client code therefore uses closure factories instead of\nclasses. With a `hermes` binary available, `HERMES_BIN=/path/to/hermes npm run\ncheck:client` runs the real Hermes syntax check too.\n\n## Known limitations\n\n- Usage arrives via agent snapshots, so step boundaries are as fine as the\n  provider's per-call usage updates. Providers without usage at all stay `≈`.\n- The plugin only counts what it watches live: after a reload or a reconnect the\n  interrupted turn is not reconstructed (final `lastUsage` still gives totals).\n- Subagent turns get their own pills and are never added to the parent's numbers.\n- Usage that arrives late from a previous turn (typically after an interrupted\n  message) is dropped by the plausibility guard, so that turn's token totals can\n  be slightly low; rates stay honest.\n- Every closed and dropped step is logged to the app log\n  (`~/Library/Logs/Paseo/main.log`, prefix `turn-speed:`) if you want to audit\n  where a number came from.\n"}