Portal by Spotify cut my Claude Code token usage by 90%
Portal by Spotify cut my Claude Code token usage by 90%
Spotify 的 Portal 将我的 Claude Code Token 使用量降低了 90%
Most of what an AI coding agent does for me isn’t thinking. It’s I/O. Reading five files to answer a question about one method. Generating a test file that follows the exact same pattern as the twenty test files next to it. Updating docs after a meeting. Thousands of tokens gone and almost zero reasoning. The seat license isn’t what hurts, it’s the tokens. And you’re feeding all of it to a frontier model that’s wildly overqualified. What if you could route the grunt work to something cheaper that handles it just as well, and save the expensive model for the problems that actually need it?
AI 编程助手为我所做的大部分工作并非“思考”,而是 I/O(输入/输出)。比如为了回答关于某个方法的问题而读取五个文件;生成一个与旁边二十个测试文件模式完全一致的测试文件;或者在会议后更新文档。成千上万的 Token 消耗掉了,却几乎没有用到推理能力。真正让人心疼的不是席位授权费,而是 Token 费用。你把所有这些任务都喂给了一个能力过剩的前沿模型。如果能将这些繁琐工作分流给更便宜且同样能胜任的模型,并将昂贵的模型留给真正需要它的难题,那该多好?
It’s hardly just my problem. By 2028, AI coding costs are expected to blow past the average developer’s salary. A quarter of engineering leaders already burn $200–$500 per developer per month on tokens. Some are well past $2,000. The tooling pays for itself but only if you stop burning frontier tokens on work that doesn’t need them.
这绝非我个人的问题。预计到 2028 年,AI 编程成本将超过普通开发者的薪资。四分之一的工程负责人每月在每个开发者身上消耗的 Token 费用已达 200 至 500 美元,有些甚至超过了 2,000 美元。这些工具本身物有所值,但前提是你必须停止在不需要的地方浪费前沿模型的 Token。
Turns out, the fix didn’t require a platform team or a new subscription. Just two modes. Two modes, zero code.
事实证明,解决这个问题并不需要平台团队或新的订阅服务,只需要两种模式。两种模式,零代码。
This is exactly the kind of use case AiKA Modes in Portal by Spotify were built for. A mode is a declarative agent that runs on an ephemeral runtime - think AWS Lambda, but for agents. You define the instructions, pick a model, set parameters like temperature, and attach MCP tools. Portal handles the rest. No infra to manage, no API keys, no long-running servers. Modes are callable from the Portal CLI or API. They can be public (shared with the whole company) or private.
这正是 Spotify Portal 中 AiKA Modes 所针对的应用场景。模式(Mode)是一种运行在临时运行时上的声明式代理——可以将其理解为代理版的 AWS Lambda。你只需定义指令、选择模型、设置温度(temperature)等参数,并挂载 MCP 工具,剩下的交给 Portal 处理即可。无需管理基础设施,无需 API 密钥,也无需长期运行的服务器。模式可以通过 Portal CLI 或 API 调用,既可以是公开的(与全公司共享),也可以是私有的。
For this router to work I created two modes. Both use Gemini 2.5 Flash as the worker model in the examples below, but the model field accepts any model you have configured in your Portal instance. Pick whichever works for you.
为了实现这个路由功能,我创建了两种模式。在下方的示例中,两者都使用 Gemini 2.5 Flash 作为工作模型,但 model 字段支持你在 Portal 实例中配置的任何模型。你可以选择最适合你的那一个。
Mode 1: bulk-reader
模式 1:bulk-reader(批量读取器)
For when Claude would otherwise read multiple large files just to answer one question.
适用于 Claude 本需要读取多个大文件才能回答一个问题的情况。
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
Mode 2: code-writer
模式 2:code-writer(代码编写器)
For tests, config scaffolding, type stubs or anything where the output is predictable from existing patterns.
适用于测试、配置脚手架、类型存根(type stubs)或任何输出可以根据现有模式预测的任务。
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
That “output only the code” instruction matters. Without it, the model wraps everything in markdown fences and explanatory prose that Claude then has to parse through.
“仅输出代码”这条指令至关重要。如果没有它,模型会将所有内容包裹在 Markdown 代码块和解释性文字中,Claude 随后还得费力去解析这些内容。
Routing
路由
The first version of this was a block of routing rules in CLAUDE.md. It sort of worked: Claude would read the instructions and self-route to Portal. But it had problems. The rules were advisory, not enforced. Claude could ignore them. And every project needed its own copy of the instructions.
最初的版本是在 CLAUDE.md 中编写的一组路由规则。它勉强能用:Claude 会读取指令并自行路由到 Portal。但它存在问题:这些规则只是建议性的,而非强制执行,Claude 可能会忽略它们。此外,每个项目都需要一份独立的指令副本。
The current version is a Claude Code plugin called shunt. Delegation goes through the Portal CLI actions registry so the plugin works against any Portal instance with AiKA plugin enabled.
当前版本是一个名为 shunt 的 Claude Code 插件。分流通过 Portal CLI 操作注册表进行,因此该插件适用于任何启用了 AiKA 插件的 Portal 实例。
Layer 1: Hooks
第一层:钩子 (Hooks)
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 tells Claude to use the /bulk-reader skill instead. Targeted reads pass through - Claude already knows what section it needs.
- check-bash-read: catches cat, head, tail, less, and more on large files. Piped commands (cat file | grep) pass through since those are targeted reads.
Claude Code 的钩子会在每次工具调用前触发。Shunt 注册了两个 PreToolUse 钩子:
- check-file-size:在每次 Read 调用时触发。如果文件超过了可配置的行数阈值(默认:350 行),钩子会拦截读取操作,并告知 Claude 改用 /bulk-reader 技能。针对性的读取操作则会放行,因为 Claude 已经知道它需要哪一部分内容。
- check-bash-read:捕获针对大文件的 cat、head、tail、less 和 more 命令。管道命令(如 cat file | grep)会放行,因为它们属于针对性读取。
The threshold is configurable via the SHUNT_MIN_LINES environment variable. Set it in your shell profile or in .claude/settings.json:
阈值可以通过 SHUNT_MIN_LINES 环境变量进行配置。你可以在 shell 配置文件或 .claude/settings.json 中设置:
{
"env": {
"SHUNT_MIN_LINES": "500"
}
}
Layer 2: Scripts
第二层:脚本 (Scripts)
I have two bash scripts that wrap the Portal CLI calls. Claude calls a script with named arguments. The scripts handle everything internally: building the request, invoking the actions, unwrapping errors, and reporting token usage to stderr.
我有两个封装了 Portal CLI 调用的 bash 脚本。Claude 通过命名参数调用脚本。脚本在内部处理一切:构建请求、调用操作、解包错误,并将 Token 使用情况报告给 stderr。
Modes are addressed by name and resolved by Portal: case-insensitively, preferring your own mode, then your team’s, then public ones. Fork the public bulk-reader into a customized version and yours automatically takes precedence - no configuration needed.
模式通过名称寻址并由 Portal 解析:不区分大小写,优先匹配你自己的模式,其次是团队的,最后是公开的。如果你将公开的 bulk-reader 分叉(Fork)为自定义版本,你的版本会自动获得优先级——无需任何配置。
bulk-read wraps each file in XML tags for clear boundaries and sends them to the bulk-reader mode along with the question.
bulk-read 将每个文件包裹在 XML 标签中以明确边界,并将其与问题一起发送到 bulk-reader 模式。
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
Every delegation is one shot. The invocation is ephemeral (nothing is stored server-side) and re-sending the files on a follow-up is free where it matters, because the corpus goes to the worker model and never enters Claude’s context.
每次分流都是一次性的。调用是临时的(服务器端不存储任何内容),在后续追问中重新发送文件在关键点上是免费的,因为语料库直接发送给工作模型,从未进入 Claude 的上下文。
code-write sends a spec and a reference file to the code-writer mode, strips markdown fences from the output, and can write directly to disk. Claude never sees the generated code. The reference is required: without a file to match patterns against, the worker would generate context-free code that fits nothing in your project.
code-write 将规范和参考文件发送到 code-writer 模式,从输出中剥离 Markdown 代码块,并可以直接写入磁盘。Claude 从未见过生成的代码。参考文件是必须的:如果没有文件来匹配模式,工作模型将生成与项目完全脱节的上下文无关代码。
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 ...