Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ jobs:
- { suffix: "-opencode", dockerfile: "Dockerfile.opencode", artifact: "opencode" }
- { suffix: "-cursor", dockerfile: "Dockerfile.cursor", artifact: "cursor" }
- { suffix: "-hermes", dockerfile: "Dockerfile.hermes", artifact: "hermes" }
- { suffix: "-openclaw", dockerfile: "Dockerfile.openclaw", artifact: "openclaw" }
platform:
- { os: linux/amd64, runner: ubuntu-latest }
- { os: linux/arm64, runner: ubuntu-24.04-arm }
Expand Down Expand Up @@ -137,6 +138,7 @@ jobs:
- { suffix: "-opencode", artifact: "opencode" }
- { suffix: "-cursor", artifact: "cursor" }
- { suffix: "-hermes", artifact: "hermes" }
- { suffix: "-openclaw", artifact: "openclaw" }
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -188,6 +190,7 @@ jobs:
- { suffix: "-opencode" }
- { suffix: "-cursor" }
- { suffix: "-hermes" }
- { suffix: "-openclaw" }
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- { dockerfile: Dockerfile.opencode, suffix: "-opencode", agent: "opencode", agent_args: "acp" }
- { dockerfile: Dockerfile.cursor, suffix: "-cursor", agent: "cursor-agent", agent_args: "acp" }
- { dockerfile: Dockerfile.hermes, suffix: "-hermes", agent: "hermes-acp", agent_args: "" }
- { dockerfile: Dockerfile.openclaw, suffix: "-openclaw", agent: "openclaw", agent_args: "acp" }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down
54 changes: 54 additions & 0 deletions Dockerfile.openclaw
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# --- Build stage ---
FROM rust:1-bookworm AS builder
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs && cargo build --release && rm -rf src
COPY src/ src/
RUN touch src/main.rs && cargo build --release

