pikuri-core 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9d64fe3a6b311841de49b193f6caa92cdb2ba0e627ef0195c73824d803fcafa
4
- data.tar.gz: fa1112813faf7c4d6d162d467b4977c7b800bc647d35c0fe6fec79a1f30e6112
3
+ metadata.gz: f26e9b56204d1fbbaf64390e643a26c4183b3c21d45e3dcc43984c677a09f400
4
+ data.tar.gz: e657171d9440ef19a53ae5ad9335c62ff34ea8db62c9753ce79352f699122f06
5
5
  SHA512:
6
- metadata.gz: f9b5618853d0501a417fbcfd021e1dd65fe45fef42331d9f8b4f1c8ab273a91437f433b7065512b46340d98670e53127cb49a5149eb7078750bd99a0e6ad22fa
7
- data.tar.gz: a9ce6040b23bcd4a2eb4f11bc10c50cd64ad8fdba685e2eba51f9181a3f3503617adb230b0601257e3f0ec3122c14d1a12b5091ab03b7612f906d72c54938189
6
+ metadata.gz: 4f53aef4566f6218750c58b0cac4d1015d9ae2d2a1d464481ff4eabae3d42eb560559517a105ccd6fb2128e0b8e1b9393fdea257757dd6c85af0dc7a47683ed3
7
+ data.tar.gz: ba4adbf32911a499111eaf26057622e94fa27511aaefb5f9fb705ac3471a20d860536d1717e6933ec3e2b55af152fbfb8b479f99e11e44ea9ad907dd1c666a66
data/README.md CHANGED
@@ -65,3 +65,13 @@ agent.run_loop(user_message: 'What is 17 * 23?')
65
65
 
66
66
  See `bin/pikuri-chat` for a worked example with REPL, signal
67
67
  handling, and cancellation.
68
+
69
+ ## Further reading
70
+
71
+ - **Narrative walkthrough:** [chapter 1 of the pikuri guide](../docs/guide/01-chat.md)
72
+ — install llama.cpp, start the server, run `pikuri-chat`, the
73
+ agentic loop, the four bundled tools, and search-provider
74
+ privacy postures.
75
+ - **API reference:** browse the YARD docs at
76
+ <https://rubydoc.info/gems/pikuri-core> (once published), or run
77
+ `bundle exec yard` in this directory for a local copy.
@@ -8,11 +8,12 @@ module Pikuri
8
8
  #
9
9
  # Bundling them is structural protection against a recurring bug
10
10
  # class — every forwarding site (the synthesizer rescue in
11
- # {Agent#run_loop}, {Tool::SubAgent} spawning a sub-agent) used to
12
- # pass the three individually, and dropping one routed the spawned
13
- # chat to a different server or raised +RubyLLM::ModelNotFoundError+
14
- # on the unknown model id. With a single value object the call site
15
- # can't silently miss a field.
11
+ # {Agent#run_loop}, the +agent+ tool from +pikuri-subagents+
12
+ # spawning a sub-agent) used to pass the three individually, and
13
+ # dropping one routed the spawned chat to a different server or
14
+ # raised +RubyLLM::ModelNotFoundError+ on the unknown model id.
15
+ # With a single value object the call site can't silently miss a
16
+ # field.
16
17
  #
17
18
  # Pure data carrier: no +RubyLLM+ references here, so the seam stays
18
19
  # in {Agent}, +bin/pikuri-chat+, and {Tool}.
@@ -5,8 +5,9 @@ module Pikuri
5
5
  # Build-time collector yielded into the +Pikuri::Agent.new+ block.
6
6
  # Hosts and {Extension} implementations call its methods to declare
7
7
  # additional tools, listeners, system-prompt snippets, +on_close+
8
- # handlers, and extension instances; {Agent#initialize} drains the
9
- # collected state into the agent's final wiring before returning.
8
+ # handlers, extension instances, and persona-flavored sub-agents;
9
+ # {Agent#initialize} drains the collected state into the agent's
10
+ # final wiring before returning.
10
11
  #
11
12
  # == Why this exists
12
13
  #
@@ -15,10 +16,27 @@ module Pikuri
15
16
  #
16
17
  # Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
17
18
  # c.add_listener Pikuri::Agent::Listener::Terminal.new
18
- # c.add_tool Pikuri::Tool::CALCULATOR
19
+ # c.add_tool Pikuri::Tool::WEB_SEARCH
20
+ # c.add_tool Pikuri::Tool::WEB_SCRAPE
21
+ # c.add_tool Pikuri::Tool::FETCH
19
22
  # c.add_extension Pikuri::Skill::Extension.new(catalog: catalog)
20
23
  # end
21
24
  #
25
+ # == Two tool pools: regular vs. sub-agent-only
26
+ #
27
+ # {#add_tool} registers a tool the parent agent can call: it lands
28
+ # in {#tools} and gets handed to ruby_llm via +chat.with_tool+.
29
+ # {#add_sub_agent_tool} registers a tool the parent *cannot* call
30
+ # — it lands in {#sub_agent_tools} and is never sent to ruby_llm
31
+ # for the parent, but is visible to {Pikuri::SubAgent::Extension}'s
32
+ # persona-tool-name resolution. The use case is the lethal-trifecta
33
+ # defense in {Pikuri::Code::Bash::Sandbox} terms: keep network tools
34
+ # (+web_search+ / +web_scrape+ / +fetch+) off the parent so a prompt-
35
+ # injected file read cannot egress through the parent's own tools,
36
+ # while still letting the +researcher+ persona reach them via the
37
+ # +agent+ delegation tool. See SECURITY.md §"Defense: capability
38
+ # boundaries via sub-agents".
39
+ #
22
40
  # Extensions implement their +configure(c)+ hook against the same
23
41
  # type, so the call sites for "block users add stuff" and
24
42
  # "extensions add stuff" share one API.
@@ -44,9 +62,9 @@ module Pikuri
44
62
  # available for diagnostics.
45
63
  attr_reader :system_prompt_base
46
64
 
47
- # @return [String] this agent's identifier; empty for the main
48
- # agent, hierarchical (+"sub_agent 0_1"+) for sub-agents.
49
- attr_reader :name
65
+ # @return [String] this agent's unique identifier; empty for the
66
+ # main agent, persona-rooted (e.g. +"researcher 0"+) for sub-agents.
67
+ attr_reader :id
50
68
 
51
69
  # @return [Boolean] +true+ when the agent opted into chunk-level
52
70
  # streaming.
@@ -68,9 +86,18 @@ module Pikuri
68
86
  attr_reader :interloper
69
87
 
70
88
  # @return [Array<Tool>] tools added via {#add_tool}, in
71
- # declaration order. Drained by {Agent#initialize}.
89
+ # declaration order. Drained by {Agent#initialize} and
90
+ # registered with ruby_llm so the parent LLM can call them.
72
91
  attr_reader :tools
73
92
 
93
+ # @return [Array<Tool>] tools added via {#add_sub_agent_tool},
94
+ # in declaration order. Drained by {Agent#initialize} but
95
+ # *not* registered with ruby_llm — invisible to the parent
96
+ # LLM, available only to sub-agents through
97
+ # {Pikuri::SubAgent::Extension}'s persona-tool-name
98
+ # resolution. See the class header.
99
+ attr_reader :sub_agent_tools
100
+
74
101
  # @return [Array<Listener::Base>] listeners added via
75
102
  # {#add_listener}, in declaration order. Drained by
76
103
  # {Agent#initialize}.
@@ -93,38 +120,25 @@ module Pikuri
93
120
  # wiring is complete.
94
121
  attr_reader :extensions
95
122
 
