Module: Phronomy::Agent::Concerns::Suspendable
- Included in:
- Base
- Defined in:
- lib/phronomy/agent/concerns/suspendable.rb
Overview
Adds suspend/resume and tool-approval support to an agent.
Included in Base. When a tool decorated with +requires_approval true+ is called and no synchronous approval handler has been registered, the invocation is suspended and a Phronomy::Agent::Checkpoint is returned so the caller can resume later.
Instance Method Summary collapse
-
#on_approval_required(&block) ⇒ self
Registers a callback that is invoked before executing any tool that has +requires_approval true+ set.
-
#resume(checkpoint, approved:, config: {}) ⇒ Hash
Resumes a previously suspended invocation from a Phronomy::Agent::Checkpoint.
Instance Method Details
#on_approval_required(&block) ⇒ self
Registers a callback that is invoked before executing any tool that has +requires_approval true+ set. The block receives the tool name (String) and the arguments Hash, and must return a truthy value to allow execution. Returning a falsy value causes the tool to return a denial message instead of executing.
When no handler is registered and a tool with +requires_approval+ is called, #invoke returns a suspended result hash containing a Phronomy::Agent::Checkpoint. Call #resume to continue execution after obtaining an approval decision from the user or an external system.
28 29 30 31 |
# File 'lib/phronomy/agent/concerns/suspendable.rb', line 28 def on_approval_required(&block) @approval_handler = block self end |
#resume(checkpoint, approved:, config: {}) ⇒ Hash
Resumes a previously suspended invocation from a Phronomy::Agent::Checkpoint.
This method reconstructs the conversation state captured at suspension time, injects the tool result (executed or denied), and continues the LLM loop until it produces a final answer.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/phronomy/agent/concerns/suspendable.rb', line 46 def resume(checkpoint, approved:, config: {}) # Build a fresh chat with all tools registered. chat = build_chat # Re-apply system instructions so the LLM has the same persona/context # as the original invocation. build_cached_system_text is memoised, so # a Proc- or PromptTemplate-based instructions block is re-evaluated # against the original input rather than using a stale cached value. system_text = build_cached_system_text(checkpoint.original_input) apply_instructions(chat, system_text) if system_text # Restore the full conversation (history + user + assistant with tool call). checkpoint..each { |msg| chat. << msg } # Determine the tool result: execute it or inject a denial string. tool_result = if approved tool_instance = chat.tools[checkpoint.pending_tool_name.to_sym] tool_instance ? tool_instance.call(checkpoint.pending_tool_args) : "Tool not found." else "Tool execution denied." end # Inject the tool result so the LLM can continue. chat.( role: :tool, content: tool_result.to_s, tool_call_id: checkpoint.pending_tool_call_id ) # Continue the React loop. response = chat.complete output = response.content usage = Phronomy::TokenUsage.from_tokens(response.tokens) run_output_guardrails!(output) {output: output, suspended: false, messages: chat., usage: usage} end |