Recursive shipped Flint in late October 2026, a 40-billion-parameter mixture-of-experts coding model that activates about 2.5 billion parameters per token. The company says the weights sit on Hugging Face under the Apache 2.0 license, so anyone can run them on their own hardware (see Recursive’s Flint announcement). For OpenCode users, that rewrites the math on a zero-dollar setup. A free hosted model can vanish next week. A model on your own disk stays put.
This tutorial builds a $0 open-source AI coding assistant stack around OpenCode in 12 steps. You will inventory the free hosted models, pull Flint to your machine, serve it through an OpenAI-compatible endpoint, wire both tiers into one config, and add a guard script that blocks accidental paid usage. A small benchmark harness then tells you which tier deserves which job. If you want the plain install first, the earlier OpenCode and Ollama setup walkthrough covers that ground, and this guide picks up where it stops.
Why build around churn instead of picking one free model and hoping? Stack Overflow’s 2025 developer survey reported that 31% of developers already use AI agents at work, according to the survey team’s results write-up. Most of those developers pay a vendor. The free alternative works, but only if you treat “free” as a moving target with data terms attached. Everything below follows that rule.
Don't miss new tech stories on Google
Add Shattered once in the Google app and our stories show up in your news suggestions.
What “Free” Means in OpenCode Right Now #
OpenCode’s own site states that the app is 100% free to use and ships with a set of free models, with extra costs only when you connect other providers. That sentence hides three separate things: a free client, a rotating list of free hosted models, and paid plans sitting one click away. Mixing them up is how people end up with a surprise bill.
The free hosted list rotates. As of November 2, 2026, OpenCode’s models page labels GLM-5.3-Flash Free, Nemotron 3.5 Lightning Free, and Nemotron 3.5 Super VL Free as available for a limited time. The page says the GLM provider follows a zero-retention policy and does not train on your data. For both Nemotron entries, the page says the team is using the window to collect feedback and improve the model. Read that as a reason to keep proprietary code away from those two endpoints.
A third-party account from October 19, 2026, posted on note.com, reached the same conclusion from the other direction. It listed several free endpoints at $0 for input and output, called them limited-time offers, and found that data terms differed by provider. Some providers kept requests for a period, and some could use collected data to improve their models. “Free” did not uniformly mean private.
Flint adds a fourth option. Catalog listings show OpenCode carrying a hosted opencode/flint-40b-free identifier, and OpenRouter listing recursive/flint-v1.0:free at $0 for input and output with a context window of roughly 262,000 tokens. Reports also say one OpenCode free route was deprecated around October 22, so treat every identifier in this article as something to confirm on your own machine before you rely on it.
Paid rungs exist too. OpenCode Go costs $10 a month, and Go Teams costs $20 per member per month. One third-party directory reports a Go Plus tier at $40 a month, but that figure comes from a single source, so check the pricing page before you budget around it.
Release lines matter as well. OpenCode now ships a 1.x line and a 2.x line side by side. Third-party trackers report v2.2.5 as the newest build, dated October 30, 2026, while the 1.x line reached v1.19.5 on October 28. The project distributes 2.x through its website, npm, and Homebrew rather than marking it as the GitHub “Latest” release, so an old client does not move over by accident. A late-October OpenCode roundup shows how fast these builds change, which is one more reason to pin versions in this project.
Prerequisites and Versions #
You need a machine that can hold a quantized 40B-parameter model, a terminal, and about an hour. The table lists what the project assumes. Where a version is not pinned, install the current release and write the number into your notes so the setup stays reproducible.
| Component | Version or size | Why it matters |
|---|---|---|
| OpenCode | 2.2.x line (v2.2.5 reported newest on Oct 30, 2026) | Client for both free hosted and local models |
| Python | Not listed | Runs the guard and benchmark scripts (standard library only) |
| Git | Any current release | Checkpoints before the agent edits files |
Hugging Face CLI ( hf ) |
Current release of huggingface_hub |
Downloads the Flint weights |
| llama.cpp | Build from the current main branch | Converts, quantizes, and serves the model |
| Disk space | About 80 GB for the BF16 download, plus 25 GB for a 4-bit file (estimate) | Weights land twice during conversion |
| RAM or unified memory | 32 GB minimum for a 4-bit build, 48 GB or more preferred (estimate) | The whole file loads into memory |
| OpenRouter account | Free tier, optional | Hosts the free Flint route for side-by-side tests |
Two hardware notes keep expectations honest. Recursive says Flint generates more than 125 tokens per second on an M4 Pro MacBook Pro and more than 150 tokens per second on a 64GB NVIDIA RTX Spark at short contexts, using its own optimized inference engine. Those numbers come from the vendor and apply to its engine, not to the llama.cpp path in this guide, so expect lower throughput from the generic route. Reporting also describes laptops with roughly 48 GB of memory running the model, which is a single-source figure.
The 80 GB figure is simple arithmetic. About 40 billion parameters at 2 bytes each in BF16 lands near 80 GB, and Recursive’s own download command carries the same “about 80 GB” note. A 4-bit quantization shrinks that to roughly a quarter. That is my estimate rather than a published spec, so measure the real file after you build it.
Operating system choice shapes the setup more than the model does. macOS on Apple silicon is the easiest path, since llama.cpp uses the GPU through Metal without extra flags. Linux with an NVIDIA card needs the CUDA build flag shown in Step 5, and the SGLang container from Recursive’s card suits that case well. Windows users get the smoothest ride through WSL2, though a native llama.cpp build also works. Whichever route you pick, run every command in this guide from the same shell session so paths line up, and keep the project folder on a fast local disk rather than a network share. Weight files this large punish slow storage during conversion.
The Architecture: Three Tiers and a Guard #
The stack has three tiers and one referee. Tier one is the free hosted models inside OpenCode. They cost nothing, start instantly, and can change or disappear without notice. Tier two is Flint on your own machine. It costs electricity and patience, but it never rotates away. Tier three is a paid ceiling you use deliberately when both free tiers fail. The referee is a guard script that fails loudly if your config points anywhere that could bill you.
Why Flint for the local tier? Recursive’s model card reports 54.9% on Terminal-Bench 2.1, 89.7% on SWE-bench Pro v2, and 74.2% on SWE-bench Verified. Those are the company’s release evaluations, so treat them as a starting claim rather than an audit. TechCrunch’s launch coverage also carried the company’s claim that AI largely built the model, which is likewise a vendor statement. The model supports text input and output, reasoning, and multi-turn tool use, which is what an agent loop needs. The Flint release coverage goes deeper on the architecture if you want the background.
Other open models compete for the local slot, and the right pick shifts monthly. The October open-source LLM roundup compares several of them on price and strength. This guide uses Flint because it is small enough to run on one machine and permissively licensed, and because OpenCode’s catalog already lists a hosted Flint route you can compare against your local copy.
The paid ceiling deserves a number so that “last resort” means something. Mistral Large 4, an open-weights model, lists API prices of $0.70 per million input tokens, $0.07 per million cached input tokens, and $2.10 per million output tokens. A month with 20 million input tokens and 4 million output tokens would cost $14.00 plus $8.40, or $22.40, before any caching discount. Mistral Large 4 is a roughly 1-trillion-parameter model, so it is not a laptop download. Use its API as a paid escape hatch, not a local tier.
| Tier | Example | Per-token cost | Main catch |
|---|---|---|---|
| 1. Free hosted | GLM-5.3-Flash Free, Nemotron 3.5 Lightning Free | $0 | Limited-time offers, data terms vary by provider |
| 2. Local | Flint on llama.cpp | $0 | Hardware, setup time, lower speed than vendor claims |
| 3a. Subscription | OpenCode Go | $10 per month | Allowances apply, plan details can change |
| 3b. Team plan | OpenCode Go Teams | $20 per member per month | Priced per seat |
| 3c. Metered API | Mistral Large 4 | $0.70 in, $2.10 out per million tokens | Bills by usage, needs a key |
Steps 1 to 3: Install OpenCode, Inventory the Free Models, Read the Terms #
These first three steps cost nothing and change nothing on your disk beyond one folder. They also produce the evidence you need later, because a snapshot of today’s free list is the only way to notice tomorrow’s changes.
Step 1: Install OpenCode and Confirm the Release Line
Copy the install command from the OpenCode homepage rather than from a blog post, because the project ships through several channels and the commands differ. The official pages list an install script, npm, Homebrew, mise (which needs mise 2026.10.9 or later), and Docker images with versioned tags such as ghcr.io/anomalyco/opencode:2.0.0. Pick one, run it, then create the project folder and confirm what you got.
mkdir -p ~/zero-dollar-opencode/{bench,scripts,models,snapshots}
cd ~/zero-dollar-opencode
git init
opencode --version
Expected output on the 2.x line looks like this:
2.2.5
If the command prints a 1.x number, you are on the older line. The 1.19.0 release notes describe an upgrade path through opencode upgrade --major, and the TUI also exposes an /update command. Run the major upgrade only after you commit your current config, because 2.x carries breaking changes that its release notes call out. Read those notes line by line before you confirm the jump.
Step 2: Snapshot the Free Model List
List the models your install can see and keep a dated copy. The opencode models subcommand prints provider and model identifiers. If your build lacks it, open the model picker in the TUI instead, since v2.2.1 added a redesigned picker with favorites, recent models, providers, and variants.
opencode models | tee snapshots/all-$(date +%F).txt | grep -i free | tee snapshots/free-$(date +%F).txt
wc -l snapshots/free-$(date +%F).txt
The output shape matters more than the exact names, because the list changes:
opencode/flint-40b-free
opencode/<other-model>-free
...
9 snapshots/free-2026-11-02.txt
A community inventory from October 26 counted nine free models in the OpenCode Console, including DeepSeek V4.1 Flash Free, GLM-5.3 Flash Free, Nemotron 3.5 Super VL Free, Nemotron 3.5 Lightning Free, Beam Free, Flint 40B Free, and Big Pickle. That is a single-source count, so your own number may differ. Save whatever you see. In Step 12 you will diff this file against future snapshots.
Step 3: Classify Your Code Against the Data Terms
Free models differ in what happens to your prompts, and the differences decide which tier may touch which repository. Build a one-page policy before you write any configuration. The table below records what the official pages and the third-party account say today.
| Endpoint | What the sources say | Safe for |
|---|---|---|
| GLM-5.3-Flash Free | OpenCode’s page says the provider follows a zero-retention policy and does not train on your data | Private repositories, with moderate trust |
| Nemotron 3.5 Lightning Free | Page says the team is collecting feedback during the limited-time window | Open-source code only |
| Nemotron 3.5 Super VL Free | Same wording as above | Open-source code only |
| Hosted Flint free routes | Hosted terms are separate from the Apache 2.0 weight license, per Recursive | Open-source code only until you read the host’s terms |
| Local Flint | Recursive says you can run it with your data kept on your device | Everything, including secrets-adjacent code |
Recursive makes the licensing point explicit: the Apache 2.0 license covers the weights, while hosted API terms and data practices are separate. That distinction trips up many teams, and the DeepSeek, Qwen, and Mistral open-weight comparison shows how much license terms differ across model families. Running the weights yourself gives you the license. Calling someone else’s endpoint that happens to serve the same weights gives you that host’s terms.
Write the policy as three rules in a file named POLICY.md and commit it. Public and open-source code may use any tier. Private code may use the zero-retention endpoint or the local model. Anything touching credentials, customer data, or unreleased security fixes stays on the local model only.
Steps 4 and 5: Download and Serve Flint Locally #
The local tier is where the stack earns its keep. Down takes patience, converting takes a few minutes, and serving takes one command. Budget most of your hour here.
Step 4: Download the Flint Weights
Recursive’s model card gives the download command directly. Install the Hugging Face CLI, check your free disk space, then pull the repository into the project’s models folder.
pip install -U "huggingface_hub[cli]"
df -h .
hf download RecursiveSI/flint --local-dir models/flint
du -sh models/flint
The card describes the repository as the initial BF16 checkpoint plus configuration, tokenizer, chat template, and an SGLang serving kit, and it notes the download is about 80 GB. Expected output after the pull:
80G models/flint
Add models/ to .gitignore right now. Committing 80 GB of weights by accident is a classic way to break a repository.
printf 'models/\nsnapshots/*.tmp\nresults.jsonl\n' >> .gitignore
Step 5: Convert, Quantize, and Serve with llama.cpp
Several routes exist for serving Flint. Recursive’s card includes an SGLang container command that binds the server to 127.0.0.1:30000, which suits an NVIDIA GPU box. Recursive also released an engine called rec, tuned for M4 Pro and M5 Pro or Max Macs, though that detail comes from a single source. Reports also mention Ollama and llama.cpp support landing within days of the release, and LM Studio carries a Flint model page. This guide uses llama.cpp because it runs on Linux, macOS, and Windows with one set of commands. The self-hosted coding assistant guide with Qwen Code uses a similar local server pattern if you want a second reference.
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build # add -DGGML_CUDA=ON on an NVIDIA machine
cmake --build build --config Release -j
cd ..
python3 llama.cpp/convert_hf_to_gguf.py models/flint --outfile models/flint-bf16.gguf --outtype bf16
llama.cpp/build/bin/llama-quantize models/flint-bf16.gguf models/flint-Q4_K_M.gguf Q4_K_M
ls -lh models/*.gguf
If the converter rejects the architecture, your llama.cpp checkout predates Flint support. Pull the latest main branch and rebuild before you try again. Once the quantized file exists, start the server on the loopback address only:
llama.cpp/build/bin/llama-server \
-m models/flint-Q4_K_M.gguf \
--alias flint \
--host 127.0.0.1 --port 8080 \
-c 65536 --jinja -ngl 99
The --jinja flag applies the model’s chat template, which tool calling depends on. The -c 65536 flag sets a 64K context, far below the 262,144-token window Recursive documents, because a full-length KV cache would swamp most machines. Test the endpoint with one request before touching OpenCode:
curl -s http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"flint","messages":[{"role":"user","content":"Write a Python one-liner that reverses a string."}],"max_tokens":80}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['choices'][0]['message']['content'])"
A working server answers with a short snippet such as s[::-1]. If you see that, tier two is alive.
Steps 6 and 7: Wire the Endpoint and Lock Down Permissions #
With both tiers reachable, one config file ties them together. The same file also decides what the agent may do without asking, which matters more than any model choice once the agent starts running shell commands.
Step 6: Write opencode.json
OpenCode accepts custom OpenAI-compatible providers through a provider block. The example below follows the schema in the 1.x docs, and the 2.x docs live under the project’s /v2/docs path, so compare the field names there if OpenCode complains about the file. The default model points at the free hosted Flint route, and the local server is registered as a second provider.
{
"$schema": "https://opencode.ai/config.json",
"model": "opencode/flint-40b-free",
"provider": {
"flint-local": {
"npm": "@ai-sdk/openai-compatible",
"name": "Flint (local llama.cpp)",
"options": { "baseURL": "http://127.0.0.1:8080/v1" },
"models": {
"flint": {
"name": "Flint 40B Q4_K_M",
"limit": { "context": 65536, "output": 8192 }
}
}
}
}
}
Save it in the project root. Official examples reference models in the provider/model format, for example opencode/gpt-6.1-sol, so the local tier is addressed as flint-local/flint. Launch OpenCode in the folder and switch with the model picker, or pass the model on the command line if your build supports a flag for it.
Step 7: Add Permission Rules and an AGENTS.md
A free model that deletes your working tree still costs you. Tell OpenCode to ask before it edits or runs commands, and deny the dangerous ones outright. The permission schema has changed between releases, so treat this block as a starting point and confirm it against the docs for your installed version.
{
"permission": {
"edit": "ask",
"bash": {
"git push*": "deny",
"rm -rf*": "deny",
"curl*": "ask",
"*": "ask"
}
}
}
Merge that block into opencode.json. Then create an AGENTS.md file that gives every model, free or local, the same house rules. Small models follow short rules better than long ones, so keep it under twenty lines.
- Run the test suite before declaring a task done.
- Never edit files under `models/` or `snapshots/`.
- Never print environment variables or read `.env` files.
- Prefer small diffs. One concern per change.
- Ask before adding a dependency.
Commit both files. The team pilot guide for AI coding assistants uses the same AGENTS.md habit when it rolls assistants out to several developers.
Steps 8 and 9: Build the Guard and the Benchmark Harness #
Two small Python scripts turn this from a pile of settings into a repeatable system. Neither needs a third-party package, so they run anywhere Python 3.11 runs.
Step 8: Write the Zero-Dollar Guard
The guard reads opencode.json and fails when the default model could bill you. It accepts two things only: hosted OpenCode models whose identifier ends in -free, and the loopback-only local provider. It also warns when paid API keys sit in your shell, because tools often discover providers from environment variables. That auto-discovery behavior varies by version, so the check errs on the side of caution.
#!/usr/bin/env python3
import json, os, sys
from urllib.parse import urlparse
LOCAL_PROVIDERS = {"flint-local"}
LOCAL_HOSTS = {"127.0.0.1", "localhost", "::1"}
PAID_ENV = ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "MISTRAL_API_KEY")
def model_ok(ref):
if not ref:
return True
provider, _, name = ref.partition("/")
if provider in LOCAL_PROVIDERS:
return True
return provider == "opencode" and name.endswith("-free")
def main(path):
cfg = json.load(open(path))
errors, warnings = [], []
for key in ("model", "small_model"):
if not model_ok(cfg.get(key)):
errors.append("%s=%r is neither a free hosted nor a local model" % (key, cfg.get(key)))
for name, prov in cfg.get("provider", {}).items():
host = urlparse(prov.get("options", {}).get("baseURL", "")).hostname
if name not in LOCAL_PROVIDERS:
errors.append("provider %s is not on the allowlist" % name)
elif host not in LOCAL_HOSTS:
errors.append("provider %s points at %s, expected loopback" % (name, host))
for var in PAID_ENV:
if os.environ.get(var):
warnings.append("%s is set in this shell" % var)
for w in warnings:
print("WARN " + w)
for e in errors:
print("FAIL " + e)
if not errors:
print("OK config routes only to free or local models")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "opencode.json"))
Save it as scripts/guard.py and run it. A clean config prints one line:
OK config routes only to free or local models
Change model to a paid identifier and the script exits with status 1, which makes it a natural pre-commit hook and CI check. Wire it in with one line in .git/hooks/pre-commit: python3 scripts/guard.py opencode.json.
Step 9: Write the Benchmark Harness
Vendor benchmarks measure vendor tasks. Your repository has its own habits, so the harness asks each endpoint to solve five small functions and checks the answers with real unit tests. Five tasks is a smoke test, not a leaderboard. It still catches the failures that matter most: an endpoint that is down, rate limited, or too weak for routine work. The free AI coding test harness guide scales the same idea to larger task sets if you outgrow this one.
#!/usr/bin/env python3
import json, os, re, statistics, subprocess, sys, tempfile, time
import urllib.error, urllib.request
TASKS = [
{"id": "slugify",
"prompt": "Write slugify(text): lowercase, replace runs of non-alphanumeric characters with one hyphen, strip leading and trailing hyphens.",
"tests": "assert slugify('Hello, World!') == 'hello-world'\nassert slugify(' A B ') == 'a-b'\nassert slugify('---') == ''\n"},
{"id": "duration",
"prompt": "Write parse_duration(s) that turns strings like '1h30m', '45s', '2h' or '1h2m3s' into total seconds as an int.",
"tests": "assert parse_duration('1h30m') == 5400\nassert parse_duration('45s') == 45\nassert parse_duration('2h') == 7200\nassert parse_duration('1h2m3s') == 3723\n"},
{"id": "dedupe",
"prompt": "Write dedupe(items) that removes duplicates from a list while keeping first-seen order.",
"tests": "assert dedupe([3,1,3,2,1]) == [3,1,2]\nassert dedupe([]) == []\nassert dedupe(['a','a']) == ['a']\n"},
{"id": "intervals",
"prompt": "Write merge_intervals(pairs) that merges overlapping [start, end] pairs and returns them sorted.",
"tests": "assert merge_intervals([[1,3],[2,6],[8,10]]) == [[1,6],[8,10]]\nassert merge_intervals([[1,4],[4,5]]) == [[1,5]]\nassert merge_intervals([]) == []\n"},
{"id": "chunk",
"prompt": "Write chunk(seq, n) that splits a list into consecutive lists of length n, with a shorter final list if needed.",
"tests": "assert chunk([1,2,3,4,5], 2) == [[1,2],[3,4],[5]]\nassert chunk([], 3) == []\nassert chunk([1,2], 5) == [[1,2]]\n"},
]
def call(ep, prompt):
body = json.dumps({
"model": ep["model"], "temperature": 0, "max_tokens": 4096,
"messages": [
{"role": "system", "content": "Reply with one fenced Python code block and nothing else."},
{"role": "user", "content": prompt},
],
}).encode()
headers = {"Content-Type": "application/json"}
key = os.environ.get(ep.get("key_env", ""), "")
if key:
headers["Authorization"] = "Bearer " + key
for attempt in range(4):
req = urllib.request.Request(ep["base_url"] + "/chat/completions", body, headers)
start = time.time()
try:
with urllib.request.urlopen(req, timeout=300) as resp:
return json.load(resp), time.time() - start
except urllib.error.HTTPError as err:
if err.code in (429, 503) and attempt < 3:
time.sleep(4 * 2 ** attempt)
continue
raise
def extract(text):
match = re.search(r"```(?:python|py)?\n(.*?)```", text, re.S)
return match.group(1) if match else text
def passes(code, tests):
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "t.py")
with open(path, "w") as fh:
fh.write(code + "\n\n" + tests)
try:
run = subprocess.run([sys.executable, path], cwd=tmp, capture_output=True,
timeout=10, env={"PATH": os.environ.get("PATH", "")})
return run.returncode == 0
except subprocess.TimeoutExpired:
return False
def main():
endpoints = json.load(open(sys.argv[1] if len(sys.argv) > 1 else "bench/endpoints.json"))
runs = int(os.environ.get("RUNS", "3"))
print("%-24s %6s %9s %7s" % ("endpoint", "pass", "median_s", "tok/s"))
with open("results.jsonl", "a") as log:
for ep in endpoints:
ok_n, total, lat, rate = 0, 0, [], []
for task in TASKS:
for _ in range(runs):
total += 1
try:
data, secs = call(ep, task["prompt"])
text = data["choices"][0]["message"].get("content") or ""
ok = passes(extract(text), task["tests"])
toks = data.get("usage", {}).get("completion_tokens", 0)
except Exception as exc:
ok, secs, toks = False, 0.0, 0
print(" %s/%s error: %s" % (ep["name"], task["id"], exc))
ok_n += ok
if secs:
lat.append(secs)
rate.append(toks / secs)
log.write(json.dumps({"endpoint": ep["name"], "task": task["id"], "pass": ok, "secs": round(secs, 2), "tokens": toks}) + "\n")
print("%-24s %3d/%-2d %9.1f %7.1f" % (ep["name"], ok_n, total,
statistics.median(lat) if lat else 0, statistics.median(rate) if rate else 0))
if __name__ == "__main__":
main()
The harness executes model-written code, so it strips the environment down to PATH, runs in a throwaway directory, and kills anything that takes longer than ten seconds. For anything beyond toy tasks, run it inside a container or a disposable virtual machine. Define the endpoints in bench/endpoints.json:
[
{"name": "local-flint", "base_url": "http://127.0.0.1:8080/v1", "model": "flint"},
{"name": "openrouter-flint-free", "base_url": "https://openrouter.ai/api/v1",
"model": "recursive/flint-v1.0:free", "key_env": "OPENROUTER_API_KEY"}
]
Steps 10 to 12: Run, Route, and Automate #
The remaining steps turn measurements into habits. Run the harness once, write down a routing rule per task type, and schedule a weekly check so the free list never surprises you.
Step 10: Run the Harness and Read the Results
Export your OpenRouter key if you want the hosted comparison, run the guard first, then the benchmark. Fifteen requests per endpoint finish in a few minutes on most setups.
export OPENROUTER_API_KEY=your-free-tier-key
python3 scripts/guard.py opencode.json && python3 bench/bench.py bench/endpoints.json
The output has this shape. The numbers below are illustrative formatting only, not measured results, so expect your own figures to differ with hardware, quantization, and free-tier load.
endpoint pass median_s tok/s
local-flint 13/15 9.8 31.2
openrouter-flint-free 14/15 6.1 48.0
Read three columns together. The pass count shows whether the endpoint can do routine work at all. Median seconds shows how long you wait per answer. Tokens per second separates a slow network from a slow model. A free hosted route that wins on speed but loses on pass count is a trap, because a fast wrong answer wastes more time than a slow right one. Also look for error lines. A burst of rate-limit errors on the free route is the clearest sign to lean on the local tier.
Step 11: Route Work by Task Type
Pick a tier per kind of work and stop deciding mid-session. The table encodes the policy from Step 3 and the results from Step 10. A tiny helper script flips the default model so switching takes one command.
| Task type | Default tier | Reason |
|---|---|---|
| Tests, docstrings, boilerplate in public repos | Free hosted | Cheap, fast, low risk |
| Refactors in private repos | Local Flint or the zero-retention endpoint | Code stays on your machine or on a no-training endpoint |
| Anything near credentials or customer data | Local Flint only | No third-party retention at all |
| Large multi-file changes needing long context | Local with a larger context, or the paid ceiling | Free routes cap output and rate |
| Free route returns 429 for several minutes | Local Flint | Keeps work moving without a bill |
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
case "${1:-}" in
free) model="opencode/flint-40b-free" ;;
local) model="flint-local/flint" ;;
*) echo "usage: use.sh free|local" >&2; exit 2 ;;
esac
tmp=$(mktemp)
jq --arg m "$model" '.model = $m' opencode.json > "$tmp" && mv "$tmp" opencode.json
python3 scripts/guard.py opencode.json
Save that as scripts/use.sh, make it executable, and run scripts/use.sh local before touching private code. The script re-runs the guard on every switch, so a typo cannot slip through.
Step 12: Schedule the Weekly Free-List Check
Free offers change without announcements, so let a script notice. It takes a new snapshot, diffs it against the previous one, and runs the guard. Any line in the diff tells you a model arrived or left.
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
today="snapshots/free-$(date +%F).txt"
opencode models | grep -i free | sort > "$today"
prev=$(ls snapshots/free-*.txt | sort | tail -2 | head -1)
if [ "$prev" != "$today" ]; then
diff -u "$prev" "$today" || echo "FREE MODEL LIST CHANGED"
fi
python3 scripts/guard.py opencode.json
Schedule it with cron every Monday morning, and point the output at a log file you will actually read:
0 8 * * 1 $HOME/zero-dollar-opencode/scripts/weekly-check.sh >> $HOME/zero-dollar-opencode/snapshots/weekly.log 2>&1
When the diff shows a model leaving, rerun the benchmark with the replacement and update the routing table. The loop takes ten minutes and saves you from discovering the change in the middle of a deadline.
What Five Tasks Can and Cannot Tell You #
A smoke test earns its keep by catching obvious failures, and it misleads the moment you read it like a leaderboard. Keep its limits in mind before you change your workflow because of a single run.
Start with sample size. The harness sends the same prompt three times per task at temperature 0, so the three runs often return identical answers. A score of 13 out of 15 therefore describes about five distinct problems, and one failed problem costs three points at once. The repeats still serve a purpose, because they expose flaky endpoints, rate limits, and servers that crash under sequential load. They do not add statistical power. If two endpoints land within a point or two of each other, call it a tie.
Next, consider scope. Every task is a short Python function with a clear specification. Real agent work involves reading existing files, choosing which ones to edit, running commands, and recovering from failed tests. A model can ace five tidy functions and still stumble in a messy repository, and a model that stumbles on one function may still be excellent at multi-step editing. The harness cannot see tool-calling quality at all, since it uses plain chat completions. Treat a pass as a necessary condition, not a sufficient one.
Context length is the third blind spot. Recursive states its headline speeds for short contexts, while a footnote on its comparison chart averages tokens per second across a 128,000-token session. Your five prompts use a few hundred tokens. A local server that feels snappy here can crawl once an agent has loaded forty files. Test your real sessions by timing a long task with time and watching memory in your system monitor.
Finally, consider drift. Free hosted routes can change the model behind an identifier, and a quiet change shows up as a falling pass count with no announcement. That is another reason to keep results.jsonl and review it. Plot it, or just run jq -s 'group_by(.endpoint) | map({endpoint: .[0].endpoint, pass: (map(select(.pass)) | length), total: length})' results.jsonl to get a running tally per endpoint.
To make the harness more informative without turning it into a project, add tasks that fail in interesting ways. Include one that requires reading a provided file, one with an ambiguous specification where the best answer asks a question, and one that needs a standard-library module the model must remember correctly. Each new task should come from a real failure you saw while working. After a month, the set reflects your own codebase instead of a generic puzzle collection.
Common Pitfalls That Break a Zero-Dollar Stack #
Most failures in this kind of setup come from assumptions, not from bugs. These seven cause the most damage.
- Treating “free” as “private.” The October 19 account on note.com found data terms varied by provider, and OpenCode’s own page says two Nemotron endpoints collect feedback. Match code sensitivity to the endpoint, not to the price tag.
- Copying 1.x instructions onto a 2.x install. The two lines ship side by side, and 2.x carries breaking changes. When a config field is rejected, check the docs for your installed version before debugging anything else.
- Declaring the full 262,144-token window. Recursive documents that window, but a KV cache that large exhausts memory on most machines. Start at 64K and raise it only while watching memory.
- Letting stray API keys decide your model. A shell that exports a paid key can change what a tool offers by default. The guard warns about this, and you should heed it.
- Trusting vendor benchmark scores for your repository. The 74.2% on SWE-bench Verified and 89.7% on SWE-bench Pro v2 are Recursive’s own release numbers. Your five-task harness measures your reality. Scores also swing between labs, as theTerminal-Bench comparison of Claude Opus 5.5 and GPT-6 Astra shows.
- Running generated code with your real environment. A model that writes
os.environdumps into test code can leak tokens. Keep the stripped environment and the throwaway directory. - Committing weights or result logs. The
.gitignoreentries from Step 4 exist because an 80 GB commit is painful to undo.
Troubleshooting: 12 Problems and Fixes #
Work through these in order when something fails. Most problems trace back to the first three.
opencode: command not foundafter install. The installer put the binary in a directory missing from yourPATH. Open a fresh shell, or add the install directory to your profile, then rerunopencode --version.- The free model identifier is missing from the picker. Free routes rotate, and one was reportedly deprecated around October 22. Rerun Step 2, pick a model from the fresh list, and update the config.
- HTTP 429 from the free hosted route. You hit a rate limit. The harness backs off automatically, but interactive sessions do not. Switch to the local tier with
scripts/use.sh localand retry later. - The converter reports an unknown architecture. Your llama.cpp checkout predates Flint support. Run
git pullin the llama.cpp folder, rebuild, and convert again. - The server runs out of memory at startup. Lower
-cto 32768, choose a smaller quantization such as Q3_K_M, or reduce-nglso fewer layers sit on the GPU. - Tool calls appear as plain text in the chat. The chat template did not load. Confirm you started
llama-serverwith--jinja, and make sure the tokenizer files came down with the weights. - OpenCode says the provider or model was not found. The provider block does not match your version’s schema. Compare field names against the docs under the project’s
/v2/docspath if you run 2.x. Connection refusedon port 8080. The server is not running, or another process owns the port. Check withcurl http://127.0.0.1:8080/healthand pick a different port if needed.- Generation feels slow. Check that layers actually offload to the GPU, close other memory-hungry apps, and remember that Recursive’s speed claims describe its own engine at short contexts. Throughput drops as the context grows.
- The guard fails on a key you need.
OPENROUTER_API_KEYis only used by the benchmark. The guard warns about paid keys but never fails on them. Unset the warning keys in the shell you use for OpenCode. hf downloadstops partway. Rerun the same command. The CLI resumes completed files. If it keeps failing, check free disk space withdf -h.- Answers degrade on long sessions. Small active-parameter models lose focus as context fills. Start a fresh session per task, keep
AGENTS.mdshort, and commit between tasks so a bad run is cheap to discard.
Advanced Tips for Squeezing More from $0 #
Once the basics run, a few habits stretch the stack further without spending anything.
Pin your versions. Use a Docker image with a versioned tag for OpenCode and record the llama.cpp commit hash next to your GGUF file. A free stack that drifts every week stops being reproducible, and you lose the ability to compare results across dates.
Measure cost per passing task, not cost per token. Add a paid endpoint to bench/endpoints.json for comparison, then divide spend by passes. A free model with a low pass rate costs your time, and time is the expensive resource. The Aider local assistant guide shows a git-first workflow that pairs well with this habit, since every attempt becomes a commit you can revert.
Grow the task set from your own repository. Every time a model fails on real work, turn that failure into a sixth, seventh, and eighth benchmark task. After a month, the harness reflects your codebase better than any public leaderboard. Also keep the license in mind when you swap local models. Flint’s Apache 2.0 terms are permissive, while other open-weight models carry restrictions, as the open-weight license comparison lays out. Mistral Large 4, for instance, ships under Mistral’s own open-weight license, and businesses that sell the model as a hosted service need a separate commercial license from Mistral.
Patch your client. A security write-up from Safeguard claims OpenCode versions 1.0.137 through 1.18.32 carried an MCP OAuth weakness on Windows and WSL, with a fix in v1.18.33 on September 28, 2026. That claim comes from a single report, so confirm it against the project’s release notes. The lesson holds either way: an agent client with shell access deserves the same update discipline as your browser. Run scripts/weekly-check.sh on a schedule and read the release notes whenever the version changes.
Use the cheap-model slot. OpenCode lets you name a smaller model for housekeeping work such as session titles. Pointing that slot at the local model, or at a free hosted one, keeps trivial calls off any metered endpoint. The guard already checks small_model for the same reason.
Keep a worktree per experiment. When you test a new local model, create a git worktree so the agent’s edits never touch your main checkout. Free and local models make more mistakes than frontier models do, and cheap mistakes only stay cheap when they are easy to discard.
Run the local server as a background service so it starts at login. A systemd user unit on Linux or a launchd job on macOS keeps the endpoint ready, and binding to 127.0.0.1 keeps it off your network. Never bind a coding model server to 0.0.0.0 on a laptop that joins public Wi-Fi.
For broader context on picking and comparing tools in this space, the coding and developer tools section tracks new releases as they land.
The Complete Project Layout #
Everything above fits in one small repository. This is the finished tree, with each file mapped to the step that created it.
zero-dollar-opencode/
.gitignore Step 4: ignores weights and logs
AGENTS.md Step 7: shared house rules
POLICY.md Step 3: which tier may touch which code
opencode.json Steps 6 and 7: models and permissions
bench/
bench.py Step 9: five-task harness
endpoints.json Step 9: local and hosted endpoints
scripts/
guard.py Step 8: blocks billable routes
use.sh Step 11: flips the default model
weekly-check.sh Step 12: snapshot, diff, guard
snapshots/ Steps 2 and 12: dated free-model lists
models/ Steps 4 and 5: weights (ignored by git)
results.jsonl Step 10: raw benchmark log (ignored)
Commit everything except the ignored paths. A new teammate can clone the repository, download the weights, and reach a working stack by repeating Steps 4, 5, and 10. That reproducibility is the real deliverable. The models will rotate, the prices will move, and the repository will still tell you what changed.
When to Pay: The Break-Even Math #
A zero-dollar stack should come with an exit plan. Some work needs longer context, stronger reasoning, or faster turnaround than a free route or a laptop provides. The question is where the break-even sits.
Take a concrete example using published prices. Suppose an average agent task consumes a modest number of input tokens and a smaller number of output tokens. At Mistral Large 4’s $0.70 per million input tokens and $2.10 per million output tokens, each task costs only a few cents on the metered API. At a moderate monthly task count, the total lands close to OpenCode Go’s flat $10 a month. Below that volume the metered API is cheaper, and above it the flat plan wins, assuming the plan’s allowances cover your usage and the models you need are on its list.
Those numbers ignore caching, which Mistral prices at $0.07 per million cached input tokens, a tenth of the normal input rate. Agent loops resend large prompts repeatedly, so caching can cut the input bill sharply. They also ignore the subscription’s own limits. Verify both on the vendors’ pages before committing money, because plan allowances change.
The decision rule stays simple. Keep the free and local tiers as defaults. Move a task type to a paid rung only when your harness shows the free tiers failing that task type, and only after you compute cost per passing task. Most developers find the paid rung covers a small share of their work, which is exactly why the free stack pays off.
FAQ #
Is OpenCode really free?
The OpenCode client is free and open source, and its site says it comes with a set of free models. Costs appear only when you connect other providers or choose a paid plan. OpenCode Go costs $10 a month and Go Teams costs $20 per member per month.
Are the free hosted models permanent?
No. OpenCode’s models page labels the current free entries as limited-time offers, and reports say at least one free route was deprecated in late October. Treat them as a bonus and keep a local fallback.
Is my code private on the free models?
It depends on the model. OpenCode’s page says the GLM-5.3-Flash Free provider follows a zero-retention policy and does not train on your data, while the two Nemotron free entries are used to collect feedback. A local model keeps data on your device. Match each repository to the right endpoint.
Can I run Flint on a laptop?
Reports describe laptops with roughly 48 GB of memory running it, and Recursive publishes speed claims for an M4 Pro MacBook Pro using its own engine. The generic llama.cpp route in this guide will run slower. Test with the harness before you rely on it.
Which release line of OpenCode should I use?
New installs should use the 2.x line, where v2.2.5 was reported as the newest build on October 30, 2026. Existing 1.x users can upgrade deliberately, because the project does not push 2.x through the GitHub “Latest” release.
Does Flint work with tools other than OpenCode?
Any client that speaks the OpenAI-compatible API can talk to the local server, including Aider and Cline. Only the config syntax changes.
How do I stop the agent from running up a bill?
Use the guard script as a pre-commit hook, keep paid API keys out of the shell you use for OpenCode, and review the weekly snapshot diff. Those three habits catch nearly every accidental paid route.