samagotchi 0.152.0 → 0.154.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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/README.md +2 -3
  4. data/bin/chi +23 -24
  5. data/docs/architecture.md +30 -33
  6. data/docs/broadcast.md +5 -5
  7. data/docs/bundle-authoring.md +1 -1
  8. data/docs/bundles.md +93 -12
  9. data/docs/cli.md +83 -99
  10. data/docs/configuration.md +46 -40
  11. data/docs/context.md +3 -3
  12. data/docs/desktop.md +1 -1
  13. data/docs/guardrails.md +15 -15
  14. data/docs/hooks.md +13 -12
  15. data/docs/internals/anthropic.md +3 -3
  16. data/docs/internals/attached-context.md +5 -2
  17. data/docs/internals/background-tasks.md +3 -4
  18. data/docs/internals/block-renderers.md +4 -0
  19. data/docs/internals/bridge.md +3 -3
  20. data/docs/internals/context-telemetry.md +1 -1
  21. data/docs/internals/engine.md +42 -12
  22. data/docs/internals/objectives.md +6 -5
  23. data/docs/internals/participants.md +8 -8
  24. data/docs/internals/prompt-caching.md +8 -4
  25. data/docs/internals/remote-sessions.md +7 -7
  26. data/docs/internals/web-frontend.md +20 -17
  27. data/docs/internals/web-push.md +1 -1
  28. data/docs/memory.md +2 -2
  29. data/docs/plugins.md +52 -43
  30. data/docs/releasing.md +1 -1
  31. data/docs/sessions.md +25 -25
  32. data/docs/sub-agent.md +2 -2
  33. data/docs/system-one.md +1 -1
  34. data/docs/testing.md +18 -8
  35. data/lib/samagotchi/answer_text.rb +34 -0
  36. data/lib/samagotchi/bridge.rb +43 -32
  37. data/lib/samagotchi/bridge_client.rb +10 -9
  38. data/lib/samagotchi/broadcast/recipients.rb +2 -2
  39. data/lib/samagotchi/broadcast/triage.rb +1 -1
  40. data/lib/samagotchi/broadcast_command.rb +5 -5
  41. data/lib/samagotchi/bundle_command/try/command.rb +1 -1
  42. data/lib/samagotchi/bundle_command/try/run.rb +1 -1
  43. data/lib/samagotchi/bundle_command.rb +2 -2
  44. data/lib/samagotchi/bundles/decision-card/manifest.yml +4 -4
  45. data/lib/samagotchi/bundles/decision-card/plugin.rb +1 -9
  46. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  47. data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -4
  48. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  49. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  50. data/lib/samagotchi/bundles/web-search/manifest.yml +13 -0
  51. data/lib/samagotchi/bundles/web-search/plugin.rb +174 -0
  52. data/lib/samagotchi/child_move.rb +3 -3
  53. data/lib/samagotchi/commands/registry.rb +6 -6
  54. data/lib/samagotchi/composer_route.rb +4 -5
  55. data/lib/samagotchi/config.rb +14 -12
  56. data/lib/samagotchi/context_candidate_choice.rb +1 -1
  57. data/lib/samagotchi/context_candidates.rb +4 -3
  58. data/lib/samagotchi/continue_offer.rb +1 -1
  59. data/lib/samagotchi/debug_log.rb +1 -1
  60. data/lib/samagotchi/degrade.rb +75 -0
  61. data/lib/samagotchi/desktop/macos/ChiRunner.swift +2 -2
  62. data/lib/samagotchi/engine/cards.rb +87 -0
  63. data/lib/samagotchi/engine/context_judge.rb +138 -0
  64. data/lib/samagotchi/engine/events.rb +131 -0
  65. data/lib/samagotchi/engine/guardrails.rb +87 -0
  66. data/lib/samagotchi/engine/hooks.rb +119 -0
  67. data/lib/samagotchi/engine/messages.rb +124 -0
  68. data/lib/samagotchi/engine/model_settings.rb +127 -0
  69. data/lib/samagotchi/engine/models.rb +261 -0
  70. data/lib/samagotchi/engine/objectives.rb +116 -0
  71. data/lib/samagotchi/engine/participants.rb +86 -0
  72. data/lib/samagotchi/engine/plugins.rb +208 -0
  73. data/lib/samagotchi/engine/questions.rb +98 -0
  74. data/lib/samagotchi/engine/recap.rb +130 -0
  75. data/lib/samagotchi/engine/session.rb +131 -0
  76. data/lib/samagotchi/engine/side_requests.rb +135 -0
  77. data/lib/samagotchi/engine/steering.rb +134 -0
  78. data/lib/samagotchi/engine/toolkit.rb +269 -0
  79. data/lib/samagotchi/engine/turn_end.rb +180 -0
  80. data/lib/samagotchi/engine/turn_helpers.rb +160 -0
  81. data/lib/samagotchi/engine/verify.rb +153 -0
  82. data/lib/samagotchi/engine.rb +63 -2642
  83. data/lib/samagotchi/grants.rb +575 -0
  84. data/lib/samagotchi/guardrail_wiring.rb +2 -2
  85. data/lib/samagotchi/guardrails/context.rb +2 -3
  86. data/lib/samagotchi/guardrails/parent_approvals.rb +2 -2
  87. data/lib/samagotchi/guardrails/parent_policy.rb +2 -2
  88. data/lib/samagotchi/guardrails/review_boundary.rb +4 -3
  89. data/lib/samagotchi/hook_routes.rb +3 -3
  90. data/lib/samagotchi/idle_recap.rb +4 -5
  91. data/lib/samagotchi/idle_reminders.rb +3 -4
  92. data/lib/samagotchi/iteration_limit.rb +2 -2
  93. data/lib/samagotchi/kernel_loop.rb +10 -11
  94. data/lib/samagotchi/launch_mode.rb +13 -32
  95. data/lib/samagotchi/level_setting_command.rb +3 -2
  96. data/lib/samagotchi/llm_context_command.rb +3 -2
  97. data/lib/samagotchi/llm_context_stale.rb +2 -2
  98. data/lib/samagotchi/log.rb +7 -7
  99. data/lib/samagotchi/log_line.rb +1 -1
  100. data/lib/samagotchi/log_path.rb +1 -1
  101. data/lib/samagotchi/message_parts.rb +3 -2
  102. data/lib/samagotchi/note_command.rb +1 -1
  103. data/lib/samagotchi/note_delivery.rb +18 -8
  104. data/lib/samagotchi/objectives.rb +24 -3
  105. data/lib/samagotchi/objectives_check.rb +8 -27
  106. data/lib/samagotchi/objectives_command.rb +4 -69
  107. data/lib/samagotchi/objectives_edit.rb +87 -0
  108. data/lib/samagotchi/output_formatter.rb +7 -1
  109. data/lib/samagotchi/owner_lock.rb +7 -1
  110. data/lib/samagotchi/parent_answers_command.rb +5 -4
  111. data/lib/samagotchi/participant_exposure.rb +42 -0
  112. data/lib/samagotchi/participant_mode.rb +1 -1
  113. data/lib/samagotchi/participant_name.rb +1 -1
  114. data/lib/samagotchi/participants.rb +17 -17
  115. data/lib/samagotchi/participants_watch.rb +1 -1
  116. data/lib/samagotchi/pending_input_queue.rb +2 -2
  117. data/lib/samagotchi/plugin/api.rb +3 -2
  118. data/lib/samagotchi/plugin/context.rb +21 -7
  119. data/lib/samagotchi/plugin/peer.rb +2 -1
  120. data/lib/samagotchi/plugin/sessions.rb +6 -4
  121. data/lib/samagotchi/plugin_tasks.rb +1 -2
  122. data/lib/samagotchi/private_files.rb +29 -0
  123. data/lib/samagotchi/project_env.rb +2 -2
  124. data/lib/samagotchi/question_desk.rb +8 -7
  125. data/lib/samagotchi/recap_command.rb +2 -1
  126. data/lib/samagotchi/recap_setup.rb +1 -1
  127. data/lib/samagotchi/recap_store.rb +1 -1
  128. data/lib/samagotchi/redact.rb +86 -0
  129. data/lib/samagotchi/reminder_queue.rb +2 -2
  130. data/lib/samagotchi/remote_attach_command.rb +1 -1
  131. data/lib/samagotchi/reply_wait/poll.rb +2 -2
  132. data/lib/samagotchi/review_start.rb +1 -1
  133. data/lib/samagotchi/run_options.rb +6 -6
  134. data/lib/samagotchi/self_report.rb +42 -39
  135. data/lib/samagotchi/send_command/busy.rb +1 -1
  136. data/lib/samagotchi/send_command.rb +4 -1
  137. data/lib/samagotchi/session.rb +11 -2
  138. data/lib/samagotchi/session_access/contract.rb +3 -3
  139. data/lib/samagotchi/session_access/local.rb +9 -9
  140. data/lib/samagotchi/session_access/remote.rb +17 -7
  141. data/lib/samagotchi/session_access.rb +4 -4
  142. data/lib/samagotchi/session_archive_command.rb +2 -2
  143. data/lib/samagotchi/session_chain.rb +8 -6
  144. data/lib/samagotchi/session_commands.rb +45 -48
  145. data/lib/samagotchi/session_continue.rb +1 -1
  146. data/lib/samagotchi/session_delete_command.rb +4 -4
  147. data/lib/samagotchi/session_manager.rb +59 -36
  148. data/lib/samagotchi/session_metrics.rb +2 -2
  149. data/lib/samagotchi/session_model_settings.rb +3 -2
  150. data/lib/samagotchi/session_participants.rb +1 -1
  151. data/lib/samagotchi/session_retention.rb +3 -3
  152. data/lib/samagotchi/sessions_command.rb +4 -4
  153. data/lib/samagotchi/share_command.rb +10 -10
  154. data/lib/samagotchi/steer.rb +1 -1
  155. data/lib/samagotchi/system_prompt.rb +102 -109
  156. data/lib/samagotchi/task_watch.rb +1 -1
  157. data/lib/samagotchi/terminal_ui/attach_launcher.rb +72 -10
  158. data/lib/samagotchi/terminal_ui/attached_loop/composer.rb +12 -9
  159. data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +16 -0
  160. data/lib/samagotchi/terminal_ui/attached_loop/questions.rb +2 -3
  161. data/lib/samagotchi/terminal_ui/attached_loop.rb +17 -8
  162. data/lib/samagotchi/terminal_ui/attached_view.rb +2 -3
  163. data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -5
  164. data/lib/samagotchi/terminal_ui/formatting.rb +7 -7
  165. data/lib/samagotchi/terminal_ui/input_support.rb +1 -7
  166. data/lib/samagotchi/terminal_ui/line_reader.rb +2 -2
  167. data/lib/samagotchi/terminal_ui/live_region.rb +2 -2
  168. data/lib/samagotchi/terminal_ui/log_tail.rb +135 -0
  169. data/lib/samagotchi/terminal_ui/one_shot_sink.rb +2 -2
  170. data/lib/samagotchi/terminal_ui/plain_surface.rb +1 -1
  171. data/lib/samagotchi/terminal_ui/question_prompt.rb +4 -5
  172. data/lib/samagotchi/terminal_ui/reline_seam.rb +3 -3
  173. data/lib/samagotchi/terminal_ui/status_row.rb +7 -8
  174. data/lib/samagotchi/terminal_ui.rb +142 -1387
  175. data/lib/samagotchi/title_command.rb +2 -2
  176. data/lib/samagotchi/tool_runner.rb +14 -16
  177. data/lib/samagotchi/toolkit.rb +8 -5
  178. data/lib/samagotchi/toolkit_binding.rb +30 -5
  179. data/lib/samagotchi/toolkit_command.rb +2 -1
  180. data/lib/samagotchi/toolkit_report_command.rb +9 -5
  181. data/lib/samagotchi/toolkit_suggest.rb +12 -6
  182. data/lib/samagotchi/tools/builtin_calls.rb +14 -10
  183. data/lib/samagotchi/tools/deferred_tools.rb +2 -2
  184. data/lib/samagotchi/tools/delegate.rb +3 -1
  185. data/lib/samagotchi/tools/delegate_cursor.rb +3 -3
  186. data/lib/samagotchi/tools/delegate_wait.rb +1 -1
  187. data/lib/samagotchi/tools/objectives_tool.rb +2 -2
  188. data/lib/samagotchi/tools/registry.rb +44 -20
  189. data/lib/samagotchi/tools/send_note.rb +2 -1
  190. data/lib/samagotchi/tools/tool_index.rb +27 -21
  191. data/lib/samagotchi/turn_endings.rb +2 -2
  192. data/lib/samagotchi/turn_flow.rb +4 -5
  193. data/lib/samagotchi/update_hint.rb +2 -2
  194. data/lib/samagotchi/version.rb +1 -1
  195. data/lib/samagotchi/web/app.rb +39 -50
  196. data/lib/samagotchi/web/participant_access.rb +2 -2
  197. data/lib/samagotchi/web/participant_review.rb +5 -5
  198. data/lib/samagotchi/web/participant_stream_filter.rb +8 -8
  199. data/lib/samagotchi/web/participant_view.rb +2 -82
  200. data/lib/samagotchi/web/participants_admin.rb +1 -1
  201. data/lib/samagotchi/web/public/app.js +61 -357
  202. data/lib/samagotchi/web/public/block_host.js +35 -8
  203. data/lib/samagotchi/web/public/block_renderers.css +9 -5
  204. data/lib/samagotchi/web/public/chains.js +2 -2
  205. data/lib/samagotchi/web/public/claim_link.js +4 -4
  206. data/lib/samagotchi/web/public/composer_mode.js +110 -0
  207. data/lib/samagotchi/web/public/format.js +3 -3
  208. data/lib/samagotchi/web/public/owner_claim.js +81 -0
  209. data/lib/samagotchi/web/public/owner_verify.js +112 -0
  210. data/lib/samagotchi/web/public/session_fields.js +3 -3
  211. data/lib/samagotchi/web/public/strip_grip.js +3 -3
  212. data/lib/samagotchi/web/public/strip_hidden.js +65 -0
  213. data/lib/samagotchi/web/public/version_notices.js +132 -0
  214. data/lib/samagotchi/web/push/store.rb +1 -1
  215. data/lib/samagotchi/web/session_access_routes.rb +5 -4
  216. data/lib/samagotchi/web/session_control.rb +12 -8
  217. data/lib/samagotchi/web/session_create.rb +1 -1
  218. data/lib/samagotchi/web/session_hub.rb +2 -2
  219. data/lib/samagotchi/web/session_show.rb +2 -2
  220. data/lib/samagotchi/web/session_summary.rb +1 -1
  221. data/lib/samagotchi/worker.rb +106 -9
  222. data/lib/samagotchi/worker_link.rb +3 -3
  223. data/lib/samagotchi/working_set_command.rb +3 -2
  224. metadata +36 -6
  225. data/lib/samagotchi/terminal_ui/between_turns.rb +0 -138
  226. data/lib/samagotchi/terminal_ui/repl_input.rb +0 -152
  227. data/lib/samagotchi/web/grants.rb +0 -575
  228. data/lib/samagotchi/web/participant_exposure.rb +0 -44
  229. data/lib/samagotchi/web/private_files.rb +0 -31
