samagotchi 0.18.1 → 0.20.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -1
  3. data/README.md +4 -3
  4. data/docs/architecture.md +21 -2
  5. data/docs/cli.md +48 -11
  6. data/docs/configuration.md +12 -5
  7. data/docs/guardrails.md +92 -14
  8. data/docs/memory.md +4 -0
  9. data/docs/plugins.md +5 -0
  10. data/docs/sessions.md +7 -5
  11. data/lib/samagotchi/bootstrap/config_writer.rb +14 -3
  12. data/lib/samagotchi/bootstrap_command.rb +26 -5
  13. data/lib/samagotchi/bridge.rb +25 -9
  14. data/lib/samagotchi/bridge_client/event_stream.rb +37 -1
  15. data/lib/samagotchi/bridge_client.rb +9 -5
  16. data/lib/samagotchi/bundle_command.rb +24 -5
  17. data/lib/samagotchi/bundles/check-in/manifest.yml +2 -2
  18. data/lib/samagotchi/bundles/check-in/plugin.rb +14 -5
  19. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +24 -5
  20. data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -4
  21. data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
  22. data/lib/samagotchi/bundles/mcp/plugin.rb +18 -4
  23. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +7 -4
  24. data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
  25. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  26. data/lib/samagotchi/bundles/system/memory_guide.md +2 -2
  27. data/lib/samagotchi/commands/registry.rb +4 -3
  28. data/lib/samagotchi/config.rb +3 -0
  29. data/lib/samagotchi/context_usage.rb +3 -3
  30. data/lib/samagotchi/engine.rb +25 -5
  31. data/lib/samagotchi/guardrail_wiring.rb +10 -3
  32. data/lib/samagotchi/guardrails/approval.rb +14 -7
  33. data/lib/samagotchi/guardrails/approvals.rb +69 -15
  34. data/lib/samagotchi/guardrails/context.rb +16 -9
  35. data/lib/samagotchi/guardrails/parent_approvals.rb +10 -4
  36. data/lib/samagotchi/guardrails/read_only_shell.rb +322 -0
  37. data/lib/samagotchi/guardrails/rm_targets.rb +86 -0
  38. data/lib/samagotchi/guardrails/rules.rb +69 -5
  39. data/lib/samagotchi/guardrails/shell_git_dirs.rb +5 -137
  40. data/lib/samagotchi/guardrails/shell_lex.rb +164 -0
  41. data/lib/samagotchi/guardrails/shell_paths.rb +145 -0
  42. data/lib/samagotchi/guardrails/targets.rb +65 -4
  43. data/lib/samagotchi/guardrails.rb +2 -0
  44. data/lib/samagotchi/hooks/registry.rb +6 -2
  45. data/lib/samagotchi/host_registry.rb +22 -4
  46. data/lib/samagotchi/idle_recap.rb +7 -2
  47. data/lib/samagotchi/idle_scheduler.rb +1 -1
  48. data/lib/samagotchi/installed_versions.rb +91 -0
  49. data/lib/samagotchi/live_versions.rb +5 -3
  50. data/lib/samagotchi/llm/usage.rb +1 -1
  51. data/lib/samagotchi/memory_bundle/builder.rb +25 -6
  52. data/lib/samagotchi/memory_bundle/index_sync.rb +1 -9
  53. data/lib/samagotchi/memory_bundle/installer.rb +79 -13
  54. data/lib/samagotchi/memory_bundle/shipped_update.rb +3 -1
  55. data/lib/samagotchi/memory_bundle/status.rb +17 -5
  56. data/lib/samagotchi/memory_bundle/uninstaller.rb +7 -7
  57. data/lib/samagotchi/memory_paths.rb +6 -0
  58. data/lib/samagotchi/model_overlay.rb +39 -0
  59. data/lib/samagotchi/model_profile.rb +4 -0
  60. data/lib/samagotchi/relay_desk.rb +9 -0
  61. data/lib/samagotchi/self_report.rb +38 -2
  62. data/lib/samagotchi/session.rb +35 -6
  63. data/lib/samagotchi/session_commands.rb +13 -2
  64. data/lib/samagotchi/session_manager.rb +166 -11
  65. data/lib/samagotchi/session_metrics.rb +260 -32
  66. data/lib/samagotchi/sessions_command.rb +43 -4
  67. data/lib/samagotchi/system_prompt.rb +10 -1
  68. data/lib/samagotchi/terminal_ui/attached_loop.rb +48 -3
  69. data/lib/samagotchi/terminal_ui/formatting.rb +52 -1
  70. data/lib/samagotchi/terminal_ui/image_input.rb +11 -4
  71. data/lib/samagotchi/terminal_ui/version_lines.rb +48 -0
  72. data/lib/samagotchi/token_usage.rb +57 -41
  73. data/lib/samagotchi/tool_runner.rb +21 -2
  74. data/lib/samagotchi/tools/builtins.rb +21 -5
  75. data/lib/samagotchi/tools/execute.rb +9 -8
  76. data/lib/samagotchi/turn_note.rb +3 -2
  77. data/lib/samagotchi/update_command.rb +33 -12
  78. data/lib/samagotchi/version.rb +1 -1
  79. data/lib/samagotchi/web/app.rb +83 -11
  80. data/lib/samagotchi/web/public/app.js +135 -43
  81. data/lib/samagotchi/web/public/card.js +34 -2
  82. data/lib/samagotchi/web/public/ctx.js +59 -0
  83. data/lib/samagotchi/web/public/data.js +6 -0
  84. data/lib/samagotchi/web/public/format.js +15 -2
  85. data/lib/samagotchi/web/public/index.html +37 -2
  86. data/lib/samagotchi/web/public/model_pick.js +6 -2
  87. data/lib/samagotchi/web/public/model_picker.js +2 -1
  88. data/lib/samagotchi/web/public/question_card.js +14 -1
  89. data/lib/samagotchi/web/public/question_cards.js +17 -11
  90. data/lib/samagotchi/web/public/scope.js +15 -6
  91. data/lib/samagotchi/web/public/session_state.js +4 -0
  92. data/lib/samagotchi/web/public/timing.js +6 -2
  93. data/lib/samagotchi/web/public/turn_events.js +108 -4
  94. data/lib/samagotchi/web/public/update_notice.js +87 -14
  95. data/lib/samagotchi/web/server.rb +28 -1
  96. data/lib/samagotchi/web/session_hub.rb +30 -3
  97. data/lib/samagotchi/web/session_summary.rb +11 -2
  98. data/lib/samagotchi/worker.rb +25 -8
  99. data/lib/samagotchi/worker_idle_exit.rb +31 -2
  100. data/lib/samagotchi/worker_sidecar.rb +7 -5
  101. metadata +7 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34f4c3e1960aa23902e27160bd1f325ba035731c283a93b2cd819676481c90a2
