Project

a2a-rails

0.0
The project is in a healthy, maintained state
Expose Rails applications as A2A agents while keeping protocol SDK details behind an internal adapter boundary.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 5.25
~> 13.2

Runtime

>= 8.0, < 8.2
~> 2.0.0
< 3
>= 3.0, < 4
>= 8.0, < 8.2
 Project Readme

a2a-rails


Rails-native integration for exposing Rails applications as A2A v1.0 agents.

Status: v0.1.0 is released and available on RubyGems.

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-rails

Then generate the initializer and an Agent scaffold:

bin/rails generate a2a:rails:install
bin/rails generate a2a:rails:agent echo

No 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/echo

The 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
end

Handlers 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
end

A 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"]
end

For 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 server

Then 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
end

Handler:

class Shopping::SearchProducts
  def self.call(message:, context:)
    # Rails business logic
  end
end

Registration:

A2A::Rails.configure do |config|
  config.agent = "ShoppingAgent"
  config.public_base_url = ENV["A2A_PUBLIC_BASE_URL"]
end

v0.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, and output_modes map to A2A Agent Card fields;
  • Handler internals are not exposed;
  • public_base_url wins 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.0 Agent 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:

  • SendMessage
  • GetTask
  • ListTasks
  • CancelTask

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.json
  • POST /a2a
  • synchronous Task lifecycle
  • SendMessage, GetTask, ListTasks, CancelTask
  • A2A-Version: 1.0 validation
  • in-memory Task Store
  • Rails Engine / Routes
  • Configuration
  • install / agent generators
  • 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

License

The Gem is available as open source under the terms of the MIT License. See LICENSE.

Official A2A Resources / A2A公式資料

Articles & Community / 紹介記事・コミュニティ

Japanese articles / 日本語の紹介記事

Community submissions / コミュニティへの投稿