Async Python wrapper for OpenCode CLI (opencode run --format json)
Project description
oc-py-harness
Python async wrapper around the OpenCode CLI (opencode run --format json). Intended as a subprocess-based executor for multi-agent workflow orchestration.
Requirements
- Python 3.10+
opencodeonPATH(or pass an absolute path to the binary)
Install (local tree)
pip install -e ".[dev]"
Usage
One-shot run with aggregated result
import asyncio
from pathlib import Path
from opencode_wrapper import AsyncOpenCodeClient, RunConfig
async def main():
client = AsyncOpenCodeClient("opencode")
cfg = RunConfig(
model="anthropic/claude-sonnet-4-5",
agent="plan",
permission={"bash": "deny", "edit": "deny"},
mcp={
"demo": {
"type": "local",
"command": ["npx", "-y", "@modelcontextprotocol/server-everything"],
"enabled": True,
}
},
)
result = await client.async_run(
"Summarize the README in one sentence.",
Path("/path/to/repo"),
run_cfg=cfg,
timeout_s=600,
)
print(result.exit_code, result.final_text)
asyncio.run(main())
Stream structured JSON events
async def stream_example():
client = AsyncOpenCodeClient()
cfg = RunConfig(permission={"*": "allow"})
async for event in client.async_stream("List top-level files.", workspace=".", run_cfg=cfg):
print(event)
Parallel agents (asyncio.gather)
async def multi():
# startup_concurrency=1 serialises SQLite initialisation to avoid a known
# WAL-pragma race in opencode when many instances start simultaneously.
# startup_delay_s controls how long each slot is held before the next
# process is allowed to start (default 0.3 s).
client = AsyncOpenCodeClient(startup_concurrency=1, startup_delay_s=0.3)
ws = Path("/path/to/monorepo")
results = await asyncio.gather(*[
client.async_run(
f"Explain services/{svc}.",
ws / "services" / svc,
run_cfg=RunConfig(agent="explore"),
timeout_s=600,
# max_retries=2 (default): retry automatically if opencode crashes
# during SQLite startup before giving up.
)
for svc in ["api", "worker", "gateway"]
])
return results
Configuration injection
Per-call JSON is merged and passed as OPENCODE_CONFIG_CONTENT (see OpenCode config). Use RunConfig fields:
| Field | Purpose |
|---|---|
permission |
permission map (allow / deny, patterns) |
mcp |
MCP server definitions |
tools |
Enable/disable tools (including MCP globs) |
config_overrides |
Any extra top-level config keys to deep-merge |
Optional env tuning: disable_autoupdate=True sets OPENCODE_DISABLE_AUTOUPDATE=1.
Note: ask is intentionally rejected in subprocess mode (no interactive terminal); use allow or deny.
CLI arguments
RunConfig maps to flags such as --agent, -m, -f, --attach, --title, etc. Prompt text is appended as the final opencode run message argument.
Tests
Unit tests (no real OpenCode / no API calls):
pytest -q -m "not integration"
Integration tests (real opencode run, needs working provider auth — slow, may incur API usage):
pytest -m integration -q tests/test_integration_opencode.py
Multi-agent weather workflow (10 parallel city lookups + 1 summary — 11 API calls, not run by default):
OPENCODE_MULTI_AGENT_WEATHER=1 pytest -m integration -v tests/test_integration_multi_agent_weather.py
Optional: OPENCODE_WEATHER_SEQUENTIAL=1 runs the 10 city calls one-by-one (easier on rate limits).
Per-stage timeouts: OPENCODE_WEATHER_PER_CITY_TIMEOUT_S, OPENCODE_WEATHER_SUMMARY_TIMEOUT_S (default: same as OPENCODE_INTEGRATION_TIMEOUT_S).
| Env | Meaning |
|---|---|
OPENCODE_BINARY |
Absolute path to opencode if not on PATH |
OPENCODE_INTEGRATION=0 |
Skip integration tests |
OPENCODE_INTEGRATION_TIMEOUT_S |
Per-test timeout seconds (default 300) |
OPENCODE_MULTI_AGENT_WEATHER=1 |
Enable 11-call weather integration test |
OPENCODE_ENABLE_EXA |
Passed through / defaulted to 1 in that test for web search tools |
Default pytest -q runs all tests; use -m "not integration" in CI without OpenCode.
Concurrency notes
When running many tasks with asyncio.gather, three protections are enabled by default:
Startup serialisation — startup_concurrency=1 and startup_delay_s=0.3 limit how many processes enter SQLite startup at once, reducing WAL-initialisation race crashes.
DB isolation — isolate_db=True gives each run a private XDG_DATA_HOME, so concurrent runs do not contend on the same opencode.db during tool execution.
Automatic retry — async_run(max_retries=2, retry_delay_s=1.0) retries known SQLite-startup crashes with short backoff. Non-SQLite failures still fail fast.
Set startup_concurrency=0, isolate_db=False, and max_retries=0 to opt out.
Notes
- Event shapes from
--format jsonmay change between OpenCode versions; unknown fields are preserved in each parsed dict. - For fully non-interactive automation, prefer explicit
permission(allow/deny) over relying on interactiveaskprompts.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_opencode_wrapper-0.1.5.tar.gz.
File metadata
- Download URL: py_opencode_wrapper-0.1.5.tar.gz
- Upload date:
- Size: 24.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2d9fb71ad11f638c366cfd9a670e6bc3ad03b38bc56bf457aeaabaae051bc0b
|
|
| MD5 |
96eb7e93637862e81027914966c0942b
|
|
| BLAKE2b-256 |
39e3a572df03ea36d5bd9a61d6c7804eff9b201f52d50eb8bc3e5be4c612036b
|
File details
Details for the file py_opencode_wrapper-0.1.5-py3-none-any.whl.
File metadata
- Download URL: py_opencode_wrapper-0.1.5-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
949d2d7df63d5b66bb4823530c2244e284480bb3167b891e0683fc3978738564
|
|
| MD5 |
365b400cf02370ad77d45f9c55275a03
|
|
| BLAKE2b-256 |
68bfb78231d8f45497931a6aad6ed75ab22175f83491f904a6c05379d35adf05
|