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
@@ -14,78 +14,62 @@ module Samagotchi
14
14
  # audit of what comes in at a turn's start and what goes out at its end
15
15
  # (ObjectivesAudit), the view announced when it changed, and the model's
16
16
  # proposals kept as cards until the turn is over. Its state is those
17
- # proposals under their mutex, the last view announced and the audit.
17
+ # proposals under their mutex and the last view announced (under the
18
+ # event lock); the audit is built per call.
18
19
  class Objectives
19
20
  # @param side_judgments [SideJudgments] the Engine's side asks (the audit's kinds)
20
21
  # @param host_registry [HostRegistry] System 1's client resolves its host here
21
- # @param state_dir [#call] the sessions dir
22
- # @param recording [#call] (client, judgment, session, mode:) → the client saving its runs
23
- # @param runs [#call] the Engine's SystemOne::Runs
24
- # @param synchronize [#call] (&block) runs the block under the event lock (Engine#synchronize_events)
25
- # @param announce [#call] (event) tells the observers (Engine#announce)
26
- # @param running [#call] the running turn's conversation, nil between turns (Engine#live_conversation)
27
- # @param show_card [#call] (**card) shows a card (Engine#show_card)
28
- def initialize(side_judgments:, host_registry:, state_dir:, recording:, runs:, synchronize:, announce:, running:, show_card:)
22
+ def initialize(side_judgments:, host_registry:)
29
23
  @side_judgments = side_judgments
30
- @state_dir = state_dir
31
- @synchronize = synchronize
32
- @announce = announce
33
- @running = running
34
- @show_card = show_card
24
+ @host_registry = host_registry
35
25
  # The last view announced (#announce), under the event lock.
36
26
  @announced = nil
37
27
  # The model's proposals until the turn is over (#finally).
38
28
  @proposals = []
39
29
  @proposals_mutex = Mutex.new
40
- # With saving off nothing would come of an ask, so none is made.
41
- @audit = ObjectivesAudit.new(
42
- side: side_judgments, state_dir: state_dir,
43
- client_for: lambda do |judgment, session|
44
- next nil unless runs.call.on?
45
-
46
- recording.call(SystemOne::Client.from_config(host_registry: host_registry, mode: ObjectivesAudit::MODE, tag: :system_one),
47
- judgment, session, mode: nil)
48
- end
49
- )
50
30
  end
51
31
 
52
32
  # System 1 on the objectives, in audit (ObjectivesAudit), at a turn's
53
33
  # start: the owner's turns only, and for a message a person's: a
54
34
  # reminder's or a delegate's prompt isn't one.
55
- def incoming(turn)
35
+ # @param recorder [SystemOne::Recorder] the audit's runs (built for this call)
36
+ def incoming(turn, recorder:)
56
37
  return unless turn.prompt.is_a?(String) && turn.origin.human? && OwnerPrompt.turn?(session: turn.session, author: turn.origin.author)
57
38
 
58
- @audit.incoming(turn.session, turn.prompt)
39
+ audit(turn.context.state_dir, recorder).incoming(turn.session, turn.prompt)
59
40
  end
60
41
 
61
42
  # The turn's answer, after the end event and before :after_turn (the
62
43
  # caller skips a canceled or exhausted turn): the owner's turns only.
63
- def outgoing(turn, result)
44
+ # @param recorder [SystemOne::Recorder] the audit's runs (built for this
45
+ # call: after a before_turn hook's model switch, the new model)
46
+ def outgoing(turn, result, recorder:)
64
47
  return unless OwnerPrompt.turn?(session: turn.session, author: turn.origin.author)
65
48
 
66
- @audit.outgoing(turn.session, result.text)
49
+ audit(turn.context.state_dir, recorder).outgoing(turn.session, result.text)
67
50
  end
68
51
 
69
52
  # A note added between turns (Engine#add_context_note): incoming too.
70
- def note(session, note) = @audit.note(session, note)
53
+ # @param recorder [SystemOne::Recorder] the audit's runs (built for this call)
54
+ # @param state_dir [String] the sessions dir
55
+ def note(session, note, recorder:, state_dir:) = audit(state_dir, recorder).note(session, note)
71
56
 
72
57
  # The session's objectives as the page draws them (Objectives.view), when
73
58
  # that differs from the last announced: after an edit (its file written,
74
59
  # pending), once a list note is in, at a turn's end (its moves) and after
75
- # a rollback. A session with no list announces nothing. It only informs:
76
- # a failure is logged, never the turn's.
77
- def announce(session)
78
- return unless session
79
-
80
- @synchronize.call do
81
- view = Samagotchi::Objectives.view(session.id, session.messages, state_dir: @state_dir.call, running: @running.call&.dup)
82
- next if view.nil? || view == @announced
83
-
84
- @announced = view
85
- @announce.call({ type: :objectives_updated, session_id: session.id, **view })
86
- end
87
- rescue StandardError => e
88
- Log.warn(:turn, "objectives_announce_failed", error: e.class.name, msg: e.message)
60
+ # a rollback. A session with no list announces nothing. The caller
61
+ # (Engine#announce_objectives) holds the event lock, the one guard of
62
+ # the last view, announces the event and logs a failure: it only
63
+ # informs, never fails a turn.
64
+ # @param state_dir [String] the sessions dir
65
+ # @param running [Array<Hash>, nil] the running turn's conversation (Engine#live_conversation), nil between turns
66
+ # @return [Hash, nil] the :objectives_updated event to announce, nil when the view is the same
67
+ def announce(session, state_dir:, running:)
68
+ view = Samagotchi::Objectives.view(session.id, session.messages, state_dir: state_dir, running: running&.dup)
69
+ return if view.nil? || view == @announced
70
+
71
+ @announced = view
72
+ { type: :objectives_updated, session_id: session.id, **view }
89
73
  end
90
74
 
91
75
  # A change the model proposes (the objectives tool), kept until the turn
@@ -108,9 +92,15 @@ module Samagotchi
108
92
  # failed or rolled-back turn keeps none, and its proposals are dropped
109
93
  # here so the next turn does not show them either: their Apply card
110
94
  # would sit after a turn whose `objectives propose` call is not in the
111
- # history (a rollback after Ctrl-C drops it).
95
+ # history (a rollback after Ctrl-C drops it). The caller
96
+ # (Engine#show_objective_proposals) shows the cards.
97
+ # @return [Array<Hash>] the completed turn's cards (Engine#show_card's
98
+ # arguments); none for any other ending
112
99
  def finally(turn)
113
- turn.completed? ? show_proposals : discard_proposals(turn)
100
+ return proposal_cards if turn.completed?
101
+
102
+ discard_proposals(turn)
103
+ []
114
104
  end
115
105
 
116
106
  # For specs: the audit's asks' thread for +kind+ (ObjectivesAudit's
@@ -122,15 +112,13 @@ module Samagotchi
122
112
 
123
113
  private
124
114
 
125
- def show_proposals
126
- drain_proposals.each do |proposal|
115
+ def proposal_cards
116
+ drain_proposals.map do |proposal|
127
117
  id = "objectives-#{SecureRandom.hex(4)}"
128
- @show_card.call(source: Samagotchi::Objectives::SOURCE, id: id, title: proposal.title, body: proposal.body,
129
- actions: [{ label: "Apply", command: proposal.action_line(id) },
130
- { label: "Dismiss", command: "/objectives --card #{id} dismiss" }])
118
+ { source: Samagotchi::Objectives::SOURCE, id: id, title: proposal.title, body: proposal.body,
119
+ actions: [{ label: "Apply", command: proposal.action_line(id) },
120
+ { label: "Dismiss", command: "/objectives --card #{id} dismiss" }] }
131
121
  end
132
- rescue StandardError => e
133
- Log.warn(:turn, "objectives_proposal_failed", error: e.class.name, msg: e.message)
134
122
  end
