Class: Pikuri::Agent::Configurator

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/agent/configurator.rb

Overview

Build-time collector yielded into the Pikuri::Agent.new block. Hosts and Extension implementations call its methods to declare additional tools, listeners, system-prompt snippets, on_close handlers, extension instances, and persona-flavored sub-agents; #initialize drains the collected state into the agent’s final wiring before returning.

Why this exists

Splits “configure the agent” from “the agent’s runtime state”. Hosts can write a block that reads cleanly:

Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
  c.add_listener Pikuri::Agent::Listener::Terminal.new
  c.add_tool     Pikuri::Tool::WEB_SEARCH
  c.add_tool     Pikuri::Tool::WEB_SCRAPE
  c.add_tool     Pikuri::Tool::FETCH
  c.add_extension Pikuri::Skill::Extension.new(catalog: catalog)
end

Two tool pools: regular vs. sub-agent-only

#add_tool registers a tool the parent agent can call: it lands in #tools and gets handed to ruby_llm via chat.with_tool. #add_sub_agent_tool registers a tool the parent cannot call — it lands in #sub_agent_tools and is never sent to ruby_llm for the parent, but is visible to SubAgent::Extension‘s persona-tool-name resolution. The use case is the lethal-trifecta defense in Code::Bash::Sandbox terms: keep network tools (web_search / web_scrape / fetch) off the parent so a prompt- injected file read cannot egress through the parent’s own tools, while still letting the researcher persona reach them via the agent delegation tool. See SECURITY.md §“Defense: capability boundaries via sub-agents”.

Extensions implement their configure© hook against the same type, so the call sites for “block users add stuff” and “extensions add stuff” share one API.

Lifecycle

One Configurator per Agent.new invocation. The Configurator is constructed inside #initialize, yielded to the block (if any), then its collected state is drained by the Agent body. The Configurator instance is discarded once Agent.new returns — it carries no runtime state.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transport:, system_prompt_base:, id:, streaming:, step_limit:, cancellable:, interloper:) ⇒ Configurator

Returns a new instance of Configurator.

Parameters:



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/pikuri/agent/configurator.rb', line 130

def initialize(transport:, system_prompt_base:, id:, streaming:,
               step_limit:, cancellable:, interloper:)
  @transport = transport
  @system_prompt_base = system_prompt_base
  @id = id
  @streaming = streaming
  @step_limit = step_limit
  @cancellable = cancellable
  @interloper = interloper

  @tools = []
  @sub_agent_tools = []
  @listeners = []
  @system_prompt_additions = []
  @on_close_handlers = []
  @extensions = []
end

Instance Attribute Details

#cancellableControl::Cancellable? (readonly)

Returns cancellation control passed to the Agent ctor, or nil. Extensions that run sub-LLM calls during configure (e.g. an MCP description synthesizer) share this so a user cancel during boot propagates correctly.

Returns:

  • (Control::Cancellable, nil)

    cancellation control passed to the Agent ctor, or nil. Extensions that run sub-LLM calls during configure (e.g. an MCP description synthesizer) share this so a user cancel during boot propagates correctly.



82
83
84
# File 'lib/pikuri/agent/configurator.rb', line 82

def cancellable
  @cancellable
end

#extensionsArray<#configure> (readonly)

Returns extension instances added via #add_extension, in declaration order. The Agent ctor walks this list and calls bind(self) on each after wiring is complete.

