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.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/CHANGELOG.md +5 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +385 -0
- data/Rakefile +13 -0
- data/docs/api/client-base.md +383 -0
- data/docs/api/index.md +159 -0
- data/docs/api/models.md +286 -0
- data/docs/api/server-base.md +379 -0
- data/docs/api/storage.md +347 -0
- data/docs/assets/images/simple_acp.jpg +0 -0
- data/docs/client/index.md +279 -0
- data/docs/client/sessions.md +324 -0
- data/docs/client/streaming.md +345 -0
- data/docs/client/sync-async.md +308 -0
- data/docs/core-concepts/agents.md +253 -0
- data/docs/core-concepts/events.md +337 -0
- data/docs/core-concepts/index.md +147 -0
- data/docs/core-concepts/messages.md +211 -0
- data/docs/core-concepts/runs.md +278 -0
- data/docs/core-concepts/sessions.md +281 -0
- data/docs/examples.md +659 -0
- data/docs/getting-started/configuration.md +166 -0
- data/docs/getting-started/index.md +62 -0
- data/docs/getting-started/installation.md +95 -0
- data/docs/getting-started/quick-start.md +189 -0
- data/docs/index.md +119 -0
- data/docs/server/creating-agents.md +360 -0
- data/docs/server/http-endpoints.md +411 -0
- data/docs/server/index.md +218 -0
- data/docs/server/multi-turn.md +329 -0
- data/docs/server/streaming.md +315 -0
- data/docs/storage/custom.md +414 -0
- data/docs/storage/index.md +176 -0
- data/docs/storage/memory.md +198 -0
- data/docs/storage/postgresql.md +350 -0
- data/docs/storage/redis.md +287 -0
- data/examples/01_basic/client.rb +88 -0
- data/examples/01_basic/server.rb +100 -0
- data/examples/02_async_execution/client.rb +107 -0
- data/examples/02_async_execution/server.rb +56 -0
- data/examples/03_run_management/client.rb +115 -0
- data/examples/03_run_management/server.rb +84 -0
- data/examples/04_rich_messages/client.rb +160 -0
- data/examples/04_rich_messages/server.rb +180 -0
- data/examples/05_await_resume/client.rb +164 -0
- data/examples/05_await_resume/server.rb +114 -0
- data/examples/06_agent_metadata/client.rb +188 -0
- data/examples/06_agent_metadata/server.rb +192 -0
- data/examples/README.md +252 -0
- data/examples/run_demo.sh +137 -0
- data/lib/simple_acp/client/base.rb +448 -0
- data/lib/simple_acp/client/sse.rb +141 -0
- data/lib/simple_acp/models/agent_manifest.rb +129 -0
- data/lib/simple_acp/models/await.rb +123 -0
- data/lib/simple_acp/models/base.rb +147 -0
- data/lib/simple_acp/models/errors.rb +102 -0
- data/lib/simple_acp/models/events.rb +256 -0
- data/lib/simple_acp/models/message.rb +235 -0
- data/lib/simple_acp/models/message_part.rb +225 -0
- data/lib/simple_acp/models/metadata.rb +161 -0
- data/lib/simple_acp/models/run.rb +298 -0
- data/lib/simple_acp/models/session.rb +137 -0
- data/lib/simple_acp/models/types.rb +210 -0
- data/lib/simple_acp/server/agent.rb +116 -0
- data/lib/simple_acp/server/app.rb +264 -0
- data/lib/simple_acp/server/base.rb +510 -0
- data/lib/simple_acp/server/context.rb +210 -0
- data/lib/simple_acp/server/falcon_runner.rb +61 -0
- data/lib/simple_acp/storage/base.rb +129 -0
- data/lib/simple_acp/storage/memory.rb +108 -0
- data/lib/simple_acp/storage/postgresql.rb +233 -0
- data/lib/simple_acp/storage/redis.rb +178 -0
- data/lib/simple_acp/version.rb +5 -0
- data/lib/simple_acp.rb +91 -0
- data/mkdocs.yml +152 -0
- data/sig/simple_acp.rbs +4 -0
- 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
|