Rates each coding task with Haiku and runs it on Haiku, Sonnet or Opus

Four mods that work together: plan first, then code with each task sent to a model that fits how hard it is, and watch it all happen.
| Mod | What it does |
|---|---|
task-planner | /plan <goal> runs an Opus planner agent (read-only) that returns a checklist of focused tasks, each rated easy/medium/hard. The steps appear in a Plan pane. |
coding-agents | Adds a coder subagent and tells the main session to hand each focused coding task to it, one at a time. |
model-router | Before each coder starts, Haiku rates the task and the coder runs on Haiku (easy), Sonnet (medium) or Opus (hard). Falls back to Sonnet if rating fails; a model the session asks for explicitly wins. |
mod-monitor | A Mods pane: in the desktop app an animated control room (you → session → mods → Haiku/Sonnet/Opus) with messages flying along the wires, busy mods pulsing, running agents linked to their model, and the model mix; in the terminal an emoji/colour version. Hover anything for details. |
They need Claude Code on your own computer (terminal claude, or the Code tab of the desktop app). Cloud sessions in the Claude app don't load plugins.

In a Claude Code session:
/plugin install task-planner --marketplace alexatennea/claude-mods
/plugin install coding-agents --marketplace alexatennea/claude-mods
/plugin install model-router --marketplace alexatennea/claude-mods
/plugin install mod-monitor --marketplace alexatennea/claude-mods
Already added the marketplace before a mod existed? Refresh it first: /plugin marketplace update void-mods. After an update to a mod you have, run /plugin update <mod>.
Answer y to add the marketplace the first time, then pick user scope so they're on in every session. Start a new session (or run /reload-plugins) and they're active.
cd my-project && claude). Use a wide terminal: panes show beside the conversation from about 144 columns; in a narrower one, open them with the commands below./mods opens the Mods pane./plan add password reset to the login page. The planner (Opus) reads the code and returns numbered steps rated easy/medium/hard; the Plan pane opens with them. Nothing is edited yet.coder. For each one the router asks Haiku to rate it, then the coder runs on Haiku, Sonnet or Opus. In the Mods pane you'll see e.g.: `` model-router → haiku: Task: Add reset route … haiku → model-router: medium session → coding-agents:coder (sonnet): Add reset route coding-agents:coder (sonnet) → session: done in 41s: Changed 2 files … ``/plan-done 2 ticks off step 2; /plan-view reopens the Plan pane./routing shows how many tasks went to each model.You don't have to use /plan: asking for any coding work ("fix the failing date test") also goes through coder + router.
| Command | Mod | Does |
|---|---|---|
/plan <goal> | task-planner | Plan a goal with the Opus planner |
/plan-view | task-planner | Open the Plan pane |
/plan-done N | task-planner | Tick (or untick) step N |
/routing | model-router | Tasks per model so far |
/mods | mod-monitor | Open the Mods pane |
model-router options (in /config, or pluginConfigs.model-router.options in settings):
easyModel / mediumModel / hardModel: defaults haiku / sonnet / opus (aliases always mean the latest of each)classifierModel: default haikuroutedAgents: comma-separated agent types to route, default coding-agents:coder. Add more coding agents here as you build them.claude plugin validate model-router
claude plugin test model-router
claude --plugin-dir task-planner --plugin-dir coding-agents --plugin-dir model-router --plugin-dir mod-monitorhooks/register.ts 70 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Tier } from '../types'
5
6const tally = atom({ plugin: 'model-router', key: 'tally' } as const, { easy: 0, medium: 0, hard: 0, fallback: 0 })
7
8const RUBRIC = `You rate a coding task's difficulty so it can be sent to a cheap or a strong model.
9easy: mechanical change in one place - rename, text or config tweak, simple test, formatting, boilerplate.
10medium: ordinary feature or bug fix across a few files with clear requirements.
11hard: design decisions, subtle logic, concurrency, security, performance, data migrations, debugging an unclear failure, or broad refactors.
12When unsure between two, pick the harder one. Answer with exactly one word: easy, medium or hard.`
13
14export function parseTier(text: string): Tier | undefined {
15 const m = /\b(easy|medium|hard)\b/i.exec(text)
16
17 return m?.[1] ? (m[1].toLowerCase() as Tier) : undefined
18}
19
20export const register: Register = (on, options) => {
21 const str = (key: string, fallback: string) => (typeof options[key] === 'string' && options[key] !== '' ? (options[key] as string) : fallback)
22 const models: Record<Tier, string> = {
23 easy: str('easyModel', 'haiku'),
24 medium: str('mediumModel', 'sonnet'),
25 hard: str('hardModel', 'opus'),
26 }
27 const classifier = str('classifierModel', 'haiku')
28 const routed = new Set(str('routedAgents', 'coding-agents:coder').split(',').map(s => s.trim()).filter(Boolean))
29
30 on('session.start', async ($, e, next) => {
31 await $.command.register({ name: 'routing', description: 'Show how many coding tasks went to each model' })
32
33 return next(e)
34 })
35
36 on('command.run', { command: 'routing' }, async $ => {
37 const t = await read($, tally)
38
39 return {
40 text: [
41 `easy → ${models.easy}: ${t.easy}`,
42 `medium → ${models.medium}: ${t.medium}`,
43 `hard → ${models.hard}: ${t.hard}`,
44 `unrated (sent to ${models.medium}): ${t.fallback}`,
45 ].join('\n'),
46 }
47 })
48
49 on('agent.spawn', async ($, e, next) => {
50 // Only route our coding agents, and respect a model the caller chose on purpose.
51 if (!routed.has(e.subagentType) || e.model) return next(e)
52
53 const rated = await $.model.complete({
54 model: classifier,
55 system: [{ text: RUBRIC, cache: true }],
56 prompt: `Task: ${e.description}\n\n${e.prompt.slice(0, 6000)}`,
57 maxTokens: 5,
58 effort: 'low',
59 timeoutMs: 15000,
60 })
61 const tier = rated.isAnswered ? parseTier(rated.text) : undefined
62 const model = models[tier ?? 'medium']
63
64 await update($, tally, t => (tier ? { ...t, [tier]: t[tier] + 1 } : { ...t, fallback: t.fallback + 1 }))
65 $.ui.status(`router: ${e.description.slice(0, 30)} → ${model}${tier ? ` (${tier})` : ' (unrated)'}`)
66
67 return next({ ...e, model })
68 }).catch(($, e, next) => next(e))
69}
70types/index.d.ts 9 lines1export type Tier = 'easy' | 'medium' | 'hard'
2export type RouteTally = { easy: number; medium: number; hard: number; fallback: number }
3
4declare module 'claude-code' {
5 interface PluginState {
6 'model-router': { tally: RouteTally }
7 }
8}
9