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,414 @@
|
|
|
1
|
+
# Custom Storage Backends
|
|
2
|
+
|
|
3
|
+
Build your own storage backend to integrate with any data store or implement special requirements.
|
|
4
|
+
|
|
5
|
+
## Interface
|
|
6
|
+
|
|
7
|
+
All storage backends extend `SimpleAcp::Storage::Base`:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
module SimpleAcp
|
|
11
|
+
module Storage
|
|
12
|
+
class Base
|
|
13
|
+
def initialize(options = {})
|
|
14
|
+
def get_run(run_id)
|
|
15
|
+
def save_run(run)
|
|
16
|
+
def delete_run(run_id)
|
|
17
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
18
|
+
|
|
19
|
+
def get_session(session_id)
|
|
20
|
+
def save_session(session)
|
|
21
|
+
def delete_session(session_id)
|
|
22
|
+
|
|
23
|
+
def add_event(run_id, event)
|
|
24
|
+
def get_events(run_id, limit: 100, offset: 0)
|
|
25
|
+
|
|
26
|
+
def close
|
|
27
|
+
def ping
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Minimal Implementation
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
class MyStorage < SimpleAcp::Storage::Base
|
|
37
|
+
def initialize(options = {})
|
|
38
|
+
super
|
|
39
|
+
@runs = {}
|
|
40
|
+
@sessions = {}
|
|
41
|
+
@events = {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Runs
|
|
45
|
+
def get_run(run_id)
|
|
46
|
+
@runs[run_id]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def save_run(run)
|
|
50
|
+
@runs[run.run_id] = run
|
|
51
|
+
run
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def delete_run(run_id)
|
|
55
|
+
@events.delete(run_id)
|
|
56
|
+
@runs.delete(run_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
60
|
+
runs = @runs.values
|
|
61
|
+
runs = runs.select { |r| r.agent_name == agent_name } if agent_name
|
|
62
|
+
runs = runs.select { |r| r.session_id == session_id } if session_id
|
|
63
|
+
runs = runs.sort_by { |r| r.created_at || Time.at(0) }.reverse
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
runs: runs.drop(offset).take(limit),
|
|
67
|
+
total: runs.length
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sessions
|
|
72
|
+
def get_session(session_id)
|
|
73
|
+
@sessions[session_id]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def save_session(session)
|
|
77
|
+
@sessions[session.id] = session
|
|
78
|
+
session
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def delete_session(session_id)
|
|
82
|
+
@sessions.delete(session_id)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Events
|
|
86
|
+
def add_event(run_id, event)
|
|
87
|
+
@events[run_id] ||= []
|
|
88
|
+
@events[run_id] << event
|
|
89
|
+
event
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def get_events(run_id, limit: 100, offset: 0)
|
|
93
|
+
events = @events[run_id] || []
|
|
94
|
+
events.drop(offset).take(limit)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Lifecycle
|
|
98
|
+
def close
|
|
99
|
+
# Cleanup if needed
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def ping
|
|
103
|
+
true
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Example: SQLite Backend
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
require 'sequel'
|
|
112
|
+
|
|
113
|
+
class SQLiteStorage < SimpleAcp::Storage::Base
|
|
114
|
+
def initialize(options = {})
|
|
115
|
+
super
|
|
116
|
+
@db = Sequel.sqlite(options[:path] || ':memory:')
|
|
117
|
+
setup_tables
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def get_run(run_id)
|
|
121
|
+
row = @db[:runs].where(run_id: run_id).first
|
|
122
|
+
return nil unless row
|
|
123
|
+
deserialize_run(row)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def save_run(run)
|
|
127
|
+
data = serialize_run(run)
|
|
128
|
+
|
|
129
|
+
if @db[:runs].where(run_id: run.run_id).count > 0
|
|
130
|
+
@db[:runs].where(run_id: run.run_id).update(data)
|
|
131
|
+
else
|
|
132
|
+
@db[:runs].insert(data)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
run
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def delete_run(run_id)
|
|
139
|
+
@db[:events].where(run_id: run_id).delete
|
|
140
|
+
@db[:runs].where(run_id: run_id).delete
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def list_runs(agent_name: nil, session_id: nil, limit: 10, offset: 0)
|
|
144
|
+
dataset = @db[:runs]
|
|
145
|
+
dataset = dataset.where(agent_name: agent_name) if agent_name
|
|
146
|
+
dataset = dataset.where(session_id: session_id) if session_id
|
|
147
|
+
|
|
148
|
+
total = dataset.count
|
|
149
|
+
rows = dataset
|
|
150
|
+
.order(Sequel.desc(:created_at))
|
|
151
|
+
.limit(limit)
|
|
152
|
+
.offset(offset)
|
|
153
|
+
.all
|
|
154
|
+
|
|
155
|
+
{
|
|
156
|
+
runs: rows.map { |r| deserialize_run(r) },
|
|
157
|
+
total: total
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# ... similar for sessions and events
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def setup_tables
|
|
166
|
+
@db.create_table?(:runs) do
|
|
167
|
+
String :run_id, primary_key: true
|
|
168
|
+
String :agent_name
|
|
169
|
+
String :session_id
|
|
170
|
+
String :status
|
|
171
|
+
Text :output
|
|
172
|
+
Text :error
|
|
173
|
+
DateTime :created_at
|
|
174
|
+
DateTime :finished_at
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
@db.create_table?(:sessions) do
|
|
178
|
+
String :id, primary_key: true
|
|
179
|
+
Text :history
|
|
180
|
+
Text :state
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
@db.create_table?(:events) do
|
|
184
|
+
primary_key :id
|
|
185
|
+
String :run_id
|
|
186
|
+
String :event_type
|
|
187
|
+
Text :data
|
|
188
|
+
DateTime :created_at
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def serialize_run(run)
|
|
193
|
+
{
|
|
194
|
+
run_id: run.run_id,
|
|
195
|
+
agent_name: run.agent_name,
|
|
196
|
+
session_id: run.session_id,
|
|
197
|
+
status: run.status,
|
|
198
|
+
output: run.output.to_json,
|
|
199
|
+
error: run.error&.to_json,
|
|
200
|
+
created_at: run.created_at,
|
|
201
|
+
finished_at: run.finished_at
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def deserialize_run(row)
|
|
206
|
+
output = row[:output] ? JSON.parse(row[:output]).map { |m|
|
|
207
|
+
SimpleAcp::Models::Message.from_hash(m)
|
|
208
|
+
} : []
|
|
209
|
+
|
|
210
|
+
run = SimpleAcp::Models::Run.new(
|
|
211
|
+
run_id: row[:run_id],
|
|
212
|
+
agent_name: row[:agent_name],
|
|
213
|
+
session_id: row[:session_id],
|
|
214
|
+
status: row[:status]
|
|
215
|
+
)
|
|
216
|
+
run.instance_variable_set(:@output, output)
|
|
217
|
+
run.instance_variable_set(:@created_at, row[:created_at])
|
|
218
|
+
run.instance_variable_set(:@finished_at, row[:finished_at])
|
|
219
|
+
run
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Example: S3 Backend
|
|
225
|
+
|
|
226
|
+
For archival storage:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
require 'aws-sdk-s3'
|
|
230
|
+
|
|
231
|
+
class S3Storage < SimpleAcp::Storage::Base
|
|
232
|
+
def initialize(options = {})
|
|
233
|
+
super
|
|
234
|
+
@s3 = Aws::S3::Client.new(
|
|
235
|
+
region: options[:region] || 'us-east-1'
|
|
236
|
+
)
|
|
237
|
+
@bucket = options[:bucket]
|
|
238
|
+
@prefix = options[:prefix] || 'acp/'
|
|
239
|
+
|
|
240
|
+
# Use memory for active data, S3 for archive
|
|
241
|
+
@active = SimpleAcp::Storage::Memory.new
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def get_run(run_id)
|
|
245
|
+
# Check active first
|
|
246
|
+
run = @active.get_run(run_id)
|
|
247
|
+
return run if run
|
|
248
|
+
|
|
249
|
+
# Fall back to S3
|
|
250
|
+
load_from_s3("runs/#{run_id}")
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def save_run(run)
|
|
254
|
+
@active.save_run(run)
|
|
255
|
+
|
|
256
|
+
# Archive completed runs
|
|
257
|
+
if run.terminal?
|
|
258
|
+
save_to_s3("runs/#{run.run_id}", run.to_json)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
run
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# ... implement other methods
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
def save_to_s3(key, data)
|
|
269
|
+
@s3.put_object(
|
|
270
|
+
bucket: @bucket,
|
|
271
|
+
key: "#{@prefix}#{key}",
|
|
272
|
+
body: data
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def load_from_s3(key)
|
|
277
|
+
response = @s3.get_object(
|
|
278
|
+
bucket: @bucket,
|
|
279
|
+
key: "#{@prefix}#{key}"
|
|
280
|
+
)
|
|
281
|
+
JSON.parse(response.body.read)
|
|
282
|
+
rescue Aws::S3::Errors::NoSuchKey
|
|
283
|
+
nil
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Example: Hybrid Backend
|
|
289
|
+
|
|
290
|
+
Combine multiple backends:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
class HybridStorage < SimpleAcp::Storage::Base
|
|
294
|
+
def initialize(options = {})
|
|
295
|
+
super
|
|
296
|
+
@cache = SimpleAcp::Storage::Redis.new(
|
|
297
|
+
url: options[:redis_url],
|
|
298
|
+
ttl: 3600 # 1 hour cache
|
|
299
|
+
)
|
|
300
|
+
@persistent = SimpleAcp::Storage::PostgreSQL.new(
|
|
301
|
+
url: options[:database_url]
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def get_run(run_id)
|
|
306
|
+
# Try cache first
|
|
307
|
+
run = @cache.get_run(run_id)
|
|
308
|
+
return run if run
|
|
309
|
+
|
|
310
|
+
# Fall back to database
|
|
311
|
+
run = @persistent.get_run(run_id)
|
|
312
|
+
|
|
313
|
+
# Populate cache
|
|
314
|
+
@cache.save_run(run) if run
|
|
315
|
+
|
|
316
|
+
run
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def save_run(run)
|
|
320
|
+
# Write to both
|
|
321
|
+
@cache.save_run(run)
|
|
322
|
+
@persistent.save_run(run)
|
|
323
|
+
run
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def delete_run(run_id)
|
|
327
|
+
@cache.delete_run(run_id)
|
|
328
|
+
@persistent.delete_run(run_id)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# ... implement other methods delegating appropriately
|
|
332
|
+
end
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Testing Custom Backends
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
class CustomStorageTest < Minitest::Test
|
|
339
|
+
def setup
|
|
340
|
+
@storage = MyStorage.new
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def teardown
|
|
344
|
+
@storage.close
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def test_save_and_get_run
|
|
348
|
+
run = create_test_run
|
|
349
|
+
@storage.save_run(run)
|
|
350
|
+
|
|
351
|
+
loaded = @storage.get_run(run.run_id)
|
|
352
|
+
assert_equal run.run_id, loaded.run_id
|
|
353
|
+
assert_equal run.agent_name, loaded.agent_name
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def test_list_runs_filters
|
|
357
|
+
@storage.save_run(create_test_run(agent_name: "a"))
|
|
358
|
+
@storage.save_run(create_test_run(agent_name: "b"))
|
|
359
|
+
|
|
360
|
+
result = @storage.list_runs(agent_name: "a")
|
|
361
|
+
assert_equal 1, result[:total]
|
|
362
|
+
assert_equal "a", result[:runs].first.agent_name
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def test_session_persistence
|
|
366
|
+
session = create_test_session
|
|
367
|
+
@storage.save_session(session)
|
|
368
|
+
|
|
369
|
+
loaded = @storage.get_session(session.id)
|
|
370
|
+
assert_equal session.id, loaded.id
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def test_events_ordering
|
|
374
|
+
events = 3.times.map { |i| create_test_event(i) }
|
|
375
|
+
events.each { |e| @storage.add_event("run-1", e) }
|
|
376
|
+
|
|
377
|
+
loaded = @storage.get_events("run-1")
|
|
378
|
+
assert_equal 3, loaded.length
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
private
|
|
382
|
+
|
|
383
|
+
def create_test_run(overrides = {})
|
|
384
|
+
SimpleAcp::Models::Run.new({
|
|
385
|
+
run_id: SecureRandom.uuid,
|
|
386
|
+
agent_name: "test",
|
|
387
|
+
status: "completed"
|
|
388
|
+
}.merge(overrides))
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def create_test_session
|
|
392
|
+
SimpleAcp::Models::Session.new(id: SecureRandom.uuid)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def create_test_event(index)
|
|
396
|
+
SimpleAcp::Models::MessagePartEvent.new(
|
|
397
|
+
part: SimpleAcp::Models::MessagePart.text("Event #{index}")
|
|
398
|
+
)
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Best Practices
|
|
404
|
+
|
|
405
|
+
1. **Implement all methods** - Don't leave `NotImplementedError`
|
|
406
|
+
2. **Handle missing data** - Return `nil` for not found
|
|
407
|
+
3. **Be thread-safe** - Use appropriate locking
|
|
408
|
+
4. **Test thoroughly** - Cover edge cases
|
|
409
|
+
5. **Document limitations** - Note any constraints
|
|
410
|
+
|
|
411
|
+
## Next Steps
|
|
412
|
+
|
|
413
|
+
- Review built-in [Memory](memory.md), [Redis](redis.md), and [PostgreSQL](postgresql.md) implementations
|
|
414
|
+
- See [API Reference](../api/storage.md) for interface details
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Storage Backends
|
|
2
|
+
|
|
3
|
+
Storage backends persist runs, sessions, and events. Choose the right backend for your deployment scenario.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TB
|
|
9
|
+
S[Server] --> ST{Storage}
|
|
10
|
+
ST --> M[Memory]
|
|
11
|
+
ST --> R[Redis]
|
|
12
|
+
ST --> P[PostgreSQL]
|
|
13
|
+
ST --> C[Custom]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Available Backends
|
|
17
|
+
|
|
18
|
+
| Backend | Use Case | Persistence | Scaling |
|
|
19
|
+
|---------|----------|-------------|---------|
|
|
20
|
+
| [Memory](memory.md) | Development, testing | None | Single process |
|
|
21
|
+
| [Redis](redis.md) | Production, distributed | TTL-based | Horizontal |
|
|
22
|
+
| [PostgreSQL](postgresql.md) | Production, audit | Permanent | Vertical |
|
|
23
|
+
| [Custom](custom.md) | Special requirements | Variable | Variable |
|
|
24
|
+
|
|
25
|
+
## Quick Comparison
|
|
26
|
+
|
|
27
|
+
### Memory
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
storage = SimpleAcp::Storage::Memory.new
|
|
31
|
+
server = SimpleAcp::Server::Base.new(storage: storage)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- No dependencies
|
|
35
|
+
- Fast, in-process
|
|
36
|
+
- Data lost on restart
|
|
37
|
+
- Single process only
|
|
38
|
+
|
|
39
|
+
### Redis
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require 'simple_acp/storage/redis'
|
|
43
|
+
|
|
44
|
+
storage = SimpleAcp::Storage::Redis.new(
|
|
45
|
+
url: "redis://localhost:6379",
|
|
46
|
+
ttl: 86400
|
|
47
|
+
)
|
|
48
|
+
server = SimpleAcp::Server::Base.new(storage: storage)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Requires Redis server
|
|
52
|
+
- Automatic expiration
|
|
53
|
+
- Multi-process support
|
|
54
|
+
- Good for scaling
|
|
55
|
+
|
|
56
|
+
### PostgreSQL
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
require 'simple_acp/storage/postgresql'
|
|
60
|
+
|
|
61
|
+
storage = SimpleAcp::Storage::PostgreSQL.new(
|
|
62
|
+
url: "postgres://localhost/acp"
|
|
63
|
+
)
|
|
64
|
+
server = SimpleAcp::Server::Base.new(storage: storage)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- Requires PostgreSQL
|
|
68
|
+
- Permanent storage
|
|
69
|
+
- Full query capabilities
|
|
70
|
+
- Good for auditing
|
|
71
|
+
|
|
72
|
+
## Choosing a Backend
|
|
73
|
+
|
|
74
|
+
### Use Memory When
|
|
75
|
+
|
|
76
|
+
- Developing locally
|
|
77
|
+
- Running tests
|
|
78
|
+
- Single-process deployment
|
|
79
|
+
- Data persistence not needed
|
|
80
|
+
|
|
81
|
+
### Use Redis When
|
|
82
|
+
|
|
83
|
+
- Multiple server processes
|
|
84
|
+
- Horizontal scaling needed
|
|
85
|
+
- TTL-based cleanup acceptable
|
|
86
|
+
- Real-time performance critical
|
|
87
|
+
|
|
88
|
+
### Use PostgreSQL When
|
|
89
|
+
|
|
90
|
+
- Audit trail required
|
|
91
|
+
- Complex queries needed
|
|
92
|
+
- Long-term data retention
|
|
93
|
+
- Compliance requirements
|
|
94
|
+
|
|
95
|
+
## Storage Interface
|
|
96
|
+
|
|
97
|
+
All backends implement the same interface:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Storage::Base
|
|
101
|
+
def get_run(run_id)
|
|
102
|
+
def save_run(run)
|
|
103
|
+
def delete_run(run_id)
|
|
104
|
+
def list_runs(agent_name:, session_id:, limit:, offset:)
|
|
105
|
+
|
|
106
|
+
def get_session(session_id)
|
|
107
|
+
def save_session(session)
|
|
108
|
+
def delete_session(session_id)
|
|
109
|
+
|
|
110
|
+
def add_event(run_id, event)
|
|
111
|
+
def get_events(run_id, limit:, offset:)
|
|
112
|
+
|
|
113
|
+
def close
|
|
114
|
+
def ping
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
### Environment Variables
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Redis
|
|
124
|
+
export REDIS_URL=redis://localhost:6379
|
|
125
|
+
|
|
126
|
+
# PostgreSQL
|
|
127
|
+
export DATABASE_URL=postgres://user:pass@host/db
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Connection Options
|
|
131
|
+
|
|
132
|
+
Each backend accepts specific connection options. See individual backend documentation for details.
|
|
133
|
+
|
|
134
|
+
## In This Section
|
|
135
|
+
|
|
136
|
+
<div class="grid cards" markdown>
|
|
137
|
+
|
|
138
|
+
- :material-memory:{ .lg .middle } **Memory**
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
In-process storage for development
|
|
143
|
+
|
|
144
|
+
[:octicons-arrow-right-24: Memory](memory.md)
|
|
145
|
+
|
|
146
|
+
- :material-database-clock:{ .lg .middle } **Redis**
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
Distributed storage with TTL
|
|
151
|
+
|
|
152
|
+
[:octicons-arrow-right-24: Redis](redis.md)
|
|
153
|
+
|
|
154
|
+
- :material-elephant:{ .lg .middle } **PostgreSQL**
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
Persistent relational storage
|
|
159
|
+
|
|
160
|
+
[:octicons-arrow-right-24: PostgreSQL](postgresql.md)
|
|
161
|
+
|
|
162
|
+
- :material-cog:{ .lg .middle } **Custom Backends**
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
Build your own storage
|
|
167
|
+
|
|
168
|
+
[:octicons-arrow-right-24: Custom](custom.md)
|
|
169
|
+
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
## Next Steps
|
|
173
|
+
|
|
174
|
+
- Start with [Memory](memory.md) for development
|
|
175
|
+
- Move to [Redis](redis.md) or [PostgreSQL](postgresql.md) for production
|
|
176
|
+
- Build a [Custom Backend](custom.md) for special needs
|