SimpleAcp
CAUTION: This project is under active development. The API and documentation may not necessarily reflect the current codebase.
Install • Quick Start • Documentation • ACP Specification
Features
- Full ACP Protocol Support: Implements the ACP specification including agents, runs, sessions, and events
- Multiple Run Modes: Synchronous, asynchronous, and streaming execution
- Session Management: Maintain state and conversation history across interactions
- Multimodal Messages: Support for text, JSON, images, and URL references
- Pluggable Storage: In-memory, Redis, and PostgreSQL backends
- SSE Streaming: Server-Sent Events for real-time response streaming
- Falcon Server: Fiber-based concurrency for efficient handling of concurrent connections
Documentation
For comprehensive guides, tutorials, and API reference, visit the SimpleAcp Documentation.
The documentation covers:
- Installation & Quick Start
- Core Concepts (Messages, Agents, Runs, Sessions, Events)
- Server Guide (Creating Agents, Streaming, Multi-Turn Conversations)
- Client Guide (Sync/Async, Streaming, Session Management)
- Storage Backends (Memory, Redis, PostgreSQL, Custom)
- API Reference
Installation
Add this line to your application's Gemfile:
gem 'simple_acp'Or install it directly:
gem install simple_acpQuick Start
Creating an Agent Server
require 'simple_acp'
server = SimpleAcp::Server::Base.new
# Register an agent using block syntax
server.agent("echo", description: "Echoes everything") do |context|
Enumerator.new do |yielder|
context.input.each do |message|
yielder << SimpleAcp::Server::RunYield.new(
SimpleAcp::Models::Message.agent(message.text_content)
)
end
end
end
# Register a simple greeting agent
server.agent("greeter", description: "Greets the user") do |context|
name = context.input.first&.text_content || "World"
SimpleAcp::Models::Message.agent("Hello, #{name}!")
end
# Start the server
server.run(port: 8000)Using the Client
require 'simple_acp'
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
# List available agents
agents = client.agents
puts agents.agents.map(&:name)
# Run an agent synchronously
run = client.run_sync(
agent: "echo",
input: [SimpleAcp::Models::Message.user("Hello, SimpleAcp!")]
)
puts run.output.first.text_content
# => "Hello, SimpleAcp!"
# Run with streaming
client.run_stream(agent: "echo", input: "Streaming test") do |event|
case event
when SimpleAcp::Models::MessagePartEvent
print event.part.content
when SimpleAcp::Models::RunCompletedEvent
puts "\nDone!"
end
endAPI Reference
Server
The SimpleAcp::Server::Base class hosts agents and handles incoming requests.
server = SimpleAcp::Server::Base.new(storage: SimpleAcp::Storage::Memory.new)
# Register agents
server.agent("name", description: "...", input_content_types: ["text/plain"]) do |context|
# context.input - array of input messages
# context.session - current session (if any)
# context.history - conversation history
# context.state - session state
# context.set_state(data) - update session state
# context.await_message(prompt) - request client input
# Return messages or yield them for streaming
SimpleAcp::Models::Message.agent("Response")
end
# Run programmatically
run = server.run_sync(agent_name: "name", input: messages)
# Or start HTTP server
server.run(port: 8000)Client
The SimpleAcp::Client::Base class communicates with ACP servers.
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
# Discovery
client.ping # Health check
client.agents # List agents
client.agent("name") # Get agent manifest
# Execution
client.run_sync(agent: "name", input: messages)
client.run_async(agent: "name", input: messages)
client.run_stream(agent: "name", input: messages) { |event| ... }
# Run management
client.run_status(run_id)
client.run_events(run_id)
client.run_cancel(run_id)
# Resume awaited runs
client.run_resume_sync(run_id: id, await_resume: resume_payload)
client.run_resume_stream(run_id: id, await_resume: resume_payload) { |e| ... }
# Session management
client.use_session(session_id)
client.clear_sessionModels
Message
# Create messages
SimpleAcp::Models::Message.user("Hello")
SimpleAcp::Models::Message.agent("Response")
# With multiple parts
SimpleAcp::Models::Message.user(
SimpleAcp::Models::MessagePart.text("Some text"),
SimpleAcp::Models::MessagePart.json({ key: "value" })
)
# Access content
message.role # "user" or "agent"
message.parts # Array of MessagePart
message.text_content # Combined text from all partsMessagePart
# Factory methods
SimpleAcp::Models::MessagePart.text("Hello")
SimpleAcp::Models::MessagePart.json({ key: "value" })
SimpleAcp::Models::MessagePart.image(base64_data, mime_type: "image/png")
SimpleAcp::Models::MessagePart.from_url("https://...", content_type: "image/jpeg")
# Properties
part.content_type # MIME type
part.content # Inline content
part.content_url # URL reference
part.content_encoding # "plain" or "base64"
part.text? # Is text content?
part.json? # Is JSON content?
part.image? # Is image content?Run
run.run_id # UUID
run.agent_name # Agent that executed
run.status # Current status
run.output # Output messages
run.error # Error if failed
run.session_id # Associated session
# Status checks
run.terminal? # Is in final state?
run.completed?
run.failed?
run.cancelled?
run.awaiting?Storage Backends
Memory (Default)
storage = SimpleAcp::Storage::Memory.new
server = SimpleAcp::Server::Base.new(storage: storage)Redis
require 'simple_acp/storage/redis'
storage = SimpleAcp::Storage::Redis.new(
url: "redis://localhost:6379",
ttl: 86400 # 24 hours
)
server = SimpleAcp::Server::Base.new(storage: storage)PostgreSQL
require 'simple_acp/storage/postgresql'
storage = SimpleAcp::Storage::PostgreSQL.new(
url: "postgres://localhost/simple_acp_production"
)
server = SimpleAcp::Server::Base.new(storage: storage)Advanced Usage
Stateful Agents with Sessions
server.agent("counter") do |context|
count = (context.state || 0) + 1
context.set_state(count)
SimpleAcp::Models::Message.agent("Count: #{count}")
end
# Client maintains session across requests
client.use_session("my-session")
client.run_sync(agent: "counter", input: "increment") # Count: 1
client.run_sync(agent: "counter", input: "increment") # Count: 2Awaiting Client Input
server.agent("questioner") do |context|
Enumerator.new do |yielder|
# Ask a question
result = context.await_message(
SimpleAcp::Models::Message.agent("What is your name?")
)
yielder << result
# When resumed, result will contain the client's response
# This part runs after run_resume is called
name = context.resume_message&.text_content
yielder << SimpleAcp::Server::RunYield.new(
SimpleAcp::Models::Message.agent("Hello, #{name}!")
)
end
end
# Client side
run = client.run_sync(agent: "questioner", input: "start")
# run.awaiting? => true
run = client.run_resume_sync(
run_id: run.run_id,
await_resume: SimpleAcp::Models::MessageAwaitResume.new(
message: SimpleAcp::Models::Message.user("Alice")
)
)
# run.output includes "Hello, Alice!"HTTP API Endpoints
When running as an HTTP server, these endpoints are available:
-
GET /ping- Health check -
GET /agents- List agents -
GET /agents/:name- Get agent manifest -
POST /runs- Create a run -
GET /runs/:id- Get run status -
POST /runs/:id- Resume awaited run -
POST /runs/:id/cancel- Cancel run -
GET /runs/:id/events- Get run events -
GET /session/:id- Get session info
Development
# Install dependencies
bundle install
# Run tests
bundle exec rake test
# Run a specific test
bundle exec ruby -Ilib:test test/simple_acp/models/message_test.rbA Note on ACP as a Standard
This gem implements the Agent Communication Protocol specification, but I have reservations about ACP becoming "the" standard for agent-to-agent communication. Like MCP before it, ACP suffers from conceptual ambiguity—the protocol designers seem unable to clearly distinguish between what constitutes an "agent" versus a "tool." This fundamental confusion permeates both protocols.
Whether ACP achieves the same de facto adoption that MCP has gained remains to be seen. MCP's prevalence stems largely from momentum and network effects rather than technical superiority. ACP may follow the same path simply because it exists and others adopt it, not because it represents the best possible design for inter-agent communication.
For now, this implementation provides a practical way to work with ACP-compatible systems while we wait to see how the agent communication landscape evolves.
License
Apache License 2.0 - See LICENSE file for details.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
