a2a-rails
Rails-native integration for exposing Rails applications as A2A v1.0 agents.
Status: v0.1.0 is released and available on RubyGems.
-
RubyGems: https://rubygems.org/gems/a2a-rails
-
GitHub Release: https://github.com/cuichangquan/a2a-rails/releases/tag/v0.1.0
-
Changelog: CHANGELOG.md
-
Release record: docs/release/v0.1.0-record.md
-
A2Aの全体像(日本語・A4 1枚PDF) — 登場人物・依頼の流れ・主要用語・MCPとの違いをまとめた学習資料。
-
A2A at a glance (English, A4 one-page PDF) — Roles, workflow, key terms, and how MCP fits.
What is a2a-rails?
a2a-rails lets a Rails application expose an A2A-compatible Agent without making application code depend directly on SDK-specific request and response objects.
A2A Protocol
↓
Ruby A2A SDK
↓
a2a-rails
↓
Rails Application
↓
Business Logic
The Gem provides:
- a Rails-native Agent / Skill DSL;
- A2A Agent Card generation;
- automatically mounted A2A HTTP endpoints;
- synchronous Task execution;
- SDK-independent Handler inputs;
- a process-local Task Store;
- Rails generators for initial setup;
- an internal Protocol Adapter boundary around the upstream SDK.
v0.1 is intentionally server-first and non-streaming.
Requirements
- Ruby
>= 3.3 - Rails
>= 8.0, < 8.2 - A2A protocol version
1.0 agent2agent ~> 2.0.0
The v0.1.0 release is verified against:
- Ruby 3.3 / 3.4 / 4.0
- Rails 8.0 / 8.1
Installation
Add the Gem to an existing Rails application:
bundle add a2a-railsThen generate the initializer and an Agent scaffold:
bin/rails generate a2a:rails:install
bin/rails generate a2a:rails:agent echoNo explicit Engine mount or host config/routes.rb change is required.
Quick Start
The following Echo flow is verified against the published a2a-rails 0.1.0 Gem in a clean Rails 8.1 application.
1. Generate the setup
bin/rails generate a2a:rails:install
bin/rails generate a2a:rails:agent echo
mkdir -p app/services/echoThe install generator creates:
config/initializers/a2a_rails.rb
The Agent generator creates:
app/agents/echo_agent.rb
The generators deliberately do not create application business logic, jobs, migrations, Task Stores, or routing side effects.
2. Create the Handler
Create app/services/echo/reply.rb:
class Echo::Reply
def self.call(message:, context:)
text = message[:parts]
.filter_map { |part| part[:text] }
.join("\n")
"Echo: #{text}"
end
endHandlers receive SDK-independent Ruby Hashes for message and context.
3. Define the Agent and Skill
Replace app/agents/echo_agent.rb with:
class EchoAgent < A2A::Rails::Agent
name "Echo Agent"
description "Echo messages"
version "1.0"
skill :reply,
description: "Echo a message",
tags: %w[echo],
handler: Echo::Reply
endA single Skill is selected automatically. No Router is required.
4. Register the Agent
Replace config/initializers/a2a_rails.rb with:
A2A::Rails.configure do |config|
config.agent = "EchoAgent"
config.public_base_url = ENV["A2A_PUBLIC_BASE_URL"]
endFor localhost, leave A2A_PUBLIC_BASE_URL unset or set it to http://localhost:3000.
The Agent class name remains a String until an A2A endpoint resolves it, preserving Rails autoload / reload behavior.
5. Check the Agent Card
Start Rails:
bin/rails serverThen request the Agent Card:
curl -sS http://localhost:3000/.well-known/agent-card.json \
-H "A2A-Version: 1.0"Expected essentials:
- HTTP 200
- Agent name
Echo Agent - Skill ID
reply - A2A interface URL ending in
/a2a
6. Send a message
curl -sS -X POST http://localhost:3000/a2a \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-1",
"role": "ROLE_USER",
"parts": [{"text": "Hello"}]
}
}
}'A successful response reaches:
result.task.status.state == TASK_STATE_COMPLETED
Artifact Text Part == "Echo: Hello"
See docs/design/quick-start.md for the design and verification notes behind this flow.
Public Rails API
A minimal Agent looks like this:
class ShoppingAgent < A2A::Rails::Agent
name "Shopping Agent"
description "Search and purchase products"
version "1.0"
skill :search_products,
description: "Search products",
tags: %w[shopping search],
handler: Shopping::SearchProducts
endHandler:
class Shopping::SearchProducts
def self.call(message:, context:)
# Rails business logic
end
endRegistration:
A2A::Rails.configure do |config|
config.agent = "ShoppingAgent"
config.public_base_url = ENV["A2A_PUBLIC_BASE_URL"]
endv0.1 targets one public A2A Agent per Rails application.
For multiple Skills, the application supplies a Router. The Router receives skills: as a frozen Array<Symbol> of declared Skill IDs and may return a matching Symbol or String. Unknown selections raise A2A::Rails::UnknownSkillError; the Dispatcher does not silently choose the first Skill.
HTTP Endpoints
The Gem automatically exposes:
GET /.well-known/agent-card.json
POST /a2a
The Rails Engine is mounted automatically.
Agent Card
Agent Cards are generated from the Agent / Skill DSL.
Behavior in v0.1:
- Skill IDs and default display names come from Skill declarations;
- optional
examples,input_modes, andoutput_modesmap to A2A Agent Card fields; - Handler internals are not exposed;
-
public_base_urlwins when configured, otherwise the request base URL is used; - default input/output mode is
text/plain; - Streaming, Push Notifications, and Extended Agent Cards are disabled;
- generated Cards pass the
agent2agent 2.0.0Agent Card schema.
Task Lifecycle
Internally, a2a-rails keeps SDK-independent Task states:
SUBMITTED
↓
WORKING
├──→ COMPLETED
├──→ FAILED
└──→ REJECTED
Cancellation can move a non-terminal Task to CANCELED.
Handler result mapping:
normal return → COMPLETED
A2A::Rails::RejectedTask → REJECTED
unexpected exception → FAILED
Artifact mapping:
String → Text Part
Hash / Array → Data Part
nil → no Artifact
other object → ArtifactMappingError
Supported Task operations:
SendMessageGetTaskListTasksCancelTask
ListTasks supports context/state/timestamp filters, stable newest-first ordering, page sizes 1–100, and opaque snapshot pagination.
The default Task::MemoryStore is thread-safe but process-local. Tasks and pagination cursors are not durable across process restarts and are not shared between processes.
Cancellation changes Task state atomically, but does not stop already-running Handler code or reverse application side effects.
Architecture
Key boundaries:
- Rails Engine / controllers / routes form the Rails integration layer;
- SDK-specific behavior stays behind
Protocol::Adapter/Protocol::Agent2AgentAdapter; - A2A camelCase fields,
TASK_STATE_*, SDK schema objects, and SDK errors stay in the Protocol layer; - Agent / Handler application constants are resolved lazily through Rails;
- ActiveRecord and ActiveJob are not runtime requirements.
Gem structure:
lib/a2a/rails/
├── agent.rb
├── skill.rb
├── dispatcher.rb
├── configuration.rb
├── runtime.rb
├── engine.rb
├── agent_card/
├── task/
└── protocol/
lib/generators/a2a/rails/
├── install_generator.rb
├── agent_generator.rb
└── templates/
v0.1 Scope
Included:
- Rails integration
A2A::Rails::Agent- Skill DSL and Handler dispatch
- Agent Card generation
/.well-known/agent-card.jsonPOST /a2a- synchronous Task lifecycle
-
SendMessage,GetTask,ListTasks,CancelTask -
A2A-Version: 1.0validation - in-memory Task Store
- Rails Engine / Routes
- Configuration
-
install/agentgenerators - Rails logging boundary
Not included in v0.1:
- A2A Client
- ActiveRecord Task Store
- ActiveJob Task execution
- SSE / BiDi Streaming
- Push Notifications
- gRPC
- Human-in-the-loop flows
-
INPUT_REQUIRED/AUTH_REQUIRED - OAuth Server
- Agent Registry / Marketplace
- Authorization Engine
- ActingFor integration
- Admin UI
- LLM Agent Framework
- Orchestration Framework
Verification
The v0.1.0 release candidate completed:
13 / 13 CI jobs green
70 tests
240 assertions
0 failures
0 errors
0 skips
After publication, a2a-rails 0.1.0 was fetched back from RubyGems and its SHA256 matched the exact artifact that was pushed.
Final published artifact SHA256:
23d34bde6f436723bf01735f3a507cf8529975b1dfed5d5da9b063fc0480b19f
A fresh Rails 8.1 application then installed the published Gem from RubyGems and verified:
Agent Card HTTP: 200
Agent name: Echo Agent
Skill: reply
SendMessage HTTP: 200
Task state: TASK_STATE_COMPLETED
Artifact: Echo: Hello
See docs/release/v0.1.0-record.md for the complete release evidence.
Test Strategy
v0.1 uses Minitest with four layers:
4. Protocol E2E / Smoke Tests
3. Rails Integration Tests
2. Adapter Contract Tests
1. Core Unit Tests
The CI matrix covers:
- Gem: Ruby 3.3 / 3.4 / 4.0
- SDK spike: Ruby 3.3 / 3.4 / 4.0
- Rails: Ruby 3.3 / 3.4 / 4.0 × Rails 8.0 / 8.1
- Packaged Gem: Ruby 3.4 + clean Rails 8.1 application
Known warning-enabled output from upstream agent2agent 2.0.0 can include circular-require, indentation, and URI-parser warnings. Those warnings are distinct from the Rack-environment INFO logging that a2a-rails suppresses at the Rails-facing adapter boundary.
Release Documents
Design Documents
- v0.1 Design Decisions
- v0.1 Test Strategy
- v0.1 Gem Structure
- v0.1 Quick Start Design
- SDK compatibility findings
License
The Gem is available as open source under the terms of the MIT License. See LICENSE.
Official A2A Resources / A2A公式資料
- A2A Protocol overview / A2Aの全体像(日本語・A4 1枚PDF) — 公式資料をもとに作成した学習資料(2026-10-07)。
- A2A at a glance (English, A4 one-page PDF) — Learning guide based on the official documentation (2026-10-07).
- A2A Protocol documentation (Latest) / 公式ドキュメント
- A2A Protocol v1.0.0 documentation / v1.0.0ドキュメント
- A2A v1.0.0 Specification / v1.0.0仕様書
- Official GitHub / 公式GitHub: a2aproject/A2A
- Official samples / 公式サンプル: a2aproject/a2a-samples
Articles & Community / 紹介記事・コミュニティ
Japanese articles / 日本語の紹介記事
- Zenn: RailsアプリをA2A対応Agentとして公開する「a2a-rails」を作りました
- Qiita: RailsアプリをA2A対応Agentとして公開する「a2a-rails」を作りました

