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,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
|