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+



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

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



107
108
109
# File 'lib/phronomy/agent/shared_state.rb', line 107

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)


115
116
117
# File 'lib/phronomy/agent/shared_state.rb', line 115

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



88
89
90
91
# File 'lib/phronomy/agent/shared_state.rb', line 88

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



97
98
99
# File 'lib/phronomy/agent/shared_state.rb', line 97

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:



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

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)


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

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



165
166
167
168
169
170
171
172
173
174
175
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
# File 'lib/phronomy/agent/shared_state.rb', line 165

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