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
@@ -4,6 +4,7 @@ require_relative "host_registry"
4
4
  require_relative "log"
5
5
  require_relative "context_window"
6
6
  require_relative "llm_context_strategy"
7
+ require_relative "model_lookup"
7
8
  require_relative "model_profile"
8
9
  require_relative "reasoning_history"
9
10
  require_relative "sampling_settings"
@@ -22,7 +23,8 @@ module Samagotchi
22
23
 
23
24
  # The effective model resolved once (HostRegistry#resolve: its host and
24
25
  # client, the id sent) with the names its models: entry may be under
25
- # (HostRegistry#lookup_names), and its settings read from them. An
26
+ # (HostRegistry#lookup_names), and its settings read from them through
27
+ # one ModelLookup (every layer's input, with the registry's env). An
26
28
  # immutable snapshot built per read, never kept across reads: config.yml,
27
29
  # a model listing (which may move a bare id to another host) and another
28
30
  # process's saved lists all change what a resolve gives without any
@@ -32,14 +34,17 @@ module Samagotchi
32
34
  # Engine has always given (Unresolved); building one raises as
33
35
  # HostRegistry#resolve does (SessionModelSettings#model_setting gives the
34
36
  # fallback then).
35
- EffectiveModel = Data.define(:registry, :name, :typed, :target, :names) do
37
+ EffectiveModel = Data.define(:registry, :name, :typed, :target, :names, :lookup) do
36
38
  # @param registry [HostRegistry]
37
39
  # @param choice [ModelChoice]
40
+ # @param models [Hash{String => ModelSettings}, nil] one models: map for
41
+ # many models (a picker); ModelSettings.all at each read by default
38
42
  # @raise what HostRegistry#resolve raises
39
- def self.of(registry, choice)
43
+ def self.of(registry, choice, models: nil)
40
44
  target = registry.resolve(choice.name)
41
- new(registry: registry, name: choice.name, typed: choice.typed, target: target,
42
- names: registry.lookup_names(choice.typed, resolved: choice.name, target: target))
45
+ names = registry.lookup_names(choice.typed, resolved: choice.name, target: target)
46
+ new(registry: registry, name: choice.name, typed: choice.typed, target: target, names: names,
47
+ lookup: ModelLookup.for(target, names: names, models: models, env: registry.env))
43
48
  end
44
49
 
45
50
  # A saved session's model (no worker): its model name, the default when
@@ -50,6 +55,34 @@ module Samagotchi
50
55
  of(registry, ModelChoice.new(name: name, typed: session.model_typed || name))
51
56
  end
52
57
 
58
+ # A saved session's LLM context strategy, apply rule and budget, each
59
+ # with where it came from (LLMContextStrategy::Explained#summary), as
60
+ # its worker would resolve them; nil when they can't be (an unreadable
61
+ # config, a model that resolves nowhere). From config and the registry
62
+ # only: no network, no model call. What a session page, a session
63
+ # list's ctx % and a spawn-time check all read.
64
+ # @param session [Session, nil]
65
+ # @param registry [HostRegistry, nil] the hosts to resolve against;
66
+ # HostRegistry.current by default
67
+ # @return [Hash, nil] Explained#summary, symbol keys
68
+ def self.session_llm_context(session, registry: nil)
69
+ return nil if session.nil?
70
+
71
+ own = session.llm_context
72
+ of_session(session, registry: registry || HostRegistry.current).llm_context_explained(own&.resolve_args || {})&.summary(own)
73
+ rescue StandardError # degrade-ok: a saved session's model that resolves nowhere has no budget to show; the readers log their own
74
+ nil
75
+ end
76
+
77
+ # The budget a saved session's turns run under
78
+ # (.session_llm_context's :budget_tokens), nil when it is off or can't
79
+ # be resolved. What a session list's ctx % counts against, so a saved
80
+ # session's fill reads as the live meter.
81
+ # @return [Integer, nil]
82
+ def self.session_budget(session, registry: nil)
83
+ session_llm_context(session, registry: registry)&.fetch(:budget_tokens, nil)
84
+ end
85
+
53
86
  def entry = target.entry
54
87
  def client = target.client
55
88
  def bare = target.bare_model
@@ -63,21 +96,21 @@ module Samagotchi
63
96
 
64
97
  # The configured request parameters (hosts: and models: sampling).
65
98
  def sampling
66
- SamplingSettings.for(target, names: names)
99
+ SamplingSettings.for(lookup)
67
100
  rescue StandardError => e
68
101
  unresolved(:sampling_unresolved, e, SamplingSettings::EMPTY)
69
102
  end
70
103
 
71
104
  # "temperature=0.6 (hosts.work)", nil when none.
72
105
  def sampling_summary
73
- SamplingSettings.summary(target, names: names)
106
+ SamplingSettings.summary(lookup)
74
107
  rescue StandardError => e
75
108
  unresolved(:sampling_unresolved, e, nil)
76
109
  end
77
110
 
78
111
  # The thinking level (Thinking.resolve), the session's +own+ first.
79
112
  def thinking(own:)
80
- Thinking.resolve(target, names: names, session: own).first
113
+ Thinking.resolve(lookup, session: own).first
81
114
  rescue StandardError => e
82
115
  unresolved(:thinking_unresolved, e, Thinking::DEFAULT)
83
116
  end
@@ -85,23 +118,25 @@ module Samagotchi
85
118
  # The thinking level with where it came from (SettingExplained), nil
86
119
  # when it can't be resolved.
87
120
  def thinking_explained(own:)
88
- Thinking.explain(target, names: names, session: own)
121
+ Thinking.explain(lookup, session: own)
89
122
  rescue StandardError => e
90
123
  unresolved(:thinking_unresolved, e, nil)
91
124
  end
92
125
 
93
126
  # What a thinking level change costs the prompt cache
94
127
  # (Thinking.cache_cost); the block gives the prompt profile, asked only
95
- # on a native host. Raises.
128
+ # on a native host; a local chat host's is the one its name says. Raises.
96
129
  # @return [Symbol]
97
130
  def thinking_cache_cost(&)
98
- Thinking.cache_cost(target, profile: entry&.chat? ? nil : yield&.name)
131
+ return Thinking.cache_cost(target, inferred: ModelProfile.inferred_profile_name(bare)) if entry&.chat?
132
+
133
+ Thinking.cache_cost(target, profile: yield&.name)
99
134
  end
100
135
 
101
136
  # The reasoning_history level (ReasoningHistory.resolve), the session's
102
137
  # +own+ first.
103
138
  def reasoning_history(own:)
104
- ReasoningHistory.resolve(target, names: names, session: own).first
139
+ ReasoningHistory.resolve(lookup, session: own).first
105
140
  rescue StandardError => e
106
141
  unresolved(:reasoning_history_unresolved, e, ReasoningHistory::DEFAULT)
107
142
  end
@@ -112,7 +147,7 @@ module Samagotchi
112
147
  def reasoning_history_explained(own:, native: false)
113
148
  return nil unless native || entry&.chat?
114
149
 
115
- ReasoningHistory.explain(target, names: names, session: own)
150
+ ReasoningHistory.explain(lookup, session: own)
116
151
  rescue StandardError => e
117
152
  unresolved(:reasoning_history_unresolved, e, nil)
118
153
  end
@@ -122,7 +157,7 @@ module Samagotchi
122
157
  def reasoning_history_summary(own:)
123
158
  return nil unless entry&.chat?
124
159
 
125
- level, source = ReasoningHistory.resolve(target, names: names, session: own)
160
+ level, source = ReasoningHistory.resolve(lookup, session: own)
126
161
  source ? ReasoningHistory.label(level, source) : nil
127
162
  rescue StandardError => e
128
163
  unresolved(:reasoning_history_unresolved, e, nil)
@@ -139,7 +174,7 @@ module Samagotchi
139
174
  # [seconds, source] a cut waits for thinking (SteerCutAfter.resolve);
140
175
  # the global and its name when it can't be resolved.
141
176
  def steer_cut_after_explained
142
- SteerCutAfter.resolve(target, names: names)
177
+ SteerCutAfter.resolve(lookup)
143
178
  rescue StandardError => e
144
179
  unresolved(:steer_cut_after_unresolved, e, [SteerCut.cut_after, "steer.cut_after"])
145
180
  end
@@ -148,7 +183,7 @@ module Samagotchi
148
183
  # (LLMContextStrategy.explain) under the session's +args+
149
184
  # (LLMContextOverride#resolve_args), nil when it can't be resolved.
150
185
  def llm_context_explained(args)
151
- LLMContextStrategy.explain(target, names: names, **args)
186
+ LLMContextStrategy.explain(lookup, **args)
152
187
  rescue StandardError => e
153
188
  unresolved(:llm_context_unresolved, e, nil)
154
189
  end
@@ -156,14 +191,14 @@ module Samagotchi
156
191
  # The LLM context layers its turns run under; [] when they can't be
157
192
  # resolved.
158
193
  def llm_context_layers(args)
159
- LLMContextStrategy.resolve(target, names: names, **args).active_layers
194
+ LLMContextStrategy.resolve(lookup, **args).active_layers
160
195
  rescue StandardError => e
161
196
  unresolved(:llm_context_unresolved, e, [])
162
197
  end
163
198
 
164
199
  # models:/hosts: window_tokens (ContextWindow.setting), nil when unset.
165
200
  def window_setting
166
- ContextWindow.setting(target, names: names)
201
+ ContextWindow.setting(lookup)
167
202
  rescue StandardError => e
168
203
  unresolved(:window_unresolved, e, nil)
169
204
  end
@@ -171,7 +206,7 @@ module Samagotchi
171
206
  # The window config.yml gives this model or its host
172
207
  # (ContextWindow.setting), never asking a server: a session loaded on
173
208
  # it (Engine#session=, before a worker's Bridge serves). nil when none.
174
- def configured_context_window = ContextWindow.setting(target, names: names)
209
+ def configured_context_window = ContextWindow.setting(lookup)
175
210
 
176
211
  # The window as its loop would see it (ChatLoop#context_window: a remote
177
212
  # chat host has no /props, only its model list); nil when it can't be
@@ -183,7 +218,7 @@ module Samagotchi
183
218
  adapter = registry.adapter_for(entry)
184
219
  client = nil if adapter.remote?
185
220
  end
186
- ContextWindow.resolve(client: client, model: bare, adapter: adapter, setting: ContextWindow.setting(target, names: names))
221
+ ContextWindow.resolve(client: client, model: bare, adapter: adapter, setting: ContextWindow.setting(lookup))
187
222
  rescue StandardError => e
188
223
  unresolved(:window_unresolved, e, nil)
189
224
  end
@@ -232,7 +267,7 @@ module Samagotchi
232
267
 
233
268
  # Whether it sees images (VisionSupport.for) under +profile+.
234
269
  def vision_support(profile)
235
- VisionSupport.for(target, profile: profile, adapter: vision_adapter, names: names)
270
+ VisionSupport.for(lookup, profile: profile, adapter: vision_adapter)
236
271
  end
237
272
 
238
273
  # A chat host's adapter (what it says of images), nil on a native host.
@@ -246,7 +281,7 @@ module Samagotchi
246
281
  # Not cached here. Raises.
247
282
  # @return [ModelProfile::Resolution]
248
283
  def profile_resolution
249
- ModelProfile.resolve(names: names, entry: entry, client: client, bare_model: bare)
284
+ ModelProfile.resolve(names: names, entry: entry, client: client, bare_model: bare, models: lookup.models)
250
285
  end
251
286
 
252
287
  # See Unresolved.fallback.
@@ -5,7 +5,7 @@ module Samagotchi
5
5
  # The attached-context candidate judge (TurnStages::ContextJudge,
6
6
  # forwarded): prepare_turn starts its pass, run_turn's ensure shows the
7
7
  # cards it held, and a plugin's late candidate asks it. Reads
8
- # @context_judge, @side_judgments, @host_registry.
8
+ # @context_judge, @side_judgments, @host_registry, @turn_state, @session.
9
9
  module ContextJudgePart
10
10
  private
11
11
 
@@ -13,19 +13,27 @@ module Samagotchi
13
13
  attr_reader :context_judge
14
14
 
15
15
  # A late proposal no owner prompt mentions (Plugin::Host
16
- # #candidate_proposed): TurnStages::ContextJudge#late_candidates.
17
- def judge_late_candidates = @context_judge.late_candidates
16
+ # #candidate_proposed, maybe while the plugins load, before a session
17
+ # or the event log is there): TurnStages::ContextJudge#late_candidates
18
+ # on the session the Engine is on (none, or a review session: nothing,
19
+ # before anything else is read), the owner's prompts so far, the
20
+ # recorder and the sessions dir, all read now on the caller's thread.
21
+ def judge_late_candidates
22
+ session = @session
23
+ return if session.nil? || session.review?
18
24
 
19
- # The stage over this Engine's side asks and its session (built in
25
+ @context_judge.late_candidates(session, messages: plugin_messages, recorder: system_one_recorder,
26
+ state_dir: session_state_dir)
27
+ end
28
+
29
+ # The stage over this Engine's side asks (built in
20
30
  # #build_tools_and_plugins, before the plugins load: one may propose a
21
- # candidate as it loads).
31
+ # candidate as it loads). Its two callables are the asks thread's late
32
+ # reads: the session the Engine is on and where a card goes, both as
33
+ # an answer comes.
22
34
  def build_context_judge
23
- TurnStages::ContextJudge.new(
24
- side_judgments: @side_judgments, host_registry: @host_registry, state_dir: -> { session_state_dir },
25
- recording: ->(client, judgment, session, mode:) { system_one_recording(client, judgment, session, mode: mode) },
26
- session: -> { session }, messages: -> { plugin_messages }, turn_running: -> { turn_running? },
27
- show_card: ->(**card) { show_card(**card) }
28
- )
35
+ TurnStages::ContextJudge.new(side_judgments: @side_judgments, host_registry: @host_registry, turn_state: @turn_state,
36
+ show_card: ->(**card) { show_card(**card) }, current_session: -> { @session })
29
37
  end
30
38
  end
31
39
  end
@@ -46,7 +46,7 @@ module Samagotchi
46
46
  from_cwd: note[:from_cwd], text: note[:text], context_source: note[:context_source],
47
47
  created_at: note[:created_at], objectives: entry }.compact)
48
48
  announce_objectives(session) if entry
49
- @objectives_stage.note(session, note)
49
+ @objectives_stage.note(session, note, recorder: system_one_recorder, state_dir: session_state_dir)
50
50
  message
51
51
  end
52
52
  end
@@ -228,13 +228,16 @@ module Samagotchi
228
228
  private
229
229
 
230
230
  # Pick the loop for +model+ (EffectiveModel); the chat loop is pointed
231
- # at its host's adapter (one per host, kept for its cached model list).
231
+ # at its host's adapter (one per host, kept for its cached model list)
232
+ # and client (the window probe, the re-prefill's host).
232
233
  def backend_for(model)
233
234
  return @native_backend unless model.chat?
234
235
 
235
236
  @chat_backend_mutex.synchronize do
236
- @chat_backend ||= LLM::ChatLoop.new(kernel: @kernel, stable_length: ->(text) { @prompt_builder.stable_length(text) })
237
+ @chat_backend ||= LLM::ChatLoop.new(kernel: @kernel, reprefill: @reprefill,
238
+ stable_length: ->(text) { @prompt_builder.stable_length(text) })
237
239
  @chat_backend.adapter = @host_registry.adapter_for(model.entry)
240
+ @chat_backend.client = model.client
238
241
  @chat_backend.session_id = session&.id
239
242
  @chat_backend
240
243
  end
@@ -9,8 +9,22 @@ module Samagotchi
9
9
  # @host_registry, @session.
10
10
  module ObjectivesPart
11
11
  # The session's objectives as the page draws them, when that differs
12
- # from the last announced (TurnStages::Objectives#announce).
13
- def announce_objectives(session = @session) = @objectives_stage.announce(session)
12
+ # from the last announced (TurnStages::Objectives#announce), from any
13
+ # thread: end_turn's step, a command, Check this's thread, a note,
14
+ # a rollback. Under the event lock, which guards the last view: the
15
+ # sessions dir and the running turn's conversation read there, the
16
+ # event announced in the same step. It only informs: a failure is
17
+ # logged, never raised.
18
+ def announce_objectives(session = @session)
19
+ return unless session
20
+
21
+ synchronize_events do
22
+ event = @objectives_stage.announce(session, state_dir: session_state_dir, running: live_conversation)
23
+ announce(event) if event
24
+ end
25
+ rescue StandardError => e
26
+ Log.warn(:turn, "objectives_announce_failed", error: e.class.name, msg: e.message)
27
+ end
14
28
 
15
29
  # Check this (ObjectivesCheck)'s one side request. Its usage is in the
16
30
  # session's side line. Raises as ctx.ask_model's request does.
@@ -32,16 +46,20 @@ module Samagotchi
32
46
  # The objectives' stage (TurnStages::Objectives).
33
47
  attr_reader :objectives_stage
34
48
 
35
- # The stage over this Engine's side asks and its event lock (built in
36
- # #initialize, once the host registry is).
49
+ # However +turn+ ended (run_turn's ensure, after the turn flag is
50
+ # released): a completed turn's proposal cards shown
51
+ # (TurnStages::Objectives#finally). A card that fails is logged.
52
+ def show_objective_proposals(turn)
53
+ @objectives_stage.finally(turn).each { |card| show_card(**card) }
54
+ rescue StandardError => e
55
+ Log.warn(:turn, "objectives_proposal_failed", error: e.class.name, msg: e.message)
56
+ end
57
+
58
+ # The stage over this Engine's side asks (built in #initialize, once
59
+ # the host registry is). Each call takes its values: the recorder,
60
+ # the sessions dir, the running conversation.
37
61
  def build_objectives_stage
38
- TurnStages::Objectives.new(
39
- side_judgments: @side_judgments, host_registry: @host_registry, state_dir: -> { session_state_dir },
40
- recording: ->(client, judgment, session, mode:) { system_one_recording(client, judgment, session, mode: mode) },
41
- runs: -> { system_one_runs }, synchronize: ->(&block) { synchronize_events(&block) },
42
- announce: ->(event) { announce(event) }, running: -> { live_conversation },
43
- show_card: ->(**card) { show_card(**card) }
44
- )
62
+ TurnStages::Objectives.new(side_judgments: @side_judgments, host_registry: @host_registry)
45
63
  end
46
64
  end
47
65
  end
@@ -60,7 +60,7 @@ module Samagotchi
60
60
  prompt_sections: @prompt_sections,
61
61
  prompt_changed: -> { prompt_changed! },
62
62
  judgments: @judgments,
63
- hook_failed: ->(hook, failed) { @hook_routes.plugin_hook_failed(hook, failed) }
63
+ hook_failed: ->(hook, failed, effect: :log) { @hook_routes.plugin_hook_failed(hook, failed, effect: effect) }
64
64
  )
65
65
  # What plugins show while they load (an MCP server that didn't
66
66
  # start) waits for the first turn, beside the load warnings: no UI
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../recap_place"
4
+
3
5
  module Samagotchi
4
6
  class Engine
5
7
  # The idle recap (IdleRecap) and the shared idle scheduler: the recap job
@@ -96,9 +98,10 @@ module Samagotchi
96
98
  @recap ? @recap.request_now : :off
97
99
  end
98
100
 
99
- # The recap saved with the current session (recap.json), and how many
100
- # user turns came after it (0: it is current).
101
- # @return [Hash, nil] {text:, covered:, turns_since:, created_at:}
101
+ # The recap saved with the current session (recap.json), how many
102
+ # user turns came after it (0: it is current), and the stamp of the
103
+ # last turn it covers (RecapPlace: where a page puts it; nil, the count).
104
+ # @return [Hash, nil] {text:, covered:, turns_since:, opening_id:, created_at:}
102
105
  def saved_recap
103
106
  state = @recap&.state
104
107
  return nil unless state
@@ -106,7 +109,8 @@ module Samagotchi
106
109
  messages = @session&.messages || []
107
110
  covered = state[:covered].to_i
108
111
  turns_since = Array(messages).drop(covered).count { |m| Steer.turn_opening?(m) }
109
- { text: state[:text], covered: covered, turns_since: turns_since, created_at: state[:created_at] }
112
+ { text: state[:text], covered: covered, turns_since: turns_since, opening_id: RecapPlace.opening_id(messages, covered),
113
+ created_at: state[:created_at] }
110
114
  end
111
115
 
112
116
  # Start the shared idle scheduler (reminders + optional recap). The recap
@@ -6,6 +6,7 @@ module Samagotchi
6
6
  # IdleClient, their usage in the session's side line, and System 1's
7
7
  # recording of judged asks. Writes @side_client/@side_client_key (under
8
8
  # @side_client_mutex); reads @metrics, @session, @host_registry.
9
+ # The recorder (SystemOne::Recorder) is the turn stages' recording seam.
9
10
  module SideRequestsPart
10
11
  # Count one side request (a recap, a plugin's ctx.ask_model: IdleClient's
11
12
  # on_usage) in the session's side line, apart from its turns' totals.
@@ -52,24 +53,33 @@ module Samagotchi
52
53
 
53
54
  private
54
55
 
55
- # +client+ (nil for none) saving its asks as +judgment+'s runs. Anything
56
- # going wrong here leaves the client as it was: a run is never worth a
57
- # judgment.
58
- #
59
- # A scratch session's asks aren't saved; a participant's turn is never
60
- # judged, so it never comes here.
56
+ # +client+ (nil for none) saving its asks as +judgment+'s runs
57
+ # (SystemOne::Recorder#wrap, over a recorder built now): the
58
+ # guardrails' System 1 client. No client or a scratch session: the
59
+ # client as it is, with nothing read (no recorder built).
61
60
  # @param mode [String, nil] the use's mode, for its runs (Recording.wrap)
62
61
  def system_one_recording(client, judgment, session, mode: nil)
63
62
  return client if client.nil? || session&.scratch
64
63
 
65
- context = SystemOne::Recording::Context.of(session, session_model: session && effective_model_name)
66
- SystemOne::Recording.wrap(client, judgment: judgment, runs: system_one_runs, context: context, recorded_mode: mode)
67
- rescue StandardError => e
68
- Log.warn(:system_one, "system_one_recording_failed", error: e.class.name, msg: e.message.to_s[0, 200])
69
- client
64
+ system_one_recorder.wrap(client, judgment, session, mode: mode)
70
65
  end
71
66
  private :system_one_recording
72
67
 
68
+ # What a turn stage saves its System 1 asks with (SystemOne::Recorder):
69
+ # this Engine's runs and the model it names now, read at this call and
70
+ # never kept, so each stage call sees the model as it is then (an
71
+ # outgoing ask after a before_turn hook's switch, the new one). Never
72
+ # raises: on a failure, SystemOne::Recorder::NONE (no runs, the plain
73
+ # client), said once per failure as before.
74
+ # @return [SystemOne::Recorder]
75
+ def system_one_recorder
76
+ SystemOne::Recorder.new(runs: system_one_runs, session_model: effective_model_name)
77
+ rescue StandardError => e
78
+ SystemOne::Recorder.failed(e)
79
+ SystemOne::Recorder::NONE
80
+ end
81
+ private :system_one_recorder
82
+
73
83
  # Where this Engine's runs are saved; read each time, as the settings are.
74
84
  def system_one_runs = SystemOne::Runs.for_sessions_dir(session_state_dir)
75
85
  private :system_one_runs
@@ -22,10 +22,14 @@ module Samagotchi
22
22
  # turn's staged tool sets and its toolkit apply.
23
23
  def publish_tools! = @toolkit_binding.publish!
24
24
 
25
+ # That published Registry (ToolkitBinding#published): what a reader
26
+ # off the turn thread (/toolkit) reads of the tools.
27
+ def published_tools = @toolkit_binding.published
28
+
25
29
  # The tools this session registered (Tools::Registry), declared or not.
26
30
  attr_reader :tools
27
31
 
28
- public :tools, :toolkit_kits, :resolved_toolkit, :toolkit_explained, :toolkit_summary
32
+ public :tools, :published_tools, :toolkit_kits, :resolved_toolkit, :toolkit_explained, :toolkit_summary
29
33
 
30
34
  # A plugin's ctx.load_tools: stage the reachable tools +names+ names
31
35
  # (Tools::ToolIndex#deferred_name, on the published Registry) that the
@@ -35,7 +39,7 @@ module Samagotchi
35
39
  # @return [Array<String>] the wire names staged
36
40
  def load_tools(names, bundle)
37
41
  added = ToolLoads.added(@session&.messages)
38
- published = @toolkit_binding.published
42
+ published = published_tools
39
43
  surface = Tools::ToolSurface.for(published)
