Class: WritersRoom::Room

Inherits:
Object
  • Object
show all
Defined in:
lib/writers_room/room.rb

Overview

Scene container that holds the bus, shared memory, and actor roster.

Modeled on RobotLab example 16's Room pattern: actors communicate through a shared bus channel, use shared memory for persistence, and the Room coordinates completion detection.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(display:, config:, scene_info:) ⇒ Room

Returns a new instance of Room.

Parameters:

  • display (Display)

    terminal output formatter

  • config (RobotLab::RunConfig)

    shared LLM configuration

  • scene_info (Hash)

    scene metadata (scene_name, characters, etc.)



16
17
18
19
20
21
22
23
24
25
26
# File 'lib/writers_room/room.rb', line 16

def initialize(display:, config:, scene_info:)
  @bus     = TypedBus::MessageBus.new
  @memory  = RobotLab::Memory.new(enable_cache: false)
  @display = display
  @config  = config
  @scene_info = scene_info
  @actors  = {}

  # Shared broadcast channel for the scene
  @bus.add_channel(:scene)
end

Instance Attribute Details

#actorsObject (readonly)

Returns the value of attribute actors.



11
12
13
# File 'lib/writers_room/room.rb', line 11

def actors
  @actors
end

#busObject (readonly)

Returns the value of attribute bus.



11
12
13
# File 'lib/writers_room/room.rb', line 11

def bus
  @bus
end

#configObject (readonly)

Returns the value of attribute config.



11
12
13
# File 'lib/writers_room/room.rb', line 11

def config
  @config
end

#displayObject (readonly)

Returns the value of attribute display.



11
12
13
# File 'lib/writers_room/room.rb', line 11

def display
  @display
end

#memoryObject (readonly)

Returns the value of attribute memory.



11
12
13
# File 'lib/writers_room/room.rb', line 11

def memory
  @memory
end

#scene_infoObject (readonly)

Returns the value of attribute scene_info.



11
12
13
# File 'lib/writers_room/room.rb', line 11

def scene_info
  @scene_info
end

Instance Method Details

#add_actor(name, character_info:) ⇒ Actor

Add an actor to the room.

Parameters:

  • name (String)

    character name

  • character_info (Hash)

    character metadata from file

Returns:

  • (Actor)

    the created actor



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/writers_room/room.rb', line 33

def add_actor(name, character_info:)
  actor = Actor.new(
    character_info: character_info,
    scene_info:     @scene_info,
    bus:            @bus,
    shared_memory:  @memory,
    display:        @display,
    room:           self,
    config:         @config
  )
  @actors[name] = actor
  actor
end

#assemble_transcriptArray<Hash>

Assemble the transcript from shared memory.

Returns:

  • (Array<Hash>)

    dialog entries



114
115
116
# File 'lib/writers_room/room.rb', line 114

def assemble_transcript
  @memory.get(:dialog_history) || []
end

#seed(opening_prompt) ⇒ Object

Seed the scene by publishing the opening prompt from the "director". Uses a director identity so actors don't filter it as their own message.

Parameters:

  • opening_prompt (String)

    initial context to send



51
52
53
54
55
56
57
58
59
60
# File 'lib/writers_room/room.rb', line 51

def seed(opening_prompt)
  return if @actors.empty?

  message = RobotLab::RobotMessage.build(
    id: 0,
    from: "director",
    content: opening_prompt
  )
  Async { @bus.publish(:scene, message) }
end

#shutdownObject

Disconnect all actors and close the bus.



119
120
121
122
123
124
125
126
127
# File 'lib/writers_room/room.rb', line 119

def shutdown
  @actors.each_value do |actor|
    actor.disconnect
    @display.info("  - #{actor.name} has left the stage")
  end
  @bus.close_all
  @actors.clear
  @display.close
end

#wait_for_completion(timeout: 300, max_lines: 50, poll_interval: 2, heartbeat_interval: 30) ⇒ Boolean

Wait for the scene to complete.

Parameters:

  • timeout (Integer) (defaults to: 300)

    max seconds to wait

  • max_lines (Integer) (defaults to: 50)

    max dialog lines before auto-complete

  • poll_interval (Integer) (defaults to: 2)

    seconds between polls

  • heartbeat_interval (Integer) (defaults to: 30)

    seconds between heartbeats

Returns:

  • (Boolean)

    true if completed, false if timed out



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/writers_room/room.rb', line 69

def wait_for_completion(timeout: 300, max_lines: 50, poll_interval: 2, heartbeat_interval: 30)
  deadline = Time.now + timeout
  last_heartbeat = Time.now
  @last_line_count = 0

  loop do
    # Check if scene was marked complete by an actor
    if @memory.key?(:scene_complete)
      @display.director_note("Scene complete!")
      return true
    end

    # Check line count safety net
    history = @memory.get(:dialog_history) || []
    if history.length >= max_lines
      @display.director_note("Maximum lines reached (#{max_lines})")
      @memory.set(:scene_complete, true)
      return true
    end

    # Check timeout
    if Time.now > deadline
      @display.director_note("Scene timeout reached (#{timeout}s)")
      return false
    end

    # Heartbeat: display status and nudge actors if dialog has stalled
    if Time.now - last_heartbeat >= heartbeat_interval
      send_heartbeat(history.length, max_lines)

      if history.length == @last_line_count
        send_director_nudge(history)
      end

      @last_line_count = history.length
      last_heartbeat = Time.now
    end

    sleep poll_interval
  end
end