Skip to main content
What this is for: let an ACP-compatible editor (Claude Code, Cursor, Zed, custom VS Code extensions) talk to your Comis agents directly. Who it’s for: developers integrating Comis into their IDE workflow. This is how your editor talks to your agents. The ACP server implements the Agent Communication Protocol, a standard interface that lets VS Code, JetBrains, Zed, and other ACP-compatible editors connect to Comis as a first-class AI agent — with full conversation history, session continuity, and the same agent executor that powers all your other channels.

What is ACP?

ACP (Agent Communication Protocol) is an open standard for connecting AI agents to development tools. Instead of each IDE inventing its own chat protocol, ACP defines a common language: how to start a session, send a prompt, and receive a response. Comis implements the agent side of ACP. Your IDE or editor extension acts as the client. When you chat with Comis from your editor, it is running the same agent executor as a Discord message or a Telegram conversation — the ACP transport is just another channel.
The ACP implementation lives in packages/gateway/src/acp/. The startAcpServer and createAcpAgent functions are exported from @comis/gateway. The daemon-level entrypoint that wires these together is not yet shipped. The protocol implementation is complete; the integration work to expose it as a daemon command is in progress.

Transport

ACP communicates over ndjson (newline-delimited JSON) on stdin/stdout. Your IDE spawns Comis as a child process. Comis reads ACP messages from stdin and writes responses to stdout. This is standard practice for language-server-style integrations.
All logging inside the ACP server must go to stderr. If any log output reaches stdout it corrupts the ndjson stream and breaks the connection. This is enforced in the implementation — do not change the logger configuration when running in ACP mode.
There is no network port, no bearer token, and no TLS required for ACP. The process boundary is the security boundary: only the editor that spawned the process can talk to it.

Protocol

ACP uses the @agentclientprotocol/sdk (version ^0.19.0). The SDK handles framing, buffering, and partial-read recovery — you do not implement the wire format yourself. Messages are structured JSON objects carried over the ndjson stream. The five operations the agent side implements:
OperationDirectionPurpose
initializeIDE → ComisHandshake. Negotiates protocol version and returns agent identity.
newSessionIDE → ComisOpens a conversation session. Returns a session ID.
promptIDE → ComisSends one or more content blocks to the agent. Comis runs the agent executor and returns when done.
authenticateIDE → ComisNo-op. Local subprocess needs no authentication.
cancelIDE → ComisEnds a session and removes it from the session map.

Content blocks

Prompt messages are arrays of content blocks. Comis extracts all text-type blocks and joins them with newlines before passing the text to the agent executor. Non-text blocks (images, resource references) are silently ignored in the current implementation.
{
  "prompt": [
    { "type": "text", "text": "Explain the rate limiting config." },
    { "type": "text", "text": "Focus on the WebSocket limits." }
  ]
}
These two blocks arrive as a single message: "Explain the rate limiting config.\nFocus on the WebSocket limits."

Session model

Each newSession call creates a UUID session ID and maps it to a Comis session key with:
  • channelId: "acp" — identifies this as an IDE session, separate from other channels
  • userId: "ide-user" — fixed identifier for all ACP callers
  • peerId: the ACP session UUID
The session key is used to route the conversation through the same memory, context, and history systems as every other channel. Session limits. The server holds up to 1,000 active sessions in memory. When a new session is created at capacity, the oldest session is evicted (LRU by insertion order). Sessions are also removed when the IDE sends a cancel notification.

Initialize response

When your IDE calls initialize, Comis responds with:
{
  "protocolVersion": 1,
  "agentInfo": {
    "name": "comis",
    "title": "Comis",
    "version": "1.0.0"
  },
  "agentCapabilities": {}
}
The protocolVersion is echoed back from the request. agentCapabilities is currently an empty object — capability negotiation will expand as ACP evolves.

Code example

This example shows how to build an ACP entrypoint that wires startAcpServer to a real agent executor. You would build this as a small Node.js script that the IDE configuration points to.
import { startAcpServer } from "@comis/gateway";
import pino from "pino";

// Logger MUST write to stderr — stdout is reserved for the ndjson protocol.
const logger = pino({ level: "info" }, pino.destination(2));

