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,337 @@
1
+ # Events
2
+
3
+ Events provide real-time updates during run execution. They enable streaming responses, progress tracking, and reactive client applications.
4
+
5
+ ## Event Types
6
+
7
+ ```mermaid
8
+ graph LR
9
+ R[Run] --> E1[RunStartedEvent]
10
+ R --> E2[MessageCreatedEvent]
11
+ R --> E3[MessagePartEvent]
12
+ R --> E4[RunCompletedEvent]
13
+ R --> E5[RunFailedEvent]
14
+ R --> E6[RunAwaitingEvent]
15
+ ```
16
+
17
+ ### RunStartedEvent
18
+
19
+ Emitted when a run begins execution:
20
+
21
+ ```ruby
22
+ event.type # => "run_started"
23
+ event.run_id # => "550e8400-..."
24
+ ```
25
+
26
+ ### MessageCreatedEvent
27
+
28
+ Emitted when a new message is created:
29
+
30
+ ```ruby
31
+ event.type # => "message_created"
32
+ event.message # => Message object
33
+ ```
34
+
35
+ ### MessagePartEvent
36
+
37
+ Emitted for each part of a message (enables streaming):
38
+
39
+ ```ruby
40
+ event.type # => "message_part"
41
+ event.part # => MessagePart object
42
+ ```
43
+
44
+ ### MessageCompletedEvent
45
+
46
+ Emitted when a message is fully assembled:
47
+
48
+ ```ruby
49
+ event.type # => "message_completed"
50
+ event.message # => Complete Message object
51
+ ```
52
+
53
+ ### RunCompletedEvent
54
+
55
+ Emitted when a run finishes successfully:
56
+
57
+ ```ruby
58
+ event.type # => "run_completed"
59
+ event.run # => Completed Run object
60
+ ```
61
+
62
+ ### RunFailedEvent
63
+
64
+ Emitted when a run encounters an error:
65
+
66
+ ```ruby
67
+ event.type # => "run_failed"
68
+ event.run # => Failed Run object
69
+ event.error # => Error object
70
+ ```
71
+
72
+ ### RunAwaitingEvent
73
+
74
+ Emitted when a run needs additional input:
75
+
76
+ ```ruby
77
+ event.type # => "run_awaiting"
78
+ event.run # => Run object
79
+ event.await_request # => AwaitRequest object
80
+ ```
81
+
82
+ ## Consuming Events
83
+
84
+ ### Streaming Client
85
+
86
+ ```ruby
87
+ client.run_stream(agent: "chat", input: messages) do |event|
88
+ case event
89
+ when SimpleAcp::Models::RunStartedEvent
90
+ puts "Run #{event.run_id} started"
91
+
92
+ when SimpleAcp::Models::MessageCreatedEvent
93
+ puts "New message from #{event.message.role}"
94
+
95
+ when SimpleAcp::Models::MessagePartEvent
96
+ # Stream content as it arrives
97
+ print event.part.content
98
+ $stdout.flush
99
+
100
+ when SimpleAcp::Models::MessageCompletedEvent
101
+ puts "\n--- Message complete ---"
102
+
103
+ when SimpleAcp::Models::RunCompletedEvent
104
+ puts "\nRun completed!"
105
+
106
+ when SimpleAcp::Models::RunFailedEvent
107
+ puts "\nError: #{event.error.message}"
108
+
109
+ when SimpleAcp::Models::RunAwaitingEvent
110
+ puts "\nAwaiting input..."
111
+ end
112
+ end
113
+ ```
114
+
115
+ ### Fetching Historical Events
116
+
117
+ ```ruby
118
+ events = client.run_events(run_id)
119
+
120
+ events.each do |event|
121
+ puts "#{event.type}: #{event.inspect}"
122
+ end
123
+ ```
124
+
125
+ ## Server-Side Events
126
+
127
+ ### Yielding Events
128
+
129
+ Agents emit events by yielding from an Enumerator:
130
+
131
+ ```ruby
132
+ server.agent("streamer") do |context|
133
+ Enumerator.new do |yielder|
134
+ words = context.input.first&.text_content&.split || []
135
+
136
+ words.each do |word|
137
+ # Each yield creates events
138
+ yielder << SimpleAcp::Server::RunYield.new(
139
+ SimpleAcp::Models::Message.agent(word + " ")
140
+ )
141
+ sleep 0.1
142
+ end
143
+ end
144
+ end
145
+ ```
146
+
147
+ ### Event Flow
148
+
149
+ ```mermaid
150
+ sequenceDiagram
151
+ participant Client
152
+ participant Server
153
+ participant Agent
154
+
155
+ Client->>Server: run_stream
156
+ Server->>Agent: execute
157
+
158
+ Agent-->>Server: yield message
159
+ Server-->>Client: RunStartedEvent
160
+ Server-->>Client: MessageCreatedEvent
161
+ Server-->>Client: MessagePartEvent
162
+
163
+ Agent-->>Server: yield message
164
+ Server-->>Client: MessageCreatedEvent
165
+ Server-->>Client: MessagePartEvent
166
+
167
+ Agent-->>Server: return
168
+ Server-->>Client: RunCompletedEvent
169
+ ```
170
+
171
+ ## Server-Sent Events (SSE)
172
+
173
+ When using HTTP streaming, events are delivered via Server-Sent Events:
174
+
175
+ ```
176
+ GET /runs HTTP/1.1
177
+ Accept: text/event-stream
178
+
179
+ HTTP/1.1 200 OK
180
+ Content-Type: text/event-stream
181
+
182
+ event: run_started
183
+ data: {"run_id":"550e8400-..."}
184
+
185
+ event: message_part
186
+ data: {"part":{"content_type":"text/plain","content":"Hello"}}
187
+
188
+ event: run_completed
189
+ data: {"run":{...}}
190
+ ```
191
+
192
+ ### Raw SSE Handling
193
+
194
+ ```ruby
195
+ require 'faraday'
196
+
197
+ conn = Faraday.new(url: 'http://localhost:8000')
198
+
199
+ conn.post('/runs') do |req|
200
+ req.headers['Accept'] = 'text/event-stream'
201
+ req.body = { agent_name: 'chat', input: [...] }.to_json
202
+
203
+ req.options.on_data = proc do |chunk, _|
204
+ # Parse SSE format
205
+ chunk.split("\n\n").each do |event_text|
206
+ # Process event...
207
+ end
208
+ end
209
+ end
210
+ ```
211
+
212
+ ## Common Patterns
213
+
214
+ ### Progress Indicator
215
+
216
+ ```ruby
217
+ total = 0
218
+ client.run_stream(agent: "processor", input: data) do |event|
219
+ case event
220
+ when SimpleAcp::Models::MessagePartEvent
221
+ total += 1
222
+ print "\rProcessed: #{total} items"
223
+ when SimpleAcp::Models::RunCompletedEvent
224
+ puts "\nDone!"
225
+ end
226
+ end
227
+ ```
228
+
229
+ ### Collecting All Output
230
+
231
+ ```ruby
232
+ messages = []
233
+
234
+ client.run_stream(agent: "chat", input: question) do |event|
235
+ case event
236
+ when SimpleAcp::Models::MessageCompletedEvent
237
+ messages << event.message
238
+ end
239
+ end
240
+
241
+ puts "Received #{messages.length} messages"
242
+ ```
243
+
244
+ ### Error Recovery
245
+
246
+ ```ruby
247
+ begin
248
+ client.run_stream(agent: "risky", input: data) do |event|
249
+ case event
250
+ when SimpleAcp::Models::RunFailedEvent
251
+ log_error(event.error)
252
+ # Don't raise, handle gracefully
253
+ end
254
+ end
255
+ rescue Faraday::TimeoutError
256
+ puts "Stream timed out"
257
+ # Retry logic...
258
+ end
259
+ ```
260
+
261
+ ### Building Real-Time UI
262
+
263
+ ```ruby
264
+ # In a web application
265
+ def stream_response(agent, input)
266
+ response.headers['Content-Type'] = 'text/event-stream'
267
+
268
+ client.run_stream(agent: agent, input: input) do |event|
269
+ case event
270
+ when SimpleAcp::Models::MessagePartEvent
271
+ response.write("data: #{event.part.content.to_json}\n\n")
272
+ when SimpleAcp::Models::RunCompletedEvent
273
+ response.write("event: done\ndata: {}\n\n")
274
+ end
275
+ end
276
+ end
277
+ ```
278
+
279
+ ## Event Persistence
280
+
281
+ Events are stored and can be retrieved later:
282
+
283
+ ```ruby
284
+ # Run completes
285
+ run = client.run_sync(agent: "logger", input: [...])
286
+
287
+ # Later, retrieve events
288
+ events = client.run_events(run.run_id)
289
+ events.each { |e| puts e.type }
290
+ ```
291
+
292
+ ## Best Practices
293
+
294
+ ### Handle All Event Types
295
+
296
+ ```ruby
297
+ client.run_stream(agent: "...", input: [...]) do |event|
298
+ case event
299
+ when SimpleAcp::Models::RunStartedEvent
300
+ # Handle start
301
+ when SimpleAcp::Models::MessagePartEvent
302
+ # Handle streaming content
303
+ when SimpleAcp::Models::RunCompletedEvent
304
+ # Handle completion
305
+ when SimpleAcp::Models::RunFailedEvent
306
+ # Handle errors
307
+ when SimpleAcp::Models::RunAwaitingEvent
308
+ # Handle await
309
+ else
310
+ # Log unknown events
311
+ logger.warn("Unknown event: #{event.type}")
312
+ end
313
+ end
314
+ ```
315
+
316
+ ### Use Events for UX
317
+
318
+ ```ruby
319
+ # Show typing indicator
320
+ client.run_stream(agent: "chat", input: [...]) do |event|
321
+ case event
322
+ when SimpleAcp::Models::RunStartedEvent
323
+ show_typing_indicator
324
+ when SimpleAcp::Models::MessagePartEvent
325
+ hide_typing_indicator
326
+ append_content(event.part.content)
327
+ when SimpleAcp::Models::RunCompletedEvent
328
+ mark_complete
329
+ end
330
+ end
331
+ ```
332
+
333
+ ## Next Steps
334
+
335
+ - Explore [Client Streaming](../client/streaming.md) for advanced patterns
336
+ - Learn about [Server Streaming](../server/streaming.md) for generating events
337
+ - See the [API Reference](../api/models.md) for event model details
@@ -0,0 +1,147 @@
1
+ # Core Concepts
2
+
3
+ Understanding the core concepts of the Agent Communication Protocol (ACP) is essential for building effective agent systems with SimpleAcp.
4
+
5
+ ## Overview
6
+
7
+ ACP defines a standardized way for agents, applications, and humans to communicate. The protocol centers around these key concepts:
8
+
9
+ ```mermaid
10
+ graph TB
11
+ subgraph "Core Concepts"
12
+ M[Messages] --> R[Runs]
13
+ A[Agents] --> R
14
+ R --> S[Sessions]
15
+ R --> E[Events]
16
+ end
17
+ ```
18
+
19
+ ## The Building Blocks
20
+
21
+ ### Messages
22
+
23
+ [Messages](messages.md) are the fundamental unit of communication. They carry content between users and agents.
24
+
25
+ ```ruby
26
+ # User sends a message
27
+ message = SimpleAcp::Models::Message.user("What's the weather?")
28
+
29
+ # Agent responds with a message
30
+ response = SimpleAcp::Models::Message.agent("It's sunny and 72°F")
31
+ ```
32
+
33
+ ### Agents
34
+
35
+ [Agents](agents.md) are the processing units that receive input and produce output. They're registered on servers and can be invoked by clients.
36
+
37
+ ```ruby
38
+ server.agent("weather", description: "Gets weather information") do |context|
39
+ # Process context.input and return response
40
+ end
41
+ ```
42
+
43
+ ### Runs
44
+
45
+ [Runs](runs.md) represent a single execution of an agent. They track the lifecycle from start to completion, including any errors or awaiting states.
46
+
47
+ ```ruby
48
+ run = client.run_sync(agent: "weather", input: [...])
49
+ puts run.status # "completed"
50
+ puts run.output # Response messages
51
+ ```
52
+
53
+ ### Sessions
54
+
55
+ [Sessions](sessions.md) maintain state across multiple runs, enabling multi-turn conversations and stateful interactions.
56
+
57
+ ```ruby
58
+ client.use_session("conversation-123")
59
+ # All subsequent runs share this session
60
+ ```
61
+
62
+ ### Events
63
+
64
+ [Events](events.md) provide real-time updates during run execution, enabling streaming responses and progress tracking.
65
+
66
+ ```ruby
67
+ client.run_stream(agent: "...", input: [...]) do |event|
68
+ case event
69
+ when SimpleAcp::Models::MessagePartEvent
70
+ print event.part.content # Stream as it arrives
71
+ end
72
+ end
73
+ ```
74
+
75
+ ## How They Work Together
76
+
77
+ Here's how these concepts interact in a typical flow:
78
+
79
+ ```mermaid
80
+ sequenceDiagram
81
+ participant Client
82
+ participant Server
83
+ participant Agent
84
+ participant Storage
85
+
86
+ Client->>Server: Create Run (Messages)
87
+ Server->>Storage: Store Run
88
+ Server->>Agent: Execute with Context
89
+ Agent-->>Server: Events (streaming)
90
+ Server-->>Client: Events (SSE)
91
+ Agent->>Server: Output Messages
92
+ Server->>Storage: Update Run
93
+ Server->>Client: Run Completed
94
+ ```
95
+
96
+ 1. **Client** creates a run by sending input messages to the server
97
+ 2. **Server** creates a Run record and invokes the appropriate agent
98
+ 3. **Agent** processes the input through its handler, optionally yielding events
99
+ 4. **Events** stream back to the client in real-time
100
+ 5. **Run** completes with output messages
101
+ 6. **Session** (if used) maintains state for subsequent runs
102
+
103
+ ## Deep Dives
104
+
105
+ <div class="grid cards" markdown>
106
+
107
+ - :material-message-text:{ .lg .middle } **Messages**
108
+
109
+ ---
110
+
111
+ Learn about message structure, parts, and content types
112
+
113
+ [:octicons-arrow-right-24: Messages](messages.md)
114
+
115
+ - :material-robot:{ .lg .middle } **Agents**
116
+
117
+ ---
118
+
119
+ Understand agent registration and the handler pattern
120
+
121
+ [:octicons-arrow-right-24: Agents](agents.md)
122
+
123
+ - :material-play-circle:{ .lg .middle } **Runs**
124
+
125
+ ---
126
+
127
+ Explore run lifecycle, status, and execution modes
128
+
129
+ [:octicons-arrow-right-24: Runs](runs.md)
130
+
131
+ - :material-history:{ .lg .middle } **Sessions**
132
+
133
+ ---
134
+
135
+ Master stateful interactions and conversation history
136
+
137
+ [:octicons-arrow-right-24: Sessions](sessions.md)
138
+
139
+ - :material-lightning-bolt:{ .lg .middle } **Events**
140
+
141
+ ---
142
+
143
+ Handle real-time streaming updates
144
+
145
+ [:octicons-arrow-right-24: Events](events.md)
146
+
147
+ </div>
@@ -0,0 +1,211 @@
1
+ # Messages
2
+
3
+ Messages are the fundamental unit of communication in ACP. They carry content between users and agents.
4
+
5
+ ## Message Structure
6
+
7
+ Every message has two essential properties:
8
+
9
+ - **role**: Either `"user"` or `"agent"`
10
+ - **parts**: An array of content parts
11
+
12
+ ```ruby
13
+ message = SimpleAcp::Models::Message.new(
14
+ role: "user",
15
+ parts: [
16
+ SimpleAcp::Models::MessagePart.text("Hello!")
17
+ ]
18
+ )
19
+ ```
20
+
21
+ ## Creating Messages
22
+
23
+ ### Factory Methods
24
+
25
+ The simplest way to create messages:
26
+
27
+ ```ruby
28
+ # User message with text
29
+ user_msg = SimpleAcp::Models::Message.user("What's the weather?")
30
+
31
+ # Agent message with text
32
+ agent_msg = SimpleAcp::Models::Message.agent("It's sunny and 72°F")
33
+ ```
34
+
35
+ ### Multiple Parts
36
+
37
+ Messages can contain multiple content parts:
38
+
39
+ ```ruby
40
+ message = SimpleAcp::Models::Message.user(
41
+ SimpleAcp::Models::MessagePart.text("Here's my data:"),
42
+ SimpleAcp::Models::MessagePart.json({ values: [1, 2, 3] })
43
+ )
44
+ ```
45
+
46
+ ### From Hash
47
+
48
+ Parse messages from JSON/hash data:
49
+
50
+ ```ruby
51
+ message = SimpleAcp::Models::Message.from_hash({
52
+ "role" => "user",
53
+ "parts" => [
54
+ { "content_type" => "text/plain", "content" => "Hello" }
55
+ ]
56
+ })
57
+ ```
58
+
59
+ ## Message Parts
60
+
61
+ Each part has a content type and content payload.
62
+
63
+ ### Text Content
64
+
65
+ Plain text is the most common content type:
66
+
67
+ ```ruby
68
+ part = SimpleAcp::Models::MessagePart.text("Hello, world!")
69
+
70
+ part.content_type # => "text/plain"
71
+ part.content # => "Hello, world!"
72
+ part.text? # => true
73
+ ```
74
+
75
+ ### JSON Content
76
+
77
+ Structured data as JSON:
78
+
79
+ ```ruby
80
+ part = SimpleAcp::Models::MessagePart.json({
81
+ temperature: 72,
82
+ conditions: "sunny",
83
+ humidity: 45
84
+ })
85
+
86
+ part.content_type # => "application/json"
87
+ part.content # => "{\"temperature\":72,...}"
88
+ part.json? # => true
89
+ ```
90
+
91
+ ### Image Content
92
+
93
+ Base64-encoded images:
94
+
95
+ ```ruby
96
+ image_data = File.read("photo.png")
97
+ part = SimpleAcp::Models::MessagePart.image(
98
+ Base64.strict_encode64(image_data),
99
+ mime_type: "image/png"
100
+ )
101
+
102
+ part.content_type # => "image/png"
103
+ part.content_encoding # => "base64"
104
+ part.image? # => true
105
+ ```
106
+
107
+ ### URL References
108
+
109
+ Reference external content by URL:
110
+
111
+ ```ruby
112
+ part = SimpleAcp::Models::MessagePart.from_url(
113
+ "https://example.com/image.jpg",
114
+ content_type: "image/jpeg"
115
+ )
116
+
117
+ part.content_url # => "https://example.com/image.jpg"
118
+ ```
119
+
120
+ ## Accessing Content
121
+
122
+ ### Text Content
123
+
124
+ Get combined text from all parts:
125
+
126
+ ```ruby
127
+ message = SimpleAcp::Models::Message.user(
128
+ SimpleAcp::Models::MessagePart.text("Hello "),
129
+ SimpleAcp::Models::MessagePart.text("World!")
130
+ )
131
+
132
+ message.text_content # => "Hello World!"
133
+ ```
134
+
135
+ ### Individual Parts
136
+
137
+ Access parts directly:
138
+
139
+ ```ruby
140
+ message.parts.each do |part|
141
+ puts "Type: #{part.content_type}"
142
+ puts "Content: #{part.content}"
143
+ end
144
+ ```
145
+
146
+ ### Type Checking
147
+
148
+ Check content types:
149
+
150
+ ```ruby
151
+ part.text? # Is text/plain?
152
+ part.json? # Is application/json?
153
+ part.image? # Is image/*?
154
+ ```
155
+
156
+ ## Serialization
157
+
158
+ Messages can be serialized to JSON:
159
+
160
+ ```ruby
161
+ message = SimpleAcp::Models::Message.user("Hello")
162
+
163
+ # To hash
164
+ hash = message.to_h
165
+ # => { role: "user", parts: [...] }
166
+
167
+ # To JSON
168
+ json = message.to_json
169
+ # => '{"role":"user","parts":[...]}'
170
+ ```
171
+
172
+ ## Common Patterns
173
+
174
+ ### Echo Agent
175
+
176
+ ```ruby
177
+ server.agent("echo") do |context|
178
+ context.input.map do |msg|
179
+ SimpleAcp::Models::Message.agent(msg.text_content)
180
+ end
181
+ end
182
+ ```
183
+
184
+ ### Transform Content
185
+
186
+ ```ruby
187
+ server.agent("uppercase") do |context|
188
+ text = context.input.first&.text_content || ""
189
+ SimpleAcp::Models::Message.agent(text.upcase)
190
+ end
191
+ ```
192
+
193
+ ### Multi-Part Response
194
+
195
+ ```ruby
196
+ server.agent("analysis") do |context|
197
+ SimpleAcp::Models::Message.agent(
198
+ SimpleAcp::Models::MessagePart.text("Analysis complete:"),
199
+ SimpleAcp::Models::MessagePart.json({
200
+ word_count: 150,
201
+ sentiment: "positive"
202
+ })
203
+ )
204
+ end
205
+ ```
206
+
207
+ ## Next Steps
208
+
209
+ - Learn about [Agents](agents.md) that process messages
210
+ - Understand [Runs](runs.md) that execute agent logic
211
+ - Explore [Events](events.md) for streaming message delivery