This guide helps you diagnose and resolve common issues when using the A2A Ruby SDK.
- Connection Issues
- Authentication Problems
- Protocol Errors
- Performance Issues
- Rails Integration Issues
- Debugging Tools
- Common Error Codes
- Frequently Asked Questions
Symptoms:
A2A::Errors::HTTPError: Connection refusedA2A::Errors::TimeoutError: Request timeout
Solutions:
- Check the endpoint URL:
# Verify the URL is correct and accessible
client = A2A::Client::HttpClient.new("https://agent.example.com/a2a")
# Test basic connectivity
begin
card = client.get_card
puts "Connection successful"
rescue A2A::Errors::HTTPError => e
puts "Connection failed: #{e.message}"
end- Verify SSL certificates:
# For development, you might need to disable SSL verification
require 'faraday'
client = A2A::Client::HttpClient.new("https://localhost:3000/a2a") do |conn|
conn.ssl.verify = false # Only for development!
end- Check firewall and network settings:
# Test basic connectivity
curl -v https://agent.example.com/a2a/agent-card
# Check DNS resolution
nslookup agent.example.comSymptoms:
- Requests taking too long
A2A::Errors::TimeoutError
Solutions:
- Increase timeout:
config = A2A::Client::Config.new
config.timeout = 120 # 2 minutes
client = A2A::Client::HttpClient.new(url, config: config)- Use streaming for long operations:
# Instead of blocking calls, use streaming
client.send_message(message) do |response|
# Handle responses as they arrive
puts "Received: #{response}"
end- Implement retry logic:
require 'retries'
with_retries(max_tries: 3, base_sleep_seconds: 1, max_sleep_seconds: 5) do
client.send_message(message)
endSymptoms:
A2A::Errors::AuthenticationError: Invalid credentials- HTTP 401 Unauthorized responses
Solutions:
- Verify OAuth 2.0 configuration:
auth = A2A::Client::Auth::OAuth2.new(
client_id: ENV['A2A_CLIENT_ID'],
client_secret: ENV['A2A_CLIENT_SECRET'],
token_url: "https://auth.example.com/oauth/token"
)
# Test token acquisition
begin
token = auth.get_token
puts "Token acquired: #{token[0..20]}..."
rescue => e
puts "Token error: #{e.message}"
end- Check JWT token validity:
require 'jwt'
token = "your-jwt-token"
begin
payload = JWT.decode(token, nil, false) # Don't verify for debugging
puts "Token payload: #{payload}"
puts "Expires at: #{Time.at(payload[0]['exp'])}"
rescue JWT::DecodeError => e
puts "Invalid JWT: #{e.message}"
end- Verify API key configuration:
auth = A2A::Client::Auth::ApiKey.new(
key: ENV['A2A_API_KEY'],
header: "X-API-Key" # or parameter: "api_key"
)
# Test with debug logging
A2A.configure { |c| c.log_level = :debug }
client = A2A::Client::HttpClient.new(url, auth: auth)Symptoms:
- Authentication works initially, then fails
A2A::Errors::AuthenticationError: Token expired
Solutions:
- Implement automatic token refresh:
class RefreshableAuth < A2A::Client::Auth::OAuth2
def apply_auth(request)
if token_expired?
refresh_token!
end
super
end
private
def token_expired?
@token_expires_at && Time.current >= @token_expires_at
end
def refresh_token!
@token, @token_expires_at = get_token
end
end- Handle auth errors gracefully:
begin
response = client.send_message(message)
rescue A2A::Errors::AuthenticationError
# Refresh credentials and retry
client.auth.refresh!
response = client.send_message(message)
endSymptoms:
A2A::Errors::InvalidRequest: Invalid JSON-RPC formatA2A::Errors::ParseError: Invalid JSON
Solutions:
- Validate message structure:
message = A2A::Types::Message.new(
message_id: SecureRandom.uuid, # Required
role: "user", # Required: "user" or "agent"
parts: [ # Required: array of parts
A2A::Types::TextPart.new(text: "Hello")
]
)
# Validate before sending
if message.valid?
client.send_message(message)
else
puts "Invalid message: #{message.errors.full_messages}"
end- Check JSON-RPC request format:
# Manual JSON-RPC request (for debugging)
request = {
jsonrpc: "2.0", # Required
method: "message/send", # Required
params: message.to_h, # Optional
id: 1 # Required for requests (omit for notifications)
}Symptoms:
A2A::Errors::MethodNotFound: Method 'xyz' not found
Solutions:
- Check agent card for available methods:
card = client.get_card
puts "Available capabilities:"
card.capabilities.each do |capability|
puts "- #{capability.name}: #{capability.description}"
end- Verify method name spelling:
# Standard A2A methods
valid_methods = [
"message/send",
"message/stream",
"tasks/get",
"tasks/cancel",
"tasks/resubscribe",
"tasks/pushNotificationConfig/set",
"tasks/pushNotificationConfig/get",
"tasks/pushNotificationConfig/list",
"tasks/pushNotificationConfig/delete",
"agent/getAuthenticatedExtendedCard"
]Symptoms:
A2A::Errors::TaskNotFound: Task not foundA2A::Errors::TaskNotCancelable: Task cannot be canceled
Solutions:
- Check task status before operations:
begin
task = client.get_task(task_id)
puts "Task state: #{task.status.state}"
if task.status.state.in?(['submitted', 'working'])
client.cancel_task(task_id)
else
puts "Task cannot be canceled (state: #{task.status.state})"
end
rescue A2A::Errors::TaskNotFound
puts "Task #{task_id} not found"
end- Handle task lifecycle properly:
# Valid task states
CANCELABLE_STATES = ['submitted', 'working'].freeze
FINAL_STATES = ['completed', 'canceled', 'failed', 'rejected'].freeze
def can_cancel_task?(task)
CANCELABLE_STATES.include?(task.status.state)
end
def task_finished?(task)
FINAL_STATES.include?(task.status.state)
endSymptoms:
- Requests taking longer than expected
- High memory usage
Solutions:
- Enable connection pooling:
require 'faraday/net_http_persistent'
client = A2A::Client::HttpClient.new(url) do |conn|
conn.adapter :net_http_persistent
end- Use streaming for large responses:
# Instead of loading everything into memory
client.send_message(message, streaming: true) do |chunk|
process_chunk(chunk) # Process incrementally
end- Monitor performance:
A2A.configure do |config|
config.enable_metrics = true
config.log_level = :info
end
# Add custom timing
start_time = Time.current
response = client.send_message(message)
duration = Time.current - start_time
puts "Request took #{duration}s"Symptoms:
- Increasing memory usage over time
- Out of memory errors
Solutions:
- Properly close streaming connections:
enumerator = client.send_message(message)
begin
enumerator.each do |response|
process_response(response)
end
ensure
enumerator.close if enumerator.respond_to?(:close)
end- Limit message history:
# When getting tasks, limit history
task = client.get_task(task_id, history_length: 10)- Use object pooling for frequent operations:
class MessagePool
def initialize
@pool = []
end
def get_message
@pool.pop || A2A::Types::Message.allocate
end
def return_message(message)
message.reset!
@pool.push(message) if @pool.size < 100
end
endSymptoms:
- 404 errors for A2A endpoints
- Routes not appearing in
rails routes
Solutions:
- Verify engine mounting:
# config/routes.rb
Rails.application.routes.draw do
mount A2A::Engine => "/a2a"
end- Check route generation:
rails routes | grep a2a
# Should show:
# POST /a2a/rpc
# GET /a2a/agent-card
# GET /a2a/capabilities- Verify controller inclusion:
class MyAgentController < ApplicationController
include A2A::Rails::ControllerHelpers # Required
# Your A2A methods here
endSymptoms:
- Task storage errors
- Migration failures
Solutions:
- Run A2A migrations:
rails generate a2a:migration
rails db:migrate- Check database configuration:
# config/initializers/a2a.rb
A2A.configure do |config|
config.storage_backend = :database # or :memory, :redis
config.database_url = ENV['DATABASE_URL']
end- Verify model associations:
# Check if models are properly loaded
A2A::Server::Models::Task.first
A2A::Server::Models::PushNotificationConfig.firstA2A.configure do |config|
config.log_level = :debug
config.log_requests = true
config.log_responses = true
end# Rails console
rails console
# Gem console
cd a2a-ruby && bin/console# Test basic functionality
client = A2A::Client::HttpClient.new("http://localhost:3000/a2a")
card = client.get_card
puts JSON.pretty_generate(card.to_h)# Add request/response logging
require 'faraday/logging'
client = A2A::Client::HttpClient.new(url) do |conn|
conn.response :logger, Rails.logger, bodies: true
endrequire 'ruby-prof'
RubyProf.start
client.send_message(message)
result = RubyProf.stop
# Print results
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)The A2A Ruby SDK provides a comprehensive error hierarchy:
StandardError
└── A2A::Errors::A2AError
├── JSON-RPC Standard Errors
│ ├── A2A::Errors::ParseError (-32700)
│ ├── A2A::Errors::InvalidRequest (-32600)
│ ├── A2A::Errors::MethodNotFound (-32601)
│ ├── A2A::Errors::InvalidParams (-32602)
│ └── A2A::Errors::InternalError (-32603)
├── A2A Protocol Errors
│ ├── A2A::Errors::TaskNotFound (-32001)
│ ├── A2A::Errors::TaskNotCancelable (-32002)
│ └── A2A::Errors::AuthenticationRequired (-32003)
├── Transport Errors
│ ├── A2A::Errors::HTTPError
│ ├── A2A::Errors::TimeoutError
│ └── A2A::Errors::ConnectionError
└── Configuration Errors
└── A2A::Errors::ConfigurationError
begin
response = client.send_message(message)
rescue A2A::Errors::MethodNotFound => e
puts "Method not supported: #{e.message}"
rescue A2A::Errors::AuthenticationRequired => e
puts "Authentication needed: #{e.message}"
rescue A2A::Errors::HTTPError => e
puts "HTTP error: #{e.message}"
rescue A2A::Errors::A2AError => e
puts "A2A error: #{e.message}"
enda2a_method "risky_operation" do |params|
begin
# Your operation
{ success: true }
rescue ArgumentError => e
raise A2A::Errors::InvalidParams, "Invalid parameters: #{e.message}"
rescue StandardError => e
raise A2A::Errors::InternalError, "Operation failed: #{e.message}"
end
end| Code | Error | Description |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid Request | Invalid JSON-RPC format |
| -32601 | Method not found | Method doesn't exist |
| -32602 | Invalid params | Invalid method parameters |
| -32603 | Internal error | Server internal error |
| Code | Error | Description |
|---|---|---|
| -32001 | Task not found | Task ID doesn't exist |
| -32002 | Task not cancelable | Task in non-cancelable state |
| -32003 | Invalid task state | Invalid state transition |
| -32004 | Authentication required | Auth needed for operation |
| -32005 | Insufficient permissions | User lacks required permissions |
| -32006 | Rate limit exceeded | Too many requests |
| -32007 | Invalid agent card | Agent card validation failed |
| -32008 | Transport not supported | Requested transport unavailable |
| -32009 | Invalid message format | Message doesn't match schema |
| -32010 | Service unavailable | Temporary service outage |
- Ruby 2.7 or higher
- Rails 6.0+ (for Rails integration)
- JRuby and TruffleRuby compatibility
No! The SDK works with any Ruby application:
- Plain Ruby scripts
- Sinatra applications
- Rack applications
- Rails applications (with enhanced integration)
require 'a2a'
client = A2A::Client::HttpClient.new("https://agent.example.com/a2a")
message = A2A::Types::Message.new(
message_id: SecureRandom.uuid,
role: "user",
parts: [A2A::Types::TextPart.new(text: "Hello!")]
)
client.send_message(message) do |response|
puts response
end# OAuth 2.0
client = A2A::Client::HttpClient.new("https://agent.example.com/a2a") do |config|
config.auth_strategy = :oauth2
config.oauth2_token = "your_access_token"
end
# API Key
client = A2A::Client::HttpClient.new("https://agent.example.com/a2a") do |config|
config.auth_strategy = :api_key
config.api_key = "your_api_key"
endclass MyAgent
include A2A::Server::Agent
a2a_method "greet" do |params|
{ message: "Hello, #{params[:name] || 'there'}!" }
end
endA: For development with self-signed certificates:
# Disable SSL verification (development only!)
client = A2A::Client::HttpClient.new(url) do |conn|
conn.ssl.verify = false
end
# Or set certificate bundle
client = A2A::Client::HttpClient.new(url) do |conn|
conn.ssl.ca_file = '/path/to/ca-bundle.crt'
endA: Implement reconnection logic:
def stream_with_reconnect(client, message, max_retries: 3)
retries = 0
begin
client.send_message(message) do |response|
yield response
end
rescue A2A::Errors::HTTPError, A2A::Errors::TimeoutError => e
retries += 1
if retries <= max_retries
sleep(2 ** retries) # Exponential backoff
retry
else
raise e
end
end
endA: Use test doubles and helpers:
# spec/support/a2a_helpers.rb
RSpec.configure do |config|
config.include A2AHelpers
end
# In your tests
it "handles A2A messages" do
message = build_a2a_message(text: "test")
client = mock_a2a_client(send_message: mock_response)
result = client.send_message(message)
expect(result).to be_a(A2A::Types::Message)
endA: Yes! The core SDK works with any Ruby application:
# Sinatra example
require 'sinatra'
require 'a2a'
class MyAgent
include A2A::Server::Agent
# Define your methods
end
post '/a2a/rpc' do
agent = MyAgent.new
request_body = request.body.read
json_rpc_request = A2A::Protocol::JsonRpc.parse_request(request_body)
response = agent.handle_a2a_request(json_rpc_request)
content_type :json
response.to_json
endA: Create middleware classes:
class CustomLoggingMiddleware
def initialize(app)
@app = app
end
def call(request)
start_time = Time.current
response = @app.call(request)
duration = Time.current - start_time
Rails.logger.info "A2A Request: #{request.method} (#{duration}s)"
response
end
end
# Add to client
client.add_middleware(CustomLoggingMiddleware)For more help: