Class: Phronomy::Agent::Checkpoint

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

Overview

Encapsulates the suspended state of an agent invocation.

A Checkpoint is returned as the +:checkpoint+ key of the result hash when an approval-required tool is encountered and no synchronous on_approval_required handler has been registered.

Pass the checkpoint to Agent::Base#resume to continue execution after obtaining an approval decision from the user or an external system.

Examples:

Suspend and resume

result = agent.invoke("Do task X")
if result[:suspended]
  approved = prompt_user(result[:checkpoint].pending_tool_name)
  result   = agent.resume(result[:checkpoint], approved: approved)
end
puts result[:output]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(thread_id:, original_input:, messages:, pending_tool_name:, pending_tool_args:, pending_tool_call_id:, checkpoint_id: SecureRandom.uuid, agent_class: nil, requested_at: Time.now.utc) ⇒ Checkpoint

Returns a new instance of Checkpoint.

Parameters:

  • checkpoint_id (String) (defaults to: SecureRandom.uuid)

    unique identifier; defaults to a new UUID

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

    fully-qualified agent class name

  • requested_at (Time) (defaults to: Time.now.utc)

    when the checkpoint was created; defaults to +Time.now.utc+

  • thread_id (String, nil)
  • original_input (String, Hash)

    the input passed to the original #invoke call

  • messages (Array<RubyLLM::Message>)
  • pending_tool_name (String)
  • pending_tool_args (Hash)
  • pending_tool_call_id (String)


68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/phronomy/agent/checkpoint.rb', line 68

def initialize(thread_id:, original_input:, messages:, pending_tool_name:, pending_tool_args:, pending_tool_call_id:,
  checkpoint_id: SecureRandom.uuid, agent_class: nil, requested_at: Time.now.utc)
  @checkpoint_id = checkpoint_id
  @agent_class = agent_class
  @requested_at = requested_at
  @thread_id = thread_id
  @original_input = original_input
  @messages = messages.dup.freeze
  @pending_tool_name = pending_tool_name
  @pending_tool_args = pending_tool_args
  @pending_tool_call_id = pending_tool_call_id
end

Instance Attribute Details

#agent_classString? (readonly)

Returns the fully-qualified name of the agent class that created this checkpoint (e.g. +"MyApp::ReviewAgent"+); used by the class-level +resume+ method to validate the correct agent is used.

Returns:

  • (String, nil)

    the fully-qualified name of the agent class that created this checkpoint (e.g. +"MyApp::ReviewAgent"+); used by the class-level +resume+ method to validate the correct agent is used



31
32
33
# File 'lib/phronomy/agent/checkpoint.rb', line 31

def agent_class
  @agent_class
end

#checkpoint_idString (readonly)

Returns a globally unique identifier for this checkpoint; used as an idempotency key when guarding against duplicate resumes.

Returns:

  • (String)

    a globally unique identifier for this checkpoint; used as an idempotency key when guarding against duplicate resumes



26
27
28
# File 'lib/phronomy/agent/checkpoint.rb', line 26

def checkpoint_id
  @checkpoint_id
end

#messagesArray<RubyLLM::Message> (readonly)

Returns conversation messages up to and including the assistant message that requested the pending tool call.

Returns:

  • (Array<RubyLLM::Message>)

    conversation messages up to and including the assistant message that requested the pending tool call



46
47
48
# File 'lib/phronomy/agent/checkpoint.rb', line 46

def messages
  @messages
end

#original_inputString, Hash (readonly)

Returns the original input passed to #invoke; stored so that #resume can re-apply dynamic system instructions (e.g. Proc or PromptTemplate-based instructions that depend on the input value).

Returns:

  • (String, Hash)

    the original input passed to #invoke; stored so that #resume can re-apply dynamic system instructions (e.g. Proc or PromptTemplate-based instructions that depend on the input value).



42
43
44
# File 'lib/phronomy/agent/checkpoint.rb', line 42

def original_input
  @original_input
end

#pending_tool_argsHash (readonly)

Returns the arguments the LLM passed to the pending tool.

Returns:

  • (Hash)

    the arguments the LLM passed to the pending tool



52
53
54
# File 'lib/phronomy/agent/checkpoint.rb', line 52

def pending_tool_args
  @pending_tool_args
end

#pending_tool_call_idString (readonly)

Returns the tool_call_id from the LLM response (required to inject the tool result message on resume).

Returns:

  • (String)

    the tool_call_id from the LLM response (required to inject the tool result message on resume)



56
57
58
# File 'lib/phronomy/agent/checkpoint.rb', line 56

def pending_tool_call_id
  @pending_tool_call_id
end

#pending_tool_nameString (readonly)

Returns the name of the tool awaiting approval.

Returns:

  • (String)

    the name of the tool awaiting approval



49
50
51
# File 'lib/phronomy/agent/checkpoint.rb', line 49

def pending_tool_name
  @pending_tool_name
end

#requested_atTime (readonly)

Returns the UTC timestamp when this checkpoint was created.

Returns:

  • (Time)

    the UTC timestamp when this checkpoint was created



34
35
36
# File 'lib/phronomy/agent/checkpoint.rb', line 34

def requested_at
  @requested_at
end

#thread_idString? (readonly)

Returns the thread_id from the invocation config.

Returns:

  • (String, nil)

    the thread_id from the invocation config



37
38
39
# File 'lib/phronomy/agent/checkpoint.rb', line 37

def thread_id
  @thread_id
end

Class Method Details

.from_h(h) ⇒ Checkpoint

Reconstructs a +Checkpoint+ from a plain Hash (e.g. produced by #to_h and deserialized from JSON or Marshal).

Hash keys may be either Symbols or Strings; both are accepted. +RubyLLM::ToolCall+ objects inside message +:tool_calls+ arrays are reconstructed from their hash representations.

Parameters:

  • h (Hash)

    a hash previously produced by #to_h

Returns:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/phronomy/agent/checkpoint.rb', line 117

def self.from_h(h)
  h = h.transform_keys { |k|
    begin
      k.to_sym
    rescue
      k
    end
  }
  messages = Array(h[:messages]).map { |m| deserialize_message(m) }
  requested_at_raw = h[:requested_at]
  requested_at = requested_at_raw ? Time.parse(requested_at_raw.to_s).utc : nil
  new(
    checkpoint_id: h[:checkpoint_id]&.to_s || SecureRandom.uuid,
    agent_class: h[:agent_class]&.to_s,
    requested_at: requested_at || Time.now.utc,
    thread_id: h[:thread_id],
    original_input: h[:original_input],
    messages: messages,
    pending_tool_name: h[:pending_tool_name]&.to_s,
    pending_tool_args: h[:pending_tool_args] ? h[:pending_tool_args].transform_keys { |k|
      begin
        k.to_sym
      rescue
        k
      end
    } : {},
    pending_tool_call_id: h[:pending_tool_call_id]&.to_s
  )
end

Instance Method Details

#to_hHash

Converts this checkpoint to a plain Hash suitable for JSON / Marshal serialization.

All values are plain Ruby objects (String, Symbol, Hash, Array, Numeric, nil). +RubyLLM::Message+ objects in +:messages+ are deep-converted so that any embedded +RubyLLM::ToolCall+ objects are also serialized as plain hashes.

Examples:

Round-trip via JSON

json = JSON.generate(checkpoint.to_h)
checkpoint2 = Phronomy::Agent::Checkpoint.from_h(JSON.parse(json))

Returns:

  • (Hash)


93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/phronomy/agent/checkpoint.rb', line 93

def to_h
  {
    checkpoint_id: @checkpoint_id,
    agent_class: @agent_class,
    requested_at: @requested_at&.iso8601,
    thread_id: @thread_id,
    original_input: @original_input,
    messages: @messages.map { |m| serialize_message(m) },
    pending_tool_name: @pending_tool_name,
    pending_tool_args: @pending_tool_args,
    pending_tool_call_id: @pending_tool_call_id
  }
end