# --- Runtime stage ---
# node:22-bookworm-slim mirrors the base image used by Dockerfile.claude,
# Dockerfile.codex, Dockerfile.gemini, and Dockerfile.opencode, keeping the
# project on a single consistent runtime base.
#
# OpenClaw requires Node >= 22.16 (enforced by openclaw.mjs at startup).
#
# openclaw is published to npm as a single global package — `openclaw acp`
# is a thin WebSocket bridge to a separately-running openclaw gateway; the
# gateway itself is NOT bundled in this image. See docs/openclaw.md for the
# required external gateway setup.
#
# Version is pinned for reproducible builds. Bump via a dedicated PR.
FROM node:22-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl procps ripgrep tini && rm -rf /var/lib/apt/lists/*

# Install openclaw
# Note: openclaw does not publish SHA256 checksums for its npm releases,
# so checksum verification is not performed.
ARG OPENCLAW_VERSION=latest
RUN npm install -g openclaw@${OPENCLAW_VERSION} --retry 3

# Install gh CLI (matches Dockerfile.claude / Dockerfile.gemini / Dockerfile.codex)
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list && \
apt-get update && apt-get install -y --no-install-recommends gh && \
rm -rf /var/lib/apt/lists/*

ENV HOME=/home/node
WORKDIR /home/node

COPY --from=builder --chown=node:node /build/target/release/openab /usr/local/bin/openab

# Pre-create bridge state dir with correct ownership (mirrors the .opencode
# pattern in Dockerfile.opencode — exec-based config writes shouldn't end up
# root-owned).
RUN mkdir -p /home/node/.openclaw && chown -R node:node /home/node/.openclaw

USER node
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD pgrep -x openab || exit 1
ENTRYPOINT ["tini", "--"]
CMD ["openab", "run", "-c", "/etc/openab/config.toml"]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ The bot creates a thread. After that, just type in the thread — no @mention ne
| Copilot CLI ⚠️ | `copilot --acp --stdio` | Native | [docs/copilot.md](docs/copilot.md) |
| Cursor | `cursor-agent acp` | Native | [docs/cursor.md](docs/cursor.md) |
| Hermes Agent | `hermes-acp` | Native | [docs/hermes.md](docs/hermes.md) |
| OpenClaw | `openclaw acp` | Native (requires external gateway) | [docs/openclaw.md](docs/openclaw.md) |

> 🔧 Running multiple agents? See [docs/multi-agent.md](docs/multi-agent.md)

Expand Down
8 changes: 8 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ working_dir = "/home/agent"
# # Supports 30+ providers (xAI Grok OAuth, Anthropic, OpenAI Codex, Gemini, etc.)
# # Provider switching: kubectl exec -it <pod> -- hermes model

# [agent]
# command = "openclaw"
# args = ["acp", "--url", "ws://openclaw-gateway:18789", "--session", "agent:main:main"]
# working_dir = "/home/node"
# env = { OPENCLAW_GATEWAY_TOKEN = "${OPENCLAW_GATEWAY_TOKEN}" }
# # Requires a separately-running openclaw gateway — see docs/openclaw.md.
# # Model selection lives on the gateway side, not via OpenAB's /model command.

[pool]
max_sessions = 10
session_ttl_hours = 24
Expand Down
167 changes: 167 additions & 0 deletions docs/openclaw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# OpenClaw

[OpenClaw](https://github.com/openclaw/openclaw) is a self-hosted AI agent
gateway. OpenAB connects to it via the `openclaw acp` bridge, which speaks ACP
over stdio and forwards prompts to a running OpenClaw gateway over WebSocket.

Unlike other ACP backends, OpenClaw requires a **separately-running gateway
service** — the OpenAB container does not embed the gateway. Provider API keys
(OpenAI, Anthropic, etc.), agent definitions, and model selection all live in
the gateway, not in OpenAB.

## Architecture

```
Discord ──► openab ──stdio──► openclaw acp ──WS──► openclaw gateway ──HTTPS──► LLM
(inside container) (separate service)
```

## Prerequisites

- A running OpenClaw gateway, reachable from the OpenAB container. The
upstream [Quick Start](https://github.com/openclaw/openclaw#quick-start)
walks through `openclaw onboard --install-daemon` + `openclaw gateway`.
- A gateway token. Generated on first gateway start; persisted at
`~/.openclaw/gateway.token` on the gateway host.

## Docker Image

```bash
docker build -f Dockerfile.openclaw -t openab-openclaw:latest .
```

The image installs the `openclaw` npm package globally and requires Node 22.16+.

## Helm Install

```bash
helm install openab openab/openab \
--set agents.kiro.enabled=false \
--set agents.openclaw.discord.enabled=true \
--set agents.openclaw.discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string 'agents.openclaw.discord.allowedChannels[0]=YOUR_CHANNEL_ID' \
--set agents.openclaw.image=ghcr.io/openabdev/openab-openclaw:latest \
--set agents.openclaw.command=openclaw \
--set-json 'agents.openclaw.args=["acp","--url","ws://openclaw-gateway:18789","--session","agent:main:main"]' \
--set agents.openclaw.workingDir=/home/node \
--set agents.openclaw.env.OPENCLAW_GATEWAY_TOKEN="$OPENCLAW_GATEWAY_TOKEN"
```
Comment on lines +37 to +48

> Set `agents.kiro.enabled=false` to disable the default Kiro agent.

## Manual config.toml

```toml
[agent]
command = "openclaw"
args = [
"acp",
"--url", "ws://openclaw-gateway:18789",
"--session", "agent:main:main",
]
working_dir = "/home/node"
env = { OPENCLAW_GATEWAY_TOKEN = "${OPENCLAW_GATEWAY_TOKEN}" }
```

### Required flags

| Flag | Purpose |
|---|---|
| `--url <ws://...>` | Gateway WebSocket URL. Use `wss://` for TLS. |
| `--session <key>` | OpenClaw session key — see [Sessions and Models](#sessions-and-models). |
| `--token-file <path>` *(alt. to env var)* | Path to a file holding the gateway token. Useful for Kubernetes Secret mounts. |

### Gateway authentication

Provide the shared secret via **one** of:

- Env var: `OPENCLAW_GATEWAY_TOKEN`
- Token file: `--token-file /path/to/token`
- CLI flag: `--token <value>` (avoid — visible in process list)

## Sessions and Models

OpenAB's `/model` slash command **does not** select LLM models for the OpenClaw
backend.

OpenClaw routes by **session key** (e.g., `agent:main:main`), and each session
resolves to an **agent definition** on the gateway side. The agent definition
determines which provider and which model to use.

To switch models or providers:

1. Edit the agent definition in the gateway's `~/.openclaw/openclaw.json`, or
2. Change `--session` in `config.toml` to point at a different agent and
restart the pod.

In-band ACP options the bridge does pass through:

| Option | Effect |
|---|---|
| `thought_level` | Verbosity of agent thinking output |
| `reasoning_level` | Reasoning effort hint to the model |
| `verbose_level` / `trace_level` | Diagnostic detail |
| `fast_mode` | Latency-optimized routing |
| `response_usage` | Include token usage in responses |
| `timeout_seconds` | Per-prompt timeout |

## Capabilities and Limits

| Feature | Supported |
|---|---|
| Text prompts | ✅ |
| Image attachments (inbound) | ✅ |
| Audio attachments | ❌ |
| Embedded context resources | ✅ |
| `session/load` | ✅ (only for sessions created through the bridge) |
| Per-session MCP servers | ❌ — rejected by the bridge; configure on the gateway |
| `/reset`, `/agent` slash commands | ✅ |
| `/model` slash command | ⚠️ See [Sessions and Models](#sessions-and-models) |
| Prompt size cap | 2 MB |

## Persisted Paths (PVC)

| Path | Contents |
|------|----------|
| `/home/node/.openclaw/` | Bridge state (small — token file if used) |

The bulk of OpenClaw state — provider API keys, agent definitions, session
transcripts, event ledger — lives on the **gateway side**, not in the OpenAB
container.

## Troubleshooting

### Bridge exits immediately with `gateway closed before ready`

The gateway is not reachable at the configured `--url`. Check:

- The gateway service is running and listening on the expected port
(default 18789).
- DNS resolution for the gateway hostname works from inside the OpenAB pod.
- The token (env var or `--token-file`) matches the gateway's configured token.

### `invalid token` / authentication failed

Token mismatch. Regenerate the token on the gateway host per the upstream
docs, then update `OPENCLAW_GATEWAY_TOKEN` (or the file referenced by
`--token-file`) and restart the OpenAB pod.

### Messages take a long time, then return empty

Likely a gateway-side issue — the agent definition references a provider
without an API key, or the model name is invalid. The bridge cannot see this;
only the gateway logs it:

```bash
kubectl logs deployment/openclaw-gateway --tail=200
```

### `/model gpt-4o` has no effect

Expected — see [Sessions and Models](#sessions-and-models). Change the
gateway-side agent definition or use a different `--session` key.

### Per-session MCP servers don't work

The bridge rejects per-session `mcpServers` in `session/new`. Configure MCP
servers at the gateway level instead — see the upstream OpenClaw docs.
Loading