simple_a2a 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -0
- data/README.md +78 -38
- data/compare_agent2agent.md +460 -0
- data/docs/api/client/index.md +19 -0
- data/docs/api/index.md +4 -3
- data/docs/api/models/index.md +13 -11
- data/docs/api/server/index.md +42 -10
- data/docs/api/storage/index.md +0 -1
- data/docs/architecture/index.md +17 -15
- data/docs/architecture/protocol.md +16 -1
- data/docs/assets/images/simple_a2a.jpg +0 -0
- data/docs/examples/agent-chaining.md +107 -0
- data/docs/examples/auth-headers.md +105 -0
- data/docs/examples/cancellation.md +105 -0
- data/docs/examples/index.md +123 -52
- data/docs/examples/interrupted-states.md +114 -0
- data/docs/examples/multipart.md +103 -0
- data/docs/examples/push-notifications.md +117 -0
- data/docs/examples/resubscribe.md +129 -0
- data/docs/examples/sqlite-storage.md +131 -0
- data/docs/examples/streaming.md +1 -4
- data/docs/guides/push-notifications.md +4 -1
- data/docs/guides/streaming.md +34 -5
- data/docs/index.md +55 -27
- data/examples/04_resubscribe/client.rb +140 -0
- data/examples/04_resubscribe/server.rb +75 -0
- data/examples/05_cancellation/client.rb +150 -0
- data/examples/05_cancellation/server.rb +77 -0
- data/examples/06_push_notifications/client.rb +192 -0
- data/examples/06_push_notifications/server.rb +123 -0
- data/examples/07_agent_chaining/client.rb +120 -0
- data/examples/07_agent_chaining/server.rb +150 -0
- data/examples/08_interrupted_states/client.rb +148 -0
- data/examples/08_interrupted_states/server.rb +142 -0
- data/examples/09_multipart/client.rb +117 -0
- data/examples/09_multipart/server.rb +97 -0
- data/examples/10_auth_headers/client.rb +92 -0
- data/examples/10_auth_headers/server.rb +98 -0
- data/examples/11_sqlite_storage/Brewfile +1 -0
- data/examples/11_sqlite_storage/Gemfile +9 -0
- data/examples/11_sqlite_storage/client.rb +114 -0
- data/examples/11_sqlite_storage/run +154 -0
- data/examples/11_sqlite_storage/server.rb +131 -0
- data/examples/README.md +384 -0
- data/lib/simple_a2a/client/sse.rb +15 -0
- data/lib/simple_a2a/server/app.rb +131 -45
- data/lib/simple_a2a/server/base.rb +19 -17
- data/lib/simple_a2a/server/broadcast_registry.rb +24 -0
- data/lib/simple_a2a/server/multi_agent.rb +1 -1
- data/lib/simple_a2a/server/push_config_store.rb +29 -0
- data/lib/simple_a2a/server/push_sender.rb +1 -0
- data/lib/simple_a2a/server/task_broadcast.rb +46 -0
- data/lib/simple_a2a/version.rb +1 -1
- metadata +38 -20
- data/lib/simple_a2a/server/event_router.rb +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8200755c9d568b3b1161815a2dfd8df190efe773ae946a00fafcfddd25b1ca4e
|
|
4
|
+
data.tar.gz: a5f112a8138f111d137b1a443c674a309c0ad98c5c6f5b8473e4c5d3af42d316
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39509e60e4665ca244831830f6b9c5cb4e3de384c98c7e5d5ae74ab15bdce5c06fb942fb85a6738a11e2d6080f8ab2c3c6334d0be02f58e04bd1f364a9df1231
|
|
7
|
+
data.tar.gz: d6c85963a895435cffa1b3ec28ad7bc7506d168274ae2281b4e7c590ff0d4b48387cb97a4f249aaf7df01621ade3b8aeaf7e3de14e2e35cfad42625600084635
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-05-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `tasks/resubscribe` — attach an SSE stream to an existing running task (A2A `SubscribeToTask`
|
|
8
|
+
operation). The first event is the current Task snapshot (spec requirement); subsequent events
|
|
9
|
+
mirror those emitted by the executor in real time.
|
|
10
|
+
- `Client::SSE#resubscribe(task_id:, &block)` — client-side counterpart for `tasks/resubscribe`.
|
|
11
|
+
- `Server::TaskBroadcast` — lock-free, fiber-scheduler-aware fan-out broadcaster. Replaces the
|
|
12
|
+
anonymous streaming router and `EventRouter`. Each running task gets one broadcast; each SSE
|
|
13
|
+
subscriber (original or resubscribed) gets its own `RactorQueue`.
|
|
14
|
+
- `Server::BroadcastRegistry` — thread-safe `task_id → TaskBroadcast` map held at the App class
|
|
15
|
+
level. Allows `tasks/resubscribe` and `tasks/cancel` to locate a live broadcast.
|
|
16
|
+
- `tasks/pushNotification/set|get|delete|list` — push notification config CRUD now fully
|
|
17
|
+
implemented. Configs are stored per task ID in a new `Server::PushConfigStore`; `set` validates
|
|
18
|
+
the task exists and that `webhookUrl` is present before storing.
|
|
19
|
+
- `Server::PushConfigStore` — thread-safe in-memory store for `PushNotificationConfig` objects,
|
|
20
|
+
keyed by task ID. One instance is created per `Server::App` automatically.
|
|
21
|
+
- `examples/04_resubscribe` — two concurrent SSE subscribers demonstrating snapshot-on-join,
|
|
22
|
+
independent fan-out, and single-completion guarantees.
|
|
23
|
+
- `examples/05_cancellation` — three concurrent `tasks/sendSubscribe` streams; one task is
|
|
24
|
+
cancelled mid-flight while the others complete normally.
|
|
25
|
+
- `examples/06_push_notifications` — full push notification lifecycle: submit task, register
|
|
26
|
+
webhook, receive out-of-band HTTP POSTs per step, then get/list/delete the config with no
|
|
27
|
+
open SSE connection required.
|
|
28
|
+
- `examples/07_agent_chaining` — three agents (`ReverseAgent`, `ShoutAgent`, `PipelineAgent`)
|
|
29
|
+
on one port via `A2A.multi_server`; the pipeline executor chains to the other two agents over
|
|
30
|
+
the protocol, invisible to the client.
|
|
31
|
+
- `examples/08_interrupted_states` — `input_required` and `auth_required` interrupted states
|
|
32
|
+
demonstrated with `OrderAgent` (pauses for user input) and `VaultAgent` (blocks on wrong
|
|
33
|
+
token); each follow-up turn is a separate `tasks/send` threaded by `message.context_id`.
|
|
34
|
+
- `examples/09_multipart` — all four `Part` types in one artifact: text summary, JSON metadata,
|
|
35
|
+
base64-encoded binary CSV, and URL reference; client uses `text?/json?/raw?/url?` predicates.
|
|
36
|
+
- `examples/10_auth_headers` — `headers:` option on `A2A.client` with a Bearer token middleware;
|
|
37
|
+
agent card discovery is public while RPC calls require `Authorization: Bearer <token>`.
|
|
38
|
+
- `examples/11_sqlite_storage` — persistent task storage across server restarts via
|
|
39
|
+
`SqliteStorage < A2A::Storage::Base` (WAL mode, mutex, JSON blobs); Brewfile + Gemfile pattern
|
|
40
|
+
keeps SQLite dependency out of the main gem.
|
|
41
|
+
- MkDocs documentation pages for examples 05–11 (`cancellation`, `push-notifications`,
|
|
42
|
+
`agent-chaining`, `interrupted-states`, `multipart`, `auth-headers`, `sqlite-storage`).
|
|
43
|
+
- `compare_agent2agent.md` — side-by-side comparison of A2A protocol operations at the repo root.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- `handle_cancel` now routes cancellation events through the live broadcast so SSE subscribers
|
|
48
|
+
observe the `canceled` state update in real time.
|
|
49
|
+
- `Server::Base` and `Server::MultiAgent` no longer create an `EventRouter`; they inject a fresh
|
|
50
|
+
`BroadcastRegistry` into each App subclass instead.
|
|
51
|
+
- `Server::Base` now accepts a `push_config_store:` keyword so the executor and the App share
|
|
52
|
+
the same store instance (fixes injection gap).
|
|
53
|
+
- README Examples section expanded to an 11-row table; `docs/examples/index.md` rebuilt with
|
|
54
|
+
a 3×4 SVG grid covering all demos.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- Removed phantom `simple_flow` runtime dependency from gemspec.
|
|
59
|
+
- Added missing `digest` require (fixes JWT signing in push notification delivery).
|
|
60
|
+
|
|
61
|
+
### Removed
|
|
62
|
+
|
|
63
|
+
- `Server::EventRouter` and its `TypedBus`-based pub/sub — superseded by `TaskBroadcast`.
|
|
64
|
+
- `typed_bus` runtime dependency — removed entirely.
|
|
65
|
+
|
|
66
|
+
### Dependencies
|
|
67
|
+
|
|
68
|
+
- Added `ractor_queue ~> 0.2` runtime dependency (replaces `typed_bus`).
|
|
69
|
+
|
|
3
70
|
## [0.1.0] - 2026-05-07
|
|
4
71
|
|
|
5
72
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,44 +1,62 @@
|
|
|
1
1
|
# simple_a2a
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
<table>
|
|
4
|
+
<tr>
|
|
5
|
+
<td width="40%">
|
|
6
|
+
<img src="docs/assets/images/simple_a2a.jpg" alt="simple_a2a - AI agents communicating" width="100%">
|
|
7
|
+
</td>
|
|
8
|
+
<td width="60%" valign="top">
|
|
9
|
+
<strong>A Ruby gem implementing the <a href="https://a2a-protocol.org/latest/">Agent2Agent (A2A) protocol</a></strong>
|
|
10
|
+
<br><br>
|
|
11
|
+
A complete A2A client and server in a single package, built on the async Ruby ecosystem with Falcon as the recommended HTTP server.
|
|
12
|
+
<ul>
|
|
13
|
+
<li>Full A2A v1.0 protocol support (backward compatible with v0.3)</li>
|
|
14
|
+
<li>JSON-RPC 2.0 over HTTP(S) — primary binding</li>
|
|
15
|
+
<li>Server-Sent Events (SSE) for streaming responses (<code>tasks/sendSubscribe</code> and <code>tasks/resubscribe</code>)</li>
|
|
16
|
+
<li>Push notifications via webhooks (RS256 JWT)</li>
|
|
17
|
+
<li>Task lifecycle management (<code>submitted → working → completed/failed/canceled</code>)</li>
|
|
18
|
+
<li>AgentCard discovery endpoint at <code>GET /agentCard</code></li>
|
|
19
|
+
<li>Multi-agent hosting with path-based routing via <code>A2A.multi_server</code></li>
|
|
20
|
+
<li>Async-first via the <code>async</code> gem ecosystem (Falcon + async-http)</li>
|
|
21
|
+
<li>Lock-free SSE fan-out via <code>RactorQueue</code> — multiple concurrent subscribers per task</li>
|
|
22
|
+
<li>Rack-compatible server with Roda routing</li>
|
|
23
|
+
<li>Zeitwerk autoloading — top-level module is <code>A2A</code></li>
|
|
24
|
+
<li><a href="https://madbomber.github.io/simple_a2a">Full documentation website</a></li>
|
|
25
|
+
</ul>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</table>
|
|
29
|
+
|
|
30
|
+
## MCP vs. A2A
|
|
31
|
+
|
|
32
|
+
Two open protocols address different dimensions of AI agent integration — and they are designed to complement each other.
|
|
33
|
+
|
|
34
|
+
**MCP — Vertical Integration (Agent ↕ Environment)**
|
|
35
|
+
|
|
36
|
+
The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP), introduced by Anthropic in November 2024, defines how an AI agent connects to the tools, data sources, and services in its environment — file systems, databases, APIs, browsers, and code execution engines. MCP uses a client-server model where the agent is the client and each external capability is a server. This is *vertical* integration: the agent reaches downward into its local context and outward into external services through a uniform interface.
|
|
37
|
+
|
|
38
|
+
**A2A — Horizontal Integration (Agent ↔ Agent)**
|
|
39
|
+
|
|
40
|
+
The [Agent2Agent Protocol](https://a2a-protocol.org/latest/) (A2A), introduced by Google in April 2025 and donated to the Linux Foundation for vendor-neutral governance, defines how autonomous agents running on different platforms, frameworks, and vendors can discover one another, delegate tasks, and stream results in real time. This is *horizontal* integration: peer agents — each with its own specialization, runtime, and vendor — collaborate as equals across organizational and technology boundaries.
|
|
41
|
+
|
|
42
|
+
**Together**
|
|
43
|
+
|
|
44
|
+
MCP and A2A are complementary. A single agent can use MCP to access its tools and A2A to delegate subtasks to peer agents. `simple_a2a` implements the A2A layer.
|
|
10
45
|
|
|
11
46
|
## Lineage
|
|
12
47
|
|
|
13
48
|
`simple_a2a` is the successor to my earlier Ruby gem, `simple_acp`. That gem implemented the Agent Communication Protocol (ACP), which IBM Research introduced through the BeeAI project for interoperable agent communication. ACP later merged into A2A under the Linux Foundation, with the ACP team contributing its technology and expertise to the A2A effort.
|
|
14
49
|
|
|
15
|
-
A2A itself was created by Google and then donated to the Linux Foundation for neutral, open governance. The current A2A specification is maintained by the Linux Foundation-hosted [Agent2Agent project](https://github.com/a2aproject/A2A) and published at [a2a-protocol.org](https://a2a-protocol.org/latest/).
|
|
16
|
-
|
|
17
50
|
> My opinion: the A2A specification is still a little jagged in places. A simple example is that it does not clearly cover whether an A2A server is expected to host only one agent or may host multiple agents. That is a minor example, but it points to the kind of operational detail the specification still needs to tighten up.
|
|
18
51
|
|
|
19
52
|
References:
|
|
20
53
|
|
|
54
|
+
- Anthropic: [Introducing the Model Context Protocol](https://www.anthropic.com/news/model-context-protocol) (November 2024)
|
|
55
|
+
- Google Developers Blog: [A2A: A new era of agent interoperability](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/) (April 2025)
|
|
21
56
|
- IBM Research: [Agent Communication Protocol](https://research.ibm.com/projects/agent-communication-protocol)
|
|
22
57
|
- BeeAI announcement: [ACP Joins Forces with A2A Under the Linux Foundation](https://github.com/orgs/i-am-bee/discussions/5)
|
|
23
58
|
- Linux Foundation: [Launch of the Agent2Agent Protocol Project](https://www.linuxfoundation.org/press/linux-foundation-launches-the-agent2agent-protocol-project-to-enable-secure-intelligent-communication-between-ai-agents)
|
|
24
|
-
|
|
25
|
-
## Protocol Reference
|
|
26
|
-
|
|
27
|
-
- **Official A2A Specification:** [https://a2a-protocol.org/latest/](https://a2a-protocol.org/latest/)
|
|
28
|
-
- **A2A Project on GitHub:** [https://github.com/a2aproject/A2A](https://github.com/a2aproject/A2A)
|
|
29
|
-
|
|
30
|
-
## Features
|
|
31
|
-
|
|
32
|
-
- Full A2A v1.0 protocol support (backward compatible with v0.3)
|
|
33
|
-
- JSON-RPC 2.0 over HTTP(S) — primary binding
|
|
34
|
-
- Server-Sent Events (SSE) for streaming responses
|
|
35
|
-
- Push notifications via webhooks (RS256 JWT)
|
|
36
|
-
- Task lifecycle management (`submitted → working → completed/failed/canceled`)
|
|
37
|
-
- AgentCard discovery endpoint at `GET /agentCard`
|
|
38
|
-
- Multi-agent hosting with path-based routing via `A2A.multi_server`
|
|
39
|
-
- Async-first via the `async` gem ecosystem (Falcon + async-http)
|
|
40
|
-
- Rack-compatible server with Roda routing
|
|
41
|
-
- Zeitwerk autoloading — top-level module is `A2A`
|
|
59
|
+
- A2A Project on GitHub: [https://github.com/a2aproject/A2A](https://github.com/a2aproject/A2A)
|
|
42
60
|
|
|
43
61
|
## Installation
|
|
44
62
|
|
|
@@ -128,7 +146,7 @@ end
|
|
|
128
146
|
```
|
|
129
147
|
|
|
130
148
|
```ruby
|
|
131
|
-
# Client —
|
|
149
|
+
# Client — start a streaming task
|
|
132
150
|
client = A2A.sse_client(url: "http://localhost:9292")
|
|
133
151
|
|
|
134
152
|
client.send_subscribe(message: A2A::Models::Message.user("go")) do |event|
|
|
@@ -141,6 +159,19 @@ client.send_subscribe(message: A2A::Models::Message.user("go")) do |event|
|
|
|
141
159
|
end
|
|
142
160
|
```
|
|
143
161
|
|
|
162
|
+
```ruby
|
|
163
|
+
# Client — reattach to an already-running task
|
|
164
|
+
# First event is the current Task snapshot; subsequent events are the live stream.
|
|
165
|
+
client.resubscribe(task_id: "existing-task-id") do |event|
|
|
166
|
+
case event
|
|
167
|
+
when Hash
|
|
168
|
+
puts "task snapshot: #{event['status']['state']}"
|
|
169
|
+
when A2A::Models::TaskStatusUpdateEvent
|
|
170
|
+
puts "status: #{event.status.state} (final=#{event.final})"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
144
175
|
## Multi-agent server
|
|
145
176
|
|
|
146
177
|
Use `A2A.multi_server` to host multiple A2A agents in one Falcon process, with each agent mounted at its own path:
|
|
@@ -155,26 +186,35 @@ A2A.multi_server(
|
|
|
155
186
|
).run
|
|
156
187
|
```
|
|
157
188
|
|
|
158
|
-
Each mounted agent has its own AgentCard, executor, storage, and
|
|
189
|
+
Each mounted agent has its own AgentCard, executor, storage, and broadcast registry.
|
|
159
190
|
|
|
160
191
|
## Examples
|
|
161
192
|
|
|
162
|
-
The repository includes
|
|
193
|
+
The repository includes eleven runnable demo applications:
|
|
163
194
|
|
|
164
|
-
| Demo |
|
|
165
|
-
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
195
|
+
| # | Demo | What it shows |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| 01 | Basic Usage | Agent discovery, `tasks/send`, task listing, task lookup, error handling |
|
|
198
|
+
| 02 | Streaming | `tasks/sendSubscribe`, SSE, incremental artifact chunks |
|
|
199
|
+
| 03 | LLM Research | `multi_server`, parallel SSE agents, evaluator pattern, web client |
|
|
200
|
+
| 04 | Resubscribe | `tasks/resubscribe`, concurrent SSE subscribers, mid-stream join |
|
|
201
|
+
| 05 | Cancellation | `tasks/cancel`, concurrent tasks, cooperative cancellation |
|
|
202
|
+
| 06 | Push Notifications | `tasks/pushNotification` CRUD, webhook delivery, out-of-band events |
|
|
203
|
+
| 07 | Agent Chaining | `A2A.client` inside an executor, agent-to-agent delegation |
|
|
204
|
+
| 08 | Interrupted States | `input_required`, `auth_required`, multi-turn `context_id` threading |
|
|
205
|
+
| 09 | Multipart Artifacts | `Part.text`, `Part.json`, `Part.binary`, `Part.from_url`, predicates |
|
|
206
|
+
| 10 | Auth Headers | `A2A.client(headers:)`, Bearer token, Rack middleware composition |
|
|
207
|
+
| 11 | SQLite Storage | `Storage::Base` injection, WAL persistence, cross-restart survival |
|
|
169
208
|
|
|
170
|
-
Run
|
|
209
|
+
Run any demo end-to-end from the repository root:
|
|
171
210
|
|
|
172
211
|
```bash
|
|
173
212
|
bundle exec ruby examples/run 01_basic_usage
|
|
174
|
-
bundle exec ruby examples/run
|
|
213
|
+
bundle exec ruby examples/run 05_cancellation
|
|
214
|
+
bundle exec ruby examples/run 11_sqlite_storage
|
|
175
215
|
```
|
|
176
216
|
|
|
177
|
-
|
|
217
|
+
Demo 03 requires `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and additional gems (`ruby_llm`, `async-http-faraday`, `sinatra`). Demo 11 has its own `Gemfile` and `Brewfile`; its `run` script handles setup automatically. See [`examples/README.md`](examples/README.md) for full details on each demo.
|
|
178
218
|
|
|
179
219
|
## Development
|
|
180
220
|
|