96
- # @return [SubAgentRequest, nil] set when the block called
97
- # {#allow_sub_agent}; +nil+ otherwise. The Agent ctor uses
98
- # this to decide whether to create a {Tool::SubAgent}
99
- # instance after the chat is wired (so the SubAgent tool's
100
- # parent.tools snapshot doesn't include itself —
101
- # recursion guard).
102
- attr_reader :sub_agent_request
103
-
104
- # Record holding the +max_steps+ value the host passed to
105
- # {#allow_sub_agent}. The Agent ctor consumes one of these
106
- # records to build a {Tool::SubAgent} keyed to that step
107
- # budget.
108
- SubAgentRequest = Data.define(:max_steps)
109
-
110
123
  # @param transport [Agent::ChatTransport]
111
124
  # @param system_prompt_base [String]
112
- # @param name [String]
125
+ # @param id [String]
113
126
  # @param streaming [Boolean]
114
127
  # @param step_limit [Control::StepLimit, nil]
115
128
  # @param cancellable [Control::Cancellable, nil]
116
129
  # @param interloper [Control::Interloper, nil]
117
- def initialize(transport:, system_prompt_base:, name:, streaming:,
130
+ def initialize(transport:, system_prompt_base:, id:, streaming:,
118
131
  step_limit:, cancellable:, interloper:)
119
132
  @transport = transport
120
133
  @system_prompt_base = system_prompt_base
121
- @name = name
134
+ @id = id
122
135
  @streaming = streaming
123
136
  @step_limit = step_limit
124
137
  @cancellable = cancellable
125
138
  @interloper = interloper
126
139
 
127
140
  @tools = []
141
+ @sub_agent_tools = []
128
142
  @listeners = []
129
143
  @system_prompt_additions = []
130
144
  @on_close_handlers = []
@@ -142,8 +156,6 @@ module Pikuri
142
156
 
143
157
  # Append several tools at once. Equivalent to calling {#add_tool}
144
158
  # for each element of +tools+; declaration order is preserved.
145
- # Sole intended caller today is {Tool::SubAgent}, which seeds a
146
- # sub-agent's Configurator from the parent's tool snapshot.
147
159
  #
148
160
  # @param tools [Enumerable<Tool>]
149
161
  # @return [void]
@@ -152,6 +164,18 @@ module Pikuri
152
164
  nil
153
165
  end
154
166
 
167
+ # Append a tool to the sub-agent-only pool. The parent LLM
168
+ # never sees it; only sub-agents whose persona +tool_names+
169
+ # include the tool's +name+ get it in their toolset. See the
170
+ # class header for the trifecta-defense rationale.
171
+ #
172
+ # @param tool [Tool]
173
+ # @return [void]
174
+ def add_sub_agent_tool(tool)
175
+ @sub_agent_tools << tool
176
+ nil
177
+ end
178
+
155
179
  # Append a listener to the agent's listener list.
156
180
  #
157
181
  # @param listener [Listener::Base]
@@ -163,9 +187,6 @@ module Pikuri
163
187
 
164
188
  # Append several listeners at once. Accepts any enumerable
165
189
  # (Array, {ListenerList}, …); declaration order is preserved.
166
- # Sole intended caller today is {Tool::SubAgent}, which seeds a
167
- # sub-agent's Configurator from the parent's listener list (run
168
- # through {ListenerList#for_sub_agent}).
169
190
  #
170
191
  # @param listeners [Enumerable<Listener::Base>]
171
192
  # @return [void]
@@ -207,26 +228,6 @@ module Pikuri
207
228
  nil
208
229
  end
209
230
 
210
- # Retain a list of already-configured extensions for the
211
- # bind sweep without re-running +configure+. Sole intended
212
- # caller is {Tool::SubAgent}, which seeds a sub-agent's
213
- # Configurator from the parent's extension list — the parent
214
- # already drove +configure+ and its system-prompt snippets /
215
- # static tools are inherited by the sub-agent verbatim
216
- # through {#add_tools} + {#add_listeners} + the augmented
217
- # +system_prompt+; re-running +configure+ on the sub-agent
218
- # would double up those contributions. The +bind(sub_agent)+
219
- # sweep in {Agent#initialize} still fires on each inherited
220
- # extension so per-agent state (MCP's per-agent connect tool,
221
- # for example) gets installed fresh on the sub-agent.
222
- #
223
- # @param extensions [Enumerable<Extension>]
224
- # @return [void]
225
- def inherit_extensions(extensions)
226
- extensions.each { |ext| @extensions << ext }
227
- nil
228
- end
229
-
230
231
  # Register a handler called by {Agent#close}. Handlers fire in
231
232
  # LIFO order, each inside its own +rescue+ — same semantics as
232
233
  # +ensure+-block cleanup discipline.
@@ -239,32 +240,6 @@ module Pikuri
239
240
  @on_close_handlers << blk
240
241
  nil
241
242
  end
242
-
243
- # Enable the +sub_agent+ tool on this agent. Records the
244
- # +max_steps+ budget for sub-agent runs; the Agent ctor reads
245
- # {#sub_agent_request} after the block returns and constructs
246
- # the actual {Tool::SubAgent} so its snapshot of the parent's
247
- # tool list doesn't include itself (recursion guard).
248
- #
249
- # Sub-agents inherit the parent's tool list (minus +sub_agent+
250
- # itself), augmented system prompt, listeners (via
251
- # +for_sub_agent+ per listener), controls (per the per-control
252
- # rule), and the parent's extension list. Each inherited
253
- # extension's +bind(sub_agent)+ fires during the sub-agent's
254
- # construction — that's how MCP's per-agent connect tool ends
255
- # up keyed to the sub-agent rather than the parent.
256
- #
257
- # @param max_steps [Integer] step budget for each sub-agent
258
- # run, passed to {Tool::SubAgent#initialize}.
259
- # @raise [RuntimeError] if called more than once on the same
260
- # Configurator.
261
- # @return [void]
262
- def allow_sub_agent(max_steps: 10)
263
- raise 'allow_sub_agent may only be called once per agent' if @sub_agent_request
264
-
265
- @sub_agent_request = SubAgentRequest.new(max_steps: max_steps)
266
- nil
267
- end
268
243
  end
269
244
  end
270
245
  end
@@ -40,13 +40,13 @@ module Pikuri
40
40
  #
41
41
  # == Sub-agent semantics
42
42
  #
43
- # {#for_sub_agent} returns +self+ the same instance is
44
- # shared by reference across the parent, every sub-agent,
45
- # and the synthesizer rescue. One {#cancel!} call stops the
46
- # whole tree. This contrasts with {StepLimit}, which gives
47
- # each agent its own counter (because step budgets are
48
- # per-agent concerns) cancellation is a global signal
49
- # from the user, so it must propagate down.
43
+ # Cancellation is a global "stop the whole tree" signal —
44
+ # the +agent+ tool from +pikuri-subagents+ shares the
45
+ # parent's +Cancellable+ by reference when spawning a child,
46
+ # so one {#cancel!} call stops the parent, every running
47
+ # sub-agent, and the synthesizer rescue. The sharing rule
48
+ # lives at the spawn site (sub-agent code), not on this
49
+ # class.
50
50
  class Cancellable
51
51
  # Raised by {#check!} once {#cancel!} has been called.
52
52
  # Carries no fields; the cancellation reason ("the user
@@ -105,16 +105,6 @@ module Pikuri
105
105
  @cancelled = false
106
106
  end
107
107
 
108
- # Sub-agent variant: the same instance, shared by
109
- # reference, so a single {#cancel!} stops the parent,
110
- # every running sub-agent, and the synthesizer rescue.
111
- # See the class header for the rationale.
112
- #
113
- # @return [Cancellable] the receiver
114
- def for_sub_agent(**)
115
- self
116
- end
117
-
118
108
  # @return [String] short label for {Agent#to_s}; reflects
119
109
  # the current flag state so a startup banner or debug
120
110
  # print can tell an armed token apart from one that has
@@ -70,16 +70,16 @@ module Pikuri
70
70
  #
71
71
  # == Sub-agent semantics
72
72
  #
73
- # {#for_sub_agent} returns +nil+. Sub-agents are private to
74
- # the parent agent; the host has no handle to them, so a
75
- # child +Interloper+ would be unreachable. The sub-agent's
76
- # {Agent#initialize} simply receives +interloper: nil+ from
77
- # {Tool::SubAgent}, which is its default. The behavior
78
- # contrasts with {Control::Cancellable}, which shares its
79
- # instance by reference so the parent's signal propagates
80
- # to children — cancellation is a global "stop the whole
81
- # tree" event, whereas injection is a directed "talk to the
82
- # main agent" event.
73
+ # Sub-agents are private to the parent agent; the host has
74
+ # no handle to them, so a child +Interloper+ would be
75
+ # unreachable. The +agent+ tool from +pikuri-subagents+
76
+ # therefore omits the kwarg when spawning a child, leaving
77
+ # the sub-agent's +interloper+ at its default +nil+. The
78
+ # behavior contrasts with {Control::Cancellable}, which is
79
+ # shared by reference so the parent's signal propagates to
80
+ # children — cancellation is a global "stop the whole tree"
81
+ # event, whereas injection is a directed "talk to the main
82
+ # agent" event.
83
83
  class Interloper
84
84
  def initialize
85
85
  @mutex = Mutex.new
@@ -143,17 +143,6 @@ module Pikuri
143
143
  end
144
144
  end
145
145
 
146
- # Sub-agent variant: +nil+, signalling to {Agent} (and
147
- # transitively to {Tool::SubAgent}) that no +Interloper+
148
- # should be wired on a spawned sub-agent. See the class
149
- # header for the "host has no handle to sub-agents"
150
- # rationale.
151
- #
152
- # @return [nil]
153
- def for_sub_agent(**)
154
- nil
155
- end
156
-
157
146
  # @return [String] short label for {Agent#to_s}; reflects
158
147
  # the pending-count so a debug print or banner can tell
159
148
  # an idle interloper apart from one with queued items
@@ -69,20 +69,6 @@ module Pikuri
69
69
  # can introspect it (and so tests can assert it)
70
70
  attr_reader :step
71
71
 
72
- # Sub-agent variant: a fresh +StepLimit+ at the
73
- # caller-supplied +max_steps:+, or — when the key is
74
- # absent — at the receiver's own cap. The mutable counter
75
- # is per-chat, so the parent's instance cannot govern a
76
- # sub-agent's chat; every sub-agent needs its own.
77
- #
78
- # @param max_steps [Integer] positive step cap for the
79
- # sub-agent; defaults to the receiver's current cap
80
- # @return [StepLimit]
81
- # @raise [ArgumentError] if +max_steps+ is non-positive
82
- def for_sub_agent(max_steps: @max)
83
- self.class.new(max: max_steps)
84
- end
85
-
86
72
  # @return [String] short config dump for {Agent#to_s}
87
73
  def to_s
88
74
  "StepLimit(max=#{@max})"
@@ -57,22 +57,20 @@ module Pikuri
57
57
 
58
58
  # Called by {Agent#initialize} after the block returns and the
59
59
  # chat is fully wired, with the live {Agent} as the argument.
60
- # Runs once per agent on the parent during its construction,
61
- # and once more on each sub-agent during the sub-agent's
62
- # construction (same extension instance, multiple +bind+ calls
63
- # per-agent state lives in +bind+'s closures, not in
64
- # extension instance state). The default is a no-op; override
65
- # when you need to install *per-agent* state. Things you
66
- # typically do here:
60
+ # Fires once per agent the extension was registered to via
61
+ # {Configurator#add_extension} in the typical setup that's
62
+ # the parent agent only, since sub-agents do not inherit
63
+ # extensions. The default is a no-op; override when you need
64
+ # to install state keyed to the live agent object. Things
65
+ # you typically do here:
67
66
  #
68
- # * register per-agent dynamic tools via
69
- # {Agent#internal_add_tool}
70
- # * register per-agent +on_close+ handlers via
71
- # {Agent#on_close}
67
+ # * register dynamic tools via {Agent#internal_add_tool}
68
+ # (used by {Pikuri::Mcp::Extension} for +mcp_connect+,
69
+ # whose +execute+ closure needs the live agent so
70
+ # activations register on the right chat)
71
+ # * register +on_close+ handlers via {Agent#on_close}
72
72
  # * stash an +@agent+ reference if the extension's tools need
73
- # to act on this specific agent later (e.g. when a tool
74
- # fires and wants to register more tools on its owning
75
- # chat)
73
+ # to act on this specific agent later
76
74
  #
77
75
  # @param agent [Agent] the live agent, fully wired
78
76
  # @return [void]
@@ -41,10 +41,10 @@ module Pikuri
41
41
  #
42
42
  # msg #1: ctx=6.8k/32.0k Δ+6.8k ↑6.8k ↓0.0k
43
43
  #
44
- # When the owning {Agent} has a non-empty {Agent#name} (i.e.
45
- # a sub-agent), the line is prefixed with +[name] +:
44
+ # When the owning {Agent} has a non-empty {Agent#id} (i.e. a
45
+ # sub-agent), the line is prefixed with +[id] +:
46
46
  #
47
- # [sub_agent 0] msg #1: ctx=4.2k Δ+4.2k ↑4.2k ↓0.0k
47
+ # [researcher 0] msg #1: ctx=4.2k Δ+4.2k ↑4.2k ↓0.0k
48
48
  #
49
49
  # +ctx+ is the snapshot
50
50
  # (+input + cached + cache_creation + output+; see
@@ -89,16 +89,15 @@ module Pikuri
89
89
  # @return [Integer, nil]
90
90
  attr_accessor :context_window_cap
91
91
 
92
- # @return [String] owning agent's identifier. Empty by
92
+ # @return [String] owning agent's id ({Agent#id}). Empty by
93
93
  # default (main agent); set by {#for_sub_agent} from the
94
- # sub-agent's generated name so the log lines can be
95
- # prefixed with +[<name>] +. Read-only — for a
96
- # sub-agent's listener you get a fresh instance via
97
- # {#for_sub_agent}.
98
- attr_reader :name
94
+ # sub-agent's generated id so the log lines can be
95
+ # prefixed with +[<id>] +. Read-only — for a sub-agent's
96
+ # listener you get a fresh instance via {#for_sub_agent}.
97
+ attr_reader :id
99
98
 
100
99
  # The most recent log line, in the exact format written to
101
- # {LOGGER} (including any +[<name>] + prefix). Empty until
100
+ # {LOGGER} (including any +[<id>] + prefix). Empty until
102
101
  # the first {Event::Tokens} has been processed. Hosts that
103
102
  # want to surface the current context-window snapshot in
104
103
  # their own UI (e.g. a TUI status footer) read this
@@ -113,12 +112,12 @@ module Pikuri
113
112
  # @return [String]
114
113
  attr_reader :status_line
115
114
 
116
- # @param name [String] agent identifier prepended to each
117
- # log line as +[<name>] + when non-empty. Defaults to
118
- # +""+ for the main agent.
119
- def initialize(name: '')
115
+ # @param id [String] owning agent's id, prepended to each
116
+ # log line as +[<id>] + when non-empty. Defaults to +""+
117
+ # for the main agent.
118
+ def initialize(id: '')
120
119
  super()
121
- @name = name
120
+ @id = id
122
121
  @msg = 0
123
122
  @context_window_size = 0
124
123
  @context_window_cap = nil
@@ -128,17 +127,17 @@ module Pikuri
128
127
  # Sub-agent variant: a fresh +TokenLog+ with a zeroed
129
128
  # snapshot so the sub-agent's context-window readings
130
129
  # track its own +RubyLLM::Chat+ rather than continuing the
131
- # parent's. Picks the sub-agent's +name:+ out of the
132
- # forwarded params so its log lines carry the +[<name>] +
130
+ # parent's. Picks the sub-agent's +id:+ out of the
131
+ # forwarded params so its log lines carry the +[<id>] +
133
132
  # prefix; defaults to +""+ when absent. The cap is left
134
133
  # +nil+ here; the sub-agent's {Agent#initialize} emits a
135
134
  # fresh {Event::ContextCap} immediately after construction
136
135
  # and this listener picks it up off the stream.
137
136
  #
138
- # @param name [String] sub-agent's identifier
137
+ # @param id [String] sub-agent's id
139
138
  # @return [TokenLog]
140
- def for_sub_agent(name: '', **)
141
- self.class.new(name: name)
139
+ def for_sub_agent(id: '', **)
140
+ self.class.new(id: id)
142
141
  end
143
142
 
144
143
  # @param event [Agent::Event]
@@ -184,7 +183,7 @@ module Pikuri
184
183
 
185
184
  def format_line(input, output, delta)
186
185
  sign = delta.negative? ? '-' : '+'
187
- prefix = @name.empty? ? '' : "[#{@name}] "
186
+ prefix = @id.empty? ? '' : "[#{@id}] "
188
187
  "#{prefix}msg ##{@msg}: ctx=#{format_ctx} Δ#{sign}#{format_k(delta.abs)} ↑#{format_k(input)} ↓#{format_k(output)}"
189
188
  end
190
189
 
@@ -35,8 +35,9 @@ module Pikuri
35
35
 
36
36
  # Iterate over the wrapped listeners in registration order. The
37
37
  # method exists so a ListenerList can be passed directly to
38
- # {Configurator#add_listeners} (used by {Tool::SubAgent} when
39
- # seeding a sub-agent's Configurator from the parent's list).
38
+ # {Configurator#add_listeners} (used by the +agent+ tool from
39
+ # +pikuri-subagents+ when seeding a sub-agent's Configurator
40
+ # from the parent's list).
40
41
  #
41
42
  # @yield [listener]
42
43
  # @yieldparam listener [Listener::Base]
@@ -59,13 +60,14 @@ module Pikuri
59
60
  # change this class — see {Listener::Terminal#for_sub_agent}
60
61
  # (fresh padded instance) and
61
62
  # {Listener::TokenLog#for_sub_agent} (fresh, zeroed snapshot
62
- # with the forwarded +name:+).
63
+ # with the forwarded +id:+).
63
64
  #
64
65
  # +params+ is a flat hash forwarded as kwargs to every
65
66
  # listener's hook; each listener picks the keys it cares about
66
67
  # and ignores the rest. The only key currently consumed by
67
- # bundled listeners is +name:+ (used by {Listener::TokenLog}).
68
- # Calling with no params is always valid.
68
+ # bundled listeners is +id:+ (used by {Listener::TokenLog} to
69
+ # prefix its log lines with the sub-agent's id). Calling with
70
+ # no params is always valid.
69
71
  #
70
72
  # @param params [Hash{Symbol => Object}]
71
73
  # @return [ListenerList]
@@ -45,7 +45,7 @@ module Pikuri
45
45
  # reasoning and answer flow through the same listener
46
46
  # surface the parent agent uses — terminal renders them
47
47
  # inline (padded under sub-agent), an in-memory recorder
48
- # picks them up, a TokenLog tags them with the synth name.
48
+ # picks them up, a TokenLog tags them with the synth id.
49
49
  #
50
50
  # @param chat [RubyLLM::Chat] a *fresh* chat with no tools.
51
51
  # The caller is responsible for constructing it with the
@@ -58,7 +58,7 @@ module Pikuri
58
58
  # @param listeners [Agent::ListenerList] listeners to wire
59
59
  # the synth chat into. Typically the parent agent's list
60
60
  # run through {ListenerList#for_sub_agent} with the
61
- # synth's +name:+ so any +TokenLog+ tags its lines with
61
+ # synth's +id:+ so any +TokenLog+ tags its lines with
62
62
  # the synth bracket and any +Terminal+ pads its output.
63
63
  # @param step_limit [Control::StepLimit, nil] defensive
64
64
  # step budget. The synth has no tools so it should never