ACP
Agent Communication Protocol (ACP) is a standard for agent-to-agent communication, allowing different AI agents to interact regardless of how they’re built. This agent works with any ACP-compliant service.
Agent prerequisites
Section titled “Agent prerequisites”- Agent Stack installed and running locally
- BeeAI Framework installed with
pip install beeai-framework - BeeAI Framework extension for ACP installed with
pip install 'beeai-framework[acp]'
ACP Agent
Section titled “ACP Agent”When to use ACP instead of Agent Stack Integration?
- You’re connecting to your own custom ACP server
- You’re developing a multi-agent system where agents communicate via ACP
- You’re integrating with a third-party ACP-compliant service that isn’t the Agent Stack
import asyncioimport sysimport traceback
from beeai_framework.adapters.acp.agents import ACPAgentfrom beeai_framework.errors import FrameworkErrorfrom beeai_framework.memory.unconstrained_memory import UnconstrainedMemoryfrom examples.helpers.io import ConsoleReader
async def main() -> None: reader = ConsoleReader()
agent = ACPAgent(agent_name="chat", url="http://127.0.0.1:8001", memory=UnconstrainedMemory()) for prompt in reader: # Run the agent and observe events response = await agent.run(prompt).on( "update", lambda data, event: (reader.write("Agent 🤖 (debug) : ", data)), )
reader.write("Agent 🤖 : ", response.last_message.text)
if __name__ == "__main__": try: asyncio.run(main()) except FrameworkError as e: traceback.print_exc() sys.exit(e.explain())import "dotenv/config.js";import { ACPAgent } from "beeai-framework/adapters/acp/agents/agent";import { createConsoleReader } from "examples/helpers/io.js";import { FrameworkError } from "beeai-framework/errors";import { TokenMemory } from "beeai-framework/memory/tokenMemory";
const agentName = "chat";
const agent = new ACPAgent({ url: "http://127.0.0.1:8000", agentName, memory: new TokenMemory(),});
const reader = createConsoleReader();
try { for await (const { prompt } of reader) { const result = await agent.run({ input: prompt }).observe((emitter) => { emitter.on("update", (data) => { reader.write(`Agent (received progress) 🤖 : `, JSON.stringify(data.value, null, 2)); }); emitter.on("error", (data) => { reader.write(`Agent (error) 🤖 : `, data.message); }); });
reader.write(`Agent (${agentName}) 🤖 : `, result.result.text); }} catch (error) { reader.write("Agent (error) 🤖", FrameworkError.ensure(error).dump());}The availability of ACP agents depends on the server you’re connecting to. You can check which agents are available by using the check_agent_exists method:
try: await agent.check_agent_exists() print("Agent exists and is available")except AgentError as e: print(f"Agent not available: {e.message}")try { await agent.checkAgentExists(); console.log("Agent exists and is available");} catch (e) { console.error(`Agent not available: ${e.message}`);}If you need to create your own ACP server with custom agents, BeeAI framework provides the AcpServer class.
ACP Server
Section titled “ACP Server”Basic example:
from beeai_framework.adapters.acp import ACPServer, ACPServerConfigfrom beeai_framework.agents.requirement import RequirementAgentfrom beeai_framework.backend import ChatModelfrom beeai_framework.memory import UnconstrainedMemoryfrom beeai_framework.tools.search.duckduckgo import DuckDuckGoSearchToolfrom beeai_framework.tools.weather import OpenMeteoTool
def main() -> None: llm = ChatModel.from_name("ollama:granite4:micro") agent = RequirementAgent( llm=llm, tools=[DuckDuckGoSearchTool(), OpenMeteoTool()], memory=UnconstrainedMemory(), # specify the agent's name and other metadata name="chat", description="A simple agent", )
# Register the agent with the ACP server and run the HTTP server # For the ToolCallingAgent and ReActAgent, we don't need to specify ACPAgent factory method # because they are already registered in the ACPServer ACPServer(config=ACPServerConfig(port=8001)).register(agent, tags=["example"]).serve()
if __name__ == "__main__": main()Agent execution (Python)
Pass execution=AgentExecutionConfig(...) to server.register() to configure
max_iterations, total_max_retries, and max_retries_per_step for the built-in
RequirementAgent, ToolCallingAgent, and ReActAgent. These options apply to every
request served by that registration. Omitted fields and fields set to None retain the
agent’s own defaults. The configuration is copied during registration, and non-empty
execution configuration is rejected for other runnable types.
Custom agent example:
import sysimport tracebackfrom collections.abc import AsyncGeneratorfrom typing import Unpack
import acp_sdk.models as acp_modelsimport acp_sdk.server.context as acp_contextimport acp_sdk.server.types as acp_types
from beeai_framework.adapters.acp import ACPServerfrom beeai_framework.adapters.acp.serve._utils import acp_msgs_to_framework_msgsfrom beeai_framework.adapters.acp.serve.agent import ACPServerAgentfrom beeai_framework.adapters.acp.serve.server import ACPServerMetadata, to_acp_agent_metadatafrom beeai_framework.agents import AgentOptions, AgentOutput, BaseAgentfrom beeai_framework.backend.message import AnyMessage, AssistantMessage, UserMessagefrom beeai_framework.emitter.emitter import Emitterfrom beeai_framework.errors import FrameworkErrorfrom beeai_framework.memory import UnconstrainedMemoryfrom beeai_framework.memory.base_memory import BaseMemoryfrom beeai_framework.runnable import runnable_entry
# This is a simple echo agent that echoes back the last message it received.class EchoAgent(BaseAgent): memory: BaseMemory
def __init__(self, memory: BaseMemory) -> None: super().__init__() self.memory = memory
def _create_emitter(self) -> Emitter: return Emitter.root().child( namespace=["agent", "custom"], creator=self, )
@runnable_entry async def run(self, input: str | list[AnyMessage], /, **kwargs: Unpack[AgentOptions]) -> AgentOutput: assert self.memory is not None
if isinstance(input, str): await self.memory.add(UserMessage(input)) elif isinstance(input, list): await self.memory.add_many(input)
text_input = self.memory.messages[-1].text if self.memory.messages else "" return AgentOutput(output=[AssistantMessage(text_input)])
def main() -> None: # Create a custom agent factory for the EchoAgent def agent_factory(agent: EchoAgent, *, metadata: ACPServerMetadata | None = None) -> ACPServerAgent: """Factory method to create an ACPAgent from a EchoAgent.""" if metadata is None: metadata = {}
async def run( input: list[acp_models.Message], context: acp_context.Context ) -> AsyncGenerator[acp_types.RunYield, acp_types.RunYieldResume]: framework_messages = acp_msgs_to_framework_msgs(input) response = await agent.run(framework_messages) yield acp_models.MessagePart(content=response.last_message.text, role="assistant") # type: ignore[call-arg]
# Create an ACPAgent instance with the run function return ACPServerAgent( fn=run, name=metadata.get("name", agent.meta.name), description=metadata.get("description", agent.meta.description), metadata=to_acp_agent_metadata(metadata), )
# Register the custom agent factory with the ACP server # pyrefly: ignore [bad-argument-type] ACPServer.register_factory(EchoAgent, agent_factory) # Create an instance of the EchoAgent with UnconstrainedMemory agent = EchoAgent(memory=UnconstrainedMemory()) # Register the agent with the ACP server and run the HTTP server ACPServer().register(agent, name="echo_agent").serve()
if __name__ == "__main__": try: main() except FrameworkError as e: traceback.print_exc() sys.exit(e.explain())
# run: beeai agent run echo_agent "Hello"Source: python/examples/serve/acp_with_custom_agent.py