Conversation
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
|
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. |
|
also, fixes #83 |
tomsilver
left a comment
There was a problem hiding this comment.
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
| """ | ||
| name = backend_cfg["backend"] | ||
| if name == "claude": | ||
| from robocode.utils.backends.claude import ( # pylint: disable=import-outside-toplevel |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
jeez, good catch, this would have been easy to miss -- makes me wonder what else we are potentially missing 🙈
| ) | ||
|
|
||
|
|
||
| class OpenCodeBackend: |
There was a problem hiding this comment.
should this and claude inherit from AgentBackend?
| ) | ||
| return args | ||
|
|
||
| def build_env( # pylint: disable=unused-argument |
There was a problem hiding this comment.
after you inherit, you can get rid of the pylint disable
Jaraxxus-Me
left a comment
There was a problem hiding this comment.
These all look good to me! I think it is ready to merge.
With this we can:
Claude summary: