robot_lab 0.1.0 → 0.2.6

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/.architecture/AGENTS.md +32 -0
  3. data/.architecture/config.yml +8 -0
  4. data/.architecture/members.yml +60 -0
  5. data/.architecture/reviews/feature-free-will.md +490 -0
  6. data/.architecture/reviews/overall-codebase.md +427 -0
  7. data/.claude/settings.local.json +57 -0
  8. data/.codex/config.toml +2 -0
  9. data/.envrc +1 -0
  10. data/.irbrc +2 -2
  11. data/.loki +60 -0
  12. data/.quality/reek_baseline.txt +43 -0
  13. data/.rubocop.yml +10 -0
  14. data/CHANGELOG.md +126 -0
  15. data/CLAUDE.md +139 -0
  16. data/README.md +130 -73
  17. data/Rakefile +131 -9
  18. data/agent2agent_review.md +192 -0
  19. data/agentf_improvements.md +253 -0
  20. data/agents.md +14 -0
  21. data/docs/api/core/index.md +1 -0
  22. data/docs/api/core/network.md +31 -1
  23. data/docs/api/core/robot.md +91 -5
  24. data/docs/api/errors.md +47 -2
  25. data/docs/api/index.md +3 -0
  26. data/docs/architecture/core-concepts.md +40 -0
  27. data/docs/architecture/network-orchestration.md +8 -0
  28. data/docs/architecture/robot-execution.md +1 -1
  29. data/docs/examples/index.md +37 -2
  30. data/docs/getting-started/configuration.md +39 -7
  31. data/docs/guides/building-robots.md +39 -1
  32. data/docs/guides/creating-networks.md +6 -1
  33. data/docs/guides/hooks.md +997 -0
  34. data/docs/guides/index.md +19 -14
  35. data/docs/guides/knowledge.md +9 -3
  36. data/docs/guides/observability.md +206 -7
  37. data/docs/guides/using-tools.md +69 -0
  38. data/docs/index.md +31 -4
  39. data/docs/superpowers/plans/2026-05-06-agentskills.md +1303 -0
  40. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +247 -0
  41. data/examples/.envrc +1 -0
  42. data/examples/01_simple_robot.rb +5 -9
  43. data/examples/02_tools.rb +5 -9
  44. data/examples/03_network.rb +18 -10
  45. data/examples/04_mcp.rb +21 -29
  46. data/examples/05_streaming.rb +12 -18
  47. data/examples/06_prompt_templates.rb +11 -19
  48. data/examples/07_network_memory.rb +16 -31
  49. data/examples/08_llm_config.rb +10 -22
  50. data/examples/09_chaining.rb +16 -27
  51. data/examples/10_memory.rb +12 -28
  52. data/examples/11_network_introspection.rb +15 -29
  53. data/examples/12_message_bus.rb +5 -12
  54. data/examples/13_spawn.rb +5 -10
  55. data/examples/14_rusty_circuit/.envrc +1 -0
  56. data/examples/14_rusty_circuit/comic.rb +2 -0
  57. data/examples/14_rusty_circuit/heckler.rb +1 -1
  58. data/examples/14_rusty_circuit/open_mic.rb +1 -3
  59. data/examples/14_rusty_circuit/scout.rb +2 -0
  60. data/examples/15_memory_network_and_bus/.envrc +1 -0
  61. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +6 -3
  62. data/examples/15_memory_network_and_bus/linux_writer.rb +1 -1
  63. data/examples/15_memory_network_and_bus/output/combined_article.md +6 -6
  64. data/examples/15_memory_network_and_bus/output/final_article.md +6 -8
  65. data/examples/15_memory_network_and_bus/output/linux_draft.md +4 -2
  66. data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
  67. data/examples/15_memory_network_and_bus/output/memory.json +6 -6
  68. data/examples/15_memory_network_and_bus/output/revision_1.md +10 -11
  69. data/examples/15_memory_network_and_bus/output/revision_2.md +6 -8
  70. data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
  71. data/examples/16_writers_room/.envrc +1 -0
  72. data/examples/16_writers_room/writers_room.rb +2 -4
  73. data/examples/17_skills.rb +8 -17
  74. data/examples/18_rails/Gemfile +1 -0
  75. data/examples/19_token_tracking.rb +9 -15
  76. data/examples/20_circuit_breaker.rb +10 -19
  77. data/examples/21_learning_loop.rb +11 -20
  78. data/examples/22_context_compression.rb +6 -13
  79. data/examples/23_convergence.rb +6 -17
  80. data/examples/24_structured_delegation.rb +11 -15
  81. data/examples/25_history_search.rb +5 -12
  82. data/examples/26_document_store.rb +6 -13
  83. data/examples/27_incident_response/incident_response.rb +4 -5
  84. data/examples/28_mcp_discovery.rb +8 -11
  85. data/examples/29_ractor_tools.rb +4 -9
  86. data/examples/30_ractor_network.rb +10 -19
  87. data/examples/31_launch_assessment.rb +10 -23
  88. data/examples/32_newsletter_reader.rb +188 -0
  89. data/examples/33_stock_generator.rb +80 -0
  90. data/examples/33_stock_predictor.rb +306 -0
  91. data/examples/34_agentskills.rb +72 -0
  92. data/examples/35_hooks.rb +256 -0
  93. data/examples/README.md +8 -1
  94. data/examples/common.rb +80 -0
  95. data/examples/ruboruby.md +423 -0
  96. data/examples/xyzzy.rb +90 -0
  97. data/lib/robot_lab/agent_skill.rb +64 -0
  98. data/lib/robot_lab/agent_skill_catalog.rb +74 -0
  99. data/lib/robot_lab/ask_user.rb +2 -2
  100. data/lib/robot_lab/budget/ledger.rb +98 -0
  101. data/lib/robot_lab/bus_poller.rb +12 -5
  102. data/lib/robot_lab/capabilities.rb +84 -0
  103. data/lib/robot_lab/config/defaults.yml +10 -0
  104. data/lib/robot_lab/config.rb +5 -16
  105. data/lib/robot_lab/delegation_future.rb +1 -1
  106. data/lib/robot_lab/doom_loop_detector.rb +98 -0
  107. data/lib/robot_lab/error.rb +34 -4
  108. data/lib/robot_lab/errors.rb +45 -0
  109. data/lib/robot_lab/history_compressor.rb +4 -10
  110. data/lib/robot_lab/hook.rb +79 -0
  111. data/lib/robot_lab/hook_context.rb +194 -0
  112. data/lib/robot_lab/hook_registry.rb +55 -0
  113. data/lib/robot_lab/hooks.rb +87 -0
  114. data/lib/robot_lab/mcp/client.rb +1 -2
  115. data/lib/robot_lab/mcp/connection_poller.rb +5 -5
  116. data/lib/robot_lab/mcp/server.rb +1 -1
  117. data/lib/robot_lab/mcp/server_discovery.rb +0 -2
  118. data/lib/robot_lab/memory.rb +32 -27
  119. data/lib/robot_lab/memory_change.rb +2 -2
  120. data/lib/robot_lab/message.rb +4 -4
  121. data/lib/robot_lab/narrator.rb +87 -0
  122. data/lib/robot_lab/network.rb +87 -25
  123. data/lib/robot_lab/robot/agent_skill_matching.rb +99 -0
  124. data/lib/robot_lab/robot/budget.rb +89 -0
  125. data/lib/robot_lab/robot/bus_messaging.rb +79 -33
  126. data/lib/robot_lab/robot/history_search.rb +4 -1
  127. data/lib/robot_lab/robot/hooking.rb +56 -0
  128. data/lib/robot_lab/robot/mcp_management.rb +12 -12
  129. data/lib/robot_lab/robot/template_rendering.rb +62 -42
  130. data/lib/robot_lab/robot.rb +482 -242
  131. data/lib/robot_lab/robot_result.rb +6 -5
  132. data/lib/robot_lab/run_config.rb +27 -14
  133. data/lib/robot_lab/runnable.rb +51 -0
  134. data/lib/robot_lab/sandbox/null.rb +13 -0
  135. data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
  136. data/lib/robot_lab/sandbox.rb +52 -0
  137. data/lib/robot_lab/script_tool.rb +134 -0
  138. data/lib/robot_lab/state_proxy.rb +7 -5
  139. data/lib/robot_lab/task.rb +26 -20
  140. data/lib/robot_lab/tool.rb +54 -13
  141. data/lib/robot_lab/tool_config.rb +1 -1
  142. data/lib/robot_lab/tool_manifest.rb +5 -7
  143. data/lib/robot_lab/user_message.rb +2 -2
  144. data/lib/robot_lab/version.rb +1 -1
  145. data/lib/robot_lab/waiter.rb +1 -1
  146. data/lib/robot_lab.rb +89 -51
  147. data/logfile +8 -0
  148. data/mkdocs.yml +3 -3
  149. data/robot_concurrency.md +38 -0
  150. data/simple_acp_review.md +298 -0
  151. data/temp.md +6 -0
  152. data/tool_manifest_plan.md +155 -0
  153. metadata +82 -83
  154. data/docs/examples/rails-application.md +0 -419
  155. data/docs/guides/ractor-parallelism.md +0 -364
  156. data/docs/guides/rails-integration.md +0 -681
  157. data/docs/superpowers/plans/2026-04-14-ractor-integration.md +0 -1538
  158. data/docs/superpowers/specs/2026-04-14-ractor-integration-design.md +0 -258
  159. data/lib/generators/robot_lab/install_generator.rb +0 -90
  160. data/lib/generators/robot_lab/job_generator.rb +0 -40
  161. data/lib/generators/robot_lab/robot_generator.rb +0 -55
  162. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -42
  163. data/lib/generators/robot_lab/templates/job.rb.tt +0 -21
  164. data/lib/generators/robot_lab/templates/migration.rb.tt +0 -32
  165. data/lib/generators/robot_lab/templates/result_model.rb.tt +0 -52
  166. data/lib/generators/robot_lab/templates/robot.rb.tt +0 -31
  167. data/lib/generators/robot_lab/templates/robot_job.rb.tt +0 -18
  168. data/lib/generators/robot_lab/templates/robot_test.rb.tt +0 -34
  169. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +0 -59
  170. data/lib/generators/robot_lab/templates/thread_model.rb.tt +0 -40
  171. data/lib/robot_lab/document_store.rb +0 -155
  172. data/lib/robot_lab/ractor_boundary.rb +0 -42
  173. data/lib/robot_lab/ractor_job.rb +0 -37
  174. data/lib/robot_lab/ractor_memory_proxy.rb +0 -85
  175. data/lib/robot_lab/ractor_network_scheduler.rb +0 -154
  176. data/lib/robot_lab/ractor_worker_pool.rb +0 -117
  177. data/lib/robot_lab/rails_integration/engine.rb +0 -29
  178. data/lib/robot_lab/rails_integration/job.rb +0 -158
  179. data/lib/robot_lab/rails_integration/railtie.rb +0 -51
  180. data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +0 -72
data/Rakefile CHANGED
@@ -3,6 +3,8 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
5
 
6
+ RUBOCOP_ENV = { "RUBOCOP_CACHE_ROOT" => "tmp/rubocop_cache" }.freeze
7
+
6
8
  Rake::TestTask.new(:test) do |t|
7
9
  t.libs << "test"
8
10
  t.libs << "lib"
@@ -34,14 +36,98 @@ Rake::TestTask.new(:integration) do |t|
34
36
  t.ruby_opts << "-rtest_helper"
35
37
  end
36
38
 
37
- desc "Check code style with RuboCop"
38
- task :rubocop do
39
- sh "bundle exec rubocop"
39
+ desc "Check code complexity with Flog (warn ≥20, fail ≥50)"
40
+ task :flog_check do
41
+ require 'flog'
42
+
43
+ # Target to work toward; methods above this are warned but don't fail the gate.
44
+ METHOD_WARN = 20.0
45
+ # Current baseline floor — established from first run. Reduce incrementally.
46
+ METHOD_FAIL = 50.0
47
+
48
+ flogger = Flog.new(all: true)
49
+ flogger.flog(*Dir.glob('lib/**/*.rb'))
50
+
51
+ warnings = []
52
+ failures = []
53
+
54
+ flogger.each_by_score do |method, score|
55
+ next if method.end_with?('#none') # skip file-level non-method code
56
+ if score > METHOD_FAIL
57
+ failures << "#{'%.1f' % score}: #{method}"
58
+ elsif score > METHOD_WARN
59
+ warnings << "#{'%.1f' % score}: #{method}"
60
+ end
61
+ end
62
+
63
+ unless warnings.empty?
64
+ puts "\nFlog warnings (#{METHOD_WARN}–#{METHOD_FAIL}) — target for future refactoring:"
65
+ warnings.each { |v| puts " #{v}" }
66
+ end
67
+
68
+ if failures.empty?
69
+ puts "\nFlog: no methods exceed the failure threshold (≥#{METHOD_FAIL})"
70
+ else
71
+ puts "\nFlog failures (≥#{METHOD_FAIL}) — must be refactored:"
72
+ failures.each { |v| puts " #{v}" }
73
+ abort "\nFlog quality gate failed: #{failures.size} method(s) exceed #{METHOD_FAIL}"
74
+ end
40
75
  end