4
- data.tar.gz: 708e58e5bfc26d10fe9f20bd1b696f6355e2a25749a36ca6503e82ca9077fa64
3
+ metadata.gz: c3b1a6860a8c22ed75259438c755633283385c12ffbc28a8fcb4c817f59114c5
4
+ data.tar.gz: 24f7c45ebe336077bc2d9a1e670d3313b364c48d5b28aef3624593705236f29e
5
5
  SHA512:
6
- metadata.gz: 56c634e50c34f3fd027ca9ed7208d7edaab21a639050fe321c1d9fa5e8a0e818a31ae6c6351f8c21c0417984f3235a1a44b14aaa3d415b392a85d3bff2916aa1
7
- data.tar.gz: 41862c98747027f791e4aa59b4c6d2eda092d818e3492212fbc79a24970c74734c3196c480511876aa97b95a8e1245c8bfd6fceccb73d47ade67d3f96ced89fa
6
+ metadata.gz: 9f1d9dec24344b223170728f80629a76d1db18697f752b2ce7135cf3569428a902f1cc18589a928184f7809bcbe3a20289281cd4cae6324bdd3f5bb1192cf860
7
+ data.tar.gz: 8bf9a7cf6dbaac98a7583987be61d98e06ddd3ab6a0b6f36423ae41a6aa530ef42479fa28139ad89b8253e61edce1a57013dd27d0a9bcbf5b31c080cea6f8801
data/CHANGELOG.md CHANGED
@@ -8,6 +8,170 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.20.0] - 2026-10-03
12
+
13
+ ### Added
14
+
15
+ - `guardrails.mode` (config.yml only): `auto`, the new default, or `strict`. Rules can be tagged `modes: [strict]`
16
+ to vote only in strict mode; a rule without `modes:` (every rule you wrote in config.yml) votes in both.
17
+ `/guardrails` shows the mode and marks the rules it leaves out `(strict only)`. See docs/guardrails.md#modes.
18
+ - New guardrail rule keys: `skip_read_only: true` (a shell command that only reads, such as `ls`, `cat`, `rg`,
19
+ `sed -n` or `git log`, doesn't match), `touches: chi_dirs` (a shell command names a path in chi's real config,
20
+ hooks, approvals or bundles dir, or a `.git/hooks`) and `rm: outside_tmp` (an `rm -rf` reaches outside the tmp
21
+ dirs). A config.yml rule that uses one fails closed on an older chi.
22
+ - Model speed and token stats. `/stats` has a speed line (the last generation's decode speed and the
23
+ session's average: exact from llama.cpp, estimated and marked `~` elsewhere), cached and reasoning
24
+ tokens, and the cost when the provider reports it (OpenRouter). The web's info bar shows the speed
25
+ next to `ctx`, updated after each generation, and the `ctx` tooltip there and on session cards lists
26
+ tokens in / cached / out (+ reasoning) and the cost, for that session only.
27
+
28
+ ### Changed
29
+
30
+ - [BEHAVIOUR] Guardrails ask less by default (auto mode). The guardrails bundle (0.5.0) no longer asks before
31
+ `git rebase`, writes outside the session's repo (`write-outside-repo`) or git in another checkout
32
+ (`git-outside-repo`); `guardrails: { mode: strict }` in config.yml brings those back. Everything else still asks
33
+ in both modes, as do the small-model rules and chi's own protected paths.
34
+ - [BEHAVIOUR] `shell-touches-chi` matches chi's real folders instead of text, and lets read-only commands through:
35
+ `ls ~/.config/samagotchi`, `rg samagotchi/hooks lib` and a scratch copy under `/tmp` no longer ask; `echo x >>
36
+ <config dir>/config.yml` and `cp hook .git/hooks/pre-commit` still do. A word it can't resolve falls back to the
37
+ old text match.
38
+ - [BEHAVIOUR] `rm-rf-wide` no longer asks when every `rm -rf` target is inside a tmp dir (`rm -rf /tmp/x`);
39
+ `/tmp` itself, `/tmp/*`, a `$VAR` and a link out of tmp still ask.
40
+ - [BEHAVIOUR] "Allow … in this repo" approvals hold for the whole repository, in every worktree of it, and the
41
+ ask names the repository (`in this repo (samagotchi)`) instead of the worktree folder. Approvals stored
42
+ before keep matching their exact folder.
43
+ - `chi bundle status` shows a bundle whose recorded scope this chi doesn't know (one a newer chi wrote, or a hand
44
+ edit) as `scope=team (unknown)`, counted as one issue, instead of checking its files in the system memories;
45
+ the shipped-bundle update skips it (upgrade chi or reinstall), and `chi bundle diff` refuses it with a one-line
46
+ error instead of a stack trace.
47
+ - The web model picker shows a model's configured sampling (`temperature=0.6 (hosts.work)`) as its row's tooltip;
48
+ `GET /api/models` has it as `sampling` on the models that have some.
49
+ - The web shows a plugin card that is only a notice (info level, no buttons, one short line, such as check-in's
50
+ "Nudged the model at 105 tool calls.") as a one-line row, `▸ check-in: Nudged …`, that opens on a click, like the
51
+ tool rows and resolved questions; warn cards, cards with buttons and longer ones stay framed cards.
52
+ - On a phone (a window under 900 px wide) the sessions strip's `hide` is in the top bar, where the `▾ N sessions`
53
+ pill comes back, with a finger-sized target, instead of the last column of the sideways-scrolling strip.
54
+ - On a phone the web info bar shows `mem N` (the memories the session used; their names in the tooltip) after the
55
+ session's status, since the `mem: …` line is hidden there.
56
+ - The web info bar's chips (model, ctx, speed, session time) are tinted over the card's glass instead of solid, and
57
+ the empty-history cover is see-through like the session panel.
58
+
59
+ ### Fixed
60
+
61
+ - An MCP server that exits without a wait status no longer raises `NoMethodError`; its waiting calls fail with
62
+ "the server exited" instead of hanging.
63
+ - A hook that raises is now logged (`hook_failed`) instead of being swallowed silently; the turn still goes on.
64
+ - A hook's card (check-in, skills) now prints under its tool call's row: `tool_call_completed` is emitted before
65
+ `after_tool_call` fires.
66
+ - When a hook corrects a tool call (known-names), the model is told what actually ran with a leading
67
+ `ran as: <tool> <args>` line.
68
+ - MCP image content is described with a neutral `[image N: …]` line instead of claiming it was attached, which
69
+ was wrong for a model that can't see images.
70
+ - An MCP `resource` block carrying an image `blob` is now attached like an `image` block instead of being
71
+ dropped to its URI text.
72
+ - check-in's `nudge` mode no longer says "nudged" when the steer was dropped later; it says "nudge not sent" at
73
+ the turn's end.
74
+ - A session started with an annotation (a web quote) is previewed by the note typed under the quote, not
75
+ `From your thinking: > …`; a quote with no note previews by its words.
76
+ - A session that has only context notes so far (`chi note`, `send_note`) is previewed on its card and in the
77
+ lists as `note: …` instead of `—`, until a message is typed.
78
+ - A web turn of images alone names each image once in the transcript a recap is written from, not twice.
79
+ - `chi bootstrap` no longer says "loading the model?" for a slow test on a remote provider (OpenRouter and the
80
+ like); the hint is for a local server, where a slow answer is a model loading.
81
+ - `chi bootstrap` says why it saved a host as `name-2` when the derived name is already taken (or a model id
82
+ starts with it), instead of silently picking another name.
83
+ - `chi bundle upgrade` reports a hook or plugin file that is byte-identical to the installed one as "already up
84
+ to date" instead of "Updated".
85
+ - `chi bundle upgrade --agent` from a git/zip/tar source no longer hands the agent an `incoming:` path that was
86
+ already deleted: the extracted source is kept until the agent step is done, then cleaned up.
87
+ - `chi self` now checks a local `api: openai` host's reachability (a `GET <base>/models` with the same short
88
+ probe timeouts) and reports up/down with the ids it serves, instead of always saying "reported per turn".
89
+
90
+ Update with `chi update`: guardrails moves to 0.5.0 (auto mode and the new rule keys; it needs chi 0.20.0), mcp to 0.4.1 (exit status, image lines, image blobs) and check-in to 0.2.1 (a dropped nudge says so). Restart `chi web` and running sessions afterwards (`chi sessions restart ID`).
91
+
92
+ ## [0.19.0] - 2026-10-03
93
+
94
+ ### Changed
95
+
96
+ - A new or woken session worker runs the newest chi installed, whoever starts it: a `chi web`, `chi send` or
97
+ terminal left on an older chi no longer starts workers on its own version. A worker already running keeps its
98
+ version until it is restarted.
99
+
100
+ ### Added
101
+
102
+ - `chi web` notices a newer chi installed (`chi update`, `gem update`, a `git pull` in a checkout) and says to
103
+ restart it, in its terminal and once in each open page.
104
+ - The web's info bar marks a session whose worker runs an older chi than the newest installed, with a Restart button
105
+ (or, for a worker from before restarts, the `chi sessions stop ID` to run); the page and the draft in the
106
+ composer stay as they are and follow the new worker.
107
+ - An attached terminal says in one line when the session's worker or the terminal itself runs an older chi than
108
+ the newest installed, and how to move each (`chi sessions restart ID`; `/detach`, then `chi --attach ID`). When
109
+ the worker restarts, the terminal stays attached and says so.
110
+ - `chi sessions restart ID...` moves a running session to a new worker on the newest chi installed, keeping its
111
+ attached terminal and web tabs on it; it is refused, with the reason, while a turn, a question, reminders or
112
+ background tasks would be lost.
113
+ - A bundle can ship per-model memory overlays (`tips.<model-key>.md` beside `tips.md`): they load only for that
114
+ model, as a `memory_write current_model_only` overlay does, and `chi bundle build tips.md` brings them along.
115
+
116
+ ### Fixed
117
+
118
+ - `chi update` no longer says running sessions move to the new chi "at idle exit (30 min)": a worker keeps its chi
119
+ until restarted, and idles out only with nothing attached, after `session.idle_exit_minutes` (never with 0). The
120
+ workers row names `chi sessions restart ID` (or `stop` for a worker from before restarts).
121
+ - `chi web` no longer leaves exited session workers behind as zombie processes.
122
+ - The web's model picker lists a host added to (or changed in) `hosts:` while `chi web` runs, instead of the hosts
123
+ it started with.
124
+ - A bundle's model overlay no longer gets an `index.md` line of its own after `chi bundle install`, which showed it
125
+ to every model as a memory to read; `chi bundle status` reads it `ok (model overlay)`, uninstall no longer re-adds
126
+ its line, and an overlay whose base is nowhere, or only among your installed memories, installs with a warning. A
127
+ line an earlier install wrote goes with the bundle's next upgrade.
128
+ - source-links (0.3.2): a `case_insensitive: true` source no longer lists a ref in its `sources:` note when the ref
129
+ is a markdown link's label whose target names it in another case (`[jira-1](…/JIRA-1)`). Run `chi update` to get it.
130
+ - An `@path` image in a prompt takes backslash escapes, as a Finder drag into the terminal types them:
131
+ `@my\ shot.png` and `@a\ \(1\).png` attach the file instead of stopping at the first space.
132
+ - An `http` host named by a public name (`gpu.example.com`) counts as a remote provider (120-second first-token
133
+ limit, no llama.cpp `/props`), as a public IP address already did. Names without a dot and local suffixes
134
+ (`.local`, `.lan`, `.home.arpa`, `.internal`, `.ts.net`, `fritz.box`, …) stay local; `hosts.<name>.remote` decides
135
+ either way.
136
+ - `chi web` no longer logs `ERROR HTTPRequest#fixup: WEBrick::HTTPStatus::LengthRequired occurred.` after a POST
137
+ without a body.
138
+ - The web's delete confirm no longer promises a running worker from a stale list: it says the worker is stopped
139
+ first if it is still running.
140
+ - The web's scope links (`all`, `← project`) keep the page's `?view=stage|turn`.
141
+ - An approval card's verdict (`Allowed (scope)` / `Denied`) reads above the command it decided, so it no longer
142
+ looks like part of the tool call when the card's details are expanded.
143
+ - Answering or dismissing a question no longer says "Session is not running" when the worker is live but failed the
144
+ request: the server answers 502 with the worker's detail and the card shows it. 503 stays for a session with no
145
+ live worker.
146
+ - A turn that failed to send no longer leaves its prompt waiting to be restored into the composer later; and text
147
+ typed while a fast-failed turn's prompt was coming back is kept, with the restored prompt going under it.
148
+ - A worker that dies between a turn's end and its after-turn display no longer leaves the turn's answer bubble
149
+ waiting: the page stops waiting when the worker goes away, or after 20 s.
150
+ - A first web message the page answers itself (`/stats`, `/exit` …) or that is a typo of a command (`/modle`) no
151
+ longer leaves an empty session behind.
152
+ - A question card reopened after a reload shows its result once, in the collapsed summary, not twice.
153
+ - After a mid-turn reload, this tab's own prompt is still recognized as its own instead of being labelled `web`.
154
+ - A `chi scratch` session no longer offers `send_note`, so it leaves no note behind in another session.
155
+ - A `chi scratch` session's tool approvals offer only "Allow once" and "for the session", and keep a session
156
+ approval in memory: no "in this repo" or rule approval from it stays in the approvals store.
157
+ - A message sent (web, `chi send`, a delegate) to a session whose worker is slow to start no longer starts a second
158
+ worker beside it.
159
+ - The note a cancelled turn leaves for the model says "the reply above ends where it was cut off", not "the
160
+ answer", so narration cut off before a tool call isn't taken for an answer.
161
+ - A session worker that wakes again shows the model the server last said it served (`/stats`, the status row,
162
+ the web's info bar) right away, instead of none until its first generation.
163
+ - A tool call still running when its turn is cancelled (or fails) is saved in the session's analytics as
164
+ `canceled`, so the session's tool totals (`/stats`) count it as the turn did instead of dropping it.
165
+ - A session's `analytics.json` `session_duration_ms` runs from the session's `started_at`, not from when its current
166
+ worker started.
167
+ - A session started with `chi --model X` no longer hands X to the commands it runs as the default model: a `chi
168
+ self` (or other chi) run there reports and uses the configured default, with X as this session's model. A
169
+ `SAMAGOTCHI_DEFAULT_MODEL` you export yourself still passes.
170
+ - A preloaded memory that can't be loaded is warned about once, not again each time the prompt is rebuilt (another
171
+ thinking level, a `/model` switch).
172
+
173
+ Update with `chi update`: source-links moves to 0.3.2 (case-insensitive link targets). Restart `chi web` afterwards; from now on it tells you when a newer chi is installed.
174
+
11
175
  ## [0.18.1] - 2026-10-03
