Agent Client Protocol (ACP) Python SDK

repository·main·Indexed 43 days ago

https://github.com/agentclientprotocol/python-sdk

A Python implementation of the Agent Client Protocol (ACP) by Zed Industries. Version 0.12.1 provides typed Pydantic models, asyncio-based transports for stdio JSON-RPC, and lifecycle helpers to build ACP-compliant agents and clients. Includes support for Protocol v1 and an experimental v2, as well as contribution utilities like SessionAccumulator, ToolCallTracker, and PermissionBroker for managing session state and tool call workflows.

Tokens
29.2K
Snippets
66
Records
150
Agent score
56%

What's inside ACP

  1. Core features of the ACP Python SDK

    main

    The SDK provides several key capabilities for working with the Agent Client Protocol:

    • Spec parity: Uses generated Pydantic models in acp.schema that track the upstream ACP schema to ensure payload validity.
    • Direct construction: Models can be instantiated directly because generated discriminator fields include defaults.
    • Runtime ergonomics: Provides async base classes, stdio JSON-RPC plumbing, and lifecycle helpers to minimize the boilerplate needed for custom agents.
    • Ready-to-use examples: Includes demos for streaming, permissions, Gemini bridges, and duet patterns in the examples/ directory.
    • Contribution utilities: Includes session accumulators, tool call trackers, and permission brokers designed for real-world deployments.
  2. Understand session updates and prompt behavior in v2

    main

    In Protocol v2, the session/prompt call returns once the agent accepts the prompt. However, this does not create a boundary for session updates.

    Updates may arrive:

    • Before the prompt request
    • During the prompt request
    • After the prompt request

    Because updates do not carry a prompt identifier, applications are responsible for deciding how to buffer or present these updates relative to the prompt lifecycle.

  3. Coordinate tool calls and permissions with ToolCallTracker and PermissionBroker

    main

    When building an agent runtime that synthesizes tool call IDs, streams arguments, and requires user approval, use ToolCallTracker and PermissionBroker to centralize bookkeeping.

    ToolCallTracker (acp.contrib.tool_calls)

    • start(): Emits a ToolCallStart update.
    • progress(): Emits a ToolCallProgress update.
    • append_stream_text(): Handles streaming arguments.
    • view() or tool_call_model(): Provides an in-memory view of the current tool call state.

    PermissionBroker (acp.contrib.permissions)

    • request_for(tool_call): Wraps requestPermission RPCs. It reuses the tracker state and provides standard options: Approve, Approve for session, and Reject.
    • default_permission_options(): Returns the canonical triple of options if you need to customize them.

    Best Practice: Keep one ToolCallTracker near the agent event loop. Share this tracker instance with the PermissionBroker so permission prompts always match the latest tool call state.

    from acp.contrib.tool_calls import ToolCallTracker
    from acp.contrib.permissions import PermissionBroker
    
    tracker = ToolCallTracker()
    broker = PermissionBroker()
    
    # Start a tool call
    tracker.start()
    
    # Request permission using the tracker's state
    broker.request_for(tracker.tool_call_model())
  4. Understand the ACP SDK building blocks

    main

    The SDK is organized into several core modules to help you build different parts of the Agent Client Protocol ecosystem:

    • acp.schema: Contains generated Pydantic models. These models include default values for protocol discriminator fields, ensuring your payloads match the official schema.
    • acp.agent / acp.client: Provides async base classes for implementing agents or clients. These handle JSON-RPC supervision and lifecycle orchestration.
    • acp.contrib: A collection of experimental utilities derived from production use cases, such as session accumulators, permission brokers, and tool call trackers.
    • examples/: A directory of runnable scripts including agents, clients, duet demos, and the Gemini CLI bridge.
  5. Use SessionAccumulator to manage session state for UIs

    main

    The SessionAccumulator in acp.contrib.session_state provides a live, merged view of SessionNotification events. It is designed to reconcile tool_call and tool_call_update payloads, ensuring a consistent state even if events arrive out of order.

    Key Methods:

    • apply(notification): Reconciles a SessionNotification into the current state.
    • snapshot(): Returns an immutable SessionSnapshot Pydantic model containing the plan, current mode, commands, and ordered message history.
    • subscribe(callback): Allows you to push snapshots into stores or UI components whenever the state changes.

    Best Practice: Create one accumulator per UI controller. Feed every raw SessionNotification into it and render from snapshot.tool_calls or snapshot.user_messages instead of manually mutating state.

    from acp.contrib.session_state import SessionAccumulator
    
    accumulator = SessionAccumulator()
    
    # Feed notifications into the accumulator
    accumulator.apply(notification)
    
    # Get an immutable view for the UI
    snapshot = accumulator.snapshot()
    print(snapshot.tool_calls)
    
    # Or subscribe to changes
    accumulator.subscribe(lambda snapshot: update_ui(snapshot))
  6. Migrate from ACP Python SDK 0.7.x to 0.8.x

    main
    When upgrading to ACP Python SDK 0.8, note that the SDK aligns with ACP schema 0.10.8. Key changes include the removal of TerminalHandle from the public API and increased default memory usage for stdio buffers. If you use schema-derived artifacts or vendored types, you must regenerate them against schema version 0.10.8. Additionally, if your code performs exhaustive pattern matching on SessionUpdate union types, you must now account for the new SessionInfoUpdate variant.
  7. Explore Client implementation examples

    main

    If you are building a Client to interact with agents via ACP, these projects provide useful reference implementations:

    • agent-client-kernel (wiki3-ai): A Jupyter kernel that speaks ACP, allowing notebooks to chat with external agents. Ideal for embedding ACP in notebook tooling.
    • simple-acp-client (OhadRubin): A Python client that wraps ACP executables with a friendly API (similar to the Claude Agent SDK style). Use this as a starting point for building bespoke clients.
  8. Mount an ACP application in another Starlette app

    main

    When mounting the ACP app, you must manually manage its lifespan to ensure HTTP connections are cleaned up during shutdown.

    from contextlib import asynccontextmanager
    from starlette.applications import Starlette
    from starlette.routing import Mount
    
    acp_app = create_asgi_app(lambda conn: MyAgent())
    
    @asynccontextmanager
    async def lifespan(app):
        # The parent must enter the child lifespan
        async with acp_app.router.lifespan_context(acp_app):
            yield
    
    app = Starlette(routes=[Mount("/agents", app=acp_app)], lifespan=lifespan)
    # Connect to /agents/acp using either HTTP or WebSocket.
  9. Implement Agent and Client interface methods with explicit parameters

    main

    The Agent and Client interfaces in src/acp/interfaces.py have moved to idiomatic Python protocols. Instead of receiving a single Pydantic request model (e.g., params: PromptRequest), methods now receive individual schema fields as explicit arguments.

    Migration Steps:

    1. Rename methods: Convert method names from camelCase to snake_case (e.g., requestPermission becomes request_permission).
    2. Update signatures: Replace the single params object with the specific fields required by the schema.
    3. Handle extension data: Use **kwargs in your method signature to capture any extra metadata (formerly under _meta) passed via field_meta.

    Note: The connection layer uses @param_model to automatically assemble request models, so callers no longer need to manually construct Pydantic objects.

    # Before
    class EchoAgent:
        async def prompt(self, params: PromptRequest) -> PromptResponse:
            text = params.prompt[0].text
            return PromptResponse(stopReason="end_turn")
    
    # After
    class EchoAgent:
        async def prompt(self, prompt, session_id, **kwargs) -> PromptResponse:
            text = prompt[0].text
            return PromptResponse(stop_reason="end_turn")
    
    # Client example
    class RecordingClient(Client):
        async def request_permission(self, options, session_id, tool_call, **kwargs):
            ...
  10. Install WebSocket transport dependencies

    main

    The WebSocket transport requires the websockets and http extras. If you encounter an ImportError, install the package with the [http] extra:

    pip install agent-client-protocol[http]
  11. Explore Agent implementation examples

    main

    If you are building an Agent using the Agent Client Protocol (ACP), you can refer to these existing projects to see how to implement core features like streaming updates, permission prompts, tool call UX, and execution pipelines:

    • kimi-cli (MoonshotAI): A CLI-first ACP+MCP agent for software dev and terminal workflows. Highlights streaming updates and permission prompts.
    • Mini-Agent (MiniMax-AI): A professional single agent demo showcasing the core execution pipeline and production-grade features.
    • OpenHands-CLI (OpenHands): A lightweight, modern CLI for interacting with the OpenHands agent.
    • fast-agent (evalstate): A tool to define, prompt, and test MCP-enabled agents and workflows.