samagotchi 0.155.0 → 0.157.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 (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/bin/chi +2 -1
  4. data/docs/architecture.md +22 -13
  5. data/docs/bundles.md +3 -1
  6. data/docs/cli.md +9 -8
  7. data/docs/configuration.md +36 -4
  8. data/docs/guardrails.md +9 -5
  9. data/docs/hooks.md +11 -5
  10. data/docs/internals/anthropic.md +1 -1
  11. data/docs/internals/attached-context.md +3 -2
  12. data/docs/internals/background-tasks.md +1 -1
  13. data/docs/internals/bridge.md +83 -6
  14. data/docs/internals/bundles.md +28 -18
  15. data/docs/internals/context-telemetry.md +4 -3
  16. data/docs/internals/engine.md +641 -937
  17. data/docs/internals/llm-context-forget.md +8 -3
  18. data/docs/internals/objectives.md +2 -2
  19. data/docs/internals/participants.md +3 -2
  20. data/docs/internals/prompt-caching.md +4 -1
  21. data/docs/internals/remote-sessions.md +8 -3
  22. data/docs/internals/web-frontend.md +3 -2
  23. data/docs/internals/working-set.md +2 -2
  24. data/docs/memory.md +13 -2
  25. data/docs/plugins.md +35 -2
  26. data/docs/sessions.md +11 -11
  27. data/docs/sub-agent.md +17 -2
  28. data/docs/system-one.md +3 -2
  29. data/docs/testing.md +53 -2
  30. data/lib/samagotchi/answer_command.rb +2 -1
  31. data/lib/samagotchi/archive_store.rb +4 -3
  32. data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
  33. data/lib/samagotchi/bootstrap_command.rb +3 -2
  34. data/lib/samagotchi/bridge/card_places.rb +10 -4
  35. data/lib/samagotchi/bridge/card_store.rb +3 -0
  36. data/lib/samagotchi/bridge/turn_accumulator.rb +109 -9
  37. data/lib/samagotchi/bridge/turn_drawings.rb +265 -0
  38. data/lib/samagotchi/bridge.rb +31 -6
  39. data/lib/samagotchi/broadcast/recipients.rb +8 -8
  40. data/lib/samagotchi/broadcast/triage_model.rb +1 -1
  41. data/lib/samagotchi/broadcast_command.rb +3 -3
  42. data/lib/samagotchi/bundle_command/check.rb +18 -0
  43. data/lib/samagotchi/bundle_command/try/sandbox.rb +2 -2
  44. data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
  45. data/lib/samagotchi/bundle_command.rb +5 -2
  46. data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
  47. data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
  48. data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
  49. data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
  50. data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
  51. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  52. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  53. data/lib/samagotchi/child_move.rb +13 -57
  54. data/lib/samagotchi/child_reports.rb +2 -2
  55. data/lib/samagotchi/children_status.rb +15 -21
  56. data/lib/samagotchi/cli/parent_wait.rb +9 -23
  57. data/lib/samagotchi/config.rb +20 -112
  58. data/lib/samagotchi/context_command.rb +1 -2
  59. data/lib/samagotchi/context_sources.rb +5 -4
  60. data/lib/samagotchi/context_window.rb +9 -12
  61. data/lib/samagotchi/desktop/macos.rb +1 -1
  62. data/lib/samagotchi/effective_model.rb +57 -22
  63. data/lib/samagotchi/engine/context_judge.rb +19 -11
  64. data/lib/samagotchi/engine/messages.rb +1 -1
  65. data/lib/samagotchi/engine/models.rb +5 -2
  66. data/lib/samagotchi/engine/objectives.rb +29 -11
  67. data/lib/samagotchi/engine/plugins.rb +1 -1
  68. data/lib/samagotchi/engine/recap.rb +8 -4
  69. data/lib/samagotchi/engine/side_requests.rb +21 -11
  70. data/lib/samagotchi/engine/toolkit.rb +10 -9
  71. data/lib/samagotchi/engine/turn_end.rb +1 -1
  72. data/lib/samagotchi/engine/turn_helpers.rb +7 -7
  73. data/lib/samagotchi/engine/verify.rb +1 -1
  74. data/lib/samagotchi/engine.rb +43 -12
  75. data/lib/samagotchi/events.rb +3 -2
  76. data/lib/samagotchi/extension_load.rb +6 -3
  77. data/lib/samagotchi/guardrails/targets.rb +24 -9
  78. data/lib/samagotchi/guardrails_command.rb +1 -1
  79. data/lib/samagotchi/host_address.rb +52 -0
  80. data/lib/samagotchi/host_config.rb +72 -1
  81. data/lib/samagotchi/host_model.rb +2 -6
  82. data/lib/samagotchi/host_registry.rb +58 -46
  83. data/lib/samagotchi/kernel_loop.rb +53 -124
  84. data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
  85. data/lib/samagotchi/llm/chat_loop.rb +40 -21
  86. data/lib/samagotchi/llm/native_backend.rb +1 -1
  87. data/lib/samagotchi/llm_context_apply.rb +54 -1
  88. data/lib/samagotchi/llm_context_notice.rb +1 -1
  89. data/lib/samagotchi/llm_context_strategy.rb +29 -67
  90. data/lib/samagotchi/llm_context_view.rb +9 -0
  91. data/lib/samagotchi/memory_bundle/asset_installer.rb +71 -76
  92. data/lib/samagotchi/memory_bundle/builder.rb +2 -1
  93. data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
  94. data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
  95. data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
  96. data/lib/samagotchi/memory_bundle/installed_copy.rb +2 -1
  97. data/lib/samagotchi/memory_bundle/installed_manifest.rb +8 -1
  98. data/lib/samagotchi/memory_bundle/installer.rb +185 -169
  99. data/lib/samagotchi/memory_bundle/manifest.rb +14 -4
  100. data/lib/samagotchi/memory_bundle/merger.rb +69 -65
  101. data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
  102. data/lib/samagotchi/memory_bundle/profile.rb +5 -5
  103. data/lib/samagotchi/memory_bundle/provenance.rb +15 -1
  104. data/lib/samagotchi/memory_bundle/remerge.rb +25 -12
  105. data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -6
  106. data/lib/samagotchi/memory_bundle/status.rb +14 -20
  107. data/lib/samagotchi/memory_bundle/system_bundle.rb +24 -6
  108. data/lib/samagotchi/memory_bundle/trash.rb +23 -8
  109. data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
  110. data/lib/samagotchi/memory_usage.rb +1 -1
  111. data/lib/samagotchi/model_lookup.rb +75 -0
  112. data/lib/samagotchi/model_price.rb +4 -10
  113. data/lib/samagotchi/model_profile.rb +7 -6
  114. data/lib/samagotchi/model_settings.rb +37 -2
  115. data/lib/samagotchi/models_command.rb +1 -1
  116. data/lib/samagotchi/note_command.rb +2 -3
  117. data/lib/samagotchi/objectives_audit.rb +4 -3
  118. data/lib/samagotchi/participant_mode.rb +2 -1
  119. data/lib/samagotchi/participants.rb +2 -2
  120. data/lib/samagotchi/plugin/api.rb +41 -6
  121. data/lib/samagotchi/plugin/context.rb +1 -1
  122. data/lib/samagotchi/plugin/loader.rb +33 -10
  123. data/lib/samagotchi/plugin/sessions.rb +3 -6
  124. data/lib/samagotchi/plugin/tool_spec.rb +5 -3
  125. data/lib/samagotchi/project_env.rb +1 -1
  126. data/lib/samagotchi/reasoning_history.rb +14 -20
  127. data/lib/samagotchi/recap_place.rb +27 -0
  128. data/lib/samagotchi/reminder_carry.rb +1 -1
  129. data/lib/samagotchi/reply_wait/liveness.rb +38 -0
  130. data/lib/samagotchi/reply_wait/poll.rb +44 -15
  131. data/lib/samagotchi/reply_wait/target.rb +113 -0
  132. data/lib/samagotchi/reply_wait.rb +22 -2
  133. data/lib/samagotchi/reprefill_meter.rb +45 -0
  134. data/lib/samagotchi/review_command.rb +1 -1
  135. data/lib/samagotchi/sampling_settings.rb +9 -16
  136. data/lib/samagotchi/self_report.rb +36 -37
  137. data/lib/samagotchi/send_command.rb +17 -23
  138. data/lib/samagotchi/session.rb +370 -445
  139. data/lib/samagotchi/session_access/local.rb +12 -11
  140. data/lib/samagotchi/session_access/remote.rb +22 -6
  141. data/lib/samagotchi/session_archive_command.rb +2 -2
  142. data/lib/samagotchi/session_chain.rb +7 -6
  143. data/lib/samagotchi/session_continue.rb +6 -5
  144. data/lib/samagotchi/session_delete_command.rb +1 -1
  145. data/lib/samagotchi/session_field.rb +133 -0
  146. data/lib/samagotchi/session_inbox.rb +49 -1
  147. data/lib/samagotchi/session_manager/delivery.rb +254 -0
  148. data/lib/samagotchi/session_manager/emptiness.rb +139 -0
  149. data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
  150. data/lib/samagotchi/session_manager/listing.rb +118 -0
  151. data/lib/samagotchi/session_manager/removal.rb +190 -0
  152. data/lib/samagotchi/session_manager/spawn.rb +242 -0
  153. data/lib/samagotchi/session_manager/worker_command.rb +157 -0
  154. data/lib/samagotchi/session_manager.rb +28 -1541
  155. data/lib/samagotchi/session_metrics.rb +8 -5
  156. data/lib/samagotchi/session_paths.rb +57 -0
  157. data/lib/samagotchi/session_retention.rb +2 -2
  158. data/lib/samagotchi/session_row.rb +200 -0
  159. data/lib/samagotchi/session_setup.rb +15 -12
  160. data/lib/samagotchi/sessions_command.rb +11 -129
  161. data/lib/samagotchi/share_command.rb +1 -1
  162. data/lib/samagotchi/starting_marker.rb +65 -0
  163. data/lib/samagotchi/steer_cut_after.rb +7 -14
  164. data/lib/samagotchi/system_one/recorder.rb +49 -0
  165. data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
  166. data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
  167. data/lib/samagotchi/terminal_ui/join_replay.rb +86 -8
  168. data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
  169. data/lib/samagotchi/terminal_ui.rb +5 -7
  170. data/lib/samagotchi/testing/install.rb +2 -1
  171. data/lib/samagotchi/testing/sessions.rb +1 -1
  172. data/lib/samagotchi/thinking.rb +16 -21
  173. data/lib/samagotchi/token_usage.rb +1 -1
  174. data/lib/samagotchi/tool_activity.rb +10 -4
  175. data/lib/samagotchi/tool_declarations.rb +2 -2
  176. data/lib/samagotchi/tool_output_cut.rb +17 -0
  177. data/lib/samagotchi/tool_response.rb +5 -3
  178. data/lib/samagotchi/tool_runner.rb +25 -5
  179. data/lib/samagotchi/toolkit_command.rb +1 -1
  180. data/lib/samagotchi/tools/delegate.rb +55 -45
  181. data/lib/samagotchi/tools/delegate_relay.rb +5 -5
  182. data/lib/samagotchi/tools/delegate_result.rb +5 -5
  183. data/lib/samagotchi/tools/delegate_wait.rb +41 -17
  184. data/lib/samagotchi/tools/index_hint.rb +14 -0
  185. data/lib/samagotchi/tools/list_sessions.rb +7 -7
  186. data/lib/samagotchi/tools/registry.rb +28 -11
  187. data/lib/samagotchi/tools/send_note.rb +4 -6
  188. data/lib/samagotchi/tools/tool_index.rb +52 -51
  189. data/lib/samagotchi/tools/tool_surface.rb +22 -8
  190. data/lib/samagotchi/turn_context.rb +2 -1
  191. data/lib/samagotchi/turn_stages/context_judge.rb +38 -36
  192. data/lib/samagotchi/turn_stages/objectives.rb +56 -52
  193. data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +13 -17
  194. data/lib/samagotchi/turn_stages/verify_anchors.rb +9 -10
  195. data/lib/samagotchi/turn_stages/warmup.rb +14 -15
  196. data/lib/samagotchi/update_command.rb +11 -7
  197. data/lib/samagotchi/verify_fresh.rb +7 -11
  198. data/lib/samagotchi/version.rb +1 -1
  199. data/lib/samagotchi/vision_support.rb +11 -14
  200. data/lib/samagotchi/web/app.rb +45 -28
  201. data/lib/samagotchi/web/message_display.rb +57 -7
  202. data/lib/samagotchi/web/participant_access.rb +3 -3
  203. data/lib/samagotchi/web/public/activity.js +16 -7
  204. data/lib/samagotchi/web/public/app.js +29 -8
  205. data/lib/samagotchi/web/public/bubbles.js +8 -3
  206. data/lib/samagotchi/web/public/cards_place.js +15 -0
  207. data/lib/samagotchi/web/public/commands.js +5 -1
  208. data/lib/samagotchi/web/public/data.js +4 -2
  209. data/lib/samagotchi/web/public/history_shape.js +220 -0
  210. data/lib/samagotchi/web/public/recap_stamp.js +17 -0
  211. data/lib/samagotchi/web/public/reload_check.js +71 -0
  212. data/lib/samagotchi/web/public/stage_model.js +7 -5
  213. data/lib/samagotchi/web/public/stage_view.js +7 -3
  214. data/lib/samagotchi/web/public/stream_handlers.js +8 -2
  215. data/lib/samagotchi/web/public/timing.js +43 -2
  216. data/lib/samagotchi/web/public/turn_block.js +215 -30
  217. data/lib/samagotchi/web/public/turn_drawing.js +43 -0
  218. data/lib/samagotchi/web/public/turn_events.js +10 -1
  219. data/lib/samagotchi/web/public/turn_pieces.js +29 -0
  220. data/lib/samagotchi/web/reload_check_route.rb +95 -0
  221. data/lib/samagotchi/web/server.rb +4 -2
  222. data/lib/samagotchi/web/session_access_routes.rb +6 -6
  223. data/lib/samagotchi/web/session_control.rb +4 -4
  224. data/lib/samagotchi/web/session_create.rb +3 -3
  225. data/lib/samagotchi/web/session_hub.rb +9 -17
  226. data/lib/samagotchi/web/session_show.rb +67 -5
  227. data/lib/samagotchi/web/session_summary.rb +39 -28
  228. data/lib/samagotchi/worker.rb +83 -32
  229. data/lib/samagotchi/worker_boot.rb +184 -0
  230. data/lib/samagotchi/worker_link.rb +3 -0
  231. data/lib/samagotchi/worker_signals.rb +1 -1
  232. data/lib/samagotchi/worker_task_wakes.rb +2 -2
  233. metadata +32 -4
  234. data/lib/samagotchi/send_command/wait_target.rb +0 -85
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
  require_relative "args"
5
5
  require_relative "builtin_calls"
6
+ require_relative "index_hint"
6
7
  require_relative "registry"
7
8
  require_relative "../log"
8
9
  require_relative "../token_usage"
@@ -11,7 +12,7 @@ require_relative "../plugin/tool_spec"
11
12
 
12
13
  module Samagotchi
13
14
  module Tools
14
- # Read only when a reader asks (#live_surface, a handler): the bundle
15
+ # Read only when a surface is built (#declare, #refresh!): the bundle
15
16
  # test kit loads this file for ToolIndex.new and never builds one.
16
17
  autoload :ToolSurface, File.expand_path("tool_surface", __dir__)
17
18
 
@@ -27,12 +28,14 @@ module Samagotchi
27
28
  # guardrail gate). The index searches the
28
29
  # groups, looks a call's tool up and checks it.
29
30
  #
30
- # What it reads of the session's tools is a ToolSurface: the turn's
31
- # (TurnContext#surface) where the caller has one (the two tools'
32
- # handlers, through their ToolContext; #unwrap, #wrap, #deferrable and
33
- # #unreached_tool, given it by the runner and the deferred list), else
34
- # the Registry #declare registered in, read now (#live_surface: at
35
- # load, a turn's start, a guardrail rule's targets, ctx.load_tools).
31
+ # What it reads of the session's tools is a ToolSurface, always given:
32
+ # the turn's (TurnContext#surface) on the turn's path (the two tools'
33
+ # handlers, through their ToolContext; call_tool's row and targets,
34
+ # from the row and the gate; #unwrap, #wrap, #deferrable and
35
+ # #unreached_tool, from the runner and the deferred list); one built
36
+ # from the Registry #declare registered in at load and at #refresh!;
37
+ # ToolkitBinding#published's for ctx.load_tools (#deferred_name). None
38
+ # where one is needed raises MissingSurface.
36
39
  #
37
40
  # The two tools are registered once, after the plugins load, and
38
41
  # declared per toolkit (Registry#declared?): when a source registered
@@ -69,8 +72,6 @@ module Samagotchi
69
72
  group: { type: "string", description: "search only this group" }
70
73
  }.freeze
71
74
  CALL_DESCRIPTION = "Call a tool #{FIND_NAME} found.".freeze
72
- # A keyword answer's first line.
73
- CALL_HINT = "Call one with #{CALL_NAME}(tool: \"<group>/<tool>\", args: {…}).".freeze
74
75
  CALL_PARAMS = {
75
76
  tool: { type: "string", required: true, description: "the tool as <group>/<tool>" },
76
77
  args: { type: "object", description: "the arguments, per the inputSchema #{FIND_NAME} gave" }
@@ -264,7 +265,7 @@ module Samagotchi
264
265
  # +surface+), then the sources'. A source group with a reserved name,
265
266
  # or one an earlier group has, is left out of search, lookup and the
266
267
  # description (said once per name: a source publishes after load too).
267
- def all_groups(surface = live_surface)
268
+ def all_groups(surface)
268
269
  own = registry_groups(surface)
269
270
  taken = own.map(&:name) | RESERVED_GROUPS
270
271
  kept = @sources.flat_map(&:groups).select do |group|
@@ -280,7 +281,7 @@ module Samagotchi
280
281
 
281
282
  # The groups +surface+'s toolkit reaches (#all_groups' own, and the
282
283
  # source groups #declare found that it reaches).
283
- def reached_groups(surface = live_surface)
284
+ def reached_groups(surface)
284
285
  groups = all_groups(surface)
285
286
  toolkit = surface.toolkit
286
287
  return groups if toolkit.nil? || toolkit.groups == :all
@@ -299,10 +300,11 @@ module Samagotchi
299
300
  # @return [Array<String>] the names registered
300
301
  def declare(registry, failures: nil)
301
302
  @registry = registry
302
- @described = description_lines(all_groups(live_surface).reject { |group| own?(group) }).freeze
303
+ at_load = ToolSurface.for(registry)
304
+ @described = description_lines(all_groups(at_load).reject { |group| own?(group) }).freeze
303
305
  @declared_group_names = @described.keys.freeze
304
306
  loaded = registry.registered
305
- specs = [find_spec, call_spec]
307
+ specs = [find_spec(at_load), call_spec]
306
308
  declared = specs.filter_map do |spec|
307
309
  if (taken = registry[spec.name])
308
310
  failures&.add("tool #{spec.name}", "already registered (#{taken.source}); the tool index's is left out",
@@ -320,7 +322,7 @@ module Samagotchi
320
322
  wrap: ->(call, surface) { wrap(call, surface) },
321
323
  unreached: ->(name, surface) { unreached_tool(name, surface) },
322
324
  loaded: loaded)
323
- declared!(declared)
325
+ declared!(declared, ToolSurface.for(registry))
324
326
  declared.map(&:name)
325
327
  end
326
328
 
@@ -331,8 +333,9 @@ module Samagotchi
331
333
  entry = @registry && @registry[FIND_NAME]
332
334
  return unless entry&.source == SOURCE
333
335
 
334
- entry.schema = entry.schema.merge(description: find_description)
335
- declared!([find_spec, call_spec].select { |spec| @registry[spec.name]&.source == SOURCE })
336
+ surface = ToolSurface.for(@registry)
337
+ entry.schema = entry.schema.merge(description: find_description(surface))
338
+ declared!([find_spec(surface), call_spec].select { |spec| @registry[spec.name]&.source == SOURCE }, surface)
336
339
  end
337
340
 
338
341
  # A checked tool declaration's (Plugin::ToolSpec) definition as
@@ -357,7 +360,7 @@ module Samagotchi
357
360
  # server's: its name, and its summary if it has one). Never every
358
361
  # tool: knowing the names, a model searches less and guesses
359
362
  # arguments.
360
- def find_description(surface = live_surface)
363
+ def find_description(surface)
361
364
  "Search tools this session doesn't declare, by keywords. It answers up to #{FIND_RESULTS} tools, each with " \
362
365
  "its name (<group>/<tool>) and inputSchema; call one with #{CALL_NAME}. An empty query lists every tool's " \
363
366
  "name.\nGroups:\n#{(description_lines(registry_groups(surface)).values + described_groups(surface).values).join("\n")}"
@@ -370,7 +373,7 @@ module Samagotchi
370
373
  # name before it was +group+: a model that still gives it is told
371
374
  # every group was searched. +surface+: the turn's (the handler's).
372
375
  # @return [FoundText]
373
- def find(query, only = nil, server: nil, surface: live_surface)
376
+ def find(query, only = nil, surface:, server: nil)
374
377
  answer = find_in(query, only, surface)
375
378
  return answer unless (only.nil? || only.strip.empty?) && !server.to_s.strip.empty?
376
379
 
@@ -384,7 +387,7 @@ module Samagotchi
384
387
  # guardrails were given (#claimed) is refused, and so is one of the
385
388
  # index's own (a chi tool runs as itself, #unwrap, or not at all).
386
389
  # +surface+: the turn's (the handler's).
387
- def call(name, args, surface: live_surface)
390
+ def call(name, args, surface:)
388
391
  tool = lookup(name, surface)
389
392
  return tool if tool.is_a?(String)
390
393
 
@@ -420,7 +423,7 @@ module Samagotchi
420
423
  # turn's +surface+ doesn't declare call_tool, for an MCP tool, a miss
421
424
  # or arguments that aren't an object.
422
425
  # @return [Hash, nil]
423
- def unwrap(call, surface = live_surface)
426
+ def unwrap(call, surface)
424
427
  return nil unless surface.declared_entry(CALL_NAME)
425
428
 
426
429
  args = call[:args].is_a?(Hash) ? call[:args] : {}
@@ -439,7 +442,7 @@ module Samagotchi
439
442
  # registered tool's. Sorted by acts_as, so the same index gives the
440
443
  # same list in any session and after a respawn.
441
444
  # @return [Array<Tool>]
442
- def deferrable(surface = live_surface)
445
+ def deferrable(surface)
443
446
  all = all_groups(surface).reject { |group| own?(group) }.flat_map(&:tools)
444
447
  reached = reached_groups(surface).reject { |group| own?(group) }.flat_map(&:tools)
445
448
  counts = all.map(&:acts_as).tally
@@ -455,7 +458,7 @@ module Samagotchi
455
458
  # <group>/<tool>). Nil for any other: declared, removed, unknown, or a
456
459
  # source tool that can't be deferred. Never waits for a start.
457
460
  # @return [String, nil]
458
- def deferred_name(name, surface = live_surface)
461
+ def deferred_name(name, surface)
459
462
  name = name.to_s.strip
460
463
  bare = name.delete_prefix("#{CHI_GROUP}/")
461
464
  return bare if surface.indexed_entry(bare)
@@ -469,7 +472,7 @@ module Samagotchi
469
472
  # Finds a source tool regardless of the active toolkit, without
470
473
  # waiting for a starting group. Used only to refuse a call or explain
471
474
  # a deferred stub for a group the session cannot reach.
472
- def unreached_tool(name, surface = live_surface)
475
+ def unreached_tool(name, surface)
473
476
  name = name.to_s.strip
474
477
  return nil if name.empty?
475
478
 
@@ -491,7 +494,7 @@ module Samagotchi
491
494
  # loads tools): the call tool's row, rules and approvals. Nil for any
492
495
  # other call.
493
496
  # @return [Hash, nil]
494
- def wrap(call, surface = live_surface)
497
+ def wrap(call, surface)
495
498
  name = call[:name].to_s
496
499
  return nil if name.empty? || surface.registered?(name)
497
500
 
@@ -502,7 +505,7 @@ module Samagotchi
502
505
 
503
506
  # The call tool's activity row: <group>/<tool> and its arguments, as
504
507
  # +surface+ (the turn's) reaches it.
505
- def call_preview(args, surface = live_surface)
508
+ def call_preview(args, surface)
506
509
  tool = lookup_quietly(args["tool"].to_s, surface)
507
510
  name = tool ? tool.full_name : args["tool"].to_s.strip
508
511
  inner = tool && inner_args(args["args"], tool)
@@ -515,7 +518,7 @@ module Samagotchi
515
518
  # model gave (#claimed), so a rule on it fires before the call waits
516
519
  # for the server. Nothing for a name that can't be a tool (the call
517
520
  # refuses it). +surface+: the turn's (the gate's).
518
- def call_targets(args, surface = live_surface)
521
+ def call_targets(args, surface)
519
522
  name = args["tool"].to_s.strip
520
523
  tool = lookup_quietly(name, surface)
521
524
  acts_as, label = tool ? [tool.acts_as, "#{tool.group}: #{tool.name}"] : claimed(name, surface)
@@ -526,44 +529,41 @@ module Samagotchi
526
529
 
527
530
  private
528
531
 
529
- # The Registry #declare registered in, read now as a ToolSurface (its
530
- # toolkit, every layer's tools left aside: the index's own group never
531
- # holds one): what a caller without a turn's surface sees.
532
- # @return [ToolSurface]
533
- def live_surface = ToolSurface.for(@registry)
534
-
535
532
  # The fields +spec+'s tool registers with beside its entry_fields: a
536
533
  # handler whose block is given the call's typed args and the turn's
537
- # surface (the ToolContext's: a bare kernel's computed on read; none,
538
- # a spec's nil, reads the Registry now), and call_tool's preview and
539
- # targets, given the turn's surface by the row and the gate
540
- # (Registry::Entry#preview_of / #targets_of; nil: the Registry now).
534
+ # surface (its ToolContext's), and call_tool's preview and targets,
535
+ # given the turn's surface by the row and the gate
536
+ # (Registry::Entry#preview_of / #targets_of). Without a surface each
537
+ # raises MissingSurface: none reads the Registry as it is now.
541
538
  def own_fields(spec)
542
539
  parameters = spec.schema[:parameters]
543
540
  args = ->(call) { Plugin::ToolSpec.args_of(call, parameters) }
544
541
  fields = { handler: lambda { |call, kctx|
545
- Plugin::ToolSpec.result_text(spec.block.call(args.call(call), kctx&.surface || live_surface))
542
+ Plugin::ToolSpec.result_text(spec.block.call(args.call(call), kctx&.surface || raise(MissingSurface)))
546
543
  } }
547
544
  return fields unless spec.name == CALL_NAME
548
545
 
549
- fields.merge(preview: ->(call, surface = nil) { call_preview(args.call(call), surface || live_surface) },
550
- targets: ->(call, surface = nil) { call_targets(args.call(call), surface || live_surface) })
546
+ fields.merge(preview: ->(call, surface = nil) { call_preview(args.call(call), surface || raise(MissingSurface)) },
547
+ targets: ->(call, surface = nil) { call_targets(args.call(call), surface || raise(MissingSurface)) })
551
548
  end
552
549
 
553
- def find_spec
554
- Plugin::ToolSpec.build(FIND_NAME, find_description, params: FIND_PARAMS,
550
+ # The search tool's declaration, its description read from +surface+
551
+ # (the Registry's now: #declare, #refresh!); its block searches the
552
+ # turn's (own_fields' handler gives it).
553
+ def find_spec(surface)
554
+ Plugin::ToolSpec.build(FIND_NAME, find_description(surface), params: FIND_PARAMS,
555
555
  preview: lambda { |args|
556
556
  [args["query"].to_s.strip, args["group"] && "in #{args["group"]}"].compact.join(" ")
557
- }) do |args, surface|
558
- find(args["query"].to_s, args["group"], server: args["server"], surface: surface)
557
+ }) do |args, turn|
558
+ find(args["query"].to_s, args["group"], server: args["server"], surface: turn)
559
559
  end
560
560
  end
561
561
 
562
+ # The call tool's declaration; its preview and targets are own_fields'.
562
563
  def call_spec
563
- Plugin::ToolSpec.build(CALL_NAME, CALL_DESCRIPTION, params: CALL_PARAMS, label: @sources.first&.label,
564
- preview: ->(args) { call_preview(args) },
565
- targets: ->(args) { call_targets(args) }) do |args, surface|
566
- call(args["tool"].to_s, args["args"], surface: surface)
564
+ Plugin::ToolSpec.build(CALL_NAME, CALL_DESCRIPTION, params: CALL_PARAMS,
565
+ label: @sources.first&.label) do |args, turn|
566
+ call(args["tool"].to_s, args["args"], surface: turn)
567
567
  end
568
568
  end
569
569
 
@@ -590,7 +590,7 @@ module Samagotchi
590
590
  blocks = matches.map { |tool| match_text(tool, all) }
591
591
  unsearched = searched.filter_map { |group| "#{group.name} #{group.note}" if group.tools.empty? && group.note }
592
592
  blocks << "Not searched: #{unsearched.join("; ")}." unless unsearched.empty?
593
- FoundText.new("#{CALL_HINT}\n\n#{blocks.join("\n\n")}",
593
+ FoundText.new("#{IndexHint::CALL_HINT}\n\n#{blocks.join("\n\n")}",
594
594
  loadable(matches, surface))
595
595
  end
596
596
 
@@ -608,11 +608,12 @@ module Samagotchi
608
608
  end
609
609
  end
610
610
 
611
- # What the sources hear of the declared tools (/mcp's last line).
612
- def declared!(specs)
611
+ # What the sources hear of the declared tools (/mcp's last line), the
612
+ # groups +surface+ (the Registry's now) reaches.
613
+ def declared!(specs, surface)
613
614
  names = specs.map(&:name)
614
615
  tokens = specs.sum { |spec| ToolIndex.spec_tokens(spec) }
615
- groups = reached_groups.reject { |group| own?(group) }.map(&:name)
616
+ groups = reached_groups(surface).reject { |group| own?(group) }.map(&:name)
616
617
  @sources.each { |source| source.declared!(names, tokens, groups) }
617
618
  end
618
619
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "builtin_calls"
4
4
  require_relative "deferred_tools"
5
+ require_relative "registry"
5
6
 
6
7
  module Samagotchi
7
8
  module Tools
@@ -14,8 +15,10 @@ module Samagotchi
14
15
  # tool_definitions/deferred_tools and ToolRunner's left-out, unwrap
15
16
  # and refusal checks read it. A change made while the turn runs (a
16
17
  # /toolkit change, chi.replace_tools: staged anyway) shapes the next
17
- # turn's surface only. Outside a turn (a bare kernel, a spec) a reader
18
- # computes one on read (.of): nothing is cached or invalidated.
18
+ # turn's surface only. Outside a turn a caller builds one (.for): a bare
19
+ # kernel or loop at its own context (KernelLoop#own_context), the tool
20
+ # index at load and at #refresh!, the readers off the turn thread from
21
+ # ToolkitBinding#published.
19
22
  #
20
23
  # +declared+ the entries the request declares, in prompt order (Registry#entries)
21
24
  # +index_only+ the entries the toolkit leaves out that call_tool reaches (Registry#reachable_names)
@@ -67,14 +70,25 @@ module Samagotchi
67
70
  layers: frozen_copy(Array(layers)), **)
