samagotchi 0.16.0 → 0.18.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -1
  3. data/README.md +2 -0
  4. data/docs/architecture.md +38 -2
  5. data/docs/cli.md +37 -9
  6. data/docs/configuration.md +8 -6
  7. data/docs/guardrails.md +51 -8
  8. data/docs/hooks.md +4 -1
  9. data/docs/internals/background-tasks.md +1 -0
  10. data/docs/memory.md +41 -0
  11. data/docs/plugins.md +5 -2
  12. data/docs/sessions.md +8 -6
  13. data/docs/sub-agent.md +43 -4
  14. data/docs/testing.md +47 -0
  15. data/lib/samagotchi/answer_command.rb +23 -2
  16. data/lib/samagotchi/answer_tail.rb +52 -0
  17. data/lib/samagotchi/bridge/turn_accumulator.rb +20 -5
  18. data/lib/samagotchi/bridge.rb +86 -14
  19. data/lib/samagotchi/bridge_client.rb +18 -3
  20. data/lib/samagotchi/bundle_command.rb +121 -4
  21. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +18 -3
  22. data/lib/samagotchi/bundles/guardrails/manifest.yml +4 -4
  23. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  24. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  25. data/lib/samagotchi/bundles/system/memory_guide.md +5 -5
  26. data/lib/samagotchi/bundles/system/self_map.md +1 -0
  27. data/lib/samagotchi/commands/registry.rb +28 -0
  28. data/lib/samagotchi/config.rb +7 -1
  29. data/lib/samagotchi/continue_offer.rb +177 -0
  30. data/lib/samagotchi/engine.rb +73 -19
  31. data/lib/samagotchi/guardrails/outside.rb +46 -0
  32. data/lib/samagotchi/guardrails/parent_approvals.rb +2 -0
  33. data/lib/samagotchi/guardrails/parent_continue.rb +43 -0
  34. data/lib/samagotchi/guardrails/rules.rb +17 -5
  35. data/lib/samagotchi/guardrails/shell_git_dirs.rb +267 -0
  36. data/lib/samagotchi/guardrails/targets.rb +30 -13
  37. data/lib/samagotchi/guardrails.rb +2 -0
  38. data/lib/samagotchi/iteration_limit.rb +38 -0
  39. data/lib/samagotchi/kernel_loop.rb +2 -1
  40. data/lib/samagotchi/llm/backend.rb +2 -1
  41. data/lib/samagotchi/llm/chat_loop.rb +2 -1
  42. data/lib/samagotchi/llm/native_backend.rb +2 -1
  43. data/lib/samagotchi/memory_bundle/builder.rb +22 -0
  44. data/lib/samagotchi/memory_bundle/index_updater.rb +17 -6
  45. data/lib/samagotchi/memory_bundle/installer.rb +60 -21
  46. data/lib/samagotchi/memory_bundle/profile.rb +9 -4
  47. data/lib/samagotchi/memory_bundle/provenance.rb +14 -0
  48. data/lib/samagotchi/memory_bundle/shipped_update.rb +5 -3
  49. data/lib/samagotchi/memory_bundle/system_bundle.rb +5 -3
  50. data/lib/samagotchi/memory_bundle/trash.rb +136 -0
  51. data/lib/samagotchi/memory_bundle/uninstaller.rb +34 -5
  52. data/lib/samagotchi/parent_report.rb +39 -9
  53. data/lib/samagotchi/plugin/context.rb +14 -2
  54. data/lib/samagotchi/question_desk.rb +205 -46
  55. data/lib/samagotchi/reply_wait.rb +10 -4
  56. data/lib/samagotchi/self_report.rb +49 -3
  57. data/lib/samagotchi/send_command.rb +46 -6
  58. data/lib/samagotchi/session.rb +3 -2
  59. data/lib/samagotchi/session_commands.rb +3 -2
  60. data/lib/samagotchi/session_manager.rb +4 -1
  61. data/lib/samagotchi/session_metrics.rb +137 -48
  62. data/lib/samagotchi/sessions_command.rb +3 -2
  63. data/lib/samagotchi/system_prompt.rb +46 -5
  64. data/lib/samagotchi/terminal_ui/attached_loop.rb +39 -6
  65. data/lib/samagotchi/terminal_ui/event_renderer.rb +2 -1
  66. data/lib/samagotchi/terminal_ui/question_prompt.rb +34 -2
  67. data/lib/samagotchi/terminal_ui.rb +35 -2
  68. data/lib/samagotchi/tool_declarations.rb +2 -2
  69. data/lib/samagotchi/tools/builtins.rb +11 -3
  70. data/lib/samagotchi/tools/delegate_relay.rb +4 -0
  71. data/lib/samagotchi/tools/delegate_wait.rb +14 -5
  72. data/lib/samagotchi/tools/edit.rb +6 -1
  73. data/lib/samagotchi/tools/execute.rb +5 -4
  74. data/lib/samagotchi/tools/peers.rb +3 -2
  75. data/lib/samagotchi/tools/read.rb +2 -1
  76. data/lib/samagotchi/tools/task_runtime.rb +1 -1
  77. data/lib/samagotchi/turn_flow.rb +8 -7
  78. data/lib/samagotchi/turn_note.rb +7 -0
  79. data/lib/samagotchi/version.rb +1 -1
  80. data/lib/samagotchi/web/app.rb +189 -43
  81. data/lib/samagotchi/web/public/app.js +157 -51
  82. data/lib/samagotchi/web/public/data.js +12 -13
  83. data/lib/samagotchi/web/public/index.html +6 -1
  84. data/lib/samagotchi/web/public/notify.js +12 -5
  85. data/lib/samagotchi/web/public/question_card.js +28 -1
  86. data/lib/samagotchi/web/public/question_cards.js +74 -3
  87. data/lib/samagotchi/web/public/sessions_list.js +1 -0
  88. data/lib/samagotchi/web/public/stage_view.js +13 -1
  89. data/lib/samagotchi/web/public/strip.js +11 -0
  90. data/lib/samagotchi/web/public/timing.js +67 -12
  91. data/lib/samagotchi/web/public/turn_events.js +74 -3
  92. data/lib/samagotchi/web/public/update_notice.js +15 -0
  93. data/lib/samagotchi/worker.rb +58 -60
  94. data/lib/samagotchi/worker_sidecar.rb +7 -1
  95. metadata +10 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dd06759f4b1d8b5c2d799aed0a4a57940c34bfd663599fcd1dbbad4f4421c99
