Class: Pikuri::SubAgent::Extension
- Inherits:
-
Object
- Object
- Pikuri::SubAgent::Extension
- Includes:
- Agent::Extension
- Defined in:
- lib/pikuri/sub_agent/extension.rb
Overview
An Agent::Extension that wires the agent tool onto a parent agent from a list of Persona instances. The canonical opt-in for sub-agents — same shape as Pikuri::Skill::Extension and Mcp::Extension.
Usage
Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
c.add_sub_agent_tool Pikuri::Tool::WEB_SEARCH
c.add_sub_agent_tool Pikuri::Tool::WEB_SCRAPE
c.add_sub_agent_tool Pikuri::Tool::FETCH
c.add_extension Pikuri::SubAgent::Extension.new(
personas: [Pikuri::SubAgent::RESEARCHER]
)
end
Either Agent::Configurator#add_tool or Agent::Configurator#add_sub_agent_tool satisfies a persona’s tool_names entry — the difference is whether the parent LLM also gets the tool. Use add_sub_agent_tool for tools you want only the sub-agent to be able to call (this is the lethal-trifecta defense for network tools — see SECURITY.md §“Defense: capability boundaries via sub-agents”).
Configure / bind split
Same MCP-shape division of labor used by Mcp::Extension:
-
configure© — agent-agnostic setup. Validates that every persona’s
tool_namesresolves against tools already registered on the Configurator (host-side bug to catch at boot, not at first LLM call), then appends the <available_agents> snippet via Agent::Configurator#append_system_prompt. -
bind(agent) — agent-keyed setup. Constructs the SubAgentTool closing over the live parent agent (its
tools,listeners,cancellable,context_window_cap,streamingflag) and installs it via Agent#internal_add_tool.
Sub-agents do not inherit extensions, so bind fires for the parent only.
Duplicate-persona policy
The constructor raises ArgumentError if two personas in the list share a name. Two personas with the same LLM-facing name would be indistinguishable to the model and a quiet config bug for the host — same rationale as “two Ruby classes with the same name shouldn’t exist.” Fail fast at construction rather than silently shadow.
Instance Attribute Summary collapse
-
#personas ⇒ Hash{String=>Persona}
readonly
Personas keyed by name, in declaration order.
Instance Method Summary collapse
-
#bind(agent) ⇒ void
Construct the SubAgentTool closing over the live parent agent and register it on the agent’s chat.
-
#configure(c) ⇒ void
Validate every persona’s
tool_namesagainst the union of the Configurator’s regular and sub-agent-only tool pools, then append the <available_agents> snippet to the system prompt. -
#initialize(personas:) ⇒ Extension
constructor
A new instance of Extension.
Constructor Details
#initialize(personas:) ⇒ Extension
Returns a new instance of Extension.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/pikuri/sub_agent/extension.rb', line 64 def initialize(personas:) raise ArgumentError, 'personas: must contain at least one Persona' if personas.empty? @personas = {} personas.each do |persona| raise ArgumentError, "expected Pikuri::SubAgent::Persona, got #{persona.class}" \ unless persona.is_a?(Persona) raise ArgumentError, "duplicate persona name #{persona.name.inspect} " \ 'in personas: list' \ if @personas.key?(persona.name) @personas[persona.name] = persona end end |
Instance Attribute Details
#personas ⇒ Hash{String=>Persona} (readonly)
Returns personas keyed by name, in declaration order. Exposed so tests + diagnostics can read the resolved map.
82 83 84 |
# File 'lib/pikuri/sub_agent/extension.rb', line 82 def personas @personas end |
Instance Method Details
#bind(agent) ⇒ void
This method returns an undefined value.
Construct the SubAgentTool closing over the live parent agent and register it on the agent’s chat. Goes through Agent#internal_add_tool rather than @tools because the tool’s execute closure captures parent_agent.tools at construction — by the time bind runs, that list is final.
127 128 129 130 131 |
# File 'lib/pikuri/sub_agent/extension.rb', line 127 def bind(agent) sub_tool = SubAgentTool.new(agent, personas: @personas) agent.internal_add_tool(sub_tool.to_ruby_llm_tool) nil end |
#configure(c) ⇒ void
This method returns an undefined value.
Validate every persona’s tool_names against the union of the Configurator’s regular and sub-agent-only tool pools, then append the <available_agents> snippet to the system prompt. The SubAgentTool itself is installed in #bind —it needs the live parent agent’s tools / listeners to close over.
Practical implication: call c.add_extension after the c.add_tool / c.add_sub_agent_tool calls the personas depend on; otherwise the tool_names validation will not find them.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/pikuri/sub_agent/extension.rb', line 101 def configure(c) have = (c.tools + c.sub_agent_tools).map(&:name) @personas.each_value do |persona| missing = persona.tool_names - have next if missing.empty? raise ArgumentError, "persona #{persona.name.inspect} references unregistered tool(s) " \ "#{missing.inspect}. Register them via c.add_tool or " \ "c.add_sub_agent_tool before adding Pikuri::SubAgent::Extension. " \ "Currently registered: #{have.inspect}." end c.append_system_prompt(SubAgentTool.available_agents_snippet(@personas)) nil end |