12
176
 
13
177
  ### Fixed
@@ -1035,7 +1199,9 @@ and long-lived sessions.
1035
1199
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
1036
1200
  hotkey panel that send selected text or the clipboard to your sessions.
1037
1201
 
1038
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.18.1...HEAD
1202
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.20.0...HEAD
1203
+ [0.20.0]: https://github.com/dm1try/samagotchi/compare/v0.19.0...v0.20.0
1204
+ [0.19.0]: https://github.com/dm1try/samagotchi/compare/v0.18.1...v0.19.0
1039
1205
  [0.18.1]: https://github.com/dm1try/samagotchi/compare/v0.18.0...v0.18.1
1040
1206
  [0.18.0]: https://github.com/dm1try/samagotchi/compare/v0.17.0...v0.18.0
1041
1207
  [0.17.0]: https://github.com/dm1try/samagotchi/compare/v0.16.0...v0.17.0
data/README.md CHANGED
@@ -54,9 +54,10 @@ chi update # the gem, then the bundles and the desktop helper
54
54
  stay installed: running sessions still use them), then brings the rest up to
55
55
  the new version: the system bundle, the bundles chi ships that you installed,
56
56
  and the macOS helper (rebuilt only when its sources changed). Memory files you
57
- edited are kept, and the table says which. Running sessions move to the new
58
- chi when their worker idles out (30 min) or on `chi sessions stop ID`; a
59
- running `chi web` needs a restart. From a checkout, `git pull` instead. See
57
+ edited are kept, and the table says which. Running sessions keep their chi
58
+ until restarted (`chi sessions restart ID`, or the Restart badge on the web);
59
+ new and woken ones start on the new chi. A running `chi web` needs a restart,
60
+ and its page says so. From a checkout, `git pull` instead. See
60
61
  [CLI: Updating](docs/cli.md#updating).
61
62
 
62
63
  ## Set up
data/docs/architecture.md CHANGED
@@ -213,6 +213,10 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
213
213
  too-old reconnects, and cursors from another worker's epoch, receive a `reset` marker carrying
214
214
  `session_state_snapshot`. `?snapshot=1` joins with a snapshot frame instead of a replay;
215
215
  `?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`).
216
+ An attached terminal's stream (`EventStream` with `rediscover:`) looks for the session's live
217
+ worker whenever it drops: a new one (a restart) is followed from its snapshot, and the terminal
218
+ sends its requests there (`worker_changed`); it gives up after ~12 s, or at once when the session
219
+ was stopped.
216
220
  - `POST /session/:id/turn` — fire-and-forget turn creation; returns `202` with an `enqueued_id`
217
221
  (delivery is at-least-once via the worker's file-IPC input path — it never calls `run_turn`
218
222
  across the HTTP boundary). Only the bridge's own session: another id, in the path or the body's
@@ -270,6 +274,12 @@ The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` fr
270
274
  newest turn the worker finished and any it hasn't saved yet (at most 20 turns), with their tool
271
275
  calls and the running turn's finished ones. The full history is the session's `analytics.json`,
272
276
  which `SessionMetrics#persist` rewrites after each turn; the web server merges the two by id.
277
+ The totals' `tokens` block adds the cached and reasoning sums, the cost, the decode time and tokens
278
+ behind the average speed (`avg_decode_tps`) and the last speeds (`last_decode_tps`,
279
+ `last_prefill_tps`, `tps_source`: `server` or `estimate`). Each `generation_completed` event
280
+ carries the generation's `speed` (`{decode_tps, source}`, null without one) and those running
281
+ `tokens`: the Engine closes the generation in `SessionMetrics#finish_generation` before relaying
282
+ it, so the web's info bar updates per generation without parsing chunk payloads.
273
283
 
274
284
  The page's frequent reads of `GET /api/sessions/:id` stay light too:
275
285
  - `?tail=1&recent=1` (each turn's end, a cancel, an answer display): `{tail, session: {id, status,
@@ -302,7 +312,13 @@ the server, no daemon) that keeps an in-memory projection of the session list an
302
312
  changes to every open tab over `GET /api/events` (SSE: a `snapshot` frame on every connect,
303
313
  then `session` for an upsert and `session_gone` for a removal, `: ping` while idle, no replay).
304
314
  The snapshot names chi's version: a tab served by another one (its `<body data-version>`) offers a
305
- reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade.
315
+ reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade. It also names
316
+ `installed`, the newest chi on this machine (`InstalledVersions`: the highest `samagotchi-<v>.gemspec`
317
+ in RubyGems' specification dirs, or a checkout's `version.rb` on disk; looked at again on each full
318
+ probe, every 10 s), and a change is a `chi` frame `{version, installed}`. A newer one than the
319
+ server runs is said in chi web's terminal and on the page: restart chi web (its workers already
320
+ run the newest chi, [Sessions](sessions.md)). `GET /api/info` carries `installed` too, and the
321
+ feature `newest_workers`.
306
322
  Files stay the source of truth and workers don't know the hub. Its watcher is a 1 s tick that