41
76
 
42
- desc "Auto-correct RuboCop offenses"
43
- task :rubocop_fix do
44
- sh "bundle exec rubocop -a"
77
+ desc "Check for structural code duplication with Flay (mass >= 50)"
78
+ task :flay_check do
79
+ require 'flay'
80
+
81
+ mass_threshold = 50
82
+
83
+ flay = Flay.new({ mass: mass_threshold, diff: false, verbose: false, summary: false, timeout: 60 })
84
+ flay.process(*Dir.glob('lib/**/*.rb'))
85
+ flay.analyze
86
+
87
+ if flay.hashes.empty?
88
+ puts "\nFlay: no structural duplication detected (mass >= #{mass_threshold})"
89
+ else
90
+ puts "\nFlay found structural duplication (mass >= #{mass_threshold}):"
91
+ flay.report
92
+ abort "\nFlay quality gate failed: #{flay.hashes.length} pattern(s) detected"
93
+ end
94
+ end
95
+
96
+ desc "Run all quality checks: tests (with coverage), RuboCop, Flog, and Flay"
97
+ task :quality do
98
+ results = {}
99
+
100
+ puts "\n#{'=' * 60}"
101
+ puts "Quality Gate: Tests + Coverage"
102
+ puts '=' * 60
103
+ results[:tests] = system("bundle exec rake test") ? :pass : :fail
104
+
105
+ puts "\n#{'=' * 60}"
106
+ puts "Quality Gate: RuboCop"
107
+ puts '=' * 60
108
+ results[:rubocop] = system(RUBOCOP_ENV, "bundle exec rubocop") ? :pass : :fail
109
+
110
+ puts "\n#{'=' * 60}"
111
+ puts "Quality Gate: Flog Complexity"
112
+ puts '=' * 60
113
+ results[:flog] = system("bundle exec rake flog_check") ? :pass : :fail
114
+
115
+ puts "\n#{'=' * 60}"
116
+ puts "Quality Gate: Flay Duplication"
117
+ puts '=' * 60
118
+ results[:flay] = system("bundle exec rake flay_check") ? :pass : :fail
119
+
120
+ puts "\n#{'=' * 60}"
121
+ puts "Quality Summary"
122
+ puts '=' * 60
123
+ results.each do |gate, status|
124
+ icon = status == :pass ? 'PASS' : 'FAIL'
125
+ puts " [#{icon}] #{gate}"
126
+ end
127
+ puts '=' * 60
128
+
129
+ abort "\nQuality gate failed" if results.values.any?(:fail)
130
+ puts "\nAll quality gates passed."
45
131
  end
46
132
 
47
133
  namespace :examples do
@@ -58,14 +144,37 @@ namespace :examples do
58
144
  "18_rails" => { setup: "bin/setup", run: "bin/dev" }
59
145
  }.freeze
60
146
 
147
+ # Examples that require external services or user setup not guaranteed to be present
148
+ EXTERNAL_SERVICE_EXAMPLES = {
149
+ "33_stock_generator.rb" => "Redis server on localhost:6379",
150
+ "33_stock_predictor.rb" => "Redis server on localhost:6379 + running 33_stock_generator",
151
+ "34_agentskills.rb" => "AgentSkills skill file at ~/.prompts/skills/code_reviewer/SKILL.md"
152
+ }.freeze
153
+
61
154
  desc "Run all examples (excludes standalone apps like 18_rails)"
62
155
  task :all do
156
+ failed = []
157
+
63
158
  # Single-file examples
64
- Dir.glob("examples/*.rb").sort.each do |example|
159
+ Dir.glob("examples/*.rb").each do |example|
160
+ base = File.basename(example)
161
+
162
+ if EXTERNAL_SERVICE_EXAMPLES.key?(base)
163
+ puts "\n#{'=' * 60}"
164
+ puts "Skipped: #{example} (requires #{EXTERNAL_SERVICE_EXAMPLES[base]})"
165
+ puts '=' * 60
166
+ next
167
+ end
168
+
65
169
  puts "\n#{'=' * 60}"
66
170
  puts "Running: #{example}"
