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,287 @@
1
+ # Redis Storage
2
+
3
+ Redis storage provides distributed, TTL-based storage suitable for production deployments with multiple server processes.
4
+
5
+ ## Features
6
+
7
+ - **Distributed** - Share state across multiple processes
8
+ - **TTL-based** - Automatic expiration of old data
9
+ - **Fast** - Sub-millisecond operations
10
+ - **Scalable** - Supports Redis clustering
11
+
12
+ ## Requirements
13
+
14
+ Add the Redis gem to your Gemfile:
15
+
16
+ ```ruby
17
+ gem 'redis', '~> 5.0'
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Basic Setup
23
+
24
+ ```ruby
25
+ require 'simple_acp'
26
+ require 'simple_acp/storage/redis'
27
+
28
+ storage = SimpleAcp::Storage::Redis.new(
29
+ url: "redis://localhost:6379"
30
+ )
31
+
32
+ server = SimpleAcp::Server::Base.new(storage: storage)
33
+ ```
34
+
35
+ ### Configuration Options
36
+
37
+ ```ruby
38
+ storage = SimpleAcp::Storage::Redis.new(
39
+ # Connection
40
+ url: "redis://localhost:6379", # Redis URL
41
+ host: "localhost", # Or specify individually
42
+ port: 6379,
43
+ db: 0,
44
+ password: "secret",
45
+ ssl: false,
46
+
47
+ # Or use existing connection
48
+ redis: existing_redis_client,
49
+
50
+ # Behavior
51
+ ttl: 86400, # TTL in seconds (default: 24 hours)
52
+ prefix: "acp:" # Key prefix (default: "acp:")
53
+ )
54
+ ```
55
+
56
+ ### Environment Variables
57
+
58
+ ```ruby
59
+ # Uses REDIS_URL if not specified
60
+ storage = SimpleAcp::Storage::Redis.new
61
+
62
+ # Reads from:
63
+ # ENV['REDIS_URL'] || "redis://localhost:6379"
64
+ ```
65
+
66
+ ## Data Model
67
+
68
+ ### Key Structure
69
+
70
+ ```
71
+ acp:run:{run_id} # Run data (JSON)
72
+ acp:session:{session_id} # Session data (JSON)
73
+ acp:events:{run_id} # Events list (JSON items)
74
+ acp:agent_runs:{agent_name} # Set of run IDs for agent
75
+ acp:session_runs:{session_id} # Set of run IDs for session
76
+ ```
77
+
78
+ ### TTL Behavior
79
+
80
+ All keys expire after the configured TTL:
81
+
82
+ ```ruby
83
+ storage = SimpleAcp::Storage::Redis.new(
84
+ ttl: 3600 # 1 hour
85
+ )
86
+
87
+ # Run expires 1 hour after last update
88
+ storage.save_run(run)
89
+ ```
90
+
91
+ ## Operations
92
+
93
+ ### Runs
94
+
95
+ ```ruby
96
+ # Save (sets TTL)
97
+ storage.save_run(run)
98
+
99
+ # Get
100
+ run = storage.get_run("run-id")
101
+
102
+ # Delete (also removes from indexes)
103
+ storage.delete_run("run-id")
104
+
105
+ # List by agent
106
+ result = storage.list_runs(agent_name: "echo")
107
+
108
+ # List by session
109
+ result = storage.list_runs(session_id: "session-123")
110
+ ```
111
+
112
+ ### Sessions
113
+
114
+ ```ruby
115
+ # Save (sets TTL)
116
+ storage.save_session(session)
117
+
118
+ # Get
119
+ session = storage.get_session("session-id")
120
+
121
+ # Delete
122
+ storage.delete_session("session-id")
123
+ ```
124
+
125
+ ### Events
126
+
127
+ ```ruby
128
+ # Add (extends TTL)
129
+ storage.add_event("run-id", event)
130
+
131
+ # Get
132
+ events = storage.get_events("run-id", limit: 100, offset: 0)
133
+ ```
134
+
135
+ ## Connection Management
136
+
137
+ ### Connection Pooling
138
+
139
+ Use connection pooling in multi-threaded environments:
140
+
141
+ ```ruby
142
+ require 'connection_pool'
143
+
144
+ pool = ConnectionPool.new(size: 10) do
145
+ Redis.new(url: ENV['REDIS_URL'])
146
+ end
147
+
148
+ # Create storage with pooled connection
149
+ storage = SimpleAcp::Storage::Redis.new(redis: pool)
150
+ ```
151
+
152
+ ### Close Connection
153
+
154
+ ```ruby
155
+ storage.close
156
+ ```
157
+
158
+ ### Health Check
159
+
160
+ ```ruby
161
+ if storage.ping
162
+ puts "Redis is healthy"
163
+ else
164
+ puts "Redis connection failed"
165
+ end
166
+ ```
167
+
168
+ ## Scaling
169
+
170
+ ### Redis Cluster
171
+
172
+ For high availability and horizontal scaling:
173
+
174
+ ```ruby
175
+ require 'redis-cluster-client'
176
+
177
+ redis = RedisClient.cluster(
178
+ nodes: [
179
+ { host: "node1.example.com", port: 6379 },
180
+ { host: "node2.example.com", port: 6379 },
181
+ { host: "node3.example.com", port: 6379 }
182
+ ]
183
+ ).new_pool(timeout: 5, size: 10)
184
+
185
+ storage = SimpleAcp::Storage::Redis.new(redis: redis)
186
+ ```
187
+
188
+ ### Multiple Processes
189
+
190
+ Redis naturally supports multiple processes:
191
+
192
+ ```ruby
193
+ # Process 1
194
+ server1 = SimpleAcp::Server::Base.new(
195
+ storage: SimpleAcp::Storage::Redis.new(url: ENV['REDIS_URL'])
196
+ )
197
+
198
+ # Process 2 (shares data with Process 1)
199
+ server2 = SimpleAcp::Server::Base.new(
200
+ storage: SimpleAcp::Storage::Redis.new(url: ENV['REDIS_URL'])
201
+ )
202
+ ```
203
+
204
+ ## Production Configuration
205
+
206
+ ### Recommended Settings
207
+
208
+ ```ruby
209
+ storage = SimpleAcp::Storage::Redis.new(
210
+ url: ENV['REDIS_URL'],
211
+ ttl: 86400 * 7, # 7 days
212
+ prefix: "myapp:acp:" # Namespace for your app
213
+ )
214
+ ```
215
+
216
+ ### With Sentinel
217
+
218
+ ```ruby
219
+ redis = Redis.new(
220
+ url: "redis://mymaster",
221
+ sentinels: [
222
+ { host: "sentinel1", port: 26379 },
223
+ { host: "sentinel2", port: 26379 },
224
+ { host: "sentinel3", port: 26379 }
225
+ ],
226
+ role: :master
227
+ )
228
+
229
+ storage = SimpleAcp::Storage::Redis.new(redis: redis)
230
+ ```
231
+
232
+ ## Maintenance
233
+
234
+ ### Clear All Data
235
+
236
+ ```ruby
237
+ storage.clear! # Deletes all keys with prefix
238
+ ```
239
+
240
+ ### Monitor Key Count
241
+
242
+ ```bash
243
+ redis-cli KEYS "acp:*" | wc -l
244
+ ```
245
+
246
+ ### Check Memory Usage
247
+
248
+ ```bash
249
+ redis-cli INFO memory
250
+ ```
251
+
252
+ ## Error Handling
253
+
254
+ ```ruby
255
+ begin
256
+ run = storage.get_run("run-id")
257
+ rescue Redis::CannotConnectError
258
+ puts "Redis connection failed"
259
+ # Fallback logic...
260
+ rescue Redis::TimeoutError
261
+ puts "Redis timeout"
262
+ # Retry logic...
263
+ end
264
+ ```
265
+
266
+ ## Best Practices
267
+
268
+ 1. **Use appropriate TTL** - Balance retention vs. memory usage
269
+ 2. **Use key prefix** - Namespace to avoid collisions
270
+ 3. **Monitor memory** - Watch Redis memory usage
271
+ 4. **Use connection pooling** - For multi-threaded apps
272
+ 5. **Set up replication** - For high availability
273
+
274
+ ## Comparison with Other Backends
275
+
276
+ | Feature | Redis | Memory | PostgreSQL |
277
+ |---------|-------|--------|------------|
278
+ | Persistence | TTL | None | Permanent |
279
+ | Multi-process | Yes | No | Yes |
280
+ | Speed | Fast | Fastest | Moderate |
281
+ | Query flexibility | Limited | None | Full SQL |
282
+
283
+ ## Next Steps
284
+
285
+ - Learn about [PostgreSQL](postgresql.md) for permanent storage
286
+ - Build a [Custom Backend](custom.md) for special needs
287
+ - Review [Memory](memory.md) for development
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Basic SimpleAcp Client Example
5
+ #
6
+ # First start the server: ruby examples/01_basic/server.rb
7
+ # Then run this client: ruby examples/01_basic/client.rb
8
+
9
+ require_relative "../../lib/simple_acp"
10
+
11
+ client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
12
+
13
+ puts "=== SimpleAcp Client Example ==="
14
+ puts
15
+
16
+ # Check server health
17
+ if client.ping
18
+ puts "✓ Server is healthy"
19
+ else
20
+ puts "✗ Server is not responding"
21
+ exit 1
22
+ end
23
+ puts
24
+
25
+ # List available agents
26
+ puts "Available agents:"
27
+ agents = client.agents
28
+ agents.agents.each do |agent|
29
+ puts " - #{agent.name}: #{agent.description}"
30
+ end
31
+ puts
32
+
33
+ # Test echo agent
34
+ puts "--- Testing echo agent ---"
35
+ run = client.run_sync(agent: "echo", input: "Hello, SimpleAcp!")
36
+ puts "Input: Hello, SimpleAcp!"
37
+ puts "Output: #{run.output.first.text_content}"
38
+ puts
39
+
40
+ # Test greeter agent
41
+ puts "--- Testing greeter agent ---"
42
+ run = client.run_sync(agent: "greeter", input: "Ruby Developer")
43
+ puts "Input: Ruby Developer"
44
+ puts "Output: #{run.output.first.text_content}"
45
+ puts
46
+
47
+ # Test counter agent with session
48
+ puts "--- Testing counter agent with session ---"
49
+ session_id = SecureRandom.uuid
50
+ client.use_session(session_id)
51
+
52
+ 3.times do |i|
53
+ run = client.run_sync(agent: "counter", input: "increment")
54
+ puts "Call #{i + 1}: #{run.output.first.text_content}"
55
+ end
56
+ client.clear_session
57
+ puts
58
+
59
+ # Test streaming with Gettysburg Address (word by word with 0.5s delay)
60
+ puts "--- Testing streaming (Gettysburg Address) ---"
61
+ puts
62
+ print " "
63
+ client.run_stream(agent: "gettysburg", input: "recite") do |event|
64
+ case event
65
+ when SimpleAcp::Models::MessagePartEvent
66
+ print event.part.content
67
+ $stdout.flush
68
+ when SimpleAcp::Models::RunCompletedEvent
69
+ puts
70
+ puts
71
+ puts "[Streaming complete]"
72
+ end
73
+ end
74
+ puts
75
+
76
+ # Test assistant with history
77
+ puts "--- Testing assistant with history ---"
78
+ session_id = SecureRandom.uuid
79
+ client.use_session(session_id)
80
+
81
+ ["Hi!", "Tell me more", "Thanks!"].each do |msg|
82
+ run = client.run_sync(agent: "assistant", input: msg)
83
+ puts "User: #{msg}"
84
+ puts "Agent: #{run.output.first.text_content}"
85
+ puts
86
+ end
87
+
88
+ puts "=== All tests completed ==="
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Basic SimpleAcp Server Example
5
+ #
6
+ # Run with: ruby examples/01_basic/server.rb
7
+ # Then connect with: ruby examples/01_basic/client.rb
8
+
9
+ require_relative "../../lib/simple_acp"
10
+
11
+ server = SimpleAcp::Server::Base.new
12
+
13
+ # Simple echo agent
14
+ server.agent("echo", description: "Echoes everything you send") do |context|
15
+ Enumerator.new do |yielder|
16
+ context.input.each do |message|
17
+ yielder << SimpleAcp::Server::RunYield.new(
18
+ SimpleAcp::Models::Message.agent(message.text_content)
19
+ )
20
+ end
21
+ end
22
+ end
23
+
24
+ # Greeting agent
25
+ server.agent("greeter", description: "Greets the user by name") do |context|
26
+ name = context.input.first&.text_content&.strip
27
+ name = "World" if name.nil? || name.empty?
28
+
29
+ SimpleAcp::Models::Message.agent("Hello, #{name}! Welcome to SimpleAcp.")
30
+ end
31
+
32
+ # Stateful counter agent
33
+ server.agent("counter", description: "Counts how many times you've called it") do |context|
34
+ count = (context.state || 0) + 1
35
+ context.set_state(count)
36
+
37
+ SimpleAcp::Models::Message.agent("You have called me #{count} time(s).")
38
+ end
39
+
40
+ # Streaming demonstration agent - recites the Gettysburg Address word by word
41
+ server.agent("gettysburg", description: "Recites the Gettysburg Address word by word") do |context|
42
+ address = <<~TEXT
43
+ Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
44
+ Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure.
45
+ We are met on a great battle-field of that war.
46
+ We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live.
47
+ It is altogether fitting and proper that we should do this.
48
+ But, in a larger sense, we can not dedicate — we can not consecrate — we can not hallow — this ground.
49
+ The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract.
50
+ The world will little note, nor long remember what we say here, but it can never forget what they did here.
51
+ It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced.
52
+ It is rather for us to be here dedicated to the great task remaining before us — that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion — that we here highly resolve that these dead shall not have died in vain — that this nation, under God, shall have a new birth of freedom — and that government of the people, by the people, for the people, shall not perish from the earth.
53
+ TEXT
54
+
55
+ words = address.split
56
+
57
+ Enumerator.new do |yielder|
58
+ words.each do |word|
59
+ # Use fiber-aware sleep for Async/Falcon compatibility
60
+ if defined?(Async::Task) && Async::Task.current?
61
+ Async::Task.current.sleep(0.3)
62
+ else
63
+ sleep(0.3)
64
+ end
65
+ yielder << SimpleAcp::Server::RunYield.new(
66
+ SimpleAcp::Models::Message.agent(word + " ")
67
+ )
68
+ end
69
+ end
70
+ end
71
+
72
+ # Multi-turn conversation agent
73
+ server.agent("assistant",
74
+ description: "A simple assistant that remembers conversation history",
75
+ input_content_types: ["text/plain", "application/json"],
76
+ output_content_types: ["text/plain"]
77
+ ) do |context|
78
+ # Access conversation history
79
+ history_count = context.history.length
80
+
81
+ response = if history_count.zero?
82
+ "Hello! This is our first conversation. How can I help you?"
83
+ else
84
+ "I see we've had #{history_count} previous messages. What else can I help with?"
85
+ end
86
+
87
+ SimpleAcp::Models::Message.agent(response)
88
+ end
89
+
90
+ ########################################################
91
+ ## main line
92
+ #
93
+ puts "Starting SimpleAcp Server..."
94
+ puts "Available agents:"
95
+ server.agents.each do |name, agent|
96
+ puts " - #{name}: #{agent.description}"
97
+ end
98
+ puts
99
+
100
+ server.run(port: 8000)
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Async Execution Example - Client
5
+ #
6
+ # Demonstrates:
7
+ # - Async (non-blocking) execution with run_async
8
+ # - Polling run status with run_status
9
+ # - Waiting for completion with wait_for_run
10
+ # - Running multiple tasks concurrently
11
+ #
12
+ # First start the server: ruby examples/02_async_execution/server.rb
13
+ # Then run this client: ruby examples/02_async_execution/client.rb
14
+
15
+ require_relative "../../lib/simple_acp"
16
+
17
+ client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
18
+
19
+ puts "=== Async Execution Demo ==="
20
+ puts
21
+
22
+ # Verify server is running
23
+ unless client.ping
24
+ puts "Server is not responding"
25
+ exit 1
26
+ end
27
+ puts "Server is healthy"
28
+ puts
29
+
30
+ # --- Demo 1: Async execution with manual polling ---
31
+ puts "--- Demo 1: Async Execution with Manual Polling ---"
32
+ puts
33
+
34
+ puts "Starting slow-worker asynchronously..."
35
+ run = client.run_async(agent: "slow-worker", input: "data analysis")
36
+ puts "Run started with ID: #{run.run_id}"
37
+ puts "Initial status: #{run.status}"
38
+ puts
39
+
40
+ puts "Polling for status..."
41
+ loop do
42
+ status = client.run_status(run.run_id)
43
+ puts " Status: #{status.status}"
44
+
45
+ break if status.terminal?
46
+
47
+ sleep(0.5)
48
+ end
49
+
50
+ puts
51
+ puts "Run completed! Final output:"
52
+ final_run = client.run_status(run.run_id)
53
+ final_run.output.each do |message|
54
+ puts " #{message.text_content}"
55
+ end
56
+ puts
57
+
58
+ # --- Demo 2: Using wait_for_run for simpler polling ---
59
+ puts "--- Demo 2: Using wait_for_run ---"
60
+ puts
61
+
62
+ puts "Starting another slow-worker..."
63
+ run = client.run_async(agent: "slow-worker", input: "report generation")
64
+ puts "Run ID: #{run.run_id}"
65
+ puts
66
+
67
+ puts "Waiting for completion (timeout: 10s)..."
68
+ completed_run = client.wait_for_run(run.run_id, timeout: 10)
69
+
70
+ if completed_run.status == "completed"
71
+ puts "Success! Output:"
72
+ completed_run.output.each do |message|
73
+ puts " #{message.text_content}"
74
+ end
75
+ else
76
+ puts "Run ended with status: #{completed_run.status}"
77
+ end
78
+ puts
79
+
80
+ # --- Demo 3: Running multiple tasks concurrently ---
81
+ puts "--- Demo 3: Concurrent Async Execution ---"
82
+ puts
83
+
84
+ puts "Starting 3 tasks concurrently..."
85
+ runs = []
86
+ %w[task_alpha task_beta task_gamma].each do |task_name|
87
+ run = client.run_async(agent: "slow-worker", input: task_name)
88
+ runs << run
89
+ puts " Started: #{task_name} (#{run.run_id[0..7]}...)"
90
+ end
91
+ puts
92
+
93
+ puts "Waiting for all tasks to complete..."
94
+ start_time = Time.now
95
+
96
+ runs.each do |run|
97
+ completed = client.wait_for_run(run.run_id, timeout: 15)
98
+ task_name = completed.output.last&.text_content || "unknown"
99
+ puts " Completed: #{task_name}"
100
+ end
101
+
102
+ elapsed = (Time.now - start_time).round(2)
103
+ puts
104
+ puts "All 3 tasks completed in #{elapsed}s (ran concurrently, not 9s sequentially)"
105
+ puts
106
+
107
+ puts "=== Async Execution Demo Complete ==="
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Async Execution Example - Server
5
+ #
6
+ # Demonstrates async (non-blocking) execution with polling.
7
+ #
8
+ # Run with: ruby examples/02_async_execution/server.rb
9
+ # Then connect with: ruby examples/02_async_execution/client.rb
10
+
11
+ require_relative "../../lib/simple_acp"
12
+
13
+ server = SimpleAcp::Server::Base.new
14
+
15
+ # Slow worker agent - simulates a long-running task
16
+ server.agent("slow-worker", description: "Simulates a long-running task (3 seconds)") do |context|
17
+ task_name = context.input.first&.text_content || "default task"
18
+
19
+ # Simulate work with progress updates
20
+ Enumerator.new do |yielder|
21
+ yielder << SimpleAcp::Server::RunYield.new(
22
+ SimpleAcp::Models::Message.agent("Starting task: #{task_name}")
23
+ )
24
+
25
+ # Simulate 3 seconds of work
26
+ 3.times do |i|
27
+ if defined?(Async::Task) && Async::Task.current?
28
+ Async::Task.current.sleep(1)
29
+ else
30
+ sleep(1)
31
+ end
32
+
33
+ yielder << SimpleAcp::Server::RunYield.new(
34
+ SimpleAcp::Models::Message.agent("Progress: #{(i + 1) * 33}%")
35
+ )
36
+ end
37
+
38
+ yielder << SimpleAcp::Server::RunYield.new(
39
+ SimpleAcp::Models::Message.agent("Task '#{task_name}' completed!")
40
+ )
41
+ end
42
+ end
43
+
44
+ # Quick status agent - returns immediately
45
+ server.agent("quick-status", description: "Returns status immediately") do |context|
46
+ SimpleAcp::Models::Message.agent("Status: All systems operational at #{Time.now}")
47
+ end
48
+
49
+ puts "Starting Async Execution Demo Server..."
50
+ puts "Available agents:"
51
+ server.agents.each do |name, agent|
52
+ puts " - #{name}: #{agent.description}"
53
+ end
54
+ puts
55
+
56
+ server.run(port: 8000)