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.
- checksums.yaml +4 -4
- data/.architecture/AGENTS.md +32 -0
- data/.architecture/config.yml +8 -0
- data/.architecture/members.yml +60 -0
- data/.architecture/reviews/feature-free-will.md +490 -0
- data/.architecture/reviews/overall-codebase.md +427 -0
- data/.claude/settings.local.json +57 -0
- data/.codex/config.toml +2 -0
- data/.envrc +1 -0
- data/.irbrc +2 -2
- data/.loki +60 -0
- data/.quality/reek_baseline.txt +43 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +126 -0
- data/CLAUDE.md +139 -0
- data/README.md +130 -73
- data/Rakefile +131 -9
- data/agent2agent_review.md +192 -0
- data/agentf_improvements.md +253 -0
- data/agents.md +14 -0
- data/docs/api/core/index.md +1 -0
- data/docs/api/core/network.md +31 -1
- data/docs/api/core/robot.md +91 -5
- data/docs/api/errors.md +47 -2
- data/docs/api/index.md +3 -0
- data/docs/architecture/core-concepts.md +40 -0
- data/docs/architecture/network-orchestration.md +8 -0
- data/docs/architecture/robot-execution.md +1 -1
- data/docs/examples/index.md +37 -2
- data/docs/getting-started/configuration.md +39 -7
- data/docs/guides/building-robots.md +39 -1
- data/docs/guides/creating-networks.md +6 -1
- data/docs/guides/hooks.md +997 -0
- data/docs/guides/index.md +19 -14
- data/docs/guides/knowledge.md +9 -3
- data/docs/guides/observability.md +206 -7
- data/docs/guides/using-tools.md +69 -0
- data/docs/index.md +31 -4
- data/docs/superpowers/plans/2026-05-06-agentskills.md +1303 -0
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +247 -0
- data/examples/.envrc +1 -0
- data/examples/01_simple_robot.rb +5 -9
- data/examples/02_tools.rb +5 -9
- data/examples/03_network.rb +18 -10
- data/examples/04_mcp.rb +21 -29
- data/examples/05_streaming.rb +12 -18
- data/examples/06_prompt_templates.rb +11 -19
- data/examples/07_network_memory.rb +16 -31
- data/examples/08_llm_config.rb +10 -22
- data/examples/09_chaining.rb +16 -27
- data/examples/10_memory.rb +12 -28
- data/examples/11_network_introspection.rb +15 -29
- data/examples/12_message_bus.rb +5 -12
- data/examples/13_spawn.rb +5 -10
- data/examples/14_rusty_circuit/.envrc +1 -0
- data/examples/14_rusty_circuit/comic.rb +2 -0
- data/examples/14_rusty_circuit/heckler.rb +1 -1
- data/examples/14_rusty_circuit/open_mic.rb +1 -3
- data/examples/14_rusty_circuit/scout.rb +2 -0
- data/examples/15_memory_network_and_bus/.envrc +1 -0
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +6 -3
- data/examples/15_memory_network_and_bus/linux_writer.rb +1 -1
- data/examples/15_memory_network_and_bus/output/combined_article.md +6 -6
- data/examples/15_memory_network_and_bus/output/final_article.md +6 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +4 -2
- data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
- data/examples/15_memory_network_and_bus/output/memory.json +6 -6
- data/examples/15_memory_network_and_bus/output/revision_1.md +10 -11
- data/examples/15_memory_network_and_bus/output/revision_2.md +6 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
- data/examples/16_writers_room/.envrc +1 -0
- data/examples/16_writers_room/writers_room.rb +2 -4
- data/examples/17_skills.rb +8 -17
- data/examples/18_rails/Gemfile +1 -0
- data/examples/19_token_tracking.rb +9 -15
- data/examples/20_circuit_breaker.rb +10 -19
- data/examples/21_learning_loop.rb +11 -20
- data/examples/22_context_compression.rb +6 -13
- data/examples/23_convergence.rb +6 -17
- data/examples/24_structured_delegation.rb +11 -15
- data/examples/25_history_search.rb +5 -12
- data/examples/26_document_store.rb +6 -13
- data/examples/27_incident_response/incident_response.rb +4 -5
- data/examples/28_mcp_discovery.rb +8 -11
- data/examples/29_ractor_tools.rb +4 -9
- data/examples/30_ractor_network.rb +10 -19
- data/examples/31_launch_assessment.rb +10 -23
- data/examples/32_newsletter_reader.rb +188 -0
- data/examples/33_stock_generator.rb +80 -0
- data/examples/33_stock_predictor.rb +306 -0
- data/examples/34_agentskills.rb +72 -0
- data/examples/35_hooks.rb +256 -0
- data/examples/README.md +8 -1
- data/examples/common.rb +80 -0
- data/examples/ruboruby.md +423 -0
- data/examples/xyzzy.rb +90 -0
- data/lib/robot_lab/agent_skill.rb +64 -0
- data/lib/robot_lab/agent_skill_catalog.rb +74 -0
- data/lib/robot_lab/ask_user.rb +2 -2
- data/lib/robot_lab/budget/ledger.rb +98 -0
- data/lib/robot_lab/bus_poller.rb +12 -5
- data/lib/robot_lab/capabilities.rb +84 -0
- data/lib/robot_lab/config/defaults.yml +10 -0
- data/lib/robot_lab/config.rb +5 -16
- data/lib/robot_lab/delegation_future.rb +1 -1
- data/lib/robot_lab/doom_loop_detector.rb +98 -0
- data/lib/robot_lab/error.rb +34 -4
- data/lib/robot_lab/errors.rb +45 -0
- data/lib/robot_lab/history_compressor.rb +4 -10
- data/lib/robot_lab/hook.rb +79 -0
- data/lib/robot_lab/hook_context.rb +194 -0
- data/lib/robot_lab/hook_registry.rb +55 -0
- data/lib/robot_lab/hooks.rb +87 -0
- data/lib/robot_lab/mcp/client.rb +1 -2
- data/lib/robot_lab/mcp/connection_poller.rb +5 -5
- data/lib/robot_lab/mcp/server.rb +1 -1
- data/lib/robot_lab/mcp/server_discovery.rb +0 -2
- data/lib/robot_lab/memory.rb +32 -27
- data/lib/robot_lab/memory_change.rb +2 -2
- data/lib/robot_lab/message.rb +4 -4
- data/lib/robot_lab/narrator.rb +87 -0
- data/lib/robot_lab/network.rb +87 -25
- data/lib/robot_lab/robot/agent_skill_matching.rb +99 -0
- data/lib/robot_lab/robot/budget.rb +89 -0
- data/lib/robot_lab/robot/bus_messaging.rb +79 -33
- data/lib/robot_lab/robot/history_search.rb +4 -1
- data/lib/robot_lab/robot/hooking.rb +56 -0
- data/lib/robot_lab/robot/mcp_management.rb +12 -12
- data/lib/robot_lab/robot/template_rendering.rb +62 -42
- data/lib/robot_lab/robot.rb +482 -242
- data/lib/robot_lab/robot_result.rb +6 -5
- data/lib/robot_lab/run_config.rb +27 -14
- data/lib/robot_lab/runnable.rb +51 -0
- data/lib/robot_lab/sandbox/null.rb +13 -0
- data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
- data/lib/robot_lab/sandbox.rb +52 -0
- data/lib/robot_lab/script_tool.rb +134 -0
- data/lib/robot_lab/state_proxy.rb +7 -5
- data/lib/robot_lab/task.rb +26 -20
- data/lib/robot_lab/tool.rb +54 -13
- data/lib/robot_lab/tool_config.rb +1 -1
- data/lib/robot_lab/tool_manifest.rb +5 -7
- data/lib/robot_lab/user_message.rb +2 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab/waiter.rb +1 -1
- data/lib/robot_lab.rb +89 -51
- data/logfile +8 -0
- data/mkdocs.yml +3 -3
- data/robot_concurrency.md +38 -0
- data/simple_acp_review.md +298 -0
- data/temp.md +6 -0
- data/tool_manifest_plan.md +155 -0
- metadata +82 -83
- data/docs/examples/rails-application.md +0 -419
- data/docs/guides/ractor-parallelism.md +0 -364
- data/docs/guides/rails-integration.md +0 -681
- data/docs/superpowers/plans/2026-04-14-ractor-integration.md +0 -1538
- data/docs/superpowers/specs/2026-04-14-ractor-integration-design.md +0 -258
- data/lib/generators/robot_lab/install_generator.rb +0 -90
- data/lib/generators/robot_lab/job_generator.rb +0 -40
- data/lib/generators/robot_lab/robot_generator.rb +0 -55
- data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -42
- data/lib/generators/robot_lab/templates/job.rb.tt +0 -21
- data/lib/generators/robot_lab/templates/migration.rb.tt +0 -32
- data/lib/generators/robot_lab/templates/result_model.rb.tt +0 -52
- data/lib/generators/robot_lab/templates/robot.rb.tt +0 -31
- data/lib/generators/robot_lab/templates/robot_job.rb.tt +0 -18
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +0 -34
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +0 -59
- data/lib/generators/robot_lab/templates/thread_model.rb.tt +0 -40
- data/lib/robot_lab/document_store.rb +0 -155
- data/lib/robot_lab/ractor_boundary.rb +0 -42
- data/lib/robot_lab/ractor_job.rb +0 -37
- data/lib/robot_lab/ractor_memory_proxy.rb +0 -85
- data/lib/robot_lab/ractor_network_scheduler.rb +0 -154
- data/lib/robot_lab/ractor_worker_pool.rb +0 -117
- data/lib/robot_lab/rails_integration/engine.rb +0 -29
- data/lib/robot_lab/rails_integration/job.rb +0 -158
- data/lib/robot_lab/rails_integration/railtie.rb +0 -51
- data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +0 -72
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,132 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
### Added
|
|
12
|
+
- `BusMessaging#respond_to_tasks` and `#serve` — a member auto-answers inbound
|
|
13
|
+
(non-reply) bus tasks and replies to the sender. `serve` runs each task through
|
|
14
|
+
`#run`; `respond_to_tasks { |m| ... }` is the generic form. Makes a Robot a
|
|
15
|
+
first-class bus responder (symmetric with how a Cyborg answers its human)
|
|
16
|
+
without hand-wiring `on_message`.
|
|
17
|
+
- `.loki` Asgard task file: `test`, `rubocop`, `rubocop_fix`, `flog`, `flay`, `quality`, `build`, `install`, `release`, `integration`, `docs`, and `examples` tasks via the Asgard task runner
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `BusMessaging` now synchronizes its message counter and outbox with a mutex, so
|
|
21
|
+
concurrent sends and poller-thread reply correlation can't clobber each other.
|
|
22
|
+
- `flay_check` Rake task: structural code duplication gate (mass threshold 50); integrated into the `quality` Rake task
|
|
23
|
+
- `flay` gem added to development dependencies
|
|
24
|
+
- `test_output.txt`, `flay_output.txt`, `flog_output.txt`, and `rubocop_output.txt` added to `.gitignore`
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- `test/test_helper.rb`: test output redirected to `test_output.txt` via `$stdout` reassignment; `TerminalSummaryReporter` prints a single PASS/FAIL summary line to the terminal
|
|
28
|
+
- `Rakefile`: `rubocop` and `rubocop_fix` tasks removed (now owned by Asgard); `flay_check` integrated into the `quality` gate
|
|
29
|
+
|
|
30
|
+
## [0.2.6] - 2026-05-23
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`:compaction` hook family** — fires when `maybe_compact` determines that conversation history should be compressed. Provides `before_compaction`, `around_compaction`, `after_compaction`, and `on_compaction` callbacks.
|
|
35
|
+
- `on_compaction` allows an extension to supply a complete replacement message set via `ctx.compacted_messages`; when set, the core skips its own compaction strategy entirely (`ctx.handled?` returns true).
|
|
36
|
+
- `CompactionHookContext` — carries `robot`, `messages_before` (frozen snapshot), `config`, `strategy` (`:context_window`, `:custom`, or other symbol), `compacted_messages`, and `handled?`.
|
|
37
|
+
- **`:learn` hook family** — fires on every `robot.learn(text)` call with non-empty text. Provides `before_learn`, `around_learn`, `after_learn`, and `on_learn` callbacks.
|
|
38
|
+
- `on_learn` fires after the text has been stored and `ctx.stored = true`, giving extensions a reliable hook point for cross-session persistence.
|
|
39
|
+
- `LearnHookContext` — carries `robot`, `text`, `learnings_before` (frozen snapshot), and `stored`.
|
|
40
|
+
- **`over_compact_threshold?`** private predicate extracted from `compact_if_over_context_window` for independent testability.
|
|
41
|
+
- Hook tests for both new families added to `test/robot_lab/hooks_test.rb` (18 new tests across `RobotLabCompactionHooksTest` and `RobotLabLearnHooksTest`).
|
|
42
|
+
- Documentation for `:compaction` and `:learn` hook families added to `docs/guides/hooks.md`.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`Robot#on`** now accepts and forwards `context:` to `HookRegistry#on`, allowing extensions to pass per-registration domain config at registration time (e.g. `robot.on(MyHook, context: { domain: "finance" })`). Previously `context:` was silently dropped.
|
|
47
|
+
- **`maybe_compact`** refactored to dispatch through the `:compaction` hook family and delegate to `on_compaction` before falling back to the built-in strategy.
|
|
48
|
+
- **`Robot#learn`** refactored to dispatch through the `:learn` hook family; `on_learn` fires after deduplication with `ctx.stored` reflecting whether the text was actually added.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **`RobotLab::Hook` base class** (`lib/robot_lab/hook.rb`) — all hook handlers inherit from this class. Subclasses define lifecycle callbacks as `class << self` methods (`before_run`, `after_run`, etc.). Namespace is auto-derived from the class name via snake_case conversion of the final class name segment (e.g. `AuditHook` → `:audit_hook`); override with `self.namespace = :custom`. Ractor-safe by design: handler classes are Ruby constants, not Procs, so registrations are natively serializable across Ractor boundaries.
|
|
53
|
+
- **Hook system** — lifecycle hooks across all execution boundaries. Register handler classes with `RobotLab.on`, `network.on`, or `robot.on` for five families: `:run`, `:llm_generation`, `:tool_call`, `:network_run`, and `:task`, each with `before_*`, `around_*`, `after_*`, and (where applicable) `on_error` variants.
|
|
54
|
+
- `HookRegistry::Registration` — `Data.define(:handler_class, :context)`; stores a handler class reference and optional per-registration default `DotState` values. No Proc, no namespace field — namespace is read from the handler class.
|
|
55
|
+
- `HookContext` and five typed subclasses: `RunHookContext`, `LlmGenerationHookContext`, `ToolCallHookContext`, `NetworkRunHookContext`, `TaskHookContext` — unchanged
|
|
56
|
+
- `DotState` / `ExtensionState` — namespace-isolated dot-access state carried on every context object; `ctx.local` reads/writes state for the active handler's namespace
|
|
57
|
+
- `context:` parameter on `on(handler_class, context: {...})` — sets per-registration default `DotState` values merged in before each callback fires
|
|
58
|
+
- Around hooks chain correctly across handler class registrations; return value is propagated up the chain
|
|
59
|
+
- Per-run hooks via `robot.run("msg", hooks: MyHook)` or `robot.run("msg", hooks: [MyHook, OtherHook])`
|
|
60
|
+
- `on_error` fires for `:run`, `:network_run`, and `:task` families; exception is re-raised after all error handlers complete
|
|
61
|
+
- **`examples/xyzzy.rb`** — updated reference extension: `RobotLab::Xyzzy < RobotLab::Hook` with `class << self` methods for every hook family; logs each callback with a timestamped stdout tagline and a structured logger context snapshot; registered globally with `RobotLab.on(RobotLab::Xyzzy)`
|
|
62
|
+
- **Example 35** (`examples/35_hooks.rb`) — updated full hook pipeline demo: xyzzy extension tracing all hooks, `around_run` perf timer handler class, `around_llm_generation` response cache handler class (cache hits skip the LLM entirely), `before/after_llm_generation` tracer, tool call hooks, network/task hooks, and `on_error`
|
|
63
|
+
- **`examples/common.rb`** — added explicit `openai_api_key`, `openai_organization_id`, and `openai_project_id` to `RubyLLM.configure` to match provider configurator expectations; updated default model to `gpt-4.1-mini`
|
|
64
|
+
|
|
65
|
+
## [0.2.1] - 2026-05-19
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **`examples/common.rb`** — shared setup file required by all numbered examples. Defines:
|
|
70
|
+
- `LlmConfig = Data.define(:provider, :model)` and a frozen `LLM` hash with `:default` (OpenAI/gpt-5.4), `:local` (Ollama/llama3.2), and `:anthropic` (claude-opus-4-7) entries — access as `LLM[:default].model`
|
|
71
|
+
- `RubyLLM.configure` with null logger and `LLM[:default].model` as `default_model`
|
|
72
|
+
- `RobotLab.configure` with null logger
|
|
73
|
+
- Output helpers: `banner(title)`, `section(title)`, `hr`, `show_code(ruby_string, label:)` using `rouge` for syntax highlighting
|
|
74
|
+
- **`rouge` gem** added to development group for syntax-highlighted example output
|
|
75
|
+
- **`.envrc` files** in `examples/`, `examples/14_rusty_circuit/`, `examples/15_memory_network_and_bus/`, and `examples/16_writers_room/` — each exports `ROBOT_LAB_TEMPLATE_PATH` pointing at the local `prompts/` directory for use with [direnv](https://direnv.net/)
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- **All 27+ numbered examples** refactored to `require_relative "common"` instead of requiring the gem directly, and to use `LLM[:default].model` instead of the hardcoded string `"gpt-5.4"`
|
|
80
|
+
- **Example 04 (MCP)** now calls `robot.connect_mcp!` before inspecting MCP attributes, fixing a lazy-initialization issue where `mcp_clients` and `mcp_tools` were empty until the first `run()` call
|
|
81
|
+
|
|
82
|
+
## [0.2.1] - 2026-05-11 (unreleased)
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
|
|
86
|
+
- **`flog` complexity gate** — `flog_check` Rake task enforces method-level complexity limits (warn ≥20, fail ≥50); `quality` task runs tests, RuboCop, and Flog in sequence with a unified pass/fail summary
|
|
87
|
+
- `flog` gem added to development/test group
|
|
88
|
+
- Branch coverage enabled unconditionally (previously CI-only) with minimum thresholds: line 95%, branch 75%
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- Bumped version to 0.2.1
|
|
93
|
+
- **`Robot#initialize` decomposed** into focused private methods: `assign_identity_ivars`, `build_effective_config`, `extract_config_ivars`, `initialize_runtime_state`, `initialize_memory`, `configure_learning`, `apply_template`, `apply_system_prompt`, `apply_chat_params`, `register_chat_callbacks`
|
|
94
|
+
- **`Robot#run` decomposed** into: `resolve_run_memory`, `prepare_tools`, `invoke_ask`, `enforce_token_budget!`
|
|
95
|
+
- **`BusPoller#process_and_drain`** split into `drain_queued_deliveries` and `release_robot` for independent testability
|
|
96
|
+
- **`TemplateRendering#apply_skills_and_template_to_chat`** split into `collect_prompt_content` (pure computation) and `apply_prompt_to_chat` (pure mutation)
|
|
97
|
+
- Removed `.serena/` project configuration files from version control
|
|
98
|
+
- Removed `.claude/memory.sqlite3` database files from version control
|
|
99
|
+
|
|
100
|
+
## [0.2.0] - 2026-05-07
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- **`Durable::Learning`** — cross-session and within-session learning capability for robots. Robots accept `learn: true` and `learn_domain:` constructor params to persist knowledge across sessions via `~/.robot_lab/durable/` YAML files.
|
|
105
|
+
- **`Durable::Store`** — YAML-backed knowledge store with file locking, keyword recall, and confidence tracking.
|
|
106
|
+
- **`Durable::Entry`** — immutable value object for knowledge records with confidence progression.
|
|
107
|
+
- **`Durable::Reflector`** — promotes session learnings to durable storage at end of each run.
|
|
108
|
+
- **`RecallKnowledge` tool** — robots query past knowledge before uncertain decisions.
|
|
109
|
+
- **`RecordKnowledge` tool** — robots persist new knowledge learned during a session.
|
|
110
|
+
- **`AgentSkill`** — value object for discoverable skills defined by `SKILL.md` files with YAML front matter (name, description, version, dependencies, parameters)
|
|
111
|
+
- **`AgentSkillCatalog`** — service for locating and indexing `AgentSkills/` directories at runtime
|
|
112
|
+
- **`AgentSkillMatching`** — `Robot` mixin enabling runtime embedding-based skill injection: the robot selects the most relevant skills from a catalog based on semantic similarity before each run
|
|
113
|
+
- **`ScriptTool`** — factory that wraps shell scripts as robot tools; auto-generates JSON schema from script `--help` output
|
|
114
|
+
- **`DoomLoopDetector`** — detects consecutive and cyclic tool-call repetition; wired into `Robot#run` via singleton `execute_tool` override
|
|
115
|
+
- **`doom_loop_threshold`** field on `RunConfig` — configures the repetition threshold before a `ToolLoopError` is raised
|
|
116
|
+
- **`auto_compact` and `compact_threshold`** fields on `RunConfig` — `:context_window` mode estimates token usage before each run and calls `compress_history` when usage exceeds the threshold (default 80%); a `Proc` value delegates the decision and strategy entirely to the caller
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- Bumped version to 0.2.0
|
|
121
|
+
- **Extension gems extracted** — `robot_lab-document_store`, `robot_lab-durable`, `robot_lab-ractor`, and `robot_lab-rails` are now separate gems distributed via rubygems.org; the core gemspec drops `fastembed`, `ractor_queue`, and `ractor-wrapper` as hard dependencies
|
|
122
|
+
- **`Durable::Learning` inclusion** is now conditional on the `robot_lab-durable` gem being loaded
|
|
123
|
+
- **Tool Ractor routing** guarded on `RobotLab.respond_to?(:ractor_pool)` so the core gem runs without the ractor extension
|
|
124
|
+
- **Memory drainer scheduling** — `@drainer_scheduled` remains `true` when rescheduling to prevent concurrent writers from spawning competing drain fibers
|
|
125
|
+
- `@chat` state reset now uses `reset_messages!` / `add_message` public API instead of internal instance variable manipulation
|
|
126
|
+
- Rails generators moved to `robot_lab-rails` extension gem
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
- Memory drainer double-schedule race when concurrent writers triggered overlapping drain cycles
|
|
131
|
+
- `DocumentStore` instantiation guarded with a `LoadError` message when the extension gem is absent
|
|
132
|
+
- `AgentSkill` YAML parsing hardened against empty strings and non-Hash front matter
|
|
133
|
+
- Missing requires added to examples after gem extraction
|
|
134
|
+
- `doom_loop_threshold` and `auto_compact` documented in README and guides
|
|
135
|
+
- `learn:` parameter and `robot_lab-acp` documented in README
|
|
136
|
+
|
|
11
137
|
## [0.1.0] - 2026-04-29
|
|
12
138
|
|
|
13
139
|
### Added
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
RobotLab is a Ruby framework for building and orchestrating multi-robot LLM workflows. It provides:
|
|
8
|
+
- **Robots**: LLM agents with tools, templates, and memory
|
|
9
|
+
- **Networks**: Orchestration of multiple robots with routing logic
|
|
10
|
+
- **MCP Integration**: Model Context Protocol for external tool servers
|
|
11
|
+
- **Rails Integration**: Generators and ActiveRecord support for conversation history
|
|
12
|
+
|
|
13
|
+
Built on top of [ruby_llm](https://rubyllm.com) and uses Zeitwerk for autoloading.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Run all tests
|
|
19
|
+
bundle exec rake test
|
|
20
|
+
|
|
21
|
+
# Run a single test file
|
|
22
|
+
bundle exec rake test_file[robot_lab/robot_test.rb]
|
|
23
|
+
|
|
24
|
+
# Run tests with verbose output
|
|
25
|
+
bundle exec rake test_verbose
|
|
26
|
+
|
|
27
|
+
# Run integration tests only
|
|
28
|
+
bundle exec rake integration
|
|
29
|
+
|
|
30
|
+
# Lint with RuboCop
|
|
31
|
+
bundle exec rubocop
|
|
32
|
+
|
|
33
|
+
# Auto-fix RuboCop offenses
|
|
34
|
+
bundle exec rubocop -a
|
|
35
|
+
|
|
36
|
+
# Run all examples
|
|
37
|
+
bundle exec rake examples:all
|
|
38
|
+
|
|
39
|
+
# Run specific example by number (e.g., 01, 02)
|
|
40
|
+
bundle exec rake examples:run[1]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
### Core Classes
|
|
46
|
+
|
|
47
|
+
- **`RobotLab`** (`lib/robot_lab.rb`): Module entry point with factory methods `build()` for robots and `create_network()` for networks
|
|
48
|
+
- **`Robot`** (`lib/robot_lab/robot.rb`): Subclass of `RubyLLM::Agent` with template-based prompts, tools, MCP clients, and memory. Creates a persistent chat on initialization. Use `robot.run("...")` to interact. When standalone uses its own memory; when in a network uses shared network memory
|
|
49
|
+
- **`Network`** (`lib/robot_lab/network.rb`): Orchestrates multiple robots with routing logic. Robots execute sequentially sharing memory. Router is a lambda that returns robot names
|
|
50
|
+
- **`NetworkRun`** (`lib/robot_lab/network_run.rb`): Stateful execution of a network run with isolated memory clone
|
|
51
|
+
|
|
52
|
+
### RunConfig
|
|
53
|
+
|
|
54
|
+
- **`RunConfig`** (`lib/robot_lab/run_config.rb`): Shared configuration object for LLM, tools, callbacks, and infrastructure settings. Flows through the hierarchy: `RobotLab.config -> Network -> Robot -> Template front matter -> Task -> Runtime`. Supports keyword construction, block DSL, merge semantics (more-specific wins), and `apply_to(chat)` for LLM field application. Both Robot and Network accept `config:` parameter. Infrastructure fields include: `bus`, `enable_cache`, `max_tool_rounds`, `token_budget`, `ractor_pool_size`, `max_concurrent_robots`, `doom_loop_threshold`, `auto_compact`, `compact_threshold`.
|
|
55
|
+
|
|
56
|
+
### Memory System
|
|
57
|
+
|
|
58
|
+
- **`Memory`** (`lib/robot_lab/memory.rb`): Key-value store with reserved keys (`:data`, `:results`, `:messages`, `:session_id`, `:cache`). Supports Redis backend. Includes semantic caching via RubyLLM::SemanticCache
|
|
59
|
+
|
|
60
|
+
### MCP (Model Context Protocol)
|
|
61
|
+
|
|
62
|
+
- **`MCP::Client`** (`lib/robot_lab/mcp/client.rb`): Connects to MCP servers
|
|
63
|
+
- **Transports** (`lib/robot_lab/mcp/transports/`): stdio, websocket, SSE, streamable HTTP
|
|
64
|
+
|
|
65
|
+
### Built-in Tools
|
|
66
|
+
|
|
67
|
+
- **`AskUser`** (`lib/robot_lab/ask_user.rb`): Tool that lets a robot ask the user a question via the terminal. Supports open-ended text, multiple choice, and default values. IO sourced from `robot.input`/`robot.output` (defaults to `$stdin`/`$stdout`)
|
|
68
|
+
|
|
69
|
+
### Adapters
|
|
70
|
+
|
|
71
|
+
Provider adapters in `lib/robot_lab/adapters/`: Anthropic, OpenAI, Gemini (for provider-specific formatting)
|
|
72
|
+
|
|
73
|
+
### Configuration Hierarchy
|
|
74
|
+
|
|
75
|
+
Tools and MCP servers use hierarchical configuration: `runtime > robot > network > global config`. Values can be `:none`, `:inherit`, or explicit arrays.
|
|
76
|
+
|
|
77
|
+
## Key Patterns
|
|
78
|
+
|
|
79
|
+
### Creating Robots
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# Bare robot (no template or prompt)
|
|
83
|
+
robot = RobotLab.build
|
|
84
|
+
robot.with_instructions("You are helpful.").run("Hello!")
|
|
85
|
+
|
|
86
|
+
# With template (.md file in prompts directory with YAML front matter)
|
|
87
|
+
robot = RobotLab.build(name: "helper", template: :helper, context: { key: "value" })
|
|
88
|
+
result = robot.run("Hello!")
|
|
89
|
+
|
|
90
|
+
# With inline system prompt
|
|
91
|
+
robot = RobotLab.build(name: "bot", system_prompt: "You are helpful.")
|
|
92
|
+
result = robot.run("What can you do?")
|
|
93
|
+
|
|
94
|
+
# With tools
|
|
95
|
+
robot = RobotLab.build(name: "bot", system_prompt: "...", local_tools: [my_tool])
|
|
96
|
+
|
|
97
|
+
# Chaining with_* methods
|
|
98
|
+
robot.with_temperature(0.9).with_model("claude-sonnet-4").run("Be creative!")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Creating Networks
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
router = ->(args) { args.call_count.zero? ? ["classifier"] : nil }
|
|
105
|
+
network = RobotLab.create_network(name: "support", robots: [robot1, robot2], router: router)
|
|
106
|
+
result = network.run(message: "Hello")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Router Args
|
|
110
|
+
|
|
111
|
+
Router receives `Router::Args` with: `context`, `network`, `stack`, `call_count`, `last_result`
|
|
112
|
+
|
|
113
|
+
## Testing
|
|
114
|
+
|
|
115
|
+
- Uses Minitest with SimpleCov for coverage
|
|
116
|
+
- Test helper at `test/test_helper.rb` provides `build_robot`, `build_network`, `build_tool` helpers
|
|
117
|
+
- Templates for tests are in `examples/prompts/`
|
|
118
|
+
- VCR and WebMock for HTTP stubbing
|
|
119
|
+
|
|
120
|
+
## Dependencies
|
|
121
|
+
|
|
122
|
+
Core: zeitwerk, ruby_llm (~> 1.12), ruby_llm-mcp, prompt_manager, ruby_llm-schema, ruby_llm-semantic_cache, async, simple_flow, state_machines
|
|
123
|
+
|
|
124
|
+
### Templates
|
|
125
|
+
|
|
126
|
+
Templates use prompt_manager format: single `.md` files with YAML front matter in the configured prompts directory.
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
---
|
|
130
|
+
description: A helpful assistant
|
|
131
|
+
parameters:
|
|
132
|
+
company_name: null
|
|
133
|
+
tone: friendly
|
|
134
|
+
---
|
|
135
|
+
You are a helpful assistant for <%= company_name %>.
|
|
136
|
+
Respond in a <%= tone %> manner.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Front matter supports: `description`, `parameters` (null = required), LLM config keys (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, etc.), and robot extras (`robot_name`, `tools`, `mcp`). Constructor-provided values always override front matter.
|
data/README.md
CHANGED
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
- <strong>Shared Memory</strong> - Reactive key-value store with subscriptions<br>
|
|
26
26
|
- <strong>Message Bus</strong> - Bidirectional robot communication via TypedBus<br>
|
|
27
27
|
- <strong>Dynamic Spawning</strong> - Robots create new robots at runtime<br>
|
|
28
|
+
- <strong>Hook System</strong> - Lifecycle hooks across every execution boundary for instrumentation, caching, and extensions<br>
|
|
28
29
|
- <strong>Layered Configuration</strong> - Cascading YAML, env vars, and RunConfig<br>
|
|
29
|
-
- <strong>Rails Integration</strong> - Generators, background jobs, Turbo Stream broadcasting<br>
|
|
30
|
+
- <strong>Rails Integration</strong> - Generators, background jobs, Turbo Stream broadcasting (via <a href="https://github.com/MadBomber/robot_lab-rails">robot_lab-rails</a>)<br>
|
|
30
31
|
- <strong>Token & Cost Tracking</strong> - Per-run and cumulative token counts on every robot<br>
|
|
31
32
|
- <strong>Tool Loop Circuit Breaker</strong> - <code>max_tool_rounds:</code> guards against runaway tool call loops<br>
|
|
32
33
|
- <strong>Learning Accumulation</strong> - <code>robot.learn()</code> builds up cross-run observations with deduplication<br>
|
|
@@ -93,7 +94,7 @@ robot = RobotLab.build(
|
|
|
93
94
|
|
|
94
95
|
### Configuration
|
|
95
96
|
|
|
96
|
-
RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layered configuration.
|
|
97
|
+
RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layered configuration. Configuration is loaded automatically from multiple sources in priority order:
|
|
97
98
|
|
|
98
99
|
1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
|
|
99
100
|
2. Environment-specific overrides (development, test, production)
|
|
@@ -124,6 +125,14 @@ ruby_llm:
|
|
|
124
125
|
request_timeout: 180
|
|
125
126
|
```
|
|
126
127
|
|
|
128
|
+
Runtime-only attributes (such as the logger) can be set with a `configure` block:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
RobotLab.configure do |c|
|
|
132
|
+
c.logger = Logger.new(File::NULL) # silence logging
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
127
136
|
### Using Templates
|
|
128
137
|
|
|
129
138
|
For production applications, RobotLab supports a template system built on [PromptManager](https://github.com/MadBomber/prompt_manager). Templates allow you to:
|
|
@@ -677,6 +686,67 @@ robot.clear_messages # flushes broken history; system prompt is kept
|
|
|
677
686
|
result = robot.run("Something new.") # robot is healthy again
|
|
678
687
|
```
|
|
679
688
|
|
|
689
|
+
## Doom Loop Detection
|
|
690
|
+
|
|
691
|
+
Doom loop detection catches the subtler failure mode where a robot repeats the same tool call pattern indefinitely — not hitting `max_tool_rounds`, but cycling through the same sequence over and over. Set `doom_loop_threshold:` to enable it:
|
|
692
|
+
|
|
693
|
+
```ruby
|
|
694
|
+
robot = RobotLab.build(
|
|
695
|
+
name: "runner",
|
|
696
|
+
system_prompt: "Execute steps.",
|
|
697
|
+
local_tools: [StepTool],
|
|
698
|
+
doom_loop_threshold: 3 # alert after 3 identical consecutive or cyclic sequences
|
|
699
|
+
)
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
When a doom loop is detected, a warning is embedded directly into the tool result, prompting the LLM to try a different approach. Detection covers both consecutive repetition (`A,A,A`) and cyclic patterns (`A,B,C,A,B,C`). Via `RunConfig`:
|
|
703
|
+
|
|
704
|
+
```ruby
|
|
705
|
+
config = RobotLab::RunConfig.new(doom_loop_threshold: 3)
|
|
706
|
+
robot = RobotLab.build(name: "runner", system_prompt: "...", config: config)
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## Automatic Context Compaction
|
|
710
|
+
|
|
711
|
+
`auto_compact` triggers context window compression automatically before each `run()`, preventing context overflow without manual intervention.
|
|
712
|
+
|
|
713
|
+
```ruby
|
|
714
|
+
# Built-in trigger: compact when estimated token usage exceeds 80% of context window
|
|
715
|
+
robot = RobotLab.build(
|
|
716
|
+
name: "analyst",
|
|
717
|
+
system_prompt: "You are a research analyst.",
|
|
718
|
+
auto_compact: :context_window
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
# Tune the threshold (here: compact at 70%)
|
|
722
|
+
robot = RobotLab.build(
|
|
723
|
+
name: "analyst",
|
|
724
|
+
system_prompt: "You are a research analyst.",
|
|
725
|
+
auto_compact: :context_window,
|
|
726
|
+
compact_threshold: 0.70
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
# Application-owned compaction: full control over when and how
|
|
730
|
+
robot = RobotLab.build(
|
|
731
|
+
name: "analyst",
|
|
732
|
+
system_prompt: "You are a research analyst.",
|
|
733
|
+
auto_compact: ->(r) { r.compress_history(recent_turns: 5) if r.chat.messages.size > 40 }
|
|
734
|
+
)
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
| Value | Behaviour |
|
|
738
|
+
|-------|-----------|
|
|
739
|
+
| `nil` / `:none` | No automatic compaction (default) |
|
|
740
|
+
| `:context_window` | Compact when estimated token usage exceeds `compact_threshold` fraction of model's context window |
|
|
741
|
+
| `Proc` | Called with the robot before each `run()`; application decides when and how to compact |
|
|
742
|
+
|
|
743
|
+
`compact_threshold` defaults to `0.80` (80%). Requires the `classifier` gem when using the built-in `:context_window` strategy. Via `RunConfig`:
|
|
744
|
+
|
|
745
|
+
```ruby
|
|
746
|
+
config = RobotLab::RunConfig.new(auto_compact: :context_window, compact_threshold: 0.75)
|
|
747
|
+
robot = RobotLab.build(name: "analyst", system_prompt: "...", config: config)
|
|
748
|
+
```
|
|
749
|
+
|
|
680
750
|
## Learning Accumulation
|
|
681
751
|
|
|
682
752
|
`robot.learn(text)` records a cross-run observation. On each subsequent `run()`, active learnings are automatically prepended to the user message as a `LEARNINGS FROM PREVIOUS RUNS:` block so the LLM can incorporate prior context without needing a persistent chat:
|
|
@@ -693,6 +763,17 @@ reviewer.learn("This codebase prefers map/collect over manual array accumulation
|
|
|
693
763
|
reviewer.run("Review snippet B") # learning is injected automatically
|
|
694
764
|
```
|
|
695
765
|
|
|
766
|
+
Pass `learn: true` in the constructor to enable automatic end-of-session learning promotion via the `robot_lab-durable` gem:
|
|
767
|
+
|
|
768
|
+
```ruby
|
|
769
|
+
reviewer = RobotLab.build(
|
|
770
|
+
name: "reviewer",
|
|
771
|
+
system_prompt: "You are a Ruby code reviewer.",
|
|
772
|
+
learn: true,
|
|
773
|
+
learn_domain: "ruby_review"
|
|
774
|
+
)
|
|
775
|
+
```
|
|
776
|
+
|
|
696
777
|
Learnings deduplicate bidirectionally: if a broader learning is added that contains an existing narrower one, the narrower one is dropped. Learnings are persisted to the robot's `Memory` and survive a robot rebuild when the same `Memory` object is reused.
|
|
697
778
|
|
|
698
779
|
```ruby
|
|
@@ -796,101 +877,77 @@ future.robot_name # => "analyst"
|
|
|
796
877
|
future.delegated_by # => "manager"
|
|
797
878
|
```
|
|
798
879
|
|
|
799
|
-
##
|
|
800
|
-
|
|
801
|
-
RobotLab supports true CPU parallelism via Ruby Ractors — isolated execution contexts that bypass the GVL. Two modes are available:
|
|
880
|
+
## Hook System
|
|
802
881
|
|
|
803
|
-
|
|
882
|
+
RobotLab's hook system lets you intercept any point in a robot's execution pipeline without modifying framework code. Hooks use handler classes — subclasses of `RobotLab::Hook` that define lifecycle callbacks as class methods.
|
|
804
883
|
|
|
805
884
|
```ruby
|
|
806
|
-
class
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
param :path, type: :string, desc: "Path to audio file"
|
|
885
|
+
class TimerHook < RobotLab::Hook
|
|
886
|
+
# namespace is auto-derived: TimerHook → :timer_hook
|
|
887
|
+
# override with: self.namespace = :timer
|
|
810
888
|
|
|
811
|
-
def
|
|
812
|
-
|
|
889
|
+
def self.before_run(ctx)
|
|
890
|
+
ctx.local.start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
813
891
|
end
|
|
814
|
-
end
|
|
815
|
-
```
|
|
816
892
|
|
|
817
|
-
|
|
893
|
+
def self.after_run(ctx)
|
|
894
|
+
elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - ctx.local.start) * 1000).round(1)
|
|
895
|
+
puts "[timer] #{ctx.robot.name} #{elapsed}ms"
|
|
896
|
+
end
|
|
818
897
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
task :sentiment, sentiment_robot, depends_on: [:fetch]
|
|
823
|
-
task :entities, entity_robot, depends_on: [:fetch] # runs in parallel with sentiment
|
|
824
|
-
task :summarize, summary_robot, depends_on: [:sentiment, :entities]
|
|
898
|
+
def self.on_error(ctx)
|
|
899
|
+
puts "[timer] #{ctx.robot.name} failed: #{ctx.error.message}"
|
|
900
|
+
end
|
|
825
901
|
end
|
|
826
|
-
|
|
827
|
-
results = network.run(message: "Analyze customer feedback")
|
|
828
|
-
# => { "fetch" => "...", "sentiment" => "positive", "entities" => "...", "summarize" => "..." }
|
|
829
902
|
```
|
|
830
903
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
## Rails Integration
|
|
904
|
+
Register at the global, network, or robot level — or for a single call:
|
|
834
905
|
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
|
|
906
|
+
```ruby
|
|
907
|
+
RobotLab.on(TimerHook) # every robot in this process
|
|
908
|
+
network.on(TimerHook) # robots inside this network only
|
|
909
|
+
robot.on(TimerHook) # this robot only
|
|
910
|
+
robot.run("msg", hooks: TimerHook) # this call only
|
|
911
|
+
robot.run("msg", hooks: [TimerHook, OtherHook])
|
|
838
912
|
```
|
|
839
913
|
|
|
840
|
-
|
|
841
|
-
- `config/initializers/robot_lab.rb` - Configuration
|
|
842
|
-
- `app/robots/` - Directory for your robots
|
|
843
|
-
- Database tables for conversation history
|
|
844
|
-
|
|
845
|
-
### Background Jobs
|
|
846
|
-
|
|
847
|
-
RobotLab ships with `RobotLab::Job`, an `ActiveJob::Base` subclass that handles the full robot-run lifecycle: robot class resolution, Turbo Stream wiring, thread-record persistence, and completion/error broadcasting.
|
|
848
|
-
|
|
849
|
-
**Generic job** (robot class supplied at enqueue time):
|
|
850
|
-
|
|
851
|
-
```bash
|
|
852
|
-
rails generate robot_lab:install # creates app/jobs/robot_run_job.rb
|
|
853
|
-
```
|
|
914
|
+
Pass `context:` to set default `DotState` values for the handler's namespace before each callback fires:
|
|
854
915
|
|
|
855
916
|
```ruby
|
|
856
|
-
|
|
857
|
-
class RobotRunJob < RobotLab::Job
|
|
858
|
-
queue_as :default
|
|
859
|
-
end
|
|
860
|
-
|
|
861
|
-
# Enqueue from a controller:
|
|
862
|
-
RobotRunJob.perform_later(
|
|
863
|
-
robot_class: "SupportRobot",
|
|
864
|
-
message: params[:message],
|
|
865
|
-
thread_id: session_id
|
|
866
|
-
)
|
|
917
|
+
RobotLab.on(TimerHook, context: { threshold_ms: 500 })
|
|
867
918
|
```
|
|
868
919
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
```bash
|
|
872
|
-
rails generate robot_lab:job Support # binds to SupportRobot, queue: default
|
|
873
|
-
rails generate robot_lab:job Support --queue ai # custom queue
|
|
874
|
-
```
|
|
920
|
+
Around hooks receive a block and must call it — and return its value — so the actual work executes:
|
|
875
921
|
|
|
876
922
|
```ruby
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
queue_as :default
|
|
880
|
-
robot_class SupportRobot
|
|
881
|
-
end
|
|
923
|
+
class PerfHook < RobotLab::Hook
|
|
924
|
+
self.namespace = :perf
|
|
882
925
|
|
|
883
|
-
|
|
884
|
-
|
|
926
|
+
def self.around_run(ctx, &block)
|
|
927
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
928
|
+
result = block.call # executes the run
|
|
929
|
+
ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(1)
|
|
930
|
+
$stderr.puts "[perf] #{ctx.robot.name} #{ms}ms"
|
|
931
|
+
result # must return the result
|
|
932
|
+
end
|
|
933
|
+
end
|
|
885
934
|
```
|
|
886
935
|
|
|
887
|
-
|
|
936
|
+
Because handler classes are Ruby constants (not Procs), all hook registrations are natively Ractor-serializable.
|
|
937
|
+
|
|
938
|
+
See the [full Hook System guide](docs/guides/hooks.md) for all hook families, context objects, and extension patterns.
|
|
939
|
+
|
|
940
|
+
## Extension Gems
|
|
888
941
|
|
|
889
|
-
|
|
890
|
-
- Broadcasts a **completion** event to `"robot_lab_thread_#{thread_id}"` when the run finishes
|
|
891
|
-
- Broadcasts an **error** event (HTML-escaped) if the job raises
|
|
942
|
+
RobotLab's optional capabilities are packaged as separate gems:
|
|
892
943
|
|
|
893
|
-
|
|
944
|
+
| Gem | Description |
|
|
945
|
+
|-----|-------------|
|
|
946
|
+
| [robot_lab-ractor](https://github.com/MadBomber/robot_lab-ractor) | CPU parallelism via Ruby Ractors — `ractor_safe` tools and DAG-scheduled parallel networks |
|
|
947
|
+
| [robot_lab-rails](https://github.com/MadBomber/robot_lab-rails) | Rails Engine, generators, `RobotLab::Job` ActiveJob base with Turbo Stream broadcasting |
|
|
948
|
+
| [robot_lab-durable](https://github.com/MadBomber/robot_lab-durable) | Cross-session knowledge persistence via YAML-backed durable store |
|
|
949
|
+
| [robot_lab-document_store](https://github.com/MadBomber/robot_lab-document_store) | In-memory vector store with fastembed embeddings for semantic search / RAG |
|
|
950
|
+
| [robot_lab-acp](https://github.com/MadBomber/robot_lab-acp) | Expose robots and networks as ACP (Agent Communication Protocol) HTTP+SSE services |
|
|
894
951
|
|
|
895
952
|
## Documentation
|
|
896
953
|
|