67
171
  puts '=' * 60
68
- ruby example
172
+ begin
173
+ ruby example
174
+ rescue RuntimeError => e
175
+ puts "FAILED: #{example} — #{e.message}"
176
+ failed << example
177
+ end
69
178
  end
70
179
 
71
180
  # Subdirectory-based demos
@@ -76,7 +185,12 @@ namespace :examples do
76
185
  puts "\n#{'=' * 60}"
77
186
  puts "Running: #{path}"
78
187
  puts '=' * 60
79
- ruby path
188
+ begin
189
+ ruby path
190
+ rescue RuntimeError => e
191
+ puts "FAILED: #{path} — #{e.message}"
192
+ failed << path
193
+ end
80
194
  end
81
195
 
82
196
  # Remind about standalone apps
@@ -87,6 +201,14 @@ namespace :examples do
87
201
  puts " Run: cd examples/#{dir} && #{commands[:run]}"
88
202
  puts '=' * 60
89
203
  end
204
+
205
+ if failed.any?
206
+ puts "\n#{'=' * 60}"
207
+ puts "#{failed.size} example(s) failed:"
208
+ failed.each { |f| puts " #{f}" }
209
+ puts '=' * 60
210
+ exit 1
211
+ end
90
212
  end
91
213
 
92
214
  desc "Run a specific example by number (e.g., rake examples:run[1])"
