Skip to content

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 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]'

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 asyncio
import sys
import traceback
from beeai_framework.adapters.acp.agents import ACPAgent
from beeai_framework.errors import FrameworkError
from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory
from 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())

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}")

If you need to create your own ACP server with custom agents, BeeAI framework provides the AcpServer class.

Basic example:

from beeai_framework.adapters.acp import ACPServer, ACPServerConfig
from beeai_framework.agents.requirement import RequirementAgent
from beeai_framework.backend import ChatModel
from beeai_framework.memory import UnconstrainedMemory
from beeai_framework.tools.search.duckduckgo import DuckDuckGoSearchTool
from 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 sys
import traceback
from collections.abc import AsyncGenerator
from typing import Unpack
import acp_sdk.models as acp_models
import acp_sdk.server.context as acp_context
import acp_sdk.server.types as acp_types
from beeai_framework.adapters.acp import ACPServer
from beeai_framework.adapters.acp.serve._utils import acp_msgs_to_framework_msgs
from beeai_framework.adapters.acp.serve.agent import ACPServerAgent
from beeai_framework.adapters.acp.serve.server import ACPServerMetadata, to_acp_agent_metadata
from beeai_framework.agents import AgentOptions, AgentOutput, BaseAgent
from beeai_framework.backend.message import AnyMessage, AssistantMessage, UserMessage
from beeai_framework.emitter.emitter import Emitter
from beeai_framework.errors import FrameworkError
from beeai_framework.memory import UnconstrainedMemory
from beeai_framework.memory.base_memory import BaseMemory
from 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