Run opencode, the open-source terminal AI coding agent, against apfel's OpenAI-compatible server so every token stays on-device at zero cost.
Verified: opencode 1.17.16 + apfel 1.8.2, macOS 26 (Apple Silicon). A real session transcript is at the bottom of this page.
Use the official installer - it fetches the platform binary to ~/.opencode/bin/opencode:
curl -fsSL https://opencode.ai/install | bashThen ensure ~/.opencode/bin is on your PATH.
Gotcha:
npm install -g opencode-aion its own may not produce a workingopencodecommand, because the package's post-install download is skipped under npm'sallow-scriptspolicy. Thecurlinstaller above avoids that.
apfel --serveThis serves the OpenAI API at http://127.0.0.1:11434/v1. Confirm it is up:
curl -s http://127.0.0.1:11434/v1/modelsWrite this to ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"compaction": { "auto": true, "prune": true, "reserved": 512 },
"default_agent": "lean",
"agent": {
"lean": {
"mode": "primary",
"model": "apfel/apple-foundationmodel",
"prompt": "You are a concise assistant. Answer directly.",
"permission": { "*": "deny" }
}
},
"provider": {
"apfel": {
"npm": "@ai-sdk/openai-compatible",
"name": "apfel",
"options": {
"baseURL": "http://127.0.0.1:11434/v1",
"apiKey": "not-needed"
},
"models": {
"apple-foundationmodel": { "name": "apple-foundationmodel" }
}
}
}
}The model id apple-foundationmodel must match exactly what apfel reports at /v1/models. apiKey is a placeholder: a local apfel server started without --serve-token needs no auth, but opencode's OpenAI-compatible provider still wants the field present.
One-shot (note the env var - see the 4096-token fix below):
OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1 opencode run --agent lean "In one sentence, what is a hash map?"Interactive:
OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1 opencodeSet that variable once in your shell profile (~/.zshrc) so you never forget it:
echo 'export OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1' >> ~/.zshrcapfel's on-device model has a 4096-token context window on macOS 26 (8192 on macOS 27 - apfel reads the real size at runtime; everything on this page was measured on macOS 26, and the failure mode is identical on macOS 27, just with more headroom). opencode is a full coding agent, and it injects your instruction files into the system prompt on every request. It loads them in this order (each category accumulates - they do not replace each other):
- Local
AGENTS.md/CLAUDE.md(walking up from the current directory) - Global
~/.config/opencode/AGENTS.md - Claude Code fallback:
~/.claude/CLAUDE.md
That third one is the trap. opencode has undocumented Claude Code compatibility: if you use Claude Code, your global ~/.claude/CLAUDE.md gets pasted into opencode's system prompt verbatim. A big one (this machine's was ~12 KB / ~3,300 tokens) fills the 4096-token window before you type a word, and apfel returns an honest HTTP 400:
Error: Input exceeds the model's context window. Shorten the conversation history.
Set this environment variable. It tells opencode to stop loading ~/.claude/CLAUDE.md:
export OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1Proven on this machine, with the 12 KB ~/.claude/CLAUDE.md left in place:
| Request to apfel | Result | |
|---|---|---|
| Without the var | 13,461 bytes | 400 - context overflow |
OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1 |
2,498 bytes | 200 OK, real answer |
The instructions field in opencode.json does not help here - it adds files, it cannot remove the auto-loaded CLAUDE.md. The env var is the fix. (To drop all Claude Code compatibility, not just the prompt, use OPENCODE_DISABLE_CLAUDE_CODE=1.)
With CLAUDE.md out of the way, two things in the config above keep you comfortably inside 4096 tokens:
"permission": { "*": "deny" }on theleanagent stops opencode sending tool schemas (they eat the window fast).- A short custom
"prompt"replaces opencode's default agent instructions.
Also keep any project AGENTS.md small - it loads too, and the env var does not touch it.
Because of that window, apfel is a great opencode backend for short Q&A and small, focused edits - not for large-repo, many-tool, long-running agent sessions. That is a property of the on-device model, not the wiring. The apfel --count-tokens flag (see docs/cli-reference.md) preflights how much a prompt will cost against the window.
Every one of these was hit and confirmed while testing on 2026-07-09:
- Install:
npm install -g opencode-aican leave you with no working binary (post-install script skipped by npmallow-scripts). Use thecurlinstaller; the binary lands at~/.opencode/bin/opencode. - The 4096-token window is the whole story, and global
~/.claude/CLAUDE.mdis the usual killer. opencode pastesAGENTS.md, localCLAUDE.md, and (undocumented Claude Code compatibility) global~/.claude/CLAUDE.mdinto the system prompt. A large globalCLAUDE.mdalone (~12 KB / ~3,300 tokens here) overflows the window before you type anything - HTTP 400. Fix:export OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1- proven to drop the request from 13,461 to 2,498 bytes (400 to 200). Theinstructionsconfig field cannot remove it; only the env var does. - Deny tools.
"permission": { "*": "deny" }stops opencode sending tool schemas, which otherwise consume a big slice of the 4096 tokens. - Set a short agent
prompt. It replaces opencode's default agent instructions (verified: the custom prompt does take effect); without it the default coding-agent preamble is larger. apiKeymust be present in the provideroptionseven though a local apfel server needs no auth - opencode's@ai-sdk/openai-compatibleprovider expects the field. Any placeholder works.- Model id must match
/v1/modelsexactly (apple-foundationmodel). A mismatch fails the request. - opencode makes two calls per turn: a small title-generation call (always fits) plus the main agent call (the one that can overflow). Seeing the title call succeed but the answer fail is the classic 4096-overflow signature.
--puredoes not help the overflow - it disables plugins, not instruction-file ingestion.- Restart opencode after config changes - it does not always hot-reload provider config.
apfel 1.8.2 server, opencode 1.17.16, lean agent, with a 12 KB global ~/.claude/CLAUDE.md present (the fix env var set):
$ OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1 opencode run --agent lean \
"In one sentence, what is a binary search?"
> lean · apple-foundationmodel
A binary search is an efficient algorithm for finding an item from a sorted
array of items, by repeatedly dividing the search interval in half.
apfel's request log for that turn - every call 200 OK, well inside the window, $0.00:
POST /v1/chat/completions 200 67ms stream tokens=~591 request bytes=2498
POST /v1/chat/completions 200 44ms stream tokens=~198 request bytes=713
Without OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1, the same turn sent 13,461 bytes and apfel returned 400 - Input exceeds the model's context window.
The original config and the first working screenshot came from @tvi (Tomas Virgl). This page adds an end-to-end re-verification on current opencode and the 4096-token instruction-file gotcha.