Returns:

  • (Array<#configure>)

    extension instances added via #add_extension, in declaration order. The Agent ctor walks this list and calls bind(self) on each after wiring is complete.



121
122
123
# File 'lib/pikuri/agent/configurator.rb', line 121

def extensions
  @extensions
end

#idString (readonly)

Returns this agent’s unique identifier; empty for the main agent, persona-rooted (e.g. “researcher 0”) for sub-agents.

Returns:

  • (String)

    this agent’s unique identifier; empty for the main agent, persona-rooted (e.g. “researcher 0”) for sub-agents.



67
68
69
# File 'lib/pikuri/agent/configurator.rb', line 67

def id
  @id
end

#interloperControl::Interloper? (readonly)

Returns mid-loop user-input queue passed to the Agent ctor, or nil.

Returns:



86
87
88
# File 'lib/pikuri/agent/configurator.rb', line 86

def interloper
  @interloper
end

#listenersArray<Listener::Base> (readonly)

Returns listeners added via #add_listener, in declaration order. Drained by Pikuri::Agent#initialize.

Returns:



104
105
106
# File 'lib/pikuri/agent/configurator.rb', line 104

def listeners
  @listeners
end

#on_close_handlersArray<Proc> (readonly)

Returns on_close handlers added via #on_close, in declaration order. Fired by Pikuri::Agent#close in LIFO order with per-handler rescue.

Returns:

  • (Array<Proc>)

    on_close handlers added via #on_close, in declaration order. Fired by Pikuri::Agent#close in LIFO order with per-handler rescue.



115
116
117
# File 'lib/pikuri/agent/configurator.rb', line 115

def on_close_handlers
  @on_close_handlers
end

#step_limitControl::StepLimit? (readonly)

Returns step-budget control passed to the Agent ctor, or nil.

Returns:



75
76
77
# File 'lib/pikuri/agent/configurator.rb', line 75

def step_limit
  @step_limit
end

#streamingBoolean (readonly)

Returns true when the agent opted into chunk-level streaming.

Returns:

  • (Boolean)

    true when the agent opted into chunk-level streaming.



71
72
73
# File 'lib/pikuri/agent/configurator.rb', line 71

def streaming
  @streaming
end

#sub_agent_toolsArray<Tool> (readonly)

Returns tools added via #add_sub_agent_tool, in declaration order. Drained by Pikuri::Agent#initialize but not registered with ruby_llm — invisible to the parent LLM, available only to sub-agents through SubAgent::Extension‘s persona-tool-name resolution. See the class header.

Returns:

  • (Array<Tool>)

    tools added via #add_sub_agent_tool, in declaration order. Drained by Pikuri::Agent#initialize but not registered with ruby_llm — invisible to the parent LLM, available only to sub-agents through SubAgent::Extension‘s persona-tool-name resolution. See the class header.



99
100
101
# File 'lib/pikuri/agent/configurator.rb', line 99

def sub_agent_tools
  @sub_agent_tools
end

#system_prompt_additionsArray<String> (readonly)

Returns system-prompt snippets added via #append_system_prompt, in declaration order. Joined with double-newline separators between the base prompt and each snippet by Pikuri::Agent#initialize.

Returns:



110
111
112
# File 'lib/pikuri/agent/configurator.rb', line 110

def system_prompt_additions
  @system_prompt_additions
end

#system_prompt_baseString (readonly)

Returns the system_prompt: kwarg passed to Pikuri::Agent#initialize, untouched. Extensions append to the prompt via #append_system_prompt rather than mutating this; the attribute exists so a peek at the base is available for diagnostics.

Returns:

  • (String)

    the system_prompt: kwarg passed to Pikuri::Agent#initialize, untouched. Extensions append to the prompt via #append_system_prompt rather than mutating this; the attribute exists so a peek at the base is available for diagnostics.



63
64
65
# File 'lib/pikuri/agent/configurator.rb', line 63

def system_prompt_base
  @system_prompt_base
end

#toolsArray<Tool> (readonly)

Returns tools added via #add_tool, in declaration order. Drained by Pikuri::Agent#initialize and registered with ruby_llm so the parent LLM can call them.

Returns:



91
92
93
# File 'lib/pikuri/agent/configurator.rb', line 91

def tools
  @tools
end

#transportAgent::ChatTransport (readonly)

Returns same transport the Agent will use. Extensions read this to wire helpers consistently (e.g. an MCP description-synthesizer that calls the same model the agent itself uses).

Returns:

  • (Agent::ChatTransport)

    same transport the Agent will use. Extensions read this to wire helpers consistently (e.g. an MCP description-synthesizer that calls the same model the agent itself uses).



56
57
58
# File 'lib/pikuri/agent/configurator.rb', line 56

def transport
  @transport
end

Instance Method Details

#add_extension(extension) ⇒ void

This method returns an undefined value.

Register an extension. The extension’s configure(self) is called immediately so source-order matches execution-order. The instance is also retained for the bind(agent) sweep that runs at the end of Pikuri::Agent#initialize.

Extensions must implement both configure and bind. The easy way is to include Pikuri::Agent::Extension — that mixes in empty defaults for both, so an extension overrides only what it cares about and leaves the other as a no-op.

Parameters:

  • extension (Extension, #configure, #bind)

    extension instance



225
226
227
228
229
# File 'lib/pikuri/agent/configurator.rb', line 225

def add_extension(extension)
  @extensions << extension
  extension.configure(self)
  nil
end

#add_listener(listener) ⇒ void

This method returns an undefined value.

Append a listener to the agent’s listener list.

Parameters:



183
184
185
186
# File 'lib/pikuri/agent/configurator.rb', line 183

def add_listener(listener)
  @listeners << listener
  nil
end

#add_listeners(listeners) ⇒ void

This method returns an undefined value.

Append several listeners at once. Accepts any enumerable (Array, ListenerList, …); declaration order is preserved.

Parameters:



193
194
195
196
# File 'lib/pikuri/agent/configurator.rb', line 193

def add_listeners(listeners)
  listeners.each { |l| @listeners << l }
  nil
end

#add_sub_agent_tool(tool) ⇒ void

This method returns an undefined value.

Append a tool to the sub-agent-only pool. The parent LLM never sees it; only sub-agents whose persona tool_names include the tool’s name get it in their toolset. See the class header for the trifecta-defense rationale.

Parameters:



174
175
176
177
# File 'lib/pikuri/agent/configurator.rb', line 174

def add_sub_agent_tool(tool)
  @sub_agent_tools << tool
  nil
end

#add_tool(tool) ⇒ void

This method returns an undefined value.

Append a tool to the agent’s static tool list.

Parameters:



152
153
154
155
# File 'lib/pikuri/agent/configurator.rb', line 152

def add_tool(tool)
  @tools << tool
  nil
end

#add_tools(tools) ⇒ void

This method returns an undefined value.

Append several tools at once. Equivalent to calling #add_tool for each element of tools; declaration order is preserved.

Parameters:

  • tools (Enumerable<Tool>)


162
163
164
165
# File 'lib/pikuri/agent/configurator.rb', line 162

def add_tools(tools)
  tools.each { |t| @tools << t }
  nil
end

#append_system_prompt(snippet) ⇒ void

This method returns an undefined value.

Append a snippet to the system prompt. Snippets are joined to the base prompt with double-newline separators.

Used by extensions to register <available_skills>, <available_mcps>, and similar advertisement blocks. Block users typically pass the full system prompt via the ctor’s system_prompt: kwarg instead.

Parameters:

  • snippet (String)


208
209
210
211
# File 'lib/pikuri/agent/configurator.rb', line 208

def append_system_prompt(snippet)
  @system_prompt_additions << snippet
  nil
end

#on_close { ... } ⇒ void

This method returns an undefined value.

Register a handler called by Pikuri::Agent#close. Handlers fire in LIFO order, each inside its own rescue — same semantics as ensure-block cleanup discipline.

Yields:

  • called with no arguments at close time

Raises:

  • (ArgumentError)


237
238
239
240
241
242
# File 'lib/pikuri/agent/configurator.rb', line 237

def on_close(&blk)
  raise ArgumentError, 'on_close requires a block' unless block_given?

  @on_close_handlers << blk
  nil
end