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,329 @@
1
+ # Multi-Turn Conversations
2
+
3
+ Multi-turn conversations allow agents to maintain context across multiple interactions, request additional input, and build stateful experiences.
4
+
5
+ ## Using Sessions
6
+
7
+ Sessions maintain history and state:
8
+
9
+ ```ruby
10
+ server.agent("chat") do |context|
11
+ # Access conversation history
12
+ history = context.history
13
+
14
+ # Build context from history
15
+ all_messages = history + context.input
16
+
17
+ # Generate response considering full context
18
+ response = generate_response(all_messages)
19
+
20
+ SimpleAcp::Models::Message.agent(response)
21
+ end
22
+ ```
23
+
24
+ ## Session History
25
+
26
+ History automatically accumulates:
27
+
28
+ ```ruby
29
+ # First interaction
30
+ # history: []
31
+ # input: [user: "Hello"]
32
+ # After: history becomes [user: "Hello", agent: "Hi!"]
33
+
34
+ # Second interaction
35
+ # history: [user: "Hello", agent: "Hi!"]
36
+ # input: [user: "How are you?"]
37
+ # After: history becomes [user: "Hello", agent: "Hi!", user: "How are you?", agent: "I'm good!"]
38
+ ```
39
+
40
+ ## Session State
41
+
42
+ Custom state persists across turns:
43
+
44
+ ```ruby
45
+ server.agent("counter") do |context|
46
+ count = context.state || 0
47
+ count += 1
48
+ context.set_state(count)
49
+
50
+ SimpleAcp::Models::Message.agent("Turn #{count}")
51
+ end
52
+ ```
53
+
54
+ ### Complex State
55
+
56
+ ```ruby
57
+ server.agent("form") do |context|
58
+ state = context.state || { step: 1, data: {} }
59
+
60
+ case state[:step]
61
+ when 1
62
+ context.set_state(state.merge(step: 2))
63
+ SimpleAcp::Models::Message.agent("Enter your name:")
64
+ when 2
65
+ state[:data][:name] = context.input.first&.text_content
66
+ context.set_state(state.merge(step: 3))
67
+ SimpleAcp::Models::Message.agent("Enter your email:")
68
+ when 3
69
+ state[:data][:email] = context.input.first&.text_content
70
+ context.set_state(state.merge(step: :done))
71
+ SimpleAcp::Models::Message.agent(
72
+ "Thanks #{state[:data][:name]}! We'll contact you at #{state[:data][:email]}"
73
+ )
74
+ else
75
+ SimpleAcp::Models::Message.agent("Form complete!")
76
+ end
77
+ end
78
+ ```
79
+
80
+ ## Awaiting Input
81
+
82
+ For synchronous input requests within a single run:
83
+
84
+ ```ruby
85
+ server.agent("questioner") do |context|
86
+ Enumerator.new do |yielder|
87
+ # Ask first question
88
+ result = context.await_message(
89
+ SimpleAcp::Models::Message.agent("What is your name?")
90
+ )
91
+ yielder << result
92
+
93
+ # After resume, get the answer
94
+ name = context.resume_message&.text_content
95
+
96
+ # Ask second question
97
+ result = context.await_message(
98
+ SimpleAcp::Models::Message.agent("What is your favorite color, #{name}?")
99
+ )
100
+ yielder << result
101
+
102
+ color = context.resume_message&.text_content
103
+
104
+ # Final response
105
+ yielder << SimpleAcp::Server::RunYield.new(
106
+ SimpleAcp::Models::Message.agent("Great choice, #{name}! #{color} is nice.")
107
+ )
108
+ end
109
+ end
110
+ ```
111
+
112
+ ### Await Flow
113
+
114
+ ```mermaid
115
+ sequenceDiagram
116
+ participant Client
117
+ participant Server
118
+ participant Agent
119
+
120
+ Client->>Server: run_sync (input)
121
+ Server->>Agent: execute
122
+ Agent-->>Server: await_message("Name?")
123
+ Server-->>Client: Run (awaiting)
124
+
125
+ Client->>Server: run_resume (name)
126
+ Server->>Agent: continue with resume
127
+ Agent-->>Server: await_message("Color?")
128
+ Server-->>Client: Run (awaiting)
129
+
130
+ Client->>Server: run_resume (color)
131
+ Server->>Agent: continue with resume
132
+ Agent-->>Server: final response
133
+ Server-->>Client: Run (completed)
134
+ ```
135
+
136
+ ### Client-Side Await Handling
137
+
138
+ ```ruby
139
+ run = client.run_sync(agent: "questioner", input: [...])
140
+
141
+ while run.awaiting?
142
+ puts run.await_request.message.text_content
143
+
144
+ answer = gets.chomp
145
+
146
+ run = client.run_resume_sync(
147
+ run_id: run.run_id,
148
+ await_resume: SimpleAcp::Models::MessageAwaitResume.new(
149
+ message: SimpleAcp::Models::Message.user(answer)
150
+ )
151
+ )
152
+ end
153
+
154
+ puts "Final: #{run.output.last.text_content}"
155
+ ```
156
+
157
+ ## Conversation Patterns
158
+
159
+ ### Contextual Responses
160
+
161
+ ```ruby
162
+ server.agent("assistant") do |context|
163
+ # Build full conversation
164
+ conversation = context.history.map do |msg|
165
+ { role: msg.role, content: msg.text_content }
166
+ end
167
+
168
+ conversation << {
169
+ role: "user",
170
+ content: context.input.first&.text_content
171
+ }
172
+
173
+ # Use LLM with full context
174
+ response = llm.chat(conversation)
175
+
176
+ SimpleAcp::Models::Message.agent(response)
177
+ end
178
+ ```
179
+
180
+ ### Memory Management
181
+
182
+ Limit history for efficiency:
183
+
184
+ ```ruby
185
+ server.agent("bounded-chat") do |context|
186
+ # Only use last 10 messages
187
+ recent_history = context.history.last(10)
188
+
189
+ conversation = recent_history + context.input
190
+
191
+ response = generate_response(conversation)
192
+
193
+ SimpleAcp::Models::Message.agent(response)
194
+ end
195
+ ```
196
+
197
+ ### Summarization
198
+
199
+ ```ruby
200
+ server.agent("summarizing-chat") do |context|
201
+ state = context.state || { summary: nil }
202
+
203
+ if context.history.length > 20
204
+ # Summarize older history
205
+ old_messages = context.history[0..-11]
206
+ state[:summary] = summarize(old_messages)
207
+ context.set_state(state)
208
+ end
209
+
210
+ # Use summary + recent history
211
+ conversation_context = [
212
+ state[:summary] ? "Previous context: #{state[:summary]}" : nil,
213
+ *context.history.last(10).map(&:text_content),
214
+ context.input.first&.text_content
215
+ ].compact
216
+
217
+ response = generate_response(conversation_context)
218
+
219
+ SimpleAcp::Models::Message.agent(response)
220
+ end
221
+ ```
222
+
223
+ ### Conversation Reset
224
+
225
+ ```ruby
226
+ server.agent("resettable") do |context|
227
+ command = context.input.first&.text_content
228
+
229
+ if command&.downcase == "reset"
230
+ context.set_state(nil)
231
+ return SimpleAcp::Models::Message.agent("Conversation reset!")
232
+ end
233
+
234
+ # Normal processing...
235
+ end
236
+ ```
237
+
238
+ ## Interactive Workflows
239
+
240
+ ### Quiz Game
241
+
242
+ ```ruby
243
+ server.agent("quiz") do |context|
244
+ state = context.state || {
245
+ score: 0,
246
+ question_index: 0,
247
+ questions: load_questions
248
+ }
249
+
250
+ if state[:question_index] > 0
251
+ # Check previous answer
252
+ answer = context.input.first&.text_content
253
+ correct = state[:questions][state[:question_index] - 1][:answer]
254
+
255
+ if answer&.downcase == correct.downcase
256
+ state[:score] += 1
257
+ end
258
+ end
259
+
260
+ if state[:question_index] >= state[:questions].length
261
+ context.set_state(nil)
262
+ return SimpleAcp::Models::Message.agent(
263
+ "Quiz complete! Score: #{state[:score]}/#{state[:questions].length}"
264
+ )
265
+ end
266
+
267
+ question = state[:questions][state[:question_index]]
268
+ state[:question_index] += 1
269
+ context.set_state(state)
270
+
271
+ SimpleAcp::Models::Message.agent(question[:text])
272
+ end
273
+ ```
274
+
275
+ ### Shopping Assistant
276
+
277
+ ```ruby
278
+ server.agent("shop") do |context|
279
+ cart = context.state || { items: [], total: 0.0 }
280
+ command = context.input.first&.text_content&.downcase
281
+
282
+ response = case command
283
+ when /^add (.+)/
284
+ item = find_product($1)
285
+ if item
286
+ cart[:items] << item
287
+ cart[:total] += item[:price]
288
+ "Added #{item[:name]} ($#{item[:price]})"
289
+ else
290
+ "Product not found"
291
+ end
292
+ when /^remove (.+)/
293
+ item = cart[:items].find { |i| i[:name].downcase.include?($1) }
294
+ if item
295
+ cart[:items].delete(item)
296
+ cart[:total] -= item[:price]
297
+ "Removed #{item[:name]}"
298
+ else
299
+ "Item not in cart"
300
+ end
301
+ when "cart"
302
+ items_list = cart[:items].map { |i| "- #{i[:name]}: $#{i[:price]}" }.join("\n")
303
+ "Cart:\n#{items_list}\nTotal: $#{cart[:total]}"
304
+ when "checkout"
305
+ total = cart[:total]
306
+ context.set_state(nil)
307
+ "Order placed! Total: $#{total}"
308
+ else
309
+ "Commands: add <item>, remove <item>, cart, checkout"
310
+ end
311
+
312
+ context.set_state(cart)
313
+ SimpleAcp::Models::Message.agent(response)
314
+ end
315
+ ```
316
+
317
+ ## Best Practices
318
+
319
+ 1. **Limit history size** - Don't let history grow unbounded
320
+ 2. **Use state wisely** - Store minimal necessary data
321
+ 3. **Handle resets** - Allow users to start fresh
322
+ 4. **Validate state** - Check for expected structure
323
+ 5. **Test edge cases** - Empty history, missing state, etc.
324
+
325
+ ## Next Steps
326
+
327
+ - Review [Sessions](../core-concepts/sessions.md) concept
328
+ - See [Client Sessions](../client/sessions.md) for client-side handling
329
+ - Explore [HTTP Endpoints](http-endpoints.md) for session APIs
@@ -0,0 +1,315 @@
1
+ # Streaming Responses
2
+
3
+ Streaming enables real-time delivery of agent responses via Server-Sent Events (SSE). This provides a better user experience for long-running operations.
4
+
5
+ ## Why Streaming?
6
+
7
+ - **Immediate Feedback**: Users see responses as they're generated
8
+ - **Progress Visibility**: Track long operations in real-time
9
+ - **Resource Efficiency**: Process data incrementally
10
+ - **Better UX**: No waiting for complete responses
11
+
12
+ ## Basic Streaming
13
+
14
+ Return an `Enumerator` to enable streaming:
15
+
16
+ ```ruby
17
+ server.agent("streamer") do |context|
18
+ Enumerator.new do |yielder|
19
+ 5.times do |i|
20
+ yielder << SimpleAcp::Server::RunYield.new(
21
+ SimpleAcp::Models::Message.agent("Message #{i + 1}")
22
+ )
23
+ sleep 0.5
24
+ end
25
+ end
26
+ end
27
+ ```
28
+
29
+ ## RunYield
30
+
31
+ `RunYield` wraps messages for streaming:
32
+
33
+ ```ruby
34
+ # Single message
35
+ yielder << SimpleAcp::Server::RunYield.new(
36
+ SimpleAcp::Models::Message.agent("Hello")
37
+ )
38
+
39
+ # Multiple messages
40
+ yielder << SimpleAcp::Server::RunYield.new(
41
+ [
42
+ SimpleAcp::Models::Message.agent("First"),
43
+ SimpleAcp::Models::Message.agent("Second")
44
+ ]
45
+ )
46
+ ```
47
+
48
+ ## Streaming Patterns
49
+
50
+ ### Token-by-Token
51
+
52
+ Stream individual tokens for typing effect:
53
+
54
+ ```ruby
55
+ server.agent("typer") do |context|
56
+ text = context.input.first&.text_content || "Hello World"
57
+
58
+ Enumerator.new do |yielder|
59
+ text.chars.each do |char|
60
+ yielder << SimpleAcp::Server::RunYield.new(
61
+ SimpleAcp::Models::Message.agent(char)
62
+ )
63
+ sleep 0.05
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ ### Word-by-Word
70
+
71
+ ```ruby
72
+ server.agent("word-stream") do |context|
73
+ text = context.input.first&.text_content || ""
74
+
75
+ Enumerator.new do |yielder|
76
+ text.split.each do |word|
77
+ yielder << SimpleAcp::Server::RunYield.new(
78
+ SimpleAcp::Models::Message.agent(word + " ")
79
+ )
80
+ sleep 0.1
81
+ end
82
+ end
83
+ end
84
+ ```
85
+
86
+ ### Progress Updates
87
+
88
+ ```ruby
89
+ server.agent("processor") do |context|
90
+ items = parse_items(context.input)
91
+ total = items.length
92
+
93
+ Enumerator.new do |yielder|
94
+ items.each_with_index do |item, i|
95
+ # Process item
96
+ result = process(item)
97
+
98
+ # Report progress
99
+ yielder << SimpleAcp::Server::RunYield.new(
100
+ SimpleAcp::Models::Message.agent(
101
+ "Processing #{i + 1}/#{total}: #{result}"
102
+ )
103
+ )
104
+ end
105
+
106
+ yielder << SimpleAcp::Server::RunYield.new(
107
+ SimpleAcp::Models::Message.agent("Complete!")
108
+ )
109
+ end
110
+ end
111
+ ```
112
+
113
+ ### LLM Integration
114
+
115
+ Stream responses from language models:
116
+
117
+ ```ruby
118
+ server.agent("chat") do |context|
119
+ messages = context.history + context.input
120
+
121
+ Enumerator.new do |yielder|
122
+ llm.stream_chat(messages) do |chunk|
123
+ yielder << SimpleAcp::Server::RunYield.new(
124
+ SimpleAcp::Models::Message.agent(chunk)
125
+ )
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ ### Mixed Content
132
+
133
+ Stream different content types:
134
+
135
+ ```ruby
136
+ server.agent("analyzer") do |context|
137
+ data = parse_data(context.input)
138
+
139
+ Enumerator.new do |yielder|
140
+ # Text update
141
+ yielder << SimpleAcp::Server::RunYield.new(
142
+ SimpleAcp::Models::Message.agent("Analyzing data...")
143
+ )
144
+
145
+ result = analyze(data)
146
+
147
+ # JSON result
148
+ yielder << SimpleAcp::Server::RunYield.new(
149
+ SimpleAcp::Models::Message.agent(
150
+ SimpleAcp::Models::MessagePart.json(result)
151
+ )
152
+ )
153
+
154
+ # Summary text
155
+ yielder << SimpleAcp::Server::RunYield.new(
156
+ SimpleAcp::Models::Message.agent("Analysis complete!")
157
+ )
158
+ end
159
+ end
160
+ ```
161
+
162
+ ## Event Types
163
+
164
+ Streaming produces these events:
165
+
166
+ ```mermaid
167
+ sequenceDiagram
168
+ participant Client
169
+ participant Server
170
+
171
+ Server-->>Client: RunStartedEvent
172
+ Server-->>Client: MessageCreatedEvent
173
+ Server-->>Client: MessagePartEvent
174
+ Server-->>Client: MessageCompletedEvent
175
+ Note over Server,Client: Repeat for each message
176
+ Server-->>Client: RunCompletedEvent
177
+ ```
178
+
179
+ ## HTTP Streaming
180
+
181
+ The server uses SSE for HTTP streaming:
182
+
183
+ ```
184
+ POST /runs HTTP/1.1
185
+ Accept: text/event-stream
186
+ Content-Type: application/json
187
+
188
+ {"agent_name": "chat", "input": [...]}
189
+
190
+ HTTP/1.1 200 OK
191
+ Content-Type: text/event-stream
192
+
193
+ event: run_started
194
+ data: {"run_id":"abc123"}
195
+
196
+ event: message_created
197
+ data: {"message":{"role":"agent","parts":[]}}
198
+
199
+ event: message_part
200
+ data: {"part":{"content_type":"text/plain","content":"Hello"}}
201
+
202
+ event: message_completed
203
+ data: {"message":{"role":"agent","parts":[...]}}
204
+
205
+ event: run_completed
206
+ data: {"run":{...}}
207
+ ```
208
+
209
+ ## Server-Side Streaming
210
+
211
+ Execute streaming runs programmatically:
212
+
213
+ ```ruby
214
+ server.run_stream(
215
+ agent_name: "chat",
216
+ input: messages
217
+ ) do |event|
218
+ case event
219
+ when SimpleAcp::Models::MessagePartEvent
220
+ print event.part.content
221
+ when SimpleAcp::Models::RunCompletedEvent
222
+ puts "\nDone!"
223
+ end
224
+ end
225
+ ```
226
+
227
+ ## Error Handling
228
+
229
+ Handle errors during streaming:
230
+
231
+ ```ruby
232
+ server.agent("safe-streamer") do |context|
233
+ Enumerator.new do |yielder|
234
+ begin
235
+ process_stream(context.input) do |chunk|
236
+ yielder << SimpleAcp::Server::RunYield.new(
237
+ SimpleAcp::Models::Message.agent(chunk)
238
+ )
239
+ end
240
+ rescue StreamError => e
241
+ yielder << SimpleAcp::Server::RunYield.new(
242
+ SimpleAcp::Models::Message.agent("Stream error: #{e.message}")
243
+ )
244
+ end
245
+ end
246
+ end
247
+ ```
248
+
249
+ ## Buffering
250
+
251
+ For high-frequency updates, consider buffering:
252
+
253
+ ```ruby
254
+ server.agent("buffered") do |context|
255
+ Enumerator.new do |yielder|
256
+ buffer = []
257
+
258
+ process_items(context.input) do |item|
259
+ buffer << item
260
+
261
+ # Flush every 10 items
262
+ if buffer.length >= 10
263
+ yielder << SimpleAcp::Server::RunYield.new(
264
+ SimpleAcp::Models::Message.agent(buffer.join)
265
+ )
266
+ buffer.clear
267
+ end
268
+ end
269
+
270
+ # Flush remaining
271
+ unless buffer.empty?
272
+ yielder << SimpleAcp::Server::RunYield.new(
273
+ SimpleAcp::Models::Message.agent(buffer.join)
274
+ )
275
+ end
276
+ end
277
+ end
278
+ ```
279
+
280
+ ## Best Practices
281
+
282
+ 1. **Yield frequently** - Don't wait too long between yields
283
+ 2. **Handle errors** - Catch and report errors gracefully
284
+ 3. **Use appropriate chunking** - Balance frequency with overhead
285
+ 4. **Test streaming** - Verify event order and content
286
+ 5. **Monitor memory** - Stream large responses to avoid memory issues
287
+
288
+ ## Testing Streaming
289
+
290
+ ```ruby
291
+ def test_streaming_agent
292
+ events = []
293
+
294
+ server.run_stream(
295
+ agent_name: "streamer",
296
+ input: [SimpleAcp::Models::Message.user("test")]
297
+ ) do |event|
298
+ events << event
299
+ end
300
+
301
+ # Verify event sequence
302
+ assert events.first.is_a?(SimpleAcp::Models::RunStartedEvent)
303
+ assert events.last.is_a?(SimpleAcp::Models::RunCompletedEvent)
304
+
305
+ # Check message events
306
+ message_events = events.select { |e| e.is_a?(SimpleAcp::Models::MessagePartEvent) }
307
+ assert message_events.length > 0
308
+ end
309
+ ```
310
+
311
+ ## Next Steps
312
+
313
+ - Learn about [Multi-Turn Conversations](multi-turn.md)
314
+ - See [Client Streaming](../client/streaming.md) for consuming streams
315
+ - Review [Events](../core-concepts/events.md) for event details