@@ -64,11 +64,9 @@ model_aliases:
64
64
  small: your-small-model-id
65
65
  tiny: small-box:your-small-model-id # alias may be bare or host:model (hybrid)
66
66
 
67
- # Plain chi runs its session in a background worker and attaches to it, so the
68
- # web UI can share it (default true; env SAMAGOTCHI_SESSION_SHARED). false keeps
69
- # the in-process REPL; --no-shared does for one run.
67
+ # A session runs in a background worker; chi attaches to it, and the web UI
68
+ # shares it.
70
69
  session:
71
- shared: true
72
70
  # idle_exit_minutes: 30 # an unused worker exits after this (0 = never)
73
71
  # project_env: mise # a worker's tools get its folder's `mise env` (off: the starter's env)
74
72
  # keep_empty: false # true keeps sessions nothing happened in (default: deleted when left)
@@ -172,7 +170,6 @@ or `chi web` restart.
172
170
  | `system_one.host`, `system_one.model`, `system_one.timeout_ms`, `system_one.runs`, `system_one.judgments` | Next tool call or approval | Each ask reads them. |
173
171
  | `hooks`, `bundles` | Worker start | Hooks, plugins and their bundle settings load with the engine. |
174
172
  | `recap.*` | Worker start | The engine builds its recap job at start, and again when it binds a session with its own `recap.enabled` or `recap.sentences` (`--set`, see "Per-session settings") and when `/recap on\|off\|default` changes the session's own value. |
175
- | `session.shared` | Next `chi` invocation | It selects attached-worker or in-process mode for that invocation. |
176
173
  | `session.idle_exit_minutes`, `session.project_env`, `session.carry_last_answer`, `session.keep_empty`, `session.max_children`, `session.delegate_reports`, `session.task_wakes`, `session.max_wakes` | Worker start or next matching lifecycle decision | Existing workers keep their idle-exit timer and project env; the other values are read for the next corresponding decision. |
177
174
  | `session.retention_*`, `session.max_count`, `session.keep_status`, `session.sweep_interval_hours` | Next retention sweep | `chi web` sweeps on its configured schedule and on `GET /api/sessions`; `chi sessions prune` uses the values for that invocation. The sweep interval is checked on the next hub tick. |
178
175
  | `web.*` except `web.participants.*` | `chi web` start | Restart `chi web` to change its bind address, port or page settings. |
@@ -355,10 +352,8 @@ For models on that host, chi uses the chat loop (its own OpenAI chat adapter): i
355
352
  takes the OpenAI base (`url:`, else `http://HOST:PORT/v1`) and streams messages plus
356
353
  function schemas from `/v1/chat/completions`; the model's reasoning (`reasoning_content`)
357
354
  shows as thinking. This works with Splash and with llama.cpp servers that
358
- expose the OpenAI-compatible chat endpoint. In verbose mode (`-v`), chi prints the loop the
359
- starting model uses (`[verbose] backend=chat` or `backend=native`); request
360
- bodies are not logged. The interactive REPL, `--prompt`,
361
- workers, and resumed sessions all use the loop of the model's host. Hosts without
355
+ expose the OpenAI-compatible chat endpoint. Workers, `--non-interactive` one-shots and resumed
356
+ sessions all use the loop of the model's host. Hosts without
362
357
  a chat `api:` keep using llama.cpp's native `/completion`.
363
358
 
364
359
  ### API keys from the Keychain
@@ -655,7 +650,7 @@ models:
655
650
  it, a delegate child starts without it (it runs at its model's level). Set it with:
656
651
  - `/thinking LEVEL` in the session (`/thinking default` unsets it, `/thinking` alone shows the level and where it
657
652
  came from);
658
- - `chi --thinking LEVEL` on a run that starts or resumes a session (the REPL, `-p`, `--resume`, an attached one);
653
+ - `chi --thinking LEVEL` on a run that starts or resumes a session (plain `chi`, `-p`, `--resume`, an attached one);
659
654
  - `chi send --new --thinking LEVEL`;
660
655
  - the web's ⚙ panel in the session bar (its Thinking row), and the start page's ⚙ panel (`POST /api/sessions` takes `"thinking": "low"`;
661
656
  anything that isn't a level answers `400 invalid_thinking`).
@@ -849,15 +844,15 @@ first: an unknown host or a bad alias is an error at once, not a question.
849
844
  a full-prefix change also says preserved thinking in replies is dropped from then on.
850
845
  - `Nothing changed yet: send /thinking low --yes to apply.` names the line that applies it.
851
846
  - `--yes` (or `-y`, anywhere after the command) applies it at once.
852
- - The terminal (the REPL and an attached TUI) puts the `--yes` line in the next input: Enter applies it.
847
+ - The attached terminal puts the `--yes` line in the next input: Enter applies it.
853
848
  - The web's reply bubble has an **Apply (restarts the cache)** button. The session bar's ⚙ panel is already an
854
849
  explicit choice: a tap sends the `--yes` line, and its note says what a change costs. Command bubbles are page-only, so
855
850
  the button is gone after a reload.
856
851
  - Only a person at a web tab or a chi TUI is asked. `chi send ID "/thinking low"`,
857
- `chi -p "/thinking low" --non-interactive`, lines piped into the REPL, a delegate, a plugin and a card's action
852
+ `chi -p "/thinking low" --non-interactive`, a delegate, a plugin and a card's action
858
853
  apply directly, since nobody would see the question; so do the start flags (`chi --thinking`, `--model` and
859
854
  `--llm-context` on a resumed or attached session send `/thinking LEVEL --yes`, `/model NAME --yes` and
860
- `/llm-context … --yes`). A plain `chi -p` opens the REPL, which asks.
855
+ `/llm-context … --yes`).
861
856
 
862
857
  ## Llama HTTP Timeouts
863
858
 
@@ -1107,7 +1102,7 @@ retry:
1107
1102
  `retry.empty_answer` is a different thing: the request worked, but the model's answer had no visible text and no
1108
1103
  tool calls (thinking only, or nothing; a thinking loop cut by the provider's output cap looks like this). chi then
1109
1104
  asks again in the same turn with a hidden note ("your last reply had no visible answer…"), at `temperature: 0.6`
1110
- unless `sampling:` sets one; the REPL prints `↻ empty answer, asking again (1/1)` and the web shows it as a row of
1105
+ unless `sampling:` sets one; the terminal prints `↻ empty answer, asking again (1/1)` and the web shows it as a row of
1111
1106
  the step. `retry.empty_answer` (default `1`, at most `3`, env `SAMAGOTCHI_RETRY_EMPTY_ANSWER`, no CLI flag) is how
1112
1107
  many times per turn; `0` ends the turn at the empty answer as before. An answer cut because the context is full
1113
1108
  (90 % or more) is not retried. When the retries run out the turn ends with no answer: every UI shows one muted line,
@@ -1209,8 +1204,8 @@ The turn's prompt and its completed tool calls stay in the session.
1209
1204
 
1210
1205
  ## Debug Log File
1211
1206
 
1212
- Every `chi` process (the REPL, the attached terminal, the background
1213
- workers, `chi web`) appends tagged records to one log file, so you can see
1207
+ Every `chi` process (the attached terminal, a `--non-interactive` one-shot,
1208
+ the background workers, `chi web`) appends tagged records to one log file, so you can see
1214
1209
  what happened in a session, what went over the wire and why something
1215
1210
  failed, without `--verbose`.
1216
1211
 
@@ -1248,9 +1243,10 @@ One record is one line, plus indented payload lines at debug level:
1248
1243
  space, quote or `=` is a JSON string, so a record never spans lines;
1249
1244
  control characters (terminal colours in tool output) are escaped.
1250
1245
  - Tags: `turn` (a session's event trail), `http` (model requests),
1251
- `worker`, `bridge`, `web`, `attached`, `repl`, `idle`, `recap`, `hooks`,
1246
+ `worker`, `bridge`, `web`, `attached`, `idle`, `recap`, `hooks`,
1252
1247
  `plugins`, `guardrails`, `config`, `memory`, `model` (debug dumps), `context` (attached context fetches),
1253
- `broadcast` (chi broadcast's triage).
1248
+ `broadcast` (chi broadcast's triage), `toolkit`, `system_one`, `prompt` (a system prompt section left out
1249
+ after an error).
1254
1250
  - The format is parsed by `Samagotchi::LogLine` (`parse`, `each_record`);
1255
1251
  keep tools that read it on that parser.
1256
1252
 
@@ -1259,7 +1255,9 @@ What each level adds:
1259
1255
  - `error`: crashes (a worker, a bridge connection, the idle scheduler, a
1260
1256
  recap) with the first 20 backtrace frames; failed model requests.
1261
1257
  - `warn`: retries (429, 5xx, network), failed turns, hook and guardrail
1262
- problems, config warnings. Warnings `chi` prints on stderr are logged too,
1258
+ problems, config warnings, and a fallback after an error (a prompt section or a
1259
+ Bridge reply chi left out, `Degrade`): once per process for each kind, later ones
1260
+ at `debug`. Warnings `chi` prints on stderr are logged too,
1263
1261
  with the same text as `msg=`; a worker's (its stderr goes nowhere) now
1264
1262
  only reach the file.
1265
1263
  - `info`: turns, generations and tool calls with sizes and times (never the
@@ -1270,8 +1268,12 @@ What each level adds:
1270
1268
  and results, context status), probes and model lists, every web API and
1271
1269
  bridge request.
1272
1270
 
1273
- `-v`/`--verbose` (the plain REPL only) logs at `debug` and prints every
1274
- record to stderr as well.
1271
+ `-v`/`--verbose` logs at `debug`, in this chi and in a worker it starts
1272
+ (a worker that was already running keeps its own level, and a one-line note
1273
+ says so: one `chi web` started may log to another file). The attached
1274
+ terminal shows the session's records (by `sid`) dim, above the prompt, as
1275
+ they come; a `--non-interactive` one-shot prints every record to stderr
1276
+ as well.
1275
1277
 
1276
1278
  ### Rotation and secrets
1277
1279
 
@@ -1476,8 +1478,8 @@ work tree top when inside it, else with `~`.
1476
1478
  every request`). The files are saved in the session (`appended_prompt_files`),
1477
1479
  so a respawned worker builds the same prompt, and a fork or a continue keeps
1478
1480
  them; a delegate child doesn't. With `--attach` or `--resume` the flag is
1479
- ignored with one line, as `--memory` is; a REPL `--resume` (`--no-shared`)
1480
- adds them for that run only.
1481
+ ignored with one line, as `--memory` is; a `--resume ID --non-interactive`
1482
+ one-shot adds them for that run only.
1481
1483
  - The files' text is read at each prompt build, as AGENTS.md's is: an edit is
1482
1484
  picked up by a new process or a rebuild (`/model`). `/stats` lists the files
1483
1485
  in the prompt on its `prompt files:` line.
@@ -1501,17 +1503,18 @@ toolkit:
1501
1503
 
1502
1504
  | toolkit | declares |
1503
1505
  |---|---|
1504
- | `full` | every built-in; the default |
1506
+ | `full` | every built-in and every plugin tool (a bundle's `chi.tool`, such as `web_search`); the default |
1505
1507
  | `lean` | `execute`, `read`, `write`, `edit`, `memory_read`, `memory_write`, `task_create`, `task_wait`, `task_get`, `task_list`, `task_stop`, `ask_user_question`, `web_fetch`, `context_read` |
1506
1508
  | `child` | `lean` plus `send_note`, without `memory_write`, `delegate`, `delegate_result` |
1507
1509
  | `coordinator` | `lean` plus `delegate`, `delegate_result`, `list_sessions`, `objectives` (declared by the coordinator bundle) |
1508
1510
  | `coding` | `child`, with the prompt text `- Be concise in your responses` / `- Show file paths clearly when working with files` (declared by the coordinator bundle, for build and fix children) |
1509
1511
  | `review` | `read`, `memory_read` (not `forget_outputs`, even under the `forget` layer): what a [participant's](internals/participants.md#turns) turn may call, whatever the session declares (the review boundary, [guardrails](guardrails.md#a-participants-review-boundary)). Reserved: `toolkit.kits.review` is refused ("review is reserved for participants' turns"), and a bundle can't declare it. A session may run on it too, as any kit |
1510
1512
 
1511
- - **Plugin tools** are declared in every toolkit unless its `remove:` names them, and so is `forget_outputs` under
1512
- the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:`, `prompt:`, `groups:` and
1513
- `description:`
1514
- (any other key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
1513
+ - **Plugin tools** (a bundle's `chi.tool`, such as web-search's `web_search`) follow a kit's list as built-ins do:
1514
+ `full` (and every kit on `base: full`, the `kits:` default) declares them, another kit leaves them **reachable**
1515
+ (below) unless its `remove:` names them, and `add:` declares one (`search: { base: lean, add: [web_search] }`).
1516
+ `forget_outputs` is declared in every toolkit under the `forget` layer, unless removed. A `kits:` entry takes
1517
+ `base:` (default `full`), `add:`, `remove:`, `prompt:`, `groups:` and `description:` (any other key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
1515
1518
  (`review`'s least of all: it is the participants' boundary). A bundle declares its own with `chi.toolkit`
1516
1519
  ([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil-groups-nil-description-nil)).
1517
1520
  `description:` is optional classifier input, up to 300 characters; it is not included in the session prompt.
@@ -1565,7 +1568,7 @@ toolkit:
1565
1568
  reaches none. A kit whose base doesn't resolve (an unknown name, a cycle) falls back to `full`, which reaches
1566
1569
  every group.
1567
1570
  A selector that matches no group in that load-time snapshot warns once. `find_tools` and `call_tool` are
1568
- declared only when the toolkit reaches a source group or has a reachable built-in. Calls naming an unreached
1571
+ declared only when the toolkit reaches a source group or has a reachable tool (a built-in or a plugin tool). Calls naming an unreached
1569
1572
  group are refused before guardrails and do not start its server. The first-run init wait is unchanged: it can
1570
1573
  still block the first turn even when its server's group is unreached; `start: eager` and stale-cache refreshes
1571
1574
  also still start. `/mcp` marks servers outside the current toolkit.
@@ -1599,17 +1602,21 @@ toolkit:
1599
1602
  them with `/toolkit`. The configured default is always listed. A pick closes the list and shows a `tools: lean`
1600
1603
  chip on the row (a dot on 🧰 on a phone); it is for that create only, never remembered. A review session is
1601
1604
  always `chosen`: with review files set the list says it starts on the default unless you pick a kit.
1602
- - **Left out: reachable or removed.** A built-in missing from a kit's base list (`lean` has no `delegate`; a
1603
- `kits:` entry on `base: lean` or `child` inherits that) is left out softly: it is **reachable**. `find_tools` searches it as group `chi` (`chi/delegate`, with its inputSchema) beside
1604
- the MCP servers' tools, and `call_tool(tool: "chi/delegate", args: {…})` runs it as `delegate` itself, through the
1605
- usual guardrails, approvals and hooks ([plugins](plugins.md#chitool_sourcecall-acts_as-label-wait-prefix)). A
1605
+ - **Left out: reachable or removed.** A built-in or plugin tool missing from a kit's base list (`lean` has no
1606
+ `delegate` and no `web_search`; a `kits:` entry on `base: lean` or `child` inherits that) is left out softly: it
1607
+ is **reachable**. `find_tools` searches it as group `chi` (`chi/delegate`, `chi/web_search`, with its
1608
+ inputSchema) beside the MCP servers' tools, and `call_tool(tool: "chi/delegate", args: {…})` runs it as `delegate`
1609
+ itself, through the usual guardrails, approvals and hooks ([plugins](plugins.md#chitool_sourcecall-acts_as-label-wait-prefix)). A
1606
1610
  tool in a toolkit's `remove:`, a `--no-tool` or a `/toolkit remove` is **removed**: hard, also after an add, and
1607
1611
  call_tool can't reach it (`child` removes `memory_write`, `delegate` and `delegate_result`, so a child on it
1608
- starts no children). To keep a built-in away from the model, name it under `remove:`. `find_tools` and `call_tool`
1609
- are declared when a toolkit has a reachable built-in or reaches a source group; `full` with no reachable source
1610
- declares neither, and removing them there (`--no-tool call_tool`) leaves nothing out: no prompt line, no chip.
1612
+ starts no children). To keep a tool away from the model, name it under `remove:`. `find_tools` and `call_tool`
1613
+ are declared when a toolkit has a reachable tool or reaches a source group, as the tools stood when the
1614
+ session's worker started; `full` with no reachable source declares neither, and removing them there
1615
+ (`--no-tool call_tool`) leaves nothing out: no prompt line, no chip. A plugin tool that arrives later
1616
+ (`chi.replace_tools`) on a kit that declared neither stays out of reach for that worker, with one warn notice
1617
+ (`/toolkit add NAME` declares it).
1611
1618
  - **On `api: anthropic`, reachable tools are callable by name.** Every tool `call_tool` reaches (the reachable
1612
- built-ins, tools from reached MCP groups as `mcp_<server>_<tool>`) also goes in the request's tools deferred
1619
+ built-ins and plugin tools, tools from reached MCP groups as `mcp_<server>_<tool>`) also goes in the request's tools deferred
1613
1620
  (`defer_loading`: Messages keeps them out of the model's view until something loads them, at a flat ~100 tokens).
1614
1621
  A direct call of one (`list_sessions`, `mcp_fs_read`) runs, under the same guardrails, approvals and rows as
1615
1622
  through `call_tool` (an MCP tool's call is `call_tool`'s). A `find_tools` hit **loads** its matches (up to 5):
@@ -2210,7 +2217,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2210
2217
  | `recap.timeout` | `30` | yes | Seconds a recap request may take. |
2211
2218
  | `recap.min_user_turns` | `2` | yes | Prompts a session needs before it gets a recap. |
2212
2219
  | `recap.sentences` | `2-4` | yes | Recap length, `N` or `N-M` (1–10). Per session too: `--set recap.sentences=3`. |
2213
- | `broadcast.active_hours` | `8` | | `chi broadcast` reaches a session a worker or a chi REPL runs, or whose last turn ended within this many hours. See [Broadcast](broadcast.md). |
2220
+ | `broadcast.active_hours` | `8` | | `chi broadcast` reaches a session a worker or a one-turn `--non-interactive` run has, or whose last turn ended within this many hours. See [Broadcast](broadcast.md). |
2214
2221
  | `broadcast.ticket_pattern` | `\b[A-Z][A-Z0-9]+-\d+\b` | | A Ruby regex for ticket ids; one in the note and in a session's branch (any case) or prompts delivers the broadcast there. See [Broadcast](broadcast.md). |
2215
2222
  | `broadcast.triage_model` | recap's | | The model that judges a recipient no tag matched (a model ref, as `recap.model`); unset: the recap's model, else `default.model`. A ~9B dense instruct model or larger; 4B-class ones say yes to nearly everything. See [Broadcast](broadcast.md#triage). |
2216
2223
  | `broadcast.triage_host_ref` | none | | A `hosts:` name for the triage model. |
@@ -2218,7 +2225,6 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2218
2225
  | `broadcast.triage_parallel` | `4` | | Triage requests at a time. |
2219
2226
  | `broadcast.triage_deadline` | `20` | | Seconds triage may take in all; a recipient not judged by then gets the note unchecked. |
2220
2227
  | `broadcast.threshold` | `0.5` | | The P(yes) a recipient needs, when the triage host gives logprobs; a plain yes or no is 1 or 0. |
2221
- | `session.shared` | `true` | | Plain `chi` runs its session in a worker and attaches; `--no-shared` per run. |
2222
2228
  | `session.idle_exit_minutes` | `30` | yes | An unused worker exits after this; `0` = never. |
2223
2229
  | `session.project_env` | `mise` | | The commands a session's worker runs follow its project: `mise` runs them in the session folder's `mise env`, read once at worker start (`chi sessions restart` picks up a mise.toml edit); `off` doesn't ask mise: the tools keep the env of whoever started the worker (with a chi home, the home's `chi` still goes first on their PATH). See [Project env](sessions.md#project-env). |
2224
2230
  | `session.carry_last_answer` | `true` | | A continue's carry note holds the previous session's last answer after its recap; `false`: the recap only. See [Session chains](sessions.md#session-chains). |
@@ -2250,7 +2256,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2250
2256
  | `log.file` | state dir | yes | See "Debug Log File". |
2251
2257
  | `log.disable` | `false` | yes | No file logging. |
2252
2258
  | `log.level` | `info` | yes | `debug`, `info`, `warn`, `error`. |
2253
- | `status.line` | `on` | yes | The status row under the prompt (the REPL's and attached mode's), `on` or `off`. |
2259
+ | `status.line` | `on` | yes | The status row under the attached terminal's prompt, `on` or `off`. |
2254
2260
  | `context.status` | `true` | yes | Context-usage telemetry for the model. See [context telemetry](internals/context-telemetry.md). |
2255
2261
  | `context.window_tokens` | server's, else 256000 | yes | Context window when the server doesn't report one. `models.<key>.window_tokens` and `hosts.<name>.window_tokens` come first; see "The context window". |
2256
2262
  | `models.<key>.window_tokens`, `hosts.<name>.window_tokens` | none | | A model's or host's context window when the server and its model list report none. |
data/docs/context.md CHANGED
@@ -617,13 +617,13 @@ show the answer as it is.
617
617
  after a push links may be a few lines off until the next read. Without
618
618
  `gh` or its login, nothing is linked.
619
619
  - **Where it reads**: only in a session worker (the web, an attached chi),
620
- whose answers the web shows while the session runs. The REPL and
621
- `chi -p` read nothing (no `gh api` calls), so their answers have no links,
620
+ whose answers the web shows while the session runs. A
621
+ `chi -p … --non-interactive` run reads nothing (no `gh api` calls), so its answers have no links,
622
622
  in the web too; a scratch session never reads. The PR itself is still
623
623
  attached and its context still reaches the model everywhere.
624
624
 
625
625
  `line_links:` takes `false` (off), `true` (the default: a worker only) or
626
- `always` (the REPL and `chi -p` read too, so their answers are linked when
626
+ `always` (a `chi -p … --non-interactive` run reads too, so its answers are linked when
627
627
  you open the session in the web later):
628
628
 
629
629
  ```yaml
data/docs/desktop.md CHANGED
@@ -80,7 +80,7 @@ models and aliases (from [`chi models`](cli.md#listing-the-models)), at most nin
80
80
  The chooser needs a chi with `chi models`: an older helper works with a newer chi, not the reverse
81
81
  (`chi desktop upgrade` after updating chi).
82
82
 
83
- A session open in a `chi --no-shared` REPL isn't listed: it takes no notes or messages.
83
+ A session a one-turn `chi -p … --non-interactive` run has isn't listed: it takes no notes or messages.
84
84
 
85
85
  ## Images
86
86
 
data/docs/guardrails.md CHANGED
@@ -23,7 +23,7 @@ The UI shows the tool line first, then the approval under it.
23
23
 
24
24
  ## Ask
25
25
 
26
- The REPL, the attached TUI and the web show what would run, where and why:
26
+ The attached TUI and the web show what would run, where and why:
27
27
 
28
28
  ```
29
29
  Approve tool call?
@@ -65,8 +65,7 @@ card comes back through `ApprovalFacts.from_h` (string or symbol keys, missing f
65
65
 
66
66
  Who answers:
67
67
 
68
- - REPL (`chi --no-shared`, with or without `-p`): at the `? ` prompt.
69
- - A shared session's worker (plain `chi` and `chi -p …` while `session.shared` is on, the default): any attached TUI or web page. With none attached,
68
+ - A session's worker (plain `chi`, `chi -p …`, the web): any attached TUI or web page. With none attached,
70
69
  the approval waits (in the session file) and shows on attach.
71
70
  - `-p … --non-interactive`: nobody; the call is denied ("No one to approve it
72
71
  (non-interactive run)").
@@ -134,7 +133,7 @@ others come back to the parent's model with the block below. A dismissal
134
133
  always passes (the hook decides what no answer means): the strategy is a
135
134
  convention for an honest parent, not a barrier. This section is
136
135
  the path for other agents, and for a chi parent that can't host the relay (a
137
- `--no-shared` REPL, `--non-interactive`): there the model gets the question
136
+ `--non-interactive` one-shot): there the model gets the question
138
137
  block and the rules below. With the relay, the same rules still hold for a
139
138
  parent model that answers its own relay card (a piped or marked `chi --attach`
140
139
  on the parent): that answer is a parent agent's, checked by the parent and
@@ -197,10 +196,10 @@ value that isn't a level reads as `hard`.
197
196
  config.yml's; `/parent-answers LEVEL` sets the session's own, `/parent-answers
198
197
  default` drops it (config.yml's applies). Anyone may tighten it, a parent too
199
198
  (`chi send ID -m "/parent-answers hard"`). Loosening it (toward `soft`, or
200
- `default`) is yours alone: the line must come from you at the REPL, an attached
199
+ `default`) is yours alone: the line must come from you at an attached
201
200
  terminal or the web composer. chi refuses it (`loosening parent_answers is the
202
201
  user's: …`) from `chi send` (a parent's or yours: the worker can't tell them
203
- apart), from a piped or marked `chi --attach` or REPL, from `chi -p
202
+ apart), from a piped or marked `chi --attach`, from `chi -p
204
203
  --non-interactive`, from a delegate's follow-up and from a plugin; `chi answer`
205
204
  has no command route. `--parent-answers` on `--attach`/`--resume` is ignored,
206
205
  never sent as the command.
@@ -277,7 +276,7 @@ no running turn, pending question or approval, queued prompt, or running
277
276
  background task; when one exists, stop it explicitly and then send a prompt.
278
277
 
279
278
  A parent may also type its answer into chi itself: a piped `chi --attach ID`
280
- or `printf '2\n' | chi --no-shared -p …`. So chi treats an answer as a
279
+ or `printf '2\n' | chi -p …`. So chi treats an answer as a
281
280
  parent's, held to the same setting, when its input isn't a terminal or an
282
281
  agent marker is set: `CLAUDECODE` (Claude Code), `AI_AGENT`, `CODEX_THREAD_ID`
283
282
  (Codex CLI), or `SAMAGOTCHI_PARENT_SESSION`,
@@ -841,11 +840,13 @@ trade-off, when such a clash exists:
841
840
  - deny rules cover both, which is the point.
842
841
 
843
842
  A `call_tool` of one of chi's own tools (`chi/delegate`: a built-in the
844
- session's toolkit leaves out, [toolkits](configuration.md#toolkits)) is
845
- gated **as that tool**: it is turned into the `delegate` call before the
846
- guardrails run, so every rule on `delegate` (or `execute`'s `command:`, the
847
- `rm` check, a delegate child's boundary) gives the same verdict as for a
848
- direct call, and an approval is keyed `delegate:…` either way. `tool:
843
+ session's toolkit leaves out, [toolkits](configuration.md#toolkits)), or of
844
+ a bundle's `chi.tool` it leaves out (`chi/web_search`), is gated **as that
845
+ tool**: it is turned into the `delegate` call before the guardrails run, so
846
+ every rule on `delegate` (or `execute`'s `command:`, the `rm` check, a
847
+ delegate child's boundary, a plugin tool's `targets:`) gives the same
848
+ verdict as for a direct call, and an approval is keyed `delegate:…` either
849
+ way. `tool:
849
850
  call_tool` rules no longer see such calls, only MCP calls and misses. A
850
851
  `chi/` name the toolkit removes is refused before the gate, with no card.
851
852
 
@@ -1325,9 +1326,8 @@ allows), and how many got no judgment. `--threshold` overrides
1325
1326
  for this run; `--dry-run` judges and prints without writing a line. Without a
1326
1327
  client (`mode: off`, or no `host`) it exits 2 saying what to set.
1327
1328
 
1328
- Two limits on what the replay can see: `CardStore` keeps only the last 20
1329
- questions per session, and a `--no-shared` REPL session never writes a
1330
- `cards.json` at all, so its asks are not replayable.
1329
+ A limit on what the replay can see: `CardStore` keeps only the last 20
1330
+ questions per session, so older asks are not replayable.
1331
1331
 
1332
1332
  ## The guardrails bundle
1333
1333
 
data/docs/hooks.md CHANGED
@@ -97,14 +97,14 @@ class Watchful
97
97
  def call(event)
98
98
  case event[:type]
99
99
  when :after_generation
100
- # One line in the REPL, the attached TUI and the web ("<bundle>> text",
100
+ # One line in the attached TUI and the web ("<bundle>> text",
101
101
  # or "hook> text" for a config hook); level: :warn colours it.
102
102
  # fallback_for: :display marks a line a UI that renders the answer's
103
103
  # display may leave out (below).
104
104
  event[:notify].call("the model repeated itself", level: :warn)
105
105
  when :before_tool_call
106
- # A single-select question through the question flow (REPL, attached
107
- # TUI, web); returns {selected: [...], freeform:, selected_indices:}
106
+ # A single-select question through the question flow (the attached
107
+ # TUI, the web); returns {selected: [...], freeform:, selected_indices:}
108
108
  # or nil when there is no one to ask (--non-interactive), the
109
109
  # question was dismissed, or the options (or effects) were invalid.
110
110
  # effects: says what an option does for a parent agent's answer (see
@@ -156,7 +156,7 @@ that a UI that already shows it can leave the line out. The main value is
156
156
  links), for a UI that doesn't render that display. The hook never names a UI;
157
157
  each decides by what it renders. The web with markdown on (`web.markdown` and
158
158
  the commonmarker gem) leaves such a line out, live and after a reload; with
159
- markdown off it shows, as it does in the REPL, the attached TUI and
159
+ markdown off it shows, as it does in the attached TUI and
160
160
  `chi -p`, which print the answer as text (in the web its http(s) URLs are
161
161
  links, as in any hook's notice line). The log keeps the line with
162
162
  `fallback_for=display`. `:editor` marks a line that names a file the viewer
@@ -304,8 +304,8 @@ end
304
304
  kept), on a live turn and after a reload. Its copy button copies the
305
305
  display text. The page learns about it from an `answer_display` event
306
306
  that comes after `turn_completed` (the hooks run after the turn ended).
307
- - **Terminals** (the REPL, the attached TUI) have printed the answer by then
308
- and do not change it; use `event[:notify]` for something they should show.
307
+ - **The terminal** (the attached TUI, a `--non-interactive` one-shot) has printed the answer by then
308
+ and does not change it; use `event[:notify]` for something it should show.
309
309
 
310
310
  A plugin gets the same from `chi.on(:after_turn) { |event, ctx| event[:present].call { … } }`.
311
311
 
@@ -569,7 +569,7 @@ the voter's own (a guard that wants the model to retry a corrected call:
569
569
  `Retry with "…".`).
570
570
 
571
571
  `event[:context]` is `{cwd:, repo_root:, branch:, session_id:, interface:, origin:}`
572
- (`interface` is `:repl`, `:worker` or `:non_interactive`). `event[:targets]` is
572
+ (`interface` is `:worker` or `:non_interactive`). `event[:targets]` is
573
573
  what the call acts on, resolved as the tools resolve it:
574
574
  `{command:, paths:, cwd:, repo_root:, outside_repo:, git_dirs:}`. `outside_repo`
575
575
  is measured from the session's repo, not the call's `cwd:`; `git_dirs` lists where
@@ -644,8 +644,9 @@ A `write`'s `content:` is the file text; an `edit` carries `old_text:` and
644
644
  arguments whole on `args:` (string keys) instead, and its `content:` is those
645
645
  arguments as JSON. An MCP tool's call is chi's `call_tool` (its tool index's, which the mcp
646
646
  bundle runs): `tool: "call_tool"`, with `args: {"tool" => "<server>/<tool>", "args" => {…}}`.
647
- A built-in reached through `call_tool` (`chi/delegate`, a tool the session's toolkit doesn't
648
- declare) fires as that built-in, its call built as a direct call's, with `call[:via] == "call_tool"`.
647
+ A built-in or plugin tool reached through `call_tool` (`chi/delegate`, `chi/web_search`: a tool the
648
+ session's toolkit doesn't declare) fires as that tool, its call built as a direct call's, with
649
+ `call[:via] == "call_tool"`.
649
650
  A hook that drops `via:` or rewrites the call to a tool the toolkit removes gets the call refused
650
651
  by its name.
651
652
  On a Messages host (`api: anthropic`), the model may call those tools by their own names (they
@@ -710,7 +711,7 @@ Notes:
710
711
  - Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order.
711
712
  - Settings: a hook class with `initialize(settings = {})` gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
712
713
  - 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.
713
- - Shipped bundles (each 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)) and `chi bundle install model-notes` (memories: reviewed model notes, see [Bundles](bundles.md#the-model-notes-bundle)).
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)) 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)).
714
715
  - 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).
715
716
 
716
717
  Lifecycle:
@@ -854,8 +855,8 @@ qualified ref:
854
855
 
855
856
  **Where `{repo}` and `{host}` come from.** Without a named group that took
856
857
  part, they come from `git remote get-url <remote>` run in the session's
857
- working directory (the worker's; the in-process REPL's is the terminal's
858
- current directory). `remote:` picks the remote, default `origin` — a fork
858
+ working directory (the worker's; a `--non-interactive` one-shot's is the
859
+ terminal's current directory). `remote:` picks the remote, default `origin` — a fork
859
860
  sets `remote: upstream`. Git applies `insteadOf` rewrites and includes, and a
860
861
  worktree reports its main repo's remote. The URL forms understood are
861
862
  `https://`, `http://`, `ssh://`, `git://` (credentials and port dropped) and
@@ -225,7 +225,7 @@ host refused it for that model.
225
225
  `ChatLoop#tool_definitions(conversation, model:)` appends them (`Tools::DeferredTools.list`) only on such an
226
226
  adapter, and only with at least one tool declared (Messages wants one). The list, per request:
227
227
 
228
- 1. the built-ins the toolkit reaches (`Registry#reachable_names`), in registry order;
228
+ 1. the built-ins and plugin tools the toolkit reaches (`Registry#reachable_names`), in registry order;
229
229
  2. the tool index's source tools while `call_tool` is declared (`ToolIndex#deferrable`, via
230
230
  `Registry::Index#deferrable`): callable, the only tool with its acts_as, a name Messages takes, no registered
231
231
  tool's name; by acts_as (`mcp_<server>_<tool>`), sorted by name;
@@ -270,11 +270,11 @@ sets `:tool_changes` for the model: the retry and every later request drop the s
270
270
 
271
271
  A direct call of a deferred tool runs whether something loaded it or not (the API doesn't check): the Engine sets
272
272
  `TurnSettings#loads_tools` from the turn's final model (after the before_turn hooks), and then `ToolRunner#run` and
273
- `KernelLoop#dispatch` take a reachable built-in by its own name (`Registry#indexed`) through the normal gate, and wrap
273
+ `KernelLoop#dispatch` take a reachable built-in or plugin tool by its own name (`Registry#indexed`) through the normal gate, and wrap
274
274
  an MCP tool's acts_as into the `call_tool` call it stands for (`ToolIndex#wrap`): `call_tool`'s row, rules and
275
275
  approvals. A reload rebuilds that wrapped call from the saved one (`BuiltinCalls.wrap_saved`). Each such call is
276
276
  logged (`tool_direct_deferred`, with `loaded:` whether the conversation had loaded it). On `api: openai` and the
277
- native loop nothing is deferred, and a direct call of a reachable built-in is refused with the `call_tool` route as
277
+ native loop nothing is deferred, and a direct call of a reachable built-in or plugin tool is refused with the `call_tool` route as
278
278
  before.
279
279
 
280
280
  ## 400 fallbacks
@@ -35,6 +35,9 @@ Three rules hold the shape:
35
35
  conversation already holds. Ids are deterministic: `ctx-<name>-<serial>-<revision[0,12]>` (the serial makes A, B, A
36
36
  three notes), `ctx-<name>-error-<error_since digits>`, `ctx-<name>-detached-<seen[0,12]>`.
37
37
  - **Polling is not activity.** The poller and the absorb never hold a worker up (`WorkerIdleExit`).
38
+ - **The leave absorbs too.** A pass looks at the context and then checks whether it should leave; a change that
39
+ lands in that gap has no later pass (the poller stops with the worker), so `Worker#left` absorbs whatever is
40
+ pending as plain notes before the recap, as it does for a background task that finished in the same gap.
38
41
 
39
42
  ## ContextSources
40
43
 
@@ -122,11 +125,11 @@ name (or whose hint is its URL) is attached, *declined* while the declined marke
122
125
  "PR", branch names ≥ 6 chars with `/ - _`, exact URLs, fenced code blanked). Only `OwnerPrompt` texts count
123
126
  (`owner_texts`), so a participant's message, a steer, a note or a reminder never attaches anything; a review
124
127
  session skips all of it.
125
- - **Where it runs.** `before_turn` (from `Worker#run_prompt` before the absorb, and the REPL's turn start): attach
128
+ - **Where it runs.** `before_turn` (from `Worker#run_prompt` before the absorb, and a `--non-interactive` one-shot's turn start, `TerminalUI`): attach
126
129
  each proposed candidate the owner's prompts so far mention (`attach` writes a session `Source` through
127
130
  `source_for`: a URL resolves through `ContextProviders`, else the candidate's own `cmd`), then `first_fetch` them
128
131
  together within `FIRST_FETCH_SECONDS` (10) so the note lands in this turn; cut short, the fetch records nothing and
129
- the poller tries soon. `after_propose` does the same for a late proposal. The REPL has no absorber running, so
132
+ the poller tries soon. `after_propose` does the same for a late proposal. A one-shot has no absorber running, so
130
133
  `deliver_notes` runs one.
131
134
  - Forks and continues take no candidates.
132
135
 
@@ -36,8 +36,7 @@ Behavior:
36
36
  ends the wait when a person's message waits for the turn's next step (`Engine#input_waiting?`, run_turn's
37
37
  `input_waiting:` probe through the kernel's `PeerView`): in a worker an input file that the next boundary merges
38
38
  from the user, `chi send` or a delegating parent (`SessionInbox.steer_waiting?`; never a queued, image or
39
- participant's one, never a plugin's steer or a child's report, never during a participant's turn), in the REPL
40
- (`--no-shared`) a line in its steering queue. The probe only peeks: the drain still merges the message at the
39
+ participant's one, never a plugin's steer or a child's report, never during a participant's turn). The probe only peeks: the drain still merges the message at the
41
40
  next boundary. The result says `note: a message from the user is waiting; the task is STILL RUNNING.`, how to wait
42
41
  again or stop it (`TaskWait::MESSAGE_WAITING_NOTE`), and `waited: Ns`; the task keeps running and stays watched
43
42
  (below). A task that finished meanwhile gives its finished result, as after a Stop. A `chi -p` turn has no probe. The same probe ends a blocking `delegate_result` (and `delegate`'s wait) early; see [Sessions: Delegating](../sessions.md).
@@ -51,7 +50,7 @@ Behavior:
51
50
 
52
51
  A task the model leaves running (it ends its turn: "I'll check when it's finished") is reported when it ends, so the result doesn't wait for the user's next message. `session.task_wakes` (env `SAMAGOTCHI_SESSION_TASK_WAKES`): `wake` (default), `queue` or `off` (no watch, as before).
53
52
 
54
- - **The watch** (`TaskWatch`, `task_watch.rb`) is the Worker's (`WorkerTaskWakes`, `worker_task_wakes.rb`, which writes the notes; the Worker runs the wake turn), in memory: the in-process REPL (`--no-shared`) has none, and a crash loses it (those tasks behave as before). The Worker's Engine observer feeds it the raw `:tool_call_completed` events, the input `Bridge#own_task_ids` reads too. A `task_create` result and an `execute` hand-over add the task (`TaskRuntime.created_id`: a `task_id:` the command printed doesn't count), the hand-over's limit kept for the note. What shows the model the end removes it: a `task_wait` with a final status, or with `wait_result: pattern_matched` (the model chose its own "done" marker); a `task_get` with a final status; any `task_stop`. `task_list` and a `read` of `output.log` don't: a model that read the log still gets the wake once. Tasks a tool starts inside an anytime command (`/btw` and the like) reach the same observer, so they are watched too.
53
+ - **The watch** (`TaskWatch`, `task_watch.rb`) is the Worker's (`WorkerTaskWakes`, `worker_task_wakes.rb`, which writes the notes; the Worker runs the wake turn), in memory: a `--non-interactive` one-shot has none, and a crash loses it (those tasks behave as before). The Worker's Engine observer feeds it the raw `:tool_call_completed` events, the input `Bridge#own_task_ids` reads too. A `task_create` result and an `execute` hand-over add the task (`TaskRuntime.created_id`: a `task_id:` the command printed doesn't count), the hand-over's limit kept for the note. What shows the model the end removes it: a `task_wait` with a final status, or with `wait_result: pattern_matched` (the model chose its own "done" marker); a `task_get` with a final status; any `task_stop`. `task_list` and a `read` of `output.log` don't: a model that read the log still gets the wake once. Tasks a tool starts inside an anytime command (`/btw` and the like) reach the same observer, so they are watched too.
55
54
  - **The idle tick**: with something watched, each idle pass (after delegate reports, before due reminders) refreshes the watched records (`TaskRuntime.get_record`, one `ps` per running task) and takes the finished ones, keeping only the ids the conversation still holds (`TaskRuntime.created_ids_in`: a `!rollback` or a restored turn may have dropped the result that started one; an LLM context strategy can't, since its stale and forget stubs are drawn in the request, never written over the saved result). A note that can't be written puts its task back for the next pass. An empty watch costs nothing. A task that finishes during a turn and is still unseen when the turn ends wakes on the next idle pass. One the user stopped with the web's stop-task button is dropped without a note.
56
55
  - **The note** is a context note under its own header (`ContextNote::TASK_HEADER`; `TaskFinishNote`), `note_id: task-<id>`, `source: task <id8>`:
57
56
 
@@ -70,6 +69,6 @@ A task the model leaves running (it ends its turn: "I'll check when it's finishe
70
69
  - **A plain note instead** (the same text without the last two lines, no turn start), written at once, with `queue`, the budget spent (the UIs hear `N finished background tasks noted; the model reads them with your next message` once per human input, the flag shared with the other wakes), wakes paused, a continue offer waiting, input queued, an exit asked for, or a **note-only** task: one started or waited on in a turn the user stopped (reasons `user`, `manual`, `ctrl_c`), since a wake telling the model to carry on would undo that Stop. A prompt's turn takes the finished ones as plain notes first.
71
70
  - **Failure**: a wake turn that fails and kept nothing, or is stopped and restored, turns its note back into the plain one (`WorkerInbound#unmark_wake`, as for a context wake) and pauses wakes until the user writes.
72
71
  - **Idle exit**: with `wake`, a watched task holds the worker's idle exit (`WorkerIdleExit`'s `:background_tasks`) until its wake is taken: while it runs, and once it finished until the next idle pass takes it (`TaskWatch#holding`, in memory, no `ps`), so one that ends between the pass's look and the exit's check still wakes the session. A still-running one holds `/exit` too (`TaskWatch#running`, read once per request by `WorkerIdleExit#request_hold`; the 409 lists the tasks, `{id, command, age_seconds}`, with a `hint` on stopping them, and the attached terminal names them and says to use the web's stop-task button or ask for `task_stop`), as reminders do. When the only holds are ones the user can stop, the 409 carries `holds` too and the terminal asks whether to stop them all (`HoldStop`, which stops these tasks the same way `task_stop` does). A note-only task holds neither (no wake would come for it), nor does one whose note, or the look for it (the conversation's task ids unreadable), failed 3 times (`TaskWatch::MAX_FAILURES`: it is still tried at each look, but can't keep the worker up for good). With `queue` nothing holds: nothing would wake for the end, so keeping the worker up isn't worth it. As the worker leaves (any exit but a stop, a signal or an `/exit --delete`), the tasks that finished since the last look are written as plain notes; a task that ends after the worker left gets no note (the model sees its end at its next look, as before). A forgotten dev server keeps the worker up until it is stopped; a crash of that server wakes the session.
73
- - **The tool descriptions**: a worker's `execute` and `task_create` say "if you end your turn before it finishes, chi tells you when it does" (left out under `off` and in the REPL), fixed when the worker builds its Engine.
72
+ - **The tool descriptions**: a worker's `execute` and `task_create` say "if you end your turn before it finishes, chi tells you when it does" (left out under `off` and in a `--non-interactive` one-shot), fixed when the worker builds its Engine.
74
73
 
75
74
  Work that needs a model, not a shell command, goes to a child chi session instead: `delegate` / `delegate_result` have the same shape (start, then wait) and return only the child's final reply; see [Sessions: Delegating](../sessions.md#delegating).
@@ -30,6 +30,10 @@ model. The TUI is untouched: it shows a fence as source.
30
30
  clone arrives with both. On failure the `<pre>` stays, marked `data-block-failed`, with one muted line: an attribute CSS
31
31
  shows, never markup, and not in the bubble's `textContent`, which the page's dedupe and an annotation read (`copy.js`'s rule). CSS content can't be selected, so a click on the line copies its words.
32
32
  The toggle has an icon and an `aria-label`, no text (`copy.js`'s rule). A jump into the source shows it (`revealInHistory`).
33
+ For a screen reader: the hidden `<pre>` isn't read, so the `<img>`'s alt names the toggle that shows it ("mermaid
34
+ diagram, its source under the Show the source button"). The natural size, a click on the picture, is also a
35
+ `.block-zoom` button (icon, `aria-label`, `aria-pressed`) for the keyboard: before the toggle, shown on keyboard focus
36
+ only, and taking no pointer while hidden, so the mouse's view is as it was.
33
37
  6. Cache: a `Map` on `lang + theme + source`, about 200 entries, holding the `data:` URL; a later `bubble.innerHTML` wipes the
34
38
  figure and the observer redraws in the same pass. No `crypto.subtle`: it's undefined on a plain-http LAN address. A system
35
39
  theme change redraws through the figure (a hidden `<pre>` never intersects).
@@ -98,11 +98,11 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
98
98
  `chi guardrails replay` reads. A plain upgrade doesn't reach this: a worker already running seeded before and keeps its own version 1
99
99
  file, and a spawn runs the newest chi installed (`SessionManager.worker_command`), unless that activation raises LoadError: the boot
100
100
  line then falls back to the spawner's or the home's pinned version, which can be older. Otherwise it takes an older process starting
101
- on a session a newer one wrote: a downgrade, a chi home whose newest chi is older (`home_older_than_spawner`), or a REPL or `bin/chi`
101
+ on a session a newer one wrote: a downgrade, a chi home whose newest chi is older (`home_older_than_spawner`), or a `bin/chi`
102
102
  from an older gem or checkout on the same state dir. Nothing on this side stops
103
103
  it: an older chi can't be changed, and having it read the file would mean writing fake `turns` counters into every file.
104
104
  - **A killed turn needs nothing special.** `TurnCheckpoint` keeps a running turn's prompt in the session file, with its id from
105
- the start (`Worker#run_engine_turn` settles it first), so it counts as any opening. A REPL keeps no running turn in the
105
+ the start (`Worker#run_engine_turn` settles it first), so it counts as any opening. A `--non-interactive` one-shot keeps no running turn in the
106
106
  session, so a kill takes the prompt along and there's nothing to count.
107
107
 
108
108
  ## SSEWriter
@@ -401,7 +401,7 @@ for client discovery, with the Bridge's secret (see above).
401
401
  **Worker link.** Every client asks `WorkerLink` (`worker_link.rb`) whether a session's worker can be reached, never
402
402
  reading `bridge.json` or probing a port itself: `WorkerLink.resolve(session_dir, unlink:, owner:)` answers a typed
403
403
  outcome. The rule is the owner lock plus the sidecar's `pid`; there is no port probe (a port can be reused by another
404
- session's Bridge, a free lock is the kernel's proof the worker died). In order: no lock holder, or a chi REPL's: a
404
+ session's Bridge, a free lock is the kernel's proof the worker died). In order: no lock holder, or a one-shot's (kind `"tui"`): a
405
405
  sidecar is `Stale(:lock_free)` (removed with `unlink: true`, only if it is still the file that was read and the lock,
406
406
  read again just before, is still free: a worker that took it meanwhile is resolved as that worker), else `None`;
407
407
  a holder with no kind yet: `Starting`; a leaving worker: `Leaving` (marked before its Bridge stops, on every way out:
@@ -55,7 +55,7 @@ session's first counted turn. A
55
55
  hook's switch (`before_turn`) sends nothing: its turn's first request counts on the new model anyway.
56
56
 
57
57
  A process that loads the session on another model than its last turn ran on (`Engine#session=`:
58
- `SessionMetrics#last_model_ref` differs from `effective_model_ref`; a `--resume`'d REPL with `--model`, a worker woken
58
+ `SessionMetrics#last_model_ref` differs from `effective_model_ref`; a `--resume`'d one-shot with `--model`, a worker woken
59
59
  after a `/model` with no turn since) drops the old window (`forget_model_reports!`) and calls
60
60
  `announce_switched_context!(configured_only: true)`: the configured window only
61
61
  (`EffectiveModel#configured_context_window`, no `/props` GET or model list, since a worker runs it before its Bridge