Class: Insika::ToolAssembly
- Inherits:
-
Object
- Object
- Insika::ToolAssembly
- Defined in:
- lib/insika/tool_assembly.rb
Overview
Turn-scoped assembly of the agent's tool instances (pipeline stage 3 tail): capability resolution, instantiation (Entry#factory | ready instance), turn-context (D2) injection, the capability<->direct dedup join, and the ToolEnvelope wrap (stage 7 seam: per-call timeout + side-effect recording).
Extracted from the Executor (§11 B5) to keep the hot-path file smaller. It is
a pure collaborator — it holds only the injected registries/stores and no
per-turn state; everything turn-specific arrives via state/turn_context.
The Executor keeps thin delegators (resolve_capabilities/assemble_tool_instances/
wrap_tools) so the existing private-method contract stays intact.
Instance Method Summary collapse
-
#assemble_tool_instances(allowed, state) ⇒ Object
Joins the direct instances (Policy/ToolAllowlist) with the capability-sourced ones (grant = profile.capabilities — they never went through Policy).
-
#initialize(tool_registry:, capability_registry:, event_stream:, checkpoint_store:, tool_trace_store:) ⇒ ToolAssembly
constructor
A new instance of ToolAssembly.
-
#resolve_capabilities(profile, context) ⇒ Object
Resolution sub-step BETWEEN Context and Policy — it does NOT feed candidate_tools (those stay ONLY tool_registry.entries, a capability does not go through the ToolAllowlist).
-
#wrap_tools(tools, state, skip_side_effects = []) ⇒ Object
Envelopes each allowed tool (per-call timeout + side-effect recording).
Constructor Details
#initialize(tool_registry:, capability_registry:, event_stream:, checkpoint_store:, tool_trace_store:) ⇒ ToolAssembly
Returns a new instance of ToolAssembly.
18 19 20 21 22 23 24 25 |
# File 'lib/insika/tool_assembly.rb', line 18 def initialize(tool_registry:, capability_registry:, event_stream:, checkpoint_store:, tool_trace_store:) @tool_registry = tool_registry @capability_registry = capability_registry @event_stream = event_stream @checkpoint_store = checkpoint_store @tool_trace_store = tool_trace_store end |
Instance Method Details
#assemble_tool_instances(allowed, state) ⇒ Object
Joins the direct instances (Policy/ToolAllowlist) with the capability-sourced ones (grant = profile.capabilities — they never went through Policy). Avoids double-exposure: if the SAME impl_name was also allowed directly, the DIRECT instance is discarded — the model sees only the capability alias.
59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/insika/tool_assembly.rb', line 59 def assemble_tool_instances(allowed, state) names = state.respond_to?(:capability_names) ? (state.capability_names || {}) : {} ctx = state.respond_to?(:turn_context) ? state.turn_context : nil return instantiate_tools(allowed, ctx) if names.empty? # Dedup by the ENTRY NAME (registry key = impl_name) BEFORE # instantiating — the INSTANCE's `.name` (RubyLLM) is not the registration # name. direct = Array(allowed).reject { |e| e.respond_to?(:name) && names.key?(e.name.to_s) } instantiate_tools(direct, ctx) + capability_tool_instances(names, ctx) end |
#resolve_capabilities(profile, context) ⇒ Object
Resolution sub-step BETWEEN Context and Policy —
it does NOT feed candidate_tools (those stay ONLY tool_registry.entries,
a capability does not go through the ToolAllowlist). Resolves each
capability of the profile to the concrete Entry already registered in the
tool_registry and keeps the impl_name -> capability_name mapping for the
post-Policy join. Errors
(Unavailable/Ambiguous, or an unregistered impl) propagate as a
CapabilityError -> single capture in execute (stage :capability). Without
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/insika/tool_assembly.rb', line 36 def resolve_capabilities(profile, context) return {} if @capability_registry.nil? Array(profile.capabilities).each_with_object({}) do |cap_name, names| provider = @capability_registry.resolve(cap_name, profile: profile, context: context, event_stream: @event_stream) next if provider.kind == :workflow # exposure to the agent loop is a follow-up entry = @tool_registry.entries.find { |e| e.name == provider.impl_name.to_s } if entry.nil? raise CapabilityError, "capability '#{cap_name}' resolveu para impl " \ "'#{provider.impl_name}', not registered in tool_registry" end names[entry.name] ||= cap_name.to_s # the 1st capability to claim an impl wins end end |
#wrap_tools(tools, state, skip_side_effects = []) ⇒ Object
Envelopes each allowed tool (per-call timeout + side-effect recording). The system LoadSkill (configure_chat) is NOT enveloped — it is a system tool with no side-effect and of trivial latency.
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/insika/tool_assembly.rb', line 74 def wrap_tools(tools, state, skip_side_effects = []) timeout = state.profile.limits[:tool_timeout] || 60 install_tool_gate(state) tools.map do |tool| ToolEnvelope.new(tool, state: state, checkpoint_store: @checkpoint_store, tool_registry: @tool_registry, timeout: timeout, skip_side_effects: skip_side_effects, trace_recorder: @tool_trace_store) end end |