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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55c4e338780fd58cf97835620377051e5de8efc5c487f42bdcc9de6c3a71a5b1
4
- data.tar.gz: d6f3a9f49b39b72b11a6b70c5715142ee2f54d5e6b1f94a4eb9695f3fedefd0a
3
+ metadata.gz: e6e6add2f9511492180ba39e11ebbcd9da6a703e5325facfc9ddc24bd22bc2eb
4
+ data.tar.gz: e426d754c3306c361fe54d1fde2475502e3121b2ab1a5caecad92712165056e1
5
5
  SHA512:
6
- metadata.gz: c91dd76d8d7a3bb4cc3c4432162ac91528aa8329585cc373ad6c811c68be9ff0ccf47ed12913ce19bb87edd59930ae699cf19fa5cc8128eb92f5f62cecc6624d
7
- data.tar.gz: 99dd1a2761d327300d3459407c09935aa4d8f3d2826e7db5a29087013071998f0fb1e6787d520a8d19c27b0a5ec67bf64b53aa177d17fe9a003cf3dc8190aa84
6
+ metadata.gz: a0026bf97c1ef84a78f181283c712490f1961972d49c10e2559fe29d2047d599223837c328a96b15aa382208d47f5d78a723842b575a624bfa992b71130a6082
7
+ data.tar.gz: 0f6e09c9abd81b9e218677b47373215a91788b735fb7c515d4ea72b563d9875ea836438a4b1b019ddf4d480418954363dcbe0bd01b23a545a59a864484f6f437
data/CHANGELOG.md CHANGED
@@ -8,6 +8,60 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.157.0] - 2026-11-08
12
+
13
+ ### Added
14
+
15
+ - `web.reload_check` (off by default): chi web checks each finished turn against a reload of it and logs `reload_mismatch` with the kinds that differed and the known divergences that explain them (no content).
16
+ - A bundle's plugin can guard tool calls fail-closed: `chi.on(:before_tool_call, on_error: :fail_closed)` denies the call its block raised on, and a plugin its manifest marks `required: true` (under `plugin:`) that fails to load (an edited file, an unmet `requires_chi`, a raise) makes chi deny every tool call until it is fixed, as a fail-closed bundle hook does. `chi bundle check` warns about a fail-closed hook whose plugin isn't marked required (docs/plugins.md).
17
+
18
+ ### Changed
19
+
20
+ - `/llm-context`, `/stats` and the web's LLM context chip name where a value came from as the other settings do: `session` and `hosts.NAME` (were `the session` and `hosts entry 'NAME'`).
21
+ - `chi sessions list` prints one line wherever it runs: the `--live`, `--cwd` and `--format text` listings and `chi --remote NAME sessions list` show the plain list's state (`idle`, not `live`) and description (no folder prefix), and the plain list's footer. The plain list's `ctx N%` now rounds as the picker and chi web round it (it could read 1% apart).
22
+ - A delegating parent's wait says `worker_gone` 5 s after a child's worker dies (was 15 s); a child whose worker never starts is reported `worker_gone` ("its worker did not start") after the 30 s boot limit (or the wait's timeout, if shorter: then the child shows as `running`, as for any wait that timed out).
23
+ - In chi web, a long tool output's hover shows the same cut live as after a reload (the first 2000 characters of a file read, the start and end of other output).
24
+ - A turn nobody typed (a reminder, an attached context change, a finished task, a delegate report) has an id of its own, `wake-` and 16 hex digits: `chi send --wait --format json` names it as the `turn_id`, and the `--turn` hints carry it.
25
+ - `chi send --wait -m` to a session left running by a worker that died names the message's own id as the `turn_id` in `--format json`, where it named the running turn's.
26
+ - `chi bundle install` of a bundle already installed from a source that moved on now takes the new text of each memory you never edited, as `chi bundle upgrade` would, and records the new version; a memory you edited is still kept (`--force` or `chi bundle upgrade` brings the bundle's changes).
27
+ - The known-names and source-links bundles hook through their plugin (known-names 0.2.0, source-links 0.7.0); `chi update` installs them. Nothing changes in what they do.
28
+
29
+ ### Fixed
30
+
31
+ - A `delegate` follow-up that the child's ending turn didn't take gets its own turn's reply, not the ended turn's.
32
+ - `chi send --wait` with no message on a wake turn (a reminder, a context change, a task's finish, a delegate report) names its `turn_id`, ends `canceled` (`worker_stopped`) when its worker dies in it, and `--turn` waits for it.
33
+ - A message or a delegate follow-up merged into a running turn as a steer waits for that turn's reply or end, which the worker now records, not for the session's next reply (a newer turn's could answer for it).
34
+ - `chi bundle uninstall` asks for `--force` before taking a memory you edited that has no base copy (chi compares it with the checksum its install recorded; with neither, it counts as edited), as it does for any edited file; it used to move it to the trash without asking.
35
+ - `chi bundle status` says `modified` for an edited memory that has no base copy; it said `ok` while the next upgrade left the file behind.
36
+ - `chi update`'s plan names every edited hook, rule, plugin file and script it replaces or removes, as its `Copied to the trash first` line does; it left out edited scripts and hooks installed with no recorded sha, and its row now says `a copy goes to the trash first` instead of `it wasn't loading`.
37
+ - `chi bundle install --dry-run` and `chi bundle upgrade --dry-run` report hooks, rules, the plugin and scripts as the real run does (`Would update`, `Would install`, `Up to date`); a changed hook showed as skipped, an unchanged rule or plugin as would install, and scripts not at all.
38
+ - `chi bundle install core --dry-run` (a profile's dry run) no longer names a hook it would replace as your own kept file.
39
+ - `chi bundle upgrade --dry-run` lists a memory that already has the bundle's text as up to date, as the upgrade itself does; it listed it as skipped.
40
+ - `chi bundle upgrade` (and `chi update`'s plan) leaves behind a memory that `chi bundle status` calls `modified` but whose base copy equals it, as it does any edit it can't merge; it replaced the file with the bundle's text and kept no copy.
41
+ - `chi bundle upgrade` (and `chi update`'s plan) takes the bundle's new text for a memory you never edited that has no base copy (chi compares it with the checksum its install recorded), as for any unedited memory; it left the file behind as a conflict while `chi bundle status` called it `ok`.
42
+ - A bundle memory whose bytes don't match the manifest's `files:` sha now warns `Checksum mismatch` when `chi bundle upgrade` (or a re-install from a source that moved on) brings its new text or merges it into your edit, as a first install does: a bad download warns on an upgrade too. A `--dry-run` checks the bundle's file it would write, a memory or a hook, so it warns as the real run does and no longer blames a memory you edited (`install --force --dry-run`) or the hook an upgrade would replace. `chi update` and `chi bootstrap` show these warnings on the bundle's row (a system bundle install, update or restore, and a shipped bundle's update); they dropped them.
43
+ - A system bundle memory chi puts back after you deleted it is checked against the manifest's `files:` sha too, and warns `Checksum mismatch` when its bytes differ, as an installed one does.
44
+ - A context window given with `--context-window-tokens` to a one-shot run (`-p … --non-interactive`) is named `cli`, not `config`, where its source shows: the debug log, and chi web's context tooltip for that session.
45
+ - A tool row's duration is the same live, after a reload and in the attached terminal (each timed the call with its own clock).
46
+ - chi web draws a finished turn after a reload or a reconnect exactly as it drew it live: the empty answer's "asking again" row stays in its step, a failed turn keeps its last step, a card the turn showed comes back where it was, and the reminders that went into a turn are shown. Turns from before this release, or run by a worker not yet restarted, show as before.
47
+ - `chi --attach` shows the last turn's tool rows as they ran (status, duration, retries).
48
+ - A saved recap is placed after the turn it covers, by that turn's id, where counting turns could put it a turn off (after a check-in, for one).
49
+ - In chi web, a turn that fails after its tool steps shows the same time on its timing line live as after a reload (the turn record's, as a canceled turn does).
50
+
51
+ Update with `chi update` (known-names 0.2.0, source-links 0.7.0).
52
+
53
+ ## [0.156.0] - 2026-11-08
54
+
55
+ ### Changed
56
+
57
+ - An id or prefix that names no session is refused in one wording everywhere: `no session ID` (the CLI commands, `chi --resume`/`--attach`, chi web's 404s and the session tools), where some said `Session not found: ID`. A `chi --remote` command shows it the same against an older chi web.
58
+
59
+ ### Fixed
60
+
61
+ - `chi sessions list` shows a session whose worker died mid-turn as idle, as chi web does, instead of running (the plain and the `--live`/`--cwd` listings; with `--remote`, once that chi web runs this release).
62
+
63
+ Update with `chi update`.
64
+
11
65
  ## [0.155.0] - 2026-11-07
12
66
 
13
67
  ### Added
@@ -5786,7 +5840,9 @@ and long-lived sessions.
5786
5840
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5787
5841
  hotkey panel that send selected text or the clipboard to your sessions.
5788
5842
 
5789
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.155.0...HEAD
5843
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.157.0...HEAD
5844
+ [0.157.0]: https://github.com/dm1try/samagotchi/compare/v0.156.0...v0.157.0
5845
+ [0.156.0]: https://github.com/dm1try/samagotchi/compare/v0.155.0...v0.156.0
5790
5846
  [0.155.0]: https://github.com/dm1try/samagotchi/compare/v0.154.1...v0.155.0
5791
5847
  [0.154.1]: https://github.com/dm1try/samagotchi/compare/v0.154.0...v0.154.1
5792
5848
  [0.154.0]: https://github.com/dm1try/samagotchi/compare/v0.153.0...v0.154.0
data/bin/chi CHANGED
@@ -263,6 +263,7 @@ if options[:web]
263
263
  exit Samagotchi::Web::Server.launch(port: port, scope: options[:scope] || "project", open_browser: options[:web_open],
264
264
  markdown: markdown, new_token: options[:new_token], tunnel: options[:tunnel],
265
265
  working_set: Samagotchi::Config.get("web.working_set"),
266
+ reload_check: Samagotchi::Config.get("web.reload_check"),
266
267
  annotate_presets: Samagotchi::Config.get("web.annotate_presets"),
267
268
  editor: Samagotchi::Config.get("web.editor"),
268
269
  start_dir: options[:start_dir] || Samagotchi::Config.get("web.start_dir"),
@@ -459,7 +460,7 @@ begin
459
460
  # Ctrl-C cancelled it.
460
461
  exit 1 if %i[turn_failed empty_answer iteration_limit].include?(ended)
461
462
  exit Samagotchi::CLI::Exit::INTERRUPTED if ended == :interrupted
462
- rescue Samagotchi::TerminalUI::SessionBusy, Samagotchi::TerminalUI::SessionNotFound, Samagotchi::ModelProfile::MissingModel,
463
+ rescue Samagotchi::TerminalUI::SessionBusy, Samagotchi::Session::NotFound, Samagotchi::ModelProfile::MissingModel,
463
464
  Samagotchi::Toolkit::Refused => e
464
465
  warn "Error: #{e.message}"
465
466
  exit 1
data/docs/architecture.md CHANGED
@@ -101,11 +101,11 @@ terminal renders the events it gets over the Bridge.
101
101
  | Core | `Samagotchi::Engine` | System prompt, memory injection, tool declarations, session lifecycle, model↔tool loop (`run_turn`). No terminal coupling. |
102
102
  | UI | `Samagotchi::TerminalUI`, `TerminalUI::AttachLauncher` / `AttachedLoop` | `TerminalUI`: the `--non-interactive` one-shot host, its own `Engine`. The attached TUI (every interactive `chi`): a client of the session worker's Bridge, no `Engine`. |
103
103
  | Model loops | `KernelLoop` (via `LLM::NativeBackend`), `LLM::ChatLoop` | The model↔tool loop: raw prompt, or a chat API (OpenAI chat, Anthropic Messages), chosen per host (see below). |
104
- | Turn | `TurnContext`, `TurnState`, `Engine::Turn` | One turn's frozen `TurnContext` (id, session, origin, author, participant mode, controller, folders, git facts, settings, model key/name and the tool surface (`Tools::ToolSurface`) from `generate`): built by `Engine#begin_turn`, published with the running flag by `TurnState` (the only way another thread reads the running turn: `Engine#turn_context` / `#last_turn_context`), handed explicitly down the tool path (`ModelBackend#complete(context:)`, `ToolRunner#run`, `Guardrails::Gate#evaluate`, `KernelLoop#dispatch_tool_call`). `Engine::Turn` keeps only the lifecycle state ([internals/engine.md](internals/engine.md), "Turn and TurnContext"). The turn stages (`TurnStages::ToolkitSuggestion`, `TurnStages::ContextJudge`, `TurnStages::Objectives`, `TurnStages::Warmup`, `TurnStages::VerifyAnchors`, `lib/samagotchi/turn_stages/`): the steps that keep state across the turn, built from constructor arguments and called by name from the turn's skeleton (internals/engine.md, "Turn stages"). |
104
+ | Turn | `TurnContext`, `TurnState`, `Engine::Turn` | One turn's frozen `TurnContext` (id, session, origin, author, participant mode, controller, folders, git facts, settings, model key/name and the tool surface (`Tools::ToolSurface`) from `generate`): built by `Engine#begin_turn`, published with the running flag by `TurnState` (the only way another thread reads the running turn: `Engine#turn_context` / `#last_turn_context`), handed explicitly down the tool path (`ModelBackend#complete(context:)`, `ToolRunner#run`, `Guardrails::Gate#evaluate`, `KernelLoop#dispatch_tool_call`). `Engine::Turn` keeps only the lifecycle state ([internals/engine.md](internals/engine.md), "Turn and TurnContext"). The turn stages (`TurnStages::ToolkitSuggestion`, `TurnStages::ContextJudge`, `TurnStages::Objectives`, `TurnStages::Warmup`, `TurnStages::VerifyAnchors`, `lib/samagotchi/turn_stages/`): the steps that keep state across the turn, built from the Engine-lifetime objects and called by name from the turn's skeleton with the values of the call (a `SystemOne::Recorder`, the sessions dir), not callbacks into the Engine (internals/engine.md, "Turn stages"). |
105
105
  | Adapters | `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | Raw-prompt servers, the OpenAI chat API, Anthropic's Messages API, and the HTTP they share. |
106
106
  | Tools | `lib/samagotchi/tools/*` | Execute, read, edit, write, memory, task_*, web_fetch, delegate/delegate_result, ask_user_question, reminders, send_note, list_sessions, context_read, objectives, forget_outputs, find_tools/call_tool (the tool index), plus runtime/output-guardrails. |
107
107
  | Bundles | `lib/samagotchi/memory_bundle/*`, `BundleCommand` | `chi bundle`: install/upgrade (3-way, with a text merge of edited memories), build, uninstall; the install record under `.bundles/<name>/` that the hook, guardrail and plugin loaders read ([internals/bundles.md](internals/bundles.md)). |
108
- | Background | `Samagotchi::Worker`, `Samagotchi::SessionManager` | `SessionManager` forks/spawns one worker process per session; its `Worker` builds the `Engine` and `Bridge` (no terminal rendering) and runs queued turns. The spawned process (own process group) forks the worker, which `setsid`s and is reparented to init once its starter exits (it waits until the worker holds `owner.lock`, so the spawner's `Process.detach` waiter tells a starting worker): nothing that walks the spawner's process tree reaches it (`SAMAGOTCHI_WORKER_DETACH=1`, `detach_from_spawner`). The lock's pid is the worker's; `spawn`'s `child_pid` is its starter's. |
108
+ | Background | `Samagotchi::Worker`, `Samagotchi::SessionManager` | `SessionManager` (`session_manager.rb`, its parts in `session_manager/`: spawn · worker_command · listing · delivery · lifecycle · removal · emptiness) forks/spawns one worker process per session. The worker process's main (owner lock, detach, signals, project env, what it does as it leaves) is `WorkerBoot` (`worker_boot.rb`), which SessionManager loads only as a worker boots (its `run_session_loop` forwarder); its `Worker` builds the `Engine` and `Bridge` (no terminal rendering) and runs queued turns. The spawned process (own process group) forks the worker, which `setsid`s and is reparented to init once its starter exits (it waits until the worker holds `owner.lock`, so the spawner's `Process.detach` waiter tells a starting worker): nothing that walks the spawner's process tree reaches it (`SAMAGOTCHI_WORKER_DETACH=1`, `detach_from_spawner`). The lock's pid is the worker's; `spawn`'s `child_pid` is its starter's. |
109
109
  | Web | `Samagotchi::Web::App`, `Samagotchi::Web::Server`, `Samagotchi::Web::SessionHub` | Rack+WEBrick on one port, `127.0.0.1:4567` (index.html + `/api/*` + SSE); with `web.host: lan` also on this machine's LAN address, behind an access token (`Web::Lan`, `Web::Token`). The hub is chi web's projection of the session list, pushed to every tab over `GET /api/events`. |
110
110
  | Sessions | `Samagotchi::Session`, `SessionManager` | File `sessions/<uuid>.json` + sidecar `input/`/`output/`; retention 14d/500, `updated_at desc`, lazy sweep. |
111
111
 
@@ -162,10 +162,12 @@ Unchanged: `ContextProviders`' `{ruby}` (`RbConfig.ruby`: the home's ruby inside
162
162
  project env below, so a `mise env` that sets GEM_HOME reaches it), `chi bundle
163
163
  try/test/check` (`-I LIB_DIR`), hooks (in-process). The worker's boot string, `run_session_loop(id, state_dir:)` and
164
164
  `SAMAGOTCHI_BOOT_FALLBACK` stay the cross-version contract (`spec/worker_contract_spec.rb`): the home only adds the
165
- prelude in front. An older chi that wakes a session doesn't read home.json and starts its worker as before.
165
+ prelude in front. The string names `SessionManager.run_session_loop`, which only forwards to
166
+ `WorkerBoot.run_session_loop`, the worker's main; the string switches to `WorkerBoot` one release later, and the
167
+ forwarder goes the release after that. An older chi that wakes a session doesn't read home.json and starts its worker as before.
166
168
 
167
169
  **The tools a worker runs** follow the project, not the boot, and only they do: a spawned worker
168
- (`WORKER_PROCESS_ENV`; never an in-process loop) calls `ProjectEnv.compute` in `run_session_loop`, after the owner
170
+ (`WORKER_PROCESS_ENV`; never an in-process loop) calls `ProjectEnv.compute` in `WorkerBoot.run_session_loop`, after the owner
169
171
  lock and before `Worker.new`, and installs the result (`ProjectEnv.install!`; a frozen `ProjectEnv::Overlay`,
170
172
  `{key => value or nil}`). Its ENV is never written: chi's own process (LLM calls, `HostKeys`, Net::HTTP proxies,
171
173
  Config, Paths, hooks, plugins) keeps the spawner's env. The overlay is the session folder's `mise env -C DIR --json`
@@ -426,14 +428,15 @@ each `<id>/` folder (recap.json, bridge.json, the stopped and archived markers),
426
428
  moved through `Session.summary_from_file`. Liveness is probed, since a killed owner leaves no
427
429
  file trace: the owner lock every tick for the sessions the projection believes owned, and every
428
430
  session every 10 s, so a `kill -9` shows within a second. The summary (`Web::SessionSummary`,
429
- shared with `/api/sessions` and the session view) carries `owner`, `project_root` and
431
+ shared with `/api/sessions` and the session view; built on the session's `SessionRow`, the one row every list
432
+ reads, so the page, `chi sessions list` and `/children` agree on status, liveness and a waiting question) carries `owner`, `project_root` and
430
433
  `bridge_up` (`WorkerLink` answers Live: a worker holds the lock and its sidecar names its pid; the page attaches its stream on it), and
431
434
  from that sidecar `worker_version` (the chi the worker runs) and `worker_features`
432
435
  (`Bridge::FEATURES`, what a client may ask of it beyond the routes: `restart`, `task_stop`, `reparent`, `unqueue`,
433
436
  `delivery`, a worker that queues a message for after the turn; `SessionManager.deliver_turn` and
434
437
  the attached TUI refuse a queue to a worker without it, which would steer; `turn_ids`, a worker whose running
435
438
  turn's mark, continue turn and Stop carry the turn's `enqueued_id`, which `chi answer`, `chi send --wait` with no
436
- message and `delegate_result` rely on through `ReplyWait.own_turn`). A worker's Bridge snapshot
439
+ message and `delegate_result` rely on through `ReplyWait.own_turn` (a wake or reminder turn has one too, worker-minted `wake-…`; a steer's merge is recorded in `input/merged/`, read by `ReplyWait::Poll#follow_merge`); every wait's binding is one `ReplyWait::Target`, built by its caller's constructor). A worker's Bridge snapshot
437
440
  names its `chi_version` too.
438
441
  `POST/DELETE /api/sessions` and `/stop` rescan the session before answering (`SessionHub#touch`).
439
442
  `GET /api/sessions` is the hub's projection too (its sort, paging and total). `Server` always builds
@@ -455,7 +458,7 @@ model: [internals/remote-sessions.md](internals/remote-sessions.md).
455
458
  | Route | Permission | Body / params | Answer | Refusals |
456
459
  |---|---|---|---|---|
457
460
  | `GET /api/sessions/resolve?id=` | owner | an id or a unique prefix | 200 `{id}` | 404 `not_found`; 400 `ambiguous_id` with `ids` |
458
- | `GET /api/sessions/summaries` | owner | `dir` (a folder: its project; none: every project), `cwd`, `live=1`, `archived=1`, `tests=0`, `sort`, `order`, `limit` | 200: the rows `chi sessions list` prints (`SessionManager.session_summaries`: `status`, `live`, `busy`, `owner`, `recap`, `ctx_pct`, `waiting`, `waiting_id`, `title`, `preview`, …) | 400 `invalid_dir`, `invalid_cwd` |
461
+ | `GET /api/sessions/summaries` | owner | `dir` (a folder: its project; none: every project), `cwd`, `live=1`, `archived=1`, `tests=0`, `sort`, `order`, `limit` | 200: the rows `chi sessions list` prints (`SessionRow#to_summary_h`, 28 keys in a fixed order: `status`, `live`, `busy`, `owner`, `recap`, `ctx_pct`, `waiting`, `waiting_id`, `title`, `preview`, …) | 400 `invalid_dir`, `invalid_cwd` |
459
462
  | `GET /api/projects?dir=` | owner | optional page-scope folder | 200: project rows (`Web::ProjectList`: the hub's projects plus the start project) with counts, roots, default/current marks; no `dir`: no current project | 400 `invalid_dir`; 503 `no_hub` |
460
463
  | `POST /api/sessions` (more fields) | owner | `toolkit`, `tools`, `no_tools`, `parent_answers`; `client_id` (`cli:send`/`cli:answer`: the first line is checked as chi send checks it); `check_vision: true` (images follow) | 201 adds `model_warning`, `parent_answers_warning` (a level looser than config.yml's, capped) and `first_turn_id` (the id the prompt it starts with gets for its turn; null for an `idle` create) | 400 `invalid_toolkit`, `invalid_parent_answers`, `invalid_continues` (beside `continues`); 403 `parent_answers_refused`, `parent_continue_refused`; 422 `no_vision` |
461
464
  | `POST /api/sessions/:id/turn` (more fields) | owner | `client_id` as above; `check_vision: true` with `images` | as before | 403 `parent_answers_refused`, `parent_continue_refused`; 422 `no_vision` |
@@ -500,13 +503,16 @@ verbatim) and the fields its class is built from, so a remote client raises the
500
503
  `scratch` (`ArchiveRefused`: `session_id`, `reason`, `busy_id`), a restart's `held`/`not_running`/`unsupported`/
501
504
  `failed` (`RestartRefused`: `session_id`, `reason`, `version`), a continue's `continued`/`open_children`/`folder_gone`
502
505
  (`SessionContinue::Refused`: `session_id`, `reason`, `ids`) and `ambiguous_id` (`Session::AmbiguousId`: `ids`).
506
+ A session an id names none of is `404 not_found` with `Session::NotFound`'s words (`no session X`, `#session_not_found`):
507
+ every lookup goes through `Session.resolve!` (an id or a unique prefix), and every surface shows that one message.
503
508
 
504
509
  ## Model loops and adapters
505
510
 
506
511
  A model ref (`--model`, `/model`, `default.model`, an alias's target) is parsed
507
512
  by one pure resolver, `ModelRef.parse` (host prefix, alias), given the configured
508
- hosts and aliases; `HostRegistry` routes a name that names no host (its model
509
- index after `/models`) and builds the `ModelTarget`.
513
+ hosts and aliases; `HostRegistry` (one per process for commands and chi web:
514
+ `HostRegistry.current`) routes a name that names no host (its model index after
515
+ `/models`) and builds the `ModelTarget`.
510
516
 
511
517
  Engine picks the loop from the effective model's host: `EffectiveModel.of` resolves it (`HostRegistry#resolve`)
512
518
  on every read, never kept, since config.yml and a model listing can move it without a `/model`
@@ -609,11 +615,12 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
609
615
 
610
616
  | Component | Main file(s) | What it is |
611
617
  |---|---|---|
612
- | Worker | `worker.rb` (`worker_idle_exit.rb`, `worker_inbound.rb`, `worker_signals.rb`, `worker_wakes.rb`, `worker_task_wakes.rb`, `task_watch.rb`) | A session's background process: builds the `Engine` and `Bridge`, runs queued turns and the wake turns nobody typed (delegate reports, a background task that finished unseen: [Finish wake](internals/background-tasks.md#finish-wake), attached context), exits when idle. A spawned one traps TERM, HUP and INT (`WorkerSignals`, installed by `run_session_loop` when `SAMAGOTCHI_WORKER_PROCESS=1`): the running turn stops as a Stop with reason `signal`, is saved, and the worker leaves (`:signal`, exit 0; a new worker only for input already queued). A running turn is checkpointed at its iteration boundaries (`TurnCheckpoint`, the `interrupted_turn` mark), and the next worker settles a turn its predecessor died in (`TurnCheckpoint.settle`). The session is saved from the turn's thread, Bridge threads and a signal's thread; `Session#save` takes a per-session lock around its snapshot and write, so an older snapshot never lands over a newer one. |
613
- | Config | `config.rb`, `config/restart_watch.rb` | Every setting as an `Entry`: env, config.yml and CLI layers ([configuration.md](configuration.md)); the notice a worker or chi web gets when config.yml changes a setting it reads only at its start. |
618
+ | Worker | `worker.rb` (`worker_boot.rb`, `worker_idle_exit.rb`, `worker_inbound.rb`, `worker_signals.rb`, `worker_wakes.rb`, `worker_task_wakes.rb`, `task_watch.rb`) | A session's background process: builds the `Engine` and `Bridge`, runs queued turns and the wake turns nobody typed (delegate reports, a background task that finished unseen: [Finish wake](internals/background-tasks.md#finish-wake), attached context), exits when idle. A spawned one traps TERM, HUP and INT (`WorkerSignals`, installed by `WorkerBoot.run_session_loop` when `SAMAGOTCHI_WORKER_PROCESS=1`): the running turn stops as a Stop with reason `signal`, is saved, and the worker leaves (`:signal`, exit 0; a new worker only for input already queued). A running turn is checkpointed at its iteration boundaries (`TurnCheckpoint`, the `interrupted_turn` mark), and the next worker settles a turn its predecessor died in (`TurnCheckpoint.settle`). The session is saved from the turn's thread, Bridge threads and a signal's thread; `Session#save` takes a per-session lock around its snapshot and write, so an older snapshot never lands over a newer one. |
619
+ | Config | `config.rb`, `config/restart_watch.rb` | Every setting as an `Entry`: env, config.yml and CLI layers ([configuration.md](configuration.md)); the notice a worker or chi web gets when config.yml changes a setting it reads only at its start. With `log.rb` it is the bottom layer: nothing it references reaches back to it, so `hosts:` and `models:` entries parse next to their types (`HostConfig.all`, `ModelSettings.all`). |
614
620
  | Paths | `paths.rb` (`Paths`) | The XDG base dirs chi's files live under, read from the env on every call: `Paths.state_home` (`$XDG_STATE_HOME`, else `~/.local/state`) and `Paths.config_home` (`$XDG_CONFIG_HOME`, else `~/.config`), both defaults under `Dir.home`; a blank variable counts as unset, a relative one is made absolute from the current directory. `Paths.tilde` shows a path with `~` for the home folder. `SessionManager` hands a spawned worker the spawner's `XDG_STATE_HOME` / `XDG_CONFIG_HOME`, when set, made absolute, so a relative one isn't read from the session's folder. |
615
621
  | MemoryBundle | `memory_bundle.rb`, `memory_bundle/{installer,builder,installed_bundle,plugin_ref,bundle_hook}.rb` (`Installer`, `Builder`, `InstalledBundle`, `PluginRef`, `BundleHook`) | Installing, building and reading bundles of memories, hooks and plugins. |
616
622
  | Bundle authoring | `bundle_command.rb` (`BundleCommand`), `bundle_command/{scaffold,check,test_run}.rb`, `bundle_command/try/{run,sandbox,trace}.rb`, `testing.rb` + `testing/` (the test kit) | `chi bundle new`, `check`, `test` and `try`, and the kit a bundle's tests load it with ([bundle-authoring.md](bundle-authoring.md), [internals/bundles.md](internals/bundles.md)). |
623
+ | Session file | `session.rb` (`Session`, `Session::FIELDS`), `session_field.rb` (`SessionField`), `session_paths.rb` (`SessionPaths`), `session_setup.rb` (`SessionSetup`) | A session's `<uuid>.json`: one `SessionField` row per key (default, cast, what the file gets; the codec on its type), from which `Session.new`, `.from_h` and `#to_h` are built; `SessionPaths` is where the file and the session's dir are and the id rule, a leaf `Session` extends (so `ArchiveStore`, `ContextSources` and `ParticipantMode` name no `Session`); `SessionSetup` the settings a fork and a continue carry, applied by member name. See [sessions.md](sessions.md#session-file). |
617
624
  | Session config (`--set`) | `session_config.rb` (`SessionConfig`, `SessionConfig::Settings`) | A session's own settings as one value (`Session#settings`, `SessionSetup#settings`): `core`, the `--set KEY=VALUE` values of the core keys marked `session: true` (`Config::Entry`), typed by `Config.coerce!` and read first through `Config.get(key, session:)` (`RecapSetup`; `Engine#session=` rebuilds the recap job, `IdleScheduler#replace_job`), file key `config_settings`; `bundles`, the `--set BUNDLE.KEY=VALUE` values, checked against the manifest's `session_settings:`, laid over `bundles: <name>:` in `ctx.settings` ([plugins.md](plugins.md)), file key `bundle_settings`. `SessionConfig.clash` refuses a bundle setting named like a core one (install, `chi bundle check`). See [configuration.md](configuration.md#per-session-settings). |
618
625
  | Plugin API | `plugin/api.rb` (`Plugin::Api`), `plugin/tool_spec.rb` (`Plugin::ToolSpec`: a checked tool declaration, how it is registered), `plugin/loader.rb`, `plugin/context.rb` (`ctx`), `plugin/http.rb` + `plugin/http_transport.rb` (`ctx.http`), `plugin/card.rb` (`Plugin::Card`) | What a bundle's plugin registers: tools, commands, annotation actions, hooks, services, toolkits, peer sources; what its handlers get as `ctx` ([plugins.md](plugins.md)). |
619
626
  | Peer bundles' base | `plugin/peer.rb` (`Plugin::Peer`) | What claude-peer and codex-peer share (the send tool, the command, the cards, payloads); each bundle's subclass sets its strings and transport ([bundles.md](bundles.md#the-claude-peer-bundle)). |
@@ -630,7 +637,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
630
637
  | LLM context strategies | `llm_context_strategy.rb`, `llm_context_view.rb`, `llm_context_stale.rb`, `llm_context_forget.rb` | What a request sends of the history (none, stale, forget) ([internals/llm-context-forget.md](internals/llm-context-forget.md)). |
631
638
  | Session chains | `session_continue.rb` (`SessionContinue`), `session_chain.rb` (`SessionChain`) | A session that continues an earlier one. `SessionChain.opener_for` gives the text web `continue →` prefills in the new link: the previous link's own first typed message when that link continues one itself, else nil (the 201's `opener`; not saved) ([sessions.md](sessions.md)). |
632
639
  | Review sessions | `review_start.rb` (`ReviewStart`), `session_review.rb` (`SessionReview`, the session's `review` field), `review_command.rb` (`ReviewCommand`, `chi review`), `review_apply.rb` (`ReviewApply`) | A session made to be shared: idle, its subjects project files checked by `Guardrails::ReviewPaths`, a fork of a working session from its recap (`--from`); Apply quotes a participant's review turn into the owner's composer ([sessions.md](sessions.md#review-sessions), [internals/participants.md](internals/participants.md)). |
633
- | chi web's routes | `web/app.rb` (`Web::App`: the gate, `ROUTES`, the shared helpers), `web/message_display.rb` (`Web::MessageDisplay`: saved messages and cards as display items, `POST /api/render`), `web/session_show.rb` (`Web::SessionShow`: `GET /api/sessions/:id` with its `cards`, `tail` and `timing` re-reads, `GET …/output`, the timing payload), `web/session_create.rb` (`Web::SessionCreate`: `POST /api/sessions`, a continue, a review fork), `web/session_control.rb` (`Web::SessionControl`: answer, command, turn and images, cancel, stop, restart, archive, delete), `web/push_routes.rb` (`Web::PushRoutes`: `/api/push`, its subscriptions, prefs, test and presence), `web/block_renderer_routes.rb` (`Web::BlockRendererRoutes`: `GET /api/block_renderers` and one renderer's file, [internals/block-renderers.md](internals/block-renderers.md)) | The handlers `App`'s `ROUTES` name, in the file that holds their kind; each mixin is included in `App` and reads its helpers and ivars, so `app.send(:handle_show, …)` and the specs that stub `App` methods work as before. |
640
+ | chi web's routes | `web/app.rb` (`Web::App`: the gate, `ROUTES`, the shared helpers), `web/message_display.rb` (`Web::MessageDisplay`: saved messages and cards as display items, `POST /api/render`), `web/session_show.rb` (`Web::SessionShow`: `GET /api/sessions/:id` with its `cards`, `tail` and `timing` re-reads, `GET …/output`, the timing payload), `web/session_create.rb` (`Web::SessionCreate`: `POST /api/sessions`, a continue, a review fork), `web/session_control.rb` (`Web::SessionControl`: answer, command, turn and images, cancel, stop, restart, archive, delete), `web/push_routes.rb` (`Web::PushRoutes`: `/api/push`, its subscriptions, prefs, test and presence), `web/block_renderer_routes.rb` (`Web::BlockRendererRoutes`: `GET /api/block_renderers` and one renderer's file, [internals/block-renderers.md](internals/block-renderers.md)), `web/reload_check_route.rb` (`Web::ReloadCheckRoute`: `POST /api/sessions/:id/reload_check`, the opt-in `web.reload_check`'s counts from the page's `reload_check.js`, 204 and one `reload_checked` log line, with `reload_mismatch`, its `kinds`, `excused` (ACCEPTED ids) and `explained` when the turn read otherwise after a reload; the worker's version is chi web's own view, its sidecar) | The handlers `App`'s `ROUTES` name, in the file that holds their kind; each mixin is included in `App` and reads its helpers and ivars, so `app.send(:handle_show, …)` and the specs that stub `App` methods work as before. |
634
641
  | Web Push | `web/push.rb` (`Web::Push`: `Runtime`, `preflight!`, the `web.push.*` settings), `web/push/crypto.rb` (`Push::Crypto`: RFC 8291 aes128gcm, RFC 8292 VAPID), `web/push/store.rb` (`Push::Store`, `Store::Sub`: `web-push/vapid.pem`, `subscriptions.json`), `web/push/client.rb` (`Push::Client`, `Client::Result`), `web/push/attention.rb` (`Push::Attention`: notify.js's rules in Ruby, shared cases in `spec/fixtures/attention_cases.json`), `web/push/presence.rb` (`Push::Presence`: the pages' "in front" reports), `web/push/notifier.rb` (`Push::Notifier`, a hub subscriber with a sender thread), `web/push_routes.rb` (`Web::PushRoutes`), `private_files.rb` (`PrivateFiles`, shared with the grants), `public/sw.js`, `public/push.js`, `public/manifest.webmanifest` | chi web pushes what the tab notifies for (approvals, questions, step-limit questions, cards, failed turns; done turns to a device that asks) to the owner's Home Screen app over an https public URL: the Notifier turns the hub's new attentions into show pushes after a hold, unless a chi web page is in front, at most 30 an hour, with one retry and a restart catch-up (`sent.json`) (each carries the keys still open, and the badge; no close push: every push shows a notification); the bell (`notifications.js`) subscribes the device, and the service worker and the page close the answered ones. See [internals/web-push.md](internals/web-push.md). |
635
642
  | chi web's updates | `web/update_check.rb` (`Web::UpdateCheck`), `web/update_ops.rb` (`Web::UpdateOps`), `web/auto_update.rb` (`Web::AutoUpdate`) | The RubyGems checker and its `update_check.json`; the install, worker-restart and chi-web-restart operations (one at a time) the owner's routes and AutoUpdate share; `update.auto: install`'s thread (install when no turn runs, then idle workers and chi web). See [configuration](configuration.md#unattended-updates-updateauto). |
636
643
  | Behind an auth proxy | `web/public_url.rb` (`Web::PublicUrl`: `web.public_url`, `web.proxy.identity_header`, `web.proxy.owner`), `web/proxy_gate.rb` (`Web::ProxyGate`, mixed into App; `RawHeaderNames`, prepended to WEBrick's request by `Web::Server`), `web/link_codes.rb` (`Web::LinkCodes`: the links' one-time codes), `proxy_trace.rb` (`ProxyTrace`), `web/public/token_fragment.js`, `web/public/auth_lost.js`, `web/tunnel.rb` + `web/tunnel/tailscale.rb` (`Web::Tunnel`, `Web::Tunnel::Tailscale`: `chi web --tunnel tailscale`, Tailscale Serve set for one run and the PublicUrl it makes) | Experimental: chi web behind an auth proxy. Every request needs the token; a proxied one's owner also needs the identity header (one clean value under one name); the `__Host-` cookie, an absolute `Location` from the public URL, https links with a one-time code in the fragment; the page's lost-sign-in banner. Bridges refuse proxy traces ([configuration.md](configuration.md#behind-an-auth-proxy)). |
@@ -642,11 +649,13 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
642
649
  | Tool output cut | `tool_output_cut.rb` (`ToolOutputCut`), `tool_output_files.rb` (`ToolOutputFiles`) | A tool's output over `max_tool_output_chars` (a `memory_read`'s: `memory.read_max_chars`, 0 = none, plus a guard at half a known window), cut by the tool's policy; an execute's whole output saved in the session's `tool_output/` folder ([internals/tool-output-limits.md](internals/tool-output-limits.md)). |
643
650
  | Steer delivery | `steer.rb` (`Steer`), `delivery.rb` (`Delivery`), `steer_cut.rb` (`SteerCut`), `generation_phase.rb`, `waiting_steer.rb`, `steer_cut_after.rb` (`SteerCutAfter`), `cut_policy.rb` (`CutPolicy`) | A message for a running turn: at the next boundary, cutting a thinking-only generation, or queued for the turn's end ([internals/engine.md](internals/engine.md)). |
644
651
  | Prompt files | `prompt_files.rb` (`PromptFiles`) | The AGENTS.md chain (root → cwd) and the appended files the system prompt carries. |
645
- | Model settings | `effective_model.rb` (`EffectiveModel`, `ModelChoice`), `model_profile.rb` (`ModelProfile`), `model_settings.rb`, `sampling_settings.rb`, `model_catalog.rb` (`ModelCatalog`), `model_notes.rb` (`ModelNotes`) | The effective model's settings as one snapshot per read, a model's prompt format, its `models:` entry, its request parameters, the pickable model rows (`chi models`, `/api/models`), the notes its prompt loads. |
652
+ | Model settings | `effective_model.rb` (`EffectiveModel`, `ModelChoice`), `model_lookup.rb` (`ModelLookup`: every layer's input), `model_profile.rb` (`ModelProfile`), `model_settings.rb`, `sampling_settings.rb`, `model_catalog.rb` (`ModelCatalog`), `model_notes.rb` (`ModelNotes`) | The effective model's settings as one snapshot per read, a model's prompt format, its `models:` entry, its request parameters, the pickable model rows (`chi models`, `/api/models`), the notes its prompt loads. |
646
653
  | Session settings | `session_setting.rb` (`SessionSetting`), `session_model_settings.rb` (`SessionModelSettings`), `level_setting_command.rb` (`/thinking`, `/reasoning-history`), `llm_context_command.rb`, `toolkit_command.rb`, `thinking_notices.rb` (`ThinkingNotices`) | A session's own llm_context, thinking, reasoning_history and toolkit: their chips (session state, `/stats`, `:command_ran`, the web), the commands that set them, the thinking level's notices. |
647
654
  | Dialects | `llm/dialect.rb` (`LLM::Dialect`) | What an `api: anthropic` provider's `/v1/messages` does differently (`dialect: fireworks`) ([internals/anthropic.md](internals/anthropic.md)). |
648
655
  | Logging | `log.rb` (`Log`), `log_subscriber.rb` (`LogSubscriber`), `log_path.rb` (`LogPath`), `log_line.rb` | The debug log: tagged records, a session's event trail, where the file goes ([configuration.md](configuration.md#debug-log-file)). |
649
656
  | Stores | `reminder_store.rb` (`ReminderStore`), `recap_store.rb` (`RecapStore`) | A session's reminders, and its idle recap (`recap.json`). |
657
+ | Reply waits | `reply_wait.rb` (`ReplyWait`, `GONE_AFTER`: the one dead-worker rule), `reply_wait/poll.rb` (`ReplyWait::Poll`, one look per poll), `reply_wait/target.rb` (`ReplyWait::Target`: which turn a wait binds, cursor, baseline, `owner_grace`; `first_message`, `message`, `wait_only`, `answer`, `participant`, `from_cursor`), `reply_wait/liveness.rb` (`ReplyWait::Liveness`: one owner and link read per look; `#owner?` for gone, `#worker_owned?` for an open question) | `chi send --wait`, `chi answer`, a participant's wait, the delegate tools and delegate reports wait through `ReplyWait.call(id, **target.to_h, …)` ([sub-agent.md](sub-agent.md)). |
658
+ | Session rows | `session_row.rb` (`SessionRow`) | A session as the lists see it: scalar facts only, built by `SessionManager.session_rows` (one owner, link, recap and analytics read each), `#to_summary_h` the summaries route's body and `.from_summary_h` a remote's rows read back, `#cli_line` the one `chi sessions list` line, `.running_or_starting?` the delegates' and Verify fresh's "running" (a 15 s grace for a worker on its way). A leaf: never session_manager or web. |
650
659
  | Setup and update | `bootstrap_command.rb`, `bootstrap/`, `update_command.rb`, `gem_update.rb` | `chi bootstrap` (first config.yml, system bundle, core profile) and `chi update` (the gem, shipped bundles, the macOS helper). |
651
660
  | Process groups | `process_group.rb` (`ProcessGroup`) | A command in a process group of its own, stopped as a group (TERM, grace, KILL): execute, tasks, context fetches, the mcp bundle's servers. |
652
661
  | Desktop | `desktop.rb`, `desktop/macos.rb`, `desktop_command.rb`, `desktop/macos/*.swift` | `chi desktop`, the macOS "Send to chi" helper ([desktop.md](desktop.md)). The helper's Swift side: the panel (`Panel.swift`), the chi CLI runner (`ChiRunner.swift`), the session rows and the chi calls a send makes (`Sessions.swift`: `SendPlan`; `ChiHelper --sessions` and `--send-plan` are their CLI seams), agent terminals behind `AgentTerminal` (`AgentTerminal.swift`, `Kitty.swift`, `Tmux.swift`; `ChiHelper --agents` is their CLI seam). |
data/docs/bundles.md CHANGED
@@ -4,7 +4,9 @@ What each shipped bundle does for you, its commands, settings and files. A bundl
4
4
  the plugin API ([plugins.md](plugins.md)); install, upgrade and the `chi bundle` commands are in
5
5
  [plugins.md](plugins.md#the-bundle-commands) and [cli.md](cli.md). Two shipped bundles are documented with the
6
6
  feature they belong to: **source-links** in [hooks.md](hooks.md#the-source-links-bundle) and **known-names** in
7
- [guardrails.md](guardrails.md#the-known-names-bundle).
7
+ [guardrails.md](guardrails.md#the-known-names-bundle). Both hook events from their `plugin.rb` with `chi.on`
8
+ (known-names on `before_tool_call`, priority 50; source-links on `after_turn` and `question_asked`, priority 90), as
9
+ every shipped bundle with code does; before known-names 0.2.0 and source-links 0.7.0 they shipped a `hooks/*.rb` file.
8
10
 
9
11
  ## The btw bundle
10
12
 
data/docs/cli.md CHANGED
@@ -17,7 +17,7 @@
17
17
  - `chi web push ls [--format json]`, `chi web push rm ID`, `chi web push test [ID]` — the phones chi web pushes to (see [Phone notifications](configuration.md#phone-notifications-web-push)): list them (short id, label, origin, added, last delivery, done turns, last error; `stale` for one made on another public URL than chi web's now: never sent to), remove one, or send a test notification to ID or to every device not stale. chi web need not run; nothing printed holds a device's push address or keys. A usage error exits 2; a store others can read, a corrupt one, an unknown device or a failed test push exit 1. This machine only: `chi --remote X web push` exits 2
18
18
  - `chi web --web-markdown` / `--no-web-markdown` — opt in to, or opt out of, sanitized Markdown rendering for completed assistant messages, the stage's asking text and a resolved question card's reply (`web.markdown`, off by default; `--no-web-markdown` turns a config's `web.markdown: true` off for this run)
19
19
  - `chi web --no-web-working-set` — turns the stage view's working set off (on by default; `--web-working-set` turns a config's `web.working_set: false` back on): what each turn brought into the model's context, in the stage and under each finished turn; `?working_set=1|0` on the page URL overrides it (see [The working set](#the-working-set))
20
- - `chi sessions list|stop|restart|archive|unarchive|delete|prune|clean|stats` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)). `stats ID` prints a session's cost, tokens and progress, and its own settings (`--set`; a `settings:` line), from a live worker's `GET stats`, else its saved `analytics.json` (`--format json` one object, with `settings`), without running a model turn. A usage error (an unknown subcommand or flag, a flag missing its value, `stop`, `restart` or `delete` with no ids, a bad `list --format` or `--scope`, a `--days`, `--keep` or `--limit` that isn't a whole number) exits 2; an unknown or refused session exits 1
20
+ - `chi sessions list|stop|restart|archive|unarchive|delete|prune|clean|stats` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)); one line per session (`<id> <state> <ctx> <updated_at> <text> [flags] ↳ … ↪ …`) and one footer (`N session(s) (sort=… order=…)`, or the project note) for the plain list, the `--live`/`--cwd` text and `--remote`. `stats ID` prints a session's cost, tokens and progress, and its own settings (`--set`; a `settings:` line), from a live worker's `GET stats`, else its saved `analytics.json` (`--format json` one object, with `settings`), without running a model turn. A usage error (an unknown subcommand or flag, a flag missing its value, `stop`, `restart` or `delete` with no ids, a bad `list --format` or `--scope`, a `--days`, `--keep` or `--limit` that isn't a whole number) exits 2; an unknown or refused session exits 1
21
21
  - `chi note [--source NAME] [-m TEXT] (ID|PREFIX)... | --all` — add a context note (TEXT or stdin) to sessions: background the model sees on its next turn; it starts no turn (see [Sessions: Context notes](sessions.md#context-notes))
22
22
  - `chi broadcast [-m TEXT] [--all] [--dry-run]`, `chi broadcast log [--last N] [--format json]`, `chi broadcast deliver BROADCAST_ID (ID|PREFIX)...` — share one piece of information (TEXT or stdin) with every session it may concern, as a context note from `broadcast` (see [Broadcast](broadcast.md))
23
23
  - `chi context <add|push|ls|show|refresh|rm|mute|unmute> …` — attach live external text to sessions (a command chi runs every so often, or text pushed in); the model gets a short note when it changes and reads it with `context_read`; `chi context add <URL>` attaches a GitHub PR with the github-pr bundle; `chi context push NAME -m TEXT` (the long form is `--message TEXT`) gives a `--push` source its new text, read from stdin when no `-m` is given (see [Attached context](context.md))
@@ -159,7 +159,7 @@ The loop for a bundle you're writing (the whole path: [bundle-authoring.md](bund
159
159
  then forces), `install` (each Installer warning, a `needs` one as warn, and each plugin or hook load failure at
160
160
  the source `file:line`; skipped while a placeholder sha is unfilled), `registered` (info: each command, tool,
161
161
  hook with its priority, init, toolkit, tool source, peer source and prompt section), `hook-event` (warn: a `chi.on` event chi
162
- never fires), `settings` (warn: a `--set` the manifest's `settings:` rules out, as config.yml's `bundles:` is told), `api` (warn: a `ctx.*` / `chi.*` call or a keyword this chi's API lacks, read from the source with
162
+ never fires), `fail-closed` (warn: a `chi.on(..., on_error: :fail_closed)` in a plugin whose manifest `plugin:` lacks `required: true`, so a load failure would not deny tool calls; not on an installed bundle's re-check), `settings` (warn: a `--set` the manifest's `settings:` rules out, as config.yml's `bundles:` is told), `api` (warn: a `ctx.*` / `chi.*` call or a keyword this chi's API lacks, read from the source with
163
163
  Prism against the API's own classes, on a path that never runs too; written on a local `ctx` / `chi` itself, so a
164
164
  `s = ctx.sessions` variable isn't followed; a name the plugin guards with `respond_to?` is left), `boundary` (warn: a `Samagotchi::` name outside "Stable for bundles"; a shipped bundle's listed
165
165
  exceptions aside. On an installed bundle (`chi update`) only a name this chi no longer has is a row: the constant
@@ -339,8 +339,9 @@ Also shipped, not installed: dev (mcp, skills, source-links) (chi bundle install
339
339
  the merge itself, and `--force` replacing the file. A dry run and an automatic update
340
340
  (`update.auto`) show it and leave it, and so does a failed row or one with no place
341
341
  for it (a skipped system bundle). A memory that can't be read at a
342
- newer version fails that bundle's row with the file's name. Hooks, rules and the plugin are replaced; an edited one didn't
343
- load anyway (its sha no longer matched), the row says it was replaced, and
342
+ newer version fails that bundle's row with the file's name. Hooks, rules, the plugin and scripts are replaced. One
343
+ whose bytes are neither what the install recorded (nothing recorded counts as edited) nor the new version's is
344
+ edited: the row says `replaced edited hooks/x (a copy goes to the trash first)` (`would replace` in the plan), and
344
345
  a copy goes to the bundle trash first (`chi bundle trash`; `hooks/x`,
345
346
  `guardrails/x`, `plugin/x`, `scripts/x` inside the dir).
346
347
  A bundle of the same name from elsewhere (a zip, git) is skipped ("not from
@@ -801,8 +802,8 @@ also offline (from the saved records). `/recap` is in
801
802
  Switch](#runtime-model-switch-assist-mode).
802
803
 
803
804
  `/llm-context` shows the LLM context strategy, apply rule and budget the next
804
- turn runs under, each with where it came from (`the session`, `models: <key>`,
805
- `hosts entry '<name>'`, `llm_context.*`), and the session's own values.
805
+ turn runs under, each with where it came from (`session`, `models: <key>`,
806
+ `hosts.<name>`, `llm_context.*`), and the session's own values.
806
807
  `/llm-context strategy <none|stale|stale,forget> apply <payoff|next_request|turn_end>
807
808
  budget <tokens|64k|off>` sets the session's own (any of the three, in one line;
808
809
  `default` unsets one), and `/llm-context reset` unsets them all. A change is
@@ -1527,8 +1528,8 @@ CHI_REMOTE=studio chi context push claude-outbox ab12 < answer.json
1527
1528
  - `send --new` needs `--dir`, that machine's absolute path (no folder of this
1528
1529
  one is guessed); `--toolkit`, `--set` and `--parent-answers` are checked
1529
1530
  against that machine's config and bundles. `sessions list` lists every
1530
- project's sessions there (`--scope=project --cwd DIR` one project's) in the
1531
- `--live` text form. `context` takes the session ids (no default session),
1531
+ project's sessions there (`--scope=project --cwd DIR` one project's), each
1532
+ on the same line a list here prints. `context` takes the session ids (no default session),
1532
1533
  and a URL resolves through that machine's bundles.
1533
1534
  - That machine's config.yml decides what it checks for `send` and `answer`:
1534
1535
  a parent agent's `/continue yes` and options (`guardrails.parent_answers`),
@@ -105,7 +105,9 @@ Behavior:
105
105
  - A key chi doesn't read warns at start, with the closest known key:
106
106
  `config: unknown key 'default.modle' (did you mean 'default.model'?)`.
107
107
  Names you choose under the maps below (host names, model ids) don't warn.
108
- - Environment variables and CLI flags win over config-file values.
108
+ - Environment variables and CLI flags win over config-file values. A model's
109
+ own settings (`models:`, `hosts:`) layer in between: see
110
+ [Where a model setting comes from](#where-a-model-setting-comes-from).
109
111
  - An edit to the file needs no restart of `chi web`. A new worker reads the
110
112
  file when it starts; a running worker sees only settings whose readers check
111
113
  the file again (see [When a change applies](#when-a-change-applies)). The web
@@ -148,7 +150,7 @@ or `chi web` restart.
148
150
  | `default.model` | New session | Existing sessions keep their saved model; change one with `/model`. |
149
151
  | `default.input`, `no_default_input`, `no_interrupt` | Next CLI run | These set up that run's first prompt or turn limit. |
150
152
  | `default.max_tokens`, `max_tool_output_chars`, `memory.read_max_chars`, `read.*`, `execute.*` except `execute.description`, `image.*` | Next matching request or tool use | An operation already in progress keeps its resolved limits. |
151
- | `server.host`, `server.port`, `hosts.*` | Worker start | Includes host addresses, APIs, declared models and host-level settings. The web model picker refreshes `hosts:` on file change; a running worker does not. |
153
+ | `server.host`, `server.port`, `hosts.*` | Worker start | Includes host addresses, APIs, declared models and host-level settings. Commands (`chi self`, `chi models`, the session lists) and chi web (its model picker, the session cards) read `hosts:` as it is now; a running worker keeps the hosts it started with until it restarts. |
152
154
  | `server.open_timeout`, `server.read_timeout`, `server.first_token_timeout` | Worker start | The worker's clients retain the timeouts set when they were built. |
153
155
  | `model_aliases` | Next model lookup | A session keeps its resolved model unless it is explicitly switched. |
154
156
  | `models.<key>.profile` | Session start or `/model` | It is resolved when that model is selected; editing it does not switch the current model's profile by itself. |
@@ -229,6 +231,35 @@ A few environment variables are not settings (no config.yml key, no flag):
229
231
  - `SAMAGOTCHI_LOG_STRICT=1`: an unknown log tag or a malformed log event raises instead of being dropped. The spec
230
232
  suite sets it, and a bundle's tests keep it (`Samagotchi::Testing::Sandbox`).
231
233
 
234
+ ## Where a model setting comes from
235
+
236
+ A model's settings come in layers, and the first one set wins. Every surface reads them the same way: a turn,
237
+ `/model` and `/stats`, `chi self`, the web's chips and the start page's ⚙ panel, and a session list's ctx %. A
238
+ `models:` entry is looked up under each name the model goes by, the most specific first: the name as typed (an
239
+ alias), the alias's target (`host:id`), the part after a host prefix, then the id the host is asked for.
240
+
241
+ | Setting | Order, first set wins |
242
+ |---|---|
243
+ | `thinking` | the session's own (`/thinking`, `chi --thinking`) → `chi web --thinking` or `SAMAGOTCHI_THINKING_LEVEL` → `models:` → `hosts.<name>.thinking` → `thinking.level` in config.yml → `default` |
244
+ | `reasoning_history` | the session's own (`/reasoning-history`) → `SAMAGOTCHI_REASONING_HISTORY` → `models:` → `hosts.<name>` → the api's default (`all` on `api: anthropic`, else `turn`) |
245
+ | `steer_cut_after` | `SAMAGOTCHI_STEER_CUT_AFTER` → `models:` → `hosts.<name>` → `steer.cut_after` in config.yml → 20 |
246
+ | `profile` | `--profile` or `SAMAGOTCHI_MODEL_PROFILE` → `models:` (an unknown profile warns, and the next name is tried) → `hosts.<name>.profile` → the server's chat template → the name → `qwen36` |
247
+ | `sampling` | merged: `hosts.<name>.sampling`, then `models:` over it, key by key |
248
+ | `vision` | `models:` → `hosts.<name>.vision` → what the server or the host's model list says |
249
+ | `window_tokens` | the server's `n_ctx` → the host's model list → `models:` → `hosts.<name>.window_tokens` → `context.window_tokens` (`--context-window-tokens`, `SAMAGOTCHI_CONTEXT_WINDOW_TOKENS` or config.yml) → 256000 |
250
+ | `llm_context_strategy`, `llm_context_apply`, `llm_context_budget_tokens` | each on its own: the session's own (`/llm-context`, `chi --llm-context…`) → `models:` → `hosts.<name>` → `llm_context.*` (`SAMAGOTCHI_LLM_CONTEXT_…` or config.yml) → the default |
251
+
252
+ Two rules, both intended:
253
+
254
+ - For thinking, reasoning_history, steer_cut_after and the profile, a process-wide override (a flag or an
255
+ environment variable) beats the model's own setting: it runs every model one way for a while.
256
+ - For the window and the LLM context, the global key is the fallback whatever sets it: an environment variable
257
+ or a flag there doesn't beat a model's or a host's setting. (`steer.cut_after` from config.yml, too, comes after
258
+ the host's.)
259
+
260
+ Where a value came from is named as `session`, `models: KEY`, `hosts.NAME`, or the config key or environment
261
+ variable that set it.
262
+
232
263
  ## Model Server API
233
264
 
234
265
  Chi talks to a model server over HTTP in one of two ways, set by the host's `api:`:
@@ -1774,8 +1805,8 @@ its own). The first target is a long session such as a day-long coordinator, whe
1774
1805
  budget don't ask: they only decide when a batch goes in, and each batch's break comes as designed (`next_request`
1775
1806
  with staged edits pending moves a break the old rule makes by the end of the next turn anyway). The chip's Set and
1776
1807
  `--llm-context` on an attached or resumed session send `--yes`.
1777
- - **Where to see it:** `/llm-context` (each value and where it came from: `the session`, `models: <key>`,
1778
- `hosts entry '<name>'`, `llm_context.*`), `/stats` (`llm context:`), the turn log (an `llm_context` record per
1808
+ - **Where to see it:** `/llm-context` (each value and where it came from: `session`, `models: <key>`,
1809
+ `hosts.<name>`, `llm_context.*`), `/stats` (`llm context:`), the turn log (an `llm_context` record per
1779
1810
  turn that runs a layer or whose session set its strategy) and the web's chip. A woken worker's status line counts
1780
1811
  its saved context against the session's budget.
1781
1812
 
@@ -2336,6 +2367,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2336
2367
  | `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token, asked of this machine's 127.0.0.1 too (its `Local:` link carries a one-time code; `chi web --new-token` replaces the token). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
2337
2368
  | `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
2338
2369
  | `web.working_set` | `true` | yes | The stage view's working set: the files, memories and URLs a turn brought into the model's context, in a rail in the stage, a footprint as each finished turn's block summary and a `set N` chip in place of the mem chip; `false` brings the mem chip back. `?working_set=1` or `?working_set=0` on the page URL overrides it for that page load. See [CLI: the working set](cli.md#the-working-set). |
2370
+ | `web.reload_check` | `false` | yes | Opt-in: once each finished turn has moved into the history, the page reads the session again (one full read per turn end), draws that turn as a reload would and compares the two, strictly: a known divergence counts as a mismatch too. `chi web` logs one `reload_checked` line per check and a `reload_mismatch` line naming the kinds that differed, `excused` (the known divergences, `history_shape.js` `ACCEPTED`, that would explain them) and `explained` (all of them had one), with `turn_id`, `drawn` (the turn came back from its drawing, so a mismatch with `drawn: true` is a drawing bug) and the worker's version: counts only, never content. The count is `grep -c reload_mismatch` over `grep -c reload_checked` in chi web's log, split by `excused`. Cards are left out: a reload places them after the render, which the check doesn't run (the e2e reload checks compare them). Every open owner tab runs the check; `chi web` logs a turn's first report only, and starts over when it restarts. |
2339
2371
  | `web.annotate_presets` | `Agreed\|Could you please elaborate?` | yes | Quick replies next to Annotate in `chi web`, `\|`-separated (a YAML list works too); `""` in the file or on the CLI leaves only Annotate (an empty env value means the default). See [CLI](cli.md#web-annotate-presets). |
2340
2372
  | `web.editor` | `vscode` | yes | What a file ref in `chi web` (a file tool row's title, a diff line) opens, for a viewer on this machine: a preset (`vscode`, `vscode-insiders`, `cursor`, `zed`) or a URL template with `{path}` and an optional `{line}`; `none` turns refs off. See [Opening files from chi web](#opening-files-from-chi-web). |
2341
2373
  | `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a repo on every session, and a new session from the all view starts there; the header's project picker switches projects. `~` and a relative path (resolved from your home folder) work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
data/docs/guardrails.md CHANGED
@@ -1392,7 +1392,8 @@ denies every call until the bundle is reinstalled.
1392
1392
  chi bundle install known-names # or chi bundle install dev
1393
1393
  ```
1394
1394
 
1395
- installs one `before_tool_call` hook and a short memory. A local model that
1395
+ installs a plugin with one `before_tool_call` hook (`chi.on`, priority 50; a
1396
+ `hooks/known_names.rb` file before known-names 0.2.0) and a short memory. A local model that
1396
1397
  once misspells a name inside a path (`jonathandoe` → `jonathndoe`)
1397
1398
  keeps copying the wrong spelling from its context, and every call after
1398
1399
  that fails. The hook knows the right names and compares strings: the
@@ -1449,10 +1450,13 @@ else goes to chi's own rules. It needs nah installed; see its README for the set
1449
1450
 
1450
1451
  ## Failing closed
1451
1452
 
1452
- - A config hook with `required: true`, or a bundle `before_tool_call` hook with
1453
- `on_error: fail_closed`, that fails to load (missing, syntax error, or for a
1454
- bundle hook a sha256 that differs from the installed one) makes chi deny
1455
- every tool call. One that raises when called denies that call.
1453
+ - A config hook with `required: true`, a bundle `before_tool_call` hook with
1454
+ `on_error: fail_closed`, or a bundle plugin its manifest marks `required: true`
1455
+ (`plugin:` `required: true`, for a `chi.on(:before_tool_call, on_error:
1456
+ :fail_closed)` guard, chi 0.157.0 or newer, [plugins.md](plugins.md#a-guard-that-fails-closed-on_error-fail_closed)),
1457
+ that fails to load (missing, syntax error, an unmet `requires_chi`, or for a
1458
+ bundle's file a sha256 that differs from the installed one) makes chi deny
1459
+ every tool call. A fail-closed hook that raises when called denies that call.
1456
1460
  - Other hooks stay fail-open; a load failure is a warning.
1457
1461
  - Every load failure is shown once, at the start of the first turn
1458
1462
  (`guardrails> …` in the terminal, a red line on the web).
data/docs/hooks.md CHANGED
@@ -145,8 +145,9 @@ they must never spend or permit. See
145
145
  [Guardrails](guardrails.md#answers-from-a-parent-agent). `effects:` needs a
146
146
  chi newer than 0.85.0 (an older one raises on the keyword).
147
147
 
148
- `event[:hook]` is the label the notices carry: `known_names.rb (bundle
149
- known-names)` for a bundle hook, `audit.rb (config)` for a config hook,
148
+ `event[:hook]` is the label the notices carry: `plugin.rb (bundle
149
+ known-names)` for a plugin's `chi.on` hook (`<file> (bundle <name>)` for a bundle's `hooks/*.rb`),
150
+ `audit.rb (config)` for a config hook,
150
151
  `turn hook` for one registered at runtime.
151
152
 
152
153
  `event[:notify]` also takes `fallback_for:`, what the line stands in for, so
@@ -465,6 +466,10 @@ change), as for every hook.
465
466
  (missing file, syntax error), chi denies every tool call and says why; if it
466
467
  raises as a `before_tool_call` hook, that call is denied. See
467
468
  [Guardrails](guardrails.md#failing-closed).
469
+ - A plugin's `chi.on(:before_tool_call, on_error: :fail_closed)` (chi 0.157.0 or newer) is
470
+ the same for a bundle: a raise denies that call, and a plugin its manifest
471
+ marks `required: true` that fails to load denies every call. See
472
+ [plugins.md](plugins.md#a-guard-that-fails-closed-on_error-fail_closed).
468
473
 
469
474
  A hook that fails to load is reported as a `[samagotchi:hooks]` warning and
470
475
  once in the UI.
@@ -477,7 +482,7 @@ worker, on the plugins' surface, whatever its `on_error` (the same notice a
477
482
  `chi.on` handler gets):
478
483
 
479
484
  ```
480
- plugins> guardrail guardrails.rb (bundle known-names) before_tool_call hook failed (RuntimeError: boom):
485
+ plugins> guardrail guardrails.rb (bundle my-guard) before_tool_call hook failed (RuntimeError: boom):
481
486
  it denies every tool call it guards, and its next failures are in each denial's reason
482
487
  ```
483
488
 
@@ -711,7 +716,7 @@ Notes:
711
716
  - Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order.
712
717
  - Settings: a hook class with `initialize(settings = {})` gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
713
718
  - A bundle can also ship a `plugin.rb` whose `chi.on(event)` blocks are bundle hooks too, next to commands and tools; see [Plugins](plugins.md). There `ctx.notify`, `ctx.steer` and the other `ctx` helpers act as this page's `event[:notify]`… do for the event the block runs for.
714
- - Shipped bundles (each but web-search in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Bundles](bundles.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Bundles](bundles.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Bundles](bundles.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Bundles](bundles.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Bundles](bundles.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Bundles](bundles.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Bundles](bundles.md#the-cost-cap-bundle)), `chi bundle install work-cap` (a plugin: a per-session cap on tool calls and active minutes, `/workcap`, see [Bundles](bundles.md#the-work-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Bundles](bundles.md#the-ruby-bundle)) `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Bundles](bundles.md#the-skills-bundle)), `chi bundle install github-pr` (attached context: the branch's open PR, see [Bundles](bundles.md#the-github-pr-bundle)), `chi bundle install coordinator` (a plugin: parallel work in worktrees and child sessions, see [Bundles](bundles.md#the-coordinator-bundle)), `chi bundle install claude-peer` / `codex-peer` (a plugin: talk to a Claude Code or Codex session, see [Bundles](bundles.md#the-claude-peer-bundle)), `chi bundle install model-notes` (memories: reviewed model notes, see [Bundles](bundles.md#the-model-notes-bundle)), `chi bundle install decision-card` (a plugin: a long answer's question as a card, see [Bundles](bundles.md#the-decision-card-bundle)), `chi bundle install inline-widgets` (a plugin: prompt sections that teach the `chi-ui` blocks chi web draws, see [Bundles](bundles.md#the-inline-widgets-bundle)) and `chi bundle install web-search` (a plugin: a `web_search` tool through your SearXNG, in no profile, see [Bundles](bundles.md#the-web-search-bundle)).
719
+ - Shipped bundles (each but web-search in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a plugin's `before_tool_call` hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a plugin's `after_turn` and `question_asked` hook: links source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Bundles](bundles.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Bundles](bundles.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Bundles](bundles.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Bundles](bundles.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Bundles](bundles.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Bundles](bundles.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Bundles](bundles.md#the-cost-cap-bundle)), `chi bundle install work-cap` (a plugin: a per-session cap on tool calls and active minutes, `/workcap`, see [Bundles](bundles.md#the-work-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Bundles](bundles.md#the-ruby-bundle)) `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Bundles](bundles.md#the-skills-bundle)), `chi bundle install github-pr` (attached context: the branch's open PR, see [Bundles](bundles.md#the-github-pr-bundle)), `chi bundle install coordinator` (a plugin: parallel work in worktrees and child sessions, see [Bundles](bundles.md#the-coordinator-bundle)), `chi bundle install claude-peer` / `codex-peer` (a plugin: talk to a Claude Code or Codex session, see [Bundles](bundles.md#the-claude-peer-bundle)), `chi bundle install model-notes` (memories: reviewed model notes, see [Bundles](bundles.md#the-model-notes-bundle)), `chi bundle install decision-card` (a plugin: a long answer's question as a card, see [Bundles](bundles.md#the-decision-card-bundle)), `chi bundle install inline-widgets` (a plugin: prompt sections that teach the `chi-ui` blocks chi web draws, see [Bundles](bundles.md#the-inline-widgets-bundle)) and `chi bundle install web-search` (a plugin: a `web_search` tool through your SearXNG, in no profile, see [Bundles](bundles.md#the-web-search-bundle)).
715
720
  - Installing a bundle executes its hook code at `Engine` startup. Only install bundles you trust, as you would a gem. Hooks are **not** executed at install time (copy-only); they are `module_eval`'d at `Engine.new` inside per-bundle `Samagotchi::Bundles::<name>` namespaces (no top-level `require` collisions). Keep hook files side-effect-free at load time; do work in `#call` — top-level side effects (require, IO, `at_exit`, global assignment) run once per `Engine.new` (class redefinition is idempotent).
716
721
 
717
722
  Lifecycle:
@@ -726,7 +731,8 @@ Lifecycle:
726
731
  chi bundle install source-links # or chi bundle install dev
727
732
  ```
728
733
 
729
- installs one `after_turn` hook and a short memory. When the model's answer
734
+ installs a plugin with one hook on `after_turn` and `question_asked` (`chi.on`, priority 90; a
735
+ `hooks/source_links.rb` file before source-links 0.7.0) and a short memory. When the model's answer
730
736
  mentions a known source ref — a JIRA ticket, a GitHub issue, an internal
731
737
  wiki page — the web links it in the answer (below), and every UI shows one
732
738
  line right after the message:
@@ -247,7 +247,7 @@ load nothing). `KernelLoop#dispatch` passes them on (`dispatched[:loaded]`), `To
247
247
  result's `loaded:` names among them as a tool_result holding only
248
248
  `{"type": "tool_reference", "tool_name": …}` blocks (Messages takes no other content beside a reference), followed by
249
249
  a sibling text block: `Loaded: a, b. Call them directly by these names.`, then the result's text without its
250
- `Call one with call_tool(…)` line (`ToolIndex::CALL_HINT`; the saved result keeps it). With that line and a trailing
250
+ `Call one with call_tool(…)` line (`Tools::IndexHint::CALL_HINT`; the saved result keeps it). With that line and a trailing
251
251
  "(call_tool works too)", Sonnet and Haiku 5.5 went through `call_tool` 2/2 live; without, 5/5 called the tool by
252
252
  name. A result whose names aren't deferred here (declared since, or no tool loading) goes as plain text. The sibling
253
253
  text is never marked; when breakpoint #2 falls on such a tool_result, it goes on its last reference.
@@ -145,9 +145,10 @@ lines (never twice at one owner-prompt count; one re-ask after `reask_after_turn
145
145
  nothing, so the next owner prompt asks again.
146
146
 
147
147
  The driver is the turn stage `TurnStages::ContextJudge` (`turn_stages/context_judge.rb`), not the judge:
148
- `Engine#prepare_turn` calls its `start(turn)` after the toolkit suggestion settled and before `apply_toolkit!` (an owner
148
+ `Engine#prepare_turn` calls its `start(turn, recorder:)` after the toolkit suggestion settled and before `apply_toolkit!` (an owner
149
149
  turn's start; skipped for participants, review sessions and a Continue), and `Engine#judge_late_candidates` (a proposal
150
- after the prompts) forwards to its `late_candidates`; both make one pass (docs/internals/engine.md, "Turn stages").
150
+ after the prompts) gives its `late_candidates` the session, the owner's messages, the recorder and the sessions dir
151
+ (none of it read with no session or in a review session); both make one pass (docs/internals/engine.md, "Turn stages").
151
152
  It first logs status changes (`ContextCandidatesLog.look`), returns at `mode: off`, builds the due `Job`s, and hands
152
153
  them to `SideJudgments` (`:context` kind: one thread, one batch per session at a time), asked at once on any host,
153
154
  the model's own server too.
@@ -44,7 +44,7 @@ Behavior:
44
44
  - `execute` and `task_create` both spawn with chi's own Ruby/Bundler setup scrubbed (`TaskRuntime.sanitized_spawn_env`): `RUBYOPT` loses its `-r…bundler/setup` flags (others stay) and `RUBYLIB` and what `bundle exec` sets (`BundlerEnv::VARS`: `BUNDLE_GEMFILE`, `BUNDLE_LOCKFILE`, `BUNDLE_BIN_PATH`, `BUNDLER_VERSION`, `BUNDLER_SETUP`) are unset, so a project's `ruby -e` or `bundle exec` uses its own Gemfile, or none, when chi runs under `bundle exec`. RubyGems requires what `BUNDLER_SETUP` names at boot, whatever `RUBYOPT` says. A project's own settings (`BUNDLE_PATH`, `BUNDLE_WITHOUT`) stay. `task_create` refuses those keys in the model's `env`, its error saying to write `KEY=value cmd` in the command instead; `execute`'s `env` comes from chi's callers (`Builtins`, `!cmd`), not the model, and is set over the scrub. An `execute` handed over at its limit keeps the process it spawned, so the task runs with the same env.
45
45
  - `execute` and `task_create` export chi's own markers into the command, set over the model's `env` (which can't set them: `Error: env key is reserved`): `SAMAGOTCHI_PARENT_SESSION` (the session's id, `chi` without one; a chi started there answers as a parent agent), `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown) `SAMAGOTCHI_SESSION_THINKING` (the session's own thinking level; unset when it has none) and `SAMAGOTCHI_SESSION_REASONING_HISTORY` (its own reasoning_history level, the same way). `chi self` reads the last three for its model, thinking level and reasoning history. The four are `ChildEnv::SESSION_MARKERS` (`TaskRuntime::MARKER_ENV_KEYS`), which a spawned worker unsets (`ChildEnv.unset`, merged into its spawn env). A session's worker never inherits them from whoever started it (a `chi send` run by another session's `execute`): its other commands (a user's `!cmd`, an MCP server, a context command) have none.
46
46
  - They also pass on a set `XDG_STATE_HOME` / `XDG_CONFIG_HOME` made absolute (`Builtins.xdg_absolute`, via `Paths`), so a chi the command runs in another `cwd` (`chi send`) reads this chi's state and config. These aren't reserved: an XDG dir the model sets in `task_create`'s `env` stays its own.
47
- - Every runner starts from the session's project env, `ProjectEnv.tools_env`: the overlay a spawned worker computed once at boot (`SessionManager.apply_project_env` → `ProjectEnv.compute` → `ProjectEnv.install!`; its folder's `mise env`, and the chi shim's dir first on PATH so `chi` is the chi home's), less `ProjectEnv::TOOLS_DROP` (`RUBYOPT`, `RUBYLIB`, Bundler's: the scrub above sets those). Outside a worker (`ProjectEnv::NONE`) it is `{}`. The sites: `execute` and `task_create` (`Tools::Builtins#project_env`, under the markers and the model's `env`; a participant's turn gets `{}`), the user's `!cmd` (`SessionCommands#shell`), and through `ProjectEnv.spawn_env` (the scrub above, then `tools_env`: what `execute` spawns with, for the runners that don't go through it) a context source's command (`ContextFetch#env_for`) and an MCP server, under the server's own keys (`bundles/mcp/plugin.rb` `server_env`, from mcp 0.7.4; no participant rule: the server is the session's). chi's own process env is never changed, and the project env never enters the prompt. User docs: [Project env](../sessions.md#project-env); the launch side: [How chi launches itself](../architecture.md#how-chi-launches-itself).
47
+ - Every runner starts from the session's project env, `ProjectEnv.tools_env`: the overlay a spawned worker computed once at boot (`WorkerBoot.apply_project_env` → `ProjectEnv.compute` → `ProjectEnv.install!`; its folder's `mise env`, and the chi shim's dir first on PATH so `chi` is the chi home's), less `ProjectEnv::TOOLS_DROP` (`RUBYOPT`, `RUBYLIB`, Bundler's: the scrub above sets those). Outside a worker (`ProjectEnv::NONE`) it is `{}`. The sites: `execute` and `task_create` (`Tools::Builtins#project_env`, under the markers and the model's `env`; a participant's turn gets `{}`), the user's `!cmd` (`SessionCommands#shell`), and through `ProjectEnv.spawn_env` (the scrub above, then `tools_env`: what `execute` spawns with, for the runners that don't go through it) a context source's command (`ContextFetch#env_for`) and an MCP server, under the server's own keys (`bundles/mcp/plugin.rb` `server_env`, from mcp 0.7.4; no participant rule: the server is the session's). chi's own process env is never changed, and the project env never enters the prompt. User docs: [Project env](../sessions.md#project-env); the launch side: [How chi launches itself](../architecture.md#how-chi-launches-itself).
48
48
 
49
49
  ## Finish wake
50
50