Class: Pikuri::Agent::ExtensionContext
- Inherits:
-
Object
- Object
- Pikuri::Agent::ExtensionContext
- Defined in:
- lib/pikuri/agent/extension_context.rb
Overview
Capability facade handed to Pikuri::Agent::Extension#bind and Pikuri::Agent::Extension#on_user_message — the runtime counterpart of Configurator. Where the Configurator collects build-time declarations, this grants the runtime capabilities an extension needs once the agent is wired: emitting domain events, registering raw per-agent tools, deriving sub-agent listener lists.
Why a handed object, not a getter on Agent
Pikuri::Agent exposes NO public path to these capabilities (no listeners /
chat reader, no emit method): holding an agent grants read access to
its config and nothing more. The only way to obtain this object is to be
an Extension receiving bind / on_user_message — or to be handed it
onward by one (SubAgent::SubAgentTool and
Mcp::Servers::Connect both capture the context their bind
received). Capabilities flow by explicit handoff, never by fetching from
a globally reachable object. The boundary is the API contract, not a
mechanical seal — same as every seam in CLAUDE.md.
Boundary rule
Operations that act on the live agent's wiring live here; passive
readers of constructor-given config (+transport+, id, tools, ...)
stay on Pikuri::Agent, reachable via #agent. Don't move readers in; don't add
capabilities to Agent.
Audit
One context per agent, built by #initialize before the bind
sweep. ListenerList#emit has exactly two callers: Pikuri::Agent (loop
narration) and this class (domain events). Capability-user roster:
grep -rn 'emit_event\|add_raw_tool\|sub_agent_listeners' pikuri-*/lib/.
Instance Attribute Summary collapse
-
#agent ⇒ Agent
readonly
The live agent, for read access to its configuration (tools, transport, id, streaming, ...).
Instance Method Summary collapse
-
#add_raw_tool(ruby_llm_tool) ⇒ void
Register a raw
RubyLLM::Toolsubclass on the agent's chat, bypassing the Tool strict-validation seam — hence "raw" (native tools go through Configurator#add_tool for Tool::Parameters validation). -
#emit_event(event) ⇒ void
Emit a domain event onto the agent's listener stream.
-
#initialize(agent:, chat:, listeners:, on_close_sink:) ⇒ ExtensionContext
constructor
A new instance of ExtensionContext.
-
#on_close { ... } ⇒ void
Register a handler called by #close — symmetric to Configurator#on_close (LIFO, per-handler rescue, idempotent) but available post-construction, so an Extension's
bindcan install per-agent cleanup (Memory arms its recorder's bounded flush here). -
#sub_agent_listeners(**params) ⇒ ListenerList
Derive a listener list for a spawned sub-agent via ListenerList#for_sub_agent.
Constructor Details
#initialize(agent:, chat:, listeners:, on_close_sink:) ⇒ ExtensionContext
Returns a new instance of ExtensionContext.
46 47 48 49 50 51 |
# File 'lib/pikuri/agent/extension_context.rb', line 46 def initialize(agent:, chat:, listeners:, on_close_sink:) @agent = agent @chat = chat @listeners = listeners @on_close_handlers = on_close_sink end |
Instance Attribute Details
#agent ⇒ Agent (readonly)
Returns the live agent, for read access to its configuration (tools, transport, id, streaming, ...).
55 56 57 |
# File 'lib/pikuri/agent/extension_context.rb', line 55 def agent @agent end |
Instance Method Details
#add_raw_tool(ruby_llm_tool) ⇒ void
This method returns an undefined value.
Register a raw RubyLLM::Tool subclass on the agent's chat, bypassing
the Tool strict-validation seam — hence "raw" (native tools
go through Configurator#add_tool for Tool::Parameters validation).
Callers: Mcp::Servers (MCP tools deliberately bypass) and
SubAgent::Extension (the agent tool registers after the
parent's tool list is final).
The tool does NOT enter Pikuri::Agent#tools, only the chat's list — so sub-agents can't snapshot it, which is the point: activation is strictly per-agent.
87 88 89 90 |
# File 'lib/pikuri/agent/extension_context.rb', line 87 def add_raw_tool(ruby_llm_tool) @chat.with_tool(ruby_llm_tool) nil end |
#emit_event(event) ⇒ void
This method returns an undefined value.
Emit a domain event onto the agent's listener stream. Core Pikuri::Agent::Event
variants narrate the loop (Pikuri::Agent-emitted); gems define their own
(e.g. Pikuri::Tasks::ListChanged) and emit them here. Listeners must
no-op on variants they don't recognize (the Listener::Base#on_event
default gives that for free).
Called on the agent's thread (typically inside a tool's execute,
landing between Pikuri::Agent::Event::ToolCall and Pikuri::Agent::Event::ToolResult); listeners
doing cross-thread handoff snapshot inside on_event.
69 70 71 72 |
# File 'lib/pikuri/agent/extension_context.rb', line 69 def emit_event(event) @listeners.emit(event) nil end |
#on_close { ... } ⇒ void
This method returns an undefined value.
Register a handler called by Pikuri::Agent#close — symmetric to
Configurator#on_close (LIFO, per-handler rescue, idempotent) but
available post-construction, so an Pikuri::Agent::Extension's bind can install
per-agent cleanup (Memory arms its recorder's bounded flush here).
112 113 114 115 116 117 |
# File 'lib/pikuri/agent/extension_context.rb', line 112 def on_close(&blk) raise ArgumentError, 'on_close requires a block' unless block_given? @on_close_handlers << blk nil end |
#sub_agent_listeners(**params) ⇒ ListenerList
Derive a listener list for a spawned sub-agent via ListenerList#for_sub_agent. Sole caller: SubAgent::SubAgentTool, once per spawn. The derived list aliases the parent's listener instances where a listener opts to share by reference — see ListenerList#for_sub_agent.
101 102 103 |
# File 'lib/pikuri/agent/extension_context.rb', line 101 def sub_agent_listeners(**params) @listeners.for_sub_agent(**params) end |