Skip to content

Opencode backend#95

Merged
Jaraxxus-Me merged 10 commits intomainfrom
opencode-backend
Apr 19, 2026
Merged

Opencode backend#95
Jaraxxus-Me merged 10 commits intomainfrom
opencode-backend

Conversation

@merlerm
Copy link
Copy Markdown
Collaborator

@merlerm merlerm commented Apr 10, 2026

With this we can:

  • run our approach using (almost) any model with claude through ollama -> exception for OpenAI and Gemini, these will require a small layer to translate their message format to anthropic, which ollama does automatically. will add an issue as TODO but leaving to focus on other stuff for now
  • run our approach using opencode, which supports both ollama for open source and closed source providers (tested with OpenAI). -> we can NOT use claude/anthropic models using the subscription, see here: "There are plugins that allow you to use your Claude Pro/Max models with OpenCode. Anthropic explicitly prohibits this."
  • potentially support other methods to run open source models through something like vllm if we wanna use something else other than ollama.

Claude summary:

  • OpenCode backend: new backend supporting 75+ providers (OpenAI, Google, HuggingFace, Ollama, vLLM, etc.) via the OpenCode CLI. Implements the same AgentBackend protocol as Claude — tool calling, MCP tools, subagents, stream parsing, sandbox isolation.
  • Claude + Ollama: Claude Code CLI can now talk to local models served by Ollama via ANTHROPIC_BASE_URL, enabling open-source models (Qwen, Gemma, etc.) with the Claude backend's tool format.
  • Ollama auto-management: ensure_ollama() auto-starts the Ollama server if not running, inherits CUDA_VISIBLE_DEVICES from env, and configures OLLAMA_KEEP_ALIVE from backend config to auto-unload models from GPU.
  • Backend protocol: extracted shared AgentBackend protocol, SandboxConfig/SandboxResult types, and create_backend() factory. Backend configs are Hydra YAML files under conf/approach/backend/.
  • Process group kill: agent subprocesses run in their own session (start_new_session=True) so turn-limit and budget enforcement kill the entire process tree, not just the direct child.
  • Docker sandbox: updated for both backends — firewall whitelists per-provider API domains, env passthrough for auth tokens, correct data dir mounts.
  • MCP tool naming: OpenCode uses server_tool format (underscore) vs Claude's mcp__server__tool (double-underscore). Both are handled transparently.

merlerm and others added 8 commits April 2, 2026 19:03
Abstracts the agent backend behind an AgentBackend protocol, enabling
use of OpenCode (GPT, Gemini, local models) and Claude Code CLI pointed
at Ollama/vLLM via ANTHROPIC_BASE_URL alongside the default Claude backend.

New files:
- src/robocode/utils/backends/ -- AgentBackend protocol, ClaudeBackend,
  OpenCodeBackend, centralized PROVIDERS registry
- src/robocode/utils/sandbox_types.py -- shared dataclasses (breaks
  circular imports between sandbox.py and backends)
- experiments/conf/approach/backend/ -- Hydra sub-configs for backend
  selection (claude_sonnet, opencode_gpt4omini, claude_ollama_qwen, etc.)
- tests/utils/test_backends.py -- 36 tests for both backends

Key changes:
- sandbox.py/docker_sandbox.py delegate CLI invocation, env setup,
  config file generation, and stream parsing to the backend
- Docker image installs both claude and opencode CLIs
- init-firewall.sh supports ROBOCODE_FIREWALL_EXTRA_DOMAINS for
  provider-specific API endpoint whitelisting
- entrypoint.sh runs the command passed as args (not hardcoded claude)
- Budget enforcement for OpenCode (proc.kill on cost exceeded)
- Turn limit enforcement for both backends (proc.kill on turn exceeded)
- setup_mcp_config moved to robocode.mcp module

Tested: Claude default (BFS, 100% solve), OpenCode+GPT-4o-mini (tool
calls work, budget enforcement works), Claude+Ollama (connects but
small models lack tool-calling capability).
- Fix ordering: build_cli_cmd runs before setup_sandbox_files so
  .mcp/mcp_config.json exists when opencode.json is generated
- Fix MCP tool names: Claude uses mcp__server__tool, OpenCode uses
  server_tool. Tool descriptions now use backend-specific naming via
  mcp_tool_descriptions(backend_name)
- Fix missing mcp_tools in non-Docker SandboxConfig (was only passed
  for Docker configs)
- Escape braces in MCP description templates to avoid format() errors
- Add --variant flag support for OpenCode (reasoning effort)
- Add gpt-5.4 and gpt-5-nano backend configs
- Verified: gpt-5.4 successfully calls render_state MCP tool via OpenCode
- Fix ROBOCODE_FIREWALL_EXTRA_DOMAINS not reaching init-firewall.sh
  (sudo strips env; added SETENV to sudoers and explicit passthrough
  in entrypoint.sh)
- Create /home/node/.local/state and .local/share/opencode in Dockerfile
  (OpenCode/Bun needs these dirs)
- Add --print-logs --log-level INFO to OpenCode CLI; stderr goes to
  opencode_debug.log (separate from stream.jsonl)
- Verified: Docker+OpenCode+gpt-5-nano reaches api.openai.com, runs
  turns, writes approach.py
- Auto-start Ollama server when needed (ensure_ollama), inheriting
  CUDA_VISIBLE_DEVICES from env and OLLAMA_KEEP_ALIVE from config
- Fix orphaned child processes on turn limit: start_new_session=True
  on Popen + os.killpg to kill entire process group
- Auto-inject Ollama provider config in opencode.json when model
  uses ollama/ prefix
- Add ollama_keep_alive to backend configs (default 5m)
- Update README: system prerequisites table, curl-based installs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_ollama_server.py: ensure_ollama auto-start, CUDA_VISIBLE_DEVICES
  passthrough, binary-not-found error, server timeout error
- test_backends.py: Ollama provider injection in opencode.json,
  ensure_ollama called/skipped for both backends, keep_alive config
@merlerm
Copy link
Copy Markdown
Collaborator Author

merlerm commented Apr 10, 2026

To note: there are some tests failing for oracles. I also had these last week locally (same seed 636 as I mentioned here #89) but they never happened here on github. Now these seem to be happening here as well. I'm pretty sure these are unrelated to my changes: on my laptop if I pull from main now I still get the same two test fails. I'm not sure what's going on.

@merlerm
Copy link
Copy Markdown
Collaborator Author

merlerm commented Apr 10, 2026

also, fixes #83

Copy link
Copy Markdown
Owner

@tomsilver tomsilver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome work! per Slack, please also add some more documentation or restructuring to clarify the relationship between ollama and opencode / claude. my other comments are minor, but before merging, @Jaraxxus-Me should review and test some commands locally

Comment thread src/robocode/utils/backends/base.py Outdated
"""
name = backend_cfg["backend"]
if name == "claude":
from robocode.utils.backends.claude import ( # pylint: disable=import-outside-toplevel
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move these up to the top of the file and avoid the pylint comments. I've seen this pattern from claude before, idk why it does it...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I even have avoiding it in my CLAUDE.md hahahah, seems like I should be more convincing

"""
# Already reachable — nothing to do.
try:
urllib.request.urlopen(f"{_OLLAMA_URL}/api/tags", timeout=2) # noqa: S310
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of noqa, use pylint:disable=... so that it's clear what the issue that we're ignoring is (instead of S310, would write something human-readable)

for k, v in os.environ.items()
if not k.startswith("CLAUDECODE") and not k.startswith("OPENCODE")
}
# Prevent OpenCode from reading the host's CLAUDE.md as a fallback
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jeez, good catch, this would have been easy to miss -- makes me wonder what else we are potentially missing 🙈

Comment thread src/robocode/utils/backends/opencode.py Outdated
)


class OpenCodeBackend:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this and claude inherit from AgentBackend?

Comment thread src/robocode/utils/backends/opencode.py Outdated
)
return args

def build_env( # pylint: disable=unused-argument
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after you inherit, you can get rid of the pylint disable

Copy link
Copy Markdown
Collaborator

@Jaraxxus-Me Jaraxxus-Me left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These all look good to me! I think it is ready to merge.

@Jaraxxus-Me Jaraxxus-Me merged commit 6bc4450 into main Apr 19, 2026
4 checks passed
@Jaraxxus-Me Jaraxxus-Me deleted the opencode-backend branch April 19, 2026 15:01
@Jaraxxus-Me Jaraxxus-Me restored the opencode-backend branch April 19, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants