samagotchi 0.2.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 (243) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +43 -0
  3. data/LICENSE +21 -0
  4. data/README.md +126 -0
  5. data/bin/chi +1140 -0
  6. data/docs/architecture.md +299 -0
  7. data/docs/cli.md +490 -0
  8. data/docs/configuration.md +494 -0
  9. data/docs/desktop.md +97 -0
  10. data/docs/guardrails.md +218 -0
  11. data/docs/hooks.md +309 -0
  12. data/docs/internals/background-tasks.md +26 -0
  13. data/docs/internals/context-telemetry.md +36 -0
  14. data/docs/internals/gemma4-contract.md +23 -0
  15. data/docs/internals/tool-guardrails.md +45 -0
  16. data/docs/memory.md +85 -0
  17. data/docs/plugins.md +819 -0
  18. data/docs/releasing.md +135 -0
  19. data/docs/sessions.md +155 -0
  20. data/lib/samagotchi/bridge/bounded_queue.rb +70 -0
  21. data/lib/samagotchi/bridge/card_store.rb +126 -0
  22. data/lib/samagotchi/bridge/event_id.rb +25 -0
  23. data/lib/samagotchi/bridge/ring_buffer.rb +63 -0
  24. data/lib/samagotchi/bridge/sse_writer.rb +248 -0
  25. data/lib/samagotchi/bridge/turn_accumulator.rb +189 -0
  26. data/lib/samagotchi/bridge.rb +993 -0
  27. data/lib/samagotchi/bridge_client/event_stream.rb +158 -0
  28. data/lib/samagotchi/bridge_client/sse_parser.rb +51 -0
  29. data/lib/samagotchi/bridge_client.rb +330 -0
  30. data/lib/samagotchi/bundle_needs.rb +97 -0
  31. data/lib/samagotchi/bundles/btw/manifest.yml +10 -0
  32. data/lib/samagotchi/bundles/btw/plugin.rb +100 -0
  33. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +82 -0
  34. data/lib/samagotchi/bundles/guardrails/guardrails.md +14 -0
  35. data/lib/samagotchi/bundles/guardrails/manifest.yml +8 -0
  36. data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +210 -0
  37. data/lib/samagotchi/bundles/known-names/known_names.md +3 -0
  38. data/lib/samagotchi/bundles/known-names/manifest.yml +14 -0
  39. data/lib/samagotchi/bundles/loop-guard/manifest.yml +10 -0
  40. data/lib/samagotchi/bundles/loop-guard/plugin.rb +158 -0
  41. data/lib/samagotchi/bundles/mcp/manifest.yml +11 -0
  42. data/lib/samagotchi/bundles/mcp/plugin.rb +631 -0
  43. data/lib/samagotchi/bundles/system/config_modification_protocol.md +149 -0
  44. data/lib/samagotchi/bundles/system/delegated.md +10 -0
  45. data/lib/samagotchi/bundles/system/identity.md +7 -0
  46. data/lib/samagotchi/bundles/system/manifest.yml +11 -0
  47. data/lib/samagotchi/bundles/system/memory_guide.md +107 -0
  48. data/lib/samagotchi/bundles/system/self_map.md +55 -0
  49. data/lib/samagotchi/cancellation_controller.rb +78 -0
  50. data/lib/samagotchi/client.rb +429 -0
  51. data/lib/samagotchi/commands/registry.rb +112 -0
  52. data/lib/samagotchi/config.rb +910 -0
  53. data/lib/samagotchi/context_note.rb +77 -0
  54. data/lib/samagotchi/context_quote.rb +21 -0
  55. data/lib/samagotchi/context_usage.rb +66 -0
  56. data/lib/samagotchi/context_window.rb +76 -0
  57. data/lib/samagotchi/debug_log.rb +110 -0
  58. data/lib/samagotchi/desktop/macos/App.swift +102 -0
  59. data/lib/samagotchi/desktop/macos/ChiRunner.swift +201 -0
  60. data/lib/samagotchi/desktop/macos/Hotkey.swift +42 -0
  61. data/lib/samagotchi/desktop/macos/Info.plist.erb +42 -0
  62. data/lib/samagotchi/desktop/macos/Panel.swift +383 -0
  63. data/lib/samagotchi/desktop/macos.rb +255 -0
  64. data/lib/samagotchi/desktop.rb +21 -0
  65. data/lib/samagotchi/desktop_command.rb +143 -0
  66. data/lib/samagotchi/engine.rb +2807 -0
  67. data/lib/samagotchi/guardrails/approval.rb +125 -0
  68. data/lib/samagotchi/guardrails/approvals.rb +177 -0
  69. data/lib/samagotchi/guardrails/context.rb +71 -0
  70. data/lib/samagotchi/guardrails/gate.rb +125 -0
  71. data/lib/samagotchi/guardrails/load_failures.rb +46 -0
  72. data/lib/samagotchi/guardrails/protected_paths.rb +77 -0
  73. data/lib/samagotchi/guardrails/rules.rb +199 -0
  74. data/lib/samagotchi/guardrails/targets.rb +119 -0
  75. data/lib/samagotchi/guardrails/verdict.rb +134 -0
  76. data/lib/samagotchi/guardrails.rb +18 -0
  77. data/lib/samagotchi/hooks/bundle_loader.rb +158 -0
  78. data/lib/samagotchi/hooks/loader.rb +162 -0
  79. data/lib/samagotchi/hooks/registry.rb +261 -0
  80. data/lib/samagotchi/hooks.rb +30 -0
  81. data/lib/samagotchi/host_registry.rb +315 -0
  82. data/lib/samagotchi/idle_client.rb +147 -0
  83. data/lib/samagotchi/idle_recap.rb +549 -0
  84. data/lib/samagotchi/idle_reminders.rb +101 -0
  85. data/lib/samagotchi/idle_scheduler.rb +76 -0
  86. data/lib/samagotchi/image_store.rb +393 -0
  87. data/lib/samagotchi/installed_gem.rb +38 -0
  88. data/lib/samagotchi/kernel_loop.rb +1017 -0
  89. data/lib/samagotchi/launch_mode.rb +34 -0
  90. data/lib/samagotchi/llm/backend.rb +28 -0
  91. data/lib/samagotchi/llm/chat_loop.rb +450 -0
  92. data/lib/samagotchi/llm/errors.rb +329 -0
  93. data/lib/samagotchi/llm/http.rb +412 -0
  94. data/lib/samagotchi/llm/model_result.rb +72 -0
  95. data/lib/samagotchi/llm/native_backend.rb +50 -0
  96. data/lib/samagotchi/llm/native_tool_normalizer.rb +277 -0
  97. data/lib/samagotchi/llm/openai_chat.rb +403 -0
  98. data/lib/samagotchi/llm/usage.rb +79 -0
  99. data/lib/samagotchi/log.rb +200 -0
  100. data/lib/samagotchi/log_line.rb +127 -0
  101. data/lib/samagotchi/log_path.rb +31 -0
  102. data/lib/samagotchi/log_subscriber.rb +163 -0
  103. data/lib/samagotchi/memory_bundle/builder.rb +364 -0
  104. data/lib/samagotchi/memory_bundle/index_updater.rb +123 -0
  105. data/lib/samagotchi/memory_bundle/installer.rb +528 -0
  106. data/lib/samagotchi/memory_bundle/listing.rb +72 -0
  107. data/lib/samagotchi/memory_bundle/manifest.rb +225 -0
  108. data/lib/samagotchi/memory_bundle/merger.rb +52 -0
  109. data/lib/samagotchi/memory_bundle/placeholder.rb +37 -0
  110. data/lib/samagotchi/memory_bundle/provenance.rb +257 -0
  111. data/lib/samagotchi/memory_bundle/source.rb +153 -0
  112. data/lib/samagotchi/memory_bundle/status.rb +107 -0
  113. data/lib/samagotchi/memory_bundle/system_bundle.rb +161 -0
  114. data/lib/samagotchi/memory_bundle/uninstaller.rb +128 -0
  115. data/lib/samagotchi/memory_bundle.rb +17 -0
  116. data/lib/samagotchi/memory_paths.rb +101 -0
  117. data/lib/samagotchi/model_overlay.rb +53 -0
  118. data/lib/samagotchi/model_profile.rb +309 -0
  119. data/lib/samagotchi/muted_memories.rb +66 -0
  120. data/lib/samagotchi/note_command.rb +163 -0
  121. data/lib/samagotchi/output_formatter.rb +100 -0
  122. data/lib/samagotchi/owner_lock.rb +110 -0
  123. data/lib/samagotchi/pending_input_queue.rb +48 -0
  124. data/lib/samagotchi/plugin/api.rb +362 -0
  125. data/lib/samagotchi/plugin/context.rb +193 -0
  126. data/lib/samagotchi/plugin/loader.rb +126 -0
  127. data/lib/samagotchi/plugin/service.rb +117 -0
  128. data/lib/samagotchi/plugin/sessions.rb +150 -0
  129. data/lib/samagotchi/plugin/side_question.rb +60 -0
  130. data/lib/samagotchi/plugin/tool_result.rb +24 -0
  131. data/lib/samagotchi/project_scope.rb +25 -0
  132. data/lib/samagotchi/prompt.rb +119 -0
  133. data/lib/samagotchi/prompt_literal_guard.rb +70 -0
  134. data/lib/samagotchi/recap_store.rb +92 -0
  135. data/lib/samagotchi/reminder_store.rb +165 -0
  136. data/lib/samagotchi/self_report.rb +195 -0
  137. data/lib/samagotchi/send_command.rb +170 -0
  138. data/lib/samagotchi/served_model.rb +32 -0
  139. data/lib/samagotchi/session.rb +508 -0
  140. data/lib/samagotchi/session_commands.rb +527 -0
  141. data/lib/samagotchi/session_delete_command.rb +105 -0
  142. data/lib/samagotchi/session_manager.rb +1049 -0
  143. data/lib/samagotchi/session_metrics.rb +466 -0
  144. data/lib/samagotchi/session_observer.rb +117 -0
  145. data/lib/samagotchi/terminal_ui/attach_launcher.rb +118 -0
  146. data/lib/samagotchi/terminal_ui/attached_loop.rb +1037 -0
  147. data/lib/samagotchi/terminal_ui/attached_view.rb +264 -0
  148. data/lib/samagotchi/terminal_ui/event_renderer.rb +192 -0
  149. data/lib/samagotchi/terminal_ui/formatting.rb +291 -0
  150. data/lib/samagotchi/terminal_ui/image_input.rb +36 -0
  151. data/lib/samagotchi/terminal_ui/input_support.rb +324 -0
  152. data/lib/samagotchi/terminal_ui/legacy_surface.rb +111 -0
  153. data/lib/samagotchi/terminal_ui/line_reader.rb +113 -0
  154. data/lib/samagotchi/terminal_ui/live_region.rb +36 -0
  155. data/lib/samagotchi/terminal_ui/plain_surface.rb +51 -0
  156. data/lib/samagotchi/terminal_ui/question_prompt.rb +153 -0
  157. data/lib/samagotchi/terminal_ui/question_slot.rb +131 -0
  158. data/lib/samagotchi/terminal_ui/reline_seam.rb +216 -0
  159. data/lib/samagotchi/terminal_ui/repl_input.rb +138 -0
  160. data/lib/samagotchi/terminal_ui/screen.rb +316 -0
  161. data/lib/samagotchi/terminal_ui/surface.rb +47 -0
  162. data/lib/samagotchi/terminal_ui/thinking_line.rb +101 -0
  163. data/lib/samagotchi/terminal_ui.rb +1992 -0
  164. data/lib/samagotchi/thinking_ticker.rb +110 -0
  165. data/lib/samagotchi/thought_stream_splitter.rb +149 -0
  166. data/lib/samagotchi/token_usage.rb +88 -0
  167. data/lib/samagotchi/tool_activity.rb +216 -0
  168. data/lib/samagotchi/tool_call_parser.rb +637 -0
  169. data/lib/samagotchi/tool_declarations.rb +561 -0
  170. data/lib/samagotchi/tool_runner.rb +211 -0
  171. data/lib/samagotchi/tools/args.rb +259 -0
  172. data/lib/samagotchi/tools/ask_user_question.rb +152 -0
  173. data/lib/samagotchi/tools/builtins.rb +122 -0
  174. data/lib/samagotchi/tools/cancel_reminder.rb +21 -0
  175. data/lib/samagotchi/tools/delegate.rb +167 -0
  176. data/lib/samagotchi/tools/delegate_result.rb +53 -0
  177. data/lib/samagotchi/tools/delegate_wait.rb +153 -0
  178. data/lib/samagotchi/tools/edit.rb +155 -0
  179. data/lib/samagotchi/tools/execute.rb +214 -0
  180. data/lib/samagotchi/tools/list_reminders.rb +20 -0
  181. data/lib/samagotchi/tools/list_sessions.rb +74 -0
  182. data/lib/samagotchi/tools/memory.rb +256 -0
  183. data/lib/samagotchi/tools/output_guardrails.rb +93 -0
  184. data/lib/samagotchi/tools/peers.rb +18 -0
  185. data/lib/samagotchi/tools/read.rb +182 -0
  186. data/lib/samagotchi/tools/register_reminder.rb +53 -0
  187. data/lib/samagotchi/tools/registry.rb +60 -0
  188. data/lib/samagotchi/tools/send_note.rb +49 -0
  189. data/lib/samagotchi/tools/task_create.rb +29 -0
  190. data/lib/samagotchi/tools/task_get.rb +39 -0
  191. data/lib/samagotchi/tools/task_list.rb +43 -0
  192. data/lib/samagotchi/tools/task_runtime.rb +311 -0
  193. data/lib/samagotchi/tools/task_stop.rb +29 -0
  194. data/lib/samagotchi/tools/task_wait.rb +104 -0
  195. data/lib/samagotchi/tools/tool_path.rb +18 -0
  196. data/lib/samagotchi/tools/web_fetch.rb +163 -0
  197. data/lib/samagotchi/tools/write.rb +26 -0
  198. data/lib/samagotchi/turn_flow.rb +242 -0
  199. data/lib/samagotchi/turn_note.rb +76 -0
  200. data/lib/samagotchi/turn_tally.rb +101 -0
  201. data/lib/samagotchi/version.rb +7 -0
  202. data/lib/samagotchi/vision_context.rb +132 -0
  203. data/lib/samagotchi/vision_support.rb +109 -0
  204. data/lib/samagotchi/web/app.rb +1349 -0
  205. data/lib/samagotchi/web/markdown_renderer.rb +107 -0
  206. data/lib/samagotchi/web/message_parts.rb +169 -0
  207. data/lib/samagotchi/web/public/activity.js +100 -0
  208. data/lib/samagotchi/web/public/annotations.js +67 -0
  209. data/lib/samagotchi/web/public/app.js +2382 -0
  210. data/lib/samagotchi/web/public/card.js +74 -0
  211. data/lib/samagotchi/web/public/chat_view.js +360 -0
  212. data/lib/samagotchi/web/public/chunk_router.js +25 -0
  213. data/lib/samagotchi/web/public/command_complete.js +39 -0
  214. data/lib/samagotchi/web/public/composer_size.js +19 -0
  215. data/lib/samagotchi/web/public/copy.js +142 -0
  216. data/lib/samagotchi/web/public/ctx.js +35 -0
  217. data/lib/samagotchi/web/public/data.js +256 -0
  218. data/lib/samagotchi/web/public/format.js +232 -0
  219. data/lib/samagotchi/web/public/hold.js +78 -0
  220. data/lib/samagotchi/web/public/images.js +77 -0
  221. data/lib/samagotchi/web/public/index.html +568 -0
  222. data/lib/samagotchi/web/public/init_row.js +60 -0
  223. data/lib/samagotchi/web/public/model_pick.js +23 -0
  224. data/lib/samagotchi/web/public/question_card.js +100 -0
  225. data/lib/samagotchi/web/public/route.js +17 -0
  226. data/lib/samagotchi/web/public/scope.js +36 -0
  227. data/lib/samagotchi/web/public/scroll.js +24 -0
  228. data/lib/samagotchi/web/public/sentences.js +88 -0
  229. data/lib/samagotchi/web/public/sessions_list.js +60 -0
  230. data/lib/samagotchi/web/public/strip.js +25 -0
  231. data/lib/samagotchi/web/public/tally.js +37 -0
  232. data/lib/samagotchi/web/public/thinking_ticker.js +79 -0
  233. data/lib/samagotchi/web/public/timing.js +185 -0
  234. data/lib/samagotchi/web/public/turn_events.js +209 -0
  235. data/lib/samagotchi/web/public/turn_model.js +204 -0
  236. data/lib/samagotchi/web/public/turn_view.js +587 -0
  237. data/lib/samagotchi/web/server.rb +183 -0
  238. data/lib/samagotchi/web/session_hub.rb +329 -0
  239. data/lib/samagotchi/web/session_summary.rb +85 -0
  240. data/lib/samagotchi/worker.rb +635 -0
  241. data/lib/samagotchi/worker_idle_exit.rb +87 -0
  242. data/lib/samagotchi.rb +12 -0
  243. metadata +374 -0
