samagotchi 0.148.0 → 0.150.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/README.md +4 -2
  4. data/docs/architecture.md +3 -3
  5. data/docs/bundles.md +38 -1
  6. data/docs/cli.md +76 -24
  7. data/docs/configuration.md +8 -6
  8. data/docs/guardrails.md +2 -1
  9. data/docs/hooks.md +2 -1
  10. data/docs/internals/background-tasks.md +1 -1
  11. data/docs/internals/bridge.md +8 -0
  12. data/docs/internals/bundles.md +42 -14
  13. data/docs/internals/objectives.md +269 -0
  14. data/docs/internals/web-frontend.md +4 -1
  15. data/docs/internals/web-push.md +2 -2
  16. data/docs/internals/working-set.md +5 -2
  17. data/docs/memory.md +47 -9
  18. data/docs/plugins.md +10 -2
  19. data/docs/releasing.md +1 -1
  20. data/docs/sessions.md +45 -2
  21. data/docs/system-one.md +17 -1
  22. data/docs/testing.md +5 -0
  23. data/lib/samagotchi/atomic_file.rb +3 -0
  24. data/lib/samagotchi/bootstrap_command.rb +5 -2
  25. data/lib/samagotchi/bridge/card_store.rb +52 -7
  26. data/lib/samagotchi/bridge.rb +1 -1
  27. data/lib/samagotchi/bundle_command.rb +91 -68
  28. data/lib/samagotchi/bundler_env.rb +24 -0
  29. data/lib/samagotchi/bundles/coordinator/manifest.yml +6 -4
  30. data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
  31. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +9 -0
  32. data/lib/samagotchi/bundles/decision-card/manifest.yml +13 -0
  33. data/lib/samagotchi/bundles/decision-card/plugin.rb +280 -0
  34. data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
  35. data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
  36. data/lib/samagotchi/bundles/github-pr/plugin.rb +2 -1
  37. data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +3 -1
  38. data/lib/samagotchi/bundles/known-names/manifest.yml +3 -2
  39. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +2 -2
  40. data/lib/samagotchi/bundles/source-links/manifest.yml +3 -3
  41. data/lib/samagotchi/bundles/system/manifest.yml +3 -3
  42. data/lib/samagotchi/bundles/system/memory_guide.md +5 -5
  43. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  44. data/lib/samagotchi/chi_home.rb +4 -2
  45. data/lib/samagotchi/chi_shim.rb +2 -1
  46. data/lib/samagotchi/child_env.rb +10 -3
  47. data/lib/samagotchi/config.rb +7 -1
  48. data/lib/samagotchi/context_note.rb +17 -5
  49. data/lib/samagotchi/engine.rb +113 -6
  50. data/lib/samagotchi/engine_peers.rb +11 -0
  51. data/lib/samagotchi/git_env.rb +43 -0
  52. data/lib/samagotchi/guardrails/context.rb +2 -0
  53. data/lib/samagotchi/guardrails/pair_boundary.rb +3 -2
  54. data/lib/samagotchi/guardrails/targets.rb +4 -1
  55. data/lib/samagotchi/llm/chat_loop.rb +1 -1
  56. data/lib/samagotchi/memory_bundle/asset_installer.rb +16 -2
  57. data/lib/samagotchi/memory_bundle/file_standing.rb +167 -0
  58. data/lib/samagotchi/memory_bundle/installed_bundle.rb +62 -1
  59. data/lib/samagotchi/memory_bundle/installer.rb +161 -52
  60. data/lib/samagotchi/memory_bundle/listing.rb +4 -3
  61. data/lib/samagotchi/memory_bundle/merger.rb +209 -8
  62. data/lib/samagotchi/memory_bundle/placeholder.rb +2 -1
  63. data/lib/samagotchi/memory_bundle/profile.rb +7 -3
  64. data/lib/samagotchi/memory_bundle/provenance.rb +178 -20
  65. data/lib/samagotchi/memory_bundle/remerge.rb +167 -0
  66. data/lib/samagotchi/memory_bundle/shipped_history.rb +79 -0
  67. data/lib/samagotchi/memory_bundle/shipped_update.rb +89 -24
  68. data/lib/samagotchi/memory_bundle/source.rb +3 -3
  69. data/lib/samagotchi/memory_bundle/status.rb +21 -3
  70. data/lib/samagotchi/memory_bundle/system_bundle.rb +98 -33
  71. data/lib/samagotchi/memory_bundle/text_merge.rb +399 -0
  72. data/lib/samagotchi/memory_bundle/trash.rb +58 -2
  73. data/lib/samagotchi/memory_bundle/uninstaller.rb +26 -6
  74. data/lib/samagotchi/note_command.rb +1 -1
  75. data/lib/samagotchi/note_delivery.rb +6 -2
  76. data/lib/samagotchi/objectives.rb +395 -0
  77. data/lib/samagotchi/objectives_audit.rb +115 -0
  78. data/lib/samagotchi/objectives_check.rb +365 -0
  79. data/lib/samagotchi/objectives_command.rb +192 -0
  80. data/lib/samagotchi/plugin/api.rb +2 -1
  81. data/lib/samagotchi/plugin/context.rb +21 -1
  82. data/lib/samagotchi/project_env.rb +3 -2
  83. data/lib/samagotchi/self_report.rb +2 -0
  84. data/lib/samagotchi/session_commands.rb +15 -0
  85. data/lib/samagotchi/session_continue.rb +3 -0
  86. data/lib/samagotchi/session_inbox.rb +14 -6
  87. data/lib/samagotchi/session_manager.rb +1 -1
  88. data/lib/samagotchi/steer.rb +24 -4
  89. data/lib/samagotchi/testing/boundary.rb +2 -0
  90. data/lib/samagotchi/testing/host.rb +1 -0
  91. data/lib/samagotchi/testing/minitest.rb +3 -0
  92. data/lib/samagotchi/testing/rspec.rb +3 -0
  93. data/lib/samagotchi/testing/sandbox.rb +3 -1
  94. data/lib/samagotchi/tool_activity.rb +17 -0
  95. data/lib/samagotchi/tool_declarations.rb +34 -0
  96. data/lib/samagotchi/tools/builtin_calls.rb +2 -1
  97. data/lib/samagotchi/tools/builtins.rb +4 -1
  98. data/lib/samagotchi/tools/delegate.rb +2 -1
  99. data/lib/samagotchi/tools/objectives_tool.rb +75 -0
  100. data/lib/samagotchi/tools/peers.rb +3 -0
  101. data/lib/samagotchi/tools/task_runtime.rb +13 -10
  102. data/lib/samagotchi/turn_flow.rb +3 -1
  103. data/lib/samagotchi/update_command.rb +76 -28
  104. data/lib/samagotchi/update_hint.rb +4 -1
  105. data/lib/samagotchi/version.rb +1 -1
  106. data/lib/samagotchi/web/auto_update.rb +8 -8
  107. data/lib/samagotchi/web/auto_update_record.rb +6 -4
  108. data/lib/samagotchi/web/message_display.rb +9 -0
  109. data/lib/samagotchi/web/participant_view.rb +2 -2
  110. data/lib/samagotchi/web/public/app.js +15 -3
  111. data/lib/samagotchi/web/public/block_open.js +13 -0
  112. data/lib/samagotchi/web/public/bubbles.js +4 -2
  113. data/lib/samagotchi/web/public/cards_place.js +30 -9
  114. data/lib/samagotchi/web/public/commands.js +4 -1
  115. data/lib/samagotchi/web/public/details_motion.js +2 -1
  116. data/lib/samagotchi/web/public/index.html +20 -14
  117. data/lib/samagotchi/web/public/objectives.css +80 -0
  118. data/lib/samagotchi/web/public/objectives_entry.js +22 -0
  119. data/lib/samagotchi/web/public/objectives_model.js +100 -0
  120. data/lib/samagotchi/web/public/objectives_moves.js +145 -0
  121. data/lib/samagotchi/web/public/objectives_strip.js +242 -0
  122. data/lib/samagotchi/web/public/objectives_wiring.js +62 -0
  123. data/lib/samagotchi/web/public/stage_model.js +5 -6
  124. data/lib/samagotchi/web/public/stage_view.js +8 -7
  125. data/lib/samagotchi/web/public/stream_handlers.js +6 -1
  126. data/lib/samagotchi/web/public/turn_block.js +34 -19
  127. data/lib/samagotchi/web/public/turn_html.js +4 -1
  128. data/lib/samagotchi/web/public/turn_model.js +1 -1
  129. data/lib/samagotchi/web/public/update_sheet.js +8 -3
  130. data/lib/samagotchi/web/public/working_set_view.js +10 -7
  131. data/lib/samagotchi/web/push/notifier.rb +7 -9
  132. data/lib/samagotchi/web/session_hub.rb +2 -1
  133. data/lib/samagotchi/web/session_show.rb +12 -1
  134. data/lib/samagotchi/web/update_ops.rb +2 -1
  135. data/lib/samagotchi/worker.rb +3 -0
  136. data/lib/samagotchi/worker_inbound.rb +16 -8
  137. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcbb626a7c357711e1403ac1c9825d0930eb5eb7d4e9f36e0cf004f96b0a4de7
4
- data.tar.gz: d5a85b7198fa381dd4a352389386c3d4cf5a96d89a06834e13d9518c5412eb54
3
+ metadata.gz: caeb209690eb9b78ab3977c53ed4c7bbe7388b0a15e51b59b70da653ac9a9bdc
4
+ data.tar.gz: 43cd03b5039cc16b588323f82ca0e063053d4b78de83b9785e66dd7dbf8d4a71
5
5
  SHA512:
6
- metadata.gz: b9e73f35692277dab5f9cb18435db900d35b941b64af1c878e7f3503ece14e5f46a35f120a457a53fc8d9d3fcb81c302af370b93e7920ac71d5aec2b723cd250
7
- data.tar.gz: 1bb3118536d48f1b9741d811cf9c2df9a85766dfc15e1fb758ac4caa99b95123774ef51c32afe01dc66d208562eef6ec2d7a8c236ea14264d56700f45952de8d
6
+ metadata.gz: 84a48999dd02619caa8900b8e2897614c93089519fa5e87d05e5f743b7073546e61b2c4e49a768798f1e612ef29eb768a86c7e69197e1880348ab5b767291ec4
7
+ data.tar.gz: 65ff02a943a5d58615976d531b22114c40d015b24098d230b5b389abff7bdacd33cabcb9b5622a3b57d054d2e156878a945a6e83895547ffe6dba8c929e0ab5b
data/CHANGELOG.md CHANGED
@@ -7,6 +7,65 @@ and commands may change between minor versions. How releases are made:
7
7
  [docs/releasing.md](docs/releasing.md).
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.150.0] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - The web's steps block closes to its summary line after a turn and after a reload, whatever its steps said; opened, it shows the outline as before. A canceled or failed turn with a narrated step stays open. [docs/cli.md](docs/cli.md#how-the-page-draws-a-turn)
15
+ - The web's working-set footprint is now the steps block's summary line, in body colour, the steps' count at its right (a phone shows the footprint alone); there is no separate line under the block. A click on its words opens the turn's list, the chevron or the count opens the steps. [docs/cli.md](docs/cli.md#the-working-set)
16
+ - Check this also reads the agent's visible answers, never its thinking, so a claim about what the agent said or asked can be confirmed. They settle only that: a claim of work with nothing but the agent's words behind it isn't `confirmed`. With `objectives.check_model` they go to that host too. [docs/sessions.md](docs/sessions.md#objectives)
17
+ - An upgrade now merges a memory you edited with the bundle's new version instead of leaving it behind. `chi bundle upgrade`, `chi update` and the automatic system bundle update run a text merge (needs `git`): a clean merge replaces the file, with the old one in the bundle trash and the report saying `merged 0.8.0 into your x.md`, and `--dry-run` says `would merge`. When a chi start updates the system bundle, a terminal launch says so in one line. Entries both sides added under a `Changelog` heading are kept, yours first. A real conflict, no `git`, or no recorded base leaves your file untouched, as before, and a file marked by an older chi can stay behind too (see the next line).
18
+ - A memory left behind as a conflict gets another try without waiting for the bundle's next version: the system bundle retries its files marked conflict at every chi start and on `chi update`, the other bundles chi ships on `chi update`, and any other bundle on `chi bundle upgrade`. It touches memory files only, and a row reads `0.8.0 merged 0.8.0 into your x.md`. A file marked by an older chi merges normally when its recorded base is a version chi shipped, checked against the samagotchi gems still installed. Otherwise that base may be your own text, so the file merges only if the bundle takes out or rewords no line it still holds. If it does, it stays behind until resolved by hand, with `--agent` or with `--force`, and the retry can't fix that.
19
+ - Everything that reports an edited bundle file now says what happened in one wording: `merged 0.8.0 into your x.md`, or `x.md stays behind 0.8.0: missed ## Steps, ## Rules and 2 more` with the reason when there is one (`no merge possible (git not found)`). The record keeps why and what was missed, and the bundle's text is saved at `.bundles/<name>/incoming/<file>` until the file settles. `kept your edits` is gone from `chi update`, whose `--json` rows gain `merged` next to `kept`, and from `chi bundle upgrade`, `chi bootstrap` and the system bundle's line at a start.
20
+
21
+ ### Fixed
22
+
23
+ - `bundle exec chi home set --ruby X` no longer records the bundle's vendor folder as X's gems when the bundle has a `path` and holds samagotchi: the probe of X still took `BUNDLER_SETUP`. A home set that way keeps its wrong `gem_home` until you run `chi home set` again.
24
+ - Under a `bundle exec chi`, a tool's plain `ruby` no longer loads Bundler: `BUNDLER_SETUP` still reached it, so in a folder whose Gemfile needs a missing gem `ruby -e` exited 1, and with Bundler 4 (`BUNDLE_LOCKFILE`) a folder with any other Gemfile had its lock written over chi's own `Gemfile.lock`. `execute`, tasks, `!cmd`, context sources and MCP servers all run without them, and `task_create` refuses them in `env`, saying to write `KEY=value cmd` in the command instead.
25
+ - `chi bundle install <git url>` run from a git hook or `git rebase --exec` no longer takes the repo git's variables name for its own: `GIT_WORK_TREE` failed the clone, `GIT_OBJECT_DIRECTORY` wrote the bundle's objects into that repo, and `GIT_DIR` alone recorded that repo's HEAD as the bundle's `source_commit`. Settings given around chi (`git -c url.….insteadOf=…`, `GIT_CONFIG_COUNT`) still apply to the clone. `delegate`'s `cwd:` check also asks about the folder it was given, as do the known-names (the git name), github-pr (the branch, `gh pr view`) and source-links (the remote) bundles.
26
+ - The web no longer loses a reply you steered: a line sent while a text-only step streamed left that step's whole text hidden, with no row or summary to open, live and after a reload. The step is now a row of the steps block (`1 step`), like any other text step.
27
+ - A web card updated just as a finished turn slid into the history no longer stays stuck on its first text until a reload (`/short` could stay on "Short version: …").
28
+ - A web card no longer lands a turn too early for good after `!rollback` of a canceled or failed turn: the rollback takes the turn out of the count. A rolled-back or failed turn's own cards stay where the turn was, without their buttons, instead of being read as the next turn's.
29
+ - A web card no longer lands one turn late for good after a worker was killed in the next turn (`kill -9`, a pod that didn't wait): the turn is counted, since its prompt stays in the session.
30
+ - A web card keeps its place after a reload when the turn after it failed with nothing kept: above the failed prompt, as it showed live, not below it, and an older card no longer lands one turn too late. A failed prompt isn't counted as a turn any more.
31
+ - `chi bundle list` and `chi self` say when a bundle has a file behind (`1 file behind`), and `chi bundle status` says it per file. `issues=` now counts files that stay behind, are missing or can't be read: an edit alone is no issue. A file `chi bundle status` can't read no longer crashes it.
32
+ - Every merge now leaves a note on the file's record, since a worker's reaches only the log and an automatic update's rows no one reads: `chi bundle status` shows it (with the old copy's folder), `chi update` names an earlier one in its row, and a row printed to you, or `chi bundle upgrade`'s output, ends the note. An automatic update names it without clearing it.
33
+ - `chi update` no longer crashes on an edited memory it can't read at a newer version: that bundle's row fails and says which file, and the others go on.
34
+ - The start-up hint `… can be updated: chi update` counts a bundle whose file would now merge at the same version.
35
+ - The update sheet shows `chi update`'s own words for an edited file, and the phone's push after an automatic update counts both sides (`Merged the update into 1 file you edited. 2 files stay behind.`): a merge used to go unmentioned, and every kept file read as `Kept your edits`.
36
+ - `chi update` no longer reads `up to date` for a file that stays behind at the same version: a real conflict, no `git`, a timeout, a file it can't read, or one left for the bundle's next version now name the file and why, and the summary counts them.
37
+ - A file marked by an older chi whose base chi can't vouch for no longer claims to hold your edits: it says chi can't tell, and why.
38
+ - `chi update`'s table no longer carries the trash's long path: the row says `old copies: chi bundle trash`, and `--json`'s `trash_copy` has the full line.
39
+ - The `--force` suggestions say what happens to your file: it is copied to the trash first.
40
+ - `chi bundle install --force` and `chi bundle upgrade --force` no longer destroy what they overwrite: a memory that differs from the bundle's, your own same-name file too, is copied to the bundle trash first, and the output names it (`Copied to the trash first: …`; `chi bundle trash` lists it).
41
+ - An install, upgrade or `chi update` that replaces or removes a hook, rule, plugin or script you edited copies it to the bundle trash first, under its own subfolder (`hooks/x`, `scripts/sub/x`) at any depth, and says so. A `--dev` install's links are left out.
42
+ - `chi bundle uninstall`, with `--force` or without, copies a hook, rule, plugin or script you edited to the bundle trash before removing it, and names it. Only memories reached the trash before.
43
+ - An install, upgrade or uninstall that can't read a file or write the bundle trash fails with a message naming the file, not a backtrace, and running it again after fixing that picks up where it stopped.
44
+ - A memory whose conflict was resolved through `chi bundle upgrade --agent` keeps its description in the bundle's record.
45
+ - `chi bundle upgrade --agent` no longer calls a conflict resolved when nothing merged it. A file the session didn't change, or one still holding conflict markers, stays a conflict, the output says so (`Nothing changed in X: still behind.`), and the command exits 2. It used to mark the file resolved, so later upgrades kept it without a word and the bundle's change never arrived.
46
+ - An edited memory with no recorded base keeps surviving upgrades as a conflict. Its own text used to become the base, so the following upgrade overwrote it with nothing in the trash. A record an older chi already left that way (marked conflict, the base being your text) stays behind when the update would take out a line it holds. Otherwise it takes the update, with your text copied to the bundle trash first and a line saying so.
47
+ - A plain `chi bundle install` keeps the conflict mark on a file it skipped, so `chi bundle status` still shows the file missed an update.
48
+ - `chi bundle upgrade --dry-run` no longer prints a false `Checksum mismatch` for a memory you edited, and its `Kept` line now names those files. This only changes a dry run's output.
49
+ - `chi bundle upgrade --agent` no longer takes your file as the new base when the bundle's copy disappears during the session. The next upgrade used to overwrite the edit with nothing in the trash; the file now stays a conflict and the command says so.
50
+ - An install or upgrade no longer fails at its last step on a memory that isn't valid UTF-8.
51
+ - A Changelog merge compares whole entries, nested items included: your `- a` / ` - note` beside the bundle's `- b` / ` - note` no longer loses the bundle's ` - note`.
52
+ - Putting back a deleted system memory at the same version now restores that file alone. It used to run a whole upgrade, replacing hooks, rules and the plugin under live sessions.
53
+
54
+ Update with `chi update` (github-pr 0.6.3, known-names 0.1.9, source-links 0.6.3: their git calls ask about the session's folder, and they need this chi; dev profile 0.7.6).
55
+
56
+ ## [0.149.0] - 2026-11-04
57
+
58
+ ### Added
59
+
60
+ - `/objectives` gives a session a short list of what it is to achieve (`set A | B`, `add`, `done`, `drop`), kept in the conversation as a note; a continue carries it, and a session with no list gets no note and no prompt line. A first version to try. [docs/sessions.md](docs/sessions.md#objectives)
61
+ - The `objectives` tool lets the model record a move on an objective (a line, optional evidence): information only, it never changes the list, and `/objectives` shows each objective's latest. Declared on `full` and the coordinator toolkit, so a `full` request carries its schema with or without a list; other kits name it in the not-loaded line. The coordinator bundle's skill says a move or a `done` is never approval to merge. [docs/sessions.md](docs/sessions.md#objectives)
62
+ - The `decision-card` bundle (in the `dev` profile): in a session with objectives, a long answer ending in a question gets a card with a short summary and the question, which you answer in the composer. One side request per such answer; a session with no list makes none, and nothing enters the conversation. `chi bundle uninstall decision-card` takes it away. Plugins get `ctx.objectives`. [docs/bundles.md](docs/bundles.md#the-decision-card-bundle)
63
+ - The web shows a session's objectives in the conversation: a strip above it with the current list, each list note drawn as pills or a line of what you changed, and the model's moves as chips above an answer. A tap edits the list with no message or turn (`/objectives edit ID TEXT` is new, and the terminals keep the command), Not yet tells the agent in a message, and on a phone it is one line and a bottom sheet. The `objectives` tool gains `propose`: the model suggests a change and you apply it from a card; it never changes the list by itself. `decision-card`'s `/short` gives a long answer's short version on demand. A session with no list shows nothing new. The tool's schema changed, so the prompt cache misses once. [docs/sessions.md](docs/sessions.md#objectives)
64
+ - Check this (`/objectives check O2`, or the button in the web's list): on request, one side request reads an objective's latest move against what the session's tools returned and people said, and reports a verdict with reasons in a card. Information only: no status changes and nothing enters the conversation. `objectives.check_model` picks a cheaper model, whose host then gets up to 24,000 characters of tool output, known secret shapes redacted.
65
+ - `objectives.system_one: audit` (off by default) saves a System 1 yes/no per active objective on incoming messages and finished answers, for `chi judgments`; nothing acts on it. Its text goes to the System 1 host.
66
+
67
+ Update with `chi update` (coordinator 0.8.0, decision-card 0.2.0, dev profile 0.7.5). `chi update` installs decision-card for anyone on the dev profile; it does nothing in a session with no objectives.
68
+
10
69
  ## [0.148.0] - 2026-11-04
11
70
 
12
71
  ### Added
@@ -5611,7 +5670,9 @@ and long-lived sessions.
5611
5670
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5612
5671
  hotkey panel that send selected text or the clipboard to your sessions.
5613
5672
 
5614
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.148.0...HEAD
5673
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.150.0...HEAD
5674
+ [0.150.0]: https://github.com/dm1try/samagotchi/compare/v0.149.0...v0.150.0
5675
+ [0.149.0]: https://github.com/dm1try/samagotchi/compare/v0.148.0...v0.149.0
5615
5676
  [0.148.0]: https://github.com/dm1try/samagotchi/compare/v0.147.0...v0.148.0
5616
5677
  [0.147.0]: https://github.com/dm1try/samagotchi/compare/v0.146.0...v0.147.0
5617
5678
  [0.146.0]: https://github.com/dm1try/samagotchi/compare/v0.145.0...v0.146.0
data/README.md CHANGED
@@ -53,8 +53,10 @@ chi update # the gem, then the bundles and the desktop helper
53
53
  `chi update` installs the newest samagotchi from rubygems.org (old versions
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
- and the macOS helper (rebuilt only when its sources changed). Memory files you
57
- edited are kept, and the table says which. Running sessions keep their chi
56
+ and the macOS helper (rebuilt only when its sources changed). A memory file you
57
+ edited takes the update in a text merge (the old file goes to the bundle trash),
58
+ or stays behind as it is when no merge is possible, and the table says which
59
+ and why. Running sessions keep their chi
58
60
  until restarted (`chi sessions restart ID`, or the Restart badge on the web);
59
61
  new and woken ones start on the new chi. A running `chi web` needs a restart,
60
62
  and its page says so. From a checkout, `git pull` instead. See
data/docs/architecture.md CHANGED
@@ -101,8 +101,8 @@ terminal renders the events it gets over the Bridge.
101
101
  | UI | `Samagotchi::TerminalUI` | REPL (Reline), rendering, REPL commands. Delegates all core work to an `Engine`. |
102
102
  | Model loops | `KernelLoop` (via `LLM::NativeBackend`), `LLM::ChatLoop` | The model↔tool loop: raw prompt, or a chat API (OpenAI chat, Anthropic Messages), chosen per host (see below). |
103
103
  | Adapters | `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | Raw-prompt servers, the OpenAI chat API, Anthropic's Messages API, and the HTTP they share. |
104
- | Tools | `lib/samagotchi/tools/*` | Execute, read, edit, write, memory, task_*, web_fetch, delegate/delegate_result, ask_user_question, reminders, send_note, list_sessions, context_read, forget_outputs, find_tools/call_tool (the tool index), plus runtime/output-guardrails. |
105
- | Bundles | `lib/samagotchi/memory_bundle/*`, `BundleCommand` | `chi bundle`: install/upgrade (3-way merge), build, uninstall; the install record under `.bundles/<name>/` that the hook, guardrail and plugin loaders read ([internals/bundles.md](internals/bundles.md)). |
104
+ | Tools | `lib/samagotchi/tools/*` | Execute, read, edit, write, memory, task_*, web_fetch, delegate/delegate_result, ask_user_question, reminders, send_note, list_sessions, context_read, objectives, forget_outputs, find_tools/call_tool (the tool index), plus runtime/output-guardrails. |
105
+ | Bundles | `lib/samagotchi/memory_bundle/*`, `BundleCommand` | `chi bundle`: install/upgrade (3-way, with a text merge of edited memories), build, uninstall; the install record under `.bundles/<name>/` that the hook, guardrail and plugin loaders read ([internals/bundles.md](internals/bundles.md)). |
106
106
  | Background | `Samagotchi::Worker`, `Samagotchi::SessionManager` | `SessionManager` forks/spawns one worker process per session; its `Worker` builds the `Engine` and `Bridge` (no terminal rendering) and runs queued turns. The spawned process (own process group) forks the worker, which `setsid`s and is reparented to init once its starter exits (it waits until the worker holds `owner.lock`, so the spawner's `Process.detach` waiter tells a starting worker): nothing that walks the spawner's process tree reaches it (`SAMAGOTCHI_WORKER_DETACH=1`, `detach_from_spawner`). The lock's pid is the worker's; `spawn`'s `child_pid` is its starter's. |
107
107
  | Web | `Samagotchi::Web::App`, `Samagotchi::Web::Server`, `Samagotchi::Web::SessionHub` | Rack+WEBrick on one port, `127.0.0.1:4567` (index.html + `/api/*` + SSE); with `web.host: lan` also on this machine's LAN address, behind an access token (`Web::Lan`, `Web::Token`). The hub is chi web's projection of the session list, pushed to every tab over `GET /api/events`. |
108
108
  | Sessions | `Samagotchi::Session`, `SessionManager` | File `sessions/<uuid>.json` + sidecar `input/`/`output/`; retention 14d/500, `updated_at desc`, lazy sweep. |
@@ -394,7 +394,7 @@ record starts at `phase: installed` and the run goes on as below. Ahead of every
394
394
  `SessionHub#auto_offer_restart` (`auto_offers`, in memory) when a run on record for the newest installed chi is closed with
395
395
  this chi web still older, or that chi has waited `HELD_FOR` (1 h): the trying goes on, and the page has its buttons back;
396
396
  `restart_failed` and a failed install call it at once. Its record is `update_check.json`'s `auto`:
397
- `{version, from, at, kept, phase: installed|workers|web|retrying|done|failed, workers: {restarted, left}, detail, pushed}`,
397
+ `{version, from, at, kept, merged, phase: installed|workers|web|retrying|done|failed, workers: {restarted, left}, detail, pushed}`,
398
398
  in Ruby the value object `Web::AutoUpdateRecord`, which owns that shape (`from_h`, `to_store`).
399
399
  With `update.restart: never` the run ends at the install (`chi_notice` `installed`). With `idle` it goes on
400
400
  (`phase` `installed` → `workers` → `web` → `retrying` → `done`): `UpdateOps#restart_workers(select:)` over the stale workers whose
data/docs/bundles.md CHANGED
@@ -858,7 +858,7 @@ by themselves there.
858
858
  session: the coordinator session picks the change up from the handoff,
859
859
  and merges as it always does. Experimental: it may go if it doesn't earn its place.
860
860
  - **The coordinator toolkit.** The bundle declares `coordinator`: `lean`
861
- plus `delegate`, `delegate_result` and `list_sessions`
861
+ plus `delegate`, `delegate_result`, `list_sessions` and `objectives` (the skill records moves with it)
862
862
  ([Toolkits](configuration.md#toolkits)). `/coordinate` puts a fresh session
863
863
  on it before it asks the model (`ctx.use_toolkit`). When chi refuses (a reply
864
864
  or a turn already there) and the session's toolkit has no `delegate`, the
@@ -1240,3 +1240,40 @@ for one model family, loaded into the prompt only for the models a note's
1240
1240
  through upgrades; other notes stack with the bundle's
1241
1241
  ([Model notes](memory.md#model-notes)).
1242
1242
 
1243
+ ## The decision-card bundle
1244
+
1245
+ `chi bundle install decision-card` (or the `dev` profile) installs the bundle shipped with chi. It is a try-out, written
1246
+ only against this API (`lib/samagotchi/bundles/decision-card/plugin.rb`). It does nothing in a session with no
1247
+ [objectives](sessions.md#objectives): no request, no card.
1248
+
1249
+ In a session with an active objective, an answer that is long and ends in a question gets a card when the turn is over:
1250
+ a short summary, the question, and the objective it concerns when the model names one. You answer in the composer.
1251
+ The card has no buttons, so it invents no options, and it is no "needs you": no push, no waiting mark.
1252
+
1253
+ - **The gate**: a completed owner's turn (not a stopped one, a participant's or a `--non-interactive` one), at least
1254
+ `min_chars` characters, a question mark that ends a word in the last `tail_chars` of the prose (code is left out). All of
1255
+ it is read before any request.
1256
+ - **The request**: one side answer (`ctx.ask_model`) about the turn's prompt and answer, with the list in its prompt. It
1257
+ counts in `/stats`' side requests under `decision-card`. If the model says the answer asks nothing, or leaves out the
1258
+ summary or the question, there is no card.
1259
+ - **It may be late.** It runs on a thread, so the turn's end doesn't wait, and a one-slot local server answers it after
1260
+ the running request. A new turn cancels it, which frees that slot, and a card still to come is dropped.
1261
+ - **A shown card folds** to a line (`Asked: …`) when your next turn starts (a reminder's, a child's report's or a participant's turn leaves it open): in the web a row in the same place; the terminals
1262
+ print it as an update.
1263
+ - **A failed request** is only logged (`no_card` in the debug log): no card, no notice, no push.
1264
+ - **`/short`**: the last answer's short version on demand, in a card, by the same side request. The web's objectives offer it
1265
+ under the newest long answer of a session with a list (Short version), between turns. Typed, it works in any session; during
1266
+ a turn it refuses, since that turn's answer isn't there yet.
1267
+ - **Nothing else changes**: nothing enters the conversation, so the next request is byte for byte what it would be
1268
+ without the bundle. The card lives in the session's `cards.json`, as any card. `chi bundle uninstall decision-card` takes
1269
+ it all away (a member of a profile you uninstall stays out); a session you have open needs `chi sessions restart ID`.
1270
+
1271
+ ```yaml
1272
+ bundles:
1273
+ decision-card:
1274
+ min_chars: 1200 # how long an answer is long
1275
+ tail_chars: 300 # how far from the end the question may be
1276
+ model: null # a cheap configured model ("host:id" or an alias); null is the session's own
1277
+ timeout: 120 # seconds the side request may take (a number)
1278
+ max_tokens: 300 # the side answer's limit
1279
+ ```
data/docs/cli.md CHANGED
@@ -228,9 +228,11 @@ $ chi models qwen # only the names containing "qwen" (any case)
228
228
  ```
229
229
  component from to status
230
230
  chi (gem) 0.2.0 0.3.0 updated
231
- system bundle 0.2.0 0.3.0 updated (kept your edits in identity.md: chi bundle diff samagotchi-system identity.md)
231
+ system bundle 0.2.0 0.3.0 updated (identity.md stays behind 0.3.0: missed ## Who you are (chi bundle diff samagotchi-system identity.md))
232
232
  btw 0.1.1 up to date
233
- known-names 0.1.0 0.1.1 updated
233
+ known-names 0.1.0 0.1.1 updated (merged 0.1.1 into your known_names.md; old copies: chi bundle trash)
234
+ coordinator 0.8.0 merged 0.8.0 into your skill_coordinator.md; old copies: chi bundle trash
235
+ loop-guard 0.2.0 notes.md stays behind 0.2.0: no merge possible (git not found) (chi bundle diff loop-guard notes.md)
234
236
  core 0.1.0 0.2.0 updated (+ new-bundle)
235
237
  infra_tools 1.0.0 skipped (not from chi)
236
238
  infra_tools warn api plugin.rb:3: ctx.old_name isn't in chi 0.3.0's plugin API (fix: docs/plugins.md has the API)
@@ -238,15 +240,17 @@ Chi Helper 0.2.0 up to date (launch file refreshed)
238
240
  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
239
241
  Bundles not from chi are left as they are: their author's next version, or chi bundle upgrade NAME, is what changes them
240
242
  Also shipped, not installed: dev (mcp, skills, source-links) (chi bundle install NAME)
241
- done
243
+ 2 files stay behind (see above)
242
244
  ```
243
245
 
244
246
  - `--to VERSION` installs exactly that gem version instead of checking for the
245
247
  newest release. It must be newer than the running chi; an equal or older
246
248
  version is refused.
247
249
  - `--json` writes the component rows as one JSON array on stdout. Each row has
248
- `component`, `from`, `to`, `status`, `failed`, `kept` and `level` fields;
249
- `kept` is an array of edited filenames, and `level` is `warn` or `error` on a
250
+ `component`, `from`, `to`, `status`, `failed`, `kept`, `merged`, `earlier`, `trash_copy` and `level` fields;
251
+ `kept` is an array of edited filenames left behind, `merged` of those this run
252
+ merged, `earlier` of those an earlier merge's note names, `trash_copy` the full line
253
+ saying where the old copies went (the table says `chi bundle trash`), and `level` is `warn` or `error` on a
250
254
  re-check's row (below), `null` on the others. Install progress is written to
251
255
  stderr.
252
256
 
@@ -304,11 +308,36 @@ done
304
308
  the row says what it did.
305
309
  - **Shipped bundles**: each one you installed from chi (`chi bundle install
306
310
  NAME`) is upgraded when chi ships a newer version. Memory files get the
307
- 3-way merge of `chi bundle upgrade`: an unedited file is updated, an edited
308
- one that the new version also changes is kept, and the row says so (`chi
309
- bundle diff NAME FILE` shows it; `chi bundle upgrade NAME --force` takes the
310
- bundle's). Hooks, rules and the plugin are replaced; an edited one didn't
311
- load anyway (its sha no longer matched) and the row says it was replaced.
311
+ 3-way merge of `chi bundle upgrade`: an unedited file is updated, and an
312
+ edited one that the new version also changes takes a text merge, with the old
313
+ file in the bundle trash and `merged 0.8.0 into your FILE` in the row. A real
314
+ conflict, no `git`, or no recorded base keeps your file untouched, and the row
315
+ says `FILE stays behind 0.8.0:` with why and what it missed (up to three headings
316
+ and a count, or `2 places`). `chi bundle diff NAME FILE` shows it; `chi bundle
317
+ upgrade NAME --force` takes the bundle's, with a copy of yours in the bundle
318
+ trash first. A later run retries such a file at the same version: its row reads
319
+ `0.8.0 merged 0.8.0 into your FILE` when it merges, and names it as behind when
320
+ it doesn't. So does a file left for the bundle's next version (it matches its old
321
+ base), or one the retry can't read, and the row never says `up to date` while a file
322
+ stays behind. Once a file has the bundle's bytes it isn't behind, however it got
323
+ them. A file marked by an older chi
324
+ merges normally when its recorded base is a version chi shipped (an installed
325
+ samagotchi gem still has it). Otherwise that base may be your own text, so it merges only if the bundle takes out or
326
+ rewords no line it still holds. If it does, chi can't tell whether the file holds
327
+ edits of yours, says so, and the file stays behind until resolved by
328
+ hand, with `--agent` or with `--force`; the retry can't fix that. Every merge
329
+ leaves a note on the file's record, since a worker's reaches only the log and an
330
+ automatic update's rows may be read by no one. `chi bundle status` shows it while it
331
+ exists. A `chi update` row names an earlier one (`up to date (merged 0.8.0 into your
332
+ FILE on 2026-11-05 14:03; …)`), and a row printed to you ends the note for what it
333
+ printed, that run's own merges included; so does `chi bundle upgrade`, which prints
334
+ the merge itself, and `--force` replacing the file. A dry run and an automatic update
335
+ (`update.auto`) show it and leave it, and so does a failed row or one with no place
336
+ for it (a skipped system bundle). A memory that can't be read at a
337
+ newer version fails that bundle's row with the file's name. Hooks, rules and the plugin are replaced; an edited one didn't
338
+ load anyway (its sha no longer matched), the row says it was replaced, and
339
+ a copy goes to the bundle trash first (`chi bundle trash`; `hooks/x`,
340
+ `guardrails/x`, `plugin/x`, `scripts/x` inside the dir).
312
341
  A bundle of the same name from elsewhere (a zip, git) is skipped ("not from
313
342
  chi"), a newer installed one is left, one whose new version needs a newer
314
343
  chi is skipped, and bundles you didn't install stay uninstalled.
@@ -367,9 +396,10 @@ this version's view: bundles that only a newer gem ships newer show up once
367
396
  that gem is installed (the real run installs it first and hands over).
368
397
  `--no-gem`, `--no-bundles` and `--no-desktop` leave a part alone for one run;
369
398
  `update.gem`, `update.bundles` and `update.desktop: false` in config.yml turn
370
- one off for good. It exits 0 when nothing failed (kept edits and skips are
399
+ one off for good. It exits 0 when nothing failed (merged edits, files that stay behind and skips are
371
400
  fine), 1 when a part failed, 2 on a usage error. A second run changes nothing
372
- and ends with "everything is up to date".
401
+ and ends with "everything is up to date", or with `N files stay behind (see above)`
402
+ when some do.
373
403
 
374
404
  From a checkout it refuses (`git pull`, or `chi bundle upgrade NAME` for one
375
405
  bundle). After a gem update, the first interactive start of the new version
@@ -867,6 +897,23 @@ mid-turn too, and the lists, cards, top bar and tab show it in place of the firs
867
897
  message; a continue's next link keeps it. In the web, `✎` next to the session's
868
898
  title (its strip card, or the top bar) does it. See [Session titles](sessions.md#session-titles).
869
899
 
900
+ <a id="session-objectives"></a>
901
+ `/objectives` shows what the session is to achieve, each objective's latest move under it (`last move: …`, recorded by
902
+ the model's `objectives` tool); [Objectives](sessions.md#objectives) has what that does.
903
+ - `/objectives set Two coffees | Stops before checkout (done when: no order is placed)`: a new list. Parts are split by
904
+ ` | ` or by lines; a part may end `(done when: …)`. It replaces the list, and ids go on from the old one's.
905
+ - `/objectives add TEXT`: one more (several with ` | `).
906
+ - `/objectives edit O1 TEXT`: a new text, the rest of the line whole (it may end `(done when: …)`); id and status stay.
907
+ - `/objectives done O1 [O2…]`, `/objectives drop O1 [O2…]`: mark items. An id it doesn't have refuses the whole line, and
908
+ so does `done` on a dropped item. The model is shown a done item as `O1 [done] …` and no dropped one.
909
+ - `/objectives undo O1 [O2…]`: back to active, after a mistaken `done` or `drop`.
910
+
911
+ - `/objectives check O1`: an independent read of O1's latest move, one side request; the verdict comes as a card and as
912
+ `check: …` under the move. Information only: it edits nothing, and it is refused for an objective with no move or a dropped one, and during a participant's turn. See [Objectives](sessions.md#objectives).
913
+
914
+ All of them run mid-turn too. An edit answers with the new list at once and goes into the conversation between turns.
915
+ Refused in a chi REPL's session (`--no-shared`), as `chi note` is; `/objectives` alone still answers there.
916
+
870
917
  A `/model`, `/llm-context`, `/thinking`, `/reasoning-history` or `/toolkit` change that restarts the prompt cache
871
918
  asks first: the reply says what it costs, changes nothing, and names the line
872
919
  that applies it (`/thinking low --yes`, `/model qwen --default --yes`). `--yes` (or `-y`) applies it at once.
@@ -1172,14 +1219,17 @@ again). When a step ends its thinking closes to a plain `thinking` line
1172
1219
  (unless you opened it); a step that only thought shows that line alone. The
1173
1220
  live narration is a box of about three lines that fills sentence by
1174
1221
  sentence (a sentence shows once it is complete) and scrolls to the newest
1175
- when full. When the turn ends the block shows its outline when any of its steps said
1176
- something: it stays open on one line per step that wrote text (that text's
1177
- first line, so a mid-turn note reads without a click), and each run of steps
1178
- that only called tools or thought sits behind one `· N steps` line that
1179
- shows them; old turns open the same way after a reload. A turn whose steps
1180
- said nothing collapses the block to its summary (`3 steps
1181
- · 8 tool calls · execute ×7 · read ×1`). Either way the answer is a normal
1182
- bubble under the block (Markdown, annotate). A plain answer
1222
+ when full. When the turn ends the block closes to its summary (`3 steps
1223
+ · 8 tool calls · execute ×7 · read ×1`, after the turn's
1224
+ [footprint](#the-working-set) when it has one). Opened, it shows its outline when any of
1225
+ its steps said something: one line per step that wrote text (that text's first
1226
+ line), and each run of steps that only called tools or thought sits behind one
1227
+ `· N steps` line that shows them. That includes a reply a line was steered into: its
1228
+ step is a row like any other, and the answer that follows is the bubble. A canceled or failed turn with such a step
1229
+ stays open, as its last narrated step is the only text you get. A reload draws
1230
+ the same from what was saved: a failed turn whose stream dropped mid-narration
1231
+ saved no such step, so it reloads closed. Either way the answer is a normal bubble under the block (Markdown,
1232
+ annotate). A plain answer
1183
1233
  without tools ends exactly as it does today. Rows the code collapses stay
1184
1234
  as you toggled them. A reloaded session shows the same block
1185
1235
  from the saved messages (each step's thinking, narration, tool parameters
@@ -1222,12 +1272,14 @@ session bar shows the `mem N` chip again.
1222
1272
  folds it to one line (`SET 17 + ◆ name`), kept per browser; a click opens the
1223
1273
  list. In a stage narrower than 760 px it is one line under the last calls;
1224
1274
  on a phone it isn't shown.
1225
- - **The footprint**, one line under each finished turn's block, live and after
1275
+ - **The footprint**, each finished turn's block's summary line, live and after
1226
1276
  a reload: `touched …/web/public/ · 4 files · ◆ 2 memories s2–10 · +1 url ·
1227
1277
  edited 1`, then `n stale`, `n forgotten` or `n dropped` while some are (a file
1228
- changed after the turn read it, even at its very end, says so here). A step
1229
- tag (`s2`) goes to that call's row; the rest opens the turn's list. A turn
1230
- that touched nothing has none.
1278
+ changed after the turn read it, even at its very end, says so here), with the
1279
+ steps' count at the right. A step tag (`s2`) goes to that call's row; the rest
1280
+ of its words opens the turn's list; the chevron or the count opens the steps.
1281
+ A phone shows the footprint alone, no count. A turn that touched nothing has
1282
+ none: its summary is the count.
1231
1283
  - **The `set N` chip** in the session bar, in place of the `mem N` chip (whose
1232
1284
  tooltip goes too): the session's whole set, preloaded (`--memory`) and muted
1233
1285
  (`--mute`) memories included; its dot lights when the set changed since you
@@ -166,7 +166,7 @@ or `chi web` restart.
166
166
  | `toolkit.kits`, `execute.description`, `llm_context.policy` | Worker start | These are held by the engine's toolkit or tool registry. |
167
167
  | `toolkit.suggest.mode`, `toolkit.suggest.threshold`, `toolkit.suggest.defer_turns`, `toolkit.suggest.defer_tokens` | Worker start | Suggestion settings are config-only and are resolved when the worker runs its audit or on-mode ask. |
168
168
  | `skip_agents_md` | Prompt rebuild | A running engine caches its built prompt; a new worker or `/model` rebuild uses the changed value. |
169
- | `context.*` except `context.every_seconds`, `llm_context.*` except `llm_context.policy`, `steer.cut_after`, `memory.index_warn_tokens`, `memory.entry_warn_tokens`, `status.line`, `web.participants.*` | Next matching turn, display, poll or write | Readers resolve these settings when they perform that operation. Session-specific LLM context and thinking values remain saved in the session. A participant's grant keeps the expiry and cap it was issued with; `web.participants.*` is what the next invite gets, and the cap and queue of a grant from before caps. |
169
+ | `context.*` except `context.every_seconds`, `objectives.*`, `llm_context.*` except `llm_context.policy`, `steer.cut_after`, `memory.index_warn_tokens`, `memory.entry_warn_tokens`, `status.line`, `web.participants.*` | Next matching turn, display, poll or write | Readers resolve these settings when they perform that operation. Session-specific LLM context and thinking values remain saved in the session. A participant's grant keeps the expiry and cap it was issued with; `web.participants.*` is what the next invite gets, and the cap and queue of a grant from before caps. |
170
170
  | `context.every_seconds` | Next context poll | The poller checks it for each source that has no explicit interval. |
171
171
  | `guardrails.*` | Next tool call or approval | Guardrail rules and disable lists are reloaded when the config file changes; rule settings take effect on that evaluation. `guardrails.parent_answers` is checked when a parent answers a question. |
172
172
  | `system_one.host`, `system_one.model`, `system_one.timeout_ms`, `system_one.runs`, `system_one.judgments` | Next tool call or approval | Each ask reads them. |
@@ -1504,7 +1504,7 @@ toolkit:
1504
1504
  | `full` | every built-in; the default |
1505
1505
  | `lean` | `execute`, `read`, `write`, `edit`, `memory_read`, `memory_write`, `task_create`, `task_wait`, `task_get`, `task_list`, `task_stop`, `ask_user_question`, `web_fetch`, `context_read` |
1506
1506
  | `child` | `lean` plus `send_note`, without `memory_write`, `delegate`, `delegate_result` |
1507
- | `coordinator` | `lean` plus `delegate`, `delegate_result`, `list_sessions` (declared by the coordinator bundle) |
1507
+ | `coordinator` | `lean` plus `delegate`, `delegate_result`, `list_sessions`, `objectives` (declared by the coordinator bundle) |
1508
1508
  | `coding` | `child`, with the prompt text `- Be concise in your responses` / `- Show file paths clearly when working with files` (declared by the coordinator bundle, for build and fix children) |
1509
1509
  | `review` | `read`, `memory_read` (not `forget_outputs`, even under the `forget` layer): what a [participant's](internals/participants.md#turns) turn may call, whatever the session declares (the review boundary, [guardrails](guardrails.md#a-participants-review-boundary)). Reserved: `toolkit.kits.review` is refused ("review is reserved for participants' turns"), and a bundle can't declare it. A session may run on it too, as any kit |
1510
1510
 
@@ -2141,7 +2141,7 @@ Both are read when chi web starts (restart it to apply a change). What it does,
2141
2141
  install isn't blocked (a source checkout, Bundler, a RubyGems directory chi can't write: those stay a notice, with
2142
2142
  the command to run). `chi update` also merges the shipped bundles that live sessions read, hence "no turn anywhere".
2143
2143
  2. **Installs** exactly the release the notice named, as the **Update** button does (the system bundle and shipped
2144
- bundles are merged three ways; files you edited are kept and counted in the push). The page's sticky toast says
2144
+ bundles are merged three ways; a file you edited takes a text merge, one with a real conflict stays behind, and the push counts both). The page's sticky toast says
2145
2145
  "Automatic chi update in progress" and its sheet shows the rows; with `update.restart: idle` no "installed"
2146
2146
  toast with **Restart** buttons follows (the run restarts by itself). A failed install is tried **once per version**:
2147
2147
  it pushes the failed row's reason and stays quiet until a newer release (or you, with the button) tries again.
@@ -2158,7 +2158,7 @@ Both are read when chi web starts (restart it to apply a change). What it does,
2158
2158
  the install; after that they wait for their own idle exit or the session's **Restart**. A newer release in that day
2159
2159
  installs as usual and takes them over.
2160
2160
  4. **Says so.** The restarted chi web pushes "chi updated to X" ("chi web and 3 sessions restarted; 1 restarts when
2161
- idle. Kept your edits in 2 files."); the later restarts of those sessions push nothing more. With
2161
+ idle. Merged the update into 1 file you edited. 2 files stay behind."); the later restarts of those sessions push nothing more. With
2162
2162
  `update.restart: never` the push is "chi X is installed" and chi web and the workers move over when you restart them (the page's toast offers it). The push needs the phone set up
2163
2163
  ([Phone notifications](#phone-notifications-web-push)); without it the page's toasts and
2164
2164
  `$XDG_STATE_HOME/samagotchi/update_check.json` (`auto`) are the record.
@@ -2273,6 +2273,8 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2273
2273
  | `context.candidates.threshold` | `0.8` | | The probability at which the judge attaches a candidate in on mode (a judged line's `would: attach`). config.yml only. |
2274
2274
  | `context.candidates.offer_threshold` | `0.6` | | The probability from which on mode shows the candidate's card, once (`would: offer`, below `threshold`). config.yml only. |
2275
2275
  | `context.candidates.reask_after_turns` | `3` | | Owner prompts after a judged ask before the judge's one re-ask of that candidate. config.yml only. |
2276
+ | `objectives.check_model` | none | | The model [Check this](sessions.md#objectives) asks, its one side request (a host:id, an alias, a host's model id); none: the session's own. **Each Check this sends that model's host up to 24,000 characters of the session's tool output, messages and the agent's visible answers, off the machine with a hosted model even from a local session;** known key shapes and `token=`-style values are redacted first, by pattern only. config.yml only. |
2277
+ | `objectives.system_one` | `off` | | `audit`: System 1 answers a yes/no per active objective on what comes in and goes out, saved as runs and acted on by nothing; see [System 1's saved runs](system-one.md#objectives-in-audit). `off` (the default): no conversation text goes to a System 1 host for this. Its host and model are `system_one.host` / `.model`. config.yml only. |
2276
2278
  | `toolkit.default` | `full` | | The toolkit a new session is stored on: the tools its prompts declare (`full`, `lean`, `child`, one of `toolkit.kits` or a bundle's). `chi --toolkit` picks one per session. See "Toolkits". |
2277
2279
  | `toolkit.child` | `full` | | A delegate child's toolkit, unless its `delegate` call names one (`toolkit:`). See "Toolkits". |
2278
2280
  | `toolkit.suggest.mode` | `off` | | `audit` asks System 1 for a toolkit suggestion without changing the session; `on` waits for it (up to `system_one.timeout_ms`) on an eligible turn and starts the session on a confidently suggested kit (Toolkits, On mode). The System 1 host, model and timeout come from `system_one.*`. config.yml only. |
@@ -2303,7 +2305,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2303
2305
  | `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
2304
2306
  | `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
2305
2307
  | `update.notify` | `on` | | `on`: chi web checks RubyGems once a day and shows aged stable releases newer than both this chi and the newest installed chi; `off`: no thread or network check. |
2306
- | `update.auto` | `off` | | `install`: chi web installs the release its checker found (aged by `update.min_age_hours`) once no turn runs and no question waits in any session, as the **Update** button would (shipped bundles merged, your edits kept and reported), then pushes how it went; `update.restart` says what follows, also for a chi installed some other way (`chi update` in a terminal). An install that fails is tried once per version (a newer release, or the **Update** button, tries again). The check runs even with `update.notify: off` (which then only silences the page's and the phone's "available"). An install that can't run here (a checkout, Bundler, an unwritable gem dir) stays a notice. `off`: notice only. See [Unattended updates](#unattended-updates-updateauto). |
2308
+ | `update.auto` | `off` | | `install`: chi web installs the release its checker found (aged by `update.min_age_hours`) once no turn runs and no question waits in any session, as the **Update** button would (shipped bundles merged, a file that can't merge left behind, both reported), then pushes how it went; `update.restart` says what follows, also for a chi installed some other way (`chi update` in a terminal). An install that fails is tried once per version (a newer release, or the **Update** button, tries again). The check runs even with `update.notify: off` (which then only silences the page's and the phone's "available"). An install that can't run here (a checkout, Bundler, an unwritable gem dir) stays a notice. `off`: notice only. See [Unattended updates](#unattended-updates-updateauto). |
2307
2309
  | `update.restart` | `idle` | | With `update.auto: install`: `idle` restarts the stale workers whose session is idle and untouched for 10 minutes, then chi web itself, once no turn runs anywhere and the newest chi has answered `chi --version`; it pushes "chi updated to X". `never`: the install only (the page's toast offers the restarts, workers also exit on their own when idle). |
2308
2310
  | `update.check_hours` | `24` | | Hours between RubyGems checks (at least 1). The first check starts about 30 seconds after chi web starts. |
2309
2311
  | `update.min_age_hours` | `24` | | A release must be this old before chi web offers it; `0` disables the age delay. |
@@ -2327,7 +2329,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2327
2329
  | `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
2328
2330
  | `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token, asked of this machine's 127.0.0.1 too (its `Local:` link carries a one-time code; `chi web --new-token` replaces the token). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
2329
2331
  | `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
2330
- | `web.working_set` | `true` | yes | The stage view's working set: the files, memories and URLs a turn brought into the model's context, in a rail in the stage, a footprint line under each finished turn and a `set N` chip in place of the mem chip; `false` brings the mem chip back. `?working_set=1` or `?working_set=0` on the page URL overrides it for that page load. See [CLI: the working set](cli.md#the-working-set). |
2332
+ | `web.working_set` | `true` | yes | The stage view's working set: the files, memories and URLs a turn brought into the model's context, in a rail in the stage, a footprint as each finished turn's block summary and a `set N` chip in place of the mem chip; `false` brings the mem chip back. `?working_set=1` or `?working_set=0` on the page URL overrides it for that page load. See [CLI: the working set](cli.md#the-working-set). |
2331
2333
  | `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). |
2332
2334
  | `web.editor` | `vscode` | yes | What a file ref in `chi web` (a file tool row's title, a diff line) opens, for a viewer on this machine: a preset (`vscode`, `vscode-insiders`, `cursor`, `zed`) or a URL template with `{path}` and an optional `{line}`; `none` turns refs off. See [Opening files from chi web](#opening-files-from-chi-web). |
2333
2335
  | `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a repo on every session, and a new session from the all view starts there; the header's project picker switches projects. `~` and a relative path (resolved from your home folder) work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
data/docs/guardrails.md CHANGED
@@ -529,7 +529,8 @@ message:
529
529
  - owner-voiced tools, which act in the owner's name beyond the turn:
530
530
  `delegate` and any `delegate_*` tool, `register_reminder`,
531
531
  `cancel_reminder`, `send_note`, `forget_outputs`, `task_create`,
532
- `task_stop`, `find_tools` (it loads tools into the session), and
532
+ `task_stop`, `find_tools` (it loads tools into the session), `objectives`
533
+ (a move on the owner's list), and
533
534
  `memory_write` (a memory reaches the owner's later sessions); and the tools
534
535
  that read the owner's background tasks and their output, `task_wait`,
535
536
  `task_get`, `task_list` (a pair turn starts none);
data/docs/hooks.md CHANGED
@@ -56,7 +56,7 @@ The plugin class must respond to `#call(event)` — duck-typed, no base class re
56
56
  |-------|--------------|---------------|
57
57
  | `:session_start` | The first turn this chi process runs for the session (so again after `--resume`, a worker that idle-exited and woke, or a restart) | `{ type: :session_start, session_id: "..." }` |
58
58
  | `:before_turn` | Before each turn starts | `{ type: :before_turn, session_id: "...", prompt: "..." (nil on a continue), resumed: true \| false (true on a step-limit Continue, which resumes the run before it; false on every other turn, a reminder's or a wake's too), messages: [...] (the history before this turn) }` |
59
- | `:after_turn` | After a turn completed or was stopped once the model was asked (not after one that failed, was cut short by an interrupt signal (SIGINT), or was stopped before its `:before_turn` hooks) | `{ type: :after_turn, status: "completed" \| "canceled", present: (see [Presenting the answer](#presenting-the-answer-display-only)), refs: (see [Refs on the answer](#refs-on-the-answer-display-only)), messages: [...] (the conversation the turn stored; a cancelled or empty turn ends it with a `kind: turn_note` system message, and a context line is `kind: context`, see [sessions.md](sessions.md#notes-a-turn-leaves-for-the-model)) }` |
59
+ | `:after_turn` | After a turn completed or was stopped once the model was asked (not after one that failed, was cut short by an interrupt signal (SIGINT), or was stopped before its `:before_turn` hooks) | `{ type: :after_turn, status: "completed" \| "canceled", present: (see [Presenting the answer](#presenting-the-answer-display-only)), refs: (see [Refs on the answer](#refs-on-the-answer-display-only)), messages: [...] (the conversation the turn stored; a cancelled or empty turn ends it with a `kind: turn_note` system message, and a context line is `kind: context`, see [sessions.md](sessions.md#notes-a-turn-leaves-for-the-model); on the native loop the last model message still holds its thought block until the next turn), answer: "..." (the answer's text with no thought block; nil for a stopped turn or one that ran out of steps; needs chi newer than 0.148.0) }` |
60
60
  | `:before_generation` | Before each LLM API call (both loops), before its `:generation_started`: the hook's time (a question it waits on) isn't the generation's | `{ type: :before_generation, iteration: N }` |
61
61
  | `:after_generation` | After LLM returns (both loops); not after a generation a hook cut (`stop_generation`) | `{ type: :after_generation, iteration: N, response: "...", messages: [...] (the conversation as sent) }` |
62
62
  | `:generation_progress` | While the response streams, in batches (see [Watching the stream](#watching-the-stream)) | `{ type: :generation_progress, iteration: N, thinking: "..." (new since the last fire), text: "..." (new visible text), thinking_chars: N, text_chars: N (this generation so far), elapsed_ms: N, restarted: true (only on the first fire after a dropped stream was asked again) }` |
@@ -636,6 +636,7 @@ stripped of surrounding whitespace, except file text, an edit's
636
636
  | `delegate` | `task` | — | `model`, `session`, `cwd`, `toolkit`, `wait`, `timeout` |
637
637
  | `delegate_result` | — | — | `session`, `timeout` |
638
638
  | `ask_user_question` | `question` | — | `question`, `options`, `header`, `multi_select`, `allow_freeform` |
639
+ | `objectives` | `note` | — | `action`, `id`, `note`, `evidence`, `change`, `text` |
639
640
  | `forget_outputs` | `note` | — | `ids`, `note`, `keep`, `restore` |
640
641
 
641
642
  A `write`'s `content:` is the file text; an `edit` carries `old_text:` and
@@ -42,7 +42,7 @@ Behavior:
42
42
  again or stop it (`TaskWait::MESSAGE_WAITING_NOTE`), and `waited: Ns`; the task keeps running and stays watched
43
43
  (below). A task that finished meanwhile gives its finished result, as after a Stop. A `chi -p` turn has no probe. The same probe ends a blocking `delegate_result` (and `delegate`'s wait) early; see [Sessions: Delegating](../sessions.md).
44
44
  - `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.
45
- - `execute` and `task_create` both spawn with chi's own Ruby/Bundler setup scrubbed (`TaskRuntime.sanitized_spawn_env`): `RUBYOPT` loses its `-r…bundler/setup` flags (others stay) and `RUBYLIB`, `BUNDLE_GEMFILE`, `BUNDLE_BIN_PATH`, `BUNDLER_VERSION` are unset, so a project's `ruby -e` or `bundle exec` uses its own Gemfile when chi runs under `bundle exec`. `task_create` refuses those keys in the model's `env`; `execute`'s `env` comes from chi's callers (`Builtins`, `!cmd`), not the model, and is set over the scrub. An `execute` handed over at its limit keeps the process it spawned, so the task runs with the same env.
45
+ - `execute` and `task_create` both spawn with chi's own Ruby/Bundler setup scrubbed (`TaskRuntime.sanitized_spawn_env`): `RUBYOPT` loses its `-r…bundler/setup` flags (others stay) and `RUBYLIB` and what `bundle exec` sets (`BundlerEnv::VARS`: `BUNDLE_GEMFILE`, `BUNDLE_LOCKFILE`, `BUNDLE_BIN_PATH`, `BUNDLER_VERSION`, `BUNDLER_SETUP`) are unset, so a project's `ruby -e` or `bundle exec` uses its own Gemfile, or none, when chi runs under `bundle exec`. RubyGems requires what `BUNDLER_SETUP` names at boot, whatever `RUBYOPT` says. A project's own settings (`BUNDLE_PATH`, `BUNDLE_WITHOUT`) stay. `task_create` refuses those keys in the model's `env`, its error saying to write `KEY=value cmd` in the command instead; `execute`'s `env` comes from chi's callers (`Builtins`, `!cmd`), not the model, and is set over the scrub. An `execute` handed over at its limit keeps the process it spawned, so the task runs with the same env.
46
46
  - `execute` and `task_create` export chi's own markers into the command, set over the model's `env` (which can't set them: `Error: env key is reserved`): `SAMAGOTCHI_PARENT_SESSION` (the session's id, `chi` without one; a chi started there answers as a parent agent), `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown) `SAMAGOTCHI_SESSION_THINKING` (the session's own thinking level; unset when it has none) and `SAMAGOTCHI_SESSION_REASONING_HISTORY` (its own reasoning_history level, the same way). `chi self` reads the last three for its model, thinking level and reasoning history. The four are `ChildEnv::SESSION_MARKERS` (`TaskRuntime::MARKER_ENV_KEYS`), which a spawned worker unsets (`ChildEnv.unset`, merged into its spawn env). A session's worker never inherits them from whoever started it (a `chi send` run by another session's `execute`): its other commands (a user's `!cmd`, an MCP server, a context command) have none.
47
47
  - They also pass on a set `XDG_STATE_HOME` / `XDG_CONFIG_HOME` made absolute (`Builtins.xdg_absolute`, via `Paths`), so a chi the command runs in another `cwd` (`chi send`) reads this chi's state and config. These aren't reserved: an XDG dir the model sets in `task_create`'s `env` stays its own.
48
48
  - Every runner starts from the session's project env, `ProjectEnv.tools_env`: the overlay a spawned worker computed once at boot (`SessionManager.apply_project_env` → `ProjectEnv.compute` → `ProjectEnv.install!`; its folder's `mise env`, and the chi shim's dir first on PATH so `chi` is the chi home's), less `ProjectEnv::TOOLS_DROP` (`RUBYOPT`, `RUBYLIB`, Bundler's: the scrub above sets those). Outside a worker (`ProjectEnv::NONE`) it is `{}`. The sites: `execute` and `task_create` (`Tools::Builtins#project_env`, under the markers and the model's `env`; a participant's turn gets `{}`), the user's `!cmd` (`SessionCommands#shell`), and through `ProjectEnv.spawn_env` (the scrub above, then `tools_env`: what `execute` spawns with, for the runners that don't go through it) a context source's command (`ContextFetch#env_for`) and an MCP server, under the server's own keys (`bundles/mcp/plugin.rb` `server_env`, from mcp 0.7.4; no participant rule: the server is the session's). chi's own process env is never changed, and the project env never enters the prompt. User docs: [Project env](../sessions.md#project-env); the launch side: [How chi launches itself](../architecture.md#how-chi-launches-itself).
@@ -52,6 +52,9 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
52
52
  - **Placement is relative.** An entry carries `turns` and lists with `turns_since` (turns completed after it) and
53
53
  `current` (a card of the running turn). A card that comes mid-turn but isn't the turn's (an anytime command's, such as
54
54
  `/btw`) goes after that turn (`during`). A card with an earlier card's id replaces it in place.
55
+ - **A turn that leaves nothing.** `!rollback` (`TurnFlow#rollback!`, `:turn_rolled_back`) takes a kept turn's count
56
+ back. Of a rolled-back turn, and a failed one with nothing kept, the cards and hook notices become between-turns entries
57
+ at its place (the cards lose their actions); its other rows go, having no step to sit in.
55
58
  - **Caps.** `CAPACITY` (20) each for notices, ✂ rows (`LLM_CONTEXT_TYPES`) and cards, so one burst cannot push out
56
59
  another kind. An open question (an approval too) is never pushed out.
57
60
  - **Saved.** With a `path` (`cards.json` in the session's folder) the store rewrites the file after each event in
@@ -60,6 +63,11 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
60
63
  and an unanswered question waits for no one: neither is saved or seeded. An entry from an earlier worker lists with
61
64
  `earlier: true`; one shown during a turn comes back **without its actions** (their turn no longer runs), while a
62
65
  between-turns card keeps its buttons.
66
+ - **A killed turn counts.** A worker killed mid-turn never saw the turn end, but `TurnCheckpoint` keeps its prompt (the
67
+ next worker settles it as canceled). The Bridge's store (`killed_turn_stays:`) saves `"running": true` at
68
+ `:turn_started`, and a store started from that adds the one turn. An ordered end clears it, so a graceful stop's
69
+ cancel counts once; a file without the field counts none. A REPL's store never saves it: it keeps no running turn in
70
+ the session, so a kill takes the prompt too.
63
71
 
64
72
  ## SSEWriter
65
73