307
323
  stats the sessions dir (every session writer goes tmp + rename, which bumps the dir's mtime) and
308
324
  each `<id>/` folder (recap.json, bridge.json, the stopped and archived markers), re-parsing only the files whose mtime or size
@@ -310,7 +326,10 @@ moved through `Session.summary_from_file`. Liveness is probed, since a killed ow
310
326
  file trace: the owner lock every tick for the sessions the projection believes owned, and every
311
327
  session every 10 s, so a `kill -9` shows within a second. The summary (`Web::SessionSummary`,
312
328
  shared with `/api/sessions` and the session view) carries `owner`, `project_root` and
313
- `bridge_up` (the sidecar is there *and* the lock is held: the page attaches its stream on it).
329
+ `bridge_up` (the sidecar is there *and* the lock is held: the page attaches its stream on it), and
330
+ from that sidecar `worker_version` (the chi the worker runs) and `worker_features`
331
+ (`Bridge::FEATURES`, what a client may ask of it beyond the routes). A worker's Bridge snapshot
332
+ names its `chi_version` too.
314
333
  `POST/DELETE /api/sessions` and `/stop` rescan the session before answering (`SessionHub#touch`).
315
334
  `GET /api/sessions` is the hub's projection too (its sort, paging and total). `Server` always builds
316
335
  a hub; an `App.new` without one answers both routes `503 no_hub`.
data/docs/cli.md CHANGED
@@ -15,7 +15,7 @@
15
15
  - `chi web --web-host lan` — the Web UI on your home network too, for your phone: a link with an access token and its QR code (see [chi web on your phone](#chi-web-on-your-phone)); `chi web --new-token` replaces the token
16
16
  - `chi web --web-markdown` — opt in to sanitized Markdown rendering for completed assistant messages
17
17
  - `chi web --web-view turn` — draw turns with the turn view (each turn as one block of steps, the running one at the bottom of the history) instead of the default stage view (the running turn pinned above the composer); `?view=stage|turn` on the page URL overrides it (see [Web views](#web-views))
18
- - `chi sessions list|stop|archive|unarchive|delete|prune|clean` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)). A usage error (an unknown subcommand or flag, a flag missing its value, `stop` or `delete` with no ids, a bad `list --format` or `--scope`) exits 2; an unknown or refused session exits 1
18
+ - `chi sessions list|stop|restart|archive|unarchive|delete|prune|clean` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)). A usage error (an unknown subcommand or flag, a flag missing its value, `stop`, `restart` or `delete` with no ids, a bad `list --format` or `--scope`) exits 2; an unknown or refused session exits 1
19
19
  - `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))
20
20
  - `chi send [-m TEXT] [--image PATH]... (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, and `--image` attaches images (see [Sessions: Sending a message](sessions.md#sending-a-message)); `--new` starts a session with it instead, and `--wait` prints the answer (`--wait ID` with no message waits for the next reply without sending; `--format json` prints one JSON object instead; exit 3 a question waits, its options on stderr; exit 4 `--timeout` passed with the turn still running; see [Starting a session](sessions.md#starting-a-session))
21
21
  - `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`). An approval can be denied, not allowed, unless `guardrails.parent_approvals: once` (a convention, not a security boundary; see [Guardrails](guardrails.md#approvals-from-a-parent-agent))
@@ -53,7 +53,9 @@ chi bootstrap # try localhost 8080, 11434, 1234, 8000
53
53
  - **The file.** With no config.yml it writes a small commented one:
54
54
  `default.model` as `<host>:<model>` and one `hosts:` entry named `local`
55
55
  (localhost), `lan` (an IP) or after the domain (`openrouter`); `--name`
56
- sets it. An existing file is left as it is apart from the new entry, added
56
+ sets it. When that name is taken (or a model id starts with it), chi picks
57
+ `name-2` and says why, with `--name` to choose another. An existing file is
58
+ left as it is apart from the new entry, added
57
59
  at the end of its `hosts:` block (it gets a `hosts:` block, with a
58
60
  `default` entry for its `server:` first, when it has none), after a backup
59
61
  to `config.yml.bak-<time>`. `default.model` is set only when the file has
@@ -104,17 +106,19 @@ known-names 0.1.0 0.1.1 updated
104
106
  core 0.1.0 0.2.0 updated (+ new-bundle)
105
107
  infra_tools 1.0.0 skipped (not from chi)
106
108
  Chi Helper 0.2.0 up to date (launch file refreshed)
107
- workers 2 live on 0.2.0: they move to 0.3.0 at idle exit (30 min) or chi sessions stop 2ea8c1f0 91b0d2aa
109
+ workers 2 live on 0.2.0: they keep it until restarted (chi sessions restart 2ea8c1f0 91b0d2aa); or they idle out after 30 min with nothing attached
108
110
  Also shipped, not installed: dev (mcp, skills, source-links) (chi bundle install NAME)
109
111
  done
110
112
  ```
111
113
 
112
114
  - **The gem.** It asks rubygems.org for the newest samagotchi (5 s timeout)
113
115
  and, when that's newer, runs `gem install samagotchi` with the gem command
114
- of the Ruby chi runs on (the real one, not a mise/rbenv/asdf shim). Then it
116
+ of the Ruby chi runs on (the real one, not a mise/rbenv/asdf shim; a chi
117
+ installed under another Ruby is a separate install it doesn't touch). Then it
115
118
  hands over to the new chi, which does the rest and prints the table. Old
116
- versions stay installed: running workers and an old `chi web` still use
117
- them (so don't `gem cleanup` while they run). Offline, the row says
119
+ versions stay installed: running workers and an old `chi web` still load
120
+ files from them, so a `gem cleanup` while they run breaks them (a
121
+ `LoadError`, missing web assets); restart them first. Offline, the row says
118
122
  "couldn't check" and the rest still runs; a failed install fails the row
119
123
  and the rest runs on the current version. Under Bundler (`bundle exec`)
120
124
  the row says `bundle update samagotchi` instead.
@@ -137,9 +141,15 @@ done
137
141
  - **The desktop helper** (macOS) is rebuilt and restarted only when its Swift
138
142
  sources changed (or the Ruby it runs moved); otherwise only its launch file
139
143
  is refreshed. See [Desktop helper](desktop.md).
140
- - **Running processes** are reported, never stopped: live workers on another
141
- version, and a `chi web` on `web.port` running an older chi (sessions it
142
- starts run that version too: restart it).
144
+ - **Running processes** are reported, never stopped. A live worker keeps its
145
+ chi until it is restarted: `chi sessions restart ID` (one from before
146
+ restarts: `chi sessions stop ID`, and the next prompt starts it on the new
147
+ chi); it exits on its own only when nothing is attached (no web tab, no
148
+ terminal, no reminder) and `session.idle_exit_minutes` pass (never with 0).
149
+ A new or woken worker always starts on the newest chi installed. A `chi web`
150
+ on `web.port` running an older chi needs a restart (Ctrl-C, then `chi web`):
151
+ its page says so too. One from before workers started on the newest chi
152
+ also starts its new sessions on its own version until then (the row says so).
143
153
 
144
154
  `--dry-run` shows the table with "would update" and changes nothing. It is
145
155
  this version's view: bundles that only a newer gem ships newer show up once
@@ -251,8 +261,12 @@ and `--shared` are refused. Its first line says it is a scratch session.
251
261
  answers "scratch session: nothing is saved", and `write`/`edit` into the
252
262
  memories folder are denied (a guardrail, rule `scratch-session`). `execute`
253
263
  can still write files anywhere, memories included.
264
+ - An approval offers only "Allow once" and "for the session", and a session
265
+ approval is kept in memory, not in the approvals store; approvals stored
266
+ before still apply.
254
267
  - No child sessions: the `delegate` tools are not offered, and a plugin's
255
268
  `ctx.sessions.fork` (btw's side session) refuses, since they would outlive it.
269
+ Nor is `send_note`, whose note would stay in the other session.
256
270
 
257
271
  ### Sharing a session
258
272
 
@@ -320,6 +334,20 @@ a `chi --resume ID` after it starts a fresh one. A worker still running an
320
334
  older chi (from before an upgrade) takes turns but not commands; the attached
321
335
  terminal and the Web UI say so, with that restart line.
322
336
 
337
+ `chi sessions restart ID...` hands each session to a new worker on the newest
338
+ chi installed, without stopping it: an attached terminal and the web's tabs
339
+ move to the new worker. It is refused, with the reason, while something would
340
+ be lost: a turn running or queued, a question or approval waiting, reminders,
341
+ a `/btw` still running, an approval relayed from a delegate, or background
342
+ tasks the session started. A session with no running worker needs none (its
343
+ next prompt starts one on the newest chi), and a worker from before restarts
344
+ needs `chi sessions stop ID`. The Web UI's `POST /api/sessions/:id/restart`
345
+ does the same (409 with `reason` and `detail` when it can't). An attached
346
+ terminal says at the join, in one `chi>` line, when the worker or the terminal
347
+ runs an older chi than the newest installed (the worker moves with `chi
348
+ sessions restart ID`, the terminal with `/detach` and `chi --attach ID`), and
349
+ `chi> the session's worker restarted on chi X` when its worker was replaced.
350
+
323
351
  `chi sessions archive ID...` hides sessions from every list (the terminal's,
324
352
  the web's, `list_sessions`) and keeps them for good: the retention sweep never
325
353
  deletes an archived session, nor counts it. Its delegates go with it. A live
@@ -387,8 +415,17 @@ file and index line stay as they are; only this session doesn't see it.
387
415
  `/help` lists the commands this session takes, the installed bundles' too, each
388
416
  with a line on what it does. `/stats` shows the session's numbers: turns, tool
389
417
  calls (by tool, with errors), iterations, tokens in/out summed over every
390
- request, generation latency, cancellations, retries, context used and window,
391
- the prompt profile and the model the server says it ran. `/recap` is in
418
+ request (with the cached and reasoning tokens when the server reports them),
419
+ the decode speed, generation latency, cancellations, retries, context used
420
+ and window, the prompt profile and the model the server says it ran, and the
421
+ cost when the provider reports one (OpenRouter's `usage.cost`; no prices are
422
+ guessed). The speed line is the last generation's and the session's average
423
+ (weighted by tokens): llama.cpp reports it exactly (`87 tok/s out, 1.9k tok/s
424
+ prompt (last, server)`); for other servers it is estimated from the first
425
+ streamed chunk to the end and marked `~` (`~64 tok/s out (last, estimate)`).
426
+ Generations under 8 tokens or 100 ms get no speed of their own, nor does a
427
+ reply without server token counts or a non-streamed one. A delegated child
428
+ keeps its own numbers: a parent's tokens and cost leave them out. `/recap` is in
392
429
  [Session recap](#session-recap), `/model` and `/models` in [Runtime Model
393
430
  Switch](#runtime-model-switch-assist-mode).
394
431
 
@@ -94,8 +94,9 @@ Behavior:
94
94
  - An edit to the file needs no restart of `chi web`: a new session's worker
95
95
  reads the file when it starts, and a running chi takes a changed value the
96
96
  next time it reads that setting (a worker keeps `hosts:` and what it set up
97
- at its start until it is stopped). A worker gets the CLI flags of the chi
98
- that started it through its environment.
97
+ at its start until it is stopped; the web's model picker lists a `hosts:`
98
+ edit at once). A worker gets the CLI flags of the chi that started it
99
+ through its environment.
99
100
  - Workers inherit hosts via `SAMAGOTCHI_HOSTS_JSON` propagated through `SessionManager.spawn_options`.
100
101
 
101
102
  This lets you run `chi` without repeating common defaults such as model
@@ -235,11 +236,16 @@ hosts:
235
236
  **Remote or local.** chi treats a host as a remote provider by its address, not
236
237
  by its key: an `https` url, or an `http` IP address outside the loopback,
237
238
  private (`10.*`, `172.16–31.*`, `192.168.*`, IPv6 `fc00::/7`), link-local and
238
- CGNAT/Tailscale (`100.64.0.0/10`) nets. An `http` host given by a name
239
- (`localhost`, `box`, `mac.local`) is local: chi doesn't look names up. A remote
239
+ CGNAT/Tailscale (`100.64.0.0/10`) nets, or an `http` name with a dot that
240
+ doesn't end in a local suffix (`gpu.example.com`). A name without a dot (`box`)
241
+ or ending in `localhost`, `.local`, `.lan`, `.home`, `.home.arpa`, `.internal`,
242
+ `.intranet`, `.localdomain`, `.private`, `.corp`, `.test`, `.box` (`fritz.box`)
243
+ or `.ts.net` (Tailscale) is local: chi doesn't look names up. A remote
240
244
  host keeps its model list for 10 minutes, gets a 120-second first-token limit,
241
245
  and isn't asked for llama.cpp's `/props` (its context window and served model
242
- come from its model list and each turn). `remote: true` or `remote: false` on a
246
+ come from its model list and each turn). A local `api: openai` host has no
247
+ `/props`, so `chi self` GETs its `/models` with the same short probe timeouts
248
+ and reports up/down with the ids it serves. `remote: true` or `remote: false` on a
243
249
  host decides it instead, e.g. for a llama.cpp behind an https proxy on the LAN:
244
250
 
245
251
  ```yaml
@@ -848,6 +854,7 @@ described in their own sections.
848
854
  | `image.max_bytes` | `3750000` | | See "Images". |
849
855
  | `image.max_per_request` | `20` | | See "Images". |
850
856
  | `guardrails.enabled` | `true` | | `false`: no rules, and hooks' asks are dropped (a deny still applies). config.yml only: a worker unsets any `SAMAGOTCHI_GUARDRAILS_*` it inherits. See [Guardrails](guardrails.md). |
857
+ | `guardrails.mode` | `auto` | | Which rules vote: `auto` leaves out the rules tagged `modes: [strict]` (in the guardrails bundle: `git rebase`, writes and git outside the session's repo); `strict` runs them all. An unknown value warns and is `auto`. config.yml only. See [Guardrails](guardrails.md#modes). |
851
858
  | `guardrails.small_models` | `auto` | | Which models get the `models: small` rules: `auto` (32B or less by the size in the name, an MoE's active size), a list of globs, or `[]`. config.yml only. See [Guardrails](guardrails.md#rules-for-some-models). |
852
859
  | `guardrails.parent_approvals` | `off` | | What `chi answer` lets a parent agent allow on an approval: `off` (deny only) or `once` ("Allow once", never a wider scope). config.yml only: no environment variable (a parent can still pick the config dir with `XDG_CONFIG_HOME`). A convention for an honest parent, not a security boundary. See [Guardrails](guardrails.md#approvals-from-a-parent-agent). |
853
860
  | `log.file` | state dir | yes | See "Debug Log File". |
data/docs/guardrails.md CHANGED
@@ -81,6 +81,13 @@ Allowing beyond "once" is stored in `$XDG_STATE_HOME/samagotchi/guardrails/appro
81
81
  | repo | this exact call in this repo (the cwd outside a repo), any session |
82
82
  | rule | anything this rule asks about in this repo |
83
83
 
84
+ "This repo" is the git repository, not the folder: an approval given in one
85
+ worktree holds in the main checkout and every other worktree of it (it is
86
+ stored with the repository's common git dir as `repo`, and the worktree as
87
+ `repo_root`). The ask names the repository (`Allow this call in this repo
88
+ (samagotchi)`). An entry stored before chi 0.20 has no `repo` and still only
89
+ matches its exact folder.
90
+
84
91
  A stored approval only relaxes an ask; a deny rule is never approvable.
85
92
  A file that doesn't parse is moved aside to `approvals.json.corrupt-<UTC time>`
86
93
  with one warning, and chi starts with no stored approvals (more asks, nothing lost).
@@ -137,7 +144,7 @@ grants no permission, so a parent may answer it either way by default.
137
144
  `turn.parent_continue: false` (config.yml only, like `parent_approvals`)
138
145
  makes parents stop-only. See [chi as a sub-agent](sub-agent.md#the-step-limit).
139
146
 
140
- The guardrails settings (`enabled`, `small_models`, `parent_approvals`) are
147
+ The guardrails settings (`enabled`, `mode`, `small_models`, `parent_approvals`) are
141
148
  read from config.yml only: they have no environment variable, and a worker
142
149
  chi starts unsets every `SAMAGOTCHI_GUARDRAILS_*` it would inherit. What a
143
150
  parent's environment does choose is the config dir: `XDG_CONFIG_HOME` picks
@@ -182,6 +189,7 @@ commands that name them.
182
189
  ```yaml
183
190
  guardrails:
184
191
  enabled: true # false: no rules, and hooks' asks are dropped (a deny still applies); config.yml only
192
+ mode: auto # or strict: also the rules tagged modes: [strict]; config.yml only
185
193
  rules:
186
194
  - id: git-push
187
195
  tool: shell # execute + task_create; or a tool name, a glob, or a list
@@ -201,7 +209,41 @@ guardrails:
201
209
  scopes: [once, session, rule]
202
210
  ```
203
211
 
204
- A rule gives at least one of `tool`, `command`, `path` and `git`.
212
+ A rule gives at least one of `tool`, `command`, `path`, `git`, `touches` and `rm`.
213
+
214
+ `rm: outside_tmp` (shell tools) matches a command whose `rm -r -f` reaches
215
+ outside the tmp folders (`$TMPDIR`, `/tmp`, `/var/tmp`): every such `rm` in a
216
+ chain must name only paths inside one, resolved like `touches` (so
217
+ `/tmp/../etc` and a link from `/tmp` to `/` are outside). The tmp folder
218
+ itself, a glob (`/tmp/*`), a `$VAR` other than `$HOME`/`$TMPDIR`, an `rm`
219
+ behind `sudo`/`xargs`/`sh -c` or in `$(…)`, and the tmp folder the session's
220
+ repo is in count as outside.
221
+
222
+ `touches: chi_dirs` (shell tools) matches a command that names a path in chi's
223
+ own folders: the config folder (`config.yml`, `memories/`), the hooks folder,
224
+ the approval store, installed bundles (`memories/.bundles`), the session repo's
225
+ git hooks (`core.hooksPath` honoured) or any `.git/hooks` outside a tmp folder.
226
+ Each word is read as a path against the folder the command is in by then
227
+ (`cd`, `pushd`/`popd` and `( … )` followed), with `~`, `$HOME` and
228
+ `$XDG_CONFIG_HOME`/`$XDG_STATE_HOME` expanded and symlinks resolved. So
229
+ `/tmp/x/config/samagotchi/config.yml` and chi's own source
230
+ (`lib/samagotchi/hooks/…`) don't match. A word it can't resolve (another
231
+ `$VAR`, `$(…)`, a glob, a relative path after `cd $X`) or one that reads as a
232
+ script (`sh -c '…'`, `ruby -e '…'`) is matched as text instead (`.config/samagotchi`,
233
+ `samagotchi/config.yml`, `samagotchi/hooks`, `samagotchi/guardrails`,
234
+ `memories/.bundles`, `.git/hooks`).
235
+
236
+ `skip_read_only: true` lets a shell command through when it only reads:
237
+ every command in it (split at `;`, `&&`, `||`, `|`) is `ls`, `cat`, `head`,
238
+ `tail`, `wc`, `stat`, `du`, `grep`, `rg`, `find`, `tree`, `file`, `sort`,
239
+ `uniq`, `cut`, `diff`, `echo`, `cd`, `sed`, `awk` or a reading `git`
240
+ (`log`, `show`, `diff`, `status`, `branch` listing, …), with no option that
241
+ writes or runs something (`sed -i`, `sed 's/a/b/w f'`, `find -delete`/`-exec`,
242
+ `rg --pre`, `sort -o`, `awk` with `>`/`|`/`system(`, `git -c`, `git diff
243
+ --ext-diff`, …) and no redirection other than to `/dev/null` or another fd.
244
+ `$(…)`, backticks, `$VAR` (other than `$HOME`, `$XDG_*`, `$TMPDIR` and `$PWD`
245
+ at a word's start), an env prefix, `sh -c`, `xargs`, `eval`, `sudo` and the
246
+ like make a command not read-only. It errs towards asking.
205
247
 
206
248
  A tool name may be a glob, so one rule covers a plugin's tools (an MCP server's,
207
249
  say): `tool: "mcp_*"` or `tool: ["mcp_{git,gh}_*", web_fetch]` (`*`, `?`, `[…]` and
@@ -303,6 +345,38 @@ once per bundle version to restart the session (`chi sessions stop ID`, then
303
345
  older than the bundle) denies every tool call, as for a rule that doesn't
304
346
  parse, until chi is updated or the bundle uninstalled.
305
347
 
348
+ ### Modes
349
+
350
+ `guardrails.mode` (config.yml only) picks how much is asked:
351
+
352
+ - `auto`, the default: the rules tagged `modes: [strict]` don't vote. Asks
353
+ are kept for what is hard to undo or reaches chi itself.
354
+ - `strict`: every rule votes.
355
+
356
+ ```yaml
357
+ guardrails:
358
+ mode: strict
359
+ rules:
360
+ - id: no-docker
361
+ tool: shell
362
+ command: '\bdocker\b'
363
+ modes: [strict] # or auto, or [auto, strict] (the same as leaving it out)
364
+ verdict: ask
365
+ ```
366
+
367
+ A rule without `modes:` votes in every mode, so a rule you add to config.yml
368
+ always applies unless you tag it. The core checks (protected paths, hooks)
369
+ vote in both modes. In the guardrails bundle, `git-rebase`,
370
+ `write-outside-repo` and `git-outside-repo` are strict only; its small-model
371
+ rules (`models: small`) are untagged, so they ask a small model in both modes.
372
+ `/guardrails` shows the mode on its first lines and marks the rules this mode
373
+ leaves out `(strict only)`.
374
+
375
+ A rule in config.yml that uses a key this chi doesn't know yet (`modes:`,
376
+ `touches:`, `rm:`, `skip_read_only:` before 0.20) doesn't parse, and a rule
377
+ that doesn't parse denies every tool call (see Failing closed). Bundles say
378
+ which chi they need (`requires_chi`), so their rules don't hit this.
379
+
306
380
  ## The guardrails bundle
307
381
 
308
382
  ```sh
@@ -310,18 +384,22 @@ chi bundle install guardrails # chi bootstrap installs it, with the core prof
310
384
  ```
311
385
 
312
386
  installs a default rule set plus a short memory telling the model not to
313
- route around a deny. It asks before `git push`, `reset --hard`, `clean -f`,
314
- `branch -D`, `rebase`, `filter-branch`/`filter-repo`; `rm -rf` on `/`, `~`,
315
- `$HOME` or `..` paths; `curl … | sh` and `base64 -d … | sh`; writes outside the
316
- session's repo (`write-outside-repo`) and git that changes another checkout
317
- (`git-outside-repo`: `cd ../main && git commit`, `git -C ../main add .`; it
318
- offers once, this session and "rule in this repo", the last stored for later
319
- sessions in that repo); shell commands that name chi's config, hooks or guardrails or
320
- `.git/hooks`; and answering chi's questions around `chi answer`: `chi --attach`
321
- or `chi -p` with stdin from a pipe, a here-string or a file
322
- (`chi-answer-piped`), and `curl`/`wget` to a session's `/answer` route
323
- (`chi-answer-http`), both once or for the session. `chi answer` itself isn't
324
- asked about. It denies writes into `.git/hooks`. The rules are in
387
+ route around a deny. In both modes it asks before `git push`, `reset --hard`,
388
+ `clean -f`, `branch -D`, `filter-branch`/`filter-repo`; `rm -rf` on `/`, `~`,
389
+ `$HOME` or `..` paths, unless every target is inside a tmp folder; `curl … |
390
+ sh` and `base64 -d … | sh`; shell commands that name a path in chi's config,
391
+ hooks, approvals or bundles or a `.git/hooks` (`shell-touches-chi`, `touches:
392
+ chi_dirs`), unless they only read (`skip_read_only`); and answering chi's
393
+ questions around `chi answer`: `chi --attach` or `chi -p` with stdin from a
394
+ pipe, a here-string or a file (`chi-answer-piped`), and `curl`/`wget` to a
395
+ session's `/answer` route (`chi-answer-http`), both once or for the session.
396
+ `chi answer` itself isn't asked about. It denies writes into `.git/hooks`.
397
+
398
+ In strict mode (`guardrails: { mode: strict }`) it also asks before `git
399
+ rebase`, writes outside the session's repo (`write-outside-repo`) and git that
400
+ changes another checkout (`git-outside-repo`: `cd ../main && git commit`, `git
401
+ -C ../main add .`; it offers once, this session and "rule in this repo", the
402
+ last stored for later sessions in that repo). The rules are in
325
403
  `lib/samagotchi/bundles/guardrails/guardrails/rules.yml`.
326
404
 
327
405
  Small models (`models: small`, see above) get two more asks, in
data/docs/memory.md CHANGED
@@ -27,6 +27,10 @@ Each memory entry may have a companion file named `<name>.<model-key>.md` in the
27
27
  - **In the system prompt**: the identity memory and the preloaded memories (`--memory`, config `memories:`) get their overlays too, as `memory_read` gives them; after `/model` the rebuilt prompt carries the new model's.
28
28
  - **Dormancy**: Overlays are only active under the matching model key; other models see the base entry only.
29
29
  - **Invariant**: The base entry is the contract. Overlays only add model-specific guidance and never contradict the base protocol.
30
+ - **In a bundle**: ship `<name>.<key>.md` next to `<name>.md` (ship the base too; an overlay with no base anywhere installs with a warning and loads only once the base exists; one whose base is only among your installed memories is taken as an overlay of it, with a warning naming it). The overlay gets no `index.md` line, so other models don't see it, and `chi bundle status` reads it `ok (model overlay)`; `chi bundle build FILES...` brings a named memory's overlays along.
31
+ - The key is the `model key` row of `chi self` under that model. It follows the provider's model id: OpenRouter's `deepseek/deepseek-v4.1-flash` keys as `deepseek-deepseek-v4-1-flash`, while a local server names the same model differently, so one model may need one overlay per provider.
32
+ - Key an alias by its **target**, not the alias: the alias's own key is read only as a fallback, when the alias was typed and the target has no overlay.
33
+ - The overlay is read in the scope its base is found in: a project memory with the same name shadows a system-scope bundle's base and overlay.
30
34
 
31
35
 
32
36
  At startup, the agent reads both scope indexes with blank-name memory reads
data/docs/plugins.md CHANGED
@@ -524,6 +524,11 @@ ctx.card(id: id, title: "Build finished", body: "no warnings left") # replaces
524
524
  its line; the web shows a bubble only when the command answers with
525
525
  text or fails.
526
526
  - `level:` is `:info` or `:warn` (the warning colour).
527
+ - An `:info` card with no actions and a body of one short line (up to 160
528
+ characters) is a notice: the web shows it as a one-line row,
529
+ `▸ check-in: Nudged the model at 105 tool calls.`, that opens on a click,
530
+ like a resolved question. A warn card, one with actions and one with more
531
+ to read stay framed cards.
527
532
  - `id:` names an earlier card to replace. Without one a new id is made. The
528
533
  web updates the card in place; the terminal prints it again, marked
529
534
  `(updated)`. A card that waits for something (a model's answer) shows