Skip to content

feat(acp): implement Agent Communication Protocol (ACP) server and context-aware tool execution#5

Draft
mrazza wants to merge 8 commits into
mainfrom
feat/acp
Draft

feat(acp): implement Agent Communication Protocol (ACP) server and context-aware tool execution#5
mrazza wants to merge 8 commits into
mainfrom
feat/acp

Conversation

@mrazza

@mrazza mrazza commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

This PR implements the initial Agent Communication Protocol (ACP) server specification over JSON-RPC 2.0. It enables client applications to communicate with coma as an agent service over stdio/JSON-RPC, managing agent sessions, executing turns, and streaming thought and message content back to clients in real-time.

Additionally, this PR refactors core agent types to align with ACP session semantics (AgentSession) and introduces support for context-aware tool execution.

@oca-agent oca-agent left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Overall, this is a solid implementation. Refactoring Agent -> Session clarifies the codebase architecture (distinguishing the overall agent application/service from an individual context window session), and the new acp module is clean, well-structured, and thoroughly tested. The compile-time enum string mapping (session_new <-> session/new) is particularly slick with zero runtime allocation overhead.

Below are inline comments highlighting a panic hazard, error handling edge cases, and protocol robustness suggestions to help guide the implementation before taking this out of draft.

Comment thread src/acp/Server.zig
.json_rpc_writer = &json_rpc_writer,
};

_ = try session.session.executeTurnStreaming(.{ .prompt = client_request.params.session_prompt.prompt[0].text }, handleTurnUpdate, &ctx);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Panic Hazard & Prompt Handling:

  1. If a client sends an empty prompt array (prompt: []), indexing prompt[0] directly will panic at runtime. Consider validating prompt.len > 0 first or returning a JSON-RPC error.
  2. ACP prompt turns can contain multiple content blocks (e.g. text blocks alongside embedded context or resource links). Currently, only prompt[0] is evaluated. Consider joining or handling all blocks in client_request.params.session_prompt.prompt.

Comment thread src/agent/Tool.zig
} else if (T == Io) {
args[func_idx] = io;
} else if (T == ?*anyopaque) {
args[func_idx] = ctx.?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Unnecessary unwrap: T is already ?*anyopaque, so unwrapping ctx.? forces ctx to be non-null and will panic if null is ever passed.

Assigning args[func_idx] = ctx; directly avoids the forced unwrap.

Comment thread src/acp/Server.zig
const arena_allocator = arena.allocator();

while (true) {
try self.io.checkCancel();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Server loop error handling: If request handling encounters a protocol error (e.g. unknown session ID or bad params), returning err terminates the main server loop.

Catching request-level errors inside the loop and sending standard JSON-RPC error responses back to the client will prevent standard client errors from crashing the server process.

// TODO: We assume that the first header (in LSP-style JSON-RPC) is "Content-Length".
// This may not always be true. "Content-Type" is another common header and the
// spec does not specify ordering. Additionally, the headers are not case-sensitive.
if (std.mem.startsWith(u8, line, "Content-Length:")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Header case sensitivity: std.mem.startsWith(u8, line, "Content-Length:") is case-sensitive. Using std.ascii.startsWithIgnoreCase(line, "content-length:") will make header parsing more robust against different client implementations.

Comment thread src/acp/Server.zig
} || std.json.Error;

const ServerSessionContext = struct {
server: *Server,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Unused field: server: *Server in ServerSessionContext isn't read in handleTurnUpdate. If it's not needed for future notification handlers, it can be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants