Skip to content

project: goosed to ACP-over-HTTP #6642

@alexhancock

Description

@alexhancock

Goal: Replace goosed's custom SSE-based streaming API with a standards-based ACP (Agent Communication Protocol) over HTTP interface, enabling better client portability, protocol standardization, and eventually a unified interface for all goose clients.

Background

Current Architecture

  • goose-cli (Rust): Direct in-process agent communication
  • goosed (goose-server): Custom REST + SSE streaming API consumed by the Electron desktop app

Proposed Architecture

A new ACP-over-HTTP server that:

  • Implements the standardized ACP (Agent Communication Protocol) specification
  • Uses JSON-RPC 2.0 over HTTP with SSE streaming as the transport
  • Enables any ACP-compatible client to interact with goose

Prototype

See the prototype branch: https://github.com/block/goose/tree/alexhancock/goosed-acp-and-new-cli
Example of single agent (video): ref
Example of multi-agent orchestration (screenshots): ref


Phase 1: Stabilize ACP Server

Goal: Production-ready ACP server that can fully replace goosed

Tasks

  • Session persistence and resumption (session/load)
  • Enough goosed features to power alternative alpha CLI (extensions, modalities, streaming, etc)
  • Integration tests

Success Criteria

  • Core goosed features available via ACP-over-HTTP
  • Integration tests passing

Phase 2: TypeScript TUI Alpha

Goal: Feature-complete TUI which will be an evolution of the current CLI

Tasks

  • Equivalent of goose configure
  • Equivalent of goose session (with list, resume, etc)
  • Make MCP features work (sampling, elicitation, roots, etc)
  • UX polish (syntax highlighting, markdown rendering, diffs, themes?)
  • Build process that makes a single binary

Success Criteria

  • TUI usable as daily driver
  • Docs

Phase 3: Desktop Migration

Goal: Migrate Electron desktop app from goosed to ACP

Tasks

  • Integrate ACP client into desktop app
  • Feature flag to toggle between goosed and goose-acp backends

Success Criteria

  • Desktop app multi-chat works with ACP backend
  • Feature flagged
  • No user-facing regressions

Phase 4: Consolidation

Goal: ACP becomes the single interface for all goose clients, and new tui is fully launched

Tasks

  • Deprecate and remove goosed
  • Deprecate and remove goose-cli
  • Update all documentation

Success Criteria

  • goosed removed from codebase
  • goose-cli removed from codebase
  • Distribute binary for new tui
  • Single unified architecture for addressing goose

Technical Details

ACP Protocol Overview

The ACP protocol uses JSON-RPC 2.0 with the following methods:

Client → Server Requests (client sends, server responds):

initialize          - Handshake and capability negotiation
authenticate        - Authentication (currently no-op)
session/new         - Create a new conversation session (returns ACP session_id)
session/load        - Resume an existing session (replays history via notifications)
session/prompt      - Send user message, receive streaming response

Client → Server Notifications (client sends, no response):

session/cancel      - Cancel in-progress prompt

Server → Client Requests (server sends, client must respond):

request_permission  - Request user confirmation for tool execution

Server → Client Notifications (server sends via SSE, no response):

SessionNotification with SessionUpdate variants:
  - AgentMessageChunk    - Streaming text from agent
  - AgentThoughtChunk    - Streaming reasoning/thinking content
  - UserMessageChunk     - User message (used in session/load replay)
  - ToolCall             - Tool invocation started (status: pending)
  - ToolCallUpdate       - Tool status change (completed/failed) with result

HTTP Transport

The HTTP transport wraps ACP's JSON-RPC protocol over Stremable HTTP

Message Flow

Client                              Server
  │                                   │
  │  ═══ Session Initialization ═══   │
  │                                   │
  │─── POST /acp ─────────────────────>│  { method: "initialize", id: 1 }
  │    Accept: application/json,       │  (no Acp-Session-Id header)
  │            text/event-stream       │
  │              ┌─────────────────────│  Server creates session, opens SSE stream
  │              │ (SSE stream open)   │
  │<─────────────│─ SSE event ─────────│  { id: 1, result: { capabilities } }
  │              │                     │  Response includes Acp-Session-Id header
  │              ▼                     │
  │                                    │
  │  ═══ Prompt Flow ═══               │
  │                                    │
  │─── POST /acp ─────────────────────>│  { method: "session/new", id: 2,
  │    Acp-Session-Id: <session_id>    │    params: { cwd, mcp_servers } }
  │              ┌─────────────────────│  Opens new SSE stream for response
  │<─────────────│─ SSE event ─────────│  { id: 2, result: { session_id: <goose_session> } }
  │              ▼                     │
  │                                    │
  │─── POST /acp ─────────────────────>│  { method: "session/prompt", id: 3,
  │    Acp-Session-Id: <session_id>    │    params: { session_id, prompt } }
  │              ┌─────────────────────│  Opens new SSE stream for response
  │<─────────────│─ SSE event ─────────│  notification: AgentMessageChunk
  │<─────────────│─ SSE event ─────────│  notification: AgentThoughtChunk (if reasoning)
  │<─────────────│─ SSE event ─────────│  notification: ToolCall (status: pending)
  │<─────────────│─ SSE event ─────────│  notification: ToolCallUpdate (status: completed)
  │<─────────────│─ SSE event ─────────│  notification: AgentMessageChunk
  │<─────────────│─ SSE event ─────────│  { id: 3, result: { stop_reason: "end_turn" } }
  │              ▼                     │
  │                                    │
  │  ═══ Permission Flow ═══           │
  │  (when tool requires confirmation) │
  │                                    │
  │─── POST /acp ─────────────────────>│  { method: "session/prompt", id: 4, ... }
  │    Acp-Session-Id: <session_id>    │
  │              ┌─────────────────────│
  │<─────────────│─ SSE event ─────────│  notification: ToolCall (status: pending)
  │<─────────────│─ SSE event ─────────│  { method: "request_permission", id: 99, params: {...} }
  │              │                     │  (server-to-client request)
  │              │                     │
  │─── POST /acp ┼────────────────────>│  { id: 99, result: { outcome: "allow_once" } }
  │    Acp-Session-Id: <session_id>    │  (client response, returns 202 Accepted)
  │              │                     │
  │<─────────────│─ SSE event ─────────│  notification: ToolCallUpdate (status: completed)
  │<─────────────│─ SSE event ─────────│  { id: 4, result: { stop_reason: "end_turn" } }
  │              ▼                     │
  │                                    │
  │  ═══ Cancel Flow ═══               │
  │                                    │
  │─── POST /acp ─────────────────────>│  { method: "session/prompt", id: 5, ... }
  │    Acp-Session-Id: <session_id>    │
  │              ┌─────────────────────│
  │<─────────────│─ SSE event ─────────│  notification: AgentMessageChunk
  │              │                     │
  │─── POST /acp ┼────────────────────>│  { method: "session/cancel" }
  │    Acp-Session-Id: <session_id>    │  (notification, no id - returns 202 Accepted)
  │              │                     │
  │<─────────────│─ SSE event ─────────│  { id: 5, result: { stop_reason: "cancelled" } }
  │              ▼                     │
  │                                    │
  │  ═══ Resume Session Flow ═══       │
  │                                    │
  │─── POST /acp ─────────────────────>│  { method: "initialize", id: 1 }
  │    (no Acp-Session-Id)             │  New HTTP session
  │              ┌─────────────────────│
  │<─────────────│─ SSE event ─────────│  { id: 1, result: { capabilities } }
  │              │                     │  Response includes new Acp-Session-Id
  │              ▼                     │
  │                                    │
  │─── POST /acp ─────────────────────>│  { method: "session/load", id: 2,
  │    Acp-Session-Id: <new_session>   │    params: { session_id: <existing_goose_session>, cwd } }
  │              ┌─────────────────────│
  │<─────────────│─ SSE event ─────────│  notification: UserMessageChunk (history replay)
  │<─────────────│─ SSE event ─────────│  notification: AgentMessageChunk (history replay)
  │<─────────────│─ SSE event ─────────│  notification: ToolCall (history replay)
  │<─────────────│─ SSE event ─────────│  notification: ToolCallUpdate (history replay)
  │<─────────────│─ SSE event ─────────│  { id: 2, result: {} }
  │              ▼                     │
  │                                    │
  │  ═══ Standalone SSE Stream ═══     │
  │  (optional, for server-initiated)  │
  │                                    │
  │─── GET /acp ──────────────────────>│  Open dedicated SSE listener
  │    Acp-Session-Id: <session_id>    │
  │    Accept: text/event-stream       │
  │              ┌─────────────────────│  Long-lived connection for
  │              │ (SSE stream open)   │  server-initiated messages
  │              ▼                     │
  │                                    │
  │  ═══ Session Termination ═══       │
  │                                    │
  │─── DELETE /acp ───────────────────>│  Terminate session
  │    Acp-Session-Id: <session_id>    │
  │<────────── 202 Accepted ───────────│

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions