Recipe: run Claude Code's subagents on the local model
Claude stays the brain: the main conversation goes to Anthropic as usual. Subagents, which do the reading and the legwork, run on the model in LM Studio and cost nothing upstream.
Checked on 2026-10-11 with Claude Code 2.1.296 and qwen/qwen3.8-27b. All
three ways of sending subagents local described below were run end to end.
1. Tell Janus about the local model
In janus.config.json, name an alias, point it at a model LM Studio has,
and switch routing on:
{
"local": {
"baseUrl": "http://127.0.0.1:1234",
"models": {
"janus-local": { "model": "qwen/qwen3.8-27b" }
}
},
"features": { "route": true }
}
Set baseUrl to your LM Studio's address; http://127.0.0.1:1234 is
right when it runs on the same machine as Janus.
The alias can be any name. A request for it is answered by the local model; every other request goes upstream untouched.
Run janus doctor. It checks that LM Studio has the model and says whether
it is loaded. Then start janus.
Before you switch: shadow it
To see how the local model would do on your own subagent traffic without it answering anything, start with a shadow rule:
{
"routing": [
{ "name": "watch-subagents", "class": "subagent", "route": "shadow", "local": "janus-local" }
]
}
Work as normal for a while, then run janus stats and read the "Shadow
runs" section. See Configuration
for how to read it.
When you are satisfied, there are two ways to switch on:
"route": "local-then-escalate"checks each local answer before the client sees it and asks Claude instead if it is broken. Safer, and slower to first output, because the answer is held back while it is checked. See Configuration."route": "local"streams the local answer as it is produced, with no check on what it says.
Neither can tell a wrong answer from a right one. That is what the shadow period is for.
2. Choose how requests get there
There are two ways. The first needs no change to Claude Code at all.
With a rule (simplest)
Add a rule that sends the subagent class local:
{
"routing": [
{ "name": "subagents", "class": "subagent", "route": "local", "local": "janus-local" }
]
}
Start Claude Code as usual. Janus recognises subagent requests by a header Claude Code sets, and sends them to the local model. Claude Code still believes it is talking to the model it asked for, so there is no warning and nothing to configure on its side.
While a subagent runs, Claude Code also asks for a few words on what it is
doing, for the line it shows you. Those requests are class status, not
subagent, so this rule leaves them with Claude: each carries the whole
subagent conversation for a five-word answer, which Claude reads from
cache in a moment and a local model takes seconds to a minute over.
If the local model cannot take a request (too large, LM Studio down), it
goes to Claude instead. Add "maxTokens" to the rule to keep large
subagent turns upstream on purpose.
The same happens while the local model is busy. Claude Code runs
subagents in parallel, and a local model shares your machine's memory, so
Janus sends a model one request at a time by default (maxConcurrent in
local.models); the other subagent turns go
to Claude meanwhile.
With the alias
This makes Claude Code itself ask for the local model, which is useful to pin one particular subagent.
Either of these works. Both were tested.
All subagents, with an environment variable:
ANTHROPIC_BASE_URL=http://127.0.0.1:8787 ENABLE_TOOL_SEARCH=true \
CLAUDE_CODE_SUBAGENT_MODEL=janus-local claude
or, with janus claude setting
the other two, CLAUDE_CODE_SUBAGENT_MODEL=janus-local janus claude.
One subagent, in its definition (.claude/agents/<name>.md):
---
name: line-counter
description: Counts the lines in files.
model: janus-local
tools: Read, Bash
---
You count lines in the files you are given and report one count per file.
Two more things worth setting up
Keep working when Claude is down. Add ifDown to a rule for the main
conversation, and the local model answers when Claude is unreachable, rate
limited, or failing:
{ "name": "main", "class": "main", "route": "upstream", "ifDown": "janus-local" }
Override for one session. To bypass local routing, or to keep a session off Anthropic entirely, start Claude Code with a header:
ANTHROPIC_CUSTOM_HEADERS="X-Janus-Route: upstream" claude
ANTHROPIC_CUSTOM_HEADERS="X-Janus-Route: local" claude
Both are described in Configuration.
What to expect
- It is slower. In the test runs a local turn took 7 to 24 seconds, against 1 to 3 seconds for Claude. The first turn of a subagent is the slowest; later turns reuse LM Studio's prompt cache.
- The first request after LM Studio starts waits for the model to load. Load it beforehand to avoid that.
- With the alias, Claude Code logs
unrecognized_model. It is a warning and the session carries on. With a rule there is no warning. - Claude Code's own cost figure is no longer right. With the alias it
does not know what
janus-localcosts; with a rule it prices local answers as if Claude had given them. Usejanus stats, which counts local answers as zero upstream cost, and says separately what they would have cost had Claude answered them. - With the alias, a request too large for the local model is refused, not rerouted. An alias has no upstream model behind it, so Janus returns an error saying how large the request was and what the model's context is. With a rule, the same request goes to Claude instead.
- Quality is the local model's. A subagent that reads files and
reports back is within reach of a good local model; one that must make
hard judgements may not be. To have Janus check each local answer and
ask Claude when one is broken, use a
local-then-escalaterule.
In the test, the same small task cost $0.24 at list price with everything upstream and $0.05 to $0.11 with the subagent local. That is two runs of a toy task, enough to show the mechanism works and nothing more.
Not verified
- Pinning Claude Code's small, fast model to the alias
(
ANTHROPIC_DEFAULT_HAIKU_MODEL). Headless sessions make no such calls, so there was nothing to observe. - Long subagent conversations, or ones that outgrow the loaded context.
- Any local model other than
qwen/qwen3.8-27b.
Checking that it worked
The console line for a locally answered request names the model with
(local):
16:36:03 subagent qwen3.8-27b (local) ✓ 12.1s tool call 5.5k in · 37% cached 250 out free
and janus stats shows the split under "By backend".