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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -0
- data/README.md +64 -6
- data/Rakefile +2 -1
- data/docs/api/core/index.md +41 -46
- data/docs/api/core/memory.md +200 -154
- data/docs/api/core/network.md +13 -3
- data/docs/api/core/robot.md +38 -26
- data/docs/api/core/state.md +55 -73
- data/docs/api/index.md +7 -28
- data/docs/api/messages/index.md +35 -20
- data/docs/api/messages/text-message.md +67 -21
- data/docs/api/messages/tool-call-message.md +80 -41
- data/docs/api/messages/tool-result-message.md +119 -50
- data/docs/api/messages/user-message.md +48 -24
- data/docs/architecture/core-concepts.md +10 -15
- data/docs/concepts.md +5 -7
- data/docs/examples/index.md +2 -2
- data/docs/getting-started/configuration.md +80 -0
- data/docs/guides/building-robots.md +10 -9
- data/docs/guides/creating-networks.md +49 -0
- data/docs/guides/index.md +0 -5
- data/docs/guides/rails-integration.md +244 -162
- data/docs/guides/streaming.md +118 -138
- data/docs/index.md +0 -8
- data/examples/03_network.rb +10 -7
- data/examples/08_llm_config.rb +40 -11
- data/examples/09_chaining.rb +45 -6
- data/examples/11_network_introspection.rb +30 -7
- data/examples/12_message_bus.rb +1 -1
- data/examples/14_rusty_circuit/heckler.rb +14 -8
- data/examples/14_rusty_circuit/open_mic.rb +5 -3
- data/examples/14_rusty_circuit/scout.rb +14 -31
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
- data/examples/16_writers_room/display.rb +158 -0
- data/examples/16_writers_room/output/.gitignore +4 -0
- data/examples/16_writers_room/output/README.md +69 -0
- data/examples/16_writers_room/output/opus_001.md +263 -0
- data/examples/16_writers_room/output/opus_001_notes.log +470 -0
- data/examples/16_writers_room/output/opus_002.md +245 -0
- data/examples/16_writers_room/output/opus_002_notes.log +546 -0
- data/examples/16_writers_room/output/opus_002_screenplay.md +7989 -0
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +993 -0
- data/examples/16_writers_room/prompts/screenplay_writer.md +66 -0
- data/examples/16_writers_room/prompts/writer.md +37 -0
- data/examples/16_writers_room/room.rb +186 -0
- data/examples/16_writers_room/tools.rb +173 -0
- data/examples/16_writers_room/writer.rb +121 -0
- data/examples/16_writers_room/writers_room.rb +256 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
- data/lib/robot_lab/memory.rb +8 -32
- data/lib/robot_lab/network.rb +13 -20
- data/lib/robot_lab/robot/bus_messaging.rb +239 -0
- data/lib/robot_lab/robot/mcp_management.rb +88 -0
- data/lib/robot_lab/robot/template_rendering.rb +130 -0
- data/lib/robot_lab/robot.rb +56 -420
- data/lib/robot_lab/run_config.rb +184 -0
- data/lib/robot_lab/state_proxy.rb +2 -12
- data/lib/robot_lab/task.rb +8 -1
- data/lib/robot_lab/utils.rb +39 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +29 -8
- data/mkdocs.yml +0 -11
- metadata +21 -20
- data/docs/api/adapters/anthropic.md +0 -121
- data/docs/api/adapters/gemini.md +0 -133
- data/docs/api/adapters/index.md +0 -104
- data/docs/api/adapters/openai.md +0 -134
- data/docs/api/history/active-record-adapter.md +0 -275
- data/docs/api/history/config.md +0 -284
- data/docs/api/history/index.md +0 -128
- data/docs/api/history/thread-manager.md +0 -194
- data/docs/guides/history.md +0 -359
- data/lib/robot_lab/adapters/anthropic.rb +0 -163
- data/lib/robot_lab/adapters/base.rb +0 -85
- data/lib/robot_lab/adapters/gemini.rb +0 -193
- data/lib/robot_lab/adapters/openai.rb +0 -160
- data/lib/robot_lab/adapters/registry.rb +0 -81
- data/lib/robot_lab/errors.rb +0 -70
- data/lib/robot_lab/history/active_record_adapter.rb +0 -146
- data/lib/robot_lab/history/config.rb +0 -115
- data/lib/robot_lab/history/thread_manager.rb +0 -93
- data/lib/robot_lab/robotic_model.rb +0 -324
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
# own jokes using the comedian as the punch line.
|
|
12
12
|
#
|
|
13
13
|
# Subscribes to the :room channel to hear performances.
|
|
14
|
+
# Room deliveries are routed through the core processing guard,
|
|
15
|
+
# which serializes run() calls to prevent Async fiber interleaving.
|
|
14
16
|
# Sends feedback directly to the comic's personal channel.
|
|
15
17
|
#
|
|
16
18
|
class Heckler < RobotLab::Robot
|
|
@@ -23,10 +25,10 @@ class Heckler < RobotLab::Robot
|
|
|
23
25
|
|
|
24
26
|
super(name: "heckler", template: :open_mic_heckler, bus: bus)
|
|
25
27
|
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
# Handle incoming messages — the core processing guard
|
|
29
|
+
# serializes all deliveries, preventing concurrent run()
|
|
30
|
+
# calls from corrupting chat history.
|
|
31
|
+
on_message do |message|
|
|
30
32
|
next unless message.from == "comic"
|
|
31
33
|
next if @rounds >= MAX_ROUNDS
|
|
32
34
|
|
|
@@ -39,9 +41,7 @@ class Heckler < RobotLab::Robot
|
|
|
39
41
|
).reply.strip
|
|
40
42
|
|
|
41
43
|
# The heckler chose silence — no output, no feedback
|
|
42
|
-
if verdict.match?(/\[SILENCE\]/i)
|
|
43
|
-
next
|
|
44
|
-
end
|
|
44
|
+
next if verdict.match?(/\[SILENCE\]/i)
|
|
45
45
|
|
|
46
46
|
@display.heckler("Heckler [Round #{@rounds}]", verdict)
|
|
47
47
|
|
|
@@ -51,7 +51,13 @@ class Heckler < RobotLab::Robot
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# Send feedback to comic's personal channel until the set is done
|
|
54
|
-
|
|
54
|
+
send_reply(to: message.from.to_sym, content: verdict, in_reply_to: message.key) if @rounds < MAX_ROUNDS
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Listen to the room for the comic's performances.
|
|
58
|
+
# Route through the core processing guard.
|
|
59
|
+
@bus.subscribe(:room) do |delivery|
|
|
60
|
+
handle_incoming_delivery(delivery)
|
|
55
61
|
end
|
|
56
62
|
end
|
|
57
63
|
end
|
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
# Communication uses a shared :room channel — the comic publishes
|
|
24
24
|
# performances there, and both the heckler and scout subscribe.
|
|
25
25
|
# The heckler sends feedback directly to the comic's personal channel.
|
|
26
|
-
#
|
|
27
|
-
#
|
|
26
|
+
# Room deliveries are routed through the core processing guard
|
|
27
|
+
# (BusMessaging#handle_incoming_delivery), which serializes all
|
|
28
|
+
# run() calls to prevent Async fiber interleaving from corrupting
|
|
29
|
+
# chat history.
|
|
28
30
|
#
|
|
29
31
|
# Style reinventions are injected into the next round's user prompt
|
|
30
32
|
# rather than modifying the chat's system messages, avoiding message
|
|
@@ -96,7 +98,7 @@ display.comic("Comic [Opening]", opening)
|
|
|
96
98
|
# channel, triggering the feedback loop:
|
|
97
99
|
# room → heckler → comic → room → heckler → comic → ...
|
|
98
100
|
# The loop terminates when the heckler stops replying (MAX_ROUNDS).
|
|
99
|
-
# The scout observes each round via :room
|
|
101
|
+
# The scout observes each round via :room, serialized by the core guard.
|
|
100
102
|
comic.send_message(to: :room, content: "OPENING: #{opening}")
|
|
101
103
|
|
|
102
104
|
display.separator
|
|
@@ -69,10 +69,10 @@ end
|
|
|
69
69
|
# analysts when they see something worth examining closely.
|
|
70
70
|
#
|
|
71
71
|
# Subscribes to the :room channel to observe performances.
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# run() calls
|
|
75
|
-
#
|
|
72
|
+
# Room deliveries are routed through the core processing guard
|
|
73
|
+
# (BusMessaging#handle_incoming_delivery), which serializes all
|
|
74
|
+
# run() calls to prevent Async fiber interleaving from corrupting
|
|
75
|
+
# chat history.
|
|
76
76
|
#
|
|
77
77
|
class Scout < RobotLab::Robot
|
|
78
78
|
attr_accessor :log, :analysts_spawned, :pending_criteria, :display
|
|
@@ -81,8 +81,6 @@ class Scout < RobotLab::Robot
|
|
|
81
81
|
@log = []
|
|
82
82
|
@analysts_spawned = 0
|
|
83
83
|
@pending_criteria = nil
|
|
84
|
-
@processing = false
|
|
85
|
-
@observation_queue = []
|
|
86
84
|
@display = display
|
|
87
85
|
|
|
88
86
|
super(
|
|
@@ -95,20 +93,18 @@ class Scout < RobotLab::Robot
|
|
|
95
93
|
]
|
|
96
94
|
)
|
|
97
95
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
@bus.subscribe(:room) do |delivery|
|
|
103
|
-
delivery.ack!
|
|
104
|
-
message = delivery.message
|
|
96
|
+
# Handle incoming messages — the core processing guard
|
|
97
|
+
# serializes all deliveries, preventing concurrent run()
|
|
98
|
+
# calls from corrupting chat history.
|
|
99
|
+
on_message do |message|
|
|
105
100
|
next unless message.from == "comic"
|
|
101
|
+
observe_and_note(message.content.to_s)
|
|
102
|
+
end
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
end
|
|
104
|
+
# Listen to the room for the comic's performances.
|
|
105
|
+
# Route through the core processing guard.
|
|
106
|
+
@bus.subscribe(:room) do |delivery|
|
|
107
|
+
handle_incoming_delivery(delivery)
|
|
112
108
|
end
|
|
113
109
|
end
|
|
114
110
|
|
|
@@ -133,19 +129,6 @@ class Scout < RobotLab::Robot
|
|
|
133
129
|
|
|
134
130
|
private
|
|
135
131
|
|
|
136
|
-
def process_observation(content)
|
|
137
|
-
@processing = true
|
|
138
|
-
|
|
139
|
-
observe_and_note(content)
|
|
140
|
-
|
|
141
|
-
# Drain any observations that arrived while we were processing
|
|
142
|
-
while (queued = @observation_queue.shift)
|
|
143
|
-
observe_and_note(queued)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
@processing = false
|
|
147
|
-
end
|
|
148
|
-
|
|
149
132
|
def observe_and_note(content)
|
|
150
133
|
@log << content
|
|
151
134
|
|
|
@@ -109,7 +109,7 @@ editor.on_message do |message|
|
|
|
109
109
|
puts " Editor [revised]: #{revised[0..120]}..."
|
|
110
110
|
puts " [editor] Revision written to #{path}"
|
|
111
111
|
|
|
112
|
-
editor.
|
|
112
|
+
editor.send_reply(to: message.from.to_sym, content: revised, in_reply_to: message.key)
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
chief = EditorInChief.new(
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rainbow"
|
|
4
|
+
require "io/console"
|
|
5
|
+
|
|
6
|
+
# ── Display ─────────────────────────────────────────────────
|
|
7
|
+
#
|
|
8
|
+
# Terminal formatting for the Writers' Room demo.
|
|
9
|
+
#
|
|
10
|
+
# - Broadcasts: white, prefixed with speaker name
|
|
11
|
+
# - Direct messages: magenta, shows sender → recipient
|
|
12
|
+
# - Memory writes: dimmed annotation
|
|
13
|
+
# - Spawns: green annotation
|
|
14
|
+
# - Completion: bright green
|
|
15
|
+
# - Chapter content: cyan (when assembled)
|
|
16
|
+
#
|
|
17
|
+
class Display
|
|
18
|
+
WRITER_COLORS = %i[cyan yellow blue magenta white].freeze
|
|
19
|
+
|
|
20
|
+
def initialize(log_path: nil)
|
|
21
|
+
@term_width = (IO.console&.winsize&.last || 80)
|
|
22
|
+
@wrap_width = @term_width - 8
|
|
23
|
+
@log_file = log_path ? File.open(log_path, "w") : nil
|
|
24
|
+
@color_map = {}
|
|
25
|
+
@next_color = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# ── Room Messages ──────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
def broadcast(from, text)
|
|
31
|
+
color = color_for(from)
|
|
32
|
+
puts
|
|
33
|
+
puts Rainbow(" [#{from}] (broadcast):").send(color).bright
|
|
34
|
+
wrap(text, @wrap_width).each do |line|
|
|
35
|
+
puts Rainbow(" #{line}").send(color)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
log("\n [#{from}] (broadcast):")
|
|
39
|
+
wrap(text, @wrap_width).each { |line| log(" #{line}") }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def direct_message(from, to, text)
|
|
43
|
+
puts Rainbow(" [#{from} -> #{to}]: #{text[0..100]}").magenta.faint
|
|
44
|
+
log(" [#{from} -> #{to}]: #{text[0..100]}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# ── Memory ─────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
def memory_write(writer, key)
|
|
50
|
+
puts Rainbow(" [memory] #{writer} wrote :#{key}").darkgray
|
|
51
|
+
log(" [memory] #{writer} wrote :#{key}")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ── Lifecycle ──────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
def spawn(requester, name)
|
|
57
|
+
puts Rainbow(" [spawn] #{requester} recruited #{name}").green
|
|
58
|
+
log(" [spawn] #{requester} recruited #{name}")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def complete(writer)
|
|
62
|
+
puts
|
|
63
|
+
puts Rainbow(" [#{writer}] marked the work as COMPLETE").green.bright
|
|
64
|
+
log("\n [#{writer}] marked the work as COMPLETE")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ── Incoming message (from bus, before LLM processes) ──────
|
|
68
|
+
|
|
69
|
+
def incoming(writer, from, content)
|
|
70
|
+
puts Rainbow(" [#{writer}] heard #{from}: #{content.to_s[0..80]}...").darkgray
|
|
71
|
+
log(" [#{writer}] heard #{from}: #{content.to_s[0..80]}...")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ── Chrome ─────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
def banner(text)
|
|
77
|
+
puts
|
|
78
|
+
text.each_line { |line| puts Rainbow(line.chomp).bright }
|
|
79
|
+
puts
|
|
80
|
+
|
|
81
|
+
log("")
|
|
82
|
+
text.each_line { |line| log(line.chomp) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def separator
|
|
86
|
+
puts Rainbow(" #{"─" * (@term_width - 4)}").darkgray
|
|
87
|
+
puts
|
|
88
|
+
log(" #{"─" * 56}")
|
|
89
|
+
log("")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def phase(text)
|
|
93
|
+
puts
|
|
94
|
+
puts Rainbow(" ▸ #{text}").bright
|
|
95
|
+
puts
|
|
96
|
+
log("\n ▸ #{text}\n")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def info(text)
|
|
100
|
+
puts Rainbow(" #{text}").darkgray
|
|
101
|
+
log(" #{text}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def stats(text)
|
|
105
|
+
puts
|
|
106
|
+
text.each_line { |line| puts Rainbow(line.chomp).bright }
|
|
107
|
+
log("")
|
|
108
|
+
text.each_line { |line| log(line.chomp) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def close
|
|
112
|
+
@log_file&.close
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def color_for(name)
|
|
118
|
+
@color_map[name] ||= begin
|
|
119
|
+
c = WRITER_COLORS[@next_color % WRITER_COLORS.size]
|
|
120
|
+
@next_color += 1
|
|
121
|
+
c
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def log(line)
|
|
126
|
+
return unless @log_file
|
|
127
|
+
|
|
128
|
+
@log_file.puts line
|
|
129
|
+
@log_file.flush
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def wrap(text, max_width)
|
|
133
|
+
lines = []
|
|
134
|
+
|
|
135
|
+
text.to_s.each_line do |paragraph|
|
|
136
|
+
paragraph = paragraph.strip
|
|
137
|
+
next(lines << "") if paragraph.empty?
|
|
138
|
+
|
|
139
|
+
words = paragraph.split(/\s+/)
|
|
140
|
+
current = +""
|
|
141
|
+
|
|
142
|
+
words.each do |word|
|
|
143
|
+
if current.empty?
|
|
144
|
+
current = +word
|
|
145
|
+
elsif current.length + 1 + word.length <= max_width
|
|
146
|
+
current << " " << word
|
|
147
|
+
else
|
|
148
|
+
lines << current
|
|
149
|
+
current = +word
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
lines << current unless current.empty?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
lines
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Writers' Room Output
|
|
2
|
+
|
|
3
|
+
This directory contains the creative works produced by the Writers' Room
|
|
4
|
+
self-organizing robot teams (Demo 16). No human wrote any of the prose,
|
|
5
|
+
outlines, or screenplay content — it all emerged from robots coordinating
|
|
6
|
+
through bus messages and shared memory.
|
|
7
|
+
|
|
8
|
+
## The Works
|
|
9
|
+
|
|
10
|
+
### opus_001.md — First Book
|
|
11
|
+
|
|
12
|
+
The first novella ever produced by the Writers' Room. A team of writer
|
|
13
|
+
robots self-organized to create a 10-chapter science fiction story. They
|
|
14
|
+
discussed the premise, built a story bible, outlined the plot, claimed
|
|
15
|
+
chapters, and wrote them — all without orchestration or assigned roles.
|
|
16
|
+
|
|
17
|
+
`opus_001_notes.log` contains the session log from that run.
|
|
18
|
+
|
|
19
|
+
### opus_002.md — Second Book
|
|
20
|
+
|
|
21
|
+
The second novella produced by the Writers' Room. Same process, same
|
|
22
|
+
self-organizing pattern, different story.
|
|
23
|
+
|
|
24
|
+
`opus_002_notes.log` contains the session log.
|
|
25
|
+
|
|
26
|
+
### opus_002_screenplay.md — First Screenplay
|
|
27
|
+
|
|
28
|
+
The first screenplay the robots ever created. After opus_002 was written,
|
|
29
|
+
the Writers' Room was extended with a screenplay mode that adapts a
|
|
30
|
+
finished book into a 4-act made-for-TV movie pilot.
|
|
31
|
+
|
|
32
|
+
The workflow:
|
|
33
|
+
|
|
34
|
+
1. Book mode runs and produces a novella (opus_002.md)
|
|
35
|
+
2. Book mode also dumps all creative artifacts (story bible, outline,
|
|
36
|
+
chapters) to `memory.json`
|
|
37
|
+
3. Screenplay mode is launched with `--screenplay-from memory.json`,
|
|
38
|
+
which reloads that memory into the room before the screenwriters start
|
|
39
|
+
4. The screenwriters read the source material, discuss adaptation choices,
|
|
40
|
+
build a scene outline, and write individual scenes in standard
|
|
41
|
+
screenplay format
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Step 1: Write the book (memory.json is saved automatically)
|
|
45
|
+
bundle exec ruby examples/16_writers_room/writers_room.rb
|
|
46
|
+
|
|
47
|
+
# Step 2: Adapt it to a screenplay
|
|
48
|
+
bundle exec ruby examples/16_writers_room/writers_room.rb \
|
|
49
|
+
--screenplay-from examples/16_writers_room/output/memory.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Screenplay writers work at the scene level — each robot claims and writes
|
|
53
|
+
individual scenes rather than entire acts. They maintain a `scene_registry`
|
|
54
|
+
in shared memory listing all planned scenes, and can drop or reorder
|
|
55
|
+
scenes as the adaptation takes shape. When unclaimed scenes pile up, the
|
|
56
|
+
robots spawn additional writers to help.
|
|
57
|
+
|
|
58
|
+
`opus_002_screenplay_notes.md` is the log of the robots discussing the
|
|
59
|
+
screenplay adaptation process — debating what to keep, what to cut, how
|
|
60
|
+
to restructure the story for screen, and how to handle act breaks.
|
|
61
|
+
|
|
62
|
+
## Working Files
|
|
63
|
+
|
|
64
|
+
These files are generated each run and git-ignored:
|
|
65
|
+
|
|
66
|
+
- `book.md` — latest book mode output
|
|
67
|
+
- `screenplay.md` — latest screenplay mode output
|
|
68
|
+
- `memory.json` — memory dump from the latest book mode run
|
|
69
|
+
- `room.log` — structured log from the Room (timestamps, tool calls, heartbeats)
|