4
- data.tar.gz: 040c8e4d3c4e3aee3f9605fded5676088c21a80b50426696a9cdee4a16f65d7a
3
+ metadata.gz: 9c7c0dfd8a859bb4808e10128560904cac788bc6bed8ec0b9d799425c6eee2fb
4
+ data.tar.gz: 4263cf99ea0998c74d7ae0f4e37d3bdbed8acc0140400082eed696da58e14847
5
5
  SHA512:
6
- metadata.gz: 418ba106ef5c7f610fd831b93b3441ab1de00910b84a782d37bc3fe617dab833ce35073f4733dc2fe6bdca9d90dba5c7af6fba7b7df29edf60c72c08c99acff9
7
- data.tar.gz: 8c78e649bbadc097d5cb9b3de7adaac3bcebeda06d04ea1a34f7cbfab531fcaf15e0bce7bbc25480eb28e18c47867cbd0226d57ac3cece9452e7912471aa55b5
6
+ metadata.gz: 16b8b6cda0d263cb6540e052d207483396a5fd4588e3e4ddd776e02bcc107fb63ef73e19835b20b44e5c95e850d30b018bf985d898c8a2b07e6ae7a7a2984034
7
+ data.tar.gz: ade20718b92e00ee24274d5b0cdce3ff102ae88487cb00af244168134f1f0c0d3259cb1e7f19ade71bf8fe442cba1a764bd6550c5d1911ef7cce558fabac044f
data/CHANGELOG.md CHANGED
@@ -8,6 +8,152 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.18.0] - 2026-10-03
12
+
13
+ ### Added
14
+
15
+ - The guardrails bundle (0.4.0) asks before git that changes another checkout: `cd ../main && git commit`,
16
+ `git -C ../main add .`, an `execute` with `cwd:` there (commit, add, reset, checkout, switch, rebase, merge, push,
17
+ stash, rm, mv, cherry-pick, revert, pull, restore, am). Read-only git and tests there still run unasked. Its card
18
+ offers once, this session, or "rule in this repo" (stored, so later sessions there don't ask); switch it off with
19
+ `guardrails.disable: [git-outside-repo]`. Run `chi update` (or `chi bundle upgrade guardrails`) to get it.
20
+ - Web: a tab left open across a `chi web` upgrade says so: when its event stream reconnects to a newer chi, a toast
21
+ `chi was updated to X` offers a Reload button (it never reloads by itself; ✕ dismisses it).
22
+ - `chi bundle trash` lists the bundle trash (moved files from uninstalls/upgrades) oldest first, with file count,
23
+ size, and age; `--empty` deletes all folders, `--older-than DAYS` keeps only recent ones, `--dry-run` previews.
24
+ - A turn that runs out of iterations now waits as a question (kind `continue`, the step-limit question):
25
+ `chi send --wait` exits 3 with it, `chi answer --option Continue|Stop` answers it, and the web badge and bell show
26
+ it. The web draws it as a card with Continue, Stop and a reason for Stop; `chi --attach` reads the continue words
27
+ at its prompt (Enter continues). A parent agent may Continue unless `turn.parent_continue: false` (config.yml only);
28
+ a delegate's goes back to the parent model.
29
+ - `turn.max_iterations` (default 100, env `SAMAGOTCHI_TURN_MAX_ITERATIONS`) sets the per-turn step limit;
30
+ `--no-interrupt` turns get the larger of 1000 and it.
31
+
32
+ ### Changed
33
+
34
+ - `write-outside-repo` and any `path: outside_repo` rule measure from the session's repo (not a plugin call's own
35
+ `cwd:`), with symlinks resolved: `/tmp/x` and `/private/tmp/x` are the same
36
+ folder, and a link in the repo that points elsewhere counts as outside. Writes into tmp folders (`$TMPDIR`, `/tmp`)
37
+ no longer ask, unless the session itself lives there. Hook events' `targets` gain `git_dirs`.
38
+ - A one-word `/word` that no command answers (`/modle`) is no longer sent to the model as a prompt: every UI
39
+ (the REPL, the attached TUI, the web composer) prints `Unknown command /modle. Did you mean /model? /help lists
40
+ the commands.` instead, with the "Did you mean" part only when a command name is close. Lines that are prompts
41
+ (`/foo bar`, `/usr/bin/env is missing`) still go to the model.
42
+ - `chi send --wait` reports `limit` instead of `no_answer` when a turn ran out of iterations and nobody can answer
43
+ (an older worker), with how to continue it.
44
+ - Stop at the step limit (`/continue no`) keeps the turn's work, with a note for the model, instead of erasing the
45
+ prompt and everything the turn did; `!rollback` still erases it. Reminder turns follow `turn.max_iterations`.
46
+ - The web page's end-of-turn and card re-reads fetch only what they show: the session's state, the last answer,
47
+ that turn's timings and the cards, from a new light worker read (`GET /session/:id/tail`) instead of the
48
+ whole conversation and timing history, so they stay the same size however long the session runs.
49
+ - `execute` gives a command 120 s by default (was 30 s) before it is killed; `execute.timeout_sec` /
50
+ `SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` still set it.
51
+ - An `execute` command killed at its time limit says how to run a long one: a second line
52
+ `(killed at the limit; for a long command use task_create, then task_wait)` follows `Error: command timed out after Ns`.
53
+ - `read` with only `end_line` reads from line 1 instead of failing with "start_line must be provided". A range
54
+ `edit` with only `end_line` still refuses (it would overwrite the top of the file) and now says what to pass:
55
+ `pass start_line too (1-based, the first line to replace)`.
56
+ - In a worktree or subfolder, the system prompt's "Project root" line now says it is only where shared project
57
+ memories come from, and to read, edit, run and commit in the current working directory; a model took the root for
58
+ its workspace and committed to the main checkout.
59
+
60
+ ### Fixed
61
+
62
+ - Web: a session waiting on a plugin card (check-in's Nudge / Keep going / Stop) shows its "needs you" badge as a
63
+ pill on its session card again; it was drawn as a large empty circle over the card.
64
+ - Web: with a prompt queued behind a turn, the next turn's live timing line no longer freezes or takes the
65
+ previous turn's number when the previous turn's re-read lands late.
66
+ - `chi sessions list` shows the first message of a session that has no turn yet, such as one started with
67
+ `chi send --new -m "/model x"` or from the web start page; its row was blank.
68
+ - Web: a terminal-only command (`/stats`, `/exit`, `/recap` …) typed as the start page's first message is answered on
69
+ the start page; it no longer starts a session just to show that reply.
70
+
71
+ Update with `chi update`: the guardrails bundle moves to 0.4.0 (the `git-outside-repo` rule; it needs chi 0.18.0).
72
+
73
+ ## [0.17.0] - 2026-10-03
74
+
75
+ ### Added
76
+
77
+ - The system prompt names the model the session runs on: `Model: this session runs on box:gemma-small (host box,
78
+ box.test:8080; model key gemma-small).`, plus a line to answer "which model are you" from it, not from training
79
+ (a fine-tune often knows only its base model's name), and that model-only guidance goes in
80
+ `memory_write current_model_only: true` overlays. A local llama.cpp host whose `/props` names another model adds
81
+ `; the server says it serves <name>`. The line changes only with `/model`.
82
+ - `execute` and `task_create` export `SAMAGOTCHI_SESSION_MODEL`, the session's model ref (it follows `/model`; the
83
+ model's `env` can't set it).
84
+ - Plugins: `ctx.model` (the session's model ref, right after `/model` too) and `ctx.model_key` (its memory overlay
85
+ key).
86
+ - `chi self` has a `model key` row: the memory overlay key of the model it reports.
87
+
88
+ ### Changed
89
+
90
+ - `chi self` run by a session's `execute` reports **that session's** model, and its host, loop, profile, thinking and
91
+ served model: `model splash:… (this session abcd1234; default main:…)`. Before, it showed the config default
92
+ (or the `--model` the worker started with), wrong after `/model`, for web-picker and `chi send --new --model`
93
+ sessions and delegate children. Elsewhere the row reads `main:… (default)`. `chi self --model` is still the default.
94
+ - The identity memory and the preloaded memories (`--memory`, config `memories:`) get their model overlays
95
+ (`<name>.<key>.md`) in the system prompt, as `memory_read` gives them.
96
+ - A session's live state (what the web and an attached terminal read from its worker) no longer carries every turn's
97
+ and tool call's timing record, only the newest turn's: reads stay the same size however long the session runs. The
98
+ full history stays in the session's `analytics.json`.
99
+ - A memory a bundle installed says so in its `index.md` line, and so in the prompt's memory index:
100
+ `- **memory_guide** · system · 2026-10-03 · 5120 · from samagotchi-system — …`. `memory_write` and `write`/`edit`
101
+ keep the tag; a same-name memory the install skipped gets none. Bundles installed earlier get it on their next
102
+ install or upgrade (the system bundle: with the next chi version).
103
+ - `chi bundle uninstall` (and an upgrade dropping a file the new version no longer ships) moves the bundle's memory
104
+ files to `<memories>/.bundles/.trash/<bundle>-<time>/` instead of deleting them, and says where:
105
+ `Moved to the trash: notes.md (…/.trash/notes-bundle-20261003-120000)`. Nothing empties the trash; delete it by hand.
106
+ - `chi bundle build` leaves out the memories an installed bundle owns (the system bundle's `identity.md`, a shipped
107
+ bundle's memory), one line each: `Left out identity.md: installed by bundle samagotchi-system (name it to include
108
+ it)`; naming a file in `FILES...` includes it. It now prints the builder's warnings.
109
+ - An `index.md` line a bundle install, upgrade or uninstall couldn't write or remove is now a warning
110
+ (`index.md: line for notes not updated (…)`) instead of passing silently; `chi bundle uninstall` of a profile
111
+ prints its bundles' warnings too.
112
+ - Web: a composer line is a command only when it names one of the session's commands (or is `!…`); an unknown
113
+ `/word` (a path like `/usr/bin/env …`, a typo) goes to the model as a prompt, as in a terminal, instead of being
114
+ refused with `not a session command`.
115
+ - Web: a window under 700 px tall hides the latest-sessions strip by itself (the `▾ N sessions` pill shows it again
116
+ until the window is tall), so a question or approval card in the turn's stage has room for its choices and
117
+ buttons. Taller windows and phones are unchanged.
118
+
119
+ ### Fixed
120
+
121
+ - `chi self`'s `served model` row for a chat host (`api: openai`), or a server without `/props`, says
122
+ `reported per turn (the server has no /props)`; it said `unknown (no answer from the server's /props)`, which a
123
+ model took to mean the server never said what it served.
124
+ - `chi bundle uninstall` no longer deletes a memory the user had before the install. A bundle now records (and
125
+ so upgrades and removes) only the files it wrote; a same-name file that was already there is reported
126
+ `Skipped` and stays the user's, on install, re-install and upgrade. Before, it was recorded with the user's
127
+ content as its base, so uninstall removed it without `--force`: a third-party bundle shipping `notes.md`, or
128
+ `chi bundle build` then installing the result on the same machine. An upgrade no longer reports such a file as
129
+ a conflict (or `chi update --dry-run` as kept). Bundles installed before this fix may still list such a file;
130
+ uninstall now moves it to the trash (below) rather than deleting it.
131
+ - A memory file two installed bundles list (one an older install adopted, like `identity.md` under both
132
+ `samagotchi-system` and a `chi bundle build` of your memories) stays when one of them is uninstalled, or drops it
133
+ in an upgrade: `Kept identity.md: bundle samagotchi-system has it too`. It goes with the last one.
134
+ - Web: after a reload, a turn with an empty retry mid-turn shows each tool row's own duration; the rows took their
135
+ records by iteration, so every call after the retry showed the wrong one, or none.
136
+ - Attached TUI: `/detach` typed at a question's `?` prompt detaches (the question stays open), as Ctrl-D does; it
137
+ was read as an answer and refused as an unknown option.
138
+ - Attached TUI: joining a turn while a tool call runs (a long `delegate`, a slow command), its row's duration counts
139
+ from the call's start, not from the join.
140
+ - Web: in a short window a question or approval card that opens in the turn's stage is scrolled into view (its
141
+ choices and buttons); it sat below the prompt and headline, out of sight.
142
+ - A session command sent as a message runs as the command, as typed in a terminal: `chi send -m "/model x" ID`,
143
+ `chi send --new -m "/model x"`, `chi -p "/model x"` (attached and `--non-interactive`) and the web start page's
144
+ first message went to the model as a prompt. `chi send` says `sent as a session command`; with `--wait` there is
145
+ no reply to wait for (exit 0, `--format json` status `command`). An unknown `/word` still goes to the model.
146
+ - macOS 26: a dead worker's leftover `bridge.json` no longer counts as a live worker. Ruby's `Socket.tcp` with a
147
+ connect timeout returns the socket of a refused connect there, so the check said live: clients tried its port
148
+ first, `chi update` listed the dead worker as running, and an approval relay kept watching a parent that was gone.
149
+ - REPL with piped input: when the input ends at a question or approval, no bare `? ` line is left above its
150
+ `(denied)` / `(cancelled)` summary.
151
+ - Web: an approval from a delegate its parent isn't waiting on directly rings once (the parent's relayed card), not
152
+ twice: a child's approval now waits 3.5 s (was 1.5 s) for the relay before it notifies on its own. Only delegated
153
+ children's approvals wait; one nobody relays rings that much later.
154
+
155
+ Update with `chi update` (no bundle changes; the system bundle upgrades itself with chi).
156
+
11
157
  ## [0.16.0] - 2026-10-03
12
158
 
13
159
  ### Added
@@ -856,7 +1002,9 @@ and long-lived sessions.
856
1002
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
857
1003
  hotkey panel that send selected text or the clipboard to your sessions.
858
1004
 
859
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.16.0...HEAD
1005
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.18.0...HEAD
1006
+ [0.18.0]: https://github.com/dm1try/samagotchi/compare/v0.17.0...v0.18.0
1007
+ [0.17.0]: https://github.com/dm1try/samagotchi/compare/v0.16.0...v0.17.0
860
1008
  [0.16.0]: https://github.com/dm1try/samagotchi/compare/v0.15.0...v0.16.0
861
1009
  [0.15.0]: https://github.com/dm1try/samagotchi/compare/v0.14.0...v0.15.0
862
1010
  [0.14.0]: https://github.com/dm1try/samagotchi/compare/v0.13.0...v0.14.0
data/README.md CHANGED
@@ -201,6 +201,8 @@ npm test # web frontend specs
201
201
  npm run e2e # web UI happy paths in Chromium, fake model (once: npx playwright install chromium)
202
202
  ```
203
203
 
204
+ Integration specs against a live model server, and what the suite isolates: [docs/testing.md](docs/testing.md).
205
+
204
206
  ## License
205
207
 
206
208
  MIT, see [LICENSE](LICENSE).
data/docs/architecture.md CHANGED
@@ -177,12 +177,19 @@ rescues sink errors). A new UI (web, API) supplies its own `on_event` and
177
177
  renders whatever it needs from the stream + final `Result`. The public Engine API:
178
178
 
179
179
  ```ruby
180
- engine.run_turn(session, prompt, on_event: nil, max_iterations: 100, cancel_controller: nil)
180
+ engine.run_turn(session, prompt, on_event: nil, max_iterations: nil, cancel_controller: nil) # nil: turn.max_iterations
181
181
  engine.run(session: nil, prompt: "...", on_event: nil) # create/resume session + run
182
182
  engine.system_prompt # fully built system prompt string
183
183
  engine.session # current session (Engine owns create/resume)
184
184
  ```
185
185
 
186
+ The system prompt (`SystemPrompt#build`) is the base prompt, then rg guidance,
187
+ AGENT.md, the working directory, **the model** (`Model: this session runs on
188
+ <ref> (host …; model key …)` and a line telling the model to answer "which model
189
+ are you" from it, not from training), the session id and log, the memory indexes,
190
+ the identity and the preloaded memories. It is built once per loop and rebuilt only
191
+ by a model switch (or changed tools), so the model line costs no KV churn.
192
+
186
193
  #### Subscribing to the live stream (and the bridge)
187
194
 
188
195
  For an **always-on** consumer (an external SSE client, a second UI), use
@@ -249,9 +256,36 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
249
256
  - `GET /session/:id/state` — `session_state_snapshot` (JSON).
250
257
  - `GET /session/:id/stats` — `Engine#stats_snapshot` for attached `/stats`: the metrics, with the context window and prompt profile asked from the server before the first turn.
251
258
  - `GET /session/:id/snapshot` — the snapshot frame's content as one request (the web server renders
252
- the messages itself, then streams from its `event_seq`).
259
+ the messages itself, then streams from its `event_seq`). Read by the web's full show
260
+ (`GET /api/sessions/:id`, `?parts=1`) and the plugin's `list_sessions`; every message is copied.
261
+ - `GET /session/:id/tail` — the page's light re-read (`Bridge#tail_frame`): `session_state_snapshot`,
262
+ `answer` (the last message a UI shows as an answer, `AnswerTail`: the one message, copied alone),
263
+ `cards` (`CardStore#list`), `event_seq`, `event_id`, all taken in one event-log hold; no message
264
+ list, so it costs the same however long the session is. `?turn_id=` answers that turn's answer
265
+ (an unknown or missing id: the newest); the only route that reads its query.
253
266
  - `OPTIONS *` — CORS preflight (`Access-Control-Allow-Origin: *`).
254
267
 
268
+ The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` frames
269
+ (`SessionMetrics#snapshot`) carry the session's totals and only the recent timing records: the
270
+ newest turn the worker finished and any it hasn't saved yet (at most 20 turns), with their tool
271
+ calls and the running turn's finished ones. The full history is the session's `analytics.json`,
272
+ which `SessionMetrics#persist` rewrites after each turn; the web server merges the two by id.
273
+
274
+ The page's frequent reads of `GET /api/sessions/:id` stay light too:
275
+ - `?tail=1&recent=1` (each turn's end, a cancel, an answer display): `{tail, session: {id, status,
276
+ used_memory_names}, messages: [the answer], markdown_warning, timing}`, from the worker's `/tail`
277
+ alone (no session file parsed, no `analytics.json`); `timing` carries the worker's recent records
278
+ and `turn_count` (finished turns). The page merges the records by id (`timing.js mergeTiming`) and
279
+ finishes the ended turn's own line by its `turn_id` (sent as `&turn_id=`, which also picks that
280
+ turn's answer). A plain `?tail=1` (a tab opened before this) gets the whole timing.
281
+ - `?timing=1`: the whole timing (`analytics.json` merged with the worker's `/state`) and
282
+ `turn_count`, when the page's merge came up short.
283
+ - `?cards=1`: the cards, from `/tail`; the session file is only stat'ed for the 404.
284
+
285
+ A worker without `/tail` (an older chi; `404 not_found`) is read through `/snapshot` in the same
286
+ shape; any other failure (no worker, a 500, a timeout) reads the disk, trimmed to the newest
287
+ turn's records with `recent=1`.
288
+
255
289
  Every route goes through `Bridge#dispatch`: an id other than the bridge's own session is
256
290
  `404 unknown_session`, and a handler that raises answers `500 bridge_error` (logged as
257
291
  `handler_failed`) rather than dropping the connection.
@@ -267,6 +301,8 @@ never pulls from the page: `chi web` runs one `Samagotchi::Web::SessionHub` (a t
267
301
  the server, no daemon) that keeps an in-memory projection of the session list and pushes
268
302
  changes to every open tab over `GET /api/events` (SSE: a `snapshot` frame on every connect,
269
303
  then `session` for an upsert and `session_gone` for a removal, `: ping` while idle, no replay).
304
+ 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.
270
306
  Files stay the source of truth and workers don't know the hub. Its watcher is a 1 s tick that
271
307
  stats the sessions dir (every session writer goes tmp + rename, which bumps the dir's mtime) and
272
308
  each `<id>/` folder (recap.json, bridge.json, the stopped and archived markers), re-parsing only the files whose mtime or size
data/docs/cli.md CHANGED
@@ -21,9 +21,9 @@
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))
22
22
  - `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text or images to a session (live, stopped or new) as a message (⏎, a turn runs) or a context note (⌘⏎) (see [Desktop helper](desktop.md))
23
23
  - `chi models [--format text|json] [--timeout S] [TEXT]` — list the models every configured host offers, as the names `--model` takes (see [Listing the models](#listing-the-models))
24
- - `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles; `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
24
+ - `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
25
25
  - `chi update [--dry-run] [--no-gem] [--no-bundles] [--no-desktop]` — update an installed chi: the gem, the system bundle, the shipped bundles you installed and the desktop helper, in one table (see [Updating](#updating))
26
- - `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; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
26
+ - `chi bundle install|upgrade|uninstall|status|diff|list|build|trash` — 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; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
27
27
 
28
28
  ### First setup
29
29
 
@@ -283,7 +283,12 @@ In an attached terminal:
283
283
  - The prompt stays open while a turn runs; see [Typing during a turn](#typing-during-a-turn).
284
284
  - `/model`, `/models`, `/guardrails`, `/continue`, `!rollback` and `!commands` run in the
285
285
  worker, and every UI sees their output; `/stats` and `/recap` work too. The
286
- Web UI's composer takes the same commands.
286
+ Web UI's composer takes the same commands, and so do `-p "/model x"` and
287
+ `chi send -m "/model x"`. A `/word` the session doesn't know (a path, a
288
+ typo) goes to the model as a prompt; a one-word `/word` that no command
289
+ answers (`/modle`) is not sent: the UI prints `Unknown command /modle. Did
290
+ you mean /model? /help lists the commands.` (the "Did you mean" part only
291
+ when a name is close).
287
292
  - `!commands` and the model's tools run in the session's directory (where it
288
293
  was started), whichever terminal you attach from.
289
294
  - `-p` sends its prompt once attached, `--model` switches the worker's model
@@ -395,7 +400,9 @@ REPL alike:
395
400
  - `/stats` and `/recap` answer at once, and in an attached terminal so do `/help`
396
401
  and a plugin's anytime command (`/btw`). Other commands (`!cmd`, `/model`, `/models`,
397
402
  `!rollback`, `/continue`, `/guardrails`) say `busy: wait for the turn to end`
398
- and go back into the prompt, so Enter runs them once the turn ends.
403
+ and go back into the prompt, so Enter runs them once the turn ends. A
404
+ one-word `/word` no command answers (`/modle`) is not steering text either:
405
+ the hint prints and the line is dropped.
399
406
  - A question (`ask_user_question`, a guardrails approval) turns the prompt into
400
407
  a yellow `? ` and lists its choices under it, fitted to the terminal; only a
401
408
  line submitted there answers it (a number, `1,3`, a label, `y`/`n` for an
@@ -801,8 +808,29 @@ Behavior notes:
801
808
 
802
809
  ## Iteration Limit Behavior
803
810
 
804
- - `max_iterations` remains a hard safety cap on tool-call rounds.
805
- - Tool side effects that already ran before the cap are not rolled back.
806
- - `Samagotchi::KernelLoop#run` now returns a resumable result object with the visible output plus the accumulated conversation.
807
- - 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.
808
- - 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`.
811
+ - A turn's step limit (model ↔ tool rounds) is `turn.max_iterations` in config.yml (env
812
+ `SAMAGOTCHI_TURN_MAX_ITERATIONS`, default `100`, an integer of 1 or more). `--no-interrupt` and
813
+ `--non-interactive` turns get the larger of 1000 and it. Reminder turns follow it too.
814
+ - Tool side effects that already ran before the limit are not rolled back.
815
+ - `Samagotchi::KernelLoop#run` returns a resumable result object with the visible output plus the accumulated
816
+ conversation. If the limit is reached while tool calls are still pending, the result is marked resumable so callers
817
+ can continue from the saved conversation instead of restarting from scratch. The session's `last_turn` records it
818
+ (`"exhausted": true, "limit": N`).
819
+ - In a session's worker (plain `chi`, the web, `chi send`) the offer waits as a question between turns, the
820
+ **step-limit question** (kind `continue`, header "Step limit", options Continue / Stop): the session reads
821
+ `waiting` in the lists, the web shows a card with Continue and Stop and a reason box (a reason goes with Stop), the
822
+ session card's badge reads `out of steps` and the bell rings ("hit its step limit"). `chi send --wait` exits 3 with
823
+ it and `chi answer --option Continue|Stop` answers it (see [chi as a sub-agent](sub-agent.md)). It can't be
824
+ dismissed. A new message instead (or a due reminder) drops it and the partial turn stays; a typed `/continue …`
825
+ answers it too. If the worker exits (a stop, a crash), the offer goes with it.
826
+ - In the attached terminal (`chi --attach`) the question's prompt reads the continue words: Enter alone, `yes`, `y`
827
+ or `/continue` continue; `no` stops; `no, <reason>` stops and tells the model why; `1` and `2` pick the options.
828
+ - In the plain REPL (`--no-shared`) the CLI asks at the `? ` prompt, with the choices listed under it: `yes` (Enter
829
+ alone, or `/continue`) resumes, `no` stops, and `no, <explanation>` stops while keeping the reason in conversation
830
+ context. Once answered, one line stays: `? The turn ran out of iterations. Continue it? → no, too slow`.
831
+ - Stop keeps the interrupted turn's work in the conversation, as a Ctrl-C or a new prompt does, with a note for the
832
+ model that it wasn't continued (or your reason); `!rollback` still erases it.
833
+ - `--non-interactive` (`chi -p`) has nobody to ask: the turn ends there. A `chi send --wait` on a session nobody can
834
+ answer (an older worker) reports `limit` (exit 1) with how to continue it: `chi send ID -m '/continue yes'`.
835
+ - The check-in bundle's card closes as the turn ends, before the step-limit question opens, so the two never stand
836
+ together; with check-in's default `after: 50` and the limit of 100 a long turn usually shows a check-in card first.
@@ -279,14 +279,14 @@ spec. It requires the model to call `execute` and return the current UTC date:
279
279
 
280
280
  ```shell
281
281
  SAMAGOTCHI_INTEGRATION=1 \
282
- SAMAGOTCHI_SERVER_HOST=192.0.2.10 SAMAGOTCHI_SERVER_PORT=8000 \
283
- SAMAGOTCHI_DEFAULT_MODEL=incoai/Qwen3.8-27B-Splash \
284
- bundle exec rspec spec/integration/chat_loop_spec.rb -fd < /dev/null
282
+ SAMAGOTCHI_INTEGRATION_HOST=192.0.2.10 SAMAGOTCHI_INTEGRATION_PORT=8000 \
283
+ SAMAGOTCHI_INTEGRATION_MODEL=incoai/Qwen3.8-27B-Splash \
284
+ bundle exec rspec spec/integration/chat_loop_spec.rb
285
285
  ```
286
286
 
287
287
  The same test works against a llama.cpp OpenAI-compatible server by changing
288
288
  the host, port, and model values. The test is skipped unless
289
- `SAMAGOTCHI_INTEGRATION=1` is set.
289
+ `SAMAGOTCHI_INTEGRATION=1` is set; see [Testing](testing.md).
290
290
 
291
291
  oMLX's known tool-call limitation (a stream filter that strips markup) only
292
292
  affects its `/v1/chat/completions` endpoint, not the `/v1/completions` endpoint
@@ -869,6 +869,8 @@ described in their own sections.
869
869
  | `retry.base_delay` | `0.5` | yes | |
870
870
  | `retry.max_delay` | `8.0` | yes | |
871
871
  | `retry.empty_answer` | `1` | | Times a turn asks again after an empty answer (at most 3, `0` = off). See "Llama Network Retry Behavior". |
872
+ | `turn.max_iterations` | `100` | | A turn's step limit: model ↔ tool rounds before it stops and asks to continue (an integer ≥ 1). `--no-interrupt` turns get the larger of 1000 and this. See [CLI: Iteration Limit Behavior](cli.md#iteration-limit-behavior). |
873
+ | `turn.parent_continue` | `true` | | Whether a parent agent (`chi answer`) may answer Continue to a session's step-limit question; `false`: Stop only. config.yml only: no environment variable. See [Sub-agent](sub-agent.md) and [Guardrails](guardrails.md#approvals-from-a-parent-agent). |
872
874
  | `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
873
875
  | `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
874
876
  | `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
@@ -882,14 +884,14 @@ described in their own sections.
882
884
  | `execute.truncate_at_bytes` | `65536` | yes | The same for `execute` output. |
883
885
  | `execute.preview_bytes` | `12288` | yes | |
884
886
  | `execute.telemetry_threshold_pct` | `80` | yes | |
885
- | `execute.timeout_sec` | `30` | yes | Seconds one `execute` command may run before it is stopped. |
887
+ | `execute.timeout_sec` | `120` | yes | Seconds one `execute` command may run before it is stopped. |
886
888
  | `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
887
889
  | `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token (`chi web --new-token` replaces it). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
888
890
  | `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
889
891
  | `web.view` | `stage` | yes | How `chi web` draws a turn: `stage` (the running turn pinned above the composer) or `turn` (one block per turn in the history). See [CLI](cli.md#web-views). |
890
892
  | `web.annotate_presets` | `Agreed\|Could you please elaborate?` | yes | Quick replies next to Annotate in `chi web`, `\|`-separated (a YAML list works too); `""` in the file or on the CLI leaves only Annotate (an empty env value means the default). See [CLI](cli.md#web-annotate-presets). |
891
893
  | `history.file` | state dir | | Prompt history path, shared by the TUI and the web composer. |
892
- | `no_interrupt` | `false` | `--no-interrupt` | Raise the tool-call limit of a turn to 1000; a top-level key. |
894
+ | `no_interrupt` | `false` | `--no-interrupt` | Raise the step limit of a turn to 1000 (or `turn.max_iterations`, when larger); a top-level key. |
893
895
  | `no_default_input` | `false` | `--no-default-input` | Don't pre-fill `default.input`; a top-level key. |
894
896
  | `skip_agent_md` | `false` | | Don't load AGENT.md; a top-level key (see below). |
895
897
 
data/docs/guardrails.md CHANGED
@@ -118,6 +118,12 @@ directory, the rule) stay with the web and the terminal. An approval that
118
118
  doesn't offer "once" can't be allowed from a parent, and neither can one whose
119
119
  offered scopes are missing or don't fit its options: then only Deny goes.
120
120
 
121
+ A child that has to work in another folder (`write-outside-repo`,
122
+ `git-outside-repo`) is denied this way too. Start it in that folder instead
123
+ (`chi send --new --dir PATH`), or have your user allow "rule … in this repo"
124
+ once on the web (it is stored for later sessions in that repo), or switch the
125
+ rules off: `guardrails: {disable: [write-outside-repo, git-outside-repo]}`.
126
+
121
127
  Whatever the setting, a parent can't allow a call on chi's own files: a
122
128
  `write`, `edit` or `execute` on the config dir (config.yml, installed bundles'
123
129
  rules), the hooks dir or the approval store, by its rule (`chi-config`,
@@ -126,6 +132,11 @@ config.yml rewrite would allow everything from then on, so those stay with the
126
132
  user: `chi answer` and the worker refuse the allow with `only the user can
127
133
  allow it`.
128
134
 
135
+ A turn's step-limit question (kind `continue`) isn't an approval: Continue
136
+ grants no permission, so a parent may answer it either way by default.
137
+ `turn.parent_continue: false` (config.yml only, like `parent_approvals`)
138
+ makes parents stop-only. See [chi as a sub-agent](sub-agent.md#the-step-limit).
139
+
129
140
  The guardrails settings (`enabled`, `small_models`, `parent_approvals`) are
130
141
  read from config.yml only: they have no environment variable, and a worker
131
142
  chi starts unsets every `SAMAGOTCHI_GUARDRAILS_*` it would inherit. What a
@@ -182,9 +193,16 @@ guardrails:
182
193
  tool: [write, edit]
183
194
  path: outside_repo # or a glob: "**/.git/hooks/**", "/etc/**", "config/*.yml"
184
195
  verdict: ask
185
- reason: writes outside the repository
196
+ reason: writes outside this session's repo
197
+ - id: git-outside-repo
198
+ tool: shell
199
+ git: outside_repo # a shell call runs commit/add/reset/… in another checkout
200
+ verdict: ask
201
+ scopes: [once, session, rule]
186
202
  ```
187
203
 
204
+ A rule gives at least one of `tool`, `command`, `path` and `git`.
205
+
188
206
  A tool name may be a glob, so one rule covers a plugin's tools (an MCP server's,
189
207
  say): `tool: "mcp_*"` or `tool: ["mcp_{git,gh}_*", web_fetch]` (`*`, `?`, `[…]` and
190
208
  `{a,b}`, matched with `File.fnmatch`). `/guardrails` lists the glob as given.
@@ -203,11 +221,30 @@ All the fields a rule gives must match. Absolute and `**/` globs match the
203
221
  resolved path; other globs match the path relative to the repo root. Paths
204
222
  resolve the way the tools resolve them (against the cwd; `~` expanded).
205
223
 
206
- `outside_repo` leaves out a memory's file: a `*.md` right in the project or
207
- system memories folder (not `index.md`, not a hidden file). `write`/`edit`
208
- there is what `memory_write` does unasked, and the model is told to `edit` a
209
- memory for a small change. The memories folder's `index.md`, `.bundles/`, and
210
- the rest of chi's config folder still count as outside.
224
+ `outside_repo` is measured from the session's repo: the git work tree its
225
+ folder is in (a linked worktree is its own), or the folder itself outside a
226
+ repo. Not the call's own `cwd:`: an `execute` with `cwd:` in another checkout
227
+ is outside. Symlinks are resolved on both sides (`/tmp` and `/private/tmp` are
228
+ one folder; a link in the repo that points elsewhere is outside). Never outside:
229
+
230
+ - a memory's file: a `*.md` right in the project or system memories folder (not
231
+ `index.md`, not a hidden file). `write`/`edit` there is what `memory_write`
232
+ does unasked, and the model is told to `edit` a memory for a small change.
233
+ The memories folder's `index.md`, `.bundles/`, and the rest of chi's config
234
+ folder still count as outside, as does chi's state folder (sessions,
235
+ approvals);
236
+ - a tmp folder (`$TMPDIR`, `/tmp`, `/var/tmp`), unless the session's repo is
237
+ itself in that tmp folder: a sandbox there still gets asked about its siblings.
238
+
239
+ `git: outside_repo` (shell tools only) reads the command for git subcommands
240
+ that change a checkout: `commit`, `add`, `reset`, `checkout`, `switch`,
241
+ `rebase`, `merge`, `push`, `stash` (not `stash list`/`show`), `rm`, `mv`,
242
+ `cherry-pick`, `revert`, `pull`, `restore`, `am`. It follows `cd`,
243
+ `pushd`/`popd`, `( … )`, `git -C`, `--git-dir`/`--work-tree`, `GIT_DIR`/`GIT_WORK_TREE`
244
+ and the call's `cwd:`, and matches when one of them runs outside the session's
245
+ repo (the same `outside_repo` as above). `cd /other && git status`, `git -C
246
+ /other log` and `cd /other && bundle exec rspec` don't match. A folder the text
247
+ doesn't tell (`cd $REPO`, `cd "$(…)"`, `cd -`) doesn't either; see Limits.
211
248
 
212
249
  To switch off single rules (a bundle's, say) without editing its files, list
213
250
  them under `disable:`. A plain id switches off every rule with that id; `bundle:id`
@@ -268,7 +305,10 @@ installs a default rule set plus a short memory telling the model not to
268
305
  route around a deny. It asks before `git push`, `reset --hard`, `clean -f`,
269
306
  `branch -D`, `rebase`, `filter-branch`/`filter-repo`; `rm -rf` on `/`, `~`,
270
307
  `$HOME` or `..` paths; `curl … | sh` and `base64 -d … | sh`; writes outside the
271
- repo; shell commands that name chi's config, hooks or guardrails or
308
+ session's repo (`write-outside-repo`) and git that changes another checkout
309
+ (`git-outside-repo`: `cd ../main && git commit`, `git -C ../main add .`; it
310
+ offers once, this session and "rule in this repo", the last stored for later
311
+ sessions in that repo); shell commands that name chi's config, hooks or guardrails or
272
312
  `.git/hooks`; and answering chi's questions around `chi answer`: `chi --attach`
273
313
  or `chi -p` with stdin from a pipe, a here-string or a file
274
314
  (`chi-answer-piped`), and `curl`/`wget` to a session's `/answer` route
@@ -354,6 +394,9 @@ it as `~` or `$HOME`, so the model rarely has to spell it.
354
394
 
355
395
  Text matching on shell commands stops accidents, not a model set on getting
356
396
  around it: `sh -c`, base64, a script written earlier, `git -C` variants and
357
- aliases can get past a regex. `!cmd` lines typed by you are not checked. The only
397
+ aliases can get past a regex. `outside_repo` covers the file tools and git
398
+ only: `sed -i`, `cp` or `cat > file` into another folder from the shell aren't
399
+ asked about, and neither is git run through `sh -c`, a script, an alias, `make`
400
+ or a folder in a variable. `!cmd` lines typed by you are not checked. The only
358
401
  real defence against an adversarial model is isolation (a sandbox, a git
359
402
  identity without push rights).
data/docs/hooks.md CHANGED
@@ -341,7 +341,10 @@ the voter's own (a guard that wants the model to retry a corrected call:
341
341
  `event[:context]` is `{cwd:, repo_root:, branch:, session_id:, interface:, origin:}`
342
342
  (`interface` is `:repl`, `:worker` or `:non_interactive`). `event[:targets]` is
343
343
  what the call acts on, resolved as the tools resolve it:
344
- `{command:, paths:, cwd:, repo_root:, outside_repo:}`.
344
+ `{command:, paths:, cwd:, repo_root:, outside_repo:, git_dirs:}`. `outside_repo`
345
+ is measured from the session's repo, not the call's `cwd:`; `git_dirs` lists where
346
+ a shell call runs git that changes a checkout (`"unknown"` for a folder the
347
+ text doesn't tell; `[]` for other tools).
345
348
 
346
349
  The older flag still works: `event[:blocked] = true` with an optional
347
350
  `event[:block_reason]`. It is folded into the verdict after each hook (so it
@@ -22,5 +22,6 @@ Behavior:
22
22
  - `task_get` returns metadata and `output_path`; use `read` for output contents.
23
23
  - `task_wait` accepts `timeout`, `tail_lines` (maximum 100), and `done_pattern` (a regular expression string).
24
24
  - `task_create` accepts `env` as a JSON object string for deterministic overrides such as `PATH`; use an absolute interpreter path when that is simpler. Ruby/Bundler isolation variables remain protected.
25
+ - `execute` and `task_create` export chi's own markers into the command, set over the model's `env` (which can't set them: `Error: env key is reserved`): `SAMAGOTCHI_PARENT_SESSION` (the session's id, `chi` without one; a chi started there answers as a parent agent) and `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown). `chi self` reads the latter for its `model` row.
25
26
 
26
27
  Work that needs a model, not a shell command, goes to a child chi session instead: `delegate` / `delegate_result` have the same shape (start, then wait) and return only the child's final reply; see [Sessions: Delegating](../sessions.md#delegating).
data/docs/memory.md CHANGED
@@ -23,6 +23,8 @@ Each memory entry may have a companion file named `<name>.<model-key>.md` in the
23
23
 
24
24
  - **Key derivation**: The harness normalizes the model name sent to the server, without its host prefix (`box:qwen3.6-35b-a3b` keys as `qwen3.6-35b-a3b`; an alias keys as its target, and the alias's own key is read when the target has no overlay) (lowercase, replace non-alphanumeric with `-`, squeeze dashes) to derive the file key. For example, `qwen3.6-35b-a3b` → `qwen3-6-35b-a3b`.
25
25
  - **Saving overlays**: Pass `current_model_only: true` to `memory_write` (the harness resolves the model key automatically). This writes the content as `<name>.<model-key>.md` and skips index maintenance.
26
+ - **Which key is mine**: the system prompt's `Model:` line names the session's model key, and `chi self` (via `execute`) has a `model key` row; both follow `/model`.
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.
26
28
  - **Dormancy**: Overlays are only active under the matching model key; other models see the base entry only.
27
29
  - **Invariant**: The base entry is the contract. Overlays only add model-specific guidance and never contradict the base protocol.
28
30
 
@@ -80,6 +82,45 @@ skills`, [docs/plugins.md](plugins.md#the-skills-bundle)) adds `/skill save`,
80
82
  one-line diff after each update, and nudges a model that skips a failing
81
83
  step instead of fixing the skill.
82
84
 
85
+ ## What a bundle owns
86
+
87
+ A bundle owns the memory files it wrote, nothing else. `chi bundle install`
88
+ copies each of its `*.md` files into the scope dir; a file of the same name
89
+ that is already there is left as it is and reported `Skipped` (`--force`
90
+ overwrites it, and then the bundle owns it). A skipped file stays yours: the
91
+ bundle doesn't record it, so `chi bundle upgrade` never merges into it and
92
+ `chi bundle uninstall` never removes it. That holds for a file the user wrote,
93
+ for `chi bundle build` then installing the result on the same machine, and
94
+ for a file another bundle installed.
95
+
96
+ - **The index says where a memory came from.** The `index.md` line of a file
97
+ a bundle installed ends in `· from <bundle>`, before the description:
98
+ `- **memory_guide** · system · 2026-10-03 · 5120 · from samagotchi-system —
99
+ …`. `memory_write`, `write` and `edit` keep it. A bundle installed by chi
100
+ 0.16 or earlier gets it on its next install or upgrade.
101
+ - **Uninstall moves, it doesn't delete.** The bundle's memory files go to
102
+ `$XDG_CONFIG_HOME/samagotchi/memories/.bundles/.trash/<bundle>-<YYYYmmdd-HHMMSS>/`,
103
+ and the output names them and the dir:
104
+ `Moved to the trash: notes.md (…/.trash/team-notes-20261003-120000)`. So
105
+ does an upgrade's removal of a file the new version no longer ships. A
106
+ file you edited still needs `--force`. `chi bundle trash` lists the trash
107
+ (oldest first, with file count, size, and age); `--empty` deletes all
108
+ folders, `--older-than DAYS` keeps only recent ones, and `--dry-run`
109
+ previews. Hooks, rules and the plugin live in the bundle's own dir and
110
+ are deleted with it.
111
+ - **A file two bundles list stays** until the last of them is uninstalled
112
+ (`Kept identity.md: bundle samagotchi-system has it too`); its index line
113
+ then names the bundle that still has it.
114
+ - **Installs by chi 0.16 or earlier** recorded skipped files too. Such a bundle may
115
+ still list a file that was yours; uninstalling it moves that file to the
116
+ trash rather than deleting it, so check the `Moved to the trash` line and
117
+ move a file back if it was yours (and its line in `index.md` comes back
118
+ with the next `memory_write`, `write` or `edit` of it).
119
+ - **`chi bundle build`** packs your memories, not other bundles': a memory an
120
+ installed bundle owns is left out with a line (`Left out identity.md:
121
+ installed by bundle samagotchi-system (name it to include it)`); naming it
122
+ in `FILES...` includes it.
123
+
83
124
  ## Bundle profiles: core and dev
84
125
 
85
126
  Every bundle chi ships (the system bundle aside) belongs to one of two
data/docs/plugins.md CHANGED
@@ -229,8 +229,9 @@ chi's own. Path and command rules need to know what a call acts on, and that
229
229
  is what `targets:` says:
230
230
 
231
231
  - `paths:`: files the call reads or writes, absolute or relative to `cwd:`
232
- (else the session's directory). `path:` globs, `outside_repo` and the
233
- protected paths (chi's config, …) match them.
232
+ (else the session's directory). `path:` globs, `outside_repo` (measured
233
+ from the session's repo, whatever the call's `cwd:`) and the protected
234
+ paths (chi's config, …) match them.
234
235
  - `command:`: a shell command the call runs; `command:` rules match it.
235
236
  - `cwd:`: where it runs, for the repo root and relative paths.
236
237
 
@@ -478,6 +479,8 @@ full label, `plugin.rb (bundle my-bundle)`.
478
479
  | `ctx.session_id` | the session's id (nil before there is one) |
479
480
  | `ctx.cwd` | the session's working directory |
480
481
  | `ctx.repo_root` | the git checkout holding `cwd`, or nil |
482
+ | `ctx.model` | the model the session runs on now: its resolved ref (`host:id`), right after `/model` too |
483
+ | `ctx.model_key` | that model's memory overlay key (`<name>.<key>.md`, what `memory_write current_model_only` writes) |
481
484
  | `ctx.settings` | the bundle's settings, frozen |
482
485
  | `ctx.data_dir` | `$XDG_STATE_HOME/samagotchi/plugins/<bundle>/`, created on first use. A session's own state goes in `sessions/<id>.json` or `sessions/<id>/` there: chi removes it when the session is deleted, discarded or pruned |
483
486
  | `ctx.log` | `ctx.log.info(:event, key: value)`: debug-log records tagged `plugins`, with `bundle=<bundle>` |