data/docs/cli.md ADDED
@@ -0,0 +1,490 @@
1
+ # CLI and REPL
2
+
3
+ ## Commands
4
+
5
+ - `chi` — start a session in a background worker and attach the terminal to it, so the Web UI (or another terminal) can share it (see [Sharing a session](#sharing-a-session))
6
+ - `chi -p "your prompt"` — run a prompt, then stay attached
7
+ - `chi -p "your prompt" --non-interactive` — run a prompt, print the answer, exit
8
+ - `chi --resume <session-id>` — resume a prior session (in its worker)
9
+ - `chi --no-shared [--resume <session-id>]` — the plain in-process REPL instead, for this run
10
+ - `chi --attach <session-id>` — attach the terminal to a session's worker (e.g. one started from the Web UI), waking one if it has exited
11
+ - A session id can be shortened to any unique prefix (like git): `chi --attach 2ea8`. `--resume`, `--attach`, `sessions stop` and `sessions delete` take one; an ambiguous prefix lists the sessions it matches.
12
+ - `chi web [--port 4567] [--open] [--scope=all]` — start the Web UI (single localhost port session control plane) on this git project's sessions (`--scope=all`, or a folder in no repo: every session); if a chi web already runs on the port, print (with `--open`, open) its page for this folder and exit. Something else on the port (an older chi web too) exits 1 with "port N is in use"
13
+ - `chi web --web-markdown` — opt in to sanitized Markdown rendering for completed assistant messages
14
+ - `chi web --no-web-turn-view` — show turns as the classic row of bubbles instead of the default turn view (each turn as one block of steps, the running one at the bottom); `?view=turn|chat` on the page URL overrides it (see [Web turn view](#web-turn-view))
15
+ - `chi sessions list|stop|delete|prune|clean` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>` (see [Sessions](sessions.md))
16
+ - `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))
17
+ - `chi send [-m TEXT] (ID|PREFIX)...` — send a message to sessions as if typed there: a turn starts (or a running one picks it up); piped stdin goes above `-m` as quoted context (see [Sessions: Sending a message](sessions.md#sending-a-message))
18
+ - `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text to live sessions as context notes (see [Desktop helper](desktop.md))
19
+ - `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles
20
+ - `chi bundle install|upgrade|uninstall|status|diff|list|build` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs (see [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) and [the loop-guard bundle](plugins.md#the-loop-guard-bundle))
21
+
22
+ ## Flags
23
+
24
+ Samagotchi exposes one flag that feeds a prompt (`-p`, `--prompt`) and one that
25
+ controls exit behavior (`--non-interactive`); `--resume` composes with both.
26
+
27
+ | Flag | Purpose |
28
+ |------|---------|
29
+ | `-p`, `--prompt TEXT` | Feed `TEXT` as the first turn (also prefill-equivalent; `-p` feeds **and** runs). |
30
+ | `--non-interactive` | Run a single turn then exit the REPL (sets a high iteration cap; implies `--no-interrupt`). Harmless no-op when given without `-p`. |
31
+ | `--resume SESSION_ID` | Load a prior session's history instead of creating a fresh one. |
32
+ | `--shared` | Run the session (new, or `--resume`'s) in a background worker and attach to it: the default, and the way to get it when `session.shared` is off. See [Sharing a session](#sharing-a-session). |
33
+ | `--no-shared` | Run the plain in-process REPL for this run. |
34
+ | `--attach SESSION_ID` | Attach to a session's worker, waking one if it has exited. |
35
+ | `--model NAME` | Use this model for the run (overrides the configured default and a resumed session's model). |
36
+ | `--profile NAME` | Prompt profile (`qwen36` or `gemma4`) for every model in this run, over config and the server's template (same as `--model-profile`, env `SAMAGOTCHI_MODEL_PROFILE`). See "Prompt profile" in configuration.md. |
37
+ | `--memory NAME` | Preload a memory entry into the system prompt (repeatable; a comma list too). Merged under the config.yml `memories:` baseline. Works attached: the list is stored on the session, so its worker builds the same prompt on every respawn. |
38
+ | `--mute NAME` | Hide a memory from this session (repeatable; a comma list too): its index line is not in the prompt, `memory_read` refuses it, the identity auto-load skips it, and it is dropped from the preloads (config baseline or `--memory`). A name matches in both scopes (`gh-helper`, `project/gh-helper` and `gh-helper.md` all hide `gh-helper`). Nothing on disk changes. See [Muting a memory](#muting-a-memory). |
39
+ | `--no-interrupt` | Raise the tool-call limit to 1000 iterations for long tasks. |
40
+ | `--no-default-input` | Skip prefilling the first REPL line from `SAMAGOTCHI_DEFAULT_INPUT`. |
41
+ | `-v`, `--verbose` | Log at debug level (raw LLM responses, tool call/result payloads) and print every log record to stderr too. |
42
+ | `--version` | Print `chi <version>` and exit (`chi self` shows it with the paths). |
43
+
44
+ Every setting in the config registry (`lib/samagotchi/config.rb`) that exposes a CLI
45
+ flag also works as `--kebab-case VALUE`, e.g. `--server-host`, `--server-port`,
46
+ `--read-truncate-at-bytes`. `chi --help` lists them all.
47
+
48
+ **Which loop runs.** There is no backend flag: the model's host decides. A host with
49
+ `api: openai` in config.yml is driven through the OpenAI chat API (streamed; a remote
50
+ provider via `url:` and `api_key_env:`); every other host gets chi's own raw-prompt loop. `/model` and `--model host:model` switch hosts, and
51
+ the loop with them. See [Configuration](configuration.md) (`hosts:` and `api:`).
52
+ `--backend`, `SAMAGOTCHI_BACKEND` and a `backend:` key were removed; chi says so if
53
+ it sees one.
54
+
55
+ ### Entrypoint scenarios
56
+
57
+ | Command | Behavior |
58
+ |---------|----------|
59
+ | `chi` | Start a fresh session in a worker and attach to it. |
60
+ | `chi -p "refactor this"` | Start a session in a worker, send the prompt, **stay attached**. |
61
+ | `chi -p "refactor this" --non-interactive` | Run one turn in this process, save, **exit** (no REPL, no worker). |
62
+ | `chi --non-interactive` | Harmless no-op exit; no session created, no error. |
63
+ | `chi --resume ID` | Resume session `ID` in a worker (or join the worker already running it) and attach. |
64
+ | `chi --resume ID -p "next step" --non-interactive` | Resume `ID`, run the prompt, save, exit. |
65
+ | `chi --resume ID -p "next step"` | Resume `ID`, send the prompt, **stay attached** to that session. |
66
+ | `chi --no-shared [...]` | The same, in the plain in-process REPL. |
67
+
68
+ Notes:
69
+
70
+ - `-p` always feeds **and** runs the prompt; there is no feed-and-edit variant. To
71
+ prefill (edit, not execute) the first REPL line, use the
72
+ `SAMAGOTCHI_DEFAULT_INPUT` environment variable instead.
73
+ - Prompt history is persisted per session; `--resume` preserves prior messages as
74
+ turn context (a `-p` run on a resumed session never clobbers existing history).
75
+ - Non-interactive runs (`-p` with `--non-interactive`, or bare `--non-interactive`)
76
+ print only the final result output — no spinner, status line, or REPL.
77
+
78
+ ### Sharing a session
79
+
80
+ Plain `chi` runs the session in a background worker and attaches the terminal
81
+ to it (`session.shared`, default `true`). A worker's session can have any number
82
+ of UIs at once: the Web UI and attached terminals (`chi`, `--resume`,
83
+ `--attach`). They all see the same turns as they happen, and any of them can send
84
+ a prompt, also while a turn runs (it merges into that turn as steering). The
85
+ first answer to an `ask_user_question` wins; the other UIs close their widget.
86
+ An empty answer dismisses the question in every UI.
87
+
88
+ In an attached terminal:
89
+
90
+ - Ctrl-C cancels the running turn (whoever started it) and leaves what you typed
91
+ in the prompt. At an idle prompt it clears the line; a second Ctrl-C within
92
+ 2 s, Ctrl-D or `/detach` detaches. The worker keeps running; the detach line
93
+ prints `chi --attach ID` to come back.
94
+ - `/exit` (also `/quit`, `exit`) detaches and asks the worker to exit now, so it
95
+ doesn't wait out the idle timeout. It stays up while something still needs
96
+ it, and the detach line says what: a turn is running (Ctrl-C cancels it
97
+ first), prompts are queued, a continue offer is pending, another UI is
98
+ attached, or reminders are set. A web tab you just closed can count as
99
+ attached for about 30 s. When the worker exits, `chi --resume ID` or
100
+ `chi --attach ID` starts a new one with the conversation. Before it exits
101
+ (here and on the idle exit) the worker writes the session's recap if
102
+ anything new was said, which takes a few seconds; the terminal doesn't
103
+ wait, and an attach or `chi send` meanwhile starts the next worker once it
104
+ is gone. A worker from an
105
+ older chi can't be asked; the line says to use `chi sessions stop ID`.
106
+ - `/exit --delete` (also `/quit --delete`, `exit --delete`) does the same and,
107
+ once the worker has agreed to exit (without writing a recap), deletes the session for good. When the
108
+ worker stays up, nothing is deleted and the line says why.
109
+ - The prompt stays open while a turn runs; see [Typing during a turn](#typing-during-a-turn).
110
+ - `/model`, `/models`, `/guardrails`, `/continue`, `!rollback` and `!commands` run in the
111
+ worker, and every UI sees their output; `/stats` and `/recap` work too. The
112
+ Web UI's composer takes the same commands.
113
+ - `!commands` and the model's tools run in the session's directory (where it
114
+ was started), whichever terminal you attach from.
115
+ - `-p` sends its prompt once attached, `--model` switches the worker's model
116
+ first, and `--no-interrupt` applies to each prompt this terminal sends.
117
+ - History, completion and the idle status line work as in the REPL.
118
+ - The attached view needs reline 0.6.x to draw around the open prompt; with
119
+ another version it prints plainly.
120
+
121
+ A worker nobody uses exits after `session.idle_exit_minutes` (30 by default, `0`
122
+ for never): no turn running or queued, no UI attached (an open web tab or an
123
+ attached terminal counts, even an idle one) and no reminder registered. The next
124
+ prompt or `--attach` wakes a new worker with the conversation intact; `/stats`
125
+ counters start over (the recap is saved with the session).
126
+
127
+ A session you leave with nothing in it (no prompt sent, no `/model` switch, no
128
+ note or image) is deleted as its worker exits, and `/exit` says so; set
129
+ `session.keep_empty: true` to keep such sessions. See
130
+ [Sessions](sessions.md).
131
+
132
+ `chi sessions stop ID...` stops each session's worker and waits for it to exit, so
133
+ a `chi --resume ID` after it starts a fresh one. A worker still running an
134
+ older chi (from before an upgrade) takes turns but not commands; the attached
135
+ terminal and the Web UI say so, with that restart line.
136
+
137
+ `chi sessions delete [--force] ID...` deletes sessions for good: the
138
+ session file and its whole directory (notes, images, queued input). Each id
139
+ (or unique prefix) gets one line: `deleted`, or `refused` with the reason. A
140
+ session whose worker runs is refused unless `--force` stops the worker first;
141
+ one open in a plain REPL is always refused ("close it there first"). Exit
142
+ status: 0 when all are gone, 1 when any was refused or unknown, 2 on a usage
143
+ error. The Web UI deletes too: `delete` in the info bar, or the ✕ on a card in
144
+ All sessions; it asks first and stops a live worker.
145
+
146
+ **The plain REPL.** Some launches run the session in this process instead, with
147
+ no worker:
148
+
149
+ - `--no-shared`, for one run, or `session.shared: false` in the config
150
+ (`SAMAGOTCHI_SESSION_SHARED=0`), for every run.
151
+ - `--non-interactive`, a one-shot with no REPL.
152
+ - `--verbose`, which attached mode can't honor (the worker prints nothing). It
153
+ prints a one-line note, `(session.shared: --verbose runs in a plain REPL)`.
154
+
155
+ A session the REPL has open can't be shared: `--resume` and `--attach` on it say
156
+ "close it there first", and the Web UI shows it read-only. `--attach`/`--shared`
157
+ can't be combined with `--non-interactive` or `--verbose`.
158
+
159
+ ### Muting a memory
160
+
161
+ `chi --mute NAME` runs a session without a memory: for a memory whose
162
+ description mixes the context for a small model, or one a bundle owns
163
+ (`gh-helper`, `jira-manager`) that is not worth editing locally. The memory's
164
+ file and index line stay as they are; only this session doesn't see it.
165
+
166
+ - `--memory` and `--mute` are session fields (`preloaded_memory_names`,
167
+ `muted_memory_names` in the session's JSON), written before the worker
168
+ starts. A worker respawned by `--resume`, `--attach` or `chi send` builds
169
+ the same prompt, and a REPL `--resume` of that session keeps the lists too
170
+ (merged with the flags it is given).
171
+ - A mute wins: `--mute user_preferences` drops that config baseline entry for
172
+ one session, and `--memory x --mute x` is a mute (one warning line).
173
+ - Names are checked before the launch, warnings only: `Warning: --memory 'x'
174
+ not found`, `Warning: --mute 'x' matches no memory`, `Warning: 'x' is both
175
+ --memory and --mute; muted`.
176
+ - `--attach ID` or `--resume ID` with either flag: the session's prompt is
177
+ already built, so the flags are ignored with one line,
178
+ `(--mute applies to a new session; <id>'s prompt is already built)`.
179
+ - The status row shows `mem: <used and preloaded>` and `muted: <names>`; the
180
+ Web UI's info-bar tooltip shows `memories: … · preloaded: … · muted: …`.
181
+ - The `read` tool on `memories/<name>.md` is not refused (a guardrails rule
182
+ can protect the path if wanted).
183
+
184
+ ### Typing during a turn
185
+
186
+ The prompt stays open while a turn runs, in an attached terminal and in the plain
187
+ REPL alike:
188
+
189
+ - A line you submit merges into the running turn at its next step (after the
190
+ current tool call or answer), and `(1 message merged into the running turn)`
191
+ says so. An answer the model finished just before the merge is printed first.
192
+ A line that comes after the turn's last step runs as the next turn, and so
193
+ does one sent after Ctrl-C: it doesn't merge into the turn being cancelled.
194
+ Reminder turns take merged lines too.
195
+ - `/stats` and `/recap` answer at once. Other commands (`!cmd`, `/model`,
196
+ `!rollback`, `/continue`, `/guardrails`) say `busy: wait for the turn to end`
197
+ and go back into the prompt, so Enter runs them once the turn ends.
198
+ - A question (`ask_user_question`, a guardrails approval) turns the prompt into
199
+ a yellow `? ` and lists its choices under it, fitted to the terminal; only a
200
+ line submitted there answers it (a number, `1,3`, a label, `y`/`n` for an
201
+ approval, `; text` for a reason; Enter alone dismisses it), and what you had
202
+ typed comes back once it closes. The choices then go, and one line stays:
203
+ `? Pick a fruit → Banana`.
204
+ - Ctrl-C cancels the turn and keeps what you typed.
205
+ - In the plain REPL, Ctrl-D on an empty prompt (or `exit`, `/exit`) mid-turn
206
+ exits once the turn ends: `(exits after this turn; Ctrl-C cancels it)`
207
+ (`/exit --delete` deletes the session then too). In an
208
+ attached terminal it detaches at once and the turn goes on in the worker
209
+ (`/exit` then says the worker stays up: a turn is running).
210
+
211
+ With stdin that isn't a terminal (a pipe), the REPL reads a line only between
212
+ turns.
213
+
214
+ ### Images
215
+
216
+ A model that can see images gets them three ways:
217
+
218
+ - **`@path` in a prompt** (REPL, attached terminal, `-p`): `what's wrong in
219
+ @shot.png?`, `@~/Desktop/a.jpg`, `@"my shot.png"`. Each `@` token that names an
220
+ image file (png, jpeg, gif, webp; bmp, tiff and heic are converted) goes with
221
+ the prompt, and a dim line shows it: `[image shot.png 1280×800 · ~1.3k tokens]`.
222
+ The prompt text stays as typed; an `@` token that isn't an image (a source
223
+ file, a missing path, an email address) is just text. A line with images typed
224
+ while a turn runs waits for the next turn (steering merges text only).
225
+ - **A path in plain words**: "check /home/me/shot.png and describe it". The
226
+ model calls `read` on it and sees the picture; the tool line ends in
227
+ `→ image 1280×800`.
228
+ - **The Web UI**: paste or drop images into the composer. Each shows as a chip
229
+ (× removes it) and is sent with the message; an image alone is sent as
230
+ `[image: name]`. Messages show thumbnails; a click opens one full size.
231
+
232
+ Images are downscaled to a 1568 px long side (with `sips` on macOS or
233
+ ImageMagick; without either, a larger image is refused with a hint) and stored
234
+ next to the session in `<session>/images/`, and the session file keeps small
235
+ references to them. Each request sends the newest 20 images of the conversation;
236
+ older ones become a line like `[image shot.png 1280×800 not sent: only the
237
+ newest 20 images are sent]`.
238
+
239
+ A model that can't see images (a text-only model, llama.cpp without
240
+ `--mmproj`, an mlx host) refuses a turn with images before sending it:
241
+ `host main can't take images: …; send text only, or pick a model that can see
242
+ images (/model)`, and the typed text (and the web's chips) come back. When chi
243
+ can't tell beforehand, the provider's refusal gives the same line. Images already
244
+ in the conversation go as placeholder lines after a switch to such a model.
245
+ Settings: `image.*` and `vision:` in [Configuration](configuration.md#images).
246
+
247
+ ### Web Markdown rendering
248
+
249
+ Web responses are escaped text by default. To render completed assistant
250
+ responses as HTML, enable the renderer for the web server (it uses
251
+ `commonmarker`, which `bundle install` pulls in from the Gemfile; outside
252
+ Bundler, `gem install commonmarker`):
253
+
254
+ ```sh
255
+ chi web --web-markdown
256
+ ```
257
+
258
+ The setting also supports `SAMAGOTCHI_WEB_MARKDOWN=true` or the global config:
259
+
260
+ ```yaml
261
+ web:
262
+ markdown: true
263
+ ```
264
+
265
+ Only finalized assistant messages are rendered; user messages and live streaming
266
+ chunks remain escaped text. Generated HTML is sanitized, raw HTML in model output
267
+ is not trusted, and unsafe links are removed. If Markdown is enabled without
268
+ commonmarker installed, Chi Web keeps the normal escaped-text display and shows a
269
+ warning explaining how to install the optional gem.
270
+
271
+ Every prompt and answer has a copy button (on hover; always shown, dimmed, on a
272
+ touch screen), and so does each code block of a rendered answer. An answer
273
+ copies its Markdown source, not the rendered text; a code block copies just
274
+ its code; a prompt copies the text as you typed it.
275
+
276
+ ### Web turn view
277
+
278
+ The turn view, the default, shows a turn as *one block* where the work
279
+ happens (the classic chat view renders a turn with tool calls as a row of
280
+ bubbles: one thinking block, one activity panel and one answer bubble per
281
+ generation; `web.turn_view: false` brings it back). The running
282
+ generation is the live part at the bottom (its thinking, its narration, its
283
+ tool rows), the earlier ones stack above it collapsed to one line each
284
+ (their narration's first line, else `working with <tools>`, and a call
285
+ count), expandable for inspection. The live thinking is one line: the
286
+ newest complete sentence, changing at most once per 1.5 s. Click it for the
287
+ full text; a peek is per step (the next step's thinking starts closed
288
+ again). When a step ends its thinking closes to a plain `thinking` line
289
+ (unless you opened it); a step that only thought shows that line alone. The
290
+ live narration is a box of about three lines that fills sentence by
291
+ sentence (a sentence shows once it is complete) and scrolls to the newest
292
+ when full. When the turn ends the block collapses to its summary (`3 steps
293
+ · 8 tool calls · execute ×7 · read ×1`) and the answer expands from the
294
+ box into a normal bubble under it (Markdown, annotate). A plain answer
295
+ without tools ends exactly as it does today. Rows the code collapses stay
296
+ as you toggled them. A reloaded session shows the same block
297
+ from the saved messages (each step's thinking, narration, tool parameters
298
+ and output, the output capped at 2000 characters) and the timing records
299
+ (status and duration per row). On an `api: openai` host the model's
300
+ reasoning is saved with each step for this (never sent back to the model);
301
+ steps saved before that have none, so they show no thinking.
302
+
303
+ ```sh
304
+ chi web --no-web-turn-view # the classic chat view; --web-turn-view is the default
305
+ ```
306
+
307
+ The setting also supports `SAMAGOTCHI_WEB_TURN_VIEW=false` or the global config:
308
+
309
+ ```yaml
310
+ web:
311
+ turn_view: false
312
+ ```
313
+
314
+ `?view=chat` on the page URL forces the classic chat view for that page load
315
+ and `?view=turn` the turn view, whatever the config says; the parameter is dropped
316
+ when you switch between the project and all-sessions views. The terminal
317
+ UIs are not affected.
318
+
319
+ ## Runtime Model Switch (Assist Mode)
320
+
321
+ In interactive assist mode, you can switch the request model without restarting:
322
+
323
+ - `/model <name>`: set a session-scoped model override.
324
+ - `/model host:model` or `/model host/alias`: qualified host routing (`host:alias` expands alias bare, alias may itself be `host:model` — hybrid).
325
+ - `/model --default <name>`: set session model and persist as new default in `config.yml` (also updates `SAMAGOTCHI_DEFAULT_MODEL` for future sessions; supports `host:model` full ref).
326
+ - `/model <name> --alias <alias>`: create alias for current effective model (alias value may be bare or `host:model`).
327
+ - `/model`: show the effective model (and default when diverged: `runtime model: <effective> (default: <default>, profile=<name>, <source>)`, e.g. `profile=qwen36, server (chat_template)`).
328
+ - `/model clear` (or `default`/`none`/`off`): clear the session override, reverting to the configured default.
329
+ - `/guardrails`: the guardrail rules (by source), what failed to load, and your stored approvals, numbered; `/guardrails revoke N` removes approval N (see [Guardrails](guardrails.md)).
330
+ - `/models`: list model ids aggregated across all `hosts:` (grouped `host (host:port):` with per-host `unreachable` warnings, e.g. an unset `api_key_env`; lists cached 60s, 10 minutes for a remote host; lazy — no startup prefill). At most 20 ids per host, then `… and N more`; `/models <text>` lists every id containing `<text>` (any case), e.g. `/models qwen` on OpenRouter.
331
+
332
+ Notes:
333
+
334
+ - The switch updates the request `model` field, routes to the matching host (`HostRegistry`, `lib/samagotchi/host_registry.rb:72`), and resolves the prompt profile again (config, the server's chat template, the name; see "Prompt profile" in configuration.md).
335
+ - Without `--default` the command is session-scoped and does not rewrite config files.
336
+ - With `--default` the new default is written to `~/.config/samagotchi/config.yml` (honoring `XDG_CONFIG_HOME`) and takes effect for all new sessions; the current session's effective model is also updated immediately. Bare aliases and `host:model` are both valid.
337
+ - Worker sessions inherit `hosts:` via `SAMAGOTCHI_HOSTS_JSON`.
338
+ - The idle recap uses the session's current model (a switch counts from the next recap), unless `recap: {host_ref, model}` pins one.
339
+
340
+ ## Session recap
341
+
342
+ A short recap of the session, for when you come back to it: what you were
343
+ working on, what came of it and what is still open, not a turn-by-turn log.
344
+
345
+ - It is written once the session has sat idle for `recap.inactivity` (180 s)
346
+ after at least `recap.min_user_turns` (2) prompts, and as a worker (or the
347
+ REPL) exits, when something new was said since the last one. Each one
348
+ builds on the previous recap, so it only sends what is new.
349
+ - It is saved with the session (`<session>/recap.json`) and shown as a dim
350
+ `recap>` block when you attach or `--resume`, noting how many turns came
351
+ after it (`recap (before the last 2 turns)>`). One written while you sit at
352
+ the prompt prints there.
353
+ - `/recap` shows the saved one and asks for a new one when the chat moved on
354
+ (`writing a recap…`, then the recap when it comes).
355
+ - One written while a continue offer waits (the last turn ran out of steps)
356
+ says that turn stopped before the task was finished. Answering `no` counts
357
+ as activity, so the next recap no longer says so.
358
+ - It is 2-4 sentences; `recap.sentences` sets another length (`3`, `5-7`,
359
+ up to 10; `--recap-sentences`, `SAMAGOTCHI_RECAP_SENTENCES`). A change
360
+ shows from the next recap written, and updates keep to it.
361
+ - It uses the session's own model unless `recap:` names one;
362
+ `recap: false` turns it off. See configuration.md.
363
+
364
+ ## Tool Activity Log
365
+
366
+ Samagotchi now prints a concise, human-friendly tool activity log in normal
367
+ chat output. Each tool call is summarized as:
368
+
369
+ `tool> <action> (<tool> <param-preview>): <status>`
370
+
371
+ Examples:
372
+
373
+ - `tool> reading file (read path="README.md"): ok`
374
+ - `tool> running command (execute command="bundle exec rspec spec/..." ): error`
375
+
376
+ Parameter previews are normalized to one line and truncated to keep output concise.
377
+
378
+ This is separate from verbose mode:
379
+
380
+ - Default output shows short activity status lines only.
381
+ - `-v/--verbose` prints the debug log's records (raw LLM responses and full
382
+ tool call/result payloads among them) to stderr as well; see
383
+ [Debug Log File](configuration.md#debug-log-file).
384
+
385
+ ## Persistent Prompt History
386
+
387
+ Assist mode keeps a small persistent prompt history across restarts.
388
+
389
+ - Default history file: `$XDG_STATE_HOME/samagotchi/history.json`
390
+ - XDG fallback when unset: `~/.local/state/samagotchi/history.json`
391
+ - Optional override: `SAMAGOTCHI_HISTORY_FILE=/custom/path/history.json`
392
+ - Stored entries: most recent `20` prompts
393
+ - Format: JSON array of prompt strings
394
+
395
+ Behavior details:
396
+
397
+ - Prompt history is loaded on startup before the first `>` prompt.
398
+ - Only real user prompts are persisted.
399
+ - Continue-flow inputs (`yes`, `no`, `no, <reason>`, `/continue`) are not persisted as prompts.
400
+ - In assist mode, pressing `Tab` on an `@`-prefixed token (for example `@lib/sama`) completes project file and directory paths while preserving the `@` prefix.
401
+ - Press `Tab` twice to cycle/show multiple matching candidates, similar to IRB completion behavior.
402
+ - History read/write errors are ignored so the session continues uninterrupted.
403
+
404
+ ## Status Line
405
+
406
+ Assist mode can render a compact generalized status line that can include mode,
407
+ context estimate, and active memory hints.
408
+
409
+ Behavior:
410
+
411
+ - A static status line is printed before the next `>` prompt in assist mode.
412
+ - During spinner rendering, status details are rendered in the spinner block.
413
+ - When llama.cpp streaming payload includes usage fields, status prefers server-derived token telemetry (`p`, `c`, `t`) and context percent.
414
+ - If server usage fields are absent, status falls back to the `:context_status` estimate telemetry.
415
+ - When a memory is loaded between tool rounds, the spinner line includes a `loaded: <memory>` notification immediately after the spinner frame.
416
+ - After responses, memory details are shown via the same unified `status>` line.
417
+ - The legacy standalone `memories>` summary line is no longer emitted.
418
+ - With `--mute`, the sticky and idle rows add `muted: <names>` after `mem:` (the
419
+ spinner row doesn't). Attached, `mem:` shows the used memories and the
420
+ session's `--memory` list before the first turn records them.
421
+
422
+ Configuration:
423
+
424
+ - `SAMAGOTCHI_STATUS_LINE` (default `on`): set to `off`, `false`, or `0` to disable status-line rendering.
425
+ - `SAMAGOTCHI_STATUS_WIDTH_MODE` (default `terminal_cap`): one of `terminal_cap`, `fixed`.
426
+ - `SAMAGOTCHI_STATUS_MAX_WIDTH` (default `160`): maximum width used by `terminal_cap`.
427
+ - `SAMAGOTCHI_STATUS_FIXED_WIDTH` (default `120`): fixed width used by `fixed` mode.
428
+
429
+ Width mode behavior:
430
+
431
+ - `terminal_cap`: use `min(terminal_columns, SAMAGOTCHI_STATUS_MAX_WIDTH)`, single-line with `+N` overflow indicator.
432
+ - `fixed`: use `SAMAGOTCHI_STATUS_FIXED_WIDTH`, single-line with `+N` overflow indicator.
433
+
434
+ Notes:
435
+
436
+ - Spinner rendering remains app-managed to keep cursor cleanup deterministic.
437
+ - Raw terminal auto-wrap is intentionally avoided in the spinner region.
438
+
439
+ ## Tool Tally
440
+
441
+ A long, tool-heavy turn says what it has been doing. From the turn's 3rd tool call,
442
+ a dim row under the activity row tallies its calls, with no model call:
443
+
444
+ ```
445
+ 12 tool calls (2 failed) · execute ×7 · read_file ×3 · edit_file ×2 · last: execute command=bundle exec rspec
446
+ ```
447
+
448
+ It lists the top 3 tools by count (ties go to the tool used first), the failed calls
449
+ (a call a guardrail or an approval blocked counts as a call, not as failed) and the last
450
+ call with its parameters, cut to the terminal width. It starts over with each turn.
451
+
452
+ - Attached mode: the second row of the activity slot, shown while the slot is (the
453
+ model generating or a tool running). Joining a turn mid-way seeds it from the turn so far.
454
+ - The REPL (`--no-shared`): a row under the spinner row. The spinner stops while tools
455
+ run (the `tool>` lines show them), so the tally shows while the model generates
456
+ between tool rounds; the spinner block is one row taller from then on.
457
+ - The web: the activity panel's summary reads `activity · 12 tool calls (2 failed) · execute ×7 · …`
458
+ (without `last:`: the rows show it).
459
+
460
+ ## Thinking Spinner Sentence
461
+
462
+ While the model generates, the spinner row shows the newest complete sentence of its thinking
463
+ (`model> thinking · <sentence> |` in the REPL, `| thinking · <sentence>` in attached mode), or of its
464
+ answer (`writing ·`), like the web's thinking ticker: the same sentence rules (a list number such as
465
+ `118.` is no sentence end; a newline is one), and the row changes at most once every 1.5 s so it
466
+ doesn't flicker. A long sentence is cut with `…`; a Qwen `TURN:` prefix and inline markdown are left
467
+ out. Before the first sentence the row reads `thinking...`. With `TERM=dumb` there is no spinner row.
468
+
469
+ - When a memory entry is loaded during thinking, the spinner line also shows a compact inline preview
470
+ of that tool call (for example `tool: memory_read(name=...)`) for live visibility before end-of-turn
471
+ tool logs; the sentence gets the room left.
472
+
473
+ ## Thinking-Phase Cancellation
474
+
475
+ During assist-mode thinking (while the spinner is active), you can cancel an in-flight model request without exiting the process:
476
+
477
+ - Press `Ctrl-C` to cancel the active request.
478
+
479
+ Behavior notes:
480
+
481
+ - Cancellation returns control to the prompt immediately; what you typed there stays.
482
+ - Partial model output from the canceled request is not committed as a completed model turn.
483
+
484
+ ## Iteration Limit Behavior
485
+
486
+ - `max_iterations` remains a hard safety cap on tool-call rounds.
487
+ - Tool side effects that already ran before the cap are not rolled back.
488
+ - `Samagotchi::KernelLoop#run` now returns a resumable result object with the visible output plus the accumulated conversation.
489
+ - If the cap is reached while tool calls are still pending, the result is marked resumable so callers can continue from the saved conversation instead of restarting from scratch.
490
+ - In assist mode, the CLI then asks at the `? ` prompt, with the choices listed under it: `yes` (Enter alone, or `/continue`) resumes, `no` cancels, and `no, <explanation>` cancels while keeping the reason in conversation context. Once answered, one line stays: `? The turn ran out of iterations. Continue it? → no, too slow`.