A Docker Compose stack that connects Hermes Agent + Hermes WebUI + OpenCode CLI into a fully integrated AI coding orchestrator.
Three services exposed:
| Service | Port | Purpose |
|---|---|---|
| Hermes WebUI | :8787 | Browser-based chat interface |
| Hermes Agent API | :8642 | OpenAI-compatible endpoint (/v1/chat/completions) |
| OpenCode Serve | :4096 | Headless server for remote opencode attach |
┌──────────────────────────────────────────────────────────────────────┐
│ Container: hermes-opencode │
│ │
│ ┌──────────────────────────┐ │
│ │ Hermes WebUI │ :8787 (browser chat UI) │
│ │ ┌────────────────────┐ │ │
│ │ │ Python server │ │ imports hermes_cli, creates │
│ │ │ (ghcr.io/nesquena/ │──│──> AIAgent → run_conversation() │
│ │ │ hermes-webui) │ │ │
│ │ └────────────────────┘ │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ Hermes Gateway │ :8642 (OpenAI-compatible API) │
│ │ (hermes gateway run │ │
│ │ --accept-hooks) │ Compatible with Open WebUI, LobeChat, │
│ │ /v1/chat/completions │ LibreChat, AnythingLLM, NextChat, etc.│
│ │ /v1/models │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ OpenCode Serve │ :4096 (headless server) │
│ │ (opencode serve) │ │
│ │ │ Remote attach via: │
│ │ opencode attach │ opencode attach http://host:4096 │
│ └──────────────────────────┘ │
│ │
│ Shared: │
│ Bind mount: /home/hermeswebui/.hermes/ │
│ config.yaml, state.db, skills/, logs/, webui/ │
│ │
│ External: │
│ LLM Provider (OpenAI-compatible endpoint via OPENAI_BASE_URL) │
│ OpenCode Zen auth (OPENCODE_ZEN_API_KEY) — optional │
└──────────────────────────────────────────────────────────────────────┘
git clone https://github.com/bachkukkik/hermes-x-opencode.git
cd hermes-x-opencodecp .env.example .envEdit .env and fill in your API keys:
# Required: API key for your LLM provider
OPENAI_API_KEY=sk-your-key-here
# Required: OpenAI-compatible base URL
OPENAI_BASE_URL=https://openrouter.ai/api/v1
# Required: Default model (other chat models are auto-discovered)
OPENAI_DEFAULT_MODEL=openai/gpt-4o
# Optional: OpenCode Zen API key (sign up at https://opencode.ai/auth)
# Required only for opencode/ built-in models (deepseek-v4-flash-free, etc.).
# If you only use models from your own LLM provider (via OPENAI_BASE_URL), leave this empty.
OPENCODE_ZEN_API_KEY=docker compose up -d --buildFirst build clones hermes-agent and installs OpenCode + Node.js 22. First startup installs Python dependencies, discovers models from your provider, installs skills, and starts all three services (~80-160s). Subsequent starts are faster (~25-50s).
- WebUI: http://localhost:8787
- Agent API: http://localhost:8642/v1/chat/completions
- OpenCode attach:
opencode attach http://localhost:4096
# Verify OpenCode works
opencode /workspace -m opencode/deepseek-v4-flash-free --prompt "Respond with exactly: OPENCODE_SMOKE_OK"
# Expected: output includes OPENCODE_SMOKE_OKBrowser-based chat interface. The agent has access to opencode via the terminal tool.
Start chatting and ask Hermes to delegate coding work:
"Use opencode to build me a Python CLI tool that converts CSV to JSON"
OpenAI-compatible endpoint. Connect any OpenAI-compatible client:
# List models
curl http://localhost:8642/v1/models
# Chat completion (streaming)
curl -N http://localhost:8642/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "hermes-agent",
"messages": [{"role": "user", "content": "Hello"}],
"stream": true
}'
# Chat completion (non-streaming)
curl http://localhost:8642/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "hermes-agent",
"messages": [{"role": "user", "content": "What is 2+2?"}]
}'With auth (if HERMES_API_KEY is set, or check auto-generated key):
# Get auto-generated key from logs
API_KEY=$(docker logs $(docker compose ps -q hermes-opencode) 2>&1 | grep "Generated random HERMES_API_KEY" | sed 's/.*: //')
curl http://localhost:8642/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "hermes-agent", "messages": [{"role": "user", "content": "Hello"}]}'Connect external UIs by pointing them at http://<host>:8642/v1 with model name hermes-agent. Compatible with Open WebUI, LobeChat, LibreChat, AnythingLLM, NextChat, ChatBox, etc.
Session continuity: Pass X-Hermes-Session-Id header to continue an existing conversation.
⚠ EXPERIMENTAL: The
opencode serve+attachpattern is not fully functional. See Limitations and issue #10.
Headless OpenCode server. Attach from another machine:
# From any machine on the network
opencode attach http://<host-ip>:4096
# One-shot prompt
opencode run --attach http://<host-ip>:4096 "What does this project do?"Note: The previous
opencode run --agent plan/buildcommands are broken in the current environment (see #8 and #9). The patterns below use the verified one-shot flow.
Run a single coding task in one shot, then return:
# Inside the container, or on a host with opencode installed
opencode /workspace/project -m opencode/deepseek-v4-flash-free \
--prompt "Add retry logic to api.py"Free models that require no auth: opencode/deepseek-v4-flash-free, opencode/mimo-v2.5-free, opencode/nemotron-3-ultra-free, opencode/north-mini-code-free, opencode/big-pickle.
Generate a plan first, then feed it back as the implementation prompt:
# Step 1: Generate a plan
opencode /workspace/project -m opencode/deepseek-v4-flash-free \
--prompt "Read PRD.md and output a step-by-step implementation plan" \
> /tmp/plan.md
# Step 2: Execute the plan
opencode /workspace/project -m opencode/deepseek-v4-flash-free \
--prompt "Implement the plan in /tmp/plan.md"Point any OpenAI-compatible client at :8642/v1 and use model hermes-agent. The agent runs server-side with full tool access.
curl -X POST http://localhost:8642/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"hello"}]}'For the full CEO-OpenCode multi-agent delegation workflow (Hermes decomposes, OpenCode implements, Hermes verifies), see issue #9.
| Scenario | Service | Why |
|---|---|---|
| Browser-based chat | WebUI :8787 | Full UI with sessions, file browser |
| Connect external chat UI | Agent API :8642 | OpenAI-compatible, streaming |
| Remote coding (experimental) | OpenCode :4096 | Attach from another machine — see Limitations |
| CI/CD integration | Agent API :8642 | Programmatic access |
| Code implementation | opencode <dir> --prompt |
One-shot, model-pinned, scriptable |
The following are known limitations of the current setup. Most have workarounds documented in the linked issues.
opencode runreturns "Session not found" — therunsubcommand cannot re-enter an existing session. Use the one-shotopencode <dir> --promptpattern instead (see Usage Patterns and #7).opencode serveexits immediately — the headless server is not yet stable in this environment and is disabled by default via theOPENCODE_SERVE_ENABLEDenv var. See #10.- Interactive multi-turn TUI sessions cannot receive follow-up stdin from the Hermes process layer — the agent can launch
opencodebut cannot drive an interactive REPL. Use one-shot prompts or the Agent API (see #6). host.docker.internalresolves viaextra_hostsindocker-compose.yml, which maps it to the host gateway IP. This works on all platforms including bare Linux. Previously required entrypoint-level IP detection (see #12, #27, #31).opencode acpdoes not bind TCP ports — ACP is designed for IDE stdio integration, not as a standalone TCP server. The--portflag is accepted but never binds. This is an upstream limitation (see vanilla-coder#6). Use Serve + Attach instead (opencode serve+opencode run --attach).
All configuration is done through the .env file. See .env.example for the full list.
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY |
Yes | — | API key for the LLM provider |
OPENAI_BASE_URL |
Yes | — | OpenAI-compatible base URL |
OPENAI_DEFAULT_MODEL |
No | openai/gpt-4o |
Default model (others auto-discovered). Shared fallback for both Hermes and OpenCode when no per-app override is set. |
OPENAI_SMALL_MODEL |
No | falls back to OPENAI_DEFAULT_MODEL |
Small model for lightweight OpenCode tasks. |
HERMES_DEFAULT_MODEL |
No | falls back to OPENAI_DEFAULT_MODEL |
Per-app override for the Hermes default model. |
OPENCODE_DEFAULT_MODEL |
No | falls back to OPENAI_DEFAULT_MODEL |
Per-app override for the OpenCode default model. |
OPENCODE_SMALL_MODEL |
No | falls back to OPENAI_SMALL_MODEL |
Per-app override for the OpenCode small model. |
HERMES_DELEGATION_MAX_ITERATIONS |
No | 50 |
Max tool-calling turns for delegate_task subagents. |
HERMES_DELEGATION_MODEL |
No | — (inherit parent) | Model for delegated subagent conversations. Set to route subagents to a different (typically cheaper/faster) model. |
HERMES_DELEGATION_PROVIDER |
No | — (inherit parent) | Provider for delegated subagent conversations. Set alongside HERMES_DELEGATION_MODEL to use a different provider. |
OPENCODE_ZEN_API_KEY |
No | — | API key for OpenCode Zen models (sign up at https://opencode.ai/auth). Required only for opencode/ built-in models. |
HERMES_WEBUI_PASSWORD |
No | empty | Password-protect the WebUI |
HERMES_WEBUI_PORT |
No | 8787 |
Host port for WebUI |
HERMES_API_KEY |
No | auto-generated | Bearer token for Agent API |
HERMES_API_PORT |
No | 8642 |
Host port for Agent API |
OPENCODE_SECURITY_MODE |
No | strict |
Security profile: strict/standard/yolo |
OPENCODE_SERVE_PORT |
No | 4096 |
Host port for OpenCode serve |
OPENCODE_COMPRESSION_THRESHOLD |
No | 0.76 |
DCP compress point as fraction (0.0–1.0) of each model's context window; written to managed dcp.jsonc as compress.maxContextLimit: "<pct>%" |
SKIP_SKILL_INSTALL |
No | 0 |
Skip skill installation (set 1) |
HOST_UID / HOST_GID |
No | 1000 |
File permission UID/GID |
BROWSER_HUMAN_LOOP_ENABLED |
No | false |
Enable the viewable/interactive Chromium browser stack (Xvfb + VNC + CDP). See 15 — Browser Human-in-the-Loop. |
BROWSER_VNC_PASSWORD |
No | hermes |
VNC password for the browser human-in-the-loop web client (:6901). Set a strong password if you expose the port. |
BROWSER_DISPLAY_WIDTH |
No | 1920 |
Xvfb display width in px (also Chromium --window-size). Lowers the agent's max CDP viewport. |
BROWSER_DISPLAY_HEIGHT |
No | 1080 |
Xvfb display height in px. See BROWSER_DISPLAY_WIDTH. |
When OPENAI_BASE_URL and OPENAI_API_KEY are set, the entrypoint automatically discovers all available chat models from your LLM provider at startup. Non-chat models (embeddings, TTS, image generation) and wildcard patterns are filtered out. Both the Hermes config and OpenCode config receive the same model list.
OPENAI_DEFAULT_MODEL specifies the default model. If it's not found in the discovered list, it's added automatically. Set HERMES_DEFAULT_MODEL, OPENCODE_DEFAULT_MODEL, or OPENCODE_SMALL_MODEL to use a different model for a specific app — otherwise each app follows OPENAI_DEFAULT_MODEL (and OPENAI_SMALL_MODEL where applicable). No manual model configuration needed.
The OPENCODE_SECURITY_MODE variable controls the OpenCode agent's permission profile:
| Mode | Bash rules | Interpreters | .env access | Use case |
|---|---|---|---|---|
strict (default) |
31 deny rules | Blocked | Blocked | Production |
standard |
22 deny rules | Allowed | Blocked | Development |
yolo |
Allow all | Allowed | Allowed | Trusted sandbox |
All modes include the cc-safety-net plugin which blocks destructive git and filesystem commands.
The hermes-agent version is set at build time:
# Default (main branch)
docker compose up -d --build
# Specific version
docker compose build --build-arg HERMES_AGENT_VERSION=v1.2.3
docker compose up -dSet OPENCODE_ZEN_API_KEY in .env if you want to use opencode/ built-in Zen models (e.g. opencode/deepseek-v4-flash-free). The key is validated at startup with a helpful warning on failure. If you only use models from your own LLM provider (via OPENAI_BASE_URL), you can leave this empty — the litellm provider in opencode.jsonc works without it. Get a key at https://opencode.ai/auth.
Data is stored in bind mounts under volumes_hermes_opencode/data/:
| Host path | Container path | Contents |
|---|---|---|
data/hermes-home/ |
/home/hermeswebui/.hermes |
config.yaml, state.db, skills/, logs/, webui/, hermes-agent/ |
data/workspace/ |
/workspace |
User project workspace |
This data survives container restarts and rebuilds.
If your LLM provider is behind Cloudflare (e.g., a LiteLLM proxy), the OpenAI Python SDK's default User-Agent gets blocked. This stack patches the hermes-agent CustomProfile at build time to send User-Agent: hermes-agent/1.0 instead. No manual fix needed.
If you still see 403s, verify the patch is applied:
docker exec $(docker compose ps -q hermes-opencode) grep "User-Agent" /home/hermeswebui/.hermes/hermes-agent/plugins/model-providers/custom/__init__.pyThe gateway starts after the WebUI is healthy. Check logs:
docker logs $(docker compose ps -q hermes-opencode) 2>&1 | grep -i gatewayVerify config.yaml has the api_server platform:
docker exec $(docker compose ps -q hermes-opencode) cat /home/hermeswebui/.hermes/config.yamlOpenCode serve starts after the gateway. Check:
docker logs $(docker compose ps -q hermes-opencode) 2>&1 | grep -i "opencode serve"
docker exec $(docker compose ps -q hermes-opencode) opencode --versionThe WebUI and agent share state via the bind mount. If sessions don't appear:
docker exec $(docker compose ps -q hermes-opencode) ls -la /home/hermeswebui/.hermes/The key_env field must contain the literal string OPENAI_API_KEY, not the actual key value. Verify:
docker exec $(docker compose ps -q hermes-opencode) cat /home/hermeswebui/.hermes/config.yamlThis stack has been brought into feature parity with the downstream hermes-x-opencode--host-machine repository, specifically porting:
- PR #22: Inline
OPENAI_API_KEYresolution at generation time — preventsAuthentication Error, No api key passed inwhen the key isn't exported in the current shell. - PR #23: Managed
dcp.jsoncgeneration with per-model compression thresholds — DCP now compresses at a percentage of each model's real context window (default 76%) instead of a hard 100k token ceiling.
See PRD.md section 28 for detailed problem triage, success criteria, and verification policy.
.
├── docker-compose.yml # Service: 3 ports, bind mounts, env, healthcheck
├── .env.example # All supported env vars
├── .gitignore
├── PRD.md # Engineer handoff document (includes feature parity gap analysis)
├── README.md # This file
├── docs/ # Architecture documentation (01–13)
└── volumes_hermes_opencode/
├── build/
│ ├── Dockerfile # Multi-step build: base + node + opencode + agent + patch
│ └── scripts/
│ ├── entrypoint.sh # Model discovery, config gen, start 3 services
│ └── install-skills.sh # Skills from 6 upstream sources
└── data/
├── hermes-home/.gitkeep # Bind mount target (agent config, sessions)
└── workspace/.gitkeep # Bind mount target (user workspace)
- Hermes Agent by Nous Research
- Hermes WebUI by Nicholas Griffin
- OpenCode by the OpenCode team
MIT