40
44
  wanted = names.filter_map do |name|
41
45
  wire = @tool_index.deferred_name(name, surface)
@@ -97,14 +101,11 @@ module Samagotchi
97
101
  public :warm_toolkit_suggestion!
98
102
 
99
103
  # The stage over this Engine's side asks, binding and tools (built in
100
- # #build_tools_and_plugins, before the plugins load).
104
+ # #build_tools_and_plugins, before the plugins load). Its turn's start
105
+ # takes the recorder (#system_one_recorder) per call.
101
106
  def build_toolkit_suggestion
102
- TurnStages::ToolkitSuggestion.new(
103
- side_judgments: @side_judgments, host_registry: @host_registry, toolkit_binding: @toolkit_binding,
104
- tools: @tools, state_dir: -> { session_state_dir },
105
- recording: ->(client, judgment, session, mode:) { system_one_recording(client, judgment, session, mode: mode) },
106
- runs: -> { system_one_runs }
107
- )
107
+ TurnStages::ToolkitSuggestion.new(side_judgments: @side_judgments, host_registry: @host_registry,
108
+ toolkit_binding: @toolkit_binding, tools: @tools)
108
109
  end
109
110
  private :build_toolkit_suggestion
110
111
  end
@@ -117,7 +117,7 @@ module Samagotchi
117
117
  # a display version of the answer (AnswerDisplay) and event[:refs]
118
118
  # for refs on it (AnswerRefs).
119
119
  session = turn.session
120
- @objectives_stage.outgoing(turn, result) unless canceled || result.exhausted?
120
+ @objectives_stage.outgoing(turn, result, recorder: system_one_recorder) unless canceled || result.exhausted?
121
121
  display = AnswerDisplay.new(session.messages)
122
122
  refs = AnswerRefs.new(session.messages, cwd: session.working_directory)
123
123
  after_turn = { type: :after_turn, status: canceled ? "canceled" : "completed",
@@ -7,7 +7,7 @@ module Samagotchi
7
7
  # with, the used memories it publishes, and the turn-end warm-up of the
8
8
  # next one (TurnStages::Warmup, built here). Writes @next_turn_waiting;
9
9
  # reads @model_settings, @kernel, @prompt_warmup, @thinking_notices,
10
- # @turn_state, @turn_warmup.
10
+ # @turn_state, @reminder_queue, @turn_warmup.
11
11
  module TurnHelpersPart
12
12
  # The turn's settings for the kernel (LLM::TurnSettings): vision,
13
13
  # sampling and thinking, the thinking resolved before the hooks run.
@@ -40,13 +40,13 @@ module Samagotchi
40
40
  private :turn_warmup
41
41
 
42
42
  # The stage over the kernel and the Engine's PromptWarmup (built in
43
- # #initialize, once the kernel is).
43
+ # #initialize, once the kernel and the reminder queue are). Its one
44
+ # callable is the system prompt, built late (side effects); the
45
+ # host's probe goes with each call (#next_turn_waiting, set after).
44
46
  def build_turn_warmup
45
- TurnStages::Warmup.new(
46
- prompt_warmup: @prompt_warmup, kernel: @kernel, turn_state: @turn_state, model_settings: @model_settings,
47
- model: -> { effective_model }, system_prompt: ->(model) { system_prompt(model) },
48
- reminders_due: -> { reminders_due? }, next_turn_waiting: -> { next_turn_waiting&.call }
49
- )
47
+ TurnStages::Warmup.new(prompt_warmup: @prompt_warmup, kernel: @kernel, turn_state: @turn_state,
48
+ model_settings: @model_settings, reminder_queue: @reminder_queue,
49
+ system_prompt: ->(model) { system_prompt(model) })
50
50
  end
51
51
  private :build_turn_warmup
52
52
 
@@ -11,7 +11,7 @@ module Samagotchi
11
11
  # The anchors' stage (TurnStages::VerifyAnchors).
12
12
  attr_reader :verify_anchors
13
13
 
14
- def build_verify_anchors = TurnStages::VerifyAnchors.new(state_dir: -> { session_state_dir })
14
+ def build_verify_anchors = TurnStages::VerifyAnchors.new
15
15
  end
16
16
  end
17
17
  end