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 —
atexit hooks don't fire on SIGKILL, leaving orphan opencode serve processes.
- Singleton shared state — class-level
_serve_proc / _serve_url / _serve_lock are 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
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_lock class-level state
_get_lock() classmethod
_cleanup_serve() classmethod + atexit registration
_ensure_serve() async method (49 lines of serve lifecycle)
--attach flag construction in execute()
- 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> + cwd for output placement works without the temp subdir hack
- If yes: remove the
project_dir routing block from _runner.py (lines 148–158, 195–198) and the field from types.py
- If
--dir is 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_server field from types.py
"opencode_server" from _runner.py options list
server_url parameter from OpenCodeProvider.__init__
server_url passthrough 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 run subprocess mock tests matching Codex/Gemini pattern
- Verify
debug_complex_json.py works 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
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 inopencode runheadless mode (opencode#13851, affects v1.2.10–v1.2.16).This workaround auto-spawns a long-lived
opencode serveprocess on a random port and routes all calls throughopencode run --attach <url>. This is architecturally problematic because:atexithooks don't fire on SIGKILL, leaving orphanopencode serveprocesses._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.Blocked on
opencode run"Session not found" in headless modeChanges 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()atexit,signal,socket,subprocessReplace with:
This brings opencode.py from ~205 lines down to ~75 (matching Gemini/Codex pattern).
2. Evaluate
project_dirnecessityThe
project_dirfield onHarnessConfigwas added because OpenCode's sandboxed Write tool couldn't reach files outside--dir. Once the provider simplifies:opencode run --dir <path>+cwdfor output placement works without the temp subdir hackproject_dirrouting block from_runner.py(lines 148–158, 195–198) and the field fromtypes.py--diris still useful as a generic concept: keep it but document it as provider-agnostic3. Remove
opencode_serverconfig fieldThe
opencode_serverfield onHarnessConfigandOPENCODE_SERVERenv 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.py4. Re-evaluate system prompt injection
OpenCode doesn't support a native
--system-promptflag, 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
opencode runsubprocess mock tests matching Codex/Gemini patterndebug_complex_json.pyworks with the simplified providerExpected 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