135
123
 
136
124
  def discard_proposals(turn)
@@ -141,6 +129,22 @@ module Samagotchi
141
129
  def drain_proposals
142
130
  @proposals_mutex.synchronize { @proposals.slice!(0..) }
143
131
  end
132
+
133
+ # The audit for one call, over the values it is given: nothing of it
134
+ # is kept. Its client is asked for only once an active objective is
135
+ # there (ObjectivesAudit#ask), and with saving off nothing would come
136
+ # of an ask, so none is made.
137
+ def audit(state_dir, recorder)
138
+ ObjectivesAudit.new(
139
+ side: @side_judgments, state_dir: state_dir,
140
+ client_for: lambda do |judgment, session|
141
+ next nil unless recorder.saving?
142
+
143
+ recorder.wrap(SystemOne::Client.from_config(host_registry: @host_registry, mode: ObjectivesAudit::MODE, tag: :system_one),
144
+ judgment, session, mode: nil)
145
+ end
146
+ )
147
+ end
144
148
  end
145
149
  end
146
150
  end
@@ -23,17 +23,11 @@ module Samagotchi
23
23
  # @param host_registry [HostRegistry] System 1's client resolves its host here
24
24
  # @param toolkit_binding [ToolkitBinding] the kits, #suggest and #fell_back
25
25
  # @param tools [Tools::Registry] the registered tools, for the snapshot
26
- # @param state_dir [#call] the sessions dir (the suggestion log's place)
27
- # @param recording [#call] (client, judgment, session, mode:) → the client saving its runs
28
- # @param runs [#call] the Engine's SystemOne::Runs
29
- def initialize(side_judgments:, host_registry:, toolkit_binding:, tools:, state_dir:, recording:, runs:)
26
+ def initialize(side_judgments:, host_registry:, toolkit_binding:, tools:)
30
27
  @side_judgments = side_judgments
31
28
  @host_registry = host_registry
32
29
  @toolkit_binding = toolkit_binding
33
30
  @tools = tools
34
- @state_dir = state_dir
35
- @recording = recording
36
- @runs = runs
37
31
  @pending = nil
38
32
  @pending_turn = nil
39
33
  @warmup_thread = nil
@@ -52,12 +46,14 @@ module Samagotchi
52
46
  # snapshot. Audit never blocks: the thread logs its line. On mode
53
47
  # keeps the ask's Pending for +turn+, which #settle joins before
54
48
  # apply_toolkit!, with the turn's controller as the request's cancel
55
- # controller.
49
+ # controller. The suggestion log is in the turn's sessions dir
50
+ # (TurnContext#state_dir).
51
+ # @param recorder [SystemOne::Recorder] the ask's runs (built for this call)
56
52
  # @return [ToolkitSuggest::Pending, nil] nil unless the turn joins an ask
57
- def start(turn)
53
+ def start(turn, recorder:)
58
54
  @pending = nil
59
55
  @pending_turn = turn
60
- @pending = ask(turn)
56
+ @pending = ask(turn, recorder)
61
57
  end
62
58
 
63
59
  # On mode, before apply_toolkit!: wait for +turn+'s ask (#start) up to
@@ -154,14 +150,14 @@ module Samagotchi
154
150
  # never reached #start) is never touched.
155
151
  def pending_for(turn) = (@pending if @pending_turn.equal?(turn))
156
152
 
157
- def ask(turn)
153
+ def ask(turn, recorder)
158
154
  mode = Config.get("toolkit.suggest.mode").to_s
159
155
  return if mode == "off"
160
156
 
161
157
  joined = mode == "on"
162
158
  session = turn.session
163
159
  default = Toolkit.of(session.toolkit)
164
- log = suggestion_log
160
+ log = suggestion_log(turn.context.state_dir)
165
161
  # The predicate checks the session first; the kits (memory kits are
166
162
  # read from disk) and the log (read whole) only when it gets there.
167
163
  kits = nil
@@ -185,8 +181,8 @@ module Samagotchi
185
181
  return
186
182
  end
187
183
 
188
- client = @recording.call(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :toolkit),
189
- ToolkitSuggest::JUDGMENT, session, mode: Config.get("toolkit.suggest.mode").to_s)
184
+ client = recorder.wrap(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :toolkit),
185
+ ToolkitSuggest::JUDGMENT, session, mode: Config.get("toolkit.suggest.mode").to_s)
190
186
  return unless client
191
187
 
192
188
  # The warm-up's only value is the cache: the real ask never queues
@@ -215,7 +211,7 @@ module Samagotchi
215
211
  state: { project: state[:project].dup.freeze, prompt: state[:prompt].dup.freeze }.freeze
216
212
  )
217
213
  threshold = Config.get("toolkit.suggest.threshold")
218
- runs = session.scratch ? nil : @runs.call
214
+ runs = session.scratch ? nil : recorder.runs
219
215
  pending = ToolkitSuggest::Pending.new(log: log, joined: joined,
220
216
  acted: runs && ->(judged, did) { runs.acted(judged.fields[:run_id], did) },
221
217
  timeout_seconds: SystemOne::Client.configured_timeout_ms / 1000.0)
@@ -253,8 +249,8 @@ module Samagotchi
253
249
  end
254
250
  end
255
251
 
256
- def suggestion_log
257
- SideJudgments.log(ToolkitSuggest.log_path(@state_dir.call), tag: :toolkit, event: "toolkit_suggest_log_failed")
252
+ def suggestion_log(state_dir)
253
+ SideJudgments.log(ToolkitSuggest.log_path(state_dir), tag: :toolkit, event: "toolkit_suggest_log_failed")
258
254
  end
259
255
  end
260
256
  end
@@ -17,21 +17,20 @@ module Samagotchi
17
17
  # against its source session as the turn begins; at its end (inside
18
18
  # Engine#end_turn's event step) the opening's stamp, a Revise turn's
19
19
  # draft checked and a Verify turn's verdict recorded. No state of its
20
- # own: it reads the turn, and the sessions it loads from the state dir.
20
+ # own: it reads the turn, and the sessions it loads from the state dir
21
+ # it is given.
21
22
  class VerifyAnchors
22
- # @param state_dir [#call] the sessions dir (a fresh Verify child's parent is loaded from it)
23
- def initialize(state_dir:)
24
- @state_dir = state_dir
25
- end
26
-
27
23
  # A Verify turn's claim (Engine#begin_turn), when its quote is in the
28
24
  # source session's answer for that turn; nil (logged) otherwise.
29
25
  # @param anchor [VerifyAnchor, nil]
26
+ # @param state_dir [String] the sessions dir (a fresh Verify child's
27
+ # parent is loaded from it): the Engine's at the call, as no
28
+ # TurnContext holds it yet
30
29
  # @return [VerifyAnchor, nil]
31
- def verified(session, anchor)
30
+ def verified(session, anchor, state_dir:)
32
31
  return unless anchor
33
32
 
34
- source = source_session(session, anchor)
33
+ source = source_session(session, anchor, state_dir)
35
34
  return unless source
36
35
 
37
36
  turn = AnswerTail.turn_slice(Array(source.messages), anchor.turn_id)
@@ -133,14 +132,14 @@ module Samagotchi
133
132
  comparison_text(Redact.text(answer, viewer)).include?(quote_text)
134
133
  end
135
134
 
136
- def source_session(session, anchor)
135
+ def source_session(session, anchor, state_dir)
137
136
  return session unless session.verify_fresh
138
137
 
139
138
  fresh = session.verify_fresh
140
139
  return reject_verify(anchor, :fresh_anchor_mismatch) unless fresh.anchor == anchor
141
140
  return reject_verify(anchor, :fresh_parent_missing) unless session.parent_id
142
141
 
143
- Session.load(session.parent_id, state_dir: @state_dir.call)
142
+ Session.load(session.parent_id, state_dir: state_dir)
144
143
  rescue ArgumentError, SystemCallError, IOError => e
145
144
  Log.warn(:turn, "verify_anchor_rejected", turn_id: anchor.turn_id, reason: :source_session_unavailable,
146
145
  error: e.class.name)
@@ -17,22 +17,17 @@ module Samagotchi
17
17
  # @param prompt_warmup [PromptWarmup] the Engine's warm-up (the kernel asks it for a slot pin)
18
18
  # @param kernel [KernelLoop] the warm-up prompt, the last slot, warmed_up!
19
19
  # @param turn_state [TurnState] whether steers are carried to the next turn
20
- # @param model_settings [SessionModelSettings] the next turn's LLM context strategy
21
- # @param model [#call] the effective model now (Engine#effective_model)
22
- # @param system_prompt [#call] (model) the system prompt (Engine#system_prompt)
23
- # @param reminders_due [#call] whether a reminder is due (Engine#reminders_due?)
24
- # @param next_turn_waiting [#call] whether the next turn starts at once or none follows
25
- # (Engine#next_turn_waiting, called)
26
- def initialize(prompt_warmup:, kernel:, turn_state:, model_settings:, model:, system_prompt:, reminders_due:,
27
- next_turn_waiting:)
20
+ # @param model_settings [SessionModelSettings] the effective model now, the next turn's LLM context strategy
21
+ # @param reminder_queue [ReminderQueue] whether a reminder is due
22
+ # @param system_prompt [#call] (model) the system prompt (Engine#system_prompt): called last, only
23
+ # once every check passed, as building it has side effects (the memory index metric, the prompt notes)
24
+ def initialize(prompt_warmup:, kernel:, turn_state:, model_settings:, reminder_queue:, system_prompt:)
28
25
  @prompt_warmup = prompt_warmup
29
26
  @kernel = kernel
30
27
  @turn_state = turn_state
31
28
  @model_settings = model_settings
32
- @model = model
29
+ @reminder_queue = reminder_queue
33
30
  @system_prompt = system_prompt
34
- @reminders_due = reminders_due
35
- @next_turn_waiting = next_turn_waiting
36
31
  end
37
32
 
38
33
  # After a completed turn, prefill the next turn's prompt up to its user
@@ -40,14 +35,18 @@ module Samagotchi
40
35
  # local llama.cpp host on the native loop, and not when the next turn's
41
36
  # start differs anyway or starts at once: a reminder due, a continue
42
37
  # offer (the turn ran out of steps), a carried steer or queued input.
43
- # Never fails the turn.
44
- def after(turn, result)
38
+ # Never fails the turn. Each read comes only once the checks before it
39
+ # passed: the model resolved after the turn's own outcome, the probe
40
+ # and the prompt only on a target.
41
+ # @param next_turn_waiting [#call, nil] the host's probe: true when the next turn starts at once or none
42
+ # follows (Engine#next_turn_waiting); nil: neither
43
+ def after(turn, result, next_turn_waiting:)
45
44
  return if result.canceled? || result.resumable?
46
45
 
47
- model = @model.call
46
+ model = @model_settings.effective_model
48
47
  client = model.client
49
48
  return unless target?(model, client)
50
- return if @reminders_due.call || @turn_state.carried_steers? || @next_turn_waiting.call
49
+ return if @reminder_queue.due? || @turn_state.carried_steers? || next_turn_waiting&.call
51
50
 
52
51
  messages = ContextNote.with_system_head(turn.session.messages.dup, { role: "system", content: @system_prompt.call(model) })
53
52
  # Under the next turn's strategy (a /model switch may have changed it);
@@ -22,8 +22,8 @@ require "json"
22
22
  module Samagotchi
23
23
  # `chi update`: brings an installed chi up to date in one go: the system
24
24
  # bundle, the shipped bundles the user installed, and the macOS helper.
25
- # Unedited things update; edited ones merge or stay behind (.md) or are replaced
26
- # and reported (hooks, plugin, rules, which didn't load once edited). Live
25
+ # Unedited things update; edited ones merge or stay behind (.md) or are replaced,
26
+ # copied to the trash first, and reported (hooks, plugin, rules, scripts). Live
27
27
  # workers and a running chi web are reported, never restarted. The bundles
28
28
  # chi didn't ship are only checked, under the chi that will run them
29
29
  # (BundleCommand::InstalledCheck): what it finds is rows that fail nothing.
@@ -297,10 +297,14 @@ module Samagotchi
297
297
  earlier = [] if %i[installed skipped failed].include?(result.status)
298
298
  row = Row.new(component: "system bundle", from: result.from, to: result.to)
299
299
  notes = file_notes(row, system::BUNDLE_NAME, result.to, result.merged, result.behind, result.trash_copy, earlier: earlier)
300
+ # What the run warned of (a Checksum mismatch, a file it skipped) goes
301
+ # on its row, as ensure! echoes it.
302
+ warnings = Array(result.warnings)
303
+ warned = [*notes, *warnings]
300
304
  case result.status
301
- when :installed then done(row, :install)
302
- when :updated then done(row, :update, notes.empty? ? nil : notes.join("; "))
303
- when :restored then done(row, :restore, notes.empty? ? nil : notes.join("; ")).tap { |r| r.to = nil }
305
+ when :installed then done(row, :install, warnings.empty? ? nil : warnings.join("; "))
306
+ when :updated then done(row, :update, warned.empty? ? nil : warned.join("; "))
307
+ when :restored then done(row, :restore, warned.empty? ? nil : warned.join("; ")).tap { |r| r.to = nil }
304
308
  when :merged, :up_to_date then same_version(row, notes + result.warnings, changed: result.merged.any?)
305
309
  when :newer_installed then skipped(row, ["newer than this chi's #{result.to}: left", *notes].join("; ")).tap { |r| r.to = nil }
306
310
  when :skipped then skipped(row, result.error)
@@ -352,7 +356,7 @@ module Samagotchi
352
356
  notes = file_notes(row, update.name, update.to || update.from, update.merged, update.behind, update.trash_copy, earlier: earlier)
353
357
  case update.status
354
358
  when :would_update, :updated
355
- notes = [adds(update.adds), *notes, replaced(update.replaced), *update.kept_own].compact
359
+ notes = [adds(update.adds), *notes, replaced(update.replaced), *update.kept_own, update.note].compact
356
360
  done(row, :update, notes.empty? ? nil : notes.join("; "))
357
361
  when :would_merge, :merged, :up_to_date then same_version(row, [*notes, update.note, update.manifest].compact, changed: Array(update.merged).any?)
358
362
  when :skipped then skipped(row, [update.note, *notes].join("; "))
@@ -526,7 +530,7 @@ module Samagotchi
526
530
  def replaced(files)
527
531
  return nil if files.empty?
528
532
 
529
- "#{@dry_run ? "would replace" : "replaced"} edited #{files.join(", ")} (it wasn't loading)"
533
+ "#{@dry_run ? "would replace" : "replaced"} edited #{files.join(", ")} (a copy goes to the trash first)"
530
534
  end
531
535
 
532
536
  def print_table(rows)
@@ -7,6 +7,8 @@ require_relative "atomic_file"
7
7
  require_relative "log"
8
8
  require_relative "session"
9
9
  require_relative "session_metrics"
10
+ require_relative "session_row"
11
+ require_relative "worker_link"
10
12
  require_relative "steer"
11
13
  require_relative "verify_anchor"
12
14
  require_relative "verify_fresh_origin"
@@ -37,10 +39,6 @@ module Samagotchi
37
39
  # The child's first message, after the claim.
38
40
  ASK = "Verify this claim against the files here. You have none of the original session's context. " \
39
41
  "End with a line `Verdict: confirmed|refuted|unclear — <one-line reason>`."
40
- # How long a child whose worker hasn't come up yet counts as running
41
- # (Tools::Delegate::STARTING_GRACE_SECONDS).
42
- STARTING_GRACE_SECONDS = 15
43
-
44
42
  # A start refused: the HTTP +status+, the error +code+ and its words.
45
43
  class Refused < StandardError
46
44
  attr_reader :status, :code
@@ -257,15 +255,13 @@ module Samagotchi
257
255
  private_class_method :locked
258
256
 
259
257
  # A child runs while its worker runs a turn, or while it is marked
260
- # running and its worker is on its way (Tools::Delegate.running?).
258
+ # running and its worker is on its way (SessionRow.running_or_starting?,
259
+ # the delegates' rule).
261
260
  def child_running?(child_id, state_dir)
262
261
  child = Session.summary_from_file(Session.session_file(child_id, state_dir: state_dir)) or return false
263
- return false unless child.status == Session::STATUS_RUNNING
264
- return true if SessionManager.session_owner(child_id, state_dir: state_dir)&.worker?
265
-
266
- Time.now - Time.iso8601(child.updated_at.to_s) < STARTING_GRACE_SECONDS
267
- rescue ArgumentError
268
- false
262
+ live = WorkerLink.resolve(Session.session_dir(child_id, state_dir: state_dir), unlink: false).worker_owned?
263
+ SessionRow.running_or_starting?(saved_status: child.status, live: live,
264
+ busy: live && child.status == Session::STATUS_RUNNING, updated_at: child.updated_at)
269
265
  end
270
266
  private_class_method :child_running?
271
267
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Samagotchi
4
- VERSION = "0.155.0"
4
+ VERSION = "0.157.0"
5
5
  # Sent as the User-Agent of every HTTP request chi makes.
6
6
  USER_AGENT = "chi/#{VERSION}"
7
7
  end
@@ -21,15 +21,15 @@ module Samagotchi
21
21
  def no? = value == false
22
22
  end
23
23
 
24
- # @param target [HostRegistry::ModelTarget]
24
+ # @param lookup [ModelLookup] the model's layers (EffectiveModel#lookup);
25
+ # its target is the one the probes ask
25
26
  # @param profile [ModelProfile, nil] the model's prompt profile (native)
26
27
  # @param adapter [LLM::OpenAIChat, nil] the chat host's adapter
27
- # @param models [Hash{String => ModelSettings}, nil] ConfigFile.model_settings (specs)
28
- # @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
29
28
  # @return [Answer]
30
- def self.for(target, names:, profile: nil, adapter: nil, models: nil)
29
+ def self.for(lookup, profile: nil, adapter: nil)
30
+ target = lookup.target
31
31
  entry = target.entry
32
- configured = configured(target, entry, models, names)
32
+ configured = configured(lookup)
33
33
  return configured if configured&.no?
34
34
 
35
35
  if entry.chat?
@@ -45,18 +45,15 @@ module Samagotchi
45
45
  marker.is_a?(String) && !marker.empty? ? marker : nil
46
46
  end
47
47
 
48
- def self.configured(_target, entry, models, names)
49
- models ||= begin
50
- ConfigFile.model_settings
51
- rescue StandardError
52
- {}
53
- end
54
- if (setting = ConfigFile.model_setting(names, :vision, models: models))
48
+ def self.configured(lookup)
49
+ if (setting = lookup.model(:vision))
55
50
  return Answer.new(value: setting.value, reason: setting.value ? nil : "models: #{setting.key} sets vision: false")
56
51
  end
57
- return nil if entry.vision.nil?
58
52
 
59
- Answer.new(value: entry.vision, reason: entry.vision ? nil : "hosts.#{entry.name} sets vision: false")
53
+ vision = lookup.host(:vision)
54
+ return nil if vision.nil?
55
+
56
+ Answer.new(value: vision, reason: vision ? nil : "hosts.#{lookup.host_name} sets vision: false")
60
57
  end
61
58
 
62
59
  def self.native(target, _entry, profile, trust_config:)