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()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