-
Notifications
You must be signed in to change notification settings - Fork 129
Description
Context
The OpenCode harness provider (sdk/python/agentfield/harness/providers/opencode.py) currently uses a serve+attach workaround to bypass a "Session not found" bug in opencode run headless mode (opencode#13851, affects v1.2.10–v1.2.16).
This workaround auto-spawns a long-lived opencode serve process on a random port and routes all calls through opencode run --attach <url>. This is architecturally problematic because:
- Process lifecycle in a request handler — each harness call runs inside a FastAPI reasoner endpoint. Having the provider auto-spawn and manage background server processes within a web server process introduces fragile process coupling.
- Port conflicts — random port allocation can collide in multi-process deployments (e.g., multiple Uvicorn workers).
- Cleanup edge cases —
atexithooks don't fire on SIGKILL, leaving orphanopencode serveprocesses. - Singleton shared state — class-level
_serve_proc/_serve_url/_serve_lockare shared across all provider instances within a process, making the provider non-isolatable for testing and multi-tenant scenarios. - ~160 lines of workaround code — vs. ~45 lines for a normal CLI provider (Codex/Gemini pattern).
Blocked on
- opencode#13851 —
opencode run"Session not found" in headless mode
Changes needed once upstream is fixed
1. Replace serve+attach with simple opencode run (~120 lines deleted)
Remove:
_find_free_port()helper_serve_proc/_serve_url/_serve_lockclass-level state_get_lock()classmethod_cleanup_serve()classmethod + atexit registration_ensure_serve()async method (49 lines of serve lifecycle)--attachflag construction inexecute()- Extra imports:
atexit,signal,socket,subprocess
Replace with:
cmd = [self._bin, "run"]
if options.get("model"):
cmd.extend(["--model", str(options["model"])])
cmd.append(prompt)This brings opencode.py from ~205 lines down to ~75 (matching Gemini/Codex pattern).
2. Evaluate project_dir necessity
The project_dir field on HarnessConfig was added because OpenCode's sandboxed Write tool couldn't reach files outside --dir. Once the provider simplifies:
- Check if
opencode run --dir <path>+cwdfor output placement works without the temp subdir hack - If yes: remove the
project_dirrouting block from_runner.py(lines 148–158, 195–198) and the field fromtypes.py - If
--diris still useful as a generic concept: keep it but document it as provider-agnostic
3. Remove opencode_server config field
The opencode_server field on HarnessConfig and OPENCODE_SERVER env var exist solely for the serve+attach pattern. Remove:
opencode_serverfield fromtypes.py"opencode_server"from_runner.pyoptions listserver_urlparameter fromOpenCodeProvider.__init__server_urlpassthrough from_factory.py
4. Re-evaluate system prompt injection
OpenCode doesn't support a native --system-prompt flag, so the provider currently prepends system instructions to the user prompt. Check if upstream adds system prompt support — if so, use the native flag instead.
5. Update tests
- Remove/update any serve+attach specific test fixtures
- Add simple
opencode runsubprocess mock tests matching Codex/Gemini pattern - Verify
debug_complex_json.pyworks with the simplified provider
Expected outcome
After these changes, the OpenCode provider should be a ~75-line file matching the Codex/Gemini pattern — simple CLI subprocess invocation with no process lifecycle management. The ~160 lines of workaround code and ~38 lines of supporting config/routing would be removed.
References
- PR feat(harness): OpenCode support with schema retry, error preservation, and project_dir routing #220 — current implementation with workaround
- PR feat(harness): add .harness() method for external coding agent dispatch #210 — original harness feature
- Issue [SDK] OpenCode provider for .harness() #206 — original OpenCode provider tracking issue