pikuri-core 0.0.7 → 0.1.0

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/pikuri/agent/chat_transport.rb +73 -93
  4. data/lib/pikuri/agent/configurator.rb +46 -106
  5. data/lib/pikuri/agent/context_window_detector.rb +44 -85
  6. data/lib/pikuri/agent/control/cancellable.rb +87 -66
  7. data/lib/pikuri/agent/control/interloper.rb +127 -105
  8. data/lib/pikuri/agent/control/step_limit.rb +25 -41
  9. data/lib/pikuri/agent/control.rb +14 -34
  10. data/lib/pikuri/agent/event.rb +123 -188
  11. data/lib/pikuri/agent/extension.rb +118 -94
  12. data/lib/pikuri/agent/extension_context.rb +50 -77
  13. data/lib/pikuri/agent/history.rb +653 -0
  14. data/lib/pikuri/agent/listener/rate_limited.rb +40 -66
  15. data/lib/pikuri/agent/listener/terminal.rb +143 -117
  16. data/lib/pikuri/agent/listener/token_log.rb +101 -140
  17. data/lib/pikuri/agent/listener.rb +23 -43
  18. data/lib/pikuri/agent/listener_list.rb +26 -47
  19. data/lib/pikuri/agent/synthesizer.rb +45 -87
  20. data/lib/pikuri/agent.rb +816 -474
  21. data/lib/pikuri/bundler_env.rb +68 -0
  22. data/lib/pikuri/extractor/html.rb +63 -110
  23. data/lib/pikuri/extractor/passthrough.rb +20 -30
  24. data/lib/pikuri/extractor.rb +93 -154
  25. data/lib/pikuri/file_type.rb +63 -135
  26. data/lib/pikuri/finalizers.rb +32 -47
  27. data/lib/pikuri/paths.rb +104 -13
  28. data/lib/pikuri/ruby_llm_patches.rb +106 -0
  29. data/lib/pikuri/sanitizer.rb +45 -67
  30. data/lib/pikuri/subprocess.rb +75 -119
  31. data/lib/pikuri/testing.rb +296 -0
  32. data/lib/pikuri/tool/calculator.rb +56 -66
  33. data/lib/pikuri/tool/execute_context.rb +42 -0
  34. data/lib/pikuri/tool/fetch.rb +51 -77
  35. data/lib/pikuri/tool/parameters.rb +21 -29
  36. data/lib/pikuri/tool/scraper.rb +55 -97
  37. data/lib/pikuri/tool/search/brave.rb +52 -80
  38. data/lib/pikuri/tool/search/duckduckgo.rb +59 -91
  39. data/lib/pikuri/tool/search/engines.rb +230 -97
  40. data/lib/pikuri/tool/search/exa.rb +56 -90
  41. data/lib/pikuri/tool/search/rate_limiter.rb +61 -38
  42. data/lib/pikuri/tool/search/result.rb +10 -15
  43. data/lib/pikuri/tool/trifecta_legs.rb +217 -0
  44. data/lib/pikuri/tool/web_scrape.rb +38 -54
  45. data/lib/pikuri/tool/web_search.rb +100 -24
  46. data/lib/pikuri/tool.rb +140 -65
  47. data/lib/pikuri/trifecta/contribution.rb +43 -0
  48. data/lib/pikuri/trifecta/node.rb +47 -0
  49. data/lib/pikuri/trifecta/report.rb +230 -0
  50. data/lib/pikuri/trifecta.rb +127 -0
  51. data/lib/pikuri/url_cache.rb +33 -49
  52. data/lib/pikuri/version.rb +1 -1
  53. data/lib/pikuri-core.rb +72 -88
  54. data/prompts/agent-loop.txt +5 -0
  55. data/prompts/pikuri-chat.txt +3 -12
  56. metadata +14 -3
@@ -3,48 +3,37 @@
3
3
  module Pikuri
4
4
  class Agent
5
5
  # Capability facade handed to {Extension#bind} and
6
- # {Extension#on_user_message} — the runtime counterpart of
7
- # {Configurator}. Where the Configurator collects *build-time*
8
- # declarations (tools, listeners, prompt snippets), this object
9
- # grants the *runtime* capabilities an extension needs once the
10
- # agent is fully wired: emitting domain events onto the listener
11
- # stream, registering raw per-agent tools, and deriving sub-agent
12
- # listener lists.
6
+ # {Extension#on_user_message} — the runtime counterpart of {Configurator}.
7
+ # Where the Configurator collects *build-time* declarations, this grants
8
+ # the *runtime* capabilities an extension needs once the agent is wired:
9
+ # emitting domain events, registering raw per-agent tools, deriving
10
+ # sub-agent listener lists.
13
11
  #
