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
@@ -0,0 +1,308 @@
1
+ # Sync & Async Execution
2
+
3
+ SimpleAcp supports both synchronous and asynchronous execution patterns for different use cases.
4
+
5
+ ## Synchronous Execution
6
+
7
+ ### Basic Usage
8
+
9
+ Wait for the run to complete:
10
+
11
+ ```ruby
12
+ run = client.run_sync(
13
+ agent: "echo",
14
+ input: [SimpleAcp::Models::Message.user("Hello")]
15
+ )
16
+
17
+ puts run.status # => "completed"
18
+ puts run.output # => [Message, ...]
19
+ ```
20
+
21
+ ### With Session
22
+
23
+ ```ruby
24
+ client.use_session("conversation-123")
25
+
26
+ run = client.run_sync(
27
+ agent: "chat",
28
+ input: [SimpleAcp::Models::Message.user("Hello")]
29
+ )
30
+ ```
31
+
32
+ ### Handling Results
33
+
34
+ ```ruby
35
+ run = client.run_sync(agent: "processor", input: [...])
36
+
37
+ case run.status
38
+ when "completed"
39
+ run.output.each do |message|
40
+ puts message.text_content
41
+ end
42
+ when "failed"
43
+ puts "Error code: #{run.error.code}"
44
+ puts "Error message: #{run.error.message}"
45
+ when "awaiting"
46
+ puts "Agent needs more input:"
47
+ puts run.await_request.message.text_content
48
+ # Handle await...
49
+ end
50
+ ```
51
+
52
+ ### When to Use Sync
53
+
54
+ - Quick operations (< few seconds)
55
+ - Simple request-response patterns
56
+ - When you need the result immediately
57
+ - CLI tools and scripts
58
+
59
+ ## Asynchronous Execution
60
+
61
+ ### Basic Usage
62
+
63
+ Start a run without waiting:
64
+
65
+ ```ruby
66
+ run = client.run_async(
67
+ agent: "slow-processor",
68
+ input: [SimpleAcp::Models::Message.user("Large dataset")]
69
+ )
70
+
71
+ puts "Run started: #{run.run_id}"
72
+ puts "Status: #{run.status}" # => "in_progress"
73
+ ```
74
+
75
+ ### Polling for Completion
76
+
77
+ ```ruby
78
+ run = client.run_async(agent: "processor", input: [...])
79
+
80
+ # Poll until complete
81
+ loop do
82
+ run = client.run_status(run.run_id)
83
+
84
+ puts "Status: #{run.status}"
85
+
86
+ break if run.terminal?
87
+ sleep 2
88
+ end
89
+
90
+ # Process results
91
+ puts run.output if run.completed?
92
+ puts run.error if run.failed?
93
+ ```
94
+
95
+ ### With Timeout
96
+
97
+ ```ruby
98
+ run = client.run_async(agent: "processor", input: [...])
99
+ timeout = Time.now + 300 # 5 minutes
100
+
101
+ loop do
102
+ run = client.run_status(run.run_id)
103
+ break if run.terminal?
104
+
105
+ if Time.now > timeout
106
+ client.run_cancel(run.run_id)
107
+ raise "Operation timed out"
108
+ end
109
+
110
+ sleep 2
111
+ end
112
+ ```
113
+
114
+ ### When to Use Async
115
+
116
+ - Long-running operations
117
+ - Background processing
118
+ - When you can poll for results
119
+ - Fire-and-forget patterns
120
+
121
+ ## Comparison
122
+
123
+ | Aspect | Synchronous | Asynchronous |
124
+ |--------|-------------|--------------|
125
+ | Blocking | Yes | No |
126
+ | Response time | Waits for completion | Immediate |
127
+ | Use case | Quick operations | Long operations |
128
+ | Error handling | Inline | Via polling |
129
+
130
+ ## Helper Patterns
131
+
132
+ ### Sync Wrapper for Async
133
+
134
+ ```ruby
135
+ def run_with_timeout(client, agent:, input:, timeout: 60)
136
+ run = client.run_async(agent: agent, input: input)
137
+ deadline = Time.now + timeout
138
+
139
+ loop do
140
+ run = client.run_status(run.run_id)
141
+ return run if run.terminal?
142
+
143
+ if Time.now > deadline
144
+ client.run_cancel(run.run_id)
145
+ raise "Timeout waiting for run"
146
+ end
147
+
148
+ sleep 1
149
+ end
150
+ end
151
+ ```
152
+
153
+ ### Async with Callback
154
+
155
+ ```ruby
156
+ def run_async_with_callback(client, agent:, input:, &block)
157
+ Thread.new do
158
+ run = client.run_async(agent: agent, input: input)
159
+
160
+ loop do
161
+ run = client.run_status(run.run_id)
162
+ break if run.terminal?
163
+ sleep 1
164
+ end
165
+
166
+ block.call(run)
167
+ end
168
+ end
169
+
170
+ # Usage
171
+ run_async_with_callback(client, agent: "processor", input: [...]) do |run|
172
+ puts "Completed: #{run.output}"
173
+ end
174
+
175
+ puts "Continuing while processing..."
176
+ ```
177
+
178
+ ### Batch Async Execution
179
+
180
+ ```ruby
181
+ def run_batch(client, jobs)
182
+ # Start all jobs
183
+ runs = jobs.map do |job|
184
+ client.run_async(
185
+ agent: job[:agent],
186
+ input: job[:input]
187
+ )
188
+ end
189
+
190
+ # Wait for all to complete
191
+ completed = []
192
+
193
+ until runs.empty?
194
+ runs.each do |run|
195
+ updated = client.run_status(run.run_id)
196
+ if updated.terminal?
197
+ completed << updated
198
+ runs.delete(run)
199
+ end
200
+ end
201
+ sleep 1 unless runs.empty?
202
+ end
203
+
204
+ completed
205
+ end
206
+
207
+ # Usage
208
+ results = run_batch(client, [
209
+ { agent: "processor", input: [...] },
210
+ { agent: "analyzer", input: [...] },
211
+ { agent: "formatter", input: [...] }
212
+ ])
213
+ ```
214
+
215
+ ## Resuming Awaited Runs
216
+
217
+ Both sync and async support resuming:
218
+
219
+ ### Sync Resume
220
+
221
+ ```ruby
222
+ run = client.run_sync(agent: "questioner", input: [...])
223
+
224
+ if run.awaiting?
225
+ puts run.await_request.message.text_content
226
+
227
+ run = client.run_resume_sync(
228
+ run_id: run.run_id,
229
+ await_resume: SimpleAcp::Models::MessageAwaitResume.new(
230
+ message: SimpleAcp::Models::Message.user("My answer")
231
+ )
232
+ )
233
+ end
234
+ ```
235
+
236
+ ### Async Resume
237
+
238
+ ```ruby
239
+ run = client.run_async(agent: "questioner", input: [...])
240
+
241
+ # ... later
242
+
243
+ run = client.run_status(run.run_id)
244
+
245
+ if run.awaiting?
246
+ # Resume asynchronously
247
+ run = client.run_async(
248
+ run_id: run.run_id,
249
+ await_resume: SimpleAcp::Models::MessageAwaitResume.new(
250
+ message: SimpleAcp::Models::Message.user("Answer")
251
+ )
252
+ )
253
+ end
254
+ ```
255
+
256
+ ## Error Handling
257
+
258
+ ### Sync Errors
259
+
260
+ ```ruby
261
+ begin
262
+ run = client.run_sync(agent: "processor", input: [...])
263
+
264
+ if run.failed?
265
+ handle_error(run.error)
266
+ end
267
+ rescue Faraday::TimeoutError
268
+ puts "Request timed out"
269
+ rescue Faraday::ConnectionFailed
270
+ puts "Connection failed"
271
+ end
272
+ ```
273
+
274
+ ### Async Errors
275
+
276
+ ```ruby
277
+ run = client.run_async(agent: "processor", input: [...])
278
+
279
+ begin
280
+ loop do
281
+ run = client.run_status(run.run_id)
282
+ break if run.terminal?
283
+ sleep 1
284
+ end
285
+ rescue => e
286
+ # Try to cancel on error
287
+ client.run_cancel(run.run_id) rescue nil
288
+ raise e
289
+ end
290
+
291
+ if run.failed?
292
+ handle_error(run.error)
293
+ end
294
+ ```
295
+
296
+ ## Best Practices
297
+
298
+ 1. **Choose wisely** - Use sync for quick ops, async for long ones
299
+ 2. **Set timeouts** - Always have a maximum wait time
300
+ 3. **Handle all states** - Check for completed, failed, awaiting, cancelled
301
+ 4. **Cancel on error** - Clean up in-progress runs when errors occur
302
+ 5. **Poll appropriately** - Don't poll too frequently (1-2 second intervals)
303
+
304
+ ## Next Steps
305
+
306
+ - Learn about [Streaming](streaming.md) for real-time updates
307
+ - Explore [Session Management](sessions.md)
308
+ - See [Events](../core-concepts/events.md) for event details
@@ -0,0 +1,253 @@
1
+ # Agents
2
+
3
+ Agents are the processing units in ACP that receive input messages and produce output messages. They encapsulate the logic for handling specific tasks.
4
+
5
+ ## What is an Agent?
6
+
7
+ An agent is:
8
+
9
+ - A named, registered handler on a server
10
+ - Invoked with input messages
11
+ - Returns output messages (synchronously or via streaming)
12
+ - Optionally maintains state through sessions
13
+
14
+ ```mermaid
15
+ graph LR
16
+ I[Input Messages] --> A[Agent Handler]
17
+ A --> O[Output Messages]
18
+ A --> E[Events]
19
+ ```
20
+
21
+ ## Registering Agents
22
+
23
+ ### Block Syntax
24
+
25
+ The simplest way to create an agent:
26
+
27
+ ```ruby
28
+ server = SimpleAcp::Server::Base.new
29
+
30
+ server.agent("echo", description: "Echoes input back") do |context|
31
+ text = context.input.first&.text_content
32
+ SimpleAcp::Models::Message.agent("Echo: #{text}")
33
+ end
34
+ ```
35
+
36
+ ### Agent Class
37
+
38
+ For complex agents, use a class:
39
+
40
+ ```ruby
41
+ class WeatherAgent
42
+ def call(context)
43
+ location = context.input.first&.text_content
44
+ weather = fetch_weather(location)
45
+ SimpleAcp::Models::Message.agent("Weather in #{location}: #{weather}")
46
+ end
47
+
48
+ private
49
+
50
+ def fetch_weather(location)
51
+ # API call or lookup
52
+ end
53
+ end
54
+
55
+ server.register("weather", WeatherAgent.new, description: "Gets weather")
56
+ ```
57
+
58
+ ### Agent Options
59
+
60
+ Configure agent behavior:
61
+
62
+ ```ruby
63
+ server.agent(
64
+ "processor",
65
+ description: "Processes various content types",
66
+ input_content_types: ["text/plain", "application/json"],
67
+ output_content_types: ["application/json"],
68
+ metadata: { version: "1.0", author: "Team" }
69
+ ) do |context|
70
+ # Handler logic
71
+ end
72
+ ```
73
+
74
+ ## The Context Object
75
+
76
+ Agents receive a `Context` object with:
77
+
78
+ | Property | Description |
79
+ |----------|-------------|
80
+ | `input` | Array of input messages |
81
+ | `session` | Current session (if any) |
82
+ | `history` | Previous messages in session |
83
+ | `state` | Session state data |
84
+ | `run_id` | Current run identifier |
85
+ | `agent_name` | Name of this agent |
86
+
87
+ ### Accessing Input
88
+
89
+ ```ruby
90
+ server.agent("analyzer") do |context|
91
+ # Get first message's text
92
+ text = context.input.first&.text_content
93
+
94
+ # Process all messages
95
+ all_text = context.input.map(&:text_content).join("\n")
96
+
97
+ # Check for specific content types
98
+ json_parts = context.input.flat_map(&:parts).select(&:json?)
99
+
100
+ SimpleAcp::Models::Message.agent("Processed #{context.input.length} messages")
101
+ end
102
+ ```
103
+
104
+ ### Using Session State
105
+
106
+ ```ruby
107
+ server.agent("counter") do |context|
108
+ # Read state
109
+ count = context.state || 0
110
+
111
+ # Update state
112
+ count += 1
113
+ context.set_state(count)
114
+
115
+ SimpleAcp::Models::Message.agent("Count: #{count}")
116
+ end
117
+ ```
118
+
119
+ ### Using History
120
+
121
+ ```ruby
122
+ server.agent("summarizer") do |context|
123
+ # Access conversation history
124
+ previous = context.history.map(&:text_content).join("\n")
125
+
126
+ SimpleAcp::Models::Message.agent(
127
+ "Conversation so far:\n#{previous}\n\nNew: #{context.input.first&.text_content}"
128
+ )
129
+ end
130
+ ```
131
+
132
+ ## Return Values
133
+
134
+ ### Single Message
135
+
136
+ Return a single message:
137
+
138
+ ```ruby
139
+ server.agent("simple") do |context|
140
+ SimpleAcp::Models::Message.agent("Response")
141
+ end
142
+ ```
143
+
144
+ ### Multiple Messages
145
+
146
+ Return an array of messages:
147
+
148
+ ```ruby
149
+ server.agent("multi") do |context|
150
+ [
151
+ SimpleAcp::Models::Message.agent("First response"),
152
+ SimpleAcp::Models::Message.agent("Second response")
153
+ ]
154
+ end
155
+ ```
156
+
157
+ ### Streaming with Enumerator
158
+
159
+ For streaming responses, return an Enumerator:
160
+
161
+ ```ruby
162
+ server.agent("streamer") do |context|
163
+ Enumerator.new do |yielder|
164
+ 5.times do |i|
165
+ yielder << SimpleAcp::Server::RunYield.new(
166
+ SimpleAcp::Models::Message.agent("Message #{i + 1}")
167
+ )
168
+ sleep 0.5
169
+ end
170
+ end
171
+ end
172
+ ```
173
+
174
+ ### Awaiting Input
175
+
176
+ Request additional input mid-execution:
177
+
178
+ ```ruby
179
+ server.agent("questioner") do |context|
180
+ Enumerator.new do |yielder|
181
+ # Ask for input
182
+ result = context.await_message(
183
+ SimpleAcp::Models::Message.agent("What is your name?")
184
+ )
185
+ yielder << result
186
+
187
+ # After resume, get the response
188
+ name = context.resume_message&.text_content
189
+ yielder << SimpleAcp::Server::RunYield.new(
190
+ SimpleAcp::Models::Message.agent("Hello, #{name}!")
191
+ )
192
+ end
193
+ end
194
+ ```
195
+
196
+ ## Agent Manifest
197
+
198
+ Each agent has a manifest describing its capabilities:
199
+
200
+ ```ruby
201
+ # Retrieved via client
202
+ manifest = client.agent("echo")
203
+
204
+ manifest.name # => "echo"
205
+ manifest.description # => "Echoes input back"
206
+ manifest.input_content_types # => ["text/plain"]
207
+ manifest.output_content_types # => ["text/plain"]
208
+ manifest.metadata # => {}
209
+ ```
210
+
211
+ ## Best Practices
212
+
213
+ ### Keep Handlers Focused
214
+
215
+ Each agent should do one thing well:
216
+
217
+ ```ruby
218
+ # Good: Single responsibility
219
+ server.agent("translate") { |ctx| translate(ctx.input) }
220
+ server.agent("summarize") { |ctx| summarize(ctx.input) }
221
+
222
+ # Avoid: Kitchen sink agents
223
+ server.agent("do_everything") { |ctx| ... }
224
+ ```
225
+
226
+ ### Handle Errors Gracefully
227
+
228
+ ```ruby
229
+ server.agent("safe") do |context|
230
+ begin
231
+ process(context.input)
232
+ rescue StandardError => e
233
+ SimpleAcp::Models::Message.agent("Error: #{e.message}")
234
+ end
235
+ end
236
+ ```
237
+
238
+ ### Use Descriptive Names
239
+
240
+ ```ruby
241
+ # Good
242
+ server.agent("weather-forecast", description: "Gets 7-day weather forecast")
243
+ server.agent("text-summarizer", description: "Summarizes long text")
244
+
245
+ # Avoid
246
+ server.agent("agent1", description: "Does stuff")
247
+ ```
248
+
249
+ ## Next Steps
250
+
251
+ - Learn about [Runs](runs.md) that execute agents
252
+ - Explore [Sessions](sessions.md) for stateful agents
253
+ - See the [Server Guide](../server/creating-agents.md) for advanced patterns