Class: Phronomy::Agent::FSM Private

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

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

EventLoop-registered execution unit for a single agent invocation.

+AgentFSM+ implements the minimal interface expected by EventLoop (+#id+, +#start+, +#handle+) so it can be managed alongside FSMSession instances. It is not a traditional finite-state machine; the name reflects its role in the EventLoop rather than internal state transitions.

== Execution model

#start is called by the EventLoop on the +:start+ event. It immediately returns after spawning a background IO thread that runs the agent's full invocation pipeline (via +_invoke_impl+). The EventLoop thread is never blocked by agent execution.

Inside the IO thread, the +:phronomy_agent_parallel_tools+ thread-local flag is set to +true+ so that Base#build_chat returns a ParallelToolChat instance, enabling concurrent tool dispatch when the LLM returns multiple tool calls in one response.

== Completion events

On success:

  • Posts +:finished+ to this FSM's own +#id+ so the EventLoop cleans up its registry entry and unblocks any +completion_queue.pop+ caller.
  • When +parent_id+ is set (child-FSM pattern), additionally posts +:child_completed+ to +parent_id+, carrying the result hash as the event payload. The parent FSMSession must declare an +on:+ transition for +:child_completed+ to advance correctly.

On error:

  • Posts +:error+ to this FSM's own +#id+. The EventLoop propagates the exception through the +completion_queue+ so that the original caller of +Agent::Base#invoke+ (in EventLoop mode) receives and re-raises it.

== Standalone usage (blocking caller)

Phronomy.configure { |c| c.event_loop = true } result = MyAgent.new.invoke("Hello!") # => { output:, messages:, usage: }

Base#invoke detects EventLoop mode, creates an +AgentFSM+, registers it via EventLoop#register, and blocks the calling thread on the returned +completion_queue+ until the agent finishes.

== Child-FSM usage (non-blocking, inside a Workflow)

state :run_agent entry :run_agent, ->(ctx) { MyAgent.new.run_as_child(ctx.query, ctx: ctx) } transition from: :run_agent, on: :child_completed, to: :process_result

Base#run_as_child creates an +AgentFSM+ with +parent_id+ set to +ctx.thread_id+, registers it with the EventLoop, and returns immediately. The parent FSMSession waits for the +:child_completed+ event.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent:, input:, messages: [], thread_id: nil, config: {}, parent_id: nil, result_writer: nil) ⇒ FSM

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of FSM.

Examples:

Writing result into context

entry :run_agent, ->(ctx) {
  MyAgent.new.run_as_child(ctx.query, ctx: ctx) { |r| ctx.answer = r[:output] }
}

Parameters:

  • agent (Phronomy::Agent::Base)

    agent instance to run

  • input (String, Hash)

    user input passed to +invoke_once+

  • messages (Array) (defaults to: [])

    prior conversation history

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

    conversation thread id; auto-generated when nil

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

    invocation config forwarded to +_invoke_impl+

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

    EventLoop id of the parent FSMSession; when set, a +:child_completed+ event is posted on completion

  • result_writer (Proc, nil) (defaults to: nil)

    optional callable invoked with the result hash before +:child_completed+ is posted. Use this to write the agent output back into the parent WorkflowContext. Thread::Queue provides the happens-before guarantee.



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/phronomy/agent/fsm.rb', line 92

def initialize(agent:, input:, messages: [], thread_id: nil, config: {}, parent_id: nil, result_writer: nil)
  @agent = agent
  @input = input
  @messages = Array(messages).dup
  @thread_id = thread_id || SecureRandom.uuid
  @config = config
  @parent_id = parent_id
  @result_writer = result_writer
  @id = @thread_id
  @current_phase = :idle
end

Instance Attribute Details

#current_phaseSymbol (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns current internal phase (:idle, :running).

Returns:

  • (Symbol)

    current internal phase (:idle, :running)



66
67
68
# File 'lib/phronomy/agent/fsm.rb', line 66

def current_phase
  @current_phase
end

#idString (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns unique identifier used as the EventLoop target_id.

Returns:

  • (String)

    unique identifier used as the EventLoop target_id



63
64
65
# File 'lib/phronomy/agent/fsm.rb', line 63

def id
  @id
end

Instance Method Details

#handle(_event) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Called by EventLoop for external events dispatched to this id. +AgentFSM+ is fully driven by its own IO thread and does not respond to external events after #start.



114
115
116
# File 'lib/phronomy/agent/fsm.rb', line 114

def handle(_event)
  # No-op: AgentFSM is driven entirely by its IO thread.
end

#startObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Called by EventLoop on the +:start+ event. Transitions to +:running+ and spawns the agent IO thread.



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

def start
  @current_phase = :running
  spawn_agent_thread
end