await startAcpServer({
  version: "1.0.0",

  logger: {
    info: (...args) => logger.info(...args),
    warn: (...args) => logger.warn(...args),
    error: (...args) => logger.error(...args),
  },

  async executeAgent({ message, sessionKey }) {
    // Plug in your Comis AgentExecutor here.
    // sessionKey.channelId === "acp"
    // sessionKey.peerId    === the ACP session UUID
    const result = await myAgentExecutor.execute({ message, sessionKey });
    return {
      response: result.response,
      tokensUsed: result.tokensUsed,
      finishReason: result.finishReason,
    };
  },
});
startAcpServer blocks until the IDE closes its end of the connection (stdin reaches EOF). It does not return until the session is over.

Configuration

ACP has no dedicated configuration section in ~/.comis/config.yaml today. The relevant settings when you build an ACP entrypoint are:
SettingWhereEffect
Session capacityCode — createAcpSessionMap(maxSessions)How many concurrent IDE sessions the server tracks. Default: 1000.
Agent versionAcpServerDeps.versionVersion string returned in initialize. Defaults to "0.0.1".
Logger destinationMust be stderr (fd 2)Any stdout output corrupts the protocol.

Connecting an editor

An ACP-aware editor expects to spawn your agent process directly. The exact configuration UI varies per editor, but the inputs are always the same: a command, optional arguments, and an environment.
1

Build an ACP entrypoint

Take the code example above and save it to a small Node.js script (e.g. ~/.comis/acp-entrypoint.js). It must import from @comis/gateway and call startAcpServer. Make sure pnpm build has run so packages/gateway/dist/ exists.
2

Configure the editor

In Claude Code, Cursor, or another ACP host, point its agent settings at:
  • Command: node
  • Arguments: ["/Users/you/.comis/acp-entrypoint.js"]
  • Environment: must include COMIS_CONFIG_PATHS (so the agent can find your config.yaml) and any provider keys.
3

Open a chat

The editor calls initialize first, then newSession. The first prompt you type runs through Comis’s agent executor and the response streams back. Conversation history is scoped to the ACP session UUID for the lifetime of the chat panel.
4

Verify it's wired up

Logs (on stderr) should show "newSession created" followed by "prompt received" whenever you type. If you see no output, double-check stdout isn’t capturing logs and that the entrypoint actually starts. ACP errors in the IDE almost always point to one of those two.

Security

Because ACP runs over stdio, the security model is different from the HTTP gateway:
  • No network exposure. There is no port to firewall, no token to rotate, no TLS certificate to manage.
  • Process isolation. Only the process that spawned the ACP server can send it messages. The OS enforces this.
  • No authentication step. The authenticate call is defined by the ACP protocol but is a no-op here — the spawn relationship itself is the proof of authorization.
  • Logging to stderr. Any accidental stdout writes would corrupt the protocol stream. The server is built to write only to stderr so secrets and internal state cannot leak into the ndjson channel.
The ACP session runs under userId: "ide-user" and channelId: "acp". Memory, tool access, and approval gates apply exactly as they do for any other channel — the agent does not get elevated permissions because it was invoked from an IDE.

Troubleshooting

The IDE reports a connection error immediately on launch. The most common cause is a process startup error before the ndjson stream is ready. Check stderr output from the spawned process. Look for import errors, missing build artifacts (dist/ not present), or a missing COMIS_CONFIG_PATHS environment variable. Run pnpm build first, then try again. Messages are received but the agent returns empty responses. The prompt may contain only non-text content blocks (images, resource URIs). Comis currently extracts only type: "text" blocks. Verify your IDE extension sends at least one text block in every prompt. The server stops accepting messages after a while. If the ACP session map reaches capacity (default 1,000 sessions) and cancel notifications are not being sent, old sessions are evicted silently. If your IDE opens many sessions without closing them, reduce polling frequency or send a cancel for each session when the user closes the chat view. You can also increase the limit by passing a larger maxSessions value to createAcpSessionMap in your entrypoint.
If you are building an IDE extension that integrates with Comis via ACP, start by calling initialize first, then newSession once per conversation, and cancel when the user closes the chat panel. This keeps session memory usage predictable and ensures history is correctly scoped per conversation.