68
71
  end
69
72
 
70
- # The turn's surface (+context+'s, an Engine turn's), else one computed
71
- # now from +registry+ under +context+'s layers and loads_tools (a bare
72
- # kernel or loop, a spec).
73
+ # The turn's surface (+context+'s: an Engine turn's, or a bare kernel's
74
+ # or loop's own, KernelLoop#own_context); a context without one raises
75
+ # MissingSurface (no reader falls back to the Registry as it is now).
73
76
  # @param context [TurnContext]
74
77
  # @return [ToolSurface]
75
- def self.of(context, registry)
76
- context.surface || self.for(registry, layers: context.llm_context_layers,
77
- loads_tools: context.settings&.loads_tools == true)
78
+ def self.of(context) = context.surface || raise(MissingSurface)
79
+
80
+ # A bare kernel's or loop's own +context+ (no Engine turn: a spec, a
81
+ # bench) with a surface: its own, else one built now from +registry+
82
+ # under its layers and loads_tools. Only their own default context
83
+ # goes through here (KernelLoop#own_context, ChatLoop#turn_context);
84
+ # a context handed down is read as it is (.of).
85
+ # @param context [TurnContext]
86
+ # @return [TurnContext]
87
+ def self.bare(context, registry)
88
+ return context if context.surface
89
+
90
+ context.with(surface: self.for(registry, layers: context.llm_context_layers,
91
+ loads_tools: context.settings&.loads_tools == true))
78
92
  end
79
93
 
80
94
  def self.empty(layers, loads_tools)
@@ -32,7 +32,8 @@ module Samagotchi
32
32
  # rules' models:), +model_name+ the bare model name the gate's rules match: frozen at
33
33
  # Engine#generate with the settings (a before_turn hook may switch the model)
34
34
  # +surface+ the turn's Tools::ToolSurface (what its loops declare, defer and dispatch):
35
- # nil until Engine#generate builds it; a reader without one computes one (ToolSurface.of)
35
+ # nil until Engine#generate builds it; a reader given none raises Tools::MissingSurface
36
+ # (ToolSurface.of), a bare kernel's or loop's own context builds one (ToolSurface.bare)
36
37
  #
37
38
  # Only the turn thread replaces it (TurnState#replace_context), one frozen
38
39
  # value per write; other threads read the published one (Engine#turn_context,
@@ -19,24 +19,22 @@ module Samagotchi
19
19
  # its pass at an owner turn's start and for a late candidate, the asks it
20
20
  # starts, and the cards an answer brought while a turn ran, shown once it
21
21
  # has ended. Its state is those held cards, under their mutex.
22
+ #
23
+ # Its two callables are read on the asks' thread, after the call that
24
+ # started the asks returned (an answer comes when it comes): which
25
+ # session the Engine is on then, and the card shown where cards go then.
22
26
  class ContextJudge
23
27
  # @param side_judgments [SideJudgments] the Engine's side asks (:context kind)
24
28
  # @param host_registry [HostRegistry] System 1's client resolves its host here
25
- # @param state_dir [#call] the sessions dir
26
- # @param recording [#call] (client, judgment, session, mode:) → the client saving its runs
27
- # @param session [#call] the session the Engine is on now (nil: none)
28
- # @param messages [#call] the owner's conversation so far (Engine#plugin_messages)
29
- # @param turn_running [#call] whether a turn runs now
30
- # @param show_card [#call] (**card) shows a card (Engine#show_card)
31
- def initialize(side_judgments:, host_registry:, state_dir:, recording:, session:, messages:, turn_running:, show_card:)
29
+ # @param turn_state [TurnState] whether a turn runs as an answer comes (#running?)
30
+ # @param show_card [#call] (**card) shows a card (Engine#show_card: EventRoutes, on the caller's thread)
31
+ # @param current_session [#call] the session the Engine is on as an answer comes (nil: none)
32
+ def initialize(side_judgments:, host_registry:, turn_state:, show_card:, current_session:)
32
33
  @side_judgments = side_judgments
33
34
  @host_registry = host_registry
34
- @state_dir = state_dir
35
- @recording = recording
36
- @session = session
37
- @messages = messages
38
- @turn_running = turn_running
35
+ @turn_state = turn_state
39
36
  @show_card = show_card
37
+ @current_session = current_session
40
38
  # On mode's cards an answer brought while a turn ran: shown once it
41
39
  # has ended (#show_cards_after_turn).
42
40
  @cards_after_turn = []
@@ -51,23 +49,28 @@ module Samagotchi
51
49
  # (ContextCandidatesLog#look) and, unless context.candidates.mode is
52
50
  # off, the proposed candidates due an ask are asked about the owner's
53
51
  # prompts so far, this one included. Never a participant's turn, a
54
- # review session or a Continue (no new prompt).
55
- def start(turn)
52
+ # review session or a Continue (no new prompt). The sessions dir is
53
+ # the turn's (TurnContext#state_dir).
54
+ # @param recorder [SystemOne::Recorder] the asks' runs (built for this call)
55
+ def start(turn, recorder:)
56
56
  session = turn.session
57
57
  return unless turn.prompt.is_a?(String) && OwnerPrompt.turn?(session: session, author: turn.origin.author)
58
58
 
59
- pass(session, ContextCandidates.owner_texts(session.messages) + [turn.prompt], phase: "turn")
59
+ pass(session, ContextCandidates.owner_texts(session.messages) + [turn.prompt], phase: "turn",
60
+ state_dir: turn.context.state_dir, recorder: recorder)
60
61
  end
61
62
 
62
63
  # A late proposal no owner prompt mentions (Plugin::Host
63
64
  # #candidate_proposed: github-pr's init task runs beside the first
64
65
  # turn): judged on the owner's prompts so far, the running turn's too.
65
- def late_candidates
66
- session = @session.call
67
- return if session.nil? || session.review?
68
-
69
- texts = ContextCandidates.owner_texts(@messages.call)
70
- pass(session, texts, phase: "late") unless texts.empty?
66
+ # The caller (Engine#judge_late_candidates) gives the session the
67
+ # Engine is on, never nil nor a review session.
68
+ # @param messages [Array<Hash>] the owner's conversation so far (Engine#plugin_messages)
69
+ # @param recorder [SystemOne::Recorder] the asks' runs (built for this call)
70
+ # @param state_dir [String] the sessions dir
71
+ def late_candidates(session, messages:, recorder:, state_dir:)
72
+ texts = ContextCandidates.owner_texts(messages)
73
+ pass(session, texts, phase: "late", state_dir: state_dir, recorder: recorder) unless texts.empty?
71
74
  end
72
75
 
73
76
  # The cards #choice held while a turn ran (Engine#run_turn's ensure,
@@ -83,27 +86,27 @@ module Samagotchi
83
86
 
84
87
  # One look at +session+'s candidates; the asks go to SideJudgments'
85
88
  # :context kind, one thread asking each due candidate in turn.
86
- def pass(session, texts, phase:)
89
+ def pass(session, texts, phase:, state_dir:, recorder:)
87
90
  return if session.id.nil? || session.scratch
88
91
 
89
- rows = ContextCandidates.with_status(session.id, project_root: session.project_root, state_dir: @state_dir.call)
92
+ rows = ContextCandidates.with_status(session.id, project_root: session.project_root, state_dir: state_dir)
90
93
  return if rows.empty?
91
94
 
92
- log = ContextCandidatesLog.log_for(@state_dir.call)
95
+ log = ContextCandidatesLog.log_for(state_dir)
93
96
  records = ContextCandidatesLog.records_for(log, session.id)
94
97
  ContextCandidatesLog.look(log, session.id, rows, records) if phase == "turn"
95
98
  settings = Samagotchi::ContextJudge::Settings.from_config
96
99
  return if settings.mode == "off"
97
100
 
98
101
  jobs = Samagotchi::ContextJudge.jobs(rows, records, session_id: session.id, texts: texts, settings: settings,
99
- phase: phase, parent_attached: parent_attached(session))
102
+ phase: phase, parent_attached: parent_attached(session, state_dir))
100
103
  return if jobs.empty?
101
104
 
102
- client = @recording.call(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :context),
103
- Samagotchi::ContextJudge::JUDGMENT, session, mode: settings.mode)
105
+ client = recorder.wrap(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :context),
106
+ Samagotchi::ContextJudge::JUDGMENT, session, mode: settings.mode)
104
107
  return unless client
105
108
 
106
- start_asks(client, jobs, log, settings, choice(session, log, settings))
109
+ start_asks(client, jobs, log, settings, choice(session, log, settings, state_dir))
107
110
  rescue StandardError => e
108
111
  Samagotchi::ContextJudge.warn(e)
109
112
  end
@@ -128,16 +131,15 @@ module Samagotchi
128
131
  # and not in a running turn (it isn't the turn's: no "waiting for you"
129
132
  # on it), but once it has ended.
130
133
  # @return [#call] (job, fields)
131
- def choice(session, log, settings)
134
+ def choice(session, log, settings, state_dir)
132
135
  session_id = session.id
133
136
  project_root = session.project_root
134
- state_dir = @state_dir.call
135
137
  show = lambda do |**card|
136
- current = @session.call
138
+ current = @current_session.call
137
139
  next nil unless current.nil? || current.id == session_id
138
140
 
139
141
  @cards_mutex.synchronize do
140
- next @cards_after_turn << card if @turn_running.call
142
+ next @cards_after_turn << card if @turn_state.running?
141
143
 
142
144
  @show_card.call(**card)
143
145
  end
@@ -151,15 +153,15 @@ module Samagotchi
151
153
  # A delegate child's parent's sources, by title (else name): the
152
154
  # judge's started_from. nil for any other session (a chi send --new
153
155
  # session has no parent; a fork isn't a delegate).
154
- def parent_attached(session)
156
+ def parent_attached(session, state_dir)
155
157
  return nil unless session.delegate? && session.parent_id
156
158
 
157
- parent_root = if Session.exist?(session.parent_id, state_dir: @state_dir.call)
158
- Session.load(session.parent_id, state_dir: @state_dir.call).project_root
159
+ parent_root = if Session.exist?(session.parent_id, state_dir: state_dir)
160
+ Session.load(session.parent_id, state_dir: state_dir).project_root
159
161
  else
160
162
  session.project_root
161
163
  end
162
- ContextSources.attached(session.parent_id, project_root: parent_root, state_dir: @state_dir.call)
164
+ ContextSources.attached(session.parent_id, project_root: parent_root, state_dir: state_dir)
163
165
  .map { |attached| attached.source.title || attached.name }
164
166
  rescue StandardError => e
165
167
  Log.warn(:context, "context_parent_attached_failed", error: e.class.name, msg: e.message.to_s[0, 200])