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/architecture/index.md
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# Architecture Overview
|
|
2
|
-
|
|
3
|
-
RobotLab is designed around a few core architectural principles that enable flexible, composable AI workflows.
|
|
4
|
-
|
|
5
|
-
## Design Philosophy
|
|
6
|
-
|
|
7
|
-
### 1. Separation of Concerns
|
|
8
|
-
|
|
9
|
-
Each component has a single, well-defined responsibility:
|
|
10
|
-
|
|
11
|
-
- **Robot**: LLM-powered agent (subclass of `RubyLLM::Agent`) with personality, tools, and memory
|
|
12
|
-
- **Network**: Orchestrates robot execution as a DAG pipeline via SimpleFlow
|
|
13
|
-
- **Memory**: Reactive key-value store for robot and network data
|
|
14
|
-
- **Tool**: Provides external capabilities to robots (inherits from `RubyLLM::Tool`)
|
|
15
|
-
- **Task**: Wraps a robot for pipeline execution with per-task configuration
|
|
16
|
-
|
|
17
|
-
### 2. Composability
|
|
18
|
-
|
|
19
|
-
Components are designed to be mixed and matched:
|
|
20
|
-
|
|
21
|
-
- Robots can be used standalone or within networks
|
|
22
|
-
- Tools can be shared across robots or scoped per-robot via `local_tools:`
|
|
23
|
-
- Networks define DAG pipelines with sequential, parallel, and optional execution
|
|
24
|
-
- Memory can be standalone (per-robot) or shared (per-network)
|
|
25
|
-
- `with_*` methods return `self` for fluent chaining
|
|
26
|
-
|
|
27
|
-
### 3. Provider Agnostic
|
|
28
|
-
|
|
29
|
-
RobotLab abstracts away LLM provider differences through RubyLLM:
|
|
30
|
-
|
|
31
|
-
- Unified interface across Anthropic, OpenAI, Gemini, DeepSeek, Mistral, and others
|
|
32
|
-
- Consistent tool calling interface
|
|
33
|
-
- Automatic provider detection from model names
|
|
34
|
-
- Easy switching between providers via configuration
|
|
35
|
-
|
|
36
|
-
## System Architecture
|
|
37
|
-
|
|
38
|
-
```mermaid
|
|
39
|
-
graph TB
|
|
40
|
-
subgraph "Application Layer"
|
|
41
|
-
A[Your Application]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
subgraph "RobotLab Core"
|
|
45
|
-
B[Network]
|
|
46
|
-
C[Task]
|
|
47
|
-
D[Robot < RubyLLM::Agent]
|
|
48
|
-
E[Memory]
|
|
49
|
-
F[RobotResult]
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
subgraph "Configuration"
|
|
53
|
-
G[Config < MywayConfig::Base]
|
|
54
|
-
R[RunConfig]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
subgraph "Cross-Cutting"
|
|
58
|
-
HK[Hooks<br/>HookRegistry x3]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
subgraph "Integration Layer"
|
|
62
|
-
H[MCP Client]
|
|
63
|
-
I[Tools < RubyLLM::Tool]
|
|
64
|
-
J[Templates / prompt_manager]
|
|
65
|
-
SK[AgentSkills<br/>+ optional Sandbox]
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
subgraph "Execution Layer"
|
|
69
|
-
K[SimpleFlow::Pipeline]
|
|
70
|
-
L[RubyLLM Chat]
|
|
71
|
-
TB[TypedBus + BusPoller]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
subgraph "Provider Layer"
|
|
75
|
-
M[Anthropic]
|
|
76
|
-
N[OpenAI]
|
|
77
|
-
O[Gemini]
|
|
78
|
-
P[MCP Servers]
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
A --> B
|
|
82
|
-
A --> D
|
|
83
|
-
B --> C
|
|
84
|
-
C --> D
|
|
85
|
-
B --> K
|
|
86
|
-
B --> E
|
|
87
|
-
B --> TB
|
|
88
|
-
D --> E
|
|
89
|
-
D --> L
|
|
90
|
-
D --> TB
|
|
91
|
-
D --> H
|
|
92
|
-
D --> I
|
|
93
|
-
D --> J
|
|
94
|
-
D --> SK
|
|
95
|
-
D --> F
|
|
96
|
-
SK --> I
|
|
97
|
-
G --> R
|
|
98
|
-
R --> B
|
|
99
|
-
R --> C
|
|
100
|
-
R --> D
|
|
101
|
-
G --> D
|
|
102
|
-
G --> L
|
|
103
|
-
HK -.wraps.-> B
|
|
104
|
-
HK -.wraps.-> C
|
|
105
|
-
HK -.wraps.-> D
|
|
106
|
-
HK -.wraps.-> I
|
|
107
|
-
L --> M
|
|
108
|
-
L --> N
|
|
109
|
-
L --> O
|
|
110
|
-
H --> P
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
The dotted `wraps` edges are the hook system: `Hooks.run` brackets every network
|
|
114
|
-
run, task, robot run, LLM generation, tool call, compaction, and `learn` call. It
|
|
115
|
-
is the framework's extension seam — the extension gems (`robot_lab-audit`,
|
|
116
|
-
`robot_lab-durable`, …) attach here rather than subclassing core objects. See the
|
|
117
|
-
[Hooks API](../api/hooks.md).
|
|
118
|
-
|
|
119
|
-
### Robot subsystems
|
|
120
|
-
|
|
121
|
-
A `Robot` composes several small collaborators, each documented on the
|
|
122
|
-
[Support](../api/support.md) and [Skills](../api/skills.md) API pages:
|
|
123
|
-
|
|
124
|
-
```mermaid
|
|
125
|
-
graph LR
|
|
126
|
-
D[Robot]
|
|
127
|
-
|
|
128
|
-
D --> BM[BusMessaging<br/>RobotMessage envelopes]
|
|
129
|
-
BM --> BP[BusPoller<br/>per-robot serialization]
|
|
130
|
-
D --> BD[Budget::Ledger<br/>token_budget / cost_budget]
|
|
131
|
-
D --> DL[DoomLoopDetector<br/>always installed per run]
|
|
132
|
-
D --> HC[HistoryCompressor<br/>compress_history / auto_compact]
|
|
133
|
-
D --> DF[DelegationFuture<br/>delegate async: true]
|
|
134
|
-
D --> HS[HistorySearch<br/>search_history]
|
|
135
|
-
D --> ASM[AgentSkillMatching<br/>prepended around run]
|
|
136
|
-
|
|
137
|
-
ASM --> AS[AgentSkill<br/>SKILL.md bundle]
|
|
138
|
-
AS --> CAT[AgentSkillCatalog]
|
|
139
|
-
AS --> CAP[Capabilities]
|
|
140
|
-
AS --> ST[ScriptTool]
|
|
141
|
-
ST --> EX{ScriptTool.executor}
|
|
142
|
-
EX -. "installed by<br/>robot_lab-sandbox" .-> SB[Sandbox<br/>Seatbelt or Null]
|
|
143
|
-
CAP -. "used if installed" .-> SB
|
|
144
|
-
|
|
145
|
-
HC --> TA[TextAnalysis<br/>TF / TF-IDF]
|
|
146
|
-
HS --> TA
|
|
147
|
-
CV[Convergence] --> TA
|
|
148
|
-
SD[MCP::ServerDiscovery] --> TA
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
`TextAnalysis` is the shared floor under every similarity feature — history
|
|
152
|
-
compression, history search, convergence detection, and MCP server discovery all
|
|
153
|
-
route through it, which is why they share the one optional `classifier`
|
|
154
|
-
dependency.
|
|
155
|
-
|
|
156
|
-
## Core Components
|
|
157
|
-
|
|
158
|
-
| Component | Description | Documentation |
|
|
159
|
-
|-----------|-------------|---------------|
|
|
160
|
-
| **Robot** | LLM agent (subclass of `RubyLLM::Agent`) with template-based prompts, tools, and memory | [Core Concepts](core-concepts.md) |
|
|
161
|
-
| **Network** | Orchestrates multiple robots as a SimpleFlow pipeline | [Network Orchestration](network-orchestration.md) |
|
|
162
|
-
| **Memory** | Reactive key-value store with pub/sub and blocking reads | [Memory Management](state-management.md) |
|
|
163
|
-
| **Task** | Wraps a robot for pipeline execution with per-task config | [Network Orchestration](network-orchestration.md) |
|
|
164
|
-
| **RobotResult** | Captures LLM output, tool calls, and metadata from a run | [Message Flow](message-flow.md) |
|
|
165
|
-
| **RunConfig** | Per-run settings object (LLM fields, `mcp`/`tools`, callbacks, infrastructure) that cascades global → network → task → robot | [Core Concepts](core-concepts.md) |
|
|
166
|
-
| **Config** | MywayConfig-based global configuration with env var and file support | [Configuration](#configuration) |
|
|
167
|
-
| **Hook** | Handler base class for the seven hook families — the framework's extension seam | [Hooks API](../api/hooks.md) |
|
|
168
|
-
| **AgentSkill** | A `SKILL.md` bundle whose instructions and `scripts/` become prompt text and tools | [Skills API](../api/skills.md) |
|
|
169
|
-
| **Sandbox** | Opt-in OS-level confinement (macOS Seatbelt) for skill scripts, derived from `Capabilities`; ships in the optional `robot_lab-sandbox` gem, not core | [Skills API](../api/skills.md) |
|
|
170
|
-
| **RobotMessage** | Immutable envelope for TypedBus inter-robot messaging, serialized per robot by `BusPoller` | [Support API](../api/support.md) |
|
|
171
|
-
| **Budget::Ledger** | Thread-safe reserve/reconcile ledger behind `token_budget` / `cost_budget` | [Support API](../api/support.md) |
|
|
172
|
-
|
|
173
|
-
## Configuration
|
|
174
|
-
|
|
175
|
-
Global configuration is a MywayConfig subclass (`Config < MywayConfig::Base`). It is loaded from multiple sources in priority order:
|
|
176
|
-
|
|
177
|
-
1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
|
|
178
|
-
2. **Environment overrides** (development, test, production)
|
|
179
|
-
3. **XDG user config** (`~/.config/robot_lab/robot_lab.yml` — the filename repeats the app name; `config.yml` is never read)
|
|
180
|
-
4. **Project config** (`./config/robot_lab.yml` — the only file with an ERB pass)
|
|
181
|
-
5. **Environment variables** (`ROBOT_LAB_*` prefix, double underscore for nesting)
|
|
182
|
-
6. **Constructor params**
|
|
183
|
-
|
|
184
|
-
Top-level wrappers behave differently per file. In the **XDG user config**, a section named for the current environment is honoured — the loader looks for `parsed.key?(env)` (`Anyway::Settings.current_environment`, else `RAILS_ENV`, else `RACK_ENV`, else `"development"`) and falls back to the root when absent — so both `development:` and a flat file work there. A `defaults:` wrapper is not an environment name, so it is ignored. The **project config** must be flat outside Rails (all wrappers ignored); under Rails, `anyway_config` sets `current_environment` to `Rails.env` and the project file becomes environmental, so a flat file is ignored and keys must sit under `development:` / `test:` / `production:`. See [Core Concepts](core-concepts.md#configuration) for details.
|
|
185
|
-
|
|
186
|
-
```ruby
|
|
187
|
-
# Access configuration
|
|
188
|
-
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
189
|
-
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
190
|
-
|
|
191
|
-
# Block form (yields the same Config object)
|
|
192
|
-
RobotLab.configure do |c|
|
|
193
|
-
c.logger = Logger.new($stdout)
|
|
194
|
-
end
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Data Flow
|
|
198
|
-
|
|
199
|
-
1. **Input**: User calls `robot.run("message")` or `network.run(message: "...")`
|
|
200
|
-
2. **Memory**: Robot resolves active memory (standalone or network-shared)
|
|
201
|
-
3. **MCP**: Robot resolves MCP servers from hierarchical config and connects clients. `run` defaults to `mcp: :none`, so a plain `run` connects nothing
|
|
202
|
-
4. **Tools**: Robot resolves and filters tools from hierarchical config. `run` defaults to `tools: :none`, so a plain `run` sends the LLM zero tools; pass `tools: :inherit` to send the robot's attached tools
|
|
203
|
-
5. **Execution**: Robot delegates to `Agent#ask` which calls `@chat.ask` on RubyLLM
|
|
204
|
-
6. **Tool Loop**: LLM may invoke tools; RubyLLM handles the tool call/result loop
|
|
205
|
-
7. **Result**: Robot builds and returns a `RobotResult`
|
|
206
|
-
8. **Network**: If in a network, result flows to dependent tasks via SimpleFlow
|
|
207
|
-
|
|
208
|
-
## Key Patterns
|
|
209
|
-
|
|
210
|
-
### Factory Methods
|
|
211
|
-
|
|
212
|
-
Robots and networks are created via factory methods on the `RobotLab` module:
|
|
213
|
-
|
|
214
|
-
```ruby
|
|
215
|
-
robot = RobotLab.build(
|
|
216
|
-
name: "assistant",
|
|
217
|
-
template: :assistant,
|
|
218
|
-
context: { tone: "friendly" }
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
network = RobotLab.create_network(name: "pipeline") do
|
|
222
|
-
task :analyst, analyst_robot, depends_on: :none
|
|
223
|
-
task :writer, writer_robot, depends_on: [:analyst]
|
|
224
|
-
end
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Fluent Chaining
|
|
228
|
-
|
|
229
|
-
`with_*` methods on Robot delegate to the underlying `@chat` and return `self`:
|
|
230
|
-
|
|
231
|
-
```ruby
|
|
232
|
-
robot = RobotLab.build(name: "bot")
|
|
233
|
-
.with_instructions("Be concise.")
|
|
234
|
-
.with_temperature(0.3)
|
|
235
|
-
.with_model("gpt-4o")
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Hierarchical Configuration
|
|
239
|
-
|
|
240
|
-
Tools and MCP servers use hierarchical resolution: `run()/task > robot build-time > network > global config`. Values can be `:none`, `:inherit`, or an explicit array (which filters the already-attached tools by name — entries must match how the tool was attached, class or instance). `run()` defaults both to `:none`, so the runtime level is an explicit "send nothing" unless you override it.
|
|
241
|
-
|
|
242
|
-
### SimpleFlow Pipeline
|
|
243
|
-
|
|
244
|
-
Networks are thin wrappers around `SimpleFlow::Pipeline`. Each robot is wrapped in a `Task` that implements the `call(result)` interface. Tasks define dependencies (`:none`, `[:task_names]`, or `:optional`) to control execution order.
|
|
245
|
-
|
|
246
|
-
## Next Steps
|
|
247
|
-
|
|
248
|
-
- [Core Concepts](core-concepts.md) - Deep dive into robots and tools
|
|
249
|
-
- [Robot Execution](robot-execution.md) - How robots process messages
|
|
250
|
-
- [Network Orchestration](network-orchestration.md) - Multi-robot workflows
|
|
251
|
-
- [Memory Management](state-management.md) - Managing memory and reactive features
|
|
252
|
-
- [Message Flow](message-flow.md) - How messages move through the system
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
# Message Flow
|
|
2
|
-
|
|
3
|
-
This page explains how messages move through RobotLab, from user input to LLM response.
|
|
4
|
-
|
|
5
|
-
## Message Types
|
|
6
|
-
|
|
7
|
-
`RobotLab::Message` is the abstract base for three conversation message classes. `ToolMessage` is a plain value object that they reference, and two further message classes — `UserMessage` and `RobotMessage` — live outside the hierarchy entirely:
|
|
8
|
-
|
|
9
|
-
```mermaid
|
|
10
|
-
classDiagram
|
|
11
|
-
class Message {
|
|
12
|
-
<<abstract>>
|
|
13
|
-
+type: String
|
|
14
|
-
+role: String
|
|
15
|
-
+content
|
|
16
|
-
+stop_reason: String
|
|
17
|
-
+text?() bool
|
|
18
|
-
+tool_call?() bool
|
|
19
|
-
+tool_result?() bool
|
|
20
|
-
+system?() bool
|
|
21
|
-
+user?() bool
|
|
22
|
-
+assistant?() bool
|
|
23
|
-
+stopped?() bool
|
|
24
|
-
+tool_stop?() bool
|
|
25
|
-
+to_h() Hash
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class TextMessage {
|
|
29
|
-
+content: String
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class ToolMessage {
|
|
33
|
-
<<PORO>>
|
|
34
|
-
+id: String
|
|
35
|
-
+name: String
|
|
36
|
-
+input: Hash
|
|
37
|
-
+to_h() Hash
|
|
38
|
-
+to_json() String
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class ToolCallMessage {
|
|
42
|
-
+tools: Array~ToolMessage~
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
class ToolResultMessage {
|
|
46
|
-
+tool: ToolMessage
|
|
47
|
-
+content: Hash
|
|
48
|
-
+success?() bool
|
|
49
|
-
+error?() bool
|
|
50
|
-
+data()
|
|
51
|
-
+error()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
Message <|-- TextMessage
|
|
55
|
-
Message <|-- ToolCallMessage
|
|
56
|
-
Message <|-- ToolResultMessage
|
|
57
|
-
ToolMessage -- ToolCallMessage
|
|
58
|
-
ToolMessage -- ToolResultMessage
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
`ToolMessage` does **not** inherit from `Message` — its superclass is `Object`. It has no `type`, `role`, or predicate methods; only `id`, `name`, `input`, `to_h`, and `to_json`. The `tool_call?` predicate lives on `Message`, so it answers for `ToolCallMessage`, not for `ToolMessage`.
|
|
62
|
-
|
|
63
|
-
The two classes outside the hierarchy:
|
|
64
|
-
|
|
65
|
-
| Class | Role |
|
|
66
|
-
|-------|------|
|
|
67
|
-
| `UserMessage` | Envelope for user input carrying `session_id`, an extra `system_prompt`, `metadata`, `id`, `created_at`. `UserMessage.from` normalizes a String/Hash/TextMessage; `#to_message` converts to a `TextMessage` |
|
|
68
|
-
| `RobotMessage` | Immutable `Data.define(:id, :from, :content, :in_reply_to)` envelope for robot-to-robot traffic on the message bus, with `#key` (`"from:id"`) and `#reply?` |
|
|
69
|
-
|
|
70
|
-
### TextMessage
|
|
71
|
-
|
|
72
|
-
Regular text content from users or assistants:
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
TextMessage.new(
|
|
76
|
-
role: "user",
|
|
77
|
-
content: "What's the weather in Paris?"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
TextMessage.new(
|
|
81
|
-
role: "assistant",
|
|
82
|
-
content: "The weather in Paris is sunny and 22 degrees C.",
|
|
83
|
-
stop_reason: "stop"
|
|
84
|
-
)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### ToolMessage
|
|
88
|
-
|
|
89
|
-
A standalone value object (not a `Message`) representing a tool invocation and its parameters:
|
|
90
|
-
|
|
91
|
-
```ruby
|
|
92
|
-
tool = ToolMessage.new(
|
|
93
|
-
id: "tool_123",
|
|
94
|
-
name: "get_weather",
|
|
95
|
-
input: { location: "Paris" }
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
tool.to_h #=> { type: "tool", id: "tool_123", name: "get_weather", input: { location: "Paris" } }
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Note that the `type: "tool"` key appears only in `to_h`; there is no `type` reader and no predicate methods.
|
|
102
|
-
|
|
103
|
-
### ToolCallMessage
|
|
104
|
-
|
|
105
|
-
LLM's request to execute one or more tools:
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
ToolCallMessage.new(
|
|
109
|
-
role: "assistant",
|
|
110
|
-
tools: [
|
|
111
|
-
ToolMessage.new(id: "call_1", name: "get_weather", input: { location: "Paris" })
|
|
112
|
-
],
|
|
113
|
-
stop_reason: "tool"
|
|
114
|
-
)
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
The signature is `initialize(role:, tools:, stop_reason: nil)` — `role` and
|
|
118
|
-
`tools` are required and there is **no** `content:` keyword. Passing one raises
|
|
119
|
-
`ArgumentError`.
|
|
120
|
-
|
|
121
|
-
### ToolResultMessage
|
|
122
|
-
|
|
123
|
-
Result from tool execution:
|
|
124
|
-
|
|
125
|
-
```ruby
|
|
126
|
-
ToolResultMessage.new(
|
|
127
|
-
tool: tool_message,
|
|
128
|
-
content: { data: { temp: 22, condition: "sunny" } }
|
|
129
|
-
)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Message Flow: Standalone Robot
|
|
133
|
-
|
|
134
|
-
The primary execution path is `robot.run("message")`:
|
|
135
|
-
|
|
136
|
-
```mermaid
|
|
137
|
-
sequenceDiagram
|
|
138
|
-
participant User
|
|
139
|
-
participant Robot
|
|
140
|
-
participant Memory
|
|
141
|
-
participant MCP
|
|
142
|
-
participant Tools
|
|
143
|
-
participant Agent
|
|
144
|
-
participant Chat
|
|
145
|
-
participant LLM
|
|
146
|
-
|
|
147
|
-
User->>Robot: robot.run("message")
|
|
148
|
-
Robot->>Memory: resolve_active_memory
|
|
149
|
-
Memory-->>Robot: active memory
|
|
150
|
-
|
|
151
|
-
Robot->>MCP: resolve_mcp_hierarchy
|
|
152
|
-
MCP-->>Robot: resolved MCP config
|
|
153
|
-
Robot->>Robot: ensure_mcp_clients
|
|
154
|
-
|
|
155
|
-
Robot->>Tools: resolve_tools_hierarchy
|
|
156
|
-
Tools-->>Robot: filtered + capped tools
|
|
157
|
-
Robot->>Chat: @chat.with_tools(*filtered, replace: true)
|
|
158
|
-
|
|
159
|
-
Robot->>Agent: ask("message")
|
|
160
|
-
Agent->>Chat: @chat.ask("message")
|
|
161
|
-
Chat->>LLM: Provider API call
|
|
162
|
-
|
|
163
|
-
loop Tool Loop (handled by RubyLLM)
|
|
164
|
-
LLM-->>Chat: Tool call response
|
|
165
|
-
Chat->>Tools: Execute tool
|
|
166
|
-
Tools-->>Chat: Tool result
|
|
167
|
-
Chat->>LLM: Send tool result
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
LLM-->>Chat: Final response
|
|
171
|
-
Chat-->>Agent: RubyLLM::Response
|
|
172
|
-
Agent-->>Robot: response
|
|
173
|
-
|
|
174
|
-
Robot->>Robot: build_result(response, memory)
|
|
175
|
-
Robot-->>User: RobotResult
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Step-by-Step
|
|
179
|
-
|
|
180
|
-
1. **`robot.run("message")`**: Entry point. Accepts a positional string argument.
|
|
181
|
-
|
|
182
|
-
2. **Resolve Memory**: Determines which memory to use:
|
|
183
|
-
- `network_memory` if provided (network execution)
|
|
184
|
-
- `network.memory` if in a network context
|
|
185
|
-
- `robot.memory` (standalone, the default)
|
|
186
|
-
|
|
187
|
-
3. **Merge Runtime Memory**: If a `memory:` keyword argument is passed, it is merged into the active memory.
|
|
188
|
-
|
|
189
|
-
4. **Set Current Writer**: Sets `memory.current_writer = robot.name` so subscription callbacks know which robot wrote a value.
|
|
190
|
-
|
|
191
|
-
5. **Resolve MCP Hierarchy**: Resolves MCP server configuration through the hierarchy: `runtime (run/task) > robot build-time > network > global config`. `run` defaults `mcp:` to `:none`, so nothing is connected unless you pass `mcp: :inherit` or an explicit list.
|
|
192
|
-
|
|
193
|
-
6. **Ensure MCP Clients**: Initializes or updates MCP client connections and discovers tools from them. Connection failures are logged and recorded in `failed_mcp_server_names`, never raised.
|
|
194
|
-
|
|
195
|
-
7. **Resolve Tools Hierarchy**: Resolves which tools are available through the same hierarchy. `run` defaults `tools:` to `:none`, which means "send zero tools this turn" — pass `tools: :inherit` to send the robot's attached tools.
|
|
196
|
-
|
|
197
|
-
8. **Filter Tools**: Filters by the resolved allowlist, clamps to `max_tools` (128 by default), and applies the set with `@chat.with_tools(*filtered, replace: true)` so the persistent chat holds exactly this turn's tools.
|
|
198
|
-
|
|
199
|
-
9. **Agent#ask**: Delegates to the parent class `RubyLLM::Agent#ask`, which calls `@chat.ask(message)`.
|
|
200
|
-
|
|
201
|
-
10. **LLM Interaction**: RubyLLM handles the provider-specific API call, including the tool call/result loop.
|
|
202
|
-
|
|
203
|
-
11. **Build Result**: Wraps the LLM response in a `RobotResult` containing output messages, tool calls, and metadata.
|
|
204
|
-
|
|
205
|
-
12. **Return**: Returns the `RobotResult` to the caller.
|
|
206
|
-
|
|
207
|
-
## Message Flow: Network Execution
|
|
208
|
-
|
|
209
|
-
When running through a network, the flow adds pipeline orchestration:
|
|
210
|
-
|
|
211
|
-
```mermaid
|
|
212
|
-
sequenceDiagram
|
|
213
|
-
participant User
|
|
214
|
-
participant Network
|
|
215
|
-
participant Pipeline
|
|
216
|
-
participant Task
|
|
217
|
-
participant Robot
|
|
218
|
-
participant LLM
|
|
219
|
-
|
|
220
|
-
User->>Network: network.run(message: "...")
|
|
221
|
-
Network->>Network: Inject network_memory into run_context
|
|
222
|
-
Network->>Pipeline: SimpleFlow::Pipeline.call_parallel(initial_result)
|
|
223
|
-
|
|
224
|
-
loop For each ready task
|
|
225
|
-
Pipeline->>Task: task.call(result)
|
|
226
|
-
Task->>Task: Deep merge task context with run_params
|
|
227
|
-
Task->>Robot: robot.call(enhanced_result)
|
|
228
|
-
Robot->>Robot: extract_run_context(result)
|
|
229
|
-
Robot->>Robot: run(message, network_memory: ...)
|
|
230
|
-
Robot->>LLM: Agent#ask -> @chat.ask
|
|
231
|
-
LLM-->>Robot: Response
|
|
232
|
-
Robot-->>Task: result.with_context(:name, robot_result).continue(robot_result)
|
|
233
|
-
Task-->>Pipeline: SimpleFlow::Result
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
Pipeline-->>Network: Final SimpleFlow::Result
|
|
237
|
-
Network-->>User: result
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Key Points
|
|
241
|
-
|
|
242
|
-
- **Network creates initial result**: `SimpleFlow::Result.new(run_context, context: { run_params: run_context })`, after injecting `network_memory`, `network`, and (when non-empty) `network_config` into the run context, all wrapped in the `:network_run` hook
|
|
243
|
-
- **Task wraps robot**: Each `Task` runs the `:task` hook and deep-merges its own context, `mcp`, `tools`, `memory`, and `config` into the run params before delegating to the robot
|
|
244
|
-
- **Robot extracts context**: `extract_run_context(result)` pulls the message, MCP, tools, memory, network, and task out of the SimpleFlow result and passes them to `run` as keyword arguments
|
|
245
|
-
- **Shared memory**: All robots use `network.memory` during network execution
|
|
246
|
-
- **Result accumulation**: Each robot stores its `RobotResult` in `result.context[:robot_name]` — the key comes from `@name.to_sym` in `Robot#call`, so it is the **robot's** name, not the task name. They match only when the two are spelled the same way
|
|
247
|
-
|
|
248
|
-
## RobotResult
|
|
249
|
-
|
|
250
|
-
The return value of `robot.run("message")`:
|
|
251
|
-
|
|
252
|
-
```ruby
|
|
253
|
-
result = robot.run("What is Ruby?")
|
|
254
|
-
|
|
255
|
-
result.last_text_content #=> "Ruby is a dynamic programming language..."
|
|
256
|
-
result.reply #=> alias for last_text_content
|
|
257
|
-
result.has_tool_calls? #=> false
|
|
258
|
-
result.robot_name #=> "assistant"
|
|
259
|
-
result.output #=> [TextMessage(role: "assistant", content: "...")]
|
|
260
|
-
result.tool_calls #=> []
|
|
261
|
-
result.stop_reason #=> nil (always — see below)
|
|
262
|
-
result.created_at #=> Time
|
|
263
|
-
result.id #=> "uuid"
|
|
264
|
-
result.checksum #=> "sha256-hex"
|
|
265
|
-
result.input_tokens #=> 42
|
|
266
|
-
result.output_tokens #=> 128
|
|
267
|
-
result.duration #=> Float, nil
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
`stop_reason` is always `nil` on a `Robot#run` result: `build_result` reads it as `response.respond_to?(:stop_reason) ? response.stop_reason : nil`, and `RubyLLM::Message` does not define the method. It is consequently dropped from `export`, and `result.stopped?` reduces to `!result.has_tool_calls?`.
|
|
271
|
-
|
|
272
|
-
`output` always holds exactly one synthesized `TextMessage` built from the final response text — it is not a transcript of the turn, and it never contains `ToolCallMessage` or `ToolResultMessage` entries. `tool_calls` is read off the final assistant message, which carries no tool calls once RubyLLM's tool loop has finished, so it is effectively always empty. To observe tool activity, use the `on_tool_call` / `on_tool_result` callbacks or the tool hooks.
|
|
273
|
-
|
|
274
|
-
### Result Serialization
|
|
275
|
-
|
|
276
|
-
```ruby
|
|
277
|
-
# Export for persistence (excludes debug fields)
|
|
278
|
-
hash = result.export
|
|
279
|
-
|
|
280
|
-
# Full hash including debug fields
|
|
281
|
-
hash = result.to_h
|
|
282
|
-
|
|
283
|
-
# JSON
|
|
284
|
-
json = result.to_json
|
|
285
|
-
|
|
286
|
-
# Reconstruct from hash
|
|
287
|
-
result = RobotResult.from_hash(hash)
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
## Message Predicates
|
|
291
|
-
|
|
292
|
-
These are defined on `Message`, so every `TextMessage`, `ToolCallMessage`, and `ToolResultMessage` responds to all of them. `ToolMessage` responds to none of them:
|
|
293
|
-
|
|
294
|
-
```ruby
|
|
295
|
-
message.text? # Is it a TextMessage?
|
|
296
|
-
message.tool_call? # Is it a ToolCallMessage?
|
|
297
|
-
message.tool_result? # Is it a ToolResultMessage?
|
|
298
|
-
|
|
299
|
-
message.user? # Is role "user"?
|
|
300
|
-
message.assistant? # Is role "assistant"?
|
|
301
|
-
message.system? # Is role "system"?
|
|
302
|
-
|
|
303
|
-
message.stopped? # Is stop_reason "stop"?
|
|
304
|
-
message.tool_stop? # Is stop_reason "tool"?
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
Valid values are constrained at construction: `type` must be one of `text`, `tool_call`, `tool_result`; `role` one of `system`, `user`, `assistant`, `tool_result`; `stop_reason` one of `tool`, `stop`. Anything else raises `ArgumentError`.
|
|
308
|
-
|
|
309
|
-
## Creating Messages
|
|
310
|
-
|
|
311
|
-
### From Strings
|
|
312
|
-
|
|
313
|
-
```ruby
|
|
314
|
-
TextMessage.new(role: "user", content: "Hello")
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### From Hashes
|
|
318
|
-
|
|
319
|
-
```ruby
|
|
320
|
-
Message.from_hash(
|
|
321
|
-
type: "text",
|
|
322
|
-
role: "user",
|
|
323
|
-
content: "Hello"
|
|
324
|
-
)
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
## Serialization
|
|
328
|
-
|
|
329
|
-
Messages can be serialized:
|
|
330
|
-
|
|
331
|
-
```ruby
|
|
332
|
-
# To hash
|
|
333
|
-
hash = message.to_h
|
|
334
|
-
#=> { type: "text", role: "user", content: "Hello" }
|
|
335
|
-
|
|
336
|
-
# To JSON
|
|
337
|
-
json = message.to_json
|
|
338
|
-
|
|
339
|
-
# From hash
|
|
340
|
-
message = Message.from_hash(hash)
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Template Resolution
|
|
344
|
-
|
|
345
|
-
When a robot has a template, it is resolved at build time via prompt_manager:
|
|
346
|
-
|
|
347
|
-
```ruby
|
|
348
|
-
robot = RobotLab.build(
|
|
349
|
-
name: "helper",
|
|
350
|
-
template: :helper,
|
|
351
|
-
context: { tone: "friendly" }
|
|
352
|
-
)
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
The template resolution process:
|
|
356
|
-
1. `PM.parse(:helper)` loads the template file from the configured prompts directory
|
|
357
|
-
2. Non-LLM front matter (`robot_name`, `description`, `tools`, `mcp`, `skills`) is applied to the robot, filling in only what the constructor did not provide
|
|
358
|
-
3. The LLM front-matter keys become a `RunConfig` that the robot's own `@config` merges over — front matter is the base, constructor kwargs win — and `apply_to` dispatches `chat.with_<field>` for each field the chat supports. In practice only `model` and `temperature` reach the chat this way; `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, and `stop` are parsed and silently dropped because `RubyLLM::Chat` defines no `with_` method for them. Set those via constructor kwargs instead
|
|
359
|
-
4. The template body is rendered with ERB (`<%= var %>`; `{{ var }}` is not interpolated) and set as system instructions via `@chat.with_instructions(rendered)`
|
|
360
|
-
|
|
361
|
-
If both `template:` and `system_prompt:` are provided, the system prompt is appended to the rendered template, producing one combined system message — and it is re-appended on every template re-render, so supplying run-time context never silently drops it.
|
|
362
|
-
|
|
363
|
-
## Next Steps
|
|
364
|
-
|
|
365
|
-
- [Memory Management](state-management.md) - How memory stores conversation data
|
|
366
|
-
- [Network Orchestration](network-orchestration.md) - Multi-robot pipeline execution
|