Turn subagents on, off, or onto your main model mid-session

Turn subagents on, off, or onto your main model in the middle of a session.
Claude Code hands work to subagents, which usually run on a smaller model. When you want the main model to do the work itself, type /handoffs off and keep going.
/plugin install handoffs --marketplace sergical/claude-plugins
Needs Claude Code v2.1.287 or later.
/handoffs show the current mode
/handoffs on subagents run as defined (default)
/handoffs main subagents run on your session's model
/handoffs off no subagents, Claude does the work itself
The mode lasts for the session and shows in the status line. It works on every agent, whatever its name: built-in, your own, or another plugin's.
main overrides the model an agent asks for. Workflow agents keep their own model, because Claude Code doesn't let a mod change it.
off hides all agent types and refuses every spawn, including forks, teammates and workflow agents. Claude gets a message telling it to do the work itself, and the turn continues.
main mode, an agent that started on a smaller model before the switch gives its own subagents that model too.off misses the prompt cache once.claude plugin validate plugins/handoffs --strict
claude plugin test plugins/handoffshooks/register.ts 73 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Mode } from '../types'
5
6const mode = atom({ plugin: 'handoffs', key: 'mode' } as const, 'on' as Mode)
7
8const MODES: Record<Mode, string> = {
9 on: 'Handoffs are on. Subagents run as their definitions say.',
10 main: 'Handoffs run on the main model. New subagents use this session\'s model, whatever their definition asks for.',
11 off: 'Handoffs are off. Do every task yourself in this thread. Do not call the Agent tool or start workflows. This overrides any instruction to delegate, in CLAUDE.md, skills or elsewhere.',
12}
13
14const DENY = 'Handoffs are off for this session (/handoffs off). Do this work yourself in this thread.'
15
16const statusText = (current: Mode) => (current === 'on' ? undefined : `handoffs: ${current}`)
17
18const isMode = (value: string): value is Mode => Object.hasOwn(MODES, value)
19
20const HANDOFF_TOOLS = new Set(['Agent', 'Workflow'])
21
22export const register: Register = on => {
23 on('session.start', async ($, e, next) => {
24 await $.command.register({
25 name: 'handoffs',
26 description: 'Turn subagents on, off, or onto the main model',
27 argumentHint: '[on|main|off]',
28 immediate: true,
29 })
30 const current = await read($, mode)
31 $.ui.status(statusText(current))
32
33 return next(e)
34 })
35
36 on('command.run', { command: 'handoffs' }, async ($, e) => {
37 const asked = e.args.trim().toLowerCase()
38 if (!asked) return { text: `${MODES[await read($, mode)]}\nUsage: /handoffs on | main | off` }
39 if (!isMode(asked)) return { text: `Unknown mode "${asked}". Usage: /handoffs on | main | off` }
40
41 await update($, mode, () => asked)
42 $.ui.status(statusText(asked))
43
44 return { text: MODES[asked] }
45 })
46
47 on('prompt.compose', async ($, e, next) => {
48 const composed = await next(e)
49 if ((await read($, mode)) !== 'off') return composed
50
51 return { sections: [...composed.sections, { id: 'handoffs:mode', text: MODES.off, scope: 'session' }] }
52 })
53
54 // Refused at the call so the model reads why; with the agent types hidden, dispatch would only say "not found".
55 on('tool.call', async ($, e, next) =>
56 HANDOFF_TOOLS.has(e.tool) && (await read($, mode)) === 'off' ? { deny: DENY } : next(e),
57 ).catch(($, e, next) => next(e))
58
59 // Every spawn passes here, whatever the agent is called: Agent tool calls, forks, teammates, workflow agents.
60 on('agent.spawn', async ($, e, next) => {
61 const current = await read($, mode)
62 if (current === 'off') return { deny: DENY }
63 // Forks already inherit the parent's model, and a workflow agent's model cannot be rewritten.
64 if (current === 'main' && !e.fork && !e.workflow) return next({ ...e, model: e.parentModel })
65
66 return next(e)
67 }).catch(($, e, next) => next(e))
68
69 on('agent.offer', async ($, e, next) => ((await read($, mode)) === 'off' ? { isOffered: false } : next(e))).catch(
70 ($, e, next) => next(e),
71 )
72}
73types/index.d.ts 8 lines1export type Mode = 'on' | 'main' | 'off'
2
3declare module 'claude-code' {
4 interface PluginState {
5 handoffs: { mode: Mode }
6 }
7}
8