@@ -0,0 +1,192 @@
1
+ # Review: `agent2agent` Ruby Gem
2
+
3
+ **Source:** https://github.com/general-intelligence-systems/agent2agent
4
+ **Docs:** https://general-intelligence-systems.github.io/agent2agent/
5
+ **License:** Apache 2.0
6
+ **Ruby:** >= 3.2
7
+ **Reviewed:** 2026-05-05
8
+
9
+ ---
10
+
11
+ ## What It Is
12
+
13
+ A complete Ruby implementation of Google's A2A (Agent-to-Agent) protocol — an open standard for interoperable, cross-vendor AI agent communication over HTTP. The gem provides both a server (Rack app) and a client, plus SSE streaming, task persistence, and push notifications.
14
+
15
+ ---
16
+
17
+ ## Wire Protocol
18
+
19
+ Two parallel transport bindings, both implemented:
20
+
21
+ | Transport | Path | Format |
22
+ |---|---|---|
23
+ | JSON-RPC 2.0 | `POST /a2a` | `{"jsonrpc":"2.0","method":"SendMessage","params":{...}}` |
24
+ | HTTP+JSON/REST | `POST /message:send`, `GET /tasks/{id}`, etc. | Plain JSON |
25
+ | Agent Discovery | `GET /.well-known/agent-card.json` | Capabilities manifest |
26
+
27
+ ### The 11 Protocol Operations
28
+
29
+ 1. `SendMessage` — POST `/message:send` (synchronous)
30
+ 2. `SendStreamingMessage` — POST `/message:stream` (SSE, server-streaming)
31
+ 3. `GetTask` — GET `/tasks/{id}`
32
+ 4. `ListTasks` — GET `/tasks`
33
+ 5. `CancelTask` — POST `/tasks/{id}:cancel`
34
+ 6. `SubscribeToTask` — SSE stream of task updates
35
+ 7. `CreateTaskPushNotificationConfig` — POST `/tasks/{id}/push`
36
+ 8. `GetTaskPushNotificationConfig` — GET `/tasks/{id}/push/{config_id}`
37
+ 9. `ListTaskPushNotificationConfigs` — GET `/tasks/{id}/push`
38
+ 10. `DeleteTaskPushNotificationConfig` — DELETE `/tasks/{id}/push/{config_id}`
39
+ 11. `GetExtendedAgentCard` — GET extended agent card
40
+
41
+ ### Task State Machine
42
+
43
+ `SUBMITTED → WORKING → INPUT_REQUIRED → COMPLETED / FAILED / CANCELED / REJECTED`
44
+
45
+ ---
46
+
47
+ ## Key Classes and Their Roles
48
+
49
+ | Class | Role |
50
+ |---|---|
51
+ | `A2A::Agent` | DSL: `on("SendMessage") { \|req\| respond(...) }` — registers operation handlers |
52
+ | `A2A::Server` | Rack app — mountable in any Rack stack or Rails router |
53
+ | `A2A::Client` | Async-HTTP client; all 11 ops auto-generated as snake_case methods |
54
+ | `A2A::Proto` | Parses the real `data/a2a.proto` file — single source of truth for operations |
55
+ | `A2A::Schema` | Loads 47-type `data/a2a.json`; validates with `json_schemer`; camelCase/snake_case |
56
+ | `A2A::TaskStore` | In-memory task CRUD with `Thread::Queue` pub/sub and webhook delivery |
57
+ | `A2A::Store::SQLite` | Production drop-in: WAL mode, indexed, fiber-safe `Async::Queue` pub/sub |
58
+ | `A2A::SSE::Stream` | Subclasses `Protocol::HTTP::Body::Writable`; Falcon passes it untouched |
59
+ | `A2A::Bindings::JsonRpc` | Rack middleware that parses JSON-RPC envelopes and wraps responses |
60
+
61
+ ### Agent DSL Example
62
+
63
+ ```ruby
64
+ agent = A2A::Agent.new do
65
+ on "SendMessage", "SendStreamingMessage" do |context|
66
+ task = context.store.create(context.request)
67
+ stream = context.stream
68
+
69
+ Async do
70
+ result = robot.run(context.request.params[:message])
71
+ context.store.complete(task.id, result)
72
+ stream.event(result, type: "result")
73
+ stream.finish
74
+ end
75
+
76
+ context.respond(task)
77
+ end
78
+ end
79
+
80
+ server = A2A::Server.new
81
+ server.register(agent)
82
+ run server
83
+ ```
84
+
85
+ ### Client Example
86
+
87
+ ```ruby
88
+ Async do
89
+ client = A2A::Client.new("http://localhost:9292")
90
+ card = client.agent_card
91
+ result = client.send_message(message: { role: "user", parts: [{ text: "Hello" }] })
92
+ puts result
93
+ end
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Notable Patterns
99
+
100
+ - **Duck-typed stores:** Any object implementing the task store interface can be swapped in — `TaskStore` (in-memory) or `Store::SQLite` (production), or a custom implementation.
101
+ - **Proto as source of truth:** `Proto` parses `data/a2a.proto` directly to extract operation metadata — stays in sync with the Google A2A spec automatically.
102
+ - **Fiber-native:** The SQLite store's pub/sub uses `Async::Queue`; SSE bodies use `Protocol::HTTP::Body::Writable`. Fully fiber-safe when run under Falcon.
103
+ - **`returnImmediately` flag:** Background jobs return a task ID immediately; updates stream via SSE as work proceeds.
104
+ - **`STATE_INPUT_REQUIRED`:** Multi-turn conversations — agent transitions to this state when it needs more user input before continuing. Client sends another `SendMessage` referencing the same task context.
105
+ - **Inline tests via `scampi`:** Tests live inside source files (`test do ... end`), not a separate test directory.
106
+ - **Tenant-prefixed paths:** Every REST route has a variant: `/{tenant}/message:send` for multi-tenant deployments.
107
+
108
+ ---
109
+
110
+ ## Authentication
111
+
112
+ - **AgentCard** declares supported auth schemes in the capabilities manifest
113
+ - **Push notification configs** carry per-webhook auth: `scheme` + `credentials` sent as `Authorization: Bearer <credentials>` on webhook delivery; optionally also `X-A2A-Notification-Token`
114
+ - Incoming request authentication is left to the application layer (standard Rack middleware pattern)
115
+
116
+ ---
117
+
118
+ ## Dependencies
119
+
120
+ **Runtime:**
121
+ - `async (~> 2.0)`, `async-http (~> 0.95)` — fiber concurrency and HTTP
122
+ - `rack (~> 3.0)` — server composition (pure Rack, Rails-mountable)
123
+ - `json_schemer (~> 2.5)` — schema validation against 47 A2A types
124
+ - `google-protobuf (~> 4.34)` — proto file parsing
125
+ - `sqlite3` — persistent task store
126
+ - `protocol-http (~> 0.62)` — `Body::Writable` for SSE
127
+ - `scampi` — inline test framework
128
+ - `console` — structured logging
129
+
130
+ **Development:**
131
+ - `falcon (~> 0.55)` — async HTTP server for running agents
132
+
133
+ ---
134
+
135
+ ## Applicability to Your Projects
136
+
137
+ ### RobotLab — High Value
138
+
139
+ The most compelling integration: expose each `Robot` or `Network` as a standard A2A service. This enables cross-language orchestration (Python LangChain, JS Genkit, Go agents, Google Cloud agents) without any shared code.
140
+
141
+ **Robot-as-A2A-Service:**
142
+ - `A2A::Agent` adapter delegates `SendMessage` → `robot.run(request.message)`, result becomes the A2A task result
143
+ - Publish an AgentCard at `/.well-known/agent-card.json` advertising each robot's capabilities and tools
144
+ - Use `A2A::Store::SQLite` to persist tasks across requests (stateless HTTP tier in front of stateful robots)
145
+
146
+ **Streaming:**
147
+ - `SendStreamingMessage` + `SSE::Stream` maps directly onto RubyLLM's streaming callbacks
148
+ - Streaming events become real-time SSE — no additional infrastructure needed
149
+
150
+ **Cross-process robot networks:**
151
+ - Instead of `TypedBus` (in-process pub/sub only), robots in separate processes or machines call each other via `A2A::Client`
152
+ - A Network router can delegate to remote robots via `client.send_message(...)` — standard HTTP replaces shared-memory message passing
153
+
154
+ **Background jobs:**
155
+ - A2A's push notification config CRUD gives external systems a standard webhook protocol for task completion
156
+ - Maps cleanly onto RobotLab's existing async robot semantics
157
+
158
+ **Multi-turn conversations:**
159
+ - `STATE_INPUT_REQUIRED` maps directly to RobotLab's `AskUser` tool pattern — robot needs user input before continuing
160
+ - Web clients get a proper protocol for handling confirmation flows rather than a terminal prompt
161
+
162
+ **Compatibility note:** Both RobotLab and `agent2agent` use the `async` gem — they compose cleanly. The main requirement is running under Falcon rather than a plain Ruby process. `TypedBus` (`Async::Queue`) and `A2A::Store::PubSub` (`Async::Queue`) are both fiber-based and compatible.
163
+
164
+ ### AIA — Moderate Value
165
+
166
+ - `A2A::Client` could delegate tasks to remote A2A-compliant agents (specialized coding agents, search agents, etc.) instead of calling LLM APIs directly
167
+ - AIA could optionally expose itself as a local A2A server on `localhost:PORT` so IDE plugins or other tools can send it tasks via standard protocol
168
+ - AgentCard discovery would let AIA auto-configure available capabilities when connecting to remote agents
169
+
170
+ ### Rails Apps Generally
171
+
172
+ `A2A::Server` is pure Rack:
173
+
174
+ ```ruby
175
+ # config/routes.rb
176
+ mount A2A::Server.new(agent: my_agent), at: "/agents/myagent"
177
+ ```
178
+
179
+ This works as-is. The 47-type schema validation via `json_schemer` is also useful standalone for validating A2A protocol payloads.
180
+
181
+ ---
182
+
183
+ ## Bottom Line
184
+
185
+ Production-quality gem for its scope. Clean architecture: Rack middleware chain, duck-typed stores, fiber-safe pub/sub, SSE via `protocol-http`. Inline test coverage is extensive.
186
+
187
+ **Highest-value opportunity for RobotLab:** Robot-as-A2A-Service — exposing robots as standards-compliant HTTP endpoints enables cross-language agent orchestration that the current `TypedBus` approach (in-process only) cannot support. This would position RobotLab robots as first-class participants in the emerging A2A ecosystem alongside Python, JavaScript, and Go agent frameworks.
188
+
189
+ **Specific algorithms/patterns worth porting:**
190
+ 1. `A2A::Store::SQLite` pub/sub pattern (Async::Queue-based) — applicable to RobotLab's Memory system
191
+ 2. AgentCard capability manifest — useful for RobotLab's planned tool/capability discovery
192
+ 3. `STATE_INPUT_REQUIRED` state machine entry — formalizes the `AskUser` pattern with a standard protocol state
@@ -0,0 +1,253 @@
1
+ # `agentf` Gem Analysis
2
+
3
+ **Repository:** https://github.com/nealdeters/agentf
4
+ **Author:** Neal Deters
5
+ **Ruby:** 3.3.0+ | **Runtime deps:** `redis ~> 4.8`, `dotenv ~> 2.8`
6
+
7
+ ---
8
+
9
+ ## What It Is
10
+
11
+ A Ruby multi-agent workflow engine for software development. Orchestrates
12
+ role-specialized agents (Planner, Engineer, QA, Reviewer, Security) that share
13
+ Redis memory — but **never calls an LLM itself**. It runs as an MCP server over
14
+ stdio and lets the IDE's AI (Copilot, OpenCode) do the actual inference.
15
+ It is scaffolding, not an API wrapper.
16
+
17
+ Three surfaces:
18
+ - **CLI** (`agentf <subcommand>`) — memory management, code exploration, metrics, evals
19
+ - **MCP server** (`agentf mcp-server`) — 19 tools over stdio for IDE integrations
20
+ - **Ruby API** — `WorkflowEngine`, `Agents::*`, `RedisMemory` for programmatic use
21
+
22
+ ---
23
+
24
+ ## Architecture
25
+
26
+ | Layer | Files | Role |
27
+ |---|---|---|
28
+ | Entry point | `agentf.rb`, `bin/agentf` | Config singleton + CLI boot |
29
+ | CLI | `cli/router.rb`, `cli/memory.rb`, `cli/code.rb` | Subcommand dispatch |
30
+ | Agents | `agents/base.rb`, role subclasses | Role-based agent classes |
31
+ | Workflow | `workflow_engine.rb` | Orchestrator / sequencer |
32
+ | Memory | `memory.rb`, `memory/confirmation_handler.rb` | Redis-backed storage |
33
+ | Contracts | `workflow_contract.rb`, `agent_execution_contract.rb` | Constraint enforcement |
34
+ | Tools | `tools/`, `tools.rb` | Primitive capabilities |
35
+ | Commands | `commands/registry.rb`, `commands/*.rb` | Named command registry |
36
+ | MCP | `mcp/server.rb`, `mcp/stub.rb` | stdio MCP protocol server |
37
+ | Service | `service/providers.rb` | Provider adapters |
38
+ | Installer | `installer.rb` | Manifest generation and provider setup |
39
+
40
+ ---
41
+
42
+ ## Patterns Worth Stealing
43
+
44
+ ### 1. Agents That Describe Themselves at the Class Level
45
+
46
+ All agent metadata — `description`, `deliverables`, `policy_boundaries`,
47
+ `when_to_use`, `commands` — are class methods, not instance state or external
48
+ YAML. Agents are self-documenting, introspectable at install time, and
49
+ verifiable without instantiating anything. The `Installer` reads these at
50
+ install time to generate markdown manifests.
51
+
52
+ ### 2. Three-Mode Contract Enforcement (advisory / enforcing / off)
53
+
54
+ A contract object wraps agent execution with `before!`/`after!` validation.
55
+ Run workflows in `advisory` mode during development (log violations, don't
56
+ stop), flip to `enforcing` for production. TDD phase discipline (`"red"` vs
57
+ `"green"`) is enforced at the contract layer, not by convention.
58
+
59
+ ### 3. Human-in-the-Loop Memory Writes
60
+
61
+ `ConfirmationHandler` wraps Redis writes so that when confirmation is needed,
62
+ instead of raising it returns `{ confirmation: true, payload: ..., instructions: ... }`.
63
+ The caller re-invokes with `confirmedWrite: "confirmed"`. Works identically
64
+ across CLI, MCP, and programmatic callers.
65
+
66
+ ### 4. Deterministic Local Embeddings With No ML Dependency
67
+
68
+ `EmbeddingProvider` SHA256-hashes tokens, uses the hash to pick a dimension in
69
+ a 64-element float vector, then normalizes it. Zero API calls, zero latency,
70
+ fully reproducible. Crude but sufficient for semantic memory search in a
71
+ dev-tool context.
72
+
73
+ ### 5. Black-Box Shell Script Evals
74
+
75
+ Each eval scenario is three files: `prompt.txt`, `scenario.yml`, and
76
+ `verify.sh`. The shell script asserts postconditions against real Redis state.
77
+ Simple, portable, no mocking — `agentf eval run all` just executes them.
78
+
79
+ ### 6. Graceful Redis Capability Degradation
80
+
81
+ The memory layer detects at runtime whether Redis Stack's JSON/Search/Vector
82
+ modules are present and degrades silently. Full semantic search if available;
83
+ plain key-value otherwise. No config flag needed — baked into `RedisMemory`.
84
+
85
+ ### 7. Ruby Generating Its Own TypeScript Integration
86
+
87
+ The installer generates TypeScript plugin files (`agentf-plugin.ts`,
88
+ `tsconfig.json`, `package.json`) from within Ruby for OpenCode integration.
89
+ A Ruby gem producing its own typed IDE plugin layer is unusual and practical.
90
+
91
+ ### 8. Policies Stored in Code, Not Config
92
+
93
+ `policy_boundaries` returns `{ "always" => [...], "ask_first" => [...], "never" => [...] }`
94
+ directly from Ruby class methods. Changing a policy means changing Ruby, not
95
+ YAML — no config drift.
96
+
97
+ ### 9. Workflow Profiles as Constants
98
+
99
+ Workflow compositions are defined as constants in `WorkflowEngine::PROFILES`
100
+ (e.g., `rails_standard`, `rails_37signals`), mapping task types to ordered
101
+ agent sequences. One canonical source of truth for workflow shapes.
102
+
103
+ ---
104
+
105
+ ## RobotLab Applicability Analysis
106
+
107
+ ### Patterns That Don't Apply
108
+
109
+ **Pattern 7 — Ruby Generating Its Own TypeScript Integration**
110
+ agentf is an IDE tool targeting Copilot/OpenCode. RobotLab is a Ruby library with no IDE integration surface. Not relevant.
111
+
112
+ **Pattern 6 — Graceful Redis Capability Degradation**
113
+ Already handled. `Memory` already degrades `redis → hash` fallback at initialization. `DocumentStore` uses fastembed, not Redis Stack — separate concern covered below.
114
+
115
+ ---
116
+
117
+ ### Patterns Worth Implementing
118
+
119
+ ---
120
+
121
+ #### Pattern 1 — Self-Describing Robots/Tools (HIGH VALUE)
122
+
123
+ This is the missing piece for `tool_manifest_plan.md`. Nothing auto-registers today because there is nowhere to register *to* and no class-level metadata to register. agentf's insight: put descriptors at the class level, not in instances.
124
+
125
+ ```ruby
126
+ class MyTool < RobotLab::Tool
127
+ self.description = "Fetch current weather for a location"
128
+ self.tags = [:network, :read_only]
129
+ end
130
+ ```
131
+
132
+ At class-load time, Zeitwerk triggers auto-registration into `RobotLab.tool_registry`. The selector-robot pattern becomes viable without any explicit registration ceremony. Live callable instances stay per-robot; the global registry holds lightweight descriptors only (name + description). This is the v1 the `tool_manifest_plan.md` actually needs — and avoids the MCP auto-registration problem identified in that plan's review notes.
133
+
134
+ Robot subclasses get the same treatment:
135
+ ```ruby
136
+ class SupportBot < RobotLab::Robot
137
+ self.description = "Handles tier-1 customer support"
138
+ self.capabilities = [:search, :ticket_creation]
139
+ end
140
+ ```
141
+
142
+ This also enables the `Installer`-style manifest generation: `RobotLab.tool_registry.summary` produces the compact name+description list the selector robot reasons over.
143
+
144
+ ---
145
+
146
+ #### Patterns 2 + 8 — Contract Enforcement + Policy Boundaries (HIGH VALUE, medium effort)
147
+
148
+ RobotLab has a circuit breaker (`max_tool_rounds`) and an error hierarchy — but no *pre/post validation layer* on execution. As AIA drives RobotLab into production use, this gap is significant.
149
+
150
+ The three modes map cleanly to the dev→prod lifecycle:
151
+ - `advisory` — log violations, don't block (development default)
152
+ - `enforcing` — raise on violation (production default)
153
+ - `off` — no overhead (test default)
154
+
155
+ Policies declared at the Tool class level:
156
+ ```ruby
157
+ class DeleteFileTool < RobotLab::Tool
158
+ policy :ask_first # always prompt user before executing
159
+ end
160
+
161
+ class FormatDriveTool < RobotLab::Tool
162
+ policy :never # contract blocks execution entirely in enforcing mode
163
+ end
164
+ ```
165
+
166
+ The contract wraps `robot.run()` with `before!`/`after!` hooks — checks token budget, validates tool policies, enforces max cost. A `RunConfig` field (`contract: :advisory`) controls the mode and flows through the standard hierarchy. This pairs naturally with the existing `RunConfig` merge semantics and would be the first true safety/governance layer in RobotLab.
167
+
168
+ ---
169
+
170
+ #### Pattern 9 — Workflow Profiles (LOW EFFORT, good discoverability)
171
+
172
+ Networks are currently built by hand each time. A `RobotLab::Profiles` module with named constants would reduce boilerplate and document canonical topologies:
173
+
174
+ ```ruby
175
+ RobotLab::Profiles::CONSENSUS # fan-out to N robots → reconciler
176
+ RobotLab::Profiles::PIPELINE # sequential chain
177
+ RobotLab::Profiles::PARALLEL # concurrent, no synthesis
178
+ RobotLab::Profiles::MCP_FAN_OUT # one robot per MCP server
179
+ ```
180
+
181
+ Each profile is a lambda/factory that takes robots + optional router and returns a configured network. This also gives AIA's `RobotFactory` topologies a canonical home in the library itself rather than application-level code.
182
+
183
+ ---
184
+
185
+ #### Pattern 5 — Eval Framework (MEDIUM EFFORT, high long-term value)
186
+
187
+ RobotLab has 22 example scripts but **zero assertions**. They are demos, not evals. agentf's `prompt.txt + scenario.yml + verify.sh` per scenario is the right shape.
188
+
189
+ For RobotLab the natural form:
190
+ - `evals/scenarios/convergence_basic/` — prompt, expected behavior description, `verify.rb` script
191
+ - `rake eval:run[convergence_basic]` replays against a VCR cassette and asserts postconditions
192
+ - `rake eval:run:all` becomes a regression suite for agent behavior
193
+
194
+ This connects directly to AIA's EDD (Eval-Driven Development) vision and would give confidence before a 1.0 release. The existing VCR/WebMock infrastructure is the right foundation.
195
+
196
+ ---
197
+
198
+ #### Pattern 4 — Deterministic Embedding Fallback (LOW EFFORT)
199
+
200
+ `DocumentStore` hard-requires fastembed — no fallback. On first use it downloads an ONNX model, which is slow and requires a working network. `word_hash` (stemmed TF vectors) already exists in the codebase and is used by `compress_history`, `search_history`, and `Convergence`.
201
+
202
+ A simple degradation path: when fastembed raises `LoadError` or the model is unavailable, `DocumentStore` falls back to `word_hash` cosine similarity with a logged warning. Zero new infrastructure — just wiring what already exists into `DocumentStore#embed`.
203
+
204
+ ---
205
+
206
+ #### Pattern 3 — HITL Confirmation Protocol (LOW VALUE given AskUser)
207
+
208
+ The agentf pattern — return `{ confirmation: true, payload: }` instead of raising, re-invoke with `confirmedWrite: "confirmed"` — is elegant for memory writes that need approval. However, RobotLab already has `AskUser` for the primary HITL use case. This pattern only becomes interesting if RobotLab needs programmatic HITL that doesn't require a terminal (e.g., web apps pausing for user approval via HTTP callback). Not urgent.
209
+
210
+ ---
211
+
212
+ ### Priority Ranking
213
+
214
+ | Priority | Pattern | Effort | Connects To |
215
+ |---|---|---|---|
216
+ | 1 | Self-describing tools/robots (class-level metadata) | Medium | `tool_manifest_plan.md` — makes ToolManifest v1 actionable |
217
+ | 2 | Contract enforcement + Policy boundaries | Medium | Production safety gap; RunConfig integration |
218
+ | 3 | Eval framework | Medium | Long-term regression confidence; AIA EDD |
219
+ | 4 | Workflow profiles | Low | AIA's RobotFactory; discoverability |
220
+ | 5 | DocumentStore word_hash fallback | Low | Dev ergonomics; no ONNX download needed |
221
+
222
+ **Natural weekly pairing:** Pattern 1 (self-describing classes → ToolManifest v1) + Pattern 9 (workflow profiles) form a coherent "discoverability and composability" theme. Patterns 2+8 are the right *next* architectural investment but scope to a separate week.
223
+
224
+ ---
225
+
226
+ ## Comparison to Other Agent Frameworks
227
+
228
+ | Aspect | `agentf` | LangChain (Python) | CrewAI (Python) |
229
+ |---|---|---|---|
230
+ | Language | Ruby | Python | Python |
231
+ | LLM calls | None (delegates to IDE) | Direct | Direct |
232
+ | Agent communication | Shared Redis memory | In-process state | Sequential/hierarchical |
233
+ | Memory | Episodic + semantic (Redis Stack) | Various vector stores | Basic |
234
+ | IDE integration | MCP server (Copilot, OpenCode) | None native | None native |
235
+ | Contract enforcement | 3-mode advisory/enforcing/off | None | None |
236
+ | Self-describing agents | Class-method metadata | No | Role strings in YAML |
237
+ | Eval framework | Black-box shell scripts | Unit tests | Unit tests |
238
+ | TDD enforcement | Built-in (red/green contracts) | None | None |
239
+
240
+ Within Ruby there is essentially no direct equivalent. `ruby-openai` and
241
+ `omniai` handle LLM API calls but have no orchestration. `agentf` is novel in
242
+ the Ruby ecosystem for combining Redis-backed episodic memory, role-specialized
243
+ agent classes, contract enforcement, and an MCP stdio server in one gem.
244
+
245
+ ---
246
+
247
+ ## The Big Takeaway
248
+
249
+ The most instructive thing about `agentf` is its **division of responsibility**:
250
+ the gem owns memory, sequencing, policies, and tool-exposure via MCP — the
251
+ IDE's AI owns inference. That separation means no API keys, no HTTP calls to
252
+ LLM providers, and no model coupling. If building AI-assisted tooling in Ruby,
253
+ that architecture is worth emulating.
data/agents.md ADDED
@@ -0,0 +1,14 @@
1
+ ### Local Agent Context: Repository Root
2
+
3
+ - Start here, then navigate to the closest directory AGENTS.md before coding.
4
+ - Do not operate from root unless necessary.
5
+
6
+ ## Table of Contents
7
+
8
+ - [.architecture/reviews](./.architecture/reviews/AGENTS.md): Access `config.yml` for architecture-related settings.
9
+ - [.github/workflows](./.github/workflows/AGENTS.md): Review `deploy-github-pages.yml` for CI/CD pipeline specifics.
10
+ - [.ruby-lsp](./.ruby-lsp/AGENTS.md): Initialize your environment with `Gemfile` dependencies.
11
+ - [docs](./docs/AGENTS.md): Consult `/docs/api/core/robot.md` for robot-related API documentation.
12
+ - [examples](./examples/AGENTS.md): Explore `14_rusty_circuit/open_mic.rb` for demo scripts in action.
13
+ - [lib/generators/robot_lab](./lib/generators/robot_lab/AGENTS.md): Use `templates/initializer.rb.tt` for custom project setup.
14
+ - [test/robot_lab](./test/robot_lab/AGENTS.md): Implement new tests based on `test_helper.rb` configurations.
@@ -75,6 +75,7 @@ classDiagram
75
75
  | [AskUser](tool.md#built-in-askuser) | Built-in tool for terminal-based user interaction |
76
76
  | [Memory](memory.md) | Reactive key-value store for sharing data |
77
77
  | RobotMessage | Typed envelope for bus-based inter-robot communication |
78
+ | `RobotLab::Runnable` | Shared interface (`crew`, `chief`, `run`) implemented by both `Robot` and `Network` — see [Runnable Protocol](../../architecture/core-concepts.md#runnable-protocol) |
78
79
 
79
80
  ## Quick Examples
80
81