# Spotify Portal shunt: cut Claude Code tokens 90% via bulk-reader + code-writer AiKA modes (extracted from tweet + Spotify Engineering)

> Source: <https://gist.github.com/vtri950/84b2261efbadba243870bf161764aeb7>
> Published: 2026-09-06 16:26:53+00:00

Original: [Spotify's Claude Code setup (@undefinedKi tweet)](https://x.com/undefinedKi/status/2095942506433089832?s=20) + [Spotify Engineering, Sep 3 2026](https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90). Generalized below for GitHub Copilot, Kiro, and Codex (OpenAI Codex CLI).

**Route grunt I/O to cheap model, keep reasoning on frontier, enforce with a hard block (>350 lines), not prompt instructions.**

Spotify: `Claude Opus/Sonnet` + `Gemini 2.5 Flash` via Portal AiKA Modes → **~90% token savings** on Java monorepo. Same pattern works anywhere if you add 3 layers: **1) Blocking interceptor 2) Cheap-worker scripts/MCP 3) Redirect instructions.**

Use *any* cheap/fast model: `gemini-2.5-flash` / `claude-haiku` / `gpt-4o-mini` / `gpt-4.1-nano` / `ollama/qwen2.5-coder`. Temp `0.2`.

**1. `bulk-reader`** — reads N files, returns bullets. Frontier never sees full files.

```
You are a precise code analyst. Read the provided files and answer the question concisely.
Output structured bullets only. No greetings, no prose. Lead every bullet with exact name/type/line.
Use nested bullets for details. Skip anything not asked.
```

**2. `code-writer`** — generates boilerplate from spec + reference, writes to disk.

```
You generate code files based on a spec and reference files. Match existing patterns, conventions, naming, style exactly.
Output only the code — no explanations, no markdown fences unless asked.
If ambiguous, choose what matches reference.
```

Spotify's Portal AiKA Modes implement these as `name: bulk-reader` / `code-writer` with `model: gemini-2.5-flash`. You can replace with a direct API call — see self-hosted script below.

**Invocation contract (tool-agnostic):**

```
bulk-read --question "What does this service do?" --paths src/A.java src/B.java
code-write --spec "Write tests for UserService" --reference tests/OrderTest.java --target tests/UserTest.java
# bulk-read wraps files in XML tags for boundaries; code-write strips fences and can write to --target
```

| Layer | Purpose | Claude Code (Spotify) | Generalized | 
|---|---|---|---|
| **1. Hook / Enforcement** | Hard block, not suggestion | `PreToolUse` hooks:`check-file-size` (>`SHUNT_MIN_LINES=350` ) +`check-bash-read` (cat/head/tail) | See per-tool below | 
| **2. Scripts / MCP** | Call cheap model, handle XML/fences, report tokens | `bulk-read` /`code-write` bash wrappers over`portal` CLI | Same scripts, but call `curl` to cheap model OR Portal CLI OR MCP server | 
| **3. Skills / Instructions** | Tell agent when/how to redirect | `/bulk-reader` skill (md) + block message | `copilot-instructions.md` /`.kiro/steering/` /`AGENTS.md` — but**always paired with Layer 1** | 

Lesson from Spotify: *"Written rules are a suggestion. A block is not."* `CLAUDE.md` routing was ignored until they added blocking hooks.

1. **Editing** — summaries lack reliable line numbers → keep targeted`read(offset,limit)` on frontier.
2. **Reasoning/debugging/architecture/thread-safety** — cheap model missed subtle bug frontier caught in seconds.
3. **Small files** — delegation overhead 10–30s + 30s cap; threshold exists for this reason.

Replace Portal with 20 lines of bash. Works for *all* tools:

``` bash
#!/usr/bin/env bash
# bulk-read — generic version, swap MODEL/endpoint as needed
set -euo pipefail
QUESTION="$1"; shift
FILES=("$@")
# Build XML payload
PAYLOAD=""
for f in "${FILES[@]}"; do
  PAYLOAD+="<file path=\"$f\">$(cat "$f")</file>\n"
done
# Call cheap model (example: OpenAI-compatible / Gemini / Anthropic)
curl -s https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $CHEAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<EOF | jq -r '.choices[0].message.content'
{
  "model": "gpt-4o-mini",
  "temperature": 0.2,
  "messages": [
    {"role":"system","content":"You are a precise code analyst. Output structured bullets only. No prose. Lead every bullet with exact name/type/line."},
    {"role":"user","content":"Question: $QUESTION\n\nFiles:\n$PAYLOAD"}
  ]
}
EOF
```

Expose same as MCP tool `bulk_read(question, paths)` and `code_write(spec, reference, target)` — then every agent can call it.

Copilot has **no `PreToolUse` hooks**, so enforcement must be via MCP + custom tool descriptions.

**Option A — MCP Router (recommended):**

```
// .vscode/mcp.json
{
  "servers": {
    "cheap-worker": {
      "command": "node",
      "args": ["./mcp-cheap-worker/index.js"],
      "env": { "CHEAP_MODEL": "gpt-4o-mini" }
    }
  }
}
```

