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,278 @@
1
+ # Runs
2
+
3
+ A Run represents a single execution of an agent. It tracks the complete lifecycle from initiation to completion, including status, output, and any errors.
4
+
5
+ ## Run Lifecycle
6
+
7
+ ```mermaid
8
+ stateDiagram-v2
9
+ [*] --> created
10
+ created --> in_progress
11
+ in_progress --> completed
12
+ in_progress --> failed
13
+ in_progress --> awaiting
14
+ in_progress --> cancelled
15
+ awaiting --> in_progress: resume
16
+ completed --> [*]
17
+ failed --> [*]
18
+ cancelled --> [*]
19
+ ```
20
+
21
+ ## Run States
22
+
23
+ | Status | Description |
24
+ |--------|-------------|
25
+ | `created` | Run has been created but not started |
26
+ | `in_progress` | Agent is currently executing |
27
+ | `completed` | Agent finished successfully |
28
+ | `failed` | Agent encountered an error |
29
+ | `awaiting` | Agent is waiting for additional input |
30
+ | `cancelled` | Run was cancelled by request |
31
+
32
+ ## Creating Runs
33
+
34
+ ### Synchronous
35
+
36
+ Execute and wait for completion:
37
+
38
+ ```ruby
39
+ run = client.run_sync(
40
+ agent: "echo",
41
+ input: [SimpleAcp::Models::Message.user("Hello")]
42
+ )
43
+
44
+ puts run.status # => "completed"
45
+ puts run.output.first # => Message object
46
+ ```
47
+
48
+ ### Asynchronous
49
+
50
+ Start execution and poll for results:
51
+
52
+ ```ruby
53
+ run = client.run_async(
54
+ agent: "slow-processor",
55
+ input: [SimpleAcp::Models::Message.user("Process this")]
56
+ )
57
+
58
+ puts run.status # => "in_progress"
59
+
60
+ # Poll until complete
61
+ loop do
62
+ run = client.run_status(run.run_id)
63
+ break if run.terminal?
64
+ sleep 1
65
+ end
66
+
67
+ puts run.output
68
+ ```
69
+
70
+ ### Streaming
71
+
72
+ Receive real-time events:
73
+
74
+ ```ruby
75
+ client.run_stream(
76
+ agent: "chat",
77
+ input: [SimpleAcp::Models::Message.user("Tell me a story")]
78
+ ) do |event|
79
+ case event
80
+ when SimpleAcp::Models::RunStartedEvent
81
+ puts "Started: #{event.run_id}"
82
+ when SimpleAcp::Models::MessagePartEvent
83
+ print event.part.content
84
+ when SimpleAcp::Models::RunCompletedEvent
85
+ puts "\nDone!"
86
+ end
87
+ end
88
+ ```
89
+
90
+ ## Run Properties
91
+
92
+ ```ruby
93
+ run = client.run_sync(agent: "echo", input: [...])
94
+
95
+ # Identification
96
+ run.run_id # => "550e8400-e29b-41d4-a716-446655440000"
97
+ run.agent_name # => "echo"
98
+ run.session_id # => "session-123" or nil
99
+
100
+ # Status
101
+ run.status # => "completed"
102
+ run.terminal? # => true
103
+ run.completed? # => true
104
+ run.failed? # => false
105
+ run.awaiting? # => false
106
+ run.cancelled? # => false
107
+
108
+ # Output
109
+ run.output # => Array of Messages
110
+ run.error # => Error object or nil
111
+
112
+ # Timestamps
113
+ run.created_at # => Time
114
+ run.finished_at # => Time or nil
115
+ ```
116
+
117
+ ## Handling Different States
118
+
119
+ ### Completed Runs
120
+
121
+ ```ruby
122
+ run = client.run_sync(agent: "processor", input: [...])
123
+
124
+ if run.completed?
125
+ run.output.each do |message|
126
+ puts message.text_content
127
+ end
128
+ end
129
+ ```
130
+
131
+ ### Failed Runs
132
+
133
+ ```ruby
134
+ run = client.run_sync(agent: "risky", input: [...])
135
+
136
+ if run.failed?
137
+ puts "Error code: #{run.error.code}"
138
+ puts "Error message: #{run.error.message}"
139
+ end
140
+ ```
141
+
142
+ ### Awaiting Runs
143
+
144
+ ```ruby
145
+ run = client.run_sync(agent: "questioner", input: [...])
146
+
147
+ if run.awaiting?
148
+ # The agent is waiting for more input
149
+ puts run.await_request.message.text_content
150
+
151
+ # Resume with a response
152
+ run = client.run_resume_sync(
153
+ run_id: run.run_id,
154
+ await_resume: SimpleAcp::Models::MessageAwaitResume.new(
155
+ message: SimpleAcp::Models::Message.user("My response")
156
+ )
157
+ )
158
+ end
159
+ ```
160
+
161
+ ## Run Management
162
+
163
+ ### Get Run Status
164
+
165
+ ```ruby
166
+ run = client.run_status("run-id-here")
167
+ puts run.status
168
+ ```
169
+
170
+ ### Get Run Events
171
+
172
+ ```ruby
173
+ events = client.run_events("run-id-here")
174
+ events.each do |event|
175
+ puts "#{event.type}: #{event.inspect}"
176
+ end
177
+ ```
178
+
179
+ ### Cancel a Run
180
+
181
+ ```ruby
182
+ # Cancel an in-progress run
183
+ client.run_cancel("run-id-here")
184
+ ```
185
+
186
+ ## Server-Side Runs
187
+
188
+ On the server, runs are managed internally:
189
+
190
+ ```ruby
191
+ # Create and execute a run programmatically
192
+ run = server.run_sync(
193
+ agent_name: "processor",
194
+ input: [SimpleAcp::Models::Message.user("Data")],
195
+ session_id: "optional-session"
196
+ )
197
+
198
+ # Stream execution
199
+ server.run_stream(
200
+ agent_name: "streamer",
201
+ input: messages
202
+ ) do |event|
203
+ # Handle events
204
+ end
205
+ ```
206
+
207
+ ## Run with Sessions
208
+
209
+ Associate runs with sessions for stateful interactions:
210
+
211
+ ```ruby
212
+ # Client side
213
+ client.use_session("conversation-123")
214
+
215
+ # All runs now use this session
216
+ run1 = client.run_sync(agent: "chat", input: [...])
217
+ run2 = client.run_sync(agent: "chat", input: [...])
218
+
219
+ # Server side
220
+ run = server.run_sync(
221
+ agent_name: "chat",
222
+ input: messages,
223
+ session_id: "conversation-123"
224
+ )
225
+ ```
226
+
227
+ ## Best Practices
228
+
229
+ ### Always Check Status
230
+
231
+ ```ruby
232
+ run = client.run_sync(agent: "...", input: [...])
233
+
234
+ case run.status
235
+ when "completed"
236
+ process_output(run.output)
237
+ when "failed"
238
+ handle_error(run.error)
239
+ when "awaiting"
240
+ handle_await(run)
241
+ else
242
+ log_unexpected_status(run.status)
243
+ end
244
+ ```
245
+
246
+ ### Use Streaming for Long Operations
247
+
248
+ ```ruby
249
+ # Better UX for slow operations
250
+ client.run_stream(agent: "slow-agent", input: [...]) do |event|
251
+ show_progress(event)
252
+ end
253
+ ```
254
+
255
+ ### Handle Timeouts
256
+
257
+ ```ruby
258
+ run = client.run_async(agent: "slow", input: [...])
259
+ timeout = Time.now + 60
260
+
261
+ loop do
262
+ run = client.run_status(run.run_id)
263
+ break if run.terminal?
264
+
265
+ if Time.now > timeout
266
+ client.run_cancel(run.run_id)
267
+ raise "Run timed out"
268
+ end
269
+
270
+ sleep 1
271
+ end
272
+ ```
273
+
274
+ ## Next Steps
275
+
276
+ - Learn about [Sessions](sessions.md) for stateful runs
277
+ - Explore [Events](events.md) for real-time updates
278
+ - See [Client Streaming](../client/streaming.md) for advanced streaming patterns
@@ -0,0 +1,281 @@
1
+ # Sessions
2
+
3
+ Sessions enable stateful interactions across multiple runs. They maintain conversation history and custom state data, allowing agents to remember context from previous interactions.
4
+
5
+ ## What is a Session?
6
+
7
+ A session is a container for:
8
+
9
+ - **History**: Previous messages in the conversation
10
+ - **State**: Custom data persisted across runs
11
+ - **Identity**: A unique session ID
12
+
13
+ ```mermaid
14
+ graph TB
15
+ subgraph Session
16
+ H[History] --> R1[Run 1]
17
+ H --> R2[Run 2]
18
+ H --> R3[Run 3]
19
+ S[State]
20
+ end
21
+ ```
22
+
23
+ ## Creating Sessions
24
+
25
+ ### Client-Side Sessions
26
+
27
+ ```ruby
28
+ client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
29
+
30
+ # Use a specific session
31
+ client.use_session("user-123-conversation")
32
+
33
+ # All subsequent runs use this session
34
+ client.run_sync(agent: "chat", input: [...])
35
+ client.run_sync(agent: "chat", input: [...])
36
+
37
+ # Clear when done
38
+ client.clear_session
39
+ ```
40
+
41
+ ### Server-Side Sessions
42
+
43
+ ```ruby
44
+ # Specify session when creating a run
45
+ run = server.run_sync(
46
+ agent_name: "chat",
47
+ input: messages,
48
+ session_id: "conversation-456"
49
+ )
50
+ ```
51
+
52
+ ## Session History
53
+
54
+ History automatically accumulates messages across runs:
55
+
56
+ ```ruby
57
+ # First run
58
+ client.use_session("my-session")
59
+ client.run_sync(agent: "chat", input: [Message.user("Hello")])
60
+ # History: [user: "Hello", agent: "Hi there!"]
61
+
62
+ # Second run
63
+ client.run_sync(agent: "chat", input: [Message.user("How are you?")])
64
+ # History: [user: "Hello", agent: "Hi there!", user: "How are you?", agent: "I'm good!"]
65
+ ```
66
+
67
+ ### Accessing History in Agents
68
+
69
+ ```ruby
70
+ server.agent("contextual") do |context|
71
+ # Get all previous messages
72
+ history = context.history
73
+
74
+ # Build context from history
75
+ conversation = history.map do |msg|
76
+ "#{msg.role}: #{msg.text_content}"
77
+ end.join("\n")
78
+
79
+ SimpleAcp::Models::Message.agent(
80
+ "Based on our conversation:\n#{conversation}\n\nContinuing..."
81
+ )
82
+ end
83
+ ```
84
+
85
+ ## Session State
86
+
87
+ Custom state data persists across runs:
88
+
89
+ ```ruby
90
+ server.agent("counter") do |context|
91
+ # Read current state (nil if first run)
92
+ count = context.state || 0
93
+
94
+ # Increment
95
+ count += 1
96
+
97
+ # Save new state
98
+ context.set_state(count)
99
+
100
+ SimpleAcp::Models::Message.agent("Count: #{count}")
101
+ end
102
+ ```
103
+
104
+ ### Complex State
105
+
106
+ State can be any JSON-serializable data:
107
+
108
+ ```ruby
109
+ server.agent("quiz") do |context|
110
+ state = context.state || { score: 0, questions_asked: 0 }
111
+
112
+ # Update state
113
+ state[:questions_asked] += 1
114
+ if correct_answer?(context.input)
115
+ state[:score] += 1
116
+ end
117
+
118
+ context.set_state(state)
119
+
120
+ SimpleAcp::Models::Message.agent(
121
+ "Score: #{state[:score]}/#{state[:questions_asked]}"
122
+ )
123
+ end
124
+ ```
125
+
126
+ ## Session Properties
127
+
128
+ ```ruby
129
+ session = client.session("session-id")
130
+
131
+ session.id # => "session-id"
132
+ session.history # => Array of Messages
133
+ session.state # => Custom state data or nil
134
+ ```
135
+
136
+ ## Session Storage
137
+
138
+ Sessions are persisted by the storage backend:
139
+
140
+ === "Memory"
141
+
142
+ ```ruby
143
+ # Sessions lost on restart
144
+ storage = SimpleAcp::Storage::Memory.new
145
+ ```
146
+
147
+ === "Redis"
148
+
149
+ ```ruby
150
+ # Sessions expire after TTL
151
+ storage = SimpleAcp::Storage::Redis.new(
152
+ ttl: 86400 # 24 hours
153
+ )
154
+ ```
155
+
156
+ === "PostgreSQL"
157
+
158
+ ```ruby
159
+ # Sessions persist indefinitely
160
+ storage = SimpleAcp::Storage::PostgreSQL.new(
161
+ url: ENV['DATABASE_URL']
162
+ )
163
+ ```
164
+
165
+ ## Common Patterns
166
+
167
+ ### Multi-Turn Conversation
168
+
169
+ ```ruby
170
+ server.agent("assistant") do |context|
171
+ # Build prompt with history
172
+ messages = context.history + context.input
173
+
174
+ # Call LLM with full context
175
+ response = llm.chat(messages)
176
+
177
+ SimpleAcp::Models::Message.agent(response)
178
+ end
179
+ ```
180
+
181
+ ### User Preferences
182
+
183
+ ```ruby
184
+ server.agent("personalized") do |context|
185
+ state = context.state || { preferences: {} }
186
+
187
+ # Check for preference updates
188
+ if context.input.first&.text_content&.start_with?("set preference")
189
+ key, value = parse_preference(context.input.first.text_content)
190
+ state[:preferences][key] = value
191
+ context.set_state(state)
192
+ return SimpleAcp::Models::Message.agent("Preference saved!")
193
+ end
194
+
195
+ # Use preferences
196
+ prefs = state[:preferences]
197
+ # ...
198
+ end
199
+ ```
200
+
201
+ ### Shopping Cart
202
+
203
+ ```ruby
204
+ server.agent("cart") do |context|
205
+ cart = context.state || { items: [], total: 0 }
206
+
207
+ command = context.input.first&.text_content
208
+
209
+ case command
210
+ when /^add (.+)/
211
+ item = $1
212
+ cart[:items] << item
213
+ cart[:total] += get_price(item)
214
+ when /^remove (.+)/
215
+ item = $1
216
+ cart[:items].delete(item)
217
+ cart[:total] -= get_price(item)
218
+ when "checkout"
219
+ # Process order...
220
+ cart = { items: [], total: 0 }
221
+ end
222
+
223
+ context.set_state(cart)
224
+
225
+ SimpleAcp::Models::Message.agent(
226
+ "Cart: #{cart[:items].join(', ')} - Total: $#{cart[:total]}"
227
+ )
228
+ end
229
+ ```
230
+
231
+ ## Best Practices
232
+
233
+ ### Generate Meaningful Session IDs
234
+
235
+ ```ruby
236
+ # Good: Meaningful, traceable
237
+ client.use_session("user-#{user_id}-chat-#{timestamp}")
238
+
239
+ # Avoid: Random, hard to debug
240
+ client.use_session(SecureRandom.uuid)
241
+ ```
242
+
243
+ ### Clean Up Sessions
244
+
245
+ ```ruby
246
+ # When conversation ends
247
+ client.clear_session
248
+
249
+ # Or delete server-side
250
+ storage.delete_session(session_id)
251
+ ```
252
+
253
+ ### Limit History Size
254
+
255
+ ```ruby
256
+ server.agent("bounded") do |context|
257
+ # Only use recent history
258
+ recent = context.history.last(10)
259
+ # ...
260
+ end
261
+ ```
262
+
263
+ ### Handle Missing State
264
+
265
+ ```ruby
266
+ server.agent("safe") do |context|
267
+ # Always provide defaults
268
+ state = context.state || default_state
269
+
270
+ # Validate state structure
271
+ state[:version] ||= 1
272
+ state[:data] ||= {}
273
+ # ...
274
+ end
275
+ ```
276
+
277
+ ## Next Steps
278
+
279
+ - Learn about [Events](events.md) for real-time updates
280
+ - Explore [Multi-Turn Conversations](../server/multi-turn.md)
281
+ - See [Session Management](../client/sessions.md) in the client guide