Skip to main content

Daemon that bridges chat platforms (Rocket.Chat and more) to AI agent backends (Claude, OpenCode)

Project description

agent-chat-gateway

CI Python License

Inspired by OpenClaw's vision of making AI agents accessible from any messaging app, agent-chat-gateway takes that idea to the team and developer layer: rather than running a new AI agent for you, it bridges your existing local agent sessions โ€” Claude CLI, OpenCode, or any custom backend โ€” to your team's chat platform. No code changes to your agent required; configure once, and your whole team can collaborate through it.

How it compares to Claude Code Channels: Claude Code's native Channels feature (v2.1.80+) connects a single Claude Code session to Telegram, Discord, or iMessage โ€” a great fit for personal use. agent-chat-gateway was developed independently before Channels shipped and is designed for a different layer: multi-user team deployments with multiple agent backends, role-based access control, human-in-the-loop permission approval, and sticky sessions shared across an entire workspace.

What's Supported

Supported today Extensible via
Chat platforms Rocket.Chat Connector ABC
Agent backends Claude CLI, OpenCode AgentBackend ABC

Features

  • ๐Ÿ”Œ Pluggable connectors โ€” Rocket.Chat today; add Slack, Discord, and others via the Connector ABC
  • ๐Ÿค– Multiple agent backends โ€” Claude CLI and OpenCode out of the box; add your own via AgentBackend ABC
  • ๐Ÿ”’ Role-based access control โ€” Owner and Guest roles with per-tool allow-lists (regex-based, tree-sitter Bash parsing)
  • ๐Ÿ›ก๏ธ Human-in-the-loop approval โ€” Sensitive tool calls pause for owner approve/deny before proceeding (similar to Claude Code's permission relay)
  • ๐Ÿ“Œ Sticky sessions โ€” Each chat room keeps its own persistent agent session across daemon restarts
  • ๐Ÿ“ Attachment support โ€” Files uploaded in chat are automatically downloaded and injected into the agent prompt
  • ๐Ÿง  Context injection โ€” Load domain knowledge, system prompts, and room profiles into agent sessions at startup
  • โšก Multi-connector โ€” Run multiple chat platform connections simultaneously

Key Concepts

Term Meaning
Connector Adapter for a chat platform (e.g., Rocket.Chat). Handles incoming messages and posts replies.
Agent backend The AI tool the gateway dispatches to (e.g., Claude CLI, OpenCode).
Watcher A binding between one chat room and one agent backend. One watcher per room.

Use Cases

1. Build a Super-Powered Chatbot for Your Team

Turn your existing agentic tool (Claude, OpenCode, or any custom backend) into a shared team assistant in your chat workspace. Configure multiple rooms with different agents, define per-role tool access, and keep humans in the loop for sensitive operations.

2. Access Your Agent from Any Messaging App

Already have Claude CLI or OpenCode running on your machine? Bridge it to your team's Rocket.Chat workspace so everyone can interact with it directly from chat โ€” no terminal required. Similar in concept to Claude Code's Channels feature, but works with any agent backend and is built for multi-user team access.

3. Continue an Existing Agent Session Remotely

Pin a chat room to a specific agent session ID and pick up exactly where you left off from your messaging app โ€” similar in spirit to Claude Code's Remote Control feature, but accessible from your team's shared chat room rather than a personal device.

See docs/user-guide.md for configuration examples for each use case.


Quick Start

Recommended: AI-guided install

The easiest way to install is to ask your AI agent to do it for you โ€” it handles missing dependencies, config setup, and troubleshooting automatically:

Claude Code:

claude "Please install agent-chat-gateway by following the instructions at https://raw.githubusercontent.com/HammerMei/agent-chat-gateway/main/docs/install-agent.md"

OpenCode:

opencode "Please install agent-chat-gateway by following the instructions at https://raw.githubusercontent.com/HammerMei/agent-chat-gateway/main/docs/install-agent.md"

Manual install

# Requires: Python 3.12+, git
curl -fsSL https://raw.githubusercontent.com/HammerMei/agent-chat-gateway/main/install.sh | bash

The installer checks Python 3.12+, installs uv if needed, clones the repo, and launches the interactive setup wizard.

# Start the daemon
agent-chat-gateway start

# Check it's running
agent-chat-gateway status
agent-chat-gateway list

See docs/install-agent.md for a full step-by-step installation guide.


Architecture

RC User @mention
    โ†’ RC Server (DDP WebSocket event)
    โ†’ connectors/rocketchat/            (DDP listener, normalization, attachment download)
    โ†’ core/message_processor.py         (per-room queue consumer)
        โ†’ enqueue()                     (intercepts approve/deny permission commands)
        โ†’ agent.send()                  (calls AgentBackend, blocks on response)
    โ†’ AgentBackend                      (session lifecycle + message dispatch)
        โ†’ PreToolUse HTTP hook          (pauses for owner approval if permissions.enabled)
    โ†’ connector.send_text()             (posts response back to room via REST)

The daemon exposes a Unix domain socket (~/.agent-chat-gateway/control.sock) so the agent-chat-gateway CLI can send commands (list/pause/resume/reset watchers) to the running process.


Module Layout

agent-chat-gateway/
โ”œโ”€โ”€ config.yaml              # User-editable: connectors, agents, permissions
โ”œโ”€โ”€ pyproject.toml
โ””โ”€โ”€ gateway/
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ agents/
    โ”‚   โ”œโ”€โ”€ __init__.py          # AgentBackend ABC (create_session, send โ†’ AgentResponse)
    โ”‚   โ”œโ”€โ”€ response.py          # AgentResponse + TokenUsage normalized dataclasses
    โ”‚   โ”œโ”€โ”€ session.py           # AgentSession: thin scripting wrapper around AgentBackend
    โ”‚   โ”œโ”€โ”€ claude/              # ClaudeBackend: drives the Claude CLI subprocess
    โ”‚   โ””โ”€โ”€ opencode/            # OpenCodeBackend: drives the opencode CLI subprocess
    โ”‚       โ””โ”€โ”€ hooks/
    โ”‚           โ””โ”€โ”€ role-enforcement.ts  # opencode plugin: guest RBAC + owner approval trigger
    โ”œโ”€โ”€ cli.py                   # argparse entry point (start/stop/restart/status/list/pause/resume/reset/send)
    โ”œโ”€โ”€ config.py                # YAML loader โ†’ GatewayConfig / AgentConfig / PermissionConfig
    โ”œโ”€โ”€ daemon.py                # Double-fork daemonize, PID file, signal handling
    โ”œโ”€โ”€ service.py               # Top-level orchestrator: connectors + brokers + SessionManagers
    โ”œโ”€โ”€ control.py               # Unix socket ControlServer for CLIโ†’daemon command routing
    โ”œโ”€โ”€ core/                    # Platform-agnostic library
    โ”‚   โ”œโ”€โ”€ connector.py         # Connector ABC + normalized dataclasses (Room, IncomingMessage, โ€ฆ)
    โ”‚   โ”œโ”€โ”€ session_manager.py   # Per-connector orchestrator; delegates to collaborators below
    โ”‚   โ”œโ”€โ”€ dispatch.py          # MessageDispatcher: routes messages to per-room processors
    โ”‚   โ”œโ”€โ”€ message_processor.py # Per-room async queue consumer
    โ”‚   โ”œโ”€โ”€ agent_turn_runner.py # Execute one agent turn (typing โ†’ send โ†’ deliver)
    โ”‚   โ”œโ”€โ”€ watcher_lifecycle.py # Watcher state machines (start/pause/resume/reset/stop)
    โ”‚   โ”œโ”€โ”€ permission.py        # PermissionBroker ABC
    โ”‚   โ”œโ”€โ”€ permission_state.py  # PermissionRegistry (in-memory) + PermissionRequest
    โ”‚   โ”œโ”€โ”€ tool_match.py        # Tool allow-list matching (regex + tree-sitter bash parsing)
    โ”‚   โ”œโ”€โ”€ context_injector.py  # Inject context files into agent sessions
    โ”‚   โ”œโ”€โ”€ state_store.py       # Persist WatcherState to JSON
    โ”‚   โ””โ”€โ”€ config.py            # Shared config types (ConnectorConfig, AgentConfig, โ€ฆ)
    โ””โ”€โ”€ connectors/
        โ”œโ”€โ”€ rocketchat/          # Rocket.Chat connector (DDP WebSocket + REST)
        โ”‚   โ”œโ”€โ”€ connector.py     # RocketChatConnector(Connector)
        โ”‚   โ”œโ”€โ”€ config.py        # RocketChatConfig dataclass
        โ”‚   โ”œโ”€โ”€ normalize.py     # RC raw DDP doc โ†’ IncomingMessage
        โ”‚   โ”œโ”€โ”€ outbound.py      # send_text / typing indicator / online notification
        โ”‚   โ”œโ”€โ”€ rest.py          # RC REST client (login, post_message, upload_file, โ€ฆ)
        โ”‚   โ””โ”€โ”€ websocket.py     # DDP-over-WebSocket client with reconnect + keepalive
        โ””โ”€โ”€ script/
            โ””โ”€โ”€ connector.py     # ScriptConnector โ€” in-memory, for scripting & tests

Key components

Component Responsibility
core/connector.py Platform-agnostic Connector ABC and normalized dataclasses (Room, IncomingMessage, UserRole).
core/session_manager.py Orchestrates the connector, spawns MessageProcessor per room, persists state, handles CLI control socket.
core/message_processor.py Dequeues inbound messages, intercepts approve/deny permission commands, builds agent prompt, calls AgentBackend.send(), posts replies.
connectors/rocketchat/ All Rocket.Chat knowledge: DDP subscription, REST API, message normalization, RBAC role resolution, attachment download.
connectors/script/ In-memory ScriptConnector for scripting, testing, and agent-to-agent piping. Zero network calls.
agents/response.py AgentResponse and TokenUsage normalized dataclasses returned by all backends.
agents/session.py AgentSession โ€” thin scripting wrapper around AgentBackend. No connector, no room, no state file.
core/permission.py + permission_state.py Human-in-the-loop approval system. Brokers intercept tool calls, post RC notifications, and await owner decisions.
core/tool_match.py Tool allow-list matching; bash commands split via tree-sitter AST to prevent compound-command bypasses.
service.py Wires config โ†’ connectors โ†’ permission brokers โ†’ SessionManager instances. One SessionManager per connector.
config.py Loads config.yaml into typed dataclasses. Supports env-var expansion ($VAR).

Configuration (config.yaml)

connectors:
  - name: rc-home
    type: rocketchat
    server:
      url: https://your-rocketchat.example.com
      username: bot-username
      password: "$RC_PASSWORD"        # env-var expansion supported
    allowed_users:
      owners:
        - alice                       # full access: tools, tasks, approve/deny permissions
      guests: []                      # conversation-only; restricted tool whitelist
    attachments:
      max_file_size_mb: 10
      download_timeout: 30
      cache_dir: agent-chat.cache

default_agent: assistance

agents:
  assistance:
    type: claude
    command: claude
    working_directory: /tmp/acg      # required: agent working directory
    timeout: 360                     # seconds; must be > permissions.timeout
    new_session_args: []
    session_prefix: agent-chat       # prefix for Claude session names
    permissions:
      enabled: true                  # enable human-in-the-loop tool approval
      timeout: 300                   # seconds before auto-deny (must be < timeout above)
      skip_owner_approval: false     # set true to auto-approve all owner tool calls
    owner_allowed_tools:             # auto-approved for owners (no prompt shown)
      - tool: "bash"
        params: "ls.*|pwd|echo.*"
    guest_allowed_tools:             # auto-approved for guests; others silently denied
      - tool: "read"

Role-Based Access Control

Every message sent to the agent subprocess is prefixed with a trusted header injected by the connector:

[Rocket.Chat #<room> | from: <username> | role: owner|guest]  <message text>

The agent uses this prefix to determine the sender's role. Two enforcement layers run in parallel in the agent subprocess environment:

Layer 1 โ€” Shell hook (Claude Code PreToolUse)

~/.claude/hooks/pretooluse-role-enforcement.sh runs on every tool call:

  • ACG_ROLE=owner โ†’ exits 0 (allow all tools)
  • ACG_ROLE=guest + tool in ACG_ALLOWED_TOOLS โ†’ exits 0
  • ACG_ROLE=guest + tool NOT in whitelist โ†’ exits 2 (block, no further hooks run)

Layer 2 โ€” opencode plugin (tool.execute.before)

gateway/agents/opencode/hooks/role-enforcement.ts is symlinked into the working directory's .opencode/plugins/ folder. Same logic as the shell hook for guests. For owners, triggers output.status = "ask" on sensitive tools to fire opencode's permission.asked SSE event (used by the permission approval system).


Human-in-the-Loop Permission Approval

When permissions.enabled: true, sensitive tool calls (Bash, Write, Edit, MultiEdit, NotebookEdit) are paused and require explicit owner approval via RC chat before the agent proceeds.

How it works

Agent attempts sensitive tool call
    โ†’ PreToolUse HTTP hook fires (Claude) / permission.asked SSE event (opencode)
    โ†’ PermissionBroker posts approval request to RC chat room:

        ๐Ÿ” **Permission required** `[a3k9]`
        **Tool:** `Bash`
        **Params:** `command='rm ./build'`
        Reply `approve a3k9` or `deny a3k9`

    โ†’ Owner replies in RC chat
    โ†’ MessageDispatcher.dispatch() intercepts the command (BEFORE the queue)
    โ†’ asyncio.Future resolved โ†’ agent subprocess unblocked
    โ†’ Tool executes (approve) or is skipped (deny)

Approval commands

Type these directly in the RC chat room (no slash prefix โ€” RC client would intercept / commands):

approve a3k9    โ† allow the tool call
deny a3k9       โ† block the tool call

Important: These commands are intercepted by the gateway before they reach the agent. The agent never sees them. If you type a wrong ID:

  • Wrong length โ†’ โš ๏ธ Invalid ID โ€” expected 4 characters
  • ID not found โ†’ โš ๏ธ No pending permission request with ID

Timeout

Requests not resolved within permissions.timeout seconds are auto-denied:

โฑ๏ธ Permission `a3k9` timed out โ€” auto-denied.

Concurrency

While a tool call is pending approval, new messages are queued and will not be processed until the pending request is resolved. Only approve / deny commands unblock the queue.

Known limitation โ€” Bash sandbox

Claude Code's Bash tool sandbox restricts execution to the working directory. approve resolves the HTTP hook, but Claude Code's sandbox enforcement is a separate layer that cannot be overridden via hooks. Operations outside the working directory (e.g. rm ~/foo) will still be blocked by Claude Code's Bash sandbox after approval.

Architecture

GatewayService
โ”œโ”€โ”€ PermissionRegistry          (shared in-process store: request_id โ†’ asyncio.Future)
โ”œโ”€โ”€ session_room_map            (session_id โ†’ room_id, for broker โ†’ RC room routing)
โ”œโ”€โ”€ ClaudePermissionBroker      (HTTP server on random localhost port)
โ”‚   โ””โ”€โ”€ generates temp settings.json passed as --settings to every claude -p call
โ”‚       {"hooks": {"PreToolUse": [{"matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit",
โ”‚                                   "type": "http", "url": "http://127.0.0.1:<port>/hook"}]}}
โ”œโ”€โ”€ OpenCodePermissionBroker    (SSE listener on opencode's /events endpoint)
โ”‚   โ””โ”€โ”€ calls POST /permission/{id}/reply on owner decision
โ””โ”€โ”€ expiry_task                 (background: auto-deny requests older than timeout)

Agent Backends

ClaudeBackend (agents/claude/)

Drives the Claude CLI via subprocesses:

  • Session creation โ€” claude -p --output-format json [new_session_args] [--settings <path>] Parses session_id from the JSON response and stores it in state.

  • Message sending โ€” claude -p --resume <session-id> --output-format stream-json --verbose [--settings <path>] Streams one JSON event per line; extracts text from type="assistant" content blocks, metadata from type="result" (session_id, cost, duration, turns, token usage).

  • Permission hook โ€” when permissions.enabled, --settings <path> is injected into every call pointing to a gateway-generated settings file with the HTTP PreToolUse hook.

  • Environment isolation โ€” strips CLAUDECODE from the subprocess environment; merges ACG_ROLE / ACG_ALLOWED_TOOLS per-message for role enforcement.

  • Attachments โ€” injects file paths into the prompt text (Claude CLI does not support native -f attachments in -p mode).

OpenCodeBackend (agents/opencode/)

Drives the opencode CLI:

  • Session creation โ€” opencode run --format json [new_session_args]
  • Message sending โ€” opencode run -s <session-id> --format json [-f <file>...]
  • Permission โ€” permission.asked SSE events from opencode's HTTP server, triggered by the role-enforcement.ts plugin setting output.status = "ask".
  • Attachments โ€” native -f flag support.

Agent Response (AgentResponse)

Every AgentBackend.send() call returns an AgentResponse:

@dataclass
class TokenUsage:
    input_tokens: int = 0
    output_tokens: int = 0
    cache_read_tokens: int = 0
    cache_write_tokens: int = 0
    reasoning_tokens: int = 0    # opencode only

@dataclass
class AgentResponse:
    text: str                         # agent's reply (always present)
    session_id: str | None = None
    usage: TokenUsage | None = None
    cost_usd: float | None = None
    duration_ms: int | None = None
    num_turns: int | None = None
    is_error: bool = False

    def __str__(self) -> str:
        return self.text              # transparent use in f-strings

MessageProcessor logs token usage automatically when response.usage is present:

Agent usage [@alice] in=1234 out=256 cache_read=512 cost=$0.0042

Runtime files

All runtime state lives in ~/.agent-chat-gateway/:

File Contents
gateway.pid PID of the running daemon
gateway.log Daemon log output
control.sock Unix domain socket for CLI commands
state.<connector>.json Persisted watcher definitions per connector

CLI usage

# Daemon lifecycle
agent-chat-gateway start [--config path/to/config.yaml]
agent-chat-gateway restart [--config path/to/config.yaml]
agent-chat-gateway status
agent-chat-gateway stop

# Watcher control (requires running daemon)
agent-chat-gateway list [--connector NAME]
agent-chat-gateway pause <watcher-name> [--connector NAME]
agent-chat-gateway resume <watcher-name> [--connector NAME]
agent-chat-gateway reset <watcher-name> [--connector NAME]   # clear state, start fresh session

# Send messages directly to a room (bypasses agent)
agent-chat-gateway send <room> "message text"
agent-chat-gateway send <room> -                             # read from stdin
agent-chat-gateway send <room> --file message.txt
agent-chat-gateway send <room> --attach file.pdf --file caption.txt

# Setup
agent-chat-gateway onboard                                   # interactive setup wizard
agent-chat-gateway upgrade                                   # check and install updates

Adding an Agent Integration

1. Implement the AgentBackend ABC

from gateway.agents import AgentBackend
from gateway.agents.response import AgentResponse

class MyBackend(AgentBackend):

    async def create_session(
        self,
        working_directory: str,
        extra_args: list[str] | None = None,
        session_title: str | None = None,
    ) -> str:
        """Start a new session. Return an opaque session_id string."""
        ...

    async def send(
        self,
        session_id: str,
        prompt: str,
        working_directory: str,
        timeout: int,
        attachments: list[str] | None = None,
        env: dict[str, str] | None = None,
    ) -> AgentResponse:
        """Send a message to an existing session. Raise asyncio.TimeoutError on timeout."""
        ...

2. Register in service.py

from .agents.mybackend.adapter import MyBackend

# inside _build_agent_backend():
if agent_cfg.type == "mybackend":
    return MyBackend(command=agent_cfg.command, timeout=timeout)

3. Add to config.yaml

agents:
  myagent:
    type: mybackend
    command: mybinary
    new_session_args: []

4. Smoke-test with ScriptConnector

import asyncio
from gateway.connectors.script.connector import ScriptConnector
from gateway.core.session_manager import SessionManager
from gateway.core.config import CoreConfig
from gateway.agents.mybackend.adapter import MyBackend

connector = ScriptConnector()
backend   = MyBackend(command="mybinary", timeout=60)
config    = CoreConfig(timeout=60, agents={"default": backend}, default_agent="default")
manager   = SessionManager(connector, backend, "default", config)

async def test():
    await manager.run_once()
    await manager.add_session("test-room", None, "/tmp")
    await connector.inject("Hello")
    reply = await connector.receive_reply()
    print(reply)

asyncio.run(test())

Adding a new Connector

Implement the Connector ABC from gateway/core/connector.py:

from gateway.core.connector import Connector, IncomingMessage, Room
from gateway.agents.response import AgentResponse

class SlackConnector(Connector):
    async def connect(self) -> None: ...
    async def disconnect(self) -> None: ...
    def register_handler(self, handler) -> None: ...
    async def send_text(self, room_id: str, response: AgentResponse) -> None: ...
    async def resolve_room(self, room_name: str) -> Room: ...
    def format_prompt_prefix(self, msg: IncomingMessage) -> str:
        return f"[Slack #{msg.room.name} | from: {msg.sender.username} | role: {msg.role.value}]"

Then instantiate it in service.py via connector_factory().


Direct Scripting with AgentSession

For one-off tasks and agent-to-agent pipelines, skip the full gateway stack:

from gateway.agents.session import AgentSession
from gateway.agents.claude.adapter import ClaudeBackend
from gateway.agents.opencode.adapter import OpenCodeBackend

# Single agent
async with AgentSession(ClaudeBackend("claude", [], 120), "/my/project") as session:
    response = await session.send("What files are here?")
    print(response)   # __str__ โ†’ response.text

# Agent-to-agent pipeline
async with (
    AgentSession(OpenCodeBackend("opencode", [], 120), cwd) as oc,
    AgentSession(ClaudeBackend("claude", ["--agent", "assistance"], 120), cwd) as cc,
):
    summary = await oc.send("Summarize the codebase")
    review  = await cc.send(f"Review this summary:\n{summary}")
    print(review)

Documentation

Document Description
docs/install-agent.md Installation & getting started guide
docs/user-guide.md Full configuration reference and operational guide
docs/architecture.md System architecture, module breakdown, data flow diagrams
docs/permission-reference.md RBAC and human-in-the-loop approval deep dive
docs/supported-features.md Supported features, known limitations, and roadmap
docs/requirements.md Functional specification (behavioral requirements)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agent_chat_gateway-0.1.3.tar.gz (356.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agent_chat_gateway-0.1.3-py3-none-any.whl (182.0 kB view details)

Uploaded Python 3

File details

Details for the file agent_chat_gateway-0.1.3.tar.gz.

File metadata

  • Download URL: agent_chat_gateway-0.1.3.tar.gz
  • Upload date:
  • Size: 356.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for agent_chat_gateway-0.1.3.tar.gz
Algorithm Hash digest
SHA256 4ec8ceaf457e94ad9df92f796dded5f1b9fb018fef5f22bd872627d3ae0ade67
MD5 dde1b0e2e59025d4372e223fe2298e58
BLAKE2b-256 4bc1e03f3e68f8c716050885c19cf11db033a6331ceb4382eb21c90e01eec44f

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_chat_gateway-0.1.3.tar.gz:

Publisher: publish.yml on HammerMei/agent-chat-gateway

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agent_chat_gateway-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for agent_chat_gateway-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e70a1cabe1dfc9b4f51f1f84d0f914be36ece50e0178ce3a141f2fd4ed20b237
MD5 f9d28b0822869aa8923bfef21514b8c3
BLAKE2b-256 a64edfde77f77c5a24d850bb56a3e6a0ec074b3770cd8ecbf510f698fbe3bf50

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_chat_gateway-0.1.3-py3-none-any.whl:

Publisher: publish.yml on HammerMei/agent-chat-gateway

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page