- JavaScript 82.1%
- Dockerfile 14.7%
- Shell 3.2%
| agents | ||
| commands | ||
| plugins | ||
| skills | ||
| AGENTS.md | ||
| Dockerfile | ||
| opencode.json | ||
| README.md | ||
| run.sh | ||
opencode-deepresearch
Docker image providing a pre-configured OpenCode environment for AI-based deep research workflows.
Features
- OpenCode CLI (latest) running on Debian 13 (slim)
- Deep research skill with a 7-step workflow: plan, scale, gather, draft, cite, review, deliver
- PDF summary skill — download or read local PDFs, extract text, and produce faithful structured summaries with provenance
- Research search skill — find a fact or claim in prior research outputs and report every hit verbatim with its complete citation(s) and URL(s)
- 3 subagents:
researcher,verifier,reviewer - Reasoning-effort plugin — injects
reasoning_effortinto chat completions based on the per-model variant selected in the TUI - Exa MCP (remote) for web search and source fetching
- Pre-installed tooling:
vim,curl,jq,ca-certificates, PDF tooling (poppler-utils:pdftotext,pdfinfo), Node.js LTS - Persistent config, credentials, and TUI state (last-used model) across restarts via named volumes
Prerequisites
- Docker
- An LLM provider/API key for OpenCode (see First-Time Setup)
Build
docker build -t lsteffen161/opencode-deepresearch .
Run
./run.sh
Or directly:
docker run --rm -it \
-v opencode-deepresearch-config:/home/agent/.config/opencode-config \
-v opencode-deepresearch-data:/home/agent/.local/share/opencode/ \
-v opencode-deepresearch-state:/home/agent/.local/state/opencode/ \
-v $PWD:/workspace \
opencode-deepresearch:latest
./run.sh forwards any arguments to opencode inside the container (e.g. ./run.sh <topic>). Note: run.sh is hard-coded to the published image lsteffen161/opencode-deepresearch:latest — rebuild and retag if you change the local image name.
Your current directory is mounted at /workspace. The named volume opencode-deepresearch-config persists only the single file opencode.json (your providers, MCP settings, permissions) — in the container, ~/.config/opencode/opencode.json is a symlink into that volume. opencode-deepresearch-data persists your API keys and session state. opencode-deepresearch-state persists your TUI state, which is what makes the last-used model stick across restarts. Agents, skills, commands, and plugins are never persisted — they always come from the latest image build, so a rebuild updates them automatically.
First-Time Setup
- Run the container for the first time.
- Inside the container, launch OpenCode and connect your provider with
/connect, or write your provider config directly. The image ships a minimalopencode.json(Exa MCP enabled, bash fully permitted) that you can extend:
cat > /home/agent/.config/opencode/opencode.json << 'EOF'
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": {
"*": "ask",
"git *": "allow",
"grep *": "allow",
"ls *": "allow",
"cat *": "allow",
"find *": "allow",
"rg *": "allow"
}
},
"mcp": {
"exa": {
"type": "remote",
"url": "https://mcp.exa.ai/mcp",
"enabled": true
}
},
"provider": {
"your-provider": {
"npm": "@ai-sdk/openai-compatible",
"name": "Your Provider",
"options": {
"baseURL": "https://your-api-endpoint/v1"
},
"models": {
"your-model": {
"name": "your-model",
"reasoning": true,
"tool_call": true
}
}
}
}
}
EOF
- Exit the container. Your changes persist in the
opencode-deepresearch-configvolume. - On subsequent runs your config is loaded automatically.
Security note: the shipped
opencode.jsonsets"bash": { "*": "allow" }, which grants unrestricted shell access inside the container. Tighten these permissions if you want OpenCode to ask before running commands.
Using Deep Research
Inside the container, ask OpenCode to run deep research on any topic:
/deepresearch <topic>
Or simply describe what you want researched and OpenCode will invoke the deep research workflow.
Using PDF Summaries
Inside the container, download/read and summarize one or more PDF documents (by URL or local path):
/pdf-summary https://example.com/paper.pdf
/pdf-summary path/to/report.pdf https://example.com/another.pdf
The pdf-summary skill:
- Acquires each PDF — downloads URLs with
curlor reads local files intooutputs/.pdfs/. - Extracts text with
pdftotext -layoutand records metadata (pages, title, author) withpdfinfo(viapoppler-utils). - Summarizes faithfully — never fabricates content; every claim traces to extracted text.
- Writes
outputs/<slug>-summary.mdplus a<slug>.provenance.mdsidecar (source, checksum, extraction status).
Scanned or image-only PDFs produce a metadata-only summary with Extraction: BLOCKED (OCR is out of scope). Dead URLs are reported as failed downloads.
Using Research Search
Query your own prior research output for a fact, figure, or claim:
/research-search energy per training run of RISC-V vs x86
The research-search skill greps the generated research files and reports each match with its complete statement text and full source citation(s) and URL(s) — nothing is paraphrased or truncated. It only reads files that carry a resolvable source map per statement:
| In scope | Out of scope |
|---|---|
outputs/*.md, papers/*.md |
outputs/.plans/ (uncited working notes) |
outputs/.drafts/*-cited.md, *-revised.md |
*.provenance.md |
outputs/.drafts/*-research-T<n>.md, *-research-direct.md |
*-draft.md, *-verification*.md, *-review.md |
A standalone deliverable whose name merely contains a word like verification or review is still searched if it has its own ## Sources list and per-statement inline citations. Results are reported in chat; no files are written.
Reasoning Effort Plugin
plugins/reasoning-effort.js keeps a provider's reasoning_effort parameter in sync with the variant you picked in the TUI. It intercepts outgoing /chat/completions requests and resolves the effort in this order:
- the model's
options.reasoningEffortin your provider config (wins always), - otherwise the per-model variant map in
~/.local/state/opencode/model.json(this is what the TUI writes when you cycle variants).
Recognized variants are max, high, xhigh, medium, and low, passed through verbatim. An existing reasoning_effort in the request is preserved unless override is enabled. Note that this depends on the opencode-deepresearch-state volume — without it the variant map is empty on every start and the plugin has nothing to read.
Configure it through environment variables (pass them with docker run -e):
| Variable | Default | Effect |
|---|---|---|
REASONING_EFFORT_LOG |
true |
Write a JSONL log of each injected request |
REASONING_EFFORT_OVERRIDE |
false |
Overwrite an existing reasoning_effort instead of only filling in a missing one |
REASONING_EFFORT_PROVIDERS |
(all) | Comma-separated substrings; only matching request URLs are touched |
REASONING_EFFORT_LOG_FIELDS |
model,reasoning_effort,source,stream,tools_count,messages_count |
Fields recorded per log line |
REASONING_EFFORT_LOG_DIR |
~/.local/share/opencode/log |
Log directory |
The log file is deleted on clean exit, so it is a debugging aid rather than a permanent audit trail.
Deep research workflow
The deepresearch skill orchestrates planning through delivery:
- Plan — writes a plan artifact to
outputs/.plans/<slug>.mdand asks for your approval before gathering evidence. - Scale — chooses direct search for narrow questions, or delegates to
researchersubagents for broad surveys. - Gather — collects sources via search and fetch tooling.
- Draft — synthesizes findings into a structured draft.
- Cite — the
verifiersubagent anchors every claim to a source and verifies URLs. - Review — the
reviewersubagent audits the cited draft for unsupported claims and overreach. - Deliver — writes the final brief plus a
.provenance.mdsidecar.
Artifacts
| Path | Purpose |
|---|---|
outputs/.plans/<slug>.md |
Research plan (key questions, scale decision, task ledger, verification log) |
outputs/.drafts/<slug>-draft.md |
Uncited synthesis |
outputs/.drafts/<slug>-cited.md |
Draft with inline citations |
outputs/.drafts/<slug>-draft.md → outputs/.drafts/<slug>-verification.md |
Review findings (FATAL / MAJOR / MINOR) |
outputs/<slug>.md or papers/<slug>.md |
Final research brief |
<slug>.provenance.md |
Provenance: sources consulted/accepted/rejected, verification status |
Project Layout
├── Dockerfile # Debian slim + OpenCode + tooling
├── run.sh # Docker run wrapper with volumes
├── opencode.json # OpenCode config (Exa MCP, permissions, provider slot)
├── agents/ # Subagents copied into the container
│ ├── researcher.md # Evidence gathering
│ ├── verifier.md # Citation + URL verification
│ └── reviewer.md # Peer-review style audit
├── commands/ # Custom slash commands
│ ├── deepresearch.md # /deepresearch entry point
│ ├── pdf-summary.md # /pdf-summary entry point
│ └── research-search.md # /research-search entry point
├── skills/ # Skills copied into the container
│ ├── deepresearch/ # 7-step workflow definition
│ ├── pdf-summary/ # PDF download + summary workflow
│ └── research-search/ # Cited-statement search over prior outputs
└── plugins/ # Plugins copied into the container
└── reasoning-effort.js # reasoning_effort injection from TUI variant state
When you rebuild the image, agents/, skills/, commands/, plugins/, and the default opencode.json are baked in. Your config edits (e.g. the provider setup above) are written through the symlink into the opencode-deepresearch-config volume and persist independently of rebuilds.
Volumes
| Volume | Container Path | Purpose |
|---|---|---|
opencode-deepresearch-config |
/home/agent/.config/opencode-config |
Persistent config: providers, MCP settings, permissions. Contains only opencode.json, which the image symlinks to ~/.config/opencode/opencode.json. |
opencode-deepresearch-data |
/home/agent/.local/share/opencode/ |
Auth credentials (auth.json), database (opencode.db), and caches |
opencode-deepresearch-state |
/home/agent/.local/state/opencode/ |
TUI state: model.json (recent/favorite models + per-model variant), kv.json (UI preferences), prompt-history.jsonl, frecency.jsonl |
| (bind mount) | /workspace |
Research workspace and outputs |
The image symlinks
~/.config/opencode/opencode.jsontoopencode.jsoninsideopencode-deepresearch-config, so only that single file is persisted. A fresh volume is seeded with the image's defaultopencode.jsonon first run (Docker's copy-on-first-use). Agents, skills, and commands are baked in and always come from the image — a rebuild updates them immediately. Leftoveragents//skills//commands/directories inside an older volume are ignored (the image versions always win).
Where Your Data Lives
- API key / logged-in provider: set via the
/connectcommand inside the container, stored in~/.local/share/opencode/auth.json→ persisted inopencode-deepresearch-data. No more re-entering keys every start. - Sessions, messages & snapshot history: stored in
~/.local/share/opencode/opencode.db→ also persisted inopencode-deepresearch-data. - Last-used model & per-model variant: stored in
~/.local/state/opencode/model.json→ persisted inopencode-deepresearch-state. OpenCode resolves a new session's model asopencode.json'smodelsetting → therecentlist inmodel.json→ the provider default. Without this volume every container start falls back to the provider default, which is why the same model appears each time.model.jsonalso carries the per-model reasoning variant thatplugins/reasoning-effort.jsreads. - UI preferences & prompt history:
~/.local/state/opencode/kv.jsonandprompt-history.jsonl→ also persisted inopencode-deepresearch-state. - Config (providers, MCP, permissions):
~/.config/opencode/opencode.json(symlink) → persisted asopencode.jsoninopencode-deepresearch-config. Only this single file is persisted;agents/,skills/, andcommands/always come from the rebuilt image. - Research outputs: written to
/workspace(your mounted working directory), so everything inoutputs/,papers/, andnotes/persists on your host filesystem.
License
This project is provided as-is for personal and research use.