- MCP exposes `bulk_read` (reads files*server-side* , returns summary) and`code_write` (writes file, returns path only).
- In `.github/copilot-instructions.md` :

```
## Routing (MANDATORY)
- NEVER read files >350 lines directly. Call `bulk_read` instead.
- For boilerplate/tests from a reference, call `code_write` — do not generate in context.
- Targeted reads with offset/limit are allowed for edits.
```

- **Hard block:** MCP`bulk_read` is the*only* tool that has file access; revoke Copilot's`readFile` for large files via extension`copilot-tool-filter` or workspace`settings.json` :

```
{ "github.copilot.chat.toolRestrictions": { "readFile": { "maxLines": 350 } } }
```

(Copilot CLI `gh copilot` similarly: use `GH_COPILOT_TOOL_POLICY` to deny large reads)

**Option B — Custom Chat Mode:**
Create `.github/chatmodes/research.mode.md` that *only* has `bulk_read` tool enabled, and `.github/chatmodes/build.mode.md` for code-write. Encourage `/research What does X do?` → auto-routes.

Kiro **has hooks** — closest 1:1 mapping to Claude.

```
// .kiro/hooks/bulk-reader.json
{
  "preToolUse": [
    {
      "tool": "fs_read",
      "check": "file.lines > 350 && !args.offset",
      "block": true,
      "message": "File >350 lines: use `bulk-read --question <q> --paths <files>` (cheap worker). For edits use offset/limit."
    },
    {
      "tool": "bash",
      "match": "^(cat|head|tail|less|more)\\s+",
      "blockIf": "targetFile.lines > 350"
    }
  ]
}
```

- Add steering: `.kiro/steering/routing.md` with same mandatory routing text + examples.
- Scripts identical: Kiro executes `bulk-read` bash script which can call Portal CLI*or* direct API. Kiro's ephemeral runtime similar to AiKA.
- Threshold configurable via `KIRO_SHUNT_MIN_LINES=500` env.

Codex uses `AGENTS.md` + `config.toml` + MCP.

```
# ~/.codex/config.toml or .codex/config.toml
[mcp_servers.cheap-worker]
command = "node"
args = ["./mcp-cheap-worker/index.js"]

[tools]
# Deny direct large reads — force MCP
deny = ["read_file({\"lines\" > 350})"]
php
<!-- AGENTS.md -->
## Model Routing (ENFORCED)
- Files >350 lines MUST use `bulk_read` MCP tool, not `read_file`.
- Boilerplate/tests MUST use `code_write` with a reference file.
- Direct generation without reference is forbidden.
```

- Codex respects tool `description` strongly — make`bulk_read` description:`MANDATORY for files >350 lines. Reads files server-side and returns concise bullets. Use instead of read_file.`
- CLI wrapper fallback: `codex --mcp cheap-worker` + shell alias`read = bulk-read` for`cat` .

| Scenario | Frontier | Cheap Worker | Method | 
|---|---|---|---|
| Q&A across 5 files | sees summary (200 tokens) | reads full files | `bulk-read` | 
| Generate test copying 20 examples | never sees output | reads 1 reference + writes | `code-write --target` | 
| Edit/fix bug | targeted `read(offset,limit)` | not used | direct | 
| Debug/architecture/thread-safety | full context | not used | direct | 

- Start `350` lines (~10k tokens). Measure: if`p50 delegation latency > time saved` , raise to`500` . Spotify uses`SHUNT_MIN_LINES` env.
- Temperature `0.2` for both workers;`0.0` for code-writer if you want deterministic scaffolding.
- Always require `--reference` for code-write — without it, cheap model hallucinates patterns.

```
# Spotify (Portal) — original
claude plugin marketplace add spotify/portal-ai-plugins
claude plugin install portal@portal && claude plugin install shunt@portal
/portal:setup

# Generic (no Portal) — add MCP server once, works for all three
git clone https://github.com/your-org/mcp-cheap-worker
npm i && cheap-worker --setup  # sets CHEAP_API_KEY, model
# then in Copilot/Kiro/Codex: call bulk_read / code_write — enforcement handles rest
```

- Tweet: [https://x.com/undefinedKi/status/2095942506433089832](https://x.com/undefinedKi/status/2095942506433089832)
- Spotify article: [https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90](https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90)
- Plugin: [https://github.com/sorantis/portal-ai-plugins/tree/add-shunt-claude/plugins/shunt](https://github.com/sorantis/portal-ai-plugins/tree/add-shunt-claude/plugins/shunt)
- AiKA Modes: [https://backstage.spotify.com/docs/portal/core-features-and-plugins/aika/modes](https://backstage.spotify.com/docs/portal/core-features-and-plugins/aika/modes)

*Generalized 2026-09-06 from Spotify's Claude-specific implementation. Principle is portable: cheap model + hard block + XML-wrapped delegation.*
