When the agent’s real cost is I/O
An AI coding agent burns most of its tokens on work that barely qualifies as reasoning. It reads five files to answer a question about a single method. It generates a test that mirrors the twenty tests beside it. It rewrites docs after a meeting. The frontier model handling all of it is wildly overqualified for the job, and every one of those tokens carries a price. By 2028, Gartner predicts AI coding costs will exceed the average developer’s salary; a quarter of engineering leaders already spend $200–$500 per developer per month on tokens, with some past $2,000.
The fix doesn’t require a platform team or another subscription. It’s a routing problem: send the grunt work to a cheap worker model and reserve the expensive one for problems that actually need it.
Two declarative agents
Portal by Spotify’s AiKA Modes are built for exactly this. A mode is a declarative agent running on an ephemeral runtime—think AWS Lambda, but for agents. You define instructions, choose a model, set parameters like temperature, and attach MCP tools. Portal handles infrastructure, API keys, and servers. Modes are callable from the Portal CLI or API and can be public or private.
The router works with two modes, both using Gemini 2.5 Flash as the worker model in the examples below. The model field accepts anything configured in your Portal instance.
Bulk-reader
Handles the case where Claude would otherwise read several large files just to answer one question.
name: bulk-reader
description: Bulk file reader for code analysis - delegates I/O from Claude Code
instructions: You are a precise code analyst. Read the provided files and answer the question concisely. Output structured bullets only. No greetings, no prose, no preambles. Lead every bullet with the exact name, type, or line number. Use nested bullets for details. Skip anything the caller did not ask for.
visibility: public
model: gemini-2.5-flash
resourceLimits:
temperature: 0.2
tags:
- coding
- delegation
Code-writer
For tests, config scaffolding, type stubs, or anything whose output is predictable from existing patterns.
name: code-writer
description: Boilerplate code generator - delegates output-heavy work from Claude Code
instructions: You generate code files based on a spec and reference files. Match the existing patterns, conventions, naming, and style exactly. Output only the code — no explanations, no markdown fences unless asked. If the spec is ambiguous, make reasonable choices that match the reference code's patterns.
visibility: public
model: gemini-2.5-flash
resourceLimits:
temperature: 0.2
tags:
- coding
- delegation
The “output only the code” instruction matters: without it, the model wraps everything in markdown fences and prose that Claude then has to parse.
Enforcing the route with a plugin
The first routing attempt was a block of advisory rules in CLAUDE.md. Claude could ignore them, and every project needed its own copy. The current version is a Claude Code plugin called shunt, which delegates through the Portal CLI actions registry so it works against any Portal instance with AiKA enabled.
Hooks block expensive reads
Claude Code hooks fire before every tool call. Shunt registers two PreToolUse hooks. check-file-size fires on every Read call; if the file exceeds a configurable line threshold (default: 350), the hook blocks the read and directs Claude to the /bulk-reader skill. Targeted reads with known offsets pass through. check-bash-read catches cat, head, tail, less, and more on large files—piped commands like cat file | grep are treated as targeted and pass. The threshold is adjustable via the SHUNT_MIN_LINES environment variable in your shell profile or .claude/settings.json:
{
"env": {
"SHUNT_MIN_LINES": "500"
}
}
Scripts wrap the CLI calls
Two bash scripts wrap the Portal CLI. Claude calls them with named arguments; the scripts build the request, invoke the action, unwrap errors, and report token usage to stderr. Modes are resolved by Portal case-insensitively, preferring your own mode, then your team’s, then public ones. Fork a public mode and yours automatically takes precedence.
bulk-read wraps each file in XML tags for boundaries and sends everything to the bulk-reader mode with the question.
bulk-read --question "What does this service do?" --paths src/Service.java src/Handler.java
# Follow-up: ask again with the same paths
bulk-read --question "Which methods call the database?" --paths src/Service.java src/Handler.java
Each delegation is one shot: invocation is ephemeral, nothing is stored server-side. Follow-ups are free where it matters because the corpus goes to the worker model and never enters Claude’s context.
code-write sends a spec and a reference file to the code-writer mode, strips markdown fences, and can write directly to disk so Claude never sees the generated code. The reference file is mandatory—without patterns to match, the worker produces context-free code that fits nothing in the project.
code-write --spec "Write tests for UserService" --reference tests/OrderTest.java --target tests/UserTest.java
# Output to stdout
code-write --spec "Generate a config stub" --reference config/existing.yaml
Skills bridge the gap
Two skill files tell Claude when and how to call the scripts. When a hook blocks a read, the block message points Claude to the skill with the exact invocation syntax. The system degrades gracefully: even if Claude ignores the skill, the hook still blocks the expensive read—the skill just makes the redirect smoother.
Measured savings and known limits
Testing against a Java monorepo across four scenarios measured what Claude would consume reading files directly versus consuming the bulk-reader’s summary. Mean savings were around 90%. The code-write scenario is harder to quantify because without shunt, Claude both reads references and pays expensive output-token rates to generate code; with shunt, the code goes straight to disk.
Delegation has boundaries. You can’t delegate editing—worker summaries lack reliable line numbers, so Claude still reads the specific section for targeted edits. You can’t delegate reasoning—the worker missed a subtle thread-safety bug that Claude caught in seconds, so debugging, architecture, and safety-critical code stay with the frontier model. Latency also adds up: each delegation is a network round-trip that typically takes 10–30 seconds, and Portal caps a single invocation at 30 seconds, forcing very large generations to be split. Below the line threshold, the overhead exceeds the savings.
Routing as a configuration problem
The plugin is Claude-specific, but the pattern—model routing powered by AiKA modes—is the load-bearing piece. Modes are reusable across projects and tools that can shell out to the Portal CLI, shareable publicly, and composable: a doc-writer for documentation, a reviewer for summaries, a translator for i18n. They also decouple the routing decision from the worker. The plugin decides when to delegate; the mode decides how to respond. Swap Gemini Flash for a cheaper model, change the system prompt, add MCP tools—the plugin doesn’t change.
That turns model routing from a systems engineering problem into a configuration problem. You describe what you want and name it.
Running it yourself
Install both plugins from the spotify/portal-ai-plugins marketplace:
claude plugin marketplace add spotify/portal-ai-pluginsclaude plugin install portal@portalclaude plugin install shunt@portal
In a new Claude Code session, run
/portal:setupto authenticate the Portal CLI against your Portal instance. Theportalplugin provides the CLI that shunt delegates through.Ask a question that spans multiple files. The
bulk-readerandcode-writermodes are already public—fork them in Portal to customize the worker model or instructions, and your version takes precedence automatically.



