robot_lab 0.2.1 → 0.2.7
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/.envrc +1 -0
- data/.loki +60 -0
- data/.quality/reek_baseline.txt +43 -0
- data/.rubocop.yml +5 -167
- data/CHANGELOG.md +54 -0
- data/README.md +185 -64
- data/Rakefile +28 -12
- data/docs/api/core/index.md +41 -14
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +285 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +636 -60
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +152 -14
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +83 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +118 -30
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +153 -38
- data/docs/architecture/robot-execution.md +173 -91
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +299 -93
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +296 -50
- data/docs/guides/creating-networks.md +220 -31
- data/docs/guides/hooks.md +1084 -0
- data/docs/guides/index.md +5 -0
- data/docs/guides/knowledge.md +37 -6
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +166 -51
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +300 -11
- data/docs/index.md +37 -5
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +23 -9
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +266 -0
- data/examples/README.md +203 -42
- data/examples/common.rb +79 -7
- data/examples/xyzzy.rb +97 -0
- data/lib/robot_lab/agent_skill.rb +5 -4
- data/lib/robot_lab/budget/ledger.rb +98 -0
- data/lib/robot_lab/capabilities.rb +84 -0
- data/lib/robot_lab/config/defaults.yml +10 -0
- data/lib/robot_lab/config.rb +14 -9
- data/lib/robot_lab/error.rb +34 -4
- data/lib/robot_lab/errors.rb +45 -0
- 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/connection_poller.rb +2 -2
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/narrator.rb +87 -0
- data/lib/robot_lab/network.rb +77 -20
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/budget.rb +89 -0
- data/lib/robot_lab/robot/bus_messaging.rb +90 -18
- data/lib/robot_lab/robot/hooking.rb +56 -0
- data/lib/robot_lab/robot/mcp_management.rb +8 -2
- data/lib/robot_lab/robot/template_rendering.rb +18 -5
- data/lib/robot_lab/robot.rb +233 -89
- data/lib/robot_lab/run_config.rb +24 -5
- 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 +60 -2
- data/lib/robot_lab/task.rb +26 -20
- data/lib/robot_lab/tool.rb +52 -11
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +70 -15
- data/mkdocs.yml +7 -1
- metadata +41 -99
- data/examples/temp.md +0 -51
- data/site/404.html +0 -2300
- data/site/api/core/index.html +0 -2706
- data/site/api/core/memory/index.html +0 -3793
- data/site/api/core/network/index.html +0 -3500
- data/site/api/core/robot/index.html +0 -4566
- data/site/api/core/state/index.html +0 -3390
- data/site/api/core/tool/index.html +0 -3843
- data/site/api/index.html +0 -2635
- data/site/api/mcp/client/index.html +0 -3435
- data/site/api/mcp/index.html +0 -2783
- data/site/api/mcp/server/index.html +0 -3252
- data/site/api/mcp/transports/index.html +0 -3352
- data/site/api/messages/index.html +0 -2641
- data/site/api/messages/text-message/index.html +0 -3087
- data/site/api/messages/tool-call-message/index.html +0 -3159
- data/site/api/messages/tool-result-message/index.html +0 -3252
- data/site/api/messages/user-message/index.html +0 -3212
- data/site/api/streaming/context/index.html +0 -3282
- data/site/api/streaming/events/index.html +0 -3347
- data/site/api/streaming/index.html +0 -2738
- data/site/architecture/core-concepts/index.html +0 -3757
- data/site/architecture/index.html +0 -2797
- data/site/architecture/message-flow/index.html +0 -3238
- data/site/architecture/network-orchestration/index.html +0 -3433
- data/site/architecture/robot-execution/index.html +0 -3140
- data/site/architecture/state-management/index.html +0 -3498
- data/site/assets/css/custom.css +0 -56
- data/site/assets/images/favicon.png +0 -0
- data/site/assets/images/robot_lab.jpg +0 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js +0 -16
- data/site/assets/javascripts/bundle.79ae519e.min.js.map +0 -7
- data/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
- data/site/assets/javascripts/lunr/tinyseg.js +0 -206
- data/site/assets/javascripts/lunr/wordcut.js +0 -6708
- data/site/assets/javascripts/workers/search.2c215733.min.js +0 -42
- data/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -7
- data/site/assets/stylesheets/main.484c7ddc.min.css +0 -1
- data/site/assets/stylesheets/main.484c7ddc.min.css.map +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
- data/site/concepts/index.html +0 -3455
- data/site/examples/basic-chat/index.html +0 -2880
- data/site/examples/index.html +0 -2907
- data/site/examples/mcp-server/index.html +0 -3018
- data/site/examples/multi-robot-network/index.html +0 -3131
- data/site/examples/rails-application/index.html +0 -3329
- data/site/examples/tool-usage/index.html +0 -3085
- data/site/getting-started/configuration/index.html +0 -3745
- data/site/getting-started/index.html +0 -2572
- data/site/getting-started/installation/index.html +0 -2981
- data/site/getting-started/quick-start/index.html +0 -2942
- data/site/guides/building-robots/index.html +0 -4290
- data/site/guides/creating-networks/index.html +0 -3858
- data/site/guides/index.html +0 -2586
- data/site/guides/mcp-integration/index.html +0 -3581
- data/site/guides/memory/index.html +0 -3586
- data/site/guides/rails-integration/index.html +0 -4019
- data/site/guides/streaming/index.html +0 -3157
- data/site/guides/using-tools/index.html +0 -3802
- data/site/index.html +0 -2671
- data/site/search/search_index.json +0 -1
- data/site/sitemap.xml +0 -183
- data/site/sitemap.xml.gz +0 -0
- data/site/tags.json +0 -1
data/docs/api/hooks.md
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# Hooks API
|
|
2
|
+
|
|
3
|
+
Class-level reference for the hook system. For the conceptual walkthrough —
|
|
4
|
+
families, firing order, registration levels, and worked examples — see the
|
|
5
|
+
[Hook System guide](../guides/hooks.md). This page documents the public methods
|
|
6
|
+
of the classes that implement it.
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
classDiagram
|
|
10
|
+
class Hook {
|
|
11
|
+
<<abstract>>
|
|
12
|
+
+namespace() Symbol
|
|
13
|
+
+call(hook_name, ctx, block) Object
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class HookRegistry {
|
|
17
|
+
+on(handler_class, context) Registration
|
|
18
|
+
+registrations_for(hook_name) Array
|
|
19
|
+
+registrations() Array
|
|
20
|
+
+clear() Array
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class Registration {
|
|
24
|
+
<<Data>>
|
|
25
|
+
+handler_class Class
|
|
26
|
+
+context Hash
|
|
27
|
+
+namespace() Symbol
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class Hooks {
|
|
31
|
+
<<module>>
|
|
32
|
+
+run(family, ctx, registries, per_run_hooks) Object
|
|
33
|
+
+call(hook_name, ctx, registries, per_run_hooks) void
|
|
34
|
+
+around(hook_name, ctx, registries, per_run_hooks) Object
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class HookContext {
|
|
38
|
+
+event Symbol
|
|
39
|
+
+metadata ExtensionState
|
|
40
|
+
+ext(name) DotState
|
|
41
|
+
+local() DotState
|
|
42
|
+
+with_namespace(ns) Object
|
|
43
|
+
+to_h() Hash
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
HookContext <|-- RunHookContext
|
|
47
|
+
RunHookContext <|-- LlmGenerationHookContext
|
|
48
|
+
HookContext <|-- ToolCallHookContext
|
|
49
|
+
HookContext <|-- NetworkRunHookContext
|
|
50
|
+
HookContext <|-- TaskHookContext
|
|
51
|
+
HookContext <|-- LearnHookContext
|
|
52
|
+
HookContext <|-- CompactionHookContext
|
|
53
|
+
|
|
54
|
+
HookRegistry --> Registration : builds
|
|
55
|
+
Hooks --> HookRegistry : reads
|
|
56
|
+
Hooks --> HookContext : namespaces
|
|
57
|
+
Hook <|-- Narrator
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## RobotLab::Hook
|
|
63
|
+
|
|
64
|
+
Base class for hook handlers. Subclasses implement hook methods as **class**
|
|
65
|
+
methods; any method a subclass does not define is silently skipped when that hook
|
|
66
|
+
fires.
|
|
67
|
+
|
|
68
|
+
### namespace / namespace=
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class TimerHook < RobotLab::Hook; end
|
|
72
|
+
TimerHook.namespace # => :timer_hook
|
|
73
|
+
|
|
74
|
+
class Tracer < RobotLab::Hook
|
|
75
|
+
self.namespace = :trace
|
|
76
|
+
end
|
|
77
|
+
Tracer.namespace # => :trace
|
|
78
|
+
|
|
79
|
+
RobotLab::Hook.namespace # => nil (the base class has none)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The namespace isolates a handler's `ctx.local` scratchpad from every other
|
|
83
|
+
handler. It defaults to the snake_case form of the **last** segment of the class
|
|
84
|
+
name, so `MyExt::Tracer` becomes `:tracer`. `HookRegistry#on` raises
|
|
85
|
+
`ArgumentError` when `namespace` is `nil`, which is why the base `Hook` class
|
|
86
|
+
itself can never be registered.
|
|
87
|
+
|
|
88
|
+
`inherited` resets `@namespace` to `nil` on each subclass, so a namespace set on
|
|
89
|
+
a parent handler is **not** inherited by its subclasses — each re-derives its own.
|
|
90
|
+
|
|
91
|
+
### call
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
HandlerClass.call(hook_name, context) # point / before / after hook
|
|
95
|
+
HandlerClass.call(hook_name, context) { ... } # around hook
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Dispatch one hook to this handler. This is the method `Hooks.call_registration`
|
|
99
|
+
invokes; you rarely call it directly.
|
|
100
|
+
|
|
101
|
+
| Case | Behavior |
|
|
102
|
+
|------|----------|
|
|
103
|
+
| Handler defines `hook_name` | Called with `context` (and the block, for around hooks) |
|
|
104
|
+
| Handler does not define it, block given | The block is called directly — **around passthrough**, so the chain never breaks |
|
|
105
|
+
| Handler does not define it, no block | Silent no-op |
|
|
106
|
+
|
|
107
|
+
The check is `singleton_class.public_method_defined?(hook_name)`, so a hook
|
|
108
|
+
defined as a *private* class method never fires.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## RobotLab::HookRegistry
|
|
113
|
+
|
|
114
|
+
The store behind `RobotLab.hooks`, `network.hooks`, and `robot.hooks`. Each of
|
|
115
|
+
those three is a separate instance; a run consults all three.
|
|
116
|
+
|
|
117
|
+
### on
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
registration = registry.on(HandlerClass, context: nil)
|
|
121
|
+
# => RobotLab::HookRegistry::Registration
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Register a handler class.
|
|
125
|
+
|
|
126
|
+
| Name | Type | Default | Description |
|
|
127
|
+
|------|------|---------|-------------|
|
|
128
|
+
| `handler_class` | `Class` | **required** | Must be a subclass of `RobotLab::Hook` |
|
|
129
|
+
| `context` | `Hash`, `nil` | `nil` | Defaults merged into the handler's `ctx.local` before each callback (via `DotState#merge_defaults`, so an already-set key is not overwritten) |
|
|
130
|
+
|
|
131
|
+
**Raises `ArgumentError`** when `handler_class` is not a `RobotLab::Hook`
|
|
132
|
+
subclass, or when its `namespace` is `nil`.
|
|
133
|
+
|
|
134
|
+
`RobotLab.on`, `network.on`, and `robot.on` are thin delegators to this method.
|
|
135
|
+
|
|
136
|
+
### registrations_for
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
registry.registrations_for(:before_run) # => Array<Registration>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Only the registrations whose handler class actually implements `hook_name`. This
|
|
143
|
+
is the filter that makes an unimplemented hook a no-op rather than an error, and
|
|
144
|
+
it is what `Hooks.registrations` calls on each registry.
|
|
145
|
+
|
|
146
|
+
### registrations
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
registry.registrations # => Array<Registration>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Every registration, as a defensive copy (`@registrations.dup`) — mutating the
|
|
153
|
+
returned array does not affect the registry.
|
|
154
|
+
|
|
155
|
+
Used by `Network#ractor_hook_classes_for` to collect the handler classes that
|
|
156
|
+
must cross a Ractor boundary under `parallel_mode: :ractor`.
|
|
157
|
+
|
|
158
|
+
### clear
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
registry.clear # => the emptied internal array
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Remove every registration **in place**. Note the difference from
|
|
165
|
+
`RobotLab.clear_hooks!`, which replaces the global registry object entirely
|
|
166
|
+
rather than emptying it.
|
|
167
|
+
|
|
168
|
+
### HookRegistry::Registration
|
|
169
|
+
|
|
170
|
+
A `Data` value object pairing a handler with its optional context.
|
|
171
|
+
|
|
172
|
+
| Member / method | Type | Description |
|
|
173
|
+
|-----------------|------|-------------|
|
|
174
|
+
| `handler_class` | `Class` | The `RobotLab::Hook` subclass |
|
|
175
|
+
| `context` | `Hash`, `nil` | Per-registration defaults (defaults to `nil`) |
|
|
176
|
+
| `namespace` | `Symbol` | Delegates to `handler_class.namespace` |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## RobotLab::Hooks
|
|
181
|
+
|
|
182
|
+
The dispatcher (`module_function`, so every method is called as
|
|
183
|
+
`RobotLab::Hooks.foo`). Core calls into it; extension authors normally write
|
|
184
|
+
handler classes instead. It is documented here because a host that drives
|
|
185
|
+
RobotLab objects directly — or an extension that adds its own hook family — needs
|
|
186
|
+
the contract.
|
|
187
|
+
|
|
188
|
+
### run
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
RobotLab::Hooks.run(family, context, registries:, per_run_hooks: nil) { core_work }
|
|
192
|
+
# => the block's return value
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Execute a full hook family around a block. In order: every `before_<family>`,
|
|
196
|
+
then the `around_<family>` chain wrapping the block, then
|
|
197
|
+
[`set_result`](#set_result), then every `after_<family>`.
|
|
198
|
+
|
|
199
|
+
| Name | Type | Description |
|
|
200
|
+
|------|------|-------------|
|
|
201
|
+
| `family` | `Symbol` | `:run`, `:llm_generation`, `:tool_call`, `:network_run`, `:task`, `:compaction`, `:learn` |
|
|
202
|
+
| `context` | `HookContext` | The family's context object |
|
|
203
|
+
| `registries` | `Array<HookRegistry, nil>` | Consulted in order; `nil` entries are compacted away |
|
|
204
|
+
| `per_run_hooks` | `Class`, `Array<Class>`, `nil` | Handler classes active only for this call (`robot.run(..., hooks:)`) |
|
|
205
|
+
|
|
206
|
+
**Error path:** `run` rescues `Exception` (not just `StandardError`, so
|
|
207
|
+
`Timeout::Error` and `SignalException` are covered), assigns it to
|
|
208
|
+
`context.error` when the context has an `error=` writer, fires `on_error`, and
|
|
209
|
+
**re-raises**. Hooks never swallow an exception.
|
|
210
|
+
|
|
211
|
+
### call
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
RobotLab::Hooks.call(hook_name, context, registries:, per_run_hooks: nil)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Fire one non-around hook across all matching registrations. This is how the point
|
|
218
|
+
hooks `:on_compaction` and `:on_learn` are dispatched from inside the core block
|
|
219
|
+
of their family — which is why they do not compose with `around_*` handlers and
|
|
220
|
+
have no chainable result.
|
|
221
|
+
|
|
222
|
+
### around
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
RobotLab::Hooks.around(hook_name, context, registries:, per_run_hooks: nil) { ... }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Build and invoke just the around chain for one hook name, without the
|
|
229
|
+
before/after phases.
|
|
230
|
+
|
|
231
|
+
### registrations
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
RobotLab::Hooks.registrations(hook_name, registries, per_run_hooks)
|
|
235
|
+
# => Array<Registration>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The complete ordered list for one hook name: each registry's
|
|
239
|
+
`registrations_for(hook_name)` concatenated in registry order, followed by the
|
|
240
|
+
per-run entries. This ordering is what produces **global → network → robot →
|
|
241
|
+
per-run** execution.
|
|
242
|
+
|
|
243
|
+
### error_registrations
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
RobotLab::Hooks.error_registrations(family, registries, per_run_hooks)
|
|
247
|
+
# => Array<Registration>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`on_error` registrations, but **only** for the `:run`, `:network_run`, and
|
|
251
|
+
`:task` families — every other family returns `[]`. This is the mechanism behind
|
|
252
|
+
the guide's statement that `:llm_generation`, `:tool_call`, `:compaction`, and
|
|
253
|
+
`:learn` have no `on_error` variant.
|
|
254
|
+
|
|
255
|
+
### call_all
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
RobotLab::Hooks.call_all(registrations, hook_name, context)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Invoke each registration in order for a non-around hook.
|
|
262
|
+
|
|
263
|
+
### call_around
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
RobotLab::Hooks.call_around(registrations, hook_name, context) { core_work }
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Fold the registrations into a nested chain and call it. The list is `reverse`d
|
|
270
|
+
before folding, so the **first** registration ends up outermost — a global
|
|
271
|
+
`around_run` wraps a robot-level one, not the other way round. A handler that
|
|
272
|
+
does not implement the hook passes the block straight through.
|
|
273
|
+
|
|
274
|
+
### call_registration
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
RobotLab::Hooks.call_registration(registration, hook_name, context, &block)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Invoke one registration with its namespace active. It wraps the call in
|
|
281
|
+
`context.with_namespace(registration.namespace)` and, when the registration
|
|
282
|
+
carries a `context:` hash, merges those values into
|
|
283
|
+
`context.ext(namespace)` as defaults first. This is what makes `ctx.local`
|
|
284
|
+
resolve to the *calling handler's* private `DotState`.
|
|
285
|
+
|
|
286
|
+
### per_run_entries
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
RobotLab::Hooks.per_run_entries(hook_name, hooks)
|
|
290
|
+
# => Array<Registration>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Turn `robot.run(msg, hooks: HandlerClass)` or `hooks: [A, B]` into
|
|
294
|
+
`Registration`s, skipping any class that does not implement `hook_name`. Per-run
|
|
295
|
+
registrations carry no `context:`.
|
|
296
|
+
|
|
297
|
+
### set_result
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
RobotLab::Hooks.set_result(context, family, result)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Publish a family's return value onto its context so `after_*` handlers can read
|
|
304
|
+
it. The mapping is fixed:
|
|
305
|
+
|
|
306
|
+
| Family | Assigned to |
|
|
307
|
+
|--------|-------------|
|
|
308
|
+
| `:run` | `context.response` |
|
|
309
|
+
| `:network_run`, `:task` | `context.result` |
|
|
310
|
+
| `:llm_generation` | `context.generation_response` |
|
|
311
|
+
| `:tool_call`, `:compaction`, `:learn` | *(nothing — those contexts carry their result in a family-specific accessor set by the core block)* |
|
|
312
|
+
|
|
313
|
+
Each assignment is guarded by `respond_to?`, so a custom context lacking the
|
|
314
|
+
writer is simply skipped.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## RobotLab::HookContext
|
|
319
|
+
|
|
320
|
+
Base class for every hook context. The family-specific subclasses below add the
|
|
321
|
+
accessors a handler actually reads.
|
|
322
|
+
|
|
323
|
+
| Method | Returns | Description |
|
|
324
|
+
|--------|---------|-------------|
|
|
325
|
+
| `event` | `Symbol` | The family (`:run`, `:tool_call`, …), symbolized at construction |
|
|
326
|
+
| `metadata` | `ExtensionState` | The per-run namespace store; defaults to a fresh `ExtensionState` |
|
|
327
|
+
| `ext(name)` | `DotState` | Another handler's namespaced state — `ctx.ext(:timer).start_time` |
|
|
328
|
+
| `local` | `DotState` | **This** handler's state. Raises `ArgumentError: No hook namespace active` outside a `with_namespace` block |
|
|
329
|
+
| `with_namespace(ns) { \|ctx\| }` | block's value | Sets the active namespace for the duration of the block and restores the previous one in an `ensure`. Called by `Hooks.call_registration` |
|
|
330
|
+
| `to_h` | `Hash` | Snapshot of every public reader (excluding `to_h` and `local`) plus `metadata: metadata.to_h` |
|
|
331
|
+
|
|
332
|
+
!!! warning "`ctx.local` lives for exactly one run"
|
|
333
|
+
`metadata` is a fresh `ExtensionState` per `HookContext`, and a new context is
|
|
334
|
+
built per run. Use a class-level accessor on the handler for cross-run state.
|
|
335
|
+
|
|
336
|
+
### Context subclasses
|
|
337
|
+
|
|
338
|
+
Readers are read-only unless marked **rw**.
|
|
339
|
+
|
|
340
|
+
#### RunHookContext (`event: :run`)
|
|
341
|
+
|
|
342
|
+
| Accessor | | Description |
|
|
343
|
+
|----------|--|-------------|
|
|
344
|
+
| `robot` | | The robot being run |
|
|
345
|
+
| `network` | | Owning `Network`, or `nil` when standalone |
|
|
346
|
+
| `task` | | The `Task` wrapper, when run from a pipeline |
|
|
347
|
+
| `memory` | | The resolved run memory |
|
|
348
|
+
| `config` | | The robot's effective `RunConfig` |
|
|
349
|
+
| `request` | rw | The user message. **Writable** — assign in `before_run` to rewrite the prompt |
|
|
350
|
+
| `response` | rw | The `RobotResult`; set by `Hooks.set_result` before `after_run` |
|
|
351
|
+
| `error` | rw | Set by `Hooks.run` before `on_error` |
|
|
352
|
+
|
|
353
|
+
#### LlmGenerationHookContext (`event: :run`) — subclass of `RunHookContext`
|
|
354
|
+
|
|
355
|
+
Adds `iteration` (Integer, defaults to `0`) and `generation_response` (**rw**,
|
|
356
|
+
set by `set_result`). Note it inherits `event: :run` from its parent; the family
|
|
357
|
+
name comes from the hook method names, not from `event`.
|
|
358
|
+
|
|
359
|
+
#### ToolCallHookContext (`event: :tool_call`)
|
|
360
|
+
|
|
361
|
+
| Accessor | | Description |
|
|
362
|
+
|----------|--|-------------|
|
|
363
|
+
| `tool` | | The tool instance |
|
|
364
|
+
| `tool_name` | | `tool.name`, falling back to `tool.class.name` |
|
|
365
|
+
| `tool_args` | | Arguments the LLM supplied |
|
|
366
|
+
| `robot` | | Owning robot, or `nil` |
|
|
367
|
+
| `tool_result` | rw | The result. Assign it in `around_tool_call` **without** calling the block to block a tool |
|
|
368
|
+
| `tool_error` | rw | Set by `Tool#call` when `execute` raised |
|
|
369
|
+
|
|
370
|
+
#### NetworkRunHookContext (`event: :network_run`)
|
|
371
|
+
|
|
372
|
+
`network`, `memory`, `config`; **rw**: `context` (the run params hash), `result`, `error`.
|
|
373
|
+
|
|
374
|
+
#### TaskHookContext (`event: :task`)
|
|
375
|
+
|
|
376
|
+
`network`, `task`, `task_name` (`task.name`), `robot` (falls back to
|
|
377
|
+
`task.robot`), `memory`, `config`; **rw**: `result`, `error`.
|
|
378
|
+
|
|
379
|
+
#### LearnHookContext (`event: :learn`)
|
|
380
|
+
|
|
381
|
+
| Accessor | | Description |
|
|
382
|
+
|----------|--|-------------|
|
|
383
|
+
| `robot` | | The robot learning |
|
|
384
|
+
| `text` | | The stripped learning text |
|
|
385
|
+
| `learnings_before` | | Frozen copy of `robot.learnings` prior to the write |
|
|
386
|
+
| `stored` | rw | Set `true` by core when the learning was actually appended (i.e. not deduplicated away) |
|
|
387
|
+
| `error` | rw | |
|
|
388
|
+
|
|
389
|
+
#### CompactionHookContext (`event: :compaction`)
|
|
390
|
+
|
|
391
|
+
| Accessor | | Description |
|
|
392
|
+
|----------|--|-------------|
|
|
393
|
+
| `robot` | | |
|
|
394
|
+
| `messages_before` | | Frozen copy of `chat.messages` before compaction |
|
|
395
|
+
| `config` | | The effective `RunConfig` |
|
|
396
|
+
| `strategy` | | `:context_window`, or `:custom` when `auto_compact` is a `Proc` |
|
|
397
|
+
| `compacted_messages` | rw | Assign in `on_compaction` to **replace** the core algorithm |
|
|
398
|
+
| `error` | rw | |
|
|
399
|
+
| `handled?` | | `!compacted_messages.nil?` — core skips its own compaction when true |
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## ExtensionState and DotState
|
|
404
|
+
|
|
405
|
+
The two-level store behind `ctx.local` / `ctx.ext`.
|
|
406
|
+
|
|
407
|
+
### ExtensionState
|
|
408
|
+
|
|
409
|
+
| Method | Returns | Description |
|
|
410
|
+
|--------|---------|-------------|
|
|
411
|
+
| `ext(name)` | `DotState` | The `DotState` for a namespace, auto-created on first access |
|
|
412
|
+
| `to_h` | `Hash` | `{ namespace => state_hash }` for every namespace touched |
|
|
413
|
+
|
|
414
|
+
### DotState
|
|
415
|
+
|
|
416
|
+
A schemaless dot-access bag.
|
|
417
|
+
|
|
418
|
+
| Method | Returns | Description |
|
|
419
|
+
|--------|---------|-------------|
|
|
420
|
+
| `method_missing` | value / assigned value | `state.foo` reads, `state.foo = 1` writes. Any key is allowed |
|
|
421
|
+
| `merge_defaults(hash)` | `self` | Writes each pair **only when the key is absent** — this is how a registration's `context:` becomes defaults rather than an override |
|
|
422
|
+
| `to_h` | `Hash` | A `dup` of the backing hash |
|
|
423
|
+
|
|
424
|
+
!!! warning "`DotState#respond_to_missing?` always returns true"
|
|
425
|
+
`state.respond_to?(:anything)` is `true`, and reading a key that was never
|
|
426
|
+
set returns `nil` instead of raising `NoMethodError`. A typo in a `ctx.local`
|
|
427
|
+
reader fails silently.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Module-level hook methods
|
|
432
|
+
|
|
433
|
+
```ruby
|
|
434
|
+
RobotLab.hooks # => the global HookRegistry (memoized)
|
|
435
|
+
RobotLab.on(HandlerClass, context: nil)
|
|
436
|
+
RobotLab.clear_hooks! # => replaces the global registry with a new one
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
`clear_hooks!` swaps in a brand-new `HookRegistry`; any `Registration` object you
|
|
440
|
+
held from before is orphaned. Use it in test teardown to avoid leaking handlers
|
|
441
|
+
between examples.
|
|
442
|
+
|
|
443
|
+
### with_hook_scope / current_hook_scope
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
RobotLab.with_hook_scope(registries, per_run_hooks) { ... }
|
|
447
|
+
RobotLab.current_hook_scope # => { registries: [...], per_run_hooks: ... } or nil
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Publishes the active run's registries and per-run handlers in a
|
|
451
|
+
`Thread.current` slot for the duration of the block, restoring the previous value
|
|
452
|
+
in an `ensure`. `Robot::Hooking#run` opens the scope; `Tool#call` reads it so a
|
|
453
|
+
tool invoked deep inside RubyLLM's tool loop still resolves the same registries
|
|
454
|
+
(including the network's and any `hooks:` passed to that one `run`) instead of
|
|
455
|
+
falling back to `[RobotLab.hooks, robot.hooks]`.
|
|
456
|
+
|
|
457
|
+
!!! note "Thread-local, so it does not cross a thread boundary"
|
|
458
|
+
A tool that executes on another thread — or a `delegate(async: true)` target —
|
|
459
|
+
sees `current_hook_scope == nil` and falls back to the global + robot
|
|
460
|
+
registries. Network-scoped and per-run hooks will not fire for it.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## See Also
|
|
465
|
+
|
|
466
|
+
- [Hook System guide](../guides/hooks.md) — families, ordering, and applied patterns
|
|
467
|
+
- [Observability & Safety](../guides/observability.md) — `Narrator`, budgets, doom-loop detection
|
|
468
|
+
- [Support API](support.md) — `Narrator`, the one `Hook` subclass shipped in core
|
|
469
|
+
- [Robot: on](core/robot.md#on) · [Network: on](core/network.md#on)
|
data/docs/api/index.md
CHANGED
|
@@ -8,10 +8,50 @@ The fundamental building blocks of RobotLab:
|
|
|
8
8
|
|
|
9
9
|
| Class | Description |
|
|
10
10
|
|-------|-------------|
|
|
11
|
-
| [Robot](core/robot.md) | LLM-powered agent with
|
|
12
|
-
| [
|
|
11
|
+
| [Robot](core/robot.md) | LLM-powered agent with templates, tools, memory, and MCP |
|
|
12
|
+
| [RobotResult](core/result.md) | Value object returned by every `robot.run()` |
|
|
13
|
+
| [Network](core/network.md) | Orchestrates multiple robots as a SimpleFlow pipeline |
|
|
13
14
|
| [Memory](core/memory.md) | Reactive key-value store for sharing data |
|
|
15
|
+
| [StateProxy](core/state.md) | Hash/method-access wrapper returned by `memory.data` |
|
|
14
16
|
| [Tool](core/tool.md) | Custom function robots can call |
|
|
17
|
+
| `RunConfig` | Shared LLM / tool / infrastructure configuration — see [Robot: RunConfig](core/robot.md#runconfig) |
|
|
18
|
+
|
|
19
|
+
There is **no** `RobotLab::State` class and no `RobotLab::NetworkRun` class. Runtime
|
|
20
|
+
state lives in `Memory`; `memory.data` returns a `StateProxy`.
|
|
21
|
+
|
|
22
|
+
## Hooks
|
|
23
|
+
|
|
24
|
+
The framework's extension seam — `Hooks.run` brackets every robot run, network
|
|
25
|
+
run, task, LLM generation, tool call, compaction, and `learn` call:
|
|
26
|
+
|
|
27
|
+
| Class | Description |
|
|
28
|
+
|-------|-------------|
|
|
29
|
+
| [Hook](hooks.md#robotlabhook) | Handler base class; subclasses implement hooks as class methods |
|
|
30
|
+
| [HookRegistry](hooks.md#robotlabhookregistry) | The store behind `RobotLab.hooks`, `network.hooks`, `robot.hooks` |
|
|
31
|
+
| [Hooks](hooks.md#robotlabhooks) | The dispatcher |
|
|
32
|
+
| [HookContext](hooks.md#robotlabhookcontext) | Base context, plus one subclass per family |
|
|
33
|
+
|
|
34
|
+
## Skills
|
|
35
|
+
|
|
36
|
+
`SKILL.md` bundles, the scripts they expose as tools, and the sandbox that
|
|
37
|
+
confines them:
|
|
38
|
+
|
|
39
|
+
| Class | Description |
|
|
40
|
+
|-------|-------------|
|
|
41
|
+
| [AgentSkill](skills.md#robotlabagentskill) | One skill bundle: instructions + `scripts/` |
|
|
42
|
+
| [AgentSkillCatalog](skills.md#robotlabagentskillcatalog) | Lazy registry over `~/.prompts/skills/` |
|
|
43
|
+
| [Capabilities](skills.md#robotlabcapabilities) | Declared vs. ceiling filesystem / network / timeout grant |
|
|
44
|
+
| [ScriptTool](skills.md#robotlabscripttool) | Wraps an executable script as a `Tool` |
|
|
45
|
+
| [Sandbox](skills.md#robotlabsandbox) | macOS Seatbelt confinement, or a passthrough |
|
|
46
|
+
|
|
47
|
+
## Support Classes
|
|
48
|
+
|
|
49
|
+
Everything else with a public surface — `Task`, `Runnable`, `ToolConfig`,
|
|
50
|
+
`ToolManifest`, `Budget::Ledger`, `DoomLoopDetector`, `HistoryCompressor`,
|
|
51
|
+
`Convergence`, `TextAnalysis`, `DelegationFuture`, `RobotMessage`, `BusPoller`,
|
|
52
|
+
`Waiter`, `Narrator`, `Config`, `MCP::ServerDiscovery`,
|
|
53
|
+
`MCP::ConnectionPoller`, and `Streaming::SequenceCounter` — is documented on the
|
|
54
|
+
[Support Classes](support.md) page.
|
|
15
55
|
|
|
16
56
|
## Messages
|
|
17
57
|
|
|
@@ -49,13 +89,49 @@ Real-time response streaming:
|
|
|
49
89
|
|
|
50
90
|
```ruby
|
|
51
91
|
# Configuration
|
|
52
|
-
RobotLab.config
|
|
53
|
-
RobotLab.
|
|
92
|
+
RobotLab.config # => Config instance
|
|
93
|
+
RobotLab.configure { |c| ... } # => yields the Config for mutation
|
|
94
|
+
RobotLab.reload_config! # => reload from all sources
|
|
54
95
|
|
|
55
96
|
# Building
|
|
56
|
-
RobotLab.build(name
|
|
57
|
-
|
|
58
|
-
RobotLab.
|
|
97
|
+
RobotLab.build(name: "robot", template: nil, system_prompt: nil, context: {},
|
|
98
|
+
enable_cache: true, bus: nil, skills: nil, config: nil, **options)
|
|
99
|
+
RobotLab.create_network(name:, concurrency: :auto, config: nil) { ... }
|
|
100
|
+
RobotLab.create_memory(data: {}, enable_cache: true, **options)
|
|
101
|
+
|
|
102
|
+
# Rendering a template to a String (not a robot) -- see Building Robots guide
|
|
103
|
+
RobotLab.render_template(name, **context) # => String
|
|
104
|
+
|
|
105
|
+
# Hooks -- see the Hooks API page
|
|
106
|
+
RobotLab.hooks # => HookRegistry
|
|
107
|
+
RobotLab.on(HandlerClass, context: nil)
|
|
108
|
+
RobotLab.clear_hooks! # => replaces the global registry with a fresh one
|
|
109
|
+
|
|
110
|
+
# Hook scope (internal plumbing; read by Tool#call)
|
|
111
|
+
RobotLab.with_hook_scope(registries, per_run_hooks) { ... }
|
|
112
|
+
RobotLab.current_hook_scope # => { registries:, per_run_hooks: } or nil
|
|
113
|
+
|
|
114
|
+
# Extensions
|
|
115
|
+
RobotLab.register_extension(name, mod)
|
|
116
|
+
RobotLab.extension_loaded?(:ractor) # => Boolean
|
|
117
|
+
RobotLab.extension(:ractor)
|
|
118
|
+
RobotLab.loaded_extensions # => Array<Symbol>
|
|
59
119
|
```
|
|
60
120
|
|
|
121
|
+
`with_hook_scope` publishes the active run's registries in a `Thread.current`
|
|
122
|
+
slot so a tool executing deep inside RubyLLM's tool loop resolves the same
|
|
123
|
+
registries — including the network's and any per-run `hooks:` — instead of
|
|
124
|
+
falling back to `[RobotLab.hooks, robot.hooks]`. It is thread-local, so it does
|
|
125
|
+
not reach a tool that runs on another thread. See
|
|
126
|
+
[Hooks: with_hook_scope](hooks.md#with_hook_scope-current_hook_scope).
|
|
127
|
+
|
|
128
|
+
!!! warning "Tools and MCP are opt-in per call"
|
|
129
|
+
`Robot#run` defaults to `tools: :none, mcp: :none`. A bare `robot.run("...")`
|
|
130
|
+
sends the LLM **zero** tools and connects **no** MCP servers, even when
|
|
131
|
+
`local_tools:`/`mcp:` were supplied at build time. Pass `tools: :inherit`
|
|
132
|
+
(and `mcp: :inherit`) at *run* time to use what is attached. For a standalone
|
|
133
|
+
robot, do not pass `tools: :inherit` at *build* time — it resolves to an
|
|
134
|
+
allowlist matching nothing. (Inside a network it is the required opt-in to a
|
|
135
|
+
network `config:` list.) See [Robot: Configuration Hierarchy](core/robot.md#configuration-hierarchy).
|
|
136
|
+
|
|
61
137
|
See individual class documentation for detailed method references.
|