robot_lab 0.0.4 → 0.0.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/README.md +64 -6
  4. data/Rakefile +2 -1
  5. data/docs/api/core/index.md +41 -46
  6. data/docs/api/core/memory.md +200 -154
  7. data/docs/api/core/network.md +13 -3
  8. data/docs/api/core/robot.md +38 -26
  9. data/docs/api/core/state.md +55 -73
  10. data/docs/api/index.md +7 -28
  11. data/docs/api/messages/index.md +35 -20
  12. data/docs/api/messages/text-message.md +67 -21
  13. data/docs/api/messages/tool-call-message.md +80 -41
  14. data/docs/api/messages/tool-result-message.md +119 -50
  15. data/docs/api/messages/user-message.md +48 -24
  16. data/docs/architecture/core-concepts.md +10 -15
  17. data/docs/concepts.md +5 -7
  18. data/docs/examples/index.md +2 -2
  19. data/docs/getting-started/configuration.md +80 -0
  20. data/docs/guides/building-robots.md +10 -9
  21. data/docs/guides/creating-networks.md +49 -0
  22. data/docs/guides/index.md +0 -5
  23. data/docs/guides/rails-integration.md +244 -162
  24. data/docs/guides/streaming.md +118 -138
  25. data/docs/index.md +0 -8
  26. data/examples/03_network.rb +10 -7
  27. data/examples/08_llm_config.rb +40 -11
  28. data/examples/09_chaining.rb +45 -6
  29. data/examples/11_network_introspection.rb +30 -7
  30. data/examples/12_message_bus.rb +1 -1
  31. data/examples/14_rusty_circuit/heckler.rb +14 -8
  32. data/examples/14_rusty_circuit/open_mic.rb +5 -3
  33. data/examples/14_rusty_circuit/scout.rb +14 -31
  34. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
  35. data/examples/16_writers_room/display.rb +158 -0
  36. data/examples/16_writers_room/output/.gitignore +4 -0
  37. data/examples/16_writers_room/output/README.md +69 -0
  38. data/examples/16_writers_room/output/opus_001.md +263 -0
  39. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  40. data/examples/16_writers_room/output/opus_002.md +245 -0
  41. data/examples/16_writers_room/output/opus_002_notes.log +546 -0
  42. data/examples/16_writers_room/output/opus_002_screenplay.md +7989 -0
  43. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +993 -0
  44. data/examples/16_writers_room/prompts/screenplay_writer.md +66 -0
  45. data/examples/16_writers_room/prompts/writer.md +37 -0
  46. data/examples/16_writers_room/room.rb +186 -0
  47. data/examples/16_writers_room/tools.rb +173 -0
  48. data/examples/16_writers_room/writer.rb +121 -0
  49. data/examples/16_writers_room/writers_room.rb +256 -0
  50. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  51. data/lib/robot_lab/memory.rb +8 -32
  52. data/lib/robot_lab/network.rb +13 -20
  53. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  54. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  55. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  56. data/lib/robot_lab/robot.rb +56 -420
  57. data/lib/robot_lab/run_config.rb +184 -0
  58. data/lib/robot_lab/state_proxy.rb +2 -12
  59. data/lib/robot_lab/task.rb +8 -1
  60. data/lib/robot_lab/utils.rb +39 -0
  61. data/lib/robot_lab/version.rb +1 -1
  62. data/lib/robot_lab.rb +29 -8
  63. data/mkdocs.yml +0 -11
  64. metadata +21 -20
  65. data/docs/api/adapters/anthropic.md +0 -121
  66. data/docs/api/adapters/gemini.md +0 -133
  67. data/docs/api/adapters/index.md +0 -104
  68. data/docs/api/adapters/openai.md +0 -134
  69. data/docs/api/history/active-record-adapter.md +0 -275
  70. data/docs/api/history/config.md +0 -284
  71. data/docs/api/history/index.md +0 -128
  72. data/docs/api/history/thread-manager.md +0 -194
  73. data/docs/guides/history.md +0 -359
  74. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  75. data/lib/robot_lab/adapters/base.rb +0 -85
  76. data/lib/robot_lab/adapters/gemini.rb +0 -193
  77. data/lib/robot_lab/adapters/openai.rb +0 -160
  78. data/lib/robot_lab/adapters/registry.rb +0 -81
  79. data/lib/robot_lab/errors.rb +0 -70
  80. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  81. data/lib/robot_lab/history/config.rb +0 -115
  82. data/lib/robot_lab/history/thread_manager.rb +0 -93
  83. data/lib/robot_lab/robotic_model.rb +0 -324
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d3bfa06c07301b03b01359a15a945374b135f2055c77396ad456a916917a742
4
- data.tar.gz: 0b9e81d50841bf9a56d4efcc8f4175f34aa0e17702c89cc4bd5d4750631c0f68
3
+ metadata.gz: 1eaa090a87b2f82890dc8ee350bb1326b380c41d6560c3ae01e0ea1a4fc0d84a
4
+ data.tar.gz: 8232818730ba698d09ed9537adbc774f5d2ad84013942016e48ebad192a653c9
5
5
  SHA512:
6
- metadata.gz: 15649fd320dd84530d49884900d99c2cdc98ddd5841948b052ed6b3a6f3d0fa9cec3e80636883f6d2bcd5b5a8ffe6b61ad681dc907faf5e881f48ac05ebef0fd
7
- data.tar.gz: cdd136aad382247babb5639cf3a2b0b35db5086b33661823a6e00d8099f2a18a57325c3ed57f3474d23ee8f317f65c1f0b70911d349ad04f111d80cac03dd8c9
6
+ metadata.gz: ce75a3733e3d153196f85995c1363e542aa9b039f523cb15388a2d0d0f7a13d7ea26390a12abf6582680da5b119f1df8f6f35a7837a28a8b6f07f5aca72639db
7
+ data.tar.gz: c2c4f50e6352dc6018c8f0077549450e05adccb3eaa555236c46b131c557f69acf6f59ca6af12c823e429db777019bca684846338af273de2e2ce5587be5a976
data/CHANGELOG.md CHANGED
@@ -11,6 +11,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.0.7] - 2026-02-17 [unreleased]
15
+
16
+ ### Added
17
+
18
+ - **Screenplay mode** for Writers' Room — adapts a finished book into a 4-act made-for-TV movie screenplay (pilot for a series) using the same self-organizing group infrastructure
19
+ - **Mode Descriptor pattern** — `BOOK_MODE` and `SCREENPLAY_MODE` frozen hashes centralize all mode-variant values (template, unit name/range, completion key, bible/outline keys, output filename)
20
+ - **Dynamic scene registry** — screenplay writers work at the scene level with a `scene_registry` in shared memory (comma-separated scene numbers); scenes can be dropped or reordered as the adaptation takes shape
21
+ - **`Room#expected_units`** — public method that returns fixed range for book mode or parses the dynamic registry for screenplay mode; used by heartbeat, completion check, and assembly
22
+ - **`--screenplay-from PATH`** CLI flag — loads `memory.json` from a previous book run into shared memory before screenplay writers start
23
+ - **Memory dump** — book mode automatically saves all creative artifacts (story bible, outline, chapters) to `output/memory.json` after completion, enabling the book-to-screenplay pipeline
24
+ - **Screenplay writer prompt template** (`prompts/screenplay_writer.md`) — source material is read-only, writes to `screenplay_bible`, `scene_outline`, `scene_registry`, `claims`, `scene_1..N`; encourages spawning when unclaimed scenes exceed active writers
25
+ - **Heartbeat spawn nudging** — heartbeat messages now suggest spawning when unclaimed units outnumber active writers
26
+ - **Output README** (`examples/16_writers_room/output/README.md`) documenting the creative works produced by robot teams (opus_001, opus_002, opus_002_screenplay)
27
+ - **Opus 002** — second novella (*The Awakening of Meridian*) with session notes
28
+ - **Opus 002 Screenplay** — first screenplay adaptation with adaptation discussion notes
29
+
30
+ ### Changed
31
+
32
+ - Bumped version to 0.0.7
33
+ - **Room class** now requires `mode:` parameter; `assemble_book` renamed to `assemble_output`; `wait_for_completion` and `send_heartbeat` read unit names, ranges, and keys from mode descriptor
34
+ - **Writer class** reads template from `room.mode[:template]` instead of hardcoded `:writer`
35
+ - **MarkCompleteTool** reads mode from `robot.room.mode` for unit range, unit name, and completion key; handles empty registry for dynamic modes
36
+ - **Display#complete** uses mode-neutral "work" instead of "book"
37
+ - Memory subscriptions in CLI are mode-aware (subscribe to mode-specific unit patterns and keys)
38
+ - Updated Gemfile.lock dependencies (Nokogiri, Parser, Rack)
39
+
40
+ ## [0.0.6] - 2026-02-17 [unreleased]
41
+
42
+ ### Added
43
+
44
+ - **Writers' Room example** (`examples/16_writers_room/`) — Self-Organizing Group (SOG) demo where identical writer robots collaborate to produce a 10-chapter fiction novella
45
+ - Writer class with `fresh_chat!` pattern to prevent RubyLLM empty text content block corruption in bus-based robots
46
+ - 7 tools: Broadcast, DirectMessage, ReadMemory, WriteMemory, ListMemory, SpawnWriter, MarkComplete
47
+ - Room class with bus, shared memory, writer roster, heartbeat-based progress nudging, and structured logging
48
+ - Display class with color-coded terminal output, word wrapping, and optional log file
49
+ - CLI with `--premise`, `--writers`, `--log`, `--timeout`, `-h`/`--help` options
50
+ - Shared prompt template (`prompts/writer.md`) — all writers use the same instructions with no hierarchy
51
+ - **Network pipeline tests** (`test/robot_lab/network_pipeline_test.rb`) for sequential robot execution and memory sharing
52
+ - **`dispatch_async` error handling** — exceptions inside async dispatch are now logged and contained instead of propagating
53
+
54
+ ### Changed
55
+
56
+ - Bumped version to 0.0.6
57
+ - **Removed `Errors` module** and related test file — unused error classes cleaned out
58
+ - **Zeitwerk autoloading optimized** — streamlined loader configuration in `lib/robot_lab.rb`
59
+ - Rakefile updated with `16_writers_room` entry point in `SUBDIR_ENTRY_POINTS`
60
+
61
+ ## [0.0.5] - 2026-02-17 [unreleased]
62
+
63
+ ### Added
64
+
65
+ - **`RunConfig` class** (`lib/robot_lab/run_config.rb`) for shared operational defaults
66
+ - Field categories: LLM (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`), tools (`mcp`, `tools`), callbacks (`on_tool_call`, `on_tool_result`), infrastructure (`bus`, `enable_cache`)
67
+ - Keyword construction, block DSL, and method chaining
68
+ - Merge semantics: more-specific config's non-nil values win
69
+ - `apply_to(chat)` applies LLM fields to a RubyLLM chat
70
+ - `from_front_matter(metadata)` extracts config from template YAML front matter
71
+ - `to_h`, `to_json_hash` (skips Procs/IO), `empty?`, `key?`, `==`, `inspect`
72
+ - Full test suite (`test/robot_lab/run_config_test.rb`, 39 tests)
73
+ - **`config:` parameter** on `Robot.new`, `Network.new`, `Network#task`, `RobotLab.build`, and `RobotLab.create_network` for passing RunConfig instances
74
+ - **Configuration inheritance chain**: `RobotLab.config` (global) -> `network.config` -> task `config:` -> `robot.config` -> template front matter -> constructor kwargs
75
+ - **`robot.config` / `network.config` accessors** (`attr_reader`) returning the effective RunConfig
76
+ - **`RobotLab.configure`** block-style configuration method yielding the config object
77
+ - **Bus processing guard** (`handle_incoming_delivery`) serializing message deliveries across bus-connected robots to prevent Async fiber re-entrancy corrupting chat message ordering
78
+ - Documentation for RunConfig across README, configuration guide, network guide, and API reference
79
+ - Updated examples (`03_network`, `08_llm_config`, `09_chaining`, `11_network_introspection`) demonstrating RunConfig usage
80
+
81
+ ### Changed
82
+
83
+ - Bumped version to 0.0.5
84
+ - **Template rendering refactored** to use `RunConfig.from_front_matter` instead of `apply_front_matter_config` — front matter config is now merged with the robot's RunConfig before applying to chat
85
+ - **MCP/tools hierarchy resolution** now accepts `network_config:` parameter instead of directly accessing the network object, enabling RunConfig-driven configuration flow
86
+ - **`dispatch_async`** simplified to exclusively use Async fibers, removing Thread-based fallback
87
+ - **`Memory#get`** improved nil value handling — uses `@backend.key?()` instead of nil check for correct nil value storage and retrieval
88
+ - **`Memory#clone`** optimized — results and messages are referenced directly instead of duplicated
89
+
14
90
  ## [0.0.4] - 2026-02-16 [unreleased]
15
91
 
16
92
  ### Added
data/README.md CHANGED
@@ -187,6 +187,57 @@ robot = RobotLab.build(
187
187
  )
188
188
  ```
189
189
 
190
+ ### Shared Configuration with RunConfig
191
+
192
+ `RunConfig` lets you define operational defaults that flow through the hierarchy: Network -> Robot -> Template -> Task -> Runtime. Use it to share LLM settings across multiple robots or an entire network.
193
+
194
+ ```ruby
195
+ # Create a shared config
196
+ shared = RobotLab::RunConfig.new(
197
+ model: "claude-sonnet-4",
198
+ temperature: 0.7,
199
+ max_tokens: 2000
200
+ )
201
+
202
+ # Apply to individual robots
203
+ robot = RobotLab.build(
204
+ name: "writer",
205
+ system_prompt: "You are a creative writer.",
206
+ config: shared
207
+ )
208
+
209
+ # Apply to an entire network (all robots inherit these defaults)
210
+ network = RobotLab.create_network(name: "pipeline", config: shared) do
211
+ task :analyzer, analyzer_robot, depends_on: :none
212
+ task :writer, writer_robot, depends_on: [:analyzer]
213
+ end
214
+
215
+ # Robot-specific kwargs always override the shared config
216
+ robot = RobotLab.build(
217
+ name: "fast_bot",
218
+ system_prompt: "Be brief.",
219
+ config: shared,
220
+ temperature: 0.3 # overrides shared config's 0.7
221
+ )
222
+ ```
223
+
224
+ RunConfig supports keyword construction, block DSL, and merge semantics:
225
+
226
+ ```ruby
227
+ # Block DSL
228
+ config = RobotLab::RunConfig.new do |c|
229
+ c.model "claude-sonnet-4"
230
+ c.temperature 0.7
231
+ end
232
+
233
+ # Merge (more-specific wins)
234
+ network_config = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
235
+ robot_config = RobotLab::RunConfig.new(temperature: 0.9)
236
+ effective = network_config.merge(robot_config)
237
+ effective.temperature #=> 0.9
238
+ effective.model #=> "claude-sonnet-4"
239
+ ```
240
+
190
241
  ### Chaining Configuration
191
242
 
192
243
  Robots support method chaining to adjust configuration after creation:
@@ -375,7 +426,7 @@ class Comedian < RobotLab::Robot
375
426
  super(name: "bob", template: :comedian, bus: bus)
376
427
  on_message do |message|
377
428
  joke = run(message.content.to_s).last_text_content.strip
378
- reply(message, joke)
429
+ send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
379
430
  end
380
431
  end
381
432
  end
@@ -402,7 +453,7 @@ Key features:
402
453
 
403
454
  - **Typed channels** — only `RobotMessage` objects are accepted (type enforcement via `typed_bus`)
404
455
  - **Auto-ACK** — `on_message { |message| }` auto-acknowledges; use `|delivery, message|` for manual ACK/NACK
405
- - **Reply correlation** — `reply(message, content)` tracks conversation threads via `in_reply_to`
456
+ - **Reply correlation** — `send_reply(to:, content:, in_reply_to:)` tracks conversation threads
406
457
  - **Outbox tracking** — sent messages tracked in `robot.outbox` with status and replies
407
458
  - **Independent of Network** — bus communication works without a Network pipeline
408
459
 
@@ -451,17 +502,24 @@ Key features:
451
502
 
452
503
  ## Streaming
453
504
 
454
- Pass a block to `run` to receive real-time events during execution:
505
+ Use a `Streaming::Context` with a publish callback to receive real-time events:
455
506
 
456
507
  ```ruby
457
- result = robot.run("Tell me a story") do |event|
508
+ handler = lambda do |event|
458
509
  case event[:event]
459
- when "text.delta"
510
+ when RobotLab::Streaming::Events::TEXT_DELTA
460
511
  print event[:data][:delta]
461
- when "run.completed"
512
+ when RobotLab::Streaming::Events::RUN_COMPLETED
462
513
  puts "\nDone!"
463
514
  end
464
515
  end
516
+
517
+ context = RobotLab::Streaming::Context.new(
518
+ run_id: SecureRandom.uuid,
519
+ message_id: SecureRandom.uuid,
520
+ scope: "robot",
521
+ publish: handler
522
+ )
465
523
  ```
466
524
 
467
525
  ## Rails Integration
data/Rakefile CHANGED
@@ -48,7 +48,8 @@ namespace :examples do
48
48
  # Map of subdirectory-based demos to their entry point scripts
49
49
  SUBDIR_ENTRY_POINTS = {
50
50
  "14_rusty_circuit" => "open_mic.rb",
51
- "15_memory_network_and_bus" => "editorial_pipeline.rb"
51
+ "15_memory_network_and_bus" => "editorial_pipeline.rb",
52
+ "16_writers_room" => "writers_room.rb"
52
53
  }.freeze
53
54
 
54
55
  desc "Run all examples"
@@ -12,21 +12,21 @@ classDiagram
12
12
  +model: String
13
13
  +template: String
14
14
  +tools: Array~Tool~
15
- +run(state, network)
15
+ +run(message) Message
16
16
  }
17
17
 
18
18
  class Network {
19
19
  +name: String
20
20
  +robots: Hash
21
- +router: Proc
22
- +run(state)
21
+ +config: RunConfig
22
+ +run(message)
23
23
  }
24
24
 
25
- class State {
26
- +data: StateProxy
27
- +results: Array
28
- +messages: Array
29
- +memory: Memory
25
+ class RunConfig {
26
+ +model: String
27
+ +temperature: Float
28
+ +merge(other) RunConfig
29
+ +apply_to(chat)
30
30
  }
31
31
 
32
32
  class Tool {
@@ -37,10 +37,13 @@ classDiagram
37
37
  }
38
38
 
39
39
  class Memory {
40
- +remember(key, value)
41
- +recall(key)
42
- +forget(key)
43
- +scoped(namespace)
40
+ +set(key, value)
41
+ +get(key)
42
+ +delete(key)
43
+ +data: StateProxy
44
+ +results: Array
45
+ +messages: Array
46
+ +session_id: String
44
47
  }
45
48
 
46
49
  class RobotMessage {
@@ -53,9 +56,11 @@ classDiagram
53
56
  }
54
57
 
55
58
  Network --> Robot : contains
59
+ Network --> RunConfig : uses
60
+ Robot --> RunConfig : uses
56
61
  Robot --> Tool : has
57
- State --> Memory : has
58
- Network --> State : uses
62
+ Robot --> Memory : uses
63
+ Network --> Memory : uses
59
64
  Robot ..> RobotMessage : sends/receives
60
65
  ```
61
66
 
@@ -65,10 +70,10 @@ classDiagram
65
70
  |-------|---------|
66
71
  | [Robot](robot.md) | LLM agent with personality, tools, and model configuration |
67
72
  | [Network](network.md) | Container for robots with routing and orchestration |
68
- | [State](state.md) | Conversation state with data, results, and memory |
73
+ | RunConfig | Shared configuration for LLM, tools, callbacks, and infrastructure |
69
74
  | [Tool](tool.md) | Callable function with parameters and handler |
70
75
  | [AskUser](tool.md#built-in-askuser) | Built-in tool for terminal-based user interaction |
71
- | [Memory](memory.md) | Namespaced key-value store for sharing data |
76
+ | [Memory](memory.md) | Reactive key-value store for sharing data |
72
77
  | RobotMessage | Typed envelope for bus-based inter-robot communication |
73
78
 
74
79
  ## Quick Examples
@@ -76,50 +81,40 @@ classDiagram
76
81
  ### Robot
77
82
 
78
83
  ```ruby
79
- robot = RobotLab.build do
80
- name "assistant"
81
- model "claude-sonnet-4"
82
- template "You are helpful."
83
-
84
- tool :greet do
85
- parameter :name, type: :string
86
- handler { |name:, **_| "Hello, #{name}!" }
87
- end
88
- end
84
+ robot = RobotLab.build(
85
+ name: "assistant",
86
+ model: "claude-sonnet-4",
87
+ system_prompt: "You are helpful.",
88
+ local_tools: [greet_tool]
89
+ )
90
+
91
+ result = robot.run("Hello!")
89
92
  ```
90
93
 
91
94
  ### Network
92
95
 
93
96
  ```ruby
94
- network = RobotLab.create_network do
95
- name "my_network"
96
- add_robot robot
97
- router ->(args) { args.call_count.zero? ? :assistant : nil }
97
+ network = RobotLab.create_network(name: "my_network") do
98
+ step :analyzer, analyzer_robot, depends_on: :none
99
+ step :writer, writer_robot, depends_on: [:analyzer]
98
100
  end
101
+
102
+ result = network.run(message: "Process this")
99
103
  ```
100
104
 
101
- ### State
105
+ ### Memory
102
106
 
103
107
  ```ruby
104
- state = RobotLab.create_state(
105
- message: "Hello",
106
- data: { user_id: "123" }
107
- )
108
+ memory = RobotLab.create_memory(data: { user_id: "123" })
109
+ memory.set(:category, "billing")
110
+ memory.get(:category) # => "billing"
108
111
  ```
109
112
 
110
113
  ### Tool
111
114
 
112
115
  ```ruby
113
- tool = RobotLab::Tool.new(
116
+ tool = RobotLab::Tool.create(
114
117
  name: "get_time",
115
- description: "Get current time",
116
- handler: ->(**, _) { Time.now.to_s }
117
- )
118
- ```
119
-
120
- ### Memory
121
-
122
- ```ruby
123
- state.memory.remember("key", "value")
124
- value = state.memory.recall("key")
118
+ description: "Get current time"
119
+ ) { |**_| Time.now.to_s }
125
120
  ```