DEV Community

Cover image for How OpenACP talks to 28+ AI agents through one protocol — architecture of a self-hosted messaging bridge
psychomafia.tiger
psychomafia.tiger

Posted on

How OpenACP talks to 28+ AI agents through one protocol — architecture of a self-hosted messaging bridge

I needed Claude Code to talk to Telegram. Not a chatbot — the actual coding agent, running on my machine, reading my files, writing code. I wanted to send it a task from my phone and get structured updates back, including permission prompts I could approve with a tap.

Parsing terminal stdout wasn't going to cut it. I tried that approach early on and it kept breaking whenever the agent's output format changed.

That's when I found OpenACP and started contributing. What made it work where my hacky approach didn't was a protocol layer underneath: the Agent Client Protocol (ACP), an open standard that lets any client communicate with any compatible coding agent through structured JSON-RPC messages.

Here's how the architecture works.

The problem: agents live in your terminal

AI coding agents — Claude Code, Gemini CLI, Codex, and others — are powerful but they assume you're sitting at a terminal. Leave your desk and the agent hits a permission prompt? It waits. You come back 40 minutes later to find it's been idle the whole time.

The workarounds people try: SSH into a tmux session on mobile (fragile), VPN + remote desktop (slow), or just hope nothing needs approval. None of these are actually good.

What you want is a structured bridge: your messaging app on one side, the coding agent on the other, with a session manager in between that handles streaming, permissions, and multiple agents at once.

OpenACP's three layers

OpenACP is a self-hosted bridge between messaging platforms (Telegram, Discord, Slack) and AI coding agents. Everything runs on your machine. The architecture has three layers:

┌────────────────────────────────────────────────────┐
│                    Your Phone                       │
│            Telegram / Discord / Slack               │
└──────────────────────┬─────────────────────────────┘
                       │
          Platform API (grammY / discord.js)
                       │
┌──────────────────────▼─────────────────────────────┐
│              Layer 1: Adapter                       │
│  Translates platform messages to internal events    │
│  Handles: buttons, topics/threads, streaming,       │
│  rate limiting, message chunking                    │
└──────────────────────┬─────────────────────────────┘
                       │
┌──────────────────────▼─────────────────────────────┐
│           Layer 2: Session Bridge                   │
│  Routes messages between adapters and agents        │
│  Manages: session lifecycle, prompt queue,          │
│  permission gates, cost tracking, parallel sessions │
└──────────────────────┬─────────────────────────────┘
                       │
            ACP protocol (JSON-RPC over stdio)
                       │
┌──────────────────────▼─────────────────────────────┐
│         Layer 3: Agent Connection                   │
│  Spawns agents as subprocesses, speaks ACP          │
│  Handles: initialize, session/new, session/prompt,  │
│  event streaming, permission relay                  │
└────────────────────────────────────────────────────┘
                       │
              Claude Code / Gemini CLI / Codex / ...
                       │
                  Your Codebase
Enter fullscreen mode Exit fullscreen mode

Each layer only talks to its neighbors. The adapter doesn't know which agent is running. The agent doesn't know whether it's talking to Telegram or Discord. The session bridge is the glue.

Layer 3: how agents actually communicate

This is the part that was hardest to get right without a protocol.

OpenACP spawns each agent as a subprocess and communicates over stdin/stdout using the Agent Client Protocol. ACP is built on JSON-RPC 2.0 — structured messages, not raw text.

The lifecycle has three phases:

1. Initialize — OpenACP tells the agent what capabilities the client supports (file operations, terminal access) and the agent responds with what it can do:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": 1,
    "clientCapabilities": {
      "fs": { "readTextFile": true, "writeTextFile": true },
      "terminal": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Create session — Point the agent at a working directory:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/new",
  "params": { "cwd": "/home/user/my-project" }
}
Enter fullscreen mode Exit fullscreen mode

3. Prompt — Send a task. The agent streams back typed events as it works:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "session/prompt",
  "params": {
    "sessionId": "abc-123",
    "prompt": [{ "type": "text", "text": "Fix the auth bug in middleware.ts" }]
  }
}
Enter fullscreen mode Exit fullscreen mode

While working, the agent sends back events:

  • agent_message_chunk — the agent's reasoning, streamed as it thinks
  • tool_call / tool_call_update — file reads, writes, grep, terminal commands
  • usage_update — token counts and cost
  • plan — the agent's internal task plan

And the critical one: request_permission. When the agent wants to do something that requires approval — write a file, run a destructive command — it sends a structured permission request. OpenACP turns that into a Telegram button or Discord interaction. The agent blocks until you respond.

This is why the protocol matters. Without it, you're parsing terminal output trying to figure out if the agent is asking a question or just printing something. With ACP, a permission request is an explicit method call with structured options. There's no ambiguity.

Why one protocol means 28+ agents

Here's the real payoff.

Because OpenACP speaks ACP, it works with every agent that also speaks ACP. The ACP registry lists 28+ agents right now — Claude Code, Gemini CLI, Codex CLI, GitHub Copilot, Cursor, Cline, goose, Junie, Qwen Code, and more.

Adding a new agent to OpenACP means registering it, not building a custom integration. When you run:

openacp agents install gemini
Enter fullscreen mode Exit fullscreen mode

OpenACP pulls the agent definition from the registry, resolves its distribution (npm, pip, or binary), and installs it. From that point on, you can spin up a Gemini session alongside your Claude Code session, each in its own Telegram topic or Discord thread.

The typical setup: Claude Code running a longer task in one Telegram topic while you ask Gemini something quick in another. Same bridge, same interface, different agents.

Quick note: ACP is not MCP

This confused me at first. Model Context Protocol (MCP) connects an AI model to tools — databases, APIs, browser automation. Agent Client Protocol (ACP) connects a user interface to a coding agent — prompts, streaming, permissions, sessions.

They're complementary layers. Claude Code uses MCP internally to talk to tool servers, and speaks ACP externally to talk to whatever client is driving it — VS Code, a terminal, or OpenACP.

What a session looks like end-to-end

When I send "Fix the auth bug in middleware.ts" to my Telegram bot:

  1. Telegram adapter receives the message, identifies the session
  2. Session bridge queues the prompt, routes it to the right agent instance
  3. Agent connection sends session/prompt to Claude Code via ACP
  4. Claude Code reads files → comes back as tool_call events → streamed to Telegram
  5. Claude Code greps the codebase → tool_call event → streamed
  6. Claude Code wants to write a fix → request_permission → Approve/Deny button in Telegram
  7. I tap Approve from my phone
  8. Session bridge relays the approval → Claude Code writes the file → end_turn

Total messages exchanged over ACP: dozens. Time I spent: one tap.

What stays local

Everything except the model API call. Your code, sessions, config, and cost data stay on your machine. The agent calls Anthropic/Google/OpenAI for inference — that's the tradeoff — but the orchestration layer is fully yours. No relay, no third-party storing your session history.

And because the bridge is protocol-based, swapping agents doesn't change anything else. Different subprocess, same messaging setup.

Try it

npm install -g @openacp/cli
openacp
Enter fullscreen mode Exit fullscreen mode

Setup wizard handles the rest. Platform guides in the docs.

Links

MIT licensed. I'm a contributor to OpenACP — happy to answer questions about the architecture or the protocol integration.

Top comments (0)