diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b360417..17b4505a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,6 +72,7 @@ jobs: - { suffix: "-copilot", dockerfile: "Dockerfile.copilot", artifact: "copilot" } - { suffix: "-opencode", dockerfile: "Dockerfile.opencode", artifact: "opencode" } - { suffix: "-cursor", dockerfile: "Dockerfile.cursor", artifact: "cursor" } + - { suffix: "-hermes", dockerfile: "Dockerfile.hermes", artifact: "hermes" } platform: - { os: linux/amd64, runner: ubuntu-latest } - { os: linux/arm64, runner: ubuntu-24.04-arm } @@ -135,6 +136,7 @@ jobs: - { suffix: "-copilot", artifact: "copilot" } - { suffix: "-opencode", artifact: "opencode" } - { suffix: "-cursor", artifact: "cursor" } + - { suffix: "-hermes", artifact: "hermes" } runs-on: ubuntu-latest permissions: contents: read @@ -185,6 +187,7 @@ jobs: - { suffix: "-copilot" } - { suffix: "-opencode" } - { suffix: "-cursor" } + - { suffix: "-hermes" } runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/docker-smoke-test.yml b/.github/workflows/docker-smoke-test.yml index 64b4653a..0fd0c238 100644 --- a/.github/workflows/docker-smoke-test.yml +++ b/.github/workflows/docker-smoke-test.yml @@ -20,6 +20,7 @@ jobs: - { dockerfile: Dockerfile.copilot, suffix: "-copilot", agent: "copilot", agent_args: "--acp" } - { 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: "" } runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 69ba8907..a5cac441 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -18,6 +18,7 @@ on: - copilot - cursor - gemini + - hermes - opencode default: 'default' diff --git a/Dockerfile.hermes b/Dockerfile.hermes new file mode 100644 index 00000000..7de02b41 --- /dev/null +++ b/Dockerfile.hermes @@ -0,0 +1,52 @@ +# --- 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 --- +FROM python:3.12-slim-bookworm + +# Create agent user first so WORKDIR gets correct ownership +RUN useradd -m -u 1000 agent + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl procps ripgrep tini git ffmpeg xz-utils && \ + rm -rf /var/lib/apt/lists/* + +# Install Hermes Agent — pinned to known commit with checksum verification +# Root install uses FHS layout: binary at /usr/local/bin/hermes, code at /usr/local/lib/hermes-agent +# HERMES_HOME points to agent user's data dir for OAuth tokens and config +ARG HERMES_INSTALL_COMMIT=cc07e30f45267c00fac97ea5569c606aca5a1ffb +ARG HERMES_INSTALL_SHA256=cb94b83b96cc924716bd1651411955da7495912ef68affe6788840e6cf147d41 +RUN curl -fsSL "https://raw.githubusercontent.com/NousResearch/hermes-agent/${HERMES_INSTALL_COMMIT}/scripts/install.sh" \ + -o /tmp/install-hermes.sh && \ + echo "${HERMES_INSTALL_SHA256} /tmp/install-hermes.sh" | sha256sum -c - && \ + HERMES_HOME=/home/agent/.hermes bash /tmp/install-hermes.sh && \ + rm /tmp/install-hermes.sh && \ + chmod -R a+rX /root/.local/share/uv && \ + chmod a+rx /root /root/.local /root/.local/share && \ + ln -sf /usr/local/lib/hermes-agent/venv/bin/hermes-acp /usr/local/bin/hermes-acp + +# Install gh CLI +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/agent +WORKDIR /home/agent + +COPY --from=builder --chown=1000:1000 /build/target/release/openab /usr/local/bin/openab + +RUN chown -R agent:agent /home/agent + +USER agent +HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ + CMD pgrep -x openab || exit 1 +ENTRYPOINT ["tini", "--"] +CMD ["openab", "run", "-c", "/etc/openab/config.toml"] diff --git a/README.md b/README.md index b7110cde..ae2079b3 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,23 @@ ![OpenAB banner](images/banner.jpg) -A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**, and any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI, etc.) over stdio JSON-RPC — delivering the next-generation development experience. **Telegram, LINE, Feishu/Lark, Google Chat**, and other webhook-based platforms are supported via the standalone [Custom Gateway](gateway/). +A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**, and any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI, Hermes, etc.) over stdio JSON-RPC — delivering the next-generation development experience. **Telegram, LINE, Feishu/Lark, Google Chat**, and other webhook-based platforms are supported via the standalone [Custom Gateway](gateway/). 🪼 **Join our community!** Come say hi on Discord — we'd love to have you: **[🪼 OpenAB — Official](https://discord.gg/DmbhfDZjQS)** 🎉 ``` -┌──────────────┐ Gateway WS ┌──────────────┐ ACP stdio ┌──────────────┐ -│ Discord │◄─────────────►│ │──────────────►│ coding CLI │ -│ User │ │ openab │◄── JSON-RPC ──│ (acp mode) │ -├──────────────┤ Socket Mode │ (Rust) │ └──────────────┘ -│ Slack │◄─────────────►│ │ -│ User │ └──────┬───────┘ -├──────────────┤ │ WebSocket (outbound) -│ Telegram │◄──webhook──┐ │ -│ User │ │ │ -├──────────────┤ ▼ ▼ -│ LINE │◄──webhook──┌──────────────────┐ -│ User │ │ Custom Gateway │ +┌──────────────┐ Gateway WS ┌──────────────┐ ACP stdio ┌──────────────────┐ +│ Discord │◄─────────────►│ │──────────────►│ coding CLI │ +│ User │ │ openab │◄── JSON-RPC ──│ (acp mode) │ +├──────────────┤ Socket Mode │ (Rust) │ ├──────────────────┤ +│ Slack │◄─────────────►│ │ │ kiro-cli acp │ +│ User │ └──────┬───────┘ │ claude-agent-acp │ +├──────────────┤ │ WebSocket │ codex-acp │ +│ Telegram │◄──webhook──┐ │ (outbound) │ gemini --acp │ +│ User │ │ │ │ copilot --acp │ +├──────────────┤ ▼ ▼ │ hermes-acp │ +│ LINE │◄──webhook──┌──────────────────┐ │ opencode acp │ +│ User │ │ Custom Gateway │ └──────────────────┘ ├──────────────┤ │ (standalone) │ │ Feishu/Lark │◄───WS──────│ │ │ User │ │ │ @@ -38,7 +38,7 @@ A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**, - **Multi-platform** — supports Discord and Slack, run one or both simultaneously - **Custom Gateway** — extend to Telegram, LINE, Feishu/Lark, Google Chat, MS Teams via standalone [gateway](gateway/) -- **Pluggable agent backend** — swap between Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI via config +- **Pluggable agent backend** — swap between Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI, Hermes via config - **@mention trigger** — mention the bot in an allowed channel to start a conversation - **Thread-based multi-turn** — auto-creates threads; no @mention needed for follow-ups - **Multi-agent collaboration** — bot-to-bot messaging for coordinated workflows ([docs/multi-agent.md](docs/multi-agent.md)) @@ -168,6 +168,7 @@ The bot creates a thread. After that, just type in the thread — no @mention ne | OpenCode | `opencode acp` | Native | [docs/opencode.md](docs/opencode.md) | | 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) | > 🔧 Running multiple agents? See [docs/multi-agent.md](docs/multi-agent.md) diff --git a/charts/openab/values.yaml b/charts/openab/values.yaml index 50b65915..0e4b5952 100644 --- a/charts/openab/values.yaml +++ b/charts/openab/values.yaml @@ -140,6 +140,27 @@ agents: # storageClass: "" # size: 1Gi # image: "ghcr.io/openabdev/openab-cursor:latest" + # hermes: + # command: hermes-acp + # discord: + # enabled: true + # allowedChannels: + # - "YOUR_CHANNEL_ID" + # allowedUsers: [] + # allowBotMessages: "off" + # trustedBotIds: [] + # workingDir: /home/agent + # env: {} + # envFrom: [] + # secretEnv: [] + # pool: + # maxSessions: 10 + # sessionTtlHours: 24 + # persistence: + # enabled: true + # storageClass: "" + # size: 1Gi + # image: "ghcr.io/openabdev/openab-hermes" image: "" command: kiro-cli args: diff --git a/config.toml.example b/config.toml.example index d33a0902..20ccff34 100644 --- a/config.toml.example +++ b/config.toml.example @@ -103,6 +103,13 @@ working_dir = "/home/agent" # working_dir = "/home/agent" # env = {} # Auth via: kubectl exec -it -- cursor-agent login +# [agent] +# command = "hermes-acp" +# working_dir = "/home/agent" +# # Auth via: kubectl exec -it -- hermes auth add xai-oauth +# # Supports 30+ providers (xAI Grok OAuth, Anthropic, OpenAI Codex, Gemini, etc.) +# # Provider switching: kubectl exec -it -- hermes model + [pool] max_sessions = 10 session_ttl_hours = 24 diff --git a/docs/config-reference.md b/docs/config-reference.md index a0eef687..907c122c 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -141,6 +141,11 @@ working_dir = "/home/node" command = "cursor-agent" args = ["acp", "--model", "auto", "--workspace", "/home/agent"] working_dir = "/home/agent" + +# Hermes Agent +[agent] +command = "hermes-acp" +working_dir = "/home/agent" ``` --- diff --git a/docs/hermes.md b/docs/hermes.md new file mode 100644 index 00000000..2ab9ef2f --- /dev/null +++ b/docs/hermes.md @@ -0,0 +1,137 @@ +# Hermes Agent + +[Hermes Agent](https://github.com/NousResearch/hermes-agent) by Nous Research supports ACP natively via the `hermes acp` subcommand (or the `hermes-acp` binary). + +Hermes acts as a multi-provider inference gateway — it handles OAuth token lifecycle, credential storage, and provider routing so OAB agents don't need to manage auth directly. + +## Docker Image + +```bash +docker build -f Dockerfile.hermes -t openab-hermes:latest . +``` + +The image installs Hermes Agent via the official install script. + +## Helm Install + +```bash +helm install openab openab/openab \ + --set agents.kiro.enabled=false \ + --set agents.hermes.discord.enabled=true \ + --set agents.hermes.discord.botToken="$DISCORD_BOT_TOKEN" \ + --set-string 'agents.hermes.discord.allowedChannels[0]=YOUR_CHANNEL_ID' \ + --set agents.hermes.image=ghcr.io/openabdev/openab-hermes:latest \ + --set agents.hermes.command=hermes-acp \ + --set agents.hermes.workingDir=/home/agent +``` + +> Set `agents.kiro.enabled=false` to disable the default Kiro agent. + +## Manual config.toml + +```toml +[agent] +command = "hermes-acp" +working_dir = "/home/agent" +``` + +## Authentication + +Hermes supports 30+ providers. Authenticate inside the pod: + +```bash +kubectl exec -it -- hermes auth add xai-oauth # xAI Grok (SuperGrok $30/mo) +kubectl exec -it -- hermes auth add nous # Nous Portal +kubectl exec -it -- hermes model # Interactive provider picker +``` + +### xAI Grok OAuth (Recommended) + +> ⚠️ **Requires an active [SuperGrok paid subscription](https://x.ai/grok) ($30/mo).** Auth will succeed without one, but the API silently returns empty responses — the bot appears to work but never replies. + +xAI Grok OAuth uses a loopback redirect flow — the callback listener binds `127.0.0.1:56121` inside the pod. You need a port-forward so your browser's redirect reaches the pod: + +```bash +# Terminal 1: port-forward +kubectl port-forward deployment/ 56121:56121 + +# Terminal 2: run auth +kubectl exec -it deployment/ -- hermes auth add xai-oauth --no-browser +``` + +1. Copy the printed authorize URL → open in your local browser +2. Approve access on accounts.x.ai +3. Browser redirects to `127.0.0.1:56121/callback` → port-forward delivers it to the pod +4. Terminal shows "Login successful!" + +After auth, set the default model: + +```bash +kubectl exec -- hermes config set model.provider xai-oauth +kubectl exec -- hermes config set model.default grok-4.3 +``` + +> **Note:** Tokens are stored in `~/.hermes/auth.json` and auto-refresh in the background. + +### Providers That Don't Need Port-Forward + +| Provider | Auth Method | +|----------|-------------| +| Anthropic (Claude Pro/Max) | Paste-the-code flow | +| OpenAI Codex (ChatGPT Plus/Pro) | Device code flow | +| MiniMax, Nous Portal | Device code flow | +| xAI Grok, Spotify | Loopback OAuth (port-forward required) | + +### Supported Providers (via OAuth) + +| Provider | Auth Command | Cost Model | +|----------|-------------|------------| +| xAI Grok | `hermes auth add xai-oauth` | SuperGrok subscription ($30/mo) | +| OpenAI Codex | `hermes model` → OpenAI Codex | ChatGPT subscription | +| GitHub Copilot | `hermes model` → GitHub Copilot | Copilot subscription | +| Google Gemini | `hermes model` → Google Gemini (OAuth) | Free tier available | +| Anthropic | `hermes model` → Anthropic | Claude Max + extra credits | +| Nous Portal | `hermes auth add nous` | Nous subscription | + +### Supported Providers (via API Key) + +Any provider can also be configured with an API key via environment variables: + +```toml +[agent] +command = "hermes-acp" +working_dir = "/home/agent" +env = { XAI_API_KEY = "${XAI_API_KEY}" } +``` + +## Provider Switching + +Switch providers without restarting the pod: + +```bash +kubectl exec -it -- hermes model +``` + +## Credential Persistence + +Hermes stores OAuth tokens in `~/.hermes/`. The OpenAB Helm chart's default persistence covers this automatically (PVC mounted at `workingDir`). + +If deploying manually (without the Helm chart), mount persistent storage at `/home/agent` or `/home/agent/.hermes`: + +```yaml +volumes: + - name: hermes-credentials + persistentVolumeClaim: + claimName: hermes-credentials-pvc +volumeMounts: + - name: hermes-credentials + mountPath: /home/agent/.hermes +``` + +## Advantages + +- **Cost**: SuperGrok $30/mo flat rate vs pay-per-token API pricing +- **Multi-provider**: 30+ providers accessible through one agent +- **Zero auth complexity**: Hermes handles OAuth + token refresh +- **Multi-modal**: TTS, image gen, video gen via the same OAuth token +- **Fallback chains**: Auto-switch providers on failure