simple_acp 0.0.1

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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/CHANGELOG.md +5 -0
  4. data/COMMITS.md +196 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +385 -0
  7. data/Rakefile +13 -0
  8. data/docs/api/client-base.md +383 -0
  9. data/docs/api/index.md +159 -0
  10. data/docs/api/models.md +286 -0
  11. data/docs/api/server-base.md +379 -0
  12. data/docs/api/storage.md +347 -0
  13. data/docs/assets/images/simple_acp.jpg +0 -0
  14. data/docs/client/index.md +279 -0
  15. data/docs/client/sessions.md +324 -0
  16. data/docs/client/streaming.md +345 -0
  17. data/docs/client/sync-async.md +308 -0
  18. data/docs/core-concepts/agents.md +253 -0
  19. data/docs/core-concepts/events.md +337 -0
  20. data/docs/core-concepts/index.md +147 -0
  21. data/docs/core-concepts/messages.md +211 -0
  22. data/docs/core-concepts/runs.md +278 -0
  23. data/docs/core-concepts/sessions.md +281 -0
  24. data/docs/examples.md +659 -0
  25. data/docs/getting-started/configuration.md +166 -0
  26. data/docs/getting-started/index.md +62 -0
  27. data/docs/getting-started/installation.md +95 -0
  28. data/docs/getting-started/quick-start.md +189 -0
  29. data/docs/index.md +119 -0
  30. data/docs/server/creating-agents.md +360 -0
  31. data/docs/server/http-endpoints.md +411 -0
  32. data/docs/server/index.md +218 -0
  33. data/docs/server/multi-turn.md +329 -0
  34. data/docs/server/streaming.md +315 -0
  35. data/docs/storage/custom.md +414 -0
  36. data/docs/storage/index.md +176 -0
  37. data/docs/storage/memory.md +198 -0
  38. data/docs/storage/postgresql.md +350 -0
  39. data/docs/storage/redis.md +287 -0
  40. data/examples/01_basic/client.rb +88 -0
  41. data/examples/01_basic/server.rb +100 -0
  42. data/examples/02_async_execution/client.rb +107 -0
  43. data/examples/02_async_execution/server.rb +56 -0
  44. data/examples/03_run_management/client.rb +115 -0
  45. data/examples/03_run_management/server.rb +84 -0
  46. data/examples/04_rich_messages/client.rb +160 -0
  47. data/examples/04_rich_messages/server.rb +180 -0
  48. data/examples/05_await_resume/client.rb +164 -0
  49. data/examples/05_await_resume/server.rb +114 -0
  50. data/examples/06_agent_metadata/client.rb +188 -0
  51. data/examples/06_agent_metadata/server.rb +192 -0
  52. data/examples/README.md +252 -0
  53. data/examples/run_demo.sh +137 -0
  54. data/lib/simple_acp/client/base.rb +448 -0
  55. data/lib/simple_acp/client/sse.rb +141 -0
  56. data/lib/simple_acp/models/agent_manifest.rb +129 -0
  57. data/lib/simple_acp/models/await.rb +123 -0
  58. data/lib/simple_acp/models/base.rb +147 -0
  59. data/lib/simple_acp/models/errors.rb +102 -0
  60. data/lib/simple_acp/models/events.rb +256 -0
  61. data/lib/simple_acp/models/message.rb +235 -0
  62. data/lib/simple_acp/models/message_part.rb +225 -0
  63. data/lib/simple_acp/models/metadata.rb +161 -0
  64. data/lib/simple_acp/models/run.rb +298 -0
  65. data/lib/simple_acp/models/session.rb +137 -0
  66. data/lib/simple_acp/models/types.rb +210 -0
  67. data/lib/simple_acp/server/agent.rb +116 -0
  68. data/lib/simple_acp/server/app.rb +264 -0
  69. data/lib/simple_acp/server/base.rb +510 -0
  70. data/lib/simple_acp/server/context.rb +210 -0
  71. data/lib/simple_acp/server/falcon_runner.rb +61 -0
  72. data/lib/simple_acp/storage/base.rb +129 -0
  73. data/lib/simple_acp/storage/memory.rb +108 -0
  74. data/lib/simple_acp/storage/postgresql.rb +233 -0
  75. data/lib/simple_acp/storage/redis.rb +178 -0
  76. data/lib/simple_acp/version.rb +5 -0
  77. data/lib/simple_acp.rb +91 -0
  78. data/mkdocs.yml +152 -0
  79. data/sig/simple_acp.rbs +4 -0
  80. metadata +418 -0
data/docs/examples.md ADDED
@@ -0,0 +1,659 @@
1
+ # Examples
2
+
3
+ !!! warning "CAUTION"
4
+ This project is under active development. The API and documentation may not necessarily reflect the current codebase.
5
+
6
+ This section provides detailed documentation for the example programs demonstrating SimpleAcp features. Each example is organized in its own subdirectory with both `server.rb` and `client.rb` files.
7
+
8
+ ## Quick Start
9
+
10
+ Use the unified `run_demo.sh` script to run any demo:
11
+
12
+ ```bash
13
+ # From the project root
14
+ ./examples/run_demo.sh 1 # Run demo 1 (01_basic)
15
+ ./examples/run_demo.sh 4 # Run demo 4 (04_rich_messages)
16
+
17
+ # Or from the examples directory
18
+ cd examples
19
+ ./run_demo.sh 1
20
+ ```
21
+
22
+ ### Available Commands
23
+
24
+ | Command | Description |
25
+ |---------|-------------|
26
+ | `./run_demo.sh <1-6>` | Run a specific demo by number |
27
+ | `./run_demo.sh --list` | List all available demos |
28
+ | `./run_demo.sh --all` | Run all demos sequentially |
29
+ | `./run_demo.sh --help` | Show usage information |
30
+
31
+ ---
32
+
33
+ ## 01_basic - Core Functionality
34
+
35
+ **Location:** [`examples/01_basic/`](https://github.com/MadBomber/simple_acp/tree/main/examples/01_basic)
36
+
37
+ This example demonstrates the fundamental features of SimpleAcp including agent registration, synchronous execution, streaming responses, and session-based state management.
38
+
39
+ ### Server Agents
40
+
41
+ | Agent | Description | Key Features |
42
+ |-------|-------------|--------------|
43
+ | `echo` | Echoes input messages back | Streaming with Enumerator, multiple message parts |
44
+ | `greeter` | Greets the user by name | Simple single-response pattern |
45
+ | `counter` | Counts invocations | Session state persistence with `context.state` and `context.set_state` |
46
+ | `gettysburg` | Recites the Gettysburg Address word by word | Streaming with timed delays, fiber-aware sleep |
47
+ | `assistant` | Remembers conversation history | Multi-turn conversations, `context.history` access |
48
+
49
+ ### Demonstrated Capabilities
50
+
51
+ **Agent Registration:**
52
+ ```ruby
53
+ server = SimpleAcp::Server::Base.new
54
+
55
+ server.agent("greeter", description: "Greets the user by name") do |context|
56
+ name = context.input.first&.text_content&.strip
57
+ name = "World" if name.nil? || name.empty?
58
+ SimpleAcp::Models::Message.agent("Hello, #{name}! Welcome to SimpleAcp.")
59
+ end
60
+ ```
61
+
62
+ **Streaming Responses with Enumerator:**
63
+ ```ruby
64
+ server.agent("echo", description: "Echoes everything you send") do |context|
65
+ Enumerator.new do |yielder|
66
+ context.input.each do |message|
67
+ yielder << SimpleAcp::Server::RunYield.new(
68
+ SimpleAcp::Models::Message.agent(message.text_content)
69
+ )
70
+ end
71
+ end
72
+ end
73
+ ```
74
+
75
+ **Session State Management:**
76
+ ```ruby
77
+ server.agent("counter", description: "Counts how many times you've called it") do |context|
78
+ count = (context.state || 0) + 1
79
+ context.set_state(count)
80
+ SimpleAcp::Models::Message.agent("You have called me #{count} time(s).")
81
+ end
82
+ ```
83
+
84
+ ### Client Operations
85
+
86
+ - Health check with `client.ping`
87
+ - Listing available agents with `client.agents`
88
+ - Synchronous runs with `client.run_sync`
89
+ - Streaming responses with `client.run_stream`
90
+ - Session management with `client.use_session` and `client.clear_session`
91
+
92
+ ```ruby
93
+ client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
94
+
95
+ # Synchronous execution
96
+ run = client.run_sync(agent: "greeter", input: "Ruby Developer")
97
+ puts run.output.first.text_content
98
+ # => "Hello, Ruby Developer! Welcome to SimpleAcp."
99
+
100
+ # Streaming execution
101
+ client.run_stream(agent: "gettysburg", input: "recite") do |event|
102
+ case event
103
+ when SimpleAcp::Models::MessagePartEvent
104
+ print event.part.content
105
+ when SimpleAcp::Models::RunCompletedEvent
106
+ puts "\n[Streaming complete]"
107
+ end
108
+ end
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 02_async_execution - Non-Blocking Execution
114
+
115
+ **Location:** [`examples/02_async_execution/`](https://github.com/MadBomber/simple_acp/tree/main/examples/02_async_execution)
116
+
117
+ This example demonstrates asynchronous (non-blocking) execution patterns for handling long-running tasks without blocking the client.
118
+
119
+ ### Server Agents
120
+
121
+ | Agent | Description | Key Features |
122
+ |-------|-------------|--------------|
123
+ | `slow-worker` | Simulates a 3-second task | Progress updates, streaming with timed iterations |
124
+ | `quick-status` | Returns status immediately | Instant response for comparison |
125
+
126
+ ### Demonstrated Capabilities
127
+
128
+ **Async Execution with Manual Polling:**
129
+ ```ruby
130
+ # Start task asynchronously
131
+ run = client.run_async(agent: "slow-worker", input: "data analysis")
132
+ puts "Run started with ID: #{run.run_id}"
133
+
134
+ # Poll for status
135
+ loop do
136
+ status = client.run_status(run.run_id)
137
+ puts "Status: #{status.status}"
138
+ break if status.terminal?
139
+ sleep(0.5)
140
+ end
141
+ ```
142
+
143
+ **Simplified Waiting with `wait_for_run`:**
144
+ ```ruby
145
+ run = client.run_async(agent: "slow-worker", input: "report generation")
146
+ completed_run = client.wait_for_run(run.run_id, timeout: 10)
147
+
148
+ if completed_run.status == "completed"
149
+ puts "Success!"
150
+ completed_run.output.each { |msg| puts msg.text_content }
151
+ end
152
+ ```
153
+
154
+ **Concurrent Task Execution:**
155
+ ```ruby
156
+ # Start multiple tasks concurrently
157
+ runs = %w[task_alpha task_beta task_gamma].map do |task_name|
158
+ client.run_async(agent: "slow-worker", input: task_name)
159
+ end
160
+
161
+ # Wait for all to complete
162
+ runs.each do |run|
163
+ completed = client.wait_for_run(run.run_id, timeout: 15)
164
+ puts "Completed: #{completed.output.last&.text_content}"
165
+ end
166
+ # All 3 tasks complete in ~3s (concurrent), not 9s (sequential)
167
+ ```
168
+
169
+ ### Key API Methods
170
+
171
+ | Method | Description |
172
+ |--------|-------------|
173
+ | `run_async` | Starts a run without blocking, returns immediately with run_id |
174
+ | `run_status` | Retrieves current status of a run by ID |
175
+ | `wait_for_run` | Blocks until run completes or timeout expires |
176
+ | `terminal?` | Checks if run status is terminal (completed, failed, cancelled) |
177
+
178
+ ---
179
+
180
+ ## 03_run_management - Lifecycle Control
181
+
182
+ **Location:** [`examples/03_run_management/`](https://github.com/MadBomber/simple_acp/tree/main/examples/03_run_management)
183
+
184
+ This example demonstrates run lifecycle management including cancellation and event history retrieval.
185
+
186
+ ### Server Agents
187
+
188
+ | Agent | Description | Key Features |
189
+ |-------|-------------|--------------|
190
+ | `cancellable-task` | A long-running task (10 iterations) | Cancellation checking with `context.cancelled?` |
191
+ | `event-generator` | Generates multiple events | Event history tracking, configurable event count |
192
+
193
+ ### Demonstrated Capabilities
194
+
195
+ **Run Cancellation:**
196
+ ```ruby
197
+ # Server-side: Check for cancellation
198
+ server.agent("cancellable-task", description: "A cancellable task") do |context|
199
+ Enumerator.new do |yielder|
200
+ 10.times do |i|
201
+ if context.cancelled?
202
+ yielder << SimpleAcp::Server::RunYield.new(
203
+ SimpleAcp::Models::Message.agent("Task cancelled at iteration #{i + 1}")
204
+ )
205
+ break
206
+ end
207
+ # ... perform work ...
208
+ end
209
+ end
210
+ end
211
+
212
+ # Client-side: Cancel a running task
213
+ run = client.run_async(agent: "cancellable-task", input: "data processing")
214
+ sleep(1.5) # Let it run for a bit
215
+ cancelled_run = client.run_cancel(run.run_id)
216
+ puts "Status: #{cancelled_run.status}" # => "cancelled"
217
+ ```
218
+
219
+ **Event History Retrieval:**
220
+ ```ruby
221
+ run = client.run_sync(agent: "event-generator", input: "8")
222
+
223
+ # Get all events
224
+ events = client.run_events(run.run_id)
225
+ puts "Total events: #{events.length}"
226
+
227
+ # Group by event type
228
+ event_counts = events.group_by { |e| e.class.name.split("::").last }
229
+ event_counts.each { |type, evts| puts "#{type}: #{evts.length}" }
230
+ ```
231
+
232
+ **Event Pagination:**
233
+ ```ruby
234
+ # Page through events
235
+ page1 = client.run_events(run.run_id, limit: 5, offset: 0)
236
+ page2 = client.run_events(run.run_id, limit: 5, offset: 5)
237
+ page3 = client.run_events(run.run_id, limit: 5, offset: 10)
238
+ ```
239
+
240
+ ### Run States
241
+
242
+ | Status | Description |
243
+ |--------|-------------|
244
+ | `created` | Run has been created but not started |
245
+ | `in_progress` | Run is currently executing |
246
+ | `completed` | Run finished successfully |
247
+ | `failed` | Run encountered an error |
248
+ | `cancelled` | Run was cancelled by client |
249
+ | `awaiting` | Run is paused waiting for client input |
250
+
251
+ ---
252
+
253
+ ## 04_rich_messages - Content Types
254
+
255
+ **Location:** [`examples/04_rich_messages/`](https://github.com/MadBomber/simple_acp/tree/main/examples/04_rich_messages)
256
+
257
+ This example demonstrates different message part types and content negotiation for multimodal agent responses.
258
+
259
+ ### Server Agents
260
+
261
+ | Agent | Description | Output Types |
262
+ |-------|-------------|--------------|
263
+ | `json-data` | Returns structured JSON data | `application/json`, `text/plain` |
264
+ | `image-generator` | Generates SVG images as base64 | `image/svg+xml`, `text/plain` |
265
+ | `link-provider` | Returns URL references with metadata | `text/uri-list`, `text/plain` |
266
+ | `multi-format` | Returns data in multiple formats | `application/json`, `text/plain`, `text/html` |
267
+
268
+ ### Demonstrated Capabilities
269
+
270
+ **JSON Message Parts:**
271
+ ```ruby
272
+ server.agent("json-data",
273
+ description: "Returns structured JSON data",
274
+ output_content_types: ["application/json", "text/plain"]
275
+ ) do |context|
276
+ data = {
277
+ query: "users",
278
+ count: 3,
279
+ results: [
280
+ { id: 1, name: "Alice", role: "admin" },
281
+ { id: 2, name: "Bob", role: "user" }
282
+ ]
283
+ }
284
+
285
+ SimpleAcp::Models::Message.new(
286
+ role: "agent",
287
+ parts: [
288
+ SimpleAcp::Models::MessagePart.json(data),
289
+ SimpleAcp::Models::MessagePart.text("Query returned #{data[:count]} results")
290
+ ]
291
+ )
292
+ end
293
+ ```
294
+
295
+ **Base64 Encoded Binary Data:**
296
+ ```ruby
297
+ server.agent("image-generator",
298
+ description: "Generates a simple SVG image",
299
+ output_content_types: ["image/svg+xml", "text/plain"]
300
+ ) do |context|
301
+ color = context.input.first&.text_content || "blue"
302
+
303
+ svg = <<~SVG
304
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
305
+ <rect width="100" height="100" fill="transparent"/>
306
+ <circle cx="50" cy="50" r="40" fill="#{color}" opacity="0.8"/>
307
+ </svg>
308
+ SVG
309
+
310
+ encoded = Base64.strict_encode64(svg)
311
+
312
+ SimpleAcp::Models::Message.new(
313
+ role: "agent",
314
+ parts: [
315
+ SimpleAcp::Models::MessagePart.new(
316
+ kind: "data",
317
+ content: encoded,
318
+ content_type: "image/svg+xml"
319
+ )
320
+ ]
321
+ )
322
+ end
323
+ ```
324
+
325
+ **URL References with Metadata:**
326
+ ```ruby
327
+ # Message part with metadata
328
+ SimpleAcp::Models::MessagePart.new(
329
+ kind: "text",
330
+ content: "Ruby Official Site: https://www.ruby-lang.org/",
331
+ content_type: "text/plain",
332
+ metadata: { url: "https://www.ruby-lang.org/", title: "Ruby Official Site" }
333
+ )
334
+ ```
335
+
336
+ **Client-Side Content Type Handling:**
337
+ ```ruby
338
+ run = client.run_sync(agent: "json-data", input: "users")
339
+
340
+ run.output.each do |message|
341
+ message.parts.each do |part|
342
+ case part.content_type
343
+ when "application/json"
344
+ data = JSON.parse(part.content)
345
+ puts "Query: #{data['query']}, Count: #{data['count']}"
346
+ when "text/plain"
347
+ puts part.content
348
+ when "image/svg+xml"
349
+ svg = Base64.decode64(part.content)
350
+ puts "SVG received: #{svg.length} bytes"
351
+ end
352
+ end
353
+ end
354
+ ```
355
+
356
+ ### Message Part Types
357
+
358
+ | Kind | Content Type | Usage |
359
+ |------|--------------|-------|
360
+ | `text` | `text/plain` | Plain text content |
361
+ | `text` | `text/html` | HTML formatted content |
362
+ | `text` | `text/markdown` | Markdown content |
363
+ | `data` | `application/json` | Structured JSON data |
364
+ | `data` | `image/*` | Base64 encoded images |
365
+ | `text` | `text/uri-list` | URL references |
366
+
367
+ ---
368
+
369
+ ## 05_await_resume - Interactive Flows
370
+
371
+ **Location:** [`examples/05_await_resume/`](https://github.com/MadBomber/simple_acp/tree/main/examples/05_await_resume)
372
+
373
+ This example demonstrates the await/resume pattern for building interactive multi-step agent flows that pause execution to request input from the client.
374
+
375
+ ### Server Agents
376
+
377
+ | Agent | Description | Key Features |
378
+ |-------|-------------|--------------|
379
+ | `greeter` | Asks for your name, then greets you | Single await/resume cycle |
380
+ | `survey` | A multi-step survey | Multiple awaits with state tracking |
381
+ | `confirmer` | Asks for confirmation before action | Yes/no confirmation pattern |
382
+
383
+ ### Demonstrated Capabilities
384
+
385
+ **Simple Await/Resume:**
386
+ ```ruby
387
+ server.agent("greeter", description: "Asks for your name and greets you") do |context|
388
+ if context.resume_message
389
+ # Resume: we have the client's response
390
+ name = context.resume_message.text_content
391
+ SimpleAcp::Models::Message.agent("Hello, #{name}! Nice to meet you!")
392
+ else
393
+ # Initial call: ask for input
394
+ context.await_message(
395
+ SimpleAcp::Models::Message.agent("What is your name?")
396
+ )
397
+ end
398
+ end
399
+ ```
400
+
401
+ **Multi-Step Wizard with State:**
402
+ ```ruby
403
+ server.agent("survey", description: "A multi-step survey") do |context|
404
+ survey_state = context.state || { step: 0, answers: {} }
405
+ step = survey_state[:step]
406
+
407
+ case step
408
+ when 0
409
+ context.set_state({ step: 1, answers: {} })
410
+ context.await_message(
411
+ SimpleAcp::Models::Message.agent("Question 1: What is your name?")
412
+ )
413
+ when 1
414
+ answers = survey_state[:answers].merge("name" => context.resume_message&.text_content)
415
+ context.set_state({ step: 2, answers: answers })
416
+ context.await_message(
417
+ SimpleAcp::Models::Message.agent("Question 2: What is your favorite color?")
418
+ )
419
+ when 2
420
+ # Final step: return summary
421
+ answers = survey_state[:answers].merge("color" => context.resume_message&.text_content)
422
+ SimpleAcp::Models::Message.agent("Survey complete! Answers: #{answers}")
423
+ end
424
+ end
425
+ ```
426
+
427
+ **Confirmation Pattern:**
428
+ ```ruby
429
+ server.agent("confirmer", description: "Performs action after confirmation") do |context|
430
+ action = context.input.first&.text_content
431
+ state = context.state || { confirmed: nil }
432
+
433
+ if state[:confirmed].nil?
434
+ context.set_state({ action: action, confirmed: false })
435
+ context.await_message(
436
+ SimpleAcp::Models::Message.agent("Are you sure you want to '#{action}'? (yes/no)")
437
+ )
438
+ else
439
+ response = context.resume_message&.text_content&.downcase&.strip
440
+ if %w[yes y sure ok].include?(response)
441
+ SimpleAcp::Models::Message.agent("Action '#{state[:action]}' confirmed and executed!")
442
+ else
443
+ SimpleAcp::Models::Message.agent("Action '#{state[:action]}' cancelled.")
444
+ end
445
+ end
446
+ end
447
+ ```
448
+
449
+ **Client-Side Await Handling:**
450
+ ```ruby
451
+ # Detect awaiting status and resume
452
+ run = client.run_sync(agent: "greeter", input: "start")
453
+
454
+ if run.status == "awaiting"
455
+ puts "Agent asks: #{run.await_request&.message&.text_content}"
456
+
457
+ resume = SimpleAcp::Models::MessageAwaitResume.new(
458
+ message: SimpleAcp::Models::Message.user("Alice")
459
+ )
460
+
461
+ completed_run = client.run_resume_sync(run_id: run.run_id, await_resume: resume)
462
+ puts "Agent says: #{completed_run.output.last&.text_content}"
463
+ end
464
+ ```
465
+
466
+ **Streaming Resume:**
467
+ ```ruby
468
+ client.run_resume_stream(run_id: run.run_id, await_resume: resume) do |event|
469
+ case event
470
+ when SimpleAcp::Models::RunInProgressEvent
471
+ print "[in_progress] "
472
+ when SimpleAcp::Models::RunCompletedEvent
473
+ puts "Final: #{event.run.output.last&.text_content}"
474
+ end
475
+ end
476
+ ```
477
+
478
+ ### Key API Methods
479
+
480
+ | Method | Description |
481
+ |--------|-------------|
482
+ | `context.await_message` | Pauses execution and requests client input |
483
+ | `context.resume_message` | Access the client's response when resuming |
484
+ | `run.await_request` | Get the await request details from a paused run |
485
+ | `run_resume_sync` | Resume a paused run synchronously |
486
+ | `run_resume_stream` | Resume a paused run with streaming response |
487
+
488
+ ---
489
+
490
+ ## 06_agent_metadata - Rich Metadata
491
+
492
+ **Location:** [`examples/06_agent_metadata/`](https://github.com/MadBomber/simple_acp/tree/main/examples/06_agent_metadata)
493
+
494
+ This example demonstrates rich agent metadata including author information, capabilities, documentation, and content type negotiation.
495
+
496
+ ### Server Agents
497
+
498
+ | Agent | Description | Metadata Level |
499
+ |-------|-------------|----------------|
500
+ | `text-analyzer` | Analyzes text for sentiment, keywords, summaries | Full metadata (author, capabilities, links, dependencies) |
501
+ | `simple-echo` | A simple echo agent | Minimal metadata |
502
+ | `json-processor` | Processes JSON input/output | Partial metadata (domains, tags) |
503
+
504
+ ### Demonstrated Capabilities
505
+
506
+ **Full Agent Metadata:**
507
+ ```ruby
508
+ metadata = SimpleAcp::Models::Metadata.new(
509
+ documentation: "A comprehensive text analysis agent...",
510
+ license: "MIT",
511
+ programming_language: "ruby",
512
+ natural_languages: ["en", "es", "fr"],
513
+ framework: "SimpleAcp",
514
+ created_at: "2024-01-15T10:00:00Z",
515
+ updated_at: "2024-06-20T14:30:00Z",
516
+
517
+ author: SimpleAcp::Models::Author.new(
518
+ name: "Alice Developer",
519
+ email: "alice@example.com",
520
+ url: "https://github.com/alicedev"
521
+ ),
522
+
523
+ contributors: [
524
+ SimpleAcp::Models::Contributor.new(name: "Bob Contributor", email: "bob@example.com"),
525
+ SimpleAcp::Models::Contributor.new(name: "Charlie Helper", url: "https://github.com/charliehelper")
526
+ ],
527
+
528
+ capabilities: [
529
+ SimpleAcp::Models::Capability.new(name: "summarize", description: "Summarize long text"),
530
+ SimpleAcp::Models::Capability.new(name: "sentiment", description: "Analyze sentiment"),
531
+ SimpleAcp::Models::Capability.new(name: "keywords", description: "Extract keywords")
532
+ ],
533
+
534
+ domains: ["nlp", "text-analysis", "ai"],
535
+ tags: ["text", "analysis", "nlp", "summarization"],
536
+
537
+ links: [
538
+ SimpleAcp::Models::Link.new(type: "documentation", url: "https://example.com/docs"),
539
+ SimpleAcp::Models::Link.new(type: "source-code", url: "https://github.com/example/repo"),
540
+ SimpleAcp::Models::Link.new(type: "homepage", url: "https://example.com")
541
+ ],
542
+
543
+ dependencies: [
544
+ SimpleAcp::Models::Dependency.new(type: "agent", name: "tokenizer"),
545
+ SimpleAcp::Models::Dependency.new(type: "model", name: "gpt-4")
546
+ ],
547
+
548
+ recommended_models: ["gpt-4", "claude-3", "llama-2"]
549
+ )
550
+
551
+ server.agent("text-analyzer",
552
+ description: "Analyzes text for sentiment, keywords, and provides summaries",
553
+ input_content_types: ["text/plain", "text/markdown", "application/json"],
554
+ output_content_types: ["application/json", "text/plain"],
555
+ metadata: metadata
556
+ ) do |context|
557
+ # Agent implementation...
558
+ end
559
+ ```
560
+
561
+ **Client-Side Metadata Inspection:**
562
+ ```ruby
563
+ agent = client.agent("text-analyzer")
564
+
565
+ puts "Agent: #{agent.name}"
566
+ puts "Description: #{agent.description}"
567
+
568
+ if agent.metadata
569
+ meta = agent.metadata
570
+
571
+ puts "Author: #{meta.author.name} (#{meta.author.email})"
572
+
573
+ meta.capabilities.each do |cap|
574
+ puts "Capability: #{cap.name} - #{cap.description}"
575
+ end
576
+
577
+ meta.links.each do |link|
578
+ puts "Link: #{link.type} -> #{link.url}"
579
+ end
580
+
581
+ puts "Domains: #{meta.domains.join(', ')}"
582
+ puts "Tags: #{meta.tags.join(', ')}"
583
+ end
584
+ ```
585
+
586
+ **Content Type Negotiation:**
587
+ ```ruby
588
+ agent = client.agent("text-analyzer")
589
+
590
+ # Check if agent accepts specific content types
591
+ agent.accepts_content_type?("text/plain") # => true
592
+ agent.accepts_content_type?("text/markdown") # => true
593
+ agent.accepts_content_type?("image/png") # => false
594
+
595
+ # Check if agent produces specific content types
596
+ agent.produces_content_type?("application/json") # => true
597
+ agent.produces_content_type?("text/html") # => false
598
+ ```
599
+
600
+ ### Metadata Fields
601
+
602
+ | Field | Type | Description |
603
+ |-------|------|-------------|
604
+ | `documentation` | String | Detailed documentation text |
605
+ | `license` | String | License identifier (e.g., "MIT") |
606
+ | `programming_language` | String | Implementation language |
607
+ | `natural_languages` | Array | Supported human languages |
608
+ | `framework` | String | Framework used |
609
+ | `author` | Author | Primary author information |
610
+ | `contributors` | Array | List of contributors |
611
+ | `capabilities` | Array | Agent capabilities with descriptions |
612
+ | `domains` | Array | Domain categories |
613
+ | `tags` | Array | Searchable tags |
614
+ | `links` | Array | Related URLs |
615
+ | `dependencies` | Array | Required dependencies |
616
+ | `recommended_models` | Array | Suggested AI models |
617
+ | `created_at` | String | Creation timestamp |
618
+ | `updated_at` | String | Last update timestamp |
619
+
620
+ ---
621
+
622
+ ## Feature Coverage
623
+
624
+ | Feature | Example |
625
+ |---------|---------|
626
+ | Basic agent registration | 01_basic |
627
+ | Streaming responses | 01_basic |
628
+ | Session state | 01_basic |
629
+ | Conversation history | 01_basic |
630
+ | Async execution | 02_async_execution |
631
+ | Polling (`run_status`, `wait_for_run`) | 02_async_execution |
632
+ | Concurrent runs | 02_async_execution |
633
+ | Run cancellation | 03_run_management |
634
+ | Event history | 03_run_management |
635
+ | JSON message parts | 04_rich_messages |
636
+ | Binary/base64 data | 04_rich_messages |
637
+ | URL references | 04_rich_messages |
638
+ | Content type negotiation | 04_rich_messages, 06_agent_metadata |
639
+ | Await/resume pattern | 05_await_resume |
640
+ | Multi-step flows | 05_await_resume |
641
+ | Agent metadata | 06_agent_metadata |
642
+ | Author/contributor info | 06_agent_metadata |
643
+ | Capabilities and dependencies | 06_agent_metadata |
644
+
645
+ ---
646
+
647
+ ## Running Examples Manually
648
+
649
+ If you prefer to run server and client manually in separate terminals:
650
+
651
+ ```bash
652
+ # Terminal 1: Start server
653
+ ruby examples/01_basic/server.rb
654
+
655
+ # Terminal 2: Run client
656
+ ruby examples/01_basic/client.rb
657
+ ```
658
+
659
+ The `run_demo.sh` script handles starting the server, waiting for it to be ready, running the client, and cleanup automatically.