14
12
  # == Why a handed object, not a getter on Agent
15
13
  #
16
- # The {Agent} deliberately exposes NO public path to these
17
- # capabilities — no +listeners+ reader, no +chat+ reader, no
18
- # emit method. Holding an agent reference grants read access to
19
- # its configuration ({Agent#tools}, {Agent#transport}, ...) and
20
- # nothing more; the write capabilities live here, and the only
21
- # way to obtain this object is to be an {Extension} receiving a
22
- # +bind+ / +on_user_message+ call (or to be handed it onward by
23
- # one, e.g. {Pikuri::SubAgent::SubAgentTool} and
24
- # {Pikuri::Mcp::Servers::Connect} both capture the context their
25
- # extension's +bind+ received). Capabilities flow by explicit
26
- # handoff, never by fetching from a globally reachable object.
27
- #
28
- # The usual Ruby caveat applies: nothing here is mechanically
29
- # sealed (+instance_variable_get+ exists). The boundary is the
30
- # API contract — same as every seam in CLAUDE.md.
14
+ # {Agent} exposes NO public path to these capabilities (no +listeners+ /
15
+ # +chat+ reader, no emit method): holding an agent grants read access to
16
+ # its config and nothing more. The only way to obtain this object is to be
17
+ # an {Extension} receiving +bind+ / +on_user_message+ — or to be handed it
18
+ # onward by one ({Pikuri::SubAgent::SubAgentTool} and
19
+ # {Pikuri::Mcp::Servers::Connect} both capture the context their +bind+
20
+ # received). Capabilities flow by explicit handoff, never by fetching from
21
+ # a globally reachable object. The boundary is the API contract, not a
22
+ # mechanical seal — same as every seam in CLAUDE.md.
31
23
  #
32
24
  # == Boundary rule
33
25
  #
34
- # Operations that *act on* the live agent's wiring live here.
35
- # Passive readers of constructor-given config (+transport+,
36
- # +id+, +streaming+, +tools+, +cancellable+, ...) stay on
37
- # {Agent}, reachable via {#agent}. Don't move readers in; don't
38
- # add capabilities to Agent.
26
+ # Operations that *act on* the live agent's wiring live here; passive
27
+ # readers of constructor-given config (+transport+, +id+, +tools+, ...)
28
+ # stay on {Agent}, reachable via {#agent}. Don't move readers in; don't add
29
+ # capabilities to Agent.
39
30
  #
40
31
  # == Audit
41
32
  #
42
- # One context per agent, constructed by {Agent#initialize} right
43
- # before the extension +bind+ sweep. {ListenerList#emit} has
44
- # exactly two callers: {Agent} (loop narration) and this class
45
- # (extension domain events). The roster of capability users is
46
- # +grep -rn 'emit_event\|add_raw_tool\|sub_agent_listeners'
47
- # pikuri-*/lib/+.
33
+ # One context per agent, built by {Agent#initialize} before the +bind+
34
+ # sweep. {ListenerList#emit} has exactly two callers: {Agent} (loop
35
+ # narration) and this class (domain events). Capability-user roster:
36
+ # +grep -rn 'emit_event\|add_raw_tool\|sub_agent_listeners' pikuri-*/lib/+.
48
37
  class ExtensionContext
49
38
  # @param agent [Agent] the live, fully wired agent.
50
39
  # @param chat [RubyLLM::Chat] the agent's underlying chat —
@@ -65,44 +54,33 @@ module Pikuri
65
54
  # configuration (tools, transport, id, streaming, ...).
66
55
  attr_reader :agent
67
56
 
68
- # Emit a domain event onto the agent's listener stream.
69
- #
70
- # Core {Event} variants narrate the chat loop and are emitted
71
- # by {Agent} alone; gems define their own variants (e.g.
72
- # +Pikuri::Tasks::ListChanged+) in their own namespace and
73
- # emit them here. Listeners must no-op on variants they don't
74
- # recognize — {Listener::Base#on_event}'s default and
75
- # +case+-fallthrough give that for free.
57
+ # Emit a domain event onto the agent's listener stream. Core {Event}
58
+ # variants narrate the loop ({Agent}-emitted); gems define their own
59
+ # (e.g. +Pikuri::Tasks::ListChanged+) and emit them here. Listeners must
60
+ # no-op on variants they don't recognize (the {Listener::Base#on_event}
61
+ # default gives that for free).
76
62
  #
77
- # Called on the agent's thread (typically from inside a tool's
78
- # +execute+, where the event lands between {Event::ToolCall}
79
- # and {Event::ToolResult} in the stream). Listeners doing
80
- # cross-thread handoff snapshot/serialize inside +on_event+.
63
+ # Called on the agent's thread (typically inside a tool's +execute+,
64
+ # landing between {Event::ToolCall} and {Event::ToolResult}); listeners
65
+ # doing cross-thread handoff snapshot inside +on_event+.
81
66
  #
82
- # @param event [Object] an immutable event value (by
83
- # convention a +Data+ instance).
67
+ # @param event [Object] an immutable event value (by convention a +Data+)
84
68
  # @return [void]
85
69
  def emit_event(event)
86
70
  @listeners.emit(event)
87
71
  nil
88
72
  end
89
73
 
90
- # Register a raw +RubyLLM::Tool+ subclass on the agent's
91
- # underlying chat, bypassing the {Pikuri::Tool}
92
- # strict-validation seam — hence "raw": native pikuri tools
93
- # should go through {Pikuri::Tool} (registered at build time
94
- # via {Configurator#add_tool}) so they get {Tool::Parameters}
95
- # validation and the LLM-actionable +"Error: ..."+ contract.
96
- # Intended callers: {Pikuri::Mcp::Servers} (MCP tools
97
- # deliberately bypass — see IDEAS.md §"MCP tools bypass
98
- # +Pikuri::Tool+ entirely") and
99
- # {Pikuri::SubAgent::Extension} (the +agent+ tool must
100
- # register after the parent's tool list is final).
74
+ # Register a raw +RubyLLM::Tool+ subclass on the agent's chat, bypassing
75
+ # the {Pikuri::Tool} strict-validation seam — hence "raw" (native tools
76
+ # go through {Configurator#add_tool} for {Tool::Parameters} validation).
77
+ # Callers: {Pikuri::Mcp::Servers} (MCP tools deliberately bypass) and
78
+ # {Pikuri::SubAgent::Extension} (the +agent+ tool registers after the
79
+ # parent's tool list is final).
101
80
  #
102
- # The added tool does NOT enter {Agent#tools}, only the chat's
103
- # tool list. Sub-agents therefore cannot snapshot it — which
104
- # is the whole point: activation is strictly per-agent, see
105
- # IDEAS.md §"Per-agent activation, no propagation".
81
+ # The tool does NOT enter {Agent#tools}, only the chat's list — so
82
+ # sub-agents can't snapshot it, which is the point: activation is strictly
83
+ # per-agent.
106
84
  #
107
85
  # @param ruby_llm_tool [Class] subclass of +RubyLLM::Tool+
108
86
  # @return [void]
@@ -112,27 +90,22 @@ module Pikuri
112
90
  end
113
91
 
114
92
  # Derive a listener list for a spawned sub-agent via
115
- # {ListenerList#for_sub_agent}. Sole intended caller:
116
- # {Pikuri::SubAgent::SubAgentTool}, once per spawn.
117
- #
118
- # The derived list deliberately aliases the parent's listener
119
- # *instances* where a listener opts to share by reference
120
- # (stateful sinks like {Listener::InMemoryEventList}) — see
121
- # {ListenerList#for_sub_agent} for the per-listener semantics.
93
+ # {ListenerList#for_sub_agent}. Sole caller:
94
+ # {Pikuri::SubAgent::SubAgentTool}, once per spawn. The derived list
95
+ # aliases the parent's listener *instances* where a listener opts to
96
+ # share by reference — see {ListenerList#for_sub_agent}.
122
97
  #
123
- # @param params [Hash{Symbol => Object}] forwarded to each
124
- # listener's +for_sub_agent+ hook (currently +id:+).
98
+ # @param params [Hash{Symbol => Object}] forwarded to each listener's
99
+ # +for_sub_agent+ hook (currently +id:+).
125
100
  # @return [ListenerList]
126
101
  def sub_agent_listeners(**params)
127
102
  @listeners.for_sub_agent(**params)
128
103
  end
129
104
 
130
- # Register a handler called by {Agent#close}. Symmetric to
131
- # {Configurator#on_close} — same LIFO + per-handler-rescue +
132
- # idempotent semantics — but available post-construction, so
133
- # an {Extension}'s +bind+ can install per-agent cleanup keyed
134
- # to this specific agent (e.g. +Pikuri::Memory::Extension+
135
- # arms its recorder's bounded flush here).
105
+ # Register a handler called by {Agent#close} — symmetric to
106
+ # {Configurator#on_close} (LIFO, per-handler rescue, idempotent) but
107
+ # available post-construction, so an {Extension}'s +bind+ can install
108
+ # per-agent cleanup (Memory arms its recorder's bounded flush here).
136
109
  #
137
110
  # @yield called with no arguments at close time
138
111
  # @return [void]