OpenCode Developer Guide
This is the developer runbook for running Whilly with OpenCode instead of the demo stub or Claude Code.
Quick Start
For the local Docker demo, use workshop-demo.sh and select the OpenCode CLI:
WHILLY_MODEL=opencode/big-pickle \
bash workshop-demo.sh --cli opencode --workers 1 --keep-running
Then open the dashboard:
open http://127.0.0.1:8000/
opencode/big-pickle is the zero-key OpenCode Zen model. It does not require opencode auth login or provider API keys.
Required Variables
Whilly still calls the runner through the historical CLAUDE_BIN subprocess contract. For OpenCode, CLAUDE_BIN must point to Whilly’s adapter:
| Variable | Value | Purpose |
|---|---|---|
CLAUDE_BIN | /opt/whilly/docker/cli_adapter.py | Runs Whilly’s adapter instead of the fake demo Claude stub. |
WHILLY_CLI | opencode | Selects the OpenCode adapter route. |
WHILLY_MODEL | opencode/big-pickle | Selects the OpenCode model. |
When using workshop-demo.sh --cli opencode, the script sets CLAUDE_BIN and WHILLY_CLI for you. Set WHILLY_MODEL explicitly when you want a pinned model.
Where To Set The Model
For one command:
WHILLY_MODEL=opencode/big-pickle bash workshop-demo.sh --cli opencode
For repeated local Docker runs, put the values in a repo-root .env file. The file is gitignored and must not contain committed secrets:
CLAUDE_BIN=/opt/whilly/docker/cli_adapter.py
WHILLY_CLI=opencode
WHILLY_MODEL=opencode/big-pickle
The compose defaults live in docker-compose.demo.yml under services.worker.environment. The helper script mapping lives in workshop-demo.sh in configure_cli_backend().
Important: direct docker-compose.demo.yml up uses the fake demo runner unless CLAUDE_BIN is overridden. To run real OpenCode through compose directly, set all three variables above.
Direct Compose Run
After building the image:
docker-compose -f docker-compose.demo.yml build worker
docker-compose -f docker-compose.demo.yml up -d postgres control-plane
docker-compose -f docker-compose.demo.yml run --rm --no-deps \
control-plane whilly plan import examples/demo/tasks.json
Run one local worker iteration through OpenCode:
docker-compose -f docker-compose.demo.yml run --rm --no-deps \
-e CLAUDE_BIN=/opt/whilly/docker/cli_adapter.py \
-e WHILLY_CLI=opencode \
-e WHILLY_MODEL=opencode/big-pickle \
-e WHILLY_SLACK_ENABLED=0 \
-e WHILLY_DATABASE_URL=postgresql://whilly:whilly@postgres:5432/whilly \
worker whilly run --plan demo --max-iterations 1 --worker-id opencode-dev
Using Another Provider Through OpenCode
OpenCode model ids use provider/model form. Set WHILLY_MODEL and the matching credential:
# Groq
WHILLY_MODEL=groq/openai/gpt-oss-120b
GROQ_API_KEY=gsk_...
# Anthropic through OpenCode
WHILLY_MODEL=anthropic/claude-opus-4-6
ANTHROPIC_API_KEY=sk-ant-...
# OpenAI through OpenCode
WHILLY_MODEL=openai/gpt-4o-mini
OPENAI_API_KEY=sk-...
Use .env or your shell environment for these values. Do not commit real provider tokens.
Smoke Check
To verify only the OpenCode binary in the image:
docker-compose -f docker-compose.demo.yml run --rm --no-deps \
-e WHILLY_CLI=opencode \
-e WHILLY_MODEL=opencode/big-pickle \
worker opencode run --model opencode/big-pickle "Reply with exactly: OK"
To verify the Whilly adapter path:
docker-compose -f docker-compose.demo.yml run --rm --no-deps \
-e WHILLY_CLI=opencode \
-e WHILLY_MODEL=opencode/big-pickle \
worker /opt/whilly/docker/cli_adapter.py \
--output-format json \
--model opencode/big-pickle \
-p "Reply with exactly: OK"
The adapter smoke should return a JSON envelope whose result contains OK and <promise>COMPLETE</promise>.
Troubleshooting
| Symptom | Fix |
|---|---|
WHILLY_CLI env var is required | Set WHILLY_CLI=opencode; the adapter needs it to choose the native CLI. |
| Worker uses the fake demo runner | Set CLAUDE_BIN=/opt/whilly/docker/cli_adapter.py; compose defaults to the stub for demo safety. |
GROQ_API_KEY is required | You selected WHILLY_MODEL=groq/...; either set GROQ_API_KEY or switch back to WHILLY_MODEL=opencode/big-pickle. |
| Dashboard restarts with missing static files | Rebuild the image from a revision that includes api/static/* in pyproject.toml package data. |