Class: Phronomy::Agent::SharedState

Inherits:
Object
  • Object
show all
Defined in:
lib/phronomy/agent/shared_state.rb

Overview

Implements the "Shared state" coordination pattern (Anthropic blog, Pattern 5).

Multiple peer agents collaborate through a shared KnowledgeStore. There is no central coordinator. Each agent reads the store, acts on what it finds, and writes new findings back. Later agents in a cycle immediately see findings written by earlier agents in the same cycle.

Two tools are automatically injected into every member agent at runtime:

  • +read_store+ — returns all current findings as a JSON string
  • +write_finding+ — appends a Hash finding to the store

Use +member+ to register agents and optionally provide per-agent coordination instructions. Use +coordination+ to define the team-level protocol that all members receive instead of the built-in default guide.

Examples:

Basic usage with per-agent instructions

class CodeReviewTeam < Phronomy::Agent::SharedState
  member StructureAnalyst
  member SecurityAuditor, instruction: "Focus on authentication and injection risks."
  member QualityReviewer,  instruction: "Flag methods longer than 10 lines."
  max_cycles  3
  aggregate   { |store| { findings: store.read_all, total: store.size } }
end

result = CodeReviewTeam.new.invoke("Review the files in ./src")
# => { output: { findings: [...], total: N }, cycles: 3, terminated_by: :max_cycles }

With custom team-level coordination protocol

class ResearchTeam < Phronomy::Agent::SharedState
  coordination <<~TEXT
    Shared store tools: read_store (no params), write_finding(content:).
    Workflow: read first, then write one finding per insight.
  TEXT
  member LiteratureAgent
  member IndustryAgent
  max_cycles  10
  terminate_when { |store| store.size >= 20 }
end

See Also:

Defined Under Namespace

Classes: KnowledgeStore

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.aggregate {|KnowledgeStore| ... } ⇒ Object

Defines how the final store is converted into the +:output+ of the result. When omitted, +store.read_all+ is used as-is.

Yields:

  • (KnowledgeStore)

    receives the final store; return value becomes +:output+



149
150
151
# File 'lib/phronomy/agent/shared_state.rb', line 149

def aggregate(&block)
  block ? @aggregator = block : @aggregator
end

.coordination(text = nil) ⇒ Object

Defines the team-level coordination protocol text injected into every member's prompt. When omitted the built-in default guide is used, which explains +read_store+ / +write_finding+ usage and enforces the standard workflow. Override this when you need a different protocol or tone.

Parameters:

  • text (String, nil) (defaults to: nil)

    the coordination instructions



113
114
115
# File 'lib/phronomy/agent/shared_state.rb', line 113

def coordination(text = nil)
  text ? @coordination = text : @coordination
end

.max_cycles(value = nil) ⇒ Object

Sets the maximum number of cycles to run. At least one of +max_cycles+ or +timeout+ must be configured.

Parameters:

  • value (Integer, nil) (defaults to: nil)


122
123
124
# File 'lib/phronomy/agent/shared_state.rb', line 122

def max_cycles(value = nil)
  value ? @max_cycles = Integer(value) : @max_cycles
end

.member(klass, instruction: nil) ⇒ Object

Registers a member agent class that will collaborate via the shared store. Members are invoked sequentially within each cycle in declaration order.

Parameters:

  • klass (Class)

    an Agent::Base subclass

  • instruction (String, nil) (defaults to: nil)

    optional per-agent coordination instruction appended to the team coordination text in this agent's prompt



92
93
94
95
# File 'lib/phronomy/agent/shared_state.rb', line 92

def member(klass, instruction: nil)
  @members ||= []
  @members << {klass: klass, instruction: instruction}
end

.researchers(*classes) ⇒ Object

Backward-compatible alias. Registers each class as a member without a per-agent instruction. Prefer member for new code.

Parameters:

  • classes (Array<Class>)

    Agent::Base subclasses



102
103
104
# File 'lib/phronomy/agent/shared_state.rb', line 102

def researchers(*classes)
  classes.flatten.each { |klass| member(klass) }
end

.terminate_when {|KnowledgeStore| ... } ⇒ Object

Registers an optional convergence block. Evaluated after each completed cycle; when it returns +true+ the loop terminates early.

Yields:



140
141
142
# File 'lib/phronomy/agent/shared_state.rb', line 140

def terminate_when(&block)
  block ? @terminate_when = block : @terminate_when
end

.timeout(value = nil) ⇒ Object

Sets the maximum wall-clock seconds for the entire invocation. At least one of +max_cycles+ or +timeout+ must be configured.

Parameters:

  • value (Numeric, nil) (defaults to: nil)


131
132
133
# File 'lib/phronomy/agent/shared_state.rb', line 131

def timeout(value = nil)
  value ? @timeout = value.to_f : @timeout
end

Instance Method Details

#invoke(input, config: {}) ⇒ Hash

Runs the shared-state coordination loop.

Parameters:

  • input (String)

    the seed question or task description

  • config (Hash) (defaults to: {})

    reserved for future use

Returns:

  • (Hash)

    +:output+, +:cycles+, +:terminated_by+

Raises:

  • (ArgumentError)

    when neither +max_cycles+ nor +timeout+ is configured



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/phronomy/agent/shared_state.rb', line 176

def invoke(input, config: {})
  validate_termination!

  store = KnowledgeStore.new
  max_cycles = self.class._max_cycles
  deadline = self.class._timeout ? Time.now + self.class._timeout : nil
  terminated_by = :max_cycles
  completed_cycles = 0

  cycle_limit = max_cycles || Float::INFINITY

  (1..cycle_limit).each do |cycle|
    self.class._members.each do |member_config|
      invoke_researcher(member_config[:klass], store, cycle, input, member_config[:instruction])
    end
    completed_cycles = cycle

    if self.class._terminate_when&.call(store)
      terminated_by = :terminate_when
      break
    end

    if deadline && Time.now >= deadline
      terminated_by = :timeout
      break
    end

    terminated_by = :max_cycles
  end

  output = if self.class._aggregator
    self.class._aggregator.call(store)
  else
    store.read_all
  end

  {output: output, cycles: completed_cycles, terminated_by: terminated_by}
end