robot_lab 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
data/docs/api/skills.md
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
# Skills API
|
|
2
|
-
|
|
3
|
-
Class-level reference for the AgentSkills subsystem: skill bundles, the scripts
|
|
4
|
-
they expose as tools, and the capabilities those scripts declare. Core itself
|
|
5
|
-
has **no sandboxing and no execution limitations** — every script runs
|
|
6
|
-
unconfined until the optional
|
|
7
|
-
[`robot_lab-sandbox`](https://github.com/MadBomber/robot_lab-sandbox) gem is
|
|
8
|
-
required, at which point it installs a confinement strategy (see
|
|
9
|
-
[ScriptTool.executor](#scripttoolexecutor) below). For the how-to, see
|
|
10
|
-
[Using Tools: Skill Scripts and Sandboxing](../guides/using-tools.md#skill-scripts-and-sandboxing)
|
|
11
|
-
and [Building Robots: Composable Skills](../guides/building-robots.md#composable-skills).
|
|
12
|
-
|
|
13
|
-
!!! note "Two different things are called 'skills'"
|
|
14
|
-
**Template skills** (`RobotLab.build(skills: [:clarifier])`) are ordinary
|
|
15
|
-
prompt templates whose bodies are prepended to a robot's system prompt — see
|
|
16
|
-
[Robot: Skills](core/robot.md#skills). **AgentSkills** (this page) are
|
|
17
|
-
`SKILL.md` *bundles* on disk, discovered from `~/.prompts/skills/`, matched
|
|
18
|
-
to a message by embedding similarity at run time, and capable of contributing
|
|
19
|
-
executable tools. They share the word but not the mechanism.
|
|
20
|
-
|
|
21
|
-
```mermaid
|
|
22
|
-
graph LR
|
|
23
|
-
subgraph "Discovery"
|
|
24
|
-
C[AgentSkillCatalog<br/>~/.prompts/skills/]
|
|
25
|
-
S[AgentSkill<br/>SKILL.md]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
subgraph "Capability grant"
|
|
29
|
-
D[Capabilities<br/>declared in front matter]
|
|
30
|
-
CE[Capabilities.ceiling<br/>from config.sandbox]
|
|
31
|
-
G[effective grant<br/>declared ∩ ceiling]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
subgraph "Execution (core)"
|
|
35
|
-
T[ScriptTool.from_path<br/>-> RobotLab::Tool]
|
|
36
|
-
EX{ScriptTool.executor<br/>set?}
|
|
37
|
-
UN[Open3.capture2e<br/>unconfined, no timeout]
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
subgraph "robot_lab-sandbox (optional gem)"
|
|
41
|
-
SB{Sandbox.enabled?}
|
|
42
|
-
SE[Sandbox::Seatbelt<br/>macOS]
|
|
43
|
-
NU[Sandbox::Null<br/>passthrough]
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
R[Robot<br/>AgentSkillMatching] --> C
|
|
47
|
-
C --> S
|
|
48
|
-
S --> D
|
|
49
|
-
S --> T
|
|
50
|
-
D --> G
|
|
51
|
-
CE --> G
|
|
52
|
-
T --> EX
|
|
53
|
-
EX -- "nil (default)" --> UN
|
|
54
|
-
EX -- "installed" --> SB
|
|
55
|
-
SB -- "off, or trust: core,<br/>or non-macOS" --> NU
|
|
56
|
-
SB -- "on + macOS" --> SE
|
|
57
|
-
G --> SE
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## RobotLab::AgentSkill
|
|
63
|
-
|
|
64
|
-
Immutable value object for one skill folder: a directory containing a `SKILL.md`
|
|
65
|
-
with `name` and `description` front matter, plus optional `scripts/`,
|
|
66
|
-
`references/`, and `assets/` subdirectories.
|
|
67
|
-
|
|
68
|
-
### Constructor
|
|
69
|
-
|
|
70
|
-
```ruby
|
|
71
|
-
skill = RobotLab::AgentSkill.new("~/.prompts/skills/deploy-checker/SKILL.md")
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
| Name | Type | Description |
|
|
75
|
-
|------|------|-------------|
|
|
76
|
-
| `skill_md_path` | `String`, `Pathname` | Path to the `SKILL.md` file itself, **not** the directory |
|
|
77
|
-
|
|
78
|
-
**Raises `RobotLab::ConfigurationError`** when front matter is missing `name` or
|
|
79
|
-
`description` (or either is blank) — and because a file with no `---` block
|
|
80
|
-
parses to an empty hash, a `SKILL.md` without front matter always raises.
|
|
81
|
-
Malformed YAML raises `Psych::SyntaxError` instead, straight from
|
|
82
|
-
`YAML.safe_load`. [`AgentSkillCatalog`](#robotlabagentskillcatalog) rescues both
|
|
83
|
-
and skips the bundle; construct an `AgentSkill` directly and you get the
|
|
84
|
-
exception.
|
|
85
|
-
|
|
86
|
-
### Attributes
|
|
87
|
-
|
|
88
|
-
| Attribute | Type | Description |
|
|
89
|
-
|-----------|------|-------------|
|
|
90
|
-
| `name` | `String` | Front-matter `name`; also the catalog lookup key (symbolized) |
|
|
91
|
-
| `description` | `String` | Front-matter `description`; the text matched against the user's message |
|
|
92
|
-
| `path` | `Pathname` | The skill **directory** (`dirname` of the `SKILL.md` path) |
|
|
93
|
-
| `capabilities` | `Capabilities` | Built from front matter via `Capabilities.from_front_matter` |
|
|
94
|
-
|
|
95
|
-
### instructions
|
|
96
|
-
|
|
97
|
-
```ruby
|
|
98
|
-
skill.instructions # => String
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
The `SKILL.md` body below the front matter, stripped. This is the text
|
|
102
|
-
`Robot::AgentSkillMatching` prepends to the system prompt when the skill matches.
|
|
103
|
-
Memoized.
|
|
104
|
-
|
|
105
|
-
### scripts
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
skill.scripts # => Array<Pathname>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Every **file** directly inside the skill's `scripts/` directory, sorted. Returns
|
|
112
|
-
`[]` when there is no `scripts/` directory. Not recursive — subdirectories are
|
|
113
|
-
skipped. Memoized.
|
|
114
|
-
|
|
115
|
-
### script_tools
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
skill.script_tools # => Array<RobotLab::Tool>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
One `RobotLab::Tool` per script, built with
|
|
122
|
-
[`ScriptTool.from_path`](#scripttoolfrom_path) and carrying this skill's
|
|
123
|
-
`capabilities` and directory. **Non-executable scripts are skipped** (logged at
|
|
124
|
-
`warn` and filtered out by `filter_map`), so this array can be shorter than
|
|
125
|
-
`scripts`. Memoized.
|
|
126
|
-
|
|
127
|
-
These tools are appended to `robot.local_tools` for the duration of a matched
|
|
128
|
-
`run` and removed again in the `ensure` block — see
|
|
129
|
-
[Robot Execution](../architecture/robot-execution.md#execution-overview).
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## RobotLab::AgentSkillCatalog
|
|
134
|
-
|
|
135
|
-
Lazily-loaded registry of the skill folders under a root directory.
|
|
136
|
-
|
|
137
|
-
### SKILLS_ROOT
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
RobotLab::AgentSkillCatalog::SKILLS_ROOT
|
|
141
|
-
# => #<Pathname:/Users/you/.prompts/skills>
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
`~/.prompts/skills`, expanded at load time. The path the process-level singleton
|
|
145
|
-
scans.
|
|
146
|
-
|
|
147
|
-
### instance / reset!
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
RobotLab::AgentSkillCatalog.instance # => the singleton, scanning SKILLS_ROOT
|
|
151
|
-
RobotLab::AgentSkillCatalog.reset! # => nil; next `instance` builds a fresh one
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
`instance` memoizes. `reset!` drops the memo — it exists so tests can point the
|
|
155
|
-
catalog at a fixture directory by resetting and constructing an instance
|
|
156
|
-
explicitly with a different root.
|
|
157
|
-
|
|
158
|
-
### Constructor
|
|
159
|
-
|
|
160
|
-
```ruby
|
|
161
|
-
catalog = RobotLab::AgentSkillCatalog.new("/path/to/skills")
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
| Name | Type | Default | Description |
|
|
165
|
-
|------|------|---------|-------------|
|
|
166
|
-
| `skills_root` | `String`, `Pathname` | `SKILLS_ROOT` | Directory to scan |
|
|
167
|
-
|
|
168
|
-
Construction does **no** I/O; the scan happens on the first `find`/`all`.
|
|
169
|
-
|
|
170
|
-
### find
|
|
171
|
-
|
|
172
|
-
```ruby
|
|
173
|
-
catalog.find(:deploy_checker) # => AgentSkill or nil
|
|
174
|
-
catalog.find("deploy-checker") # => AgentSkill or nil
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Look up by skill **name** (the front-matter `name`, symbolized) — not by
|
|
178
|
-
directory name, and not by file path. Returns `nil` when not found.
|
|
179
|
-
|
|
180
|
-
### all
|
|
181
|
-
|
|
182
|
-
```ruby
|
|
183
|
-
catalog.all # => Array<AgentSkill>
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Every successfully-loaded skill.
|
|
187
|
-
|
|
188
|
-
!!! note "Loading is lazy, thread-safe, and forgiving"
|
|
189
|
-
The scan runs once, under a `Mutex`, on the first `find` or `all`. A missing
|
|
190
|
-
root directory is not an error — the catalog is simply empty. A directory
|
|
191
|
-
without a `SKILL.md` is skipped silently; a `SKILL.md` that raises
|
|
192
|
-
`ConfigurationError` or `Psych::SyntaxError` is skipped with a `warn`
|
|
193
|
-
(`"AgentSkillCatalog: <message>, skipping <dir>"`). One bad bundle never
|
|
194
|
-
prevents the others from loading, and the scan is never retried.
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## RobotLab::Capabilities
|
|
199
|
-
|
|
200
|
-
What a skill's scripts may read, write, reach, and how long they may run.
|
|
201
|
-
|
|
202
|
-
A skill declares what it **wants** in `SKILL.md` front matter; the global
|
|
203
|
-
`sandbox:` config declares the **ceiling**. The effective grant is the
|
|
204
|
-
[intersection](#intersect) of the two.
|
|
205
|
-
|
|
206
|
-
### Constants
|
|
207
|
-
|
|
208
|
-
| Constant | Value | Description |
|
|
209
|
-
|----------|-------|-------------|
|
|
210
|
-
| `DEFAULT_TIMEOUT` | `60` | Seconds, used when `timeout` is absent or non-positive |
|
|
211
|
-
| `TRUST_LEVELS` | `["core", "external"]` | Any other value falls back to `"external"` |
|
|
212
|
-
|
|
213
|
-
### Constructor
|
|
214
|
-
|
|
215
|
-
```ruby
|
|
216
|
-
RobotLab::Capabilities.new(
|
|
217
|
-
fs_read: [], fs_write: [], network: false,
|
|
218
|
-
timeout: DEFAULT_TIMEOUT, trust: "external"
|
|
219
|
-
)
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
| Name | Type | Default | Coercion |
|
|
223
|
-
|------|------|---------|----------|
|
|
224
|
-
| `fs_read` | `Array<String>` | `[]` | `Array(...)` then `to_s` on each entry |
|
|
225
|
-
| `fs_write` | `Array<String>` | `[]` | Same |
|
|
226
|
-
| `network` | `Boolean` | `false` | Any truthy value becomes `true` |
|
|
227
|
-
| `timeout` | `Integer` | `60` | `to_i`; anything not positive becomes `DEFAULT_TIMEOUT` |
|
|
228
|
-
| `trust` | `String` | `"external"` | Must be in `TRUST_LEVELS`, else `"external"` |
|
|
229
|
-
|
|
230
|
-
Every value is normalized in the constructor, so the readers `fs_read`,
|
|
231
|
-
`fs_write`, `network`, `timeout`, and `trust` always return well-formed values —
|
|
232
|
-
a malformed `SKILL.md` degrades to the safe default rather than raising.
|
|
233
|
-
|
|
234
|
-
### from_front_matter
|
|
235
|
-
|
|
236
|
-
```ruby
|
|
237
|
-
RobotLab::Capabilities.from_front_matter(front_matter_hash) # => Capabilities
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Build from a parsed `SKILL.md` front-matter hash, reading `fs_read`, `fs_write`,
|
|
241
|
-
`network`, `timeout`, and `trust`. A `nil` front matter yields an all-defaults
|
|
242
|
-
instance.
|
|
243
|
-
|
|
244
|
-
### fm_value
|
|
245
|
-
|
|
246
|
-
```ruby
|
|
247
|
-
RobotLab::Capabilities.fm_value(front_matter, :network, false)
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
Look up a front-matter key tolerating **either** string or symbol keys (string
|
|
251
|
-
first, then symbol), returning `default` when both are `nil`. Exposed because
|
|
252
|
-
`from_front_matter` uses it and skill-tooling may need the same leniency.
|
|
253
|
-
|
|
254
|
-
### ceiling
|
|
255
|
-
|
|
256
|
-
```ruby
|
|
257
|
-
RobotLab::Capabilities.ceiling # => from RobotLab.config.sandbox
|
|
258
|
-
RobotLab::Capabilities.ceiling(custom_config)
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
The maximum grant any skill may receive, read from the config's `sandbox:`
|
|
262
|
-
section. When there is no `sandbox` section at all, the ceiling is
|
|
263
|
-
`Capabilities.new(fs_read: ["."])` — read-only access to the working directory,
|
|
264
|
-
no writes, no network.
|
|
265
|
-
|
|
266
|
-
Note the ceiling never carries a `trust` — trust is a property of the skill, not
|
|
267
|
-
of the ceiling, and `intersect` keeps the declared value.
|
|
268
|
-
|
|
269
|
-
### core?
|
|
270
|
-
|
|
271
|
-
```ruby
|
|
272
|
-
capabilities.core? # => trust == "core"
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
A `core` skill is exempt from confinement: when `robot_lab-sandbox` is loaded,
|
|
276
|
-
`Sandbox.for` returns a `Null` strategy for it regardless of platform or
|
|
277
|
-
config. Reserve `trust: core` for bundles you wrote and audited.
|
|
278
|
-
|
|
279
|
-
### intersect
|
|
280
|
-
|
|
281
|
-
```ruby
|
|
282
|
-
grant = declared.intersect(RobotLab::Capabilities.ceiling)
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
The effective grant. Per field:
|
|
286
|
-
|
|
287
|
-
| Field | Rule |
|
|
288
|
-
|-------|------|
|
|
289
|
-
| `fs_read` / `fs_write` | A requested path survives only when it **is** a ceiling root or lives beneath one. Both sides are `File.expand_path`ed before comparison, so `~` and relative paths resolve first |
|
|
290
|
-
| `network` | `declared && ceiling` — both must allow it |
|
|
291
|
-
| `timeout` | The **smaller** of the two |
|
|
292
|
-
| `trust` | The **declared** value, unchanged |
|
|
293
|
-
|
|
294
|
-
Because the check is prefix-based on expanded paths, a ceiling of `["."]` grants
|
|
295
|
-
nothing outside the working directory even if a skill asks for `/etc`.
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## RobotLab::ScriptTool
|
|
300
|
-
|
|
301
|
-
Factory module that turns an executable script into a `RobotLab::Tool`. All
|
|
302
|
-
methods are module functions.
|
|
303
|
-
|
|
304
|
-
### ScriptTool.from_path
|
|
305
|
-
|
|
306
|
-
```ruby
|
|
307
|
-
tool = RobotLab::ScriptTool.from_path(script_path, capabilities: nil, skill_dir: nil)
|
|
308
|
-
# => RobotLab::Tool, or nil
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
| Name | Type | Default | Description |
|
|
312
|
-
|------|------|---------|-------------|
|
|
313
|
-
| `script_path` | `String`, `Pathname` | **required** | The script file |
|
|
314
|
-
| `capabilities` | `Capabilities`, `nil` | `nil` → `Capabilities.new` | The skill's declared capabilities |
|
|
315
|
-
| `skill_dir` | `String`, `nil` | `nil` → the script's own directory | Bundle root; always granted read access under Seatbelt |
|
|
316
|
-
|
|
317
|
-
**Returns `nil`** when the file is not executable, logging
|
|
318
|
-
`"ScriptTool: <basename> is not executable, skipping"` at `warn`. It never raises.
|
|
319
|
-
|
|
320
|
-
The generated tool takes a single optional `args` string parameter, which is
|
|
321
|
-
`Shellwords.split` and appended to `bash <script>`. Its name comes from
|
|
322
|
-
[`derive_name`](#scripttoolderive_name) and its description from
|
|
323
|
-
[`extract_description`](#scripttoolextract_description).
|
|
324
|
-
|
|
325
|
-
### ScriptTool.executor
|
|
326
|
-
|
|
327
|
-
```ruby
|
|
328
|
-
RobotLab::ScriptTool.executor # => #call, or nil (the default)
|
|
329
|
-
RobotLab::ScriptTool.executor = obj # any object responding to
|
|
330
|
-
# call(cmd, capabilities:, skill_dir:)
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
The extension point core exposes for confinement. `nil` by default — core has
|
|
334
|
-
no sandboxing of its own. `robot_lab-sandbox`, when required, sets this to
|
|
335
|
-
`RobotLab::Sandbox::Executor`, which handles `capabilities`/timeout/cleanup
|
|
336
|
-
itself. See that gem's docs for what it does when installed.
|
|
337
|
-
|
|
338
|
-
### ScriptTool.execute
|
|
339
|
-
|
|
340
|
-
```ruby
|
|
341
|
-
RobotLab::ScriptTool.execute(cmd, capabilities:, skill_dir:) # => String
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
Run a command array and return its combined stdout+stderr, or an error string.
|
|
345
|
-
Two paths:
|
|
346
|
-
|
|
347
|
-
- **`executor` is `nil`** (the default) — `Open3.capture2e`, unconfined, **no timeout**.
|
|
348
|
-
- **`executor` is set** — delegates entirely to `executor.call(cmd, capabilities:, skill_dir:)`.
|
|
349
|
-
Core no longer knows or cares what the executor does with `capabilities` or
|
|
350
|
-
how (or whether) it bounds execution time.
|
|
351
|
-
|
|
352
|
-
### ScriptTool.format_result
|
|
353
|
-
|
|
354
|
-
```ruby
|
|
355
|
-
RobotLab::ScriptTool.format_result(output, status) # => String
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
| `status` | Result |
|
|
359
|
-
|----------|--------|
|
|
360
|
-
| `nil` | `"Error (timed out):\n<output>"` |
|
|
361
|
-
| success | `output` verbatim |
|
|
362
|
-
| non-zero exit | `"Error (exit <N>):\n<output>"` |
|
|
363
|
-
|
|
364
|
-
Failures come back as **text for the LLM**, not exceptions — the model sees the
|
|
365
|
-
error and can adapt.
|
|
366
|
-
|
|
367
|
-
### ScriptTool.derive_name
|
|
368
|
-
|
|
369
|
-
```ruby
|
|
370
|
-
RobotLab::ScriptTool.derive_name(Pathname.new("check-deploy.sh")) # => "check_deploy"
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
Strips the final extension, replaces every run of non-alphanumerics with `_`, and
|
|
374
|
-
trims leading/trailing underscores.
|
|
375
|
-
|
|
376
|
-
### ScriptTool.extract_description
|
|
377
|
-
|
|
378
|
-
```ruby
|
|
379
|
-
RobotLab::ScriptTool.extract_description(path) # => String
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
The first non-shebang comment line in the file, with leading `#` and whitespace
|
|
383
|
-
removed. Falls back to `derive_name(path)` when there is no comment or the file
|
|
384
|
-
cannot be read.
|
|
385
|
-
|
|
386
|
-
```bash
|
|
387
|
-
#!/usr/bin/env bash
|
|
388
|
-
# Verifies a deployment's health before promoting it. <- becomes the description
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## Confinement: robot_lab-sandbox
|
|
394
|
-
|
|
395
|
-
`RobotLab::Sandbox`, `RobotLab::Sandbox::Seatbelt`, and `RobotLab::Sandbox::Null`
|
|
396
|
-
used to live here; they now ship in the separate
|
|
397
|
-
[`robot_lab-sandbox`](https://github.com/MadBomber/robot_lab-sandbox) gem, which
|
|
398
|
-
core has no dependency on. Requiring it installs `RobotLab::Sandbox::Executor`
|
|
399
|
-
as [`ScriptTool.executor`](#scripttoolexecutor):
|
|
400
|
-
|
|
401
|
-
- `Sandbox.enabled?` — reads `config.sandbox.enabled` (default `false`).
|
|
402
|
-
- `Sandbox.for(grant, skill_dir:, macos: macos?)` — picks `Sandbox::Seatbelt` on
|
|
403
|
-
macOS, `Sandbox::Null` (passthrough) elsewhere or for `trust: core` grants.
|
|
404
|
-
- `Sandbox::Seatbelt` — generates a deny-by-default `sandbox-exec` profile from
|
|
405
|
-
the effective grant (`fs_read`/`fs_write`/`network`), wrapping the command as
|
|
406
|
-
`sandbox-exec -f <profile> <cmd...>`. `$HOME` is never implicitly readable.
|
|
407
|
-
- `Sandbox::Null` — `wrap(cmd)` returns `cmd` unchanged; `cleanup` is a no-op.
|
|
408
|
-
- `Sandbox::Executor` — the piece that plugs into core: intersects the skill's
|
|
409
|
-
`Capabilities` with `Capabilities.ceiling`, wraps and runs the command under
|
|
410
|
-
the chosen strategy, and bounds it with the grant's `timeout`
|
|
411
|
-
(`Process.kill('-TERM', ...)` on the process group on expiry).
|
|
412
|
-
|
|
413
|
-
Full reference lives in that gem's own docs.
|
|
414
|
-
|
|
415
|
-
---
|
|
416
|
-
|
|
417
|
-
## See Also
|
|
418
|
-
|
|
419
|
-
- [Using Tools: Skill Scripts and Sandboxing](../guides/using-tools.md#skill-scripts-and-sandboxing)
|
|
420
|
-
- [Configuration: `sandbox:` section](../getting-started/configuration.md#skill-script-sandboxing-sandbox-section)
|
|
421
|
-
- [robot_lab-sandbox](https://github.com/MadBomber/robot_lab-sandbox) — the optional confinement gem
|
|
422
|
-
- [Robot: Skills](core/robot.md#skills) — template skills, the other meaning
|
|
423
|
-
- [Tool](core/tool.md) — `Tool.create`, which `ScriptTool` builds on
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
# Streaming::Context
|
|
2
|
-
|
|
3
|
-
Manages streaming event publishing with automatic sequencing, timestamping, and ID generation.
|
|
4
|
-
|
|
5
|
-
> **Not wired into the framework.** No robot, network, task, or hook ever
|
|
6
|
-
> constructs a `Streaming::Context` — `grep -rn "Streaming::" lib/` finds nothing
|
|
7
|
-
> outside `lib/robot_lab/streaming/`. The class works, but it only publishes what
|
|
8
|
-
> *you* hand it. For token streaming from a robot, use `on_content:` or the block
|
|
9
|
-
> form of `run`; see the [Streaming overview](index.md#the-real-streaming-api).
|
|
10
|
-
|
|
11
|
-
## Class: `RobotLab::Streaming::Context`
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
context = RobotLab::Streaming::Context.new(
|
|
15
|
-
run_id: "run_123",
|
|
16
|
-
message_id: "msg_456",
|
|
17
|
-
scope: "network",
|
|
18
|
-
publish: ->(event) { broadcast(event) }
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
context.publish_event(event: "text.delta", data: { delta: "Hello" })
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Constructor
|
|
25
|
-
|
|
26
|
-
```ruby
|
|
27
|
-
Context.new(
|
|
28
|
-
run_id:,
|
|
29
|
-
message_id:,
|
|
30
|
-
scope:,
|
|
31
|
-
publish:,
|
|
32
|
-
parent_run_id: nil,
|
|
33
|
-
sequence_counter: nil
|
|
34
|
-
)
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
**Parameters:**
|
|
38
|
-
|
|
39
|
-
| Name | Type | Default | Description |
|
|
40
|
-
|------|------|---------|-------------|
|
|
41
|
-
| `run_id` | `String` | required | Unique identifier for this run |
|
|
42
|
-
| `message_id` | `String` | required | Current message identifier |
|
|
43
|
-
| `scope` | `String`, `Symbol` | required | Context scope (e.g., `"network"`, `"robot"`) |
|
|
44
|
-
| `publish` | `Proc` | required | Callback invoked with each event hash |
|
|
45
|
-
| `parent_run_id` | `String`, `nil` | `nil` | Parent run identifier for nested contexts |
|
|
46
|
-
| `sequence_counter` | `SequenceCounter`, `nil` | `nil` | Shared sequence counter (creates new one if nil) |
|
|
47
|
-
|
|
48
|
-
## Attributes
|
|
49
|
-
|
|
50
|
-
### run_id
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
context.run_id # => String
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
The unique run identifier for this context.
|
|
57
|
-
|
|
58
|
-
### parent_run_id
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
context.parent_run_id # => String | nil
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
The parent run identifier. Set when this is a child context created by `create_child_context`.
|
|
65
|
-
|
|
66
|
-
### message_id
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
context.message_id # => String
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
The current message identifier.
|
|
73
|
-
|
|
74
|
-
### scope
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
context.scope # => String
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The context scope (converted to string). Typically `"network"` or `"robot"`.
|
|
81
|
-
|
|
82
|
-
## Methods
|
|
83
|
-
|
|
84
|
-
### publish_event
|
|
85
|
-
|
|
86
|
-
```ruby
|
|
87
|
-
chunk = context.publish_event(event:, data: {})
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Publish a streaming event. The event is wrapped in a chunk with automatic sequencing, timestamping, and context metadata injection.
|
|
91
|
-
|
|
92
|
-
**Parameters:**
|
|
93
|
-
|
|
94
|
-
| Name | Type | Description |
|
|
95
|
-
|------|------|-------------|
|
|
96
|
-
| `event` | `String` | Event type (e.g., `"text.delta"`, `"run.started"`) |
|
|
97
|
-
| `data` | `Hash` | Event payload (default: `{}`) |
|
|
98
|
-
|
|
99
|
-
**Returns:** The constructed event chunk hash.
|
|
100
|
-
|
|
101
|
-
The chunk structure:
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
{
|
|
105
|
-
event: "text.delta",
|
|
106
|
-
data: {
|
|
107
|
-
delta: "Hello", # from data parameter
|
|
108
|
-
run_id: "run_123", # injected from context
|
|
109
|
-
message_id: "msg_456", # injected from context
|
|
110
|
-
scope: "robot" # injected from context
|
|
111
|
-
},
|
|
112
|
-
timestamp: 1707900000000, # millisecond Unix timestamp
|
|
113
|
-
sequence_number: 1, # monotonically increasing
|
|
114
|
-
id: "publish-1:text.delta" # unique event ID
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
If the publish callback raises an error, it is caught and logged as a warning via `RobotLab.config.logger`. The chunk is still returned.
|
|
119
|
-
|
|
120
|
-
### create_child_context
|
|
121
|
-
|
|
122
|
-
```ruby
|
|
123
|
-
child = context.create_child_context(robot_run_id)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Create a child context for a nested robot execution. The child shares the same publish callback and sequence counter, ensuring events are ordered globally across the parent and child.
|
|
127
|
-
|
|
128
|
-
**Parameters:**
|
|
129
|
-
|
|
130
|
-
| Name | Type | Description |
|
|
131
|
-
|------|------|-------------|
|
|
132
|
-
| `robot_run_id` | `String` | Run ID for the child context |
|
|
133
|
-
|
|
134
|
-
**Returns:** A new `Context` with:
|
|
135
|
-
- `run_id` set to `robot_run_id`
|
|
136
|
-
- `parent_run_id` set to the current context's `run_id`
|
|
137
|
-
- `scope` set to `"robot"`
|
|
138
|
-
- A new `message_id` (generated UUID)
|
|
139
|
-
- Shared `sequence_counter` and `publish` callback
|
|
140
|
-
|
|
141
|
-
### create_context_with_shared_sequence
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
sibling = context.create_context_with_shared_sequence(
|
|
145
|
-
run_id: "run_789",
|
|
146
|
-
message_id: "msg_789",
|
|
147
|
-
scope: "robot"
|
|
148
|
-
)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Create a new context that shares the same sequence counter as this context, but with different identifiers.
|
|
152
|
-
|
|
153
|
-
**Parameters:**
|
|
154
|
-
|
|
155
|
-
| Name | Type | Description |
|
|
156
|
-
|------|------|-------------|
|
|
157
|
-
| `run_id` | `String` | Run ID for the new context |
|
|
158
|
-
| `message_id` | `String` | Message ID for the new context |
|
|
159
|
-
| `scope` | `String` | Scope for the new context |
|
|
160
|
-
|
|
161
|
-
**Returns:** A new `Context` sharing the sequence counter and publish callback.
|
|
162
|
-
|
|
163
|
-
### generate_part_id
|
|
164
|
-
|
|
165
|
-
```ruby
|
|
166
|
-
context.generate_part_id # => "part_37ac1d49_815007_8f522876"
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Generate an OpenAI-compatible part ID (max 40 characters), assembled as
|
|
170
|
-
`"part_<msg>_<ts>_<rand>"`:
|
|
171
|
-
|
|
172
|
-
| Segment | Source | Length |
|
|
173
|
-
|---------|--------|--------|
|
|
174
|
-
| `<msg>` | First 8 characters of **`message_id`** (not `run_id`) | 8 |
|
|
175
|
-
| `<ts>` | Last 6 digits of the millisecond Unix timestamp | 6 |
|
|
176
|
-
| `<rand>` | `SecureRandom.hex(4)` | 8 hex chars |
|
|
177
|
-
|
|
178
|
-
Note this reads `message_id`, so two contexts sharing a message produce IDs with
|
|
179
|
-
the same first segment.
|
|
180
|
-
|
|
181
|
-
### generate_step_id
|
|
182
|
-
|
|
183
|
-
```ruby
|
|
184
|
-
context.generate_step_id("text_output") # => "publish-3:text_output"
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Generate a step ID for durable execution compatibility, formatted as
|
|
188
|
-
`"publish-<n>:<base_name>"`. It reads the counter with `current`, which does
|
|
189
|
-
**not** increment: `<n>` is the sequence number of the most recently published
|
|
190
|
-
event (`0` before anything has been published), so calling it repeatedly between
|
|
191
|
-
publishes yields the same ID.
|
|
192
|
-
|
|
193
|
-
**Parameters:**
|
|
194
|
-
|
|
195
|
-
| Name | Type | Description |
|
|
196
|
-
|------|------|-------------|
|
|
197
|
-
| `base_name` | `String` | Base name for the step |
|
|
198
|
-
|
|
199
|
-
### generate_message_id
|
|
200
|
-
|
|
201
|
-
```ruby
|
|
202
|
-
context.generate_message_id # => "a1b2c3d4-..."
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Generate a new UUID for use as a message identifier.
|
|
206
|
-
|
|
207
|
-
## Examples
|
|
208
|
-
|
|
209
|
-
### Basic Event Publishing
|
|
210
|
-
|
|
211
|
-
```ruby
|
|
212
|
-
publish = ->(event) {
|
|
213
|
-
puts "[#{event[:event]}] #{event[:data]}"
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
context = RobotLab::Streaming::Context.new(
|
|
217
|
-
run_id: SecureRandom.uuid,
|
|
218
|
-
message_id: SecureRandom.uuid,
|
|
219
|
-
scope: "robot",
|
|
220
|
-
publish: publish
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
context.publish_event(event: "run.started", data: { robot_name: "assistant" })
|
|
224
|
-
context.publish_event(event: "text.delta", data: { delta: "Hello " })
|
|
225
|
-
context.publish_event(event: "text.delta", data: { delta: "world!" })
|
|
226
|
-
context.publish_event(event: "run.completed", data: {})
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Modelling a Multi-Robot Run with Child Contexts
|
|
230
|
-
|
|
231
|
-
`RobotLab::Network` does not do any of this for you — the nesting below is
|
|
232
|
-
something you would write by hand around your own orchestration.
|
|
233
|
-
|
|
234
|
-
```ruby
|
|
235
|
-
# Parent context
|
|
236
|
-
network_ctx = RobotLab::Streaming::Context.new(
|
|
237
|
-
run_id: "net_run_1",
|
|
238
|
-
message_id: "net_msg_1",
|
|
239
|
-
scope: "network",
|
|
240
|
-
publish: ->(e) { stream_to_client(e) }
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
network_ctx.publish_event(event: "run.started", data: {})
|
|
244
|
-
|
|
245
|
-
# Robot 1 executes
|
|
246
|
-
robot1_ctx = network_ctx.create_child_context("robot1_run_1")
|
|
247
|
-
robot1_ctx.publish_event(event: "step.started", data: { robot: "classifier" })
|
|
248
|
-
robot1_ctx.publish_event(event: "text.delta", data: { delta: "Category: billing" })
|
|
249
|
-
robot1_ctx.publish_event(event: "step.completed", data: { robot: "classifier" })
|
|
250
|
-
|
|
251
|
-
# Robot 2 executes (sequence numbers continue from robot 1)
|
|
252
|
-
robot2_ctx = network_ctx.create_child_context("robot2_run_1")
|
|
253
|
-
robot2_ctx.publish_event(event: "step.started", data: { robot: "responder" })
|
|
254
|
-
robot2_ctx.publish_event(event: "text.delta", data: { delta: "I can help with that." })
|
|
255
|
-
robot2_ctx.publish_event(event: "step.completed", data: { robot: "responder" })
|
|
256
|
-
|
|
257
|
-
network_ctx.publish_event(event: "run.completed", data: {})
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Error-Safe Publishing
|
|
261
|
-
|
|
262
|
-
```ruby
|
|
263
|
-
# Errors in the publish callback are caught and logged,
|
|
264
|
-
# so streaming failures do not interrupt execution.
|
|
265
|
-
context = RobotLab::Streaming::Context.new(
|
|
266
|
-
run_id: "run_1",
|
|
267
|
-
message_id: "msg_1",
|
|
268
|
-
scope: "robot",
|
|
269
|
-
publish: ->(e) { raise "connection lost" }
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
# This does not raise -- the error is logged via RobotLab.config.logger
|
|
273
|
-
chunk = context.publish_event(event: "text.delta", data: { delta: "test" })
|
|
274
|
-
# chunk is still returned with the event data
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## See Also
|
|
278
|
-
|
|
279
|
-
- [Streaming Overview](index.md)
|
|
280
|
-
- [Events](events.md)
|