Hands feature and test work to delegate's cheaper worker: a fast classifier (TypeSafe's Jev or ScaleDown) reads your prompt, and only where it is feature or…

<img alt="claude-modpack: eight Claude Code mods, each measured on real tasks before it shipped, built to work together. statusline, smart-compact, delegate, lsp-plus, cells, eta, seek, route." src="assets/banner-light.svg" width="880">
<h3>Install in Claude Code</h3>
/plugin install modpack --marketplace HarzerHeribert/claude-modpack
Answer y, then run /modpack:setup to set everything up step by step.<br> Prefer doing it by hand? SETUP.md has every step.
Eight free add-ons that work on their own. One command installs them, and there is nothing new to learn.<br> Every number on this page was measured, including where it doesn't help.
Install · What you get · Honest numbers · The site: ideas and evidence · Lab notes · Questions
<img alt="Animated: the cost per job with and without the pack, as bars that grow: in a long session $0.44 against $0.29 (−33%; measured twice, −26% one day and −40% the next), bigger jobs in a fresh session $0.55 against $0.51 (−8%), quick questions in a fresh session $0.23 against $0.24 (+2%, about even: +5% one day, −4% the next); then four numbers: −33% cost per job in a long session, −21% steps for the same work, −22% cost of a feature a cheaper model builds from your plan, 224 of 224 results correct." src="assets/numbers.gif" width="880">
Every time Claude takes a step (reads a file, makes an edit, runs the tests), it reads your whole conversation again. That re-reading is most of what a session costs, and it grows the longer you work. So the cheapest step is the one Claude never has to take.
The pack takes out steps that need no thinking. Several edits and their test run as one step. A search for a name in your code comes back with the exact answer. And when you approve a plan, a cheaper model does the building while Claude checks the result. The work comes out the same; Claude just gets there in fewer round trips.
<img alt="A real Claude Code session: Claude starts the stress tests in the background; above the prompt a row shows them with a bar, a percentage and a finish time, climbing from 8% to 100%; under the prompt the status line shows the model, the context, the plan's 5-hour and weekly use and a green ready dot." src="assets/demo-band.gif" width="880">
One line under the prompt: the model, how full the conversation is, and how much of your plan's 5-hour and weekly limits you have used, with when each one resets. Long jobs get a progress bar and a finish time above the prompt. And when you step away, your session is kept ready, or tidied up before it goes cold, whichever costs less; the line tells you which.
These are the statusline, eta and smart-compact mods.
<img alt="A real Claude Code session: asked to add two docstrings and run the whole test suite, Claude runs one cell; an orange card shows the read, the two edits, the diff and the test run live, and the tests pass: 1990 passed." src="assets/demo-cells.gif" width="880">
Claude makes several edits and runs the tests as one step, and you watch each part happen in a live card. Every edit still goes through Claude Code's own edit tool: you see each change, and /rewind undoes it.
That's the cells mod.
<img alt="A real Claude Code session: in plan mode Claude plans a new click.Duration parameter type; the plan is approved; Claude hands it to a Sonnet worker, whose orange card shows its steps live with a row above the prompt (hand-off 1, sonnet medium, requests and cost); then Claude checks the diff and reports: the new type works and the full suite passes." src="assets/demo-delegate.gif" width="880">
When you approve a plan, Claude hands the building to Sonnet, a cheaper model, and checks its work before reporting back. In our tests that made a feature 22% cheaper and new tests 26% cheaper, with every result correct (corrected 16 Oct: worker costs were undercounted; it was 27% and 33%). You can watch the helper work in its card, and /delegate shows every step it took.
That's the delegate mod.
A small, fast classifier reads each prompt you send. Only when it is feature or test work is Claude told to hand it straight to delegate's worker: the worker reads the code and builds, delegate checks the result itself, and Claude reports after two steps of its own. On a TypeScript feature that made the job 51% cheaper and 42% faster than the pack alone, with every result correct (corrected 16 Oct: worker costs were undercounted; it was 57%); everything else stays with Claude. The classifier is an outside service (TypeSafe's Jev or ScaleDown), so this is off until you turn it on and give it a key: from then on, the start of each prompt goes to it, in the US, to be classified.
That's the route mod.
When Claude searches your code for a name, it gets the exact list of places that use it, from the same engine your editor uses, instead of reading through search results. Renames and "who calls this?" questions take fewer steps. New in 0.5.0: Go, Kotlin and Ruby code, shell scripts and Terraform. Run /lsp-plus:setup once in your project to turn it on.
That's the lsp-plus mod.
Before Claude changes anything, it looks around your code, and everything it reads stays in the conversation, read again with every later step. Beside the first prompt you send in a session, and again as the session grows, the pack passes on your preference to look around in few, lean steps: the searches it can already name in one go, the lines that matter rather than whole files, nothing read twice. Against the pack without it, that made jobs 12% cheaper in a fresh session, 16% on a TypeScript library and 10 to 16% in long sessions, at the same quality. It runs on your machine and sends nothing anywhere.
That's the seek mod.
<img alt="A real Claude Code session: /mods lists the pack's mods (recorded with the six of 0.4.0, before route and seek) and opens a pane of cards; the status line is turned off and disappears from under the prompt, then turned on again." src="assets/demo-mods.gif" width="880">
/mods lists the eight and turns any of them on or off, right away, with a click or by typing, such as /mods statusline off. When something can't fully work on your computer, the pack tells you once, with the command that fixes it. When another plugin's permission or notification hook hangs (Warp's does outside the Warp terminal), the pack stops waiting for it after 10 seconds, so Claude Code can't freeze, and tells you which one it was. It keeps itself up to date (or only lets you know, if you prefer).
That's the modpack plugin, which installs the other eight.
In Claude Code, type:
/plugin install modpack --marketplace HarzerHeribert/claude-modpack
Answer y to add the marketplace, then pick where to install it. That installs all eight. Then, in the project you work in:
/modpack:setup
It checks your machine, asks once what you want on (route stays off unless you say yes), sets each mod up in order, including the language servers for the languages your project uses, and checks that each one works. Done. /mods shows what's on.
By hand instead: SETUP.md has every step.
Each mod installs on its own the same way, for example /plugin install smart-compact --marketplace HarzerHeribert/claude-modpack. lsp-plus, delegate and cells build on Claude Code's LSP tool, which needs a language server for each language: Language servers lists them.
We measured the pack on Claude Opus 5.5 doing seven real jobs in an open-source project (click), from a one-line lookup to building a feature, on two days, 13 and 15 October. Every result was checked by hidden tests. The same jobs ran with the pack and without it, and we compared what they cost at API prices.
| Cost per job | Without | With the pack | |
|---|---|---|---|
| In a long session (Claude has already read the whole project) | $0.44 | $0.29 | −33% |
| Bigger jobs in a long session (a feature, tests, a rename) | $0.91 | $0.57 | −37% |
| In a fresh session | $0.37 | $0.35 | −4% |
| Bigger jobs in a fresh session | $0.55 | $0.51 | −8%, and 15% faster |
| Quick questions in a fresh session | $0.23 | $0.24 | +2%, about even |
<img alt="Cost per task, plain Claude Code against the modpack, in a fresh session and in a session already holding 216k tokens." src="assets/cost-light.svg" width="880">
<img alt="Model requests per task, plain Claude Code against the modpack: 3.2 against 5.6 in a session already holding 216k tokens." src="assets/requests-light.svg" width="880">
<img alt="Cost per task, Opus alone against a plan handed to a worker: feature work 22% less, tests 26% less." src="assets/delegate-light.svg" width="880">
How we measured, every run's record, and the scripts to run it all again are in benchmarks/, starting with SUITE.md. What we tried that did not make it, a 10,000-star code graph among it, is in the lab notes (as Markdown). Not measured: what smart-compact saves you while you're away (worked out from prices, not tested) and Windows.
Will Claude's work get worse? No. In every run we graded, 730 in all, the result was correct with the pack and without, and every one was graded again for this release. The pack changes how many steps Claude takes to get there, and in our tests the results came out the same.
Is it safe? It's open source (MIT), so every line is here to read. Code that Claude runs inside a cell runs in a sandbox with no network access. Edits go through Claude Code's own edit tool, so your usual permission prompts and /rewind still apply. The pack sends nothing anywhere except a once-a-day check on GitHub for a new release, which you can turn off, and, only if you turn route on, the start of each prompt to the classifier you chose (TypeSafe or ScaleDown).
Does the pack add anything to my prompts? Your prompt itself is never changed. Beside it, Claude can get a short note in your name: seek's setting to look around in few, lean steps (with the first prompt of a session, and again after a compaction), and, only if you turn route on, the order to hand feature or test work to the worker; delegate gives the same order when you approve a plan of 20 lines or more. A subagent Claude starts gets cells' and seek's settings at the end of its task, since your prompts never reach it. /mods seek off and /mods route off stop theirs; subagents: false in cells or seek stops theirs for subagents.
I'm on a Pro or Max plan. Does this help me? You don't pay per job, but every step counts against your 5-hour and weekly limits. We measured cost at API prices, not plan usage, so we can't promise a number. Fewer, lighter steps should leave more of your limits, and the status line shows exactly where you stand.
Does it work in the desktop app? Yes, cards, progress and the status line included. One limit: in auto mode the desktop app doesn't let an add-on start a helper model, so plans aren't handed off there (turn route off there too); in manual mode, or in the terminal, they are. Windows hasn't been tested yet.
How do I turn it off? /mods switches each part off or on, right away. /plugin removes them.
Were any of these numbers wrong? Yes, the hand-off ones. On 16 October we found that our bench had counted a worker's output only as each of its responses began, so every saving that involved a worker came out too large: a planned feature was −27% and is −22%. They are corrected here, the old and new number side by side in CORRECTION-1016.md. The pack-against-plain numbers had no workers and stand.
What does it cost? Nothing. It's free and open source.
One line under the prompt, drawn in Claude Code's own hint row so the mode indicator and the status share it:
⏵⏵ auto mode on Opus 5.5 xhigh 412k ━━━━━━────── 5h 34% ↺2h14 7d 12% ↺3d ● ready · cold in 44m
$.statusline.set({ id, priority, parts, short?, side? }) and lists statusline among its dependencies. A part is text, a countdown ({ until, prefix?, warnMs? }, counted by the line) or a spinner ({ spin: true }), so a mod sends a segment when it changes, not every second. $.statusline.remove({ id }) takes it off./rc) from the desktop app, the web or the phone gets no mod drawing and no status text as of Claude Code 2.1.296.| Option | Default | |
|---|---|---|
enabled | true | false turns the mod off; /mods does it for all of the pack. |
placement | line | tail: appended dim to Claude Code's hint. band: above the prompt. off. |
limits | always | half: a plan window only once it is half used. off: never. |
animate | true | The highlight along the gauge. |
An idle session's prompt cache expires after its TTL: 1 hour on a subscription, 5 minutes in overage or on an API key. The next prompt then writes the whole context to the cache again, at 1.25x (5 min) or 2x (1 h) the input price. For a 400k-token Opus 5.5 session on the 1-hour cache:
| Option | Cost |
|---|---|
| Let it expire, re-cache on return | $3.20 |
| One keep-alive (re-read the cached prefix; a read resets the TTL) | $0.09 |
| Compact before it expires | ~$0.87, plus detail lost to the summary |
A few minutes before the cache expires (2.5 min on the 1-hour TTL, 45 s on the 5-minute one), the smart strategy:
compactPenaltyUsd. It stops after maxIdleHours.minSavingsUsd of re-cache cost, or below minCompactTokens of context, it never compacts.It never compacts while the prompt holds a draft or agents are running; it sends a keep-alive to hold the cache instead. If the machine sleeps past the deadline, it marks the cache expired and does nothing. A failed action is retried twice, 20 s apart. The TTL is detected from the plan and corrected from the cache hits and misses it observes.
On statusline's line it says whether the session is ready to prompt or has gone cold: ● ready, · cold in m:ss (amber in the last five minutes), · compacts in … when an idle compaction is near, ○ cold, or what it is doing (compacting, keeping warm, paused). /cache shows the live figures and plan; /cache pause, /cache resume and /cache keepalive control it.
| Option | Default | |
|---|---|---|
enabled | true | false turns the mod off; /mods does it for all of the pack. |
strategy | smart | compact: compact at the first expiry. keepalive: never compact. off: status line only. |
cacheTtl | auto | 1h or 5m to override detection. |
minSavingsUsd | 0.25 | Below this re-cache cost, the cache is left to expire. |
compactPenaltyUsd | 0.5 | Higher keeps the cache warm longer before compacting. |
minCompactTokens | 100000 | Never compacts a smaller context. |
maxIdleHours | 6 | Stops keep-alives after this long idle. |
compactInstructions | empty | Passed to idle compactions, like /compact <text>. |
Prices are list prices, so on a subscription they are API-equivalent estimates, not a bill.
Every request of the main model re-reads its whole context: at 600k tokens on Opus 5.5 that is about $0.12 a step before any work is done. A worker reads only its own small context. Claude calls hand_off with a task that stands alone, its kind (read: changes no file; mechanical: the task spells out every change; build: something is left to work out), the symbols or file:line places it touches (focus), and for edits a check command. The kind picks the worker, so Claude has no model to weigh: Haiku at low effort for read and mechanical work, Sonnet at medium for build work.
read, with Edit and Write for mechanical and build. The call waits for it inside the tool call (one request of the main model) and returns the report with the evidence; with background it returns at once and the report arrives as a message. A follow-up within 50 minutes goes to the same worker, its reading still in its context.~/.claude/mods/delegate/log.jsonl; /delegate sums it up and opens a side pane. While a worker runs, its card is drawn like a cell (the task, a row per step, the state and cost), and with eta loaded it is a row above the prompt too.Measured on 120 Opus runs, all correct: a feature or tests cost 23–35% less handed off; lookups, traces, explanations and small edits cost more. Opus does not hand off unless told to, so ask for it as an instruction: "hand the tests to a worker". An order the mod gives works as well as yours (8 of 8 runs, 22–26% less), so when you approve a plan of 20 lines or more, delegate gives that order: plan in plan mode, approve, and a Sonnet worker builds it. Corrected 16 Oct: worker costs were undercounted; these were 27–37% and 27–33%.
A verified hand-off (0.3.0). The worker ends its report with a VERIFY block: the command that runs only its new tests, and what it left open. d
hooks/register.ts 38 lines1import type { Register } from 'claude-code'
2
3import { HAND_OFF, classify, order } from './classify'
4import type { Classifier } from './classify'
5
6/** delegate's tool: without it there is nothing to hand off to, and route stays out of the way. */
7const HAND_OFF_TOOL = 'mcp__delegate__hand_off'
8/** Shorter than this a prompt is a reply or a command ("yes", "commit this"), not work to classify. */
9const MIN_CHARS = 40
10
11export const register: Register = (on, options) => {
12 // Off unless the person turned it on: turned on, every prompt's text leaves the machine.
13 if (options.enabled !== true) return
14 const scaledownKey = typeof options.scaledownKey === 'string' ? options.scaledownKey : ''
15 const typesafeKey = typeof options.typesafeKey === 'string' ? options.typesafeKey : ''
16 const choice = options.classifier === 'jev' || options.classifier === 'scaledown' ? options.classifier : 'auto'
17 const minScore = typeof options.minScore === 'number' ? options.minScore : 0.7
18 const timeoutMs = typeof options.timeoutMs === 'number' ? options.timeoutMs : 3000
19 const direct = options.direct !== false
20
21 // The person's own prompt (typed, from the phone, or a `claude -p` turn): feature or test work gets the order.
22 on('prompt.submit', async ($, e, next) => {
23 const kind = e.origin?.kind
24 if (kind !== 'composer' && kind !== 'bridge' && kind !== 'sdk') return next(e)
25 const text = e.text.trim()
26 if (text.length < MIN_CHARS || text.startsWith('/')) return next(e)
27 // auto: Jev when its key is there (the faster of the two, measured), else ScaleDown.
28 const jevKey = choice === 'scaledown' ? '' : typesafeKey || ((await $.env.get('TYPESAFE_API_KEY')) ?? '')
29 const sdKey = choice === 'jev' ? '' : scaledownKey || ((await $.env.get('SCALEDOWN_API_KEY')) ?? '')
30 const [classifier, key]: [Classifier, string] = jevKey !== '' ? ['jev', jevKey] : ['scaledown', sdKey]
31 if (key === '') return next(e)
32 if (!(await $.tool.list().catch(() => [])).some(t => t.name === HAND_OFF_TOOL)) return next(e)
33 const v = await classify($, classifier, key, text, timeoutMs)
34 if (v === undefined || !HAND_OFF.has(v.label) || v.score < minScore) return next(e)
35 return next({ ...e, context: [...(e.context ?? []), order(v.label, direct)] })
36 }).catch(($, e, next) => next(e))
37}
38hooks/classify.ts 91 lines1// What kind of work a prompt asks for, from one of two classifiers: ScaleDown's Classify
2// (https://docs.scaledown.ai/api-reference/classify) or TypeSafe's Jev (https://docs.typesafe.ai/introduction/quickstart).
3// Measured on 25 prompts (the benchmark's eight and 17 everyday ones), one call each, the two alternating, two rounds
4// (pilot/scaledown/jev.py, 15 Oct): both 50 of 50 right; Jev 0.25 s median and 0.34 s for the slowest tenth, ScaleDown
5// 1.15 s and 2.5 s. These are the labels and rubrics of that measurement, unchanged: a different wording is a different
6// classifier, and would need measuring again.
7import type { EngineInterface } from 'claude-code'
8
9export const CLASSIFY_URL = 'https://api.scaledown.xyz/classify'
10export const JEV_URL = 'https://api.typesafe.ai/v1/systemone'
11/** Only the start of a prompt leaves the machine: the kind of work is in its first lines, and a pasted log is not. */
12export const MAX_CHARS = 8000
13
14export type Classifier = 'scaledown' | 'jev'
15
16export const LABELS = [
17 { name: 'lookup', rubric: 'Does this request ask to find or list where something is in the code (a definition, callers, usages, a value), without changing anything?' },
18 { name: 'explain', rubric: 'Does this request ask why or how something works or fails, expecting an explanation rather than a change?' },
19 { name: 'small-edit', rubric: 'Does this request ask for one small, local change to code, config or text, such as a value, a typo or a version?' },
20 { name: 'rename', rubric: 'Does this request ask to rename or move a symbol, file or parameter and update every place that uses it?' },
21 { name: 'feature', rubric: 'Does this request ask to implement new behaviour or a new capability in the code?' },
22 { name: 'tests', rubric: 'Does this request ask to write or add tests, or raise test coverage?' },
23 { name: 'other', rubric: 'Is this request about version control, running tools, pull requests, or anything that is not reading or writing code?' },
24]
25
26export type Verdict = { label: string; score: number }
27
28/** Jev's choice and its probability, from its answer; undefined for anything else. */
29export function jevVerdict(body: string): Verdict | undefined {
30 try {
31 const a = JSON.parse(body)?.answers?.kind
32 const label = a?.choice
33 const score = a?.probabilities?.[label]
34 return typeof label === 'string' && typeof score === 'number' ? { label, score } : undefined
35 } catch {
36 return undefined
37 }
38}
39
40/** The request each service takes for one prompt. */
41export function request(classifier: Classifier, key: string, text: string): { url: string; headers: Record<string, string>; body: string } {
42 const sent = text.slice(0, MAX_CHARS)
43 if (classifier === 'jev') {
44 const criteria = Object.fromEntries(LABELS.map(l => [l.name, l.rubric]))
45 return {
46 url: JEV_URL,
47 headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
48 body: JSON.stringify({ state: sent, model: 'jev-latest', questions: { kind: { type: 'choice', instructions: 'Which kind of request to a coding assistant is this?', criteria } } }),
49 }
50 }
51 return { url: CLASSIFY_URL, headers: { 'content-type': 'application/json', 'x-api-key': key }, body: JSON.stringify({ text: sent, labels: LABELS, reasoning: false }) }
52}
53
54/** ScaleDown's top label and its score, from its answer; undefined for anything else (it answers some errors with 200). */
55export function verdict(body: string): Verdict | undefined {
56 try {
57 const d = JSON.parse(body)
58 const label = d?.top_label
59 const score = d?.scores?.[label]
60 return typeof label === 'string' && typeof score === 'number' ? { label, score } : undefined
61 } catch {
62 return undefined
63 }
64}
65
66/** The kind of work a prompt asks for, or undefined when the classifier did not answer in time or at all. */
67export async function classify($: EngineInterface, classifier: Classifier, key: string, text: string, timeoutMs: number): Promise<Verdict | undefined> {
68 const { url, headers, body } = request(classifier, key, text)
69 const read = classifier === 'jev' ? jevVerdict : verdict
70 const call = $.http
71 .fetch(url, { method: 'POST', headers, body })
72 .then(r => (r.ok ? read(r.text) : undefined))
73 .catch(() => undefined)
74 const late = $.clock.sleep(timeoutMs).then(() => undefined, () => undefined)
75 return Promise.race([call, late])
76}
77
78/** Work a hand-off pays for: a feature or tests, 23–35% cheaper handed off; everything else costs more handed off. */
79export const HAND_OFF = new Set(['feature', 'tests'])
80
81/**
82 * The order beside the prompt. An order a mod gives in the person's name was followed in 8 of 8 runs (delegate's
83 * afterPlan uses the same form); advice was followed in none. It names the person's setting, which is what it is.
84 */
85export function order(label: string, direct = true): string {
86 const what = label === 'tests' ? 'test work' : 'feature work'
87 // direct: the worker reads the code, so the main model skips its own look around (1.6 requests a hand-off, measured).
88 const when = direct ? ' Hand it off before you read the code: the worker reads it, so write the task from the request as given.' : ''
89 return `This prompt is ${what}, and the person's setting for ${what} is: implement it through the hand_off tool (kind build).${when} Write the request into the task so it stands alone, with the project's whole test suite as the check. The tool checks the worker's work itself; when its result says Verified and the diff is what was asked, report to the person without running anything again.`
90}
91