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,324 @@
1
+ # Session Management
2
+
3
+ Sessions enable stateful interactions across multiple runs, maintaining conversation history and custom state.
4
+
5
+ ## Using Sessions
6
+
7
+ ### Enable Session
8
+
9
+ ```ruby
10
+ client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
11
+
12
+ # Set the session to use
13
+ client.use_session("my-session-id")
14
+
15
+ # All subsequent runs use this session
16
+ client.run_sync(agent: "chat", input: [...])
17
+ client.run_sync(agent: "chat", input: [...])
18
+ ```
19
+
20
+ ### Clear Session
21
+
22
+ ```ruby
23
+ # Stop using the session
24
+ client.clear_session
25
+
26
+ # Runs are now sessionless
27
+ client.run_sync(agent: "chat", input: [...])
28
+ ```
29
+
30
+ ### Check Active Session
31
+
32
+ ```ruby
33
+ if client.session_id
34
+ puts "Using session: #{client.session_id}"
35
+ else
36
+ puts "No session active"
37
+ end
38
+ ```
39
+
40
+ ## Session IDs
41
+
42
+ ### Generate Session IDs
43
+
44
+ ```ruby
45
+ # User-specific session
46
+ client.use_session("user-#{user_id}")
47
+
48
+ # Conversation-specific
49
+ client.use_session("chat-#{SecureRandom.uuid}")
50
+
51
+ # Time-based
52
+ client.use_session("session-#{Time.now.to_i}")
53
+ ```
54
+
55
+ ### Meaningful Session IDs
56
+
57
+ ```ruby
58
+ # Good: Traceable, meaningful
59
+ client.use_session("user-42-support-ticket-123")
60
+ client.use_session("api-key-abc123-chat")
61
+
62
+ # Avoid: Random, untraceable
63
+ client.use_session(SecureRandom.hex(32))
64
+ ```
65
+
66
+ ## Retrieving Session Data
67
+
68
+ ### Get Session Info
69
+
70
+ ```ruby
71
+ session = client.session("my-session-id")
72
+
73
+ puts "Session ID: #{session.id}"
74
+ puts "History length: #{session.history.length}"
75
+ puts "State: #{session.state.inspect}"
76
+ ```
77
+
78
+ ### Access History
79
+
80
+ ```ruby
81
+ session = client.session("my-session-id")
82
+
83
+ session.history.each do |message|
84
+ puts "#{message.role}: #{message.text_content}"
85
+ end
86
+ ```
87
+
88
+ ### Access State
89
+
90
+ ```ruby
91
+ session = client.session("my-session-id")
92
+
93
+ if session.state
94
+ puts "Current step: #{session.state['step']}"
95
+ puts "User data: #{session.state['data'].inspect}"
96
+ end
97
+ ```
98
+
99
+ ## Conversation Patterns
100
+
101
+ ### Multi-Turn Chat
102
+
103
+ ```ruby
104
+ def chat_loop(client, agent)
105
+ client.use_session("chat-#{SecureRandom.uuid}")
106
+
107
+ loop do
108
+ print "You: "
109
+ input = gets.chomp
110
+ break if input.downcase == 'quit'
111
+
112
+ run = client.run_sync(
113
+ agent: agent,
114
+ input: [SimpleAcp::Models::Message.user(input)]
115
+ )
116
+
117
+ puts "Agent: #{run.output.first&.text_content}"
118
+ end
119
+
120
+ client.clear_session
121
+ end
122
+ ```
123
+
124
+ ### Context-Aware Responses
125
+
126
+ ```ruby
127
+ client.use_session("support-123")
128
+
129
+ # First message
130
+ client.run_sync(
131
+ agent: "support",
132
+ input: [SimpleAcp::Models::Message.user("My order is late")]
133
+ )
134
+ # Agent: "I'm sorry to hear that. Can you provide your order number?"
135
+
136
+ # Follow-up (agent remembers context)
137
+ client.run_sync(
138
+ agent: "support",
139
+ input: [SimpleAcp::Models::Message.user("It's #12345")]
140
+ )
141
+ # Agent: "I found order #12345. It's expected to arrive tomorrow."
142
+ ```
143
+
144
+ ### Resuming Conversations
145
+
146
+ ```ruby
147
+ # Save session ID for later
148
+ session_id = "user-#{user_id}-conversation"
149
+ client.use_session(session_id)
150
+
151
+ # ... user interacts ...
152
+
153
+ # Later, resume the same conversation
154
+ client = SimpleAcp::Client::Base.new(base_url: "...")
155
+ client.use_session(session_id)
156
+
157
+ # Continues where it left off
158
+ run = client.run_sync(agent: "chat", input: [...])
159
+ ```
160
+
161
+ ## Session Lifecycle
162
+
163
+ ```mermaid
164
+ sequenceDiagram
165
+ participant Client
166
+ participant Server
167
+ participant Storage
168
+
169
+ Client->>Client: use_session("id")
170
+ Client->>Server: run_sync (with session_id)
171
+ Server->>Storage: get_session("id")
172
+ Storage-->>Server: Session (or nil)
173
+ Server->>Server: Execute with history
174
+ Server->>Storage: save_session
175
+ Server-->>Client: Run result
176
+ ```
177
+
178
+ ## Managing Multiple Sessions
179
+
180
+ ### Per-User Sessions
181
+
182
+ ```ruby
183
+ class ChatService
184
+ def initialize(base_url)
185
+ @clients = {}
186
+ @base_url = base_url
187
+ end
188
+
189
+ def get_client(user_id)
190
+ @clients[user_id] ||= begin
191
+ client = SimpleAcp::Client::Base.new(base_url: @base_url)
192
+ client.use_session("user-#{user_id}")
193
+ client
194
+ end
195
+ end
196
+
197
+ def chat(user_id, message)
198
+ client = get_client(user_id)
199
+ client.run_sync(
200
+ agent: "chat",
201
+ input: [SimpleAcp::Models::Message.user(message)]
202
+ )
203
+ end
204
+ end
205
+ ```
206
+
207
+ ### Session Pool
208
+
209
+ ```ruby
210
+ class SessionPool
211
+ def initialize(client)
212
+ @client = client
213
+ @sessions = {}
214
+ end
215
+
216
+ def with_session(session_id)
217
+ @client.use_session(session_id)
218
+ yield @client
219
+ ensure
220
+ @client.clear_session
221
+ end
222
+ end
223
+
224
+ # Usage
225
+ pool = SessionPool.new(client)
226
+
227
+ pool.with_session("user-123") do |c|
228
+ c.run_sync(agent: "chat", input: [...])
229
+ end
230
+ ```
231
+
232
+ ## Session Expiration
233
+
234
+ Sessions may expire based on storage backend:
235
+
236
+ ### Redis (TTL-based)
237
+
238
+ ```ruby
239
+ # Sessions expire after TTL (default 24 hours)
240
+ client.use_session("short-lived")
241
+ # ... interact ...
242
+
243
+ # After TTL, session is gone
244
+ # New run starts fresh session
245
+ ```
246
+
247
+ ### PostgreSQL (Persistent)
248
+
249
+ ```ruby
250
+ # Sessions persist until explicitly deleted
251
+ client.use_session("persistent")
252
+ # ... can resume days later ...
253
+ ```
254
+
255
+ ### Memory (Process Lifetime)
256
+
257
+ ```ruby
258
+ # Sessions lost when server restarts
259
+ ```
260
+
261
+ ## Error Handling
262
+
263
+ ```ruby
264
+ begin
265
+ session = client.session("unknown-session")
266
+ rescue SimpleAcp::Error => e
267
+ if e.message.include?("not found")
268
+ puts "Session doesn't exist"
269
+ else
270
+ raise
271
+ end
272
+ end
273
+ ```
274
+
275
+ ## Best Practices
276
+
277
+ 1. **Use meaningful IDs** - Include user/context info for debugging
278
+ 2. **Clean up sessions** - Delete when conversations end
279
+ 3. **Handle expiration** - Gracefully handle missing sessions
280
+ 4. **Scope appropriately** - One session per conversation, not per user
281
+ 5. **Monitor size** - Long sessions may have large history
282
+
283
+ ## Session Cleanup
284
+
285
+ ```ruby
286
+ # After conversation ends
287
+ def end_conversation(client, session_id)
288
+ client.clear_session
289
+
290
+ # Optionally delete server-side
291
+ # (if supported by your storage)
292
+ end
293
+ ```
294
+
295
+ ## Testing with Sessions
296
+
297
+ ```ruby
298
+ def test_multi_turn_conversation
299
+ client = SimpleAcp::Client::Base.new(base_url: test_server_url)
300
+ client.use_session("test-#{SecureRandom.uuid}")
301
+
302
+ # First turn
303
+ run1 = client.run_sync(
304
+ agent: "counter",
305
+ input: [SimpleAcp::Models::Message.user("increment")]
306
+ )
307
+ assert_includes run1.output.first.text_content, "1"
308
+
309
+ # Second turn (should remember)
310
+ run2 = client.run_sync(
311
+ agent: "counter",
312
+ input: [SimpleAcp::Models::Message.user("increment")]
313
+ )
314
+ assert_includes run2.output.first.text_content, "2"
315
+ ensure
316
+ client.clear_session
317
+ end
318
+ ```
319
+
320
+ ## Next Steps
321
+
322
+ - Learn about [Sessions](../core-concepts/sessions.md) concept
323
+ - Explore [Multi-Turn Conversations](../server/multi-turn.md)
324
+ - Review [Storage Backends](../storage/index.md) for persistence options
@@ -0,0 +1,345 @@
1
+ # Streaming
2
+
3
+ Streaming enables real-time delivery of agent responses via Server-Sent Events (SSE), providing immediate feedback and better user experience.
4
+
5
+ ## Basic Streaming
6
+
7
+ ```ruby
8
+ client.run_stream(
9
+ agent: "chat",
10
+ input: [SimpleAcp::Models::Message.user("Tell me a story")]
11
+ ) do |event|
12
+ case event
13
+ when SimpleAcp::Models::MessagePartEvent
14
+ print event.part.content
15
+ $stdout.flush
16
+ when SimpleAcp::Models::RunCompletedEvent
17
+ puts "\n--- Complete ---"
18
+ end
19
+ end
20
+ ```
21
+
22
+ ## Event Types
23
+
24
+ Handle different event types for full control:
25
+
26
+ ```ruby
27
+ client.run_stream(agent: "processor", input: [...]) do |event|
28
+ case event
29
+ when SimpleAcp::Models::RunStartedEvent
30
+ puts "Run started: #{event.run_id}"
31
+
32
+ when SimpleAcp::Models::MessageCreatedEvent
33
+ puts "New message from: #{event.message.role}"
34
+
35
+ when SimpleAcp::Models::MessagePartEvent
36
+ # Content chunk - stream to output
37
+ print event.part.content
38
+
39
+ when SimpleAcp::Models::MessageCompletedEvent
40
+ puts "\n[Message complete]"
41
+
42
+ when SimpleAcp::Models::RunCompletedEvent
43
+ puts "Run completed!"
44
+ puts "Output messages: #{event.run.output.length}"
45
+
46
+ when SimpleAcp::Models::RunFailedEvent
47
+ puts "Run failed: #{event.error.message}"
48
+
49
+ when SimpleAcp::Models::RunAwaitingEvent
50
+ puts "Run awaiting input..."
51
+ puts event.await_request.message.text_content
52
+ end
53
+ end
54
+ ```
55
+
56
+ ## Collecting Output
57
+
58
+ ### Accumulate All Content
59
+
60
+ ```ruby
61
+ output = ""
62
+
63
+ client.run_stream(agent: "chat", input: [...]) do |event|
64
+ case event
65
+ when SimpleAcp::Models::MessagePartEvent
66
+ output << event.part.content
67
+ end
68
+ end
69
+
70
+ puts "Full response: #{output}"
71
+ ```
72
+
73
+ ### Collect Messages
74
+
75
+ ```ruby
76
+ messages = []
77
+
78
+ client.run_stream(agent: "multi", input: [...]) do |event|
79
+ case event
80
+ when SimpleAcp::Models::MessageCompletedEvent
81
+ messages << event.message
82
+ end
83
+ end
84
+
85
+ puts "Received #{messages.length} messages"
86
+ ```
87
+
88
+ ## Progress Tracking
89
+
90
+ ### Simple Progress
91
+
92
+ ```ruby
93
+ parts_received = 0
94
+
95
+ client.run_stream(agent: "processor", input: [...]) do |event|
96
+ case event
97
+ when SimpleAcp::Models::MessagePartEvent
98
+ parts_received += 1
99
+ print "\rReceived: #{parts_received} parts"
100
+ when SimpleAcp::Models::RunCompletedEvent
101
+ puts "\nDone!"
102
+ end
103
+ end
104
+ ```
105
+
106
+ ### Detailed Progress
107
+
108
+ ```ruby
109
+ def stream_with_progress(client, agent:, input:)
110
+ state = { started_at: nil, parts: 0, messages: 0 }
111
+
112
+ client.run_stream(agent: agent, input: input) do |event|
113
+ case event
114
+ when SimpleAcp::Models::RunStartedEvent
115
+ state[:started_at] = Time.now
116
+ puts "Processing..."
117
+
118
+ when SimpleAcp::Models::MessagePartEvent
119
+ state[:parts] += 1
120
+ print "."
121
+ $stdout.flush
122
+
123
+ when SimpleAcp::Models::MessageCompletedEvent
124
+ state[:messages] += 1
125
+
126
+ when SimpleAcp::Models::RunCompletedEvent
127
+ elapsed = Time.now - state[:started_at]
128
+ puts "\nComplete in #{elapsed.round(2)}s"
129
+ puts "#{state[:messages]} messages, #{state[:parts]} parts"
130
+ end
131
+ end
132
+ end
133
+ ```
134
+
135
+ ## Error Handling
136
+
137
+ ### Basic Error Handling
138
+
139
+ ```ruby
140
+ begin
141
+ client.run_stream(agent: "risky", input: [...]) do |event|
142
+ case event
143
+ when SimpleAcp::Models::RunFailedEvent
144
+ puts "Agent error: #{event.error.message}"
145
+ when SimpleAcp::Models::MessagePartEvent
146
+ print event.part.content
147
+ end
148
+ end
149
+ rescue Faraday::TimeoutError
150
+ puts "Stream timed out"
151
+ rescue Faraday::ConnectionFailed
152
+ puts "Connection lost"
153
+ end
154
+ ```
155
+
156
+ ### Graceful Recovery
157
+
158
+ ```ruby
159
+ def stream_with_retry(client, agent:, input:, max_retries: 3)
160
+ retries = 0
161
+
162
+ begin
163
+ client.run_stream(agent: agent, input: input) do |event|
164
+ yield event
165
+ end
166
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
167
+ retries += 1
168
+ if retries <= max_retries
169
+ puts "Connection issue, retrying (#{retries}/#{max_retries})..."
170
+ sleep 2 ** retries # Exponential backoff
171
+ retry
172
+ else
173
+ raise e
174
+ end
175
+ end
176
+ end
177
+
178
+ # Usage
179
+ stream_with_retry(client, agent: "chat", input: [...]) do |event|
180
+ # Handle events
181
+ end
182
+ ```
183
+
184
+ ## Streaming with Sessions
185
+
186
+ ```ruby
187
+ client.use_session("conversation-123")
188
+
189
+ # Stream with session context
190
+ client.run_stream(agent: "chat", input: [Message.user("Hello")]) do |event|
191
+ # Handle events...
192
+ end
193
+
194
+ # Subsequent streams maintain history
195
+ client.run_stream(agent: "chat", input: [Message.user("Continue")]) do |event|
196
+ # Has access to previous conversation
197
+ end
198
+ ```
199
+
200
+ ## Resume Streaming
201
+
202
+ For awaiting runs, resume with streaming:
203
+
204
+ ```ruby
205
+ run = client.run_sync(agent: "questioner", input: [...])
206
+
207
+ if run.awaiting?
208
+ puts run.await_request.message.text_content
209
+
210
+ # Resume with streaming
211
+ client.run_resume_stream(
212
+ run_id: run.run_id,
213
+ await_resume: SimpleAcp::Models::MessageAwaitResume.new(
214
+ message: SimpleAcp::Models::Message.user("My answer")
215
+ )
216
+ ) do |event|
217
+ case event
218
+ when SimpleAcp::Models::MessagePartEvent
219
+ print event.part.content
220
+ end
221
+ end
222
+ end
223
+ ```
224
+
225
+ ## UI Integration
226
+
227
+ ### Terminal UI
228
+
229
+ ```ruby
230
+ require 'io/console'
231
+
232
+ def chat_interface(client, agent)
233
+ puts "Chat with #{agent} (type 'quit' to exit)"
234
+ puts "-" * 40
235
+
236
+ client.use_session("chat-#{SecureRandom.uuid}")
237
+
238
+ loop do
239
+ print "\nYou: "
240
+ input = gets.chomp
241
+ break if input.downcase == 'quit'
242
+
243
+ print "Agent: "
244
+ client.run_stream(
245
+ agent: agent,
246
+ input: [SimpleAcp::Models::Message.user(input)]
247
+ ) do |event|
248
+ case event
249
+ when SimpleAcp::Models::MessagePartEvent
250
+ print event.part.content
251
+ $stdout.flush
252
+ end
253
+ end
254
+ puts
255
+ end
256
+ end
257
+
258
+ chat_interface(client, "chat")
259
+ ```
260
+
261
+ ### Web Application
262
+
263
+ ```ruby
264
+ # Sinatra example
265
+ get '/stream' do
266
+ content_type 'text/event-stream'
267
+
268
+ stream(:keep_open) do |out|
269
+ client.run_stream(
270
+ agent: params[:agent],
271
+ input: [SimpleAcp::Models::Message.user(params[:input])]
272
+ ) do |event|
273
+ case event
274
+ when SimpleAcp::Models::MessagePartEvent
275
+ out << "data: #{event.part.content.to_json}\n\n"
276
+ when SimpleAcp::Models::RunCompletedEvent
277
+ out << "event: done\ndata: {}\n\n"
278
+ end
279
+ end
280
+ end
281
+ end
282
+ ```
283
+
284
+ ## Performance Tips
285
+
286
+ ### Buffer Output
287
+
288
+ ```ruby
289
+ buffer = []
290
+
291
+ client.run_stream(agent: "fast", input: [...]) do |event|
292
+ case event
293
+ when SimpleAcp::Models::MessagePartEvent
294
+ buffer << event.part.content
295
+
296
+ # Flush periodically
297
+ if buffer.length >= 10
298
+ print buffer.join
299
+ buffer.clear
300
+ end
301
+ when SimpleAcp::Models::RunCompletedEvent
302
+ print buffer.join unless buffer.empty?
303
+ end
304
+ end
305
+ ```
306
+
307
+ ### Async Processing
308
+
309
+ ```ruby
310
+ require 'concurrent'
311
+
312
+ def stream_async(client, agent:, input:)
313
+ future = Concurrent::Future.execute do
314
+ result = []
315
+ client.run_stream(agent: agent, input: input) do |event|
316
+ result << event
317
+ end
318
+ result
319
+ end
320
+
321
+ future
322
+ end
323
+
324
+ # Start streaming in background
325
+ future = stream_async(client, agent: "processor", input: [...])
326
+
327
+ # Do other work...
328
+
329
+ # Get results when ready
330
+ events = future.value
331
+ ```
332
+
333
+ ## Best Practices
334
+
335
+ 1. **Flush output** - Call `$stdout.flush` for real-time display
336
+ 2. **Handle all events** - Don't ignore error events
337
+ 3. **Use timeouts** - Configure appropriate connection timeouts
338
+ 4. **Buffer intelligently** - Balance responsiveness with efficiency
339
+ 5. **Clean up** - Handle connection errors gracefully
340
+
341
+ ## Next Steps
342
+
343
+ - Learn about [Session Management](sessions.md)
344
+ - Explore [Events](../core-concepts/events.md) in depth
345
+ - See [Server Streaming](../server/streaming.md) for server-side details