samagotchi 0.141.0 → 0.143.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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -7
  3. data/README.md +2 -1
  4. data/bin/chi +2 -0
  5. data/docs/architecture.md +25 -289
  6. data/docs/bundle-authoring.md +7 -6
  7. data/docs/bundles.md +1232 -0
  8. data/docs/cli.md +51 -13
  9. data/docs/configuration.md +68 -30
  10. data/docs/context.md +12 -12
  11. data/docs/guardrails.md +27 -20
  12. data/docs/hooks.md +28 -9
  13. data/docs/internals/attached-context.md +225 -0
  14. data/docs/internals/background-tasks.md +1 -1
  15. data/docs/internals/bridge.md +380 -0
  16. data/docs/internals/bundles.md +6 -3
  17. data/docs/internals/context-telemetry.md +1 -1
  18. data/docs/internals/engine.md +31 -19
  19. data/docs/internals/participants.md +13 -12
  20. data/docs/internals/prompt-caching.md +12 -12
  21. data/docs/internals/remote-sessions.md +1 -1
  22. data/docs/internals/{tool-guardrails.md → tool-output-limits.md} +21 -2
  23. data/docs/internals/web-push.md +1 -1
  24. data/docs/memory.md +33 -3
  25. data/docs/plugins.md +127 -1231
  26. data/docs/sessions.md +51 -27
  27. data/docs/sub-agent.md +6 -6
  28. data/docs/system-one.md +122 -0
  29. data/docs/testing.md +3 -1
  30. data/lib/samagotchi/bridge.rb +2 -0
  31. data/lib/samagotchi/bundle_command/api_calls.rb +132 -0
  32. data/lib/samagotchi/bundle_command/check.rb +50 -34
  33. data/lib/samagotchi/bundle_command/installed_check.rb +86 -0
  34. data/lib/samagotchi/bundle_command/scaffold.rb +1 -0
  35. data/lib/samagotchi/bundle_settings_check.rb +58 -0
  36. data/lib/samagotchi/bundles/agents-md/manifest.yml +4 -2
  37. data/lib/samagotchi/bundles/agents-md/plugin.rb +1 -1
  38. data/lib/samagotchi/bundles/btw/manifest.yml +4 -2
  39. data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
  40. data/lib/samagotchi/bundles/check-in/manifest.yml +7 -3
  41. data/lib/samagotchi/bundles/check-in/plugin.rb +101 -11
  42. data/lib/samagotchi/bundles/claude-peer/manifest.yml +3 -3
  43. data/lib/samagotchi/bundles/claude-peer/plugin.rb +1 -1
  44. data/lib/samagotchi/bundles/claude-peer/scripts/pair +1 -1
  45. data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
  46. data/lib/samagotchi/bundles/codex-peer/plugin.rb +1 -1
  47. data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
  48. data/lib/samagotchi/bundles/coordinator/plugin.rb +1 -1
  49. data/lib/samagotchi/bundles/core/manifest.yml +2 -2
  50. data/lib/samagotchi/bundles/cost-cap/manifest.yml +6 -3
  51. data/lib/samagotchi/bundles/cost-cap/plugin.rb +5 -7
  52. data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -1
  53. data/lib/samagotchi/bundles/known-names/manifest.yml +3 -1
  54. data/lib/samagotchi/bundles/loop-guard/manifest.yml +4 -2
  55. data/lib/samagotchi/bundles/loop-guard/plugin.rb +1 -1
  56. data/lib/samagotchi/bundles/mcp/manifest.yml +4 -2
  57. data/lib/samagotchi/bundles/mcp/plugin.rb +2 -2
  58. data/lib/samagotchi/bundles/ruby/manifest.yml +2 -2
  59. data/lib/samagotchi/bundles/ruby/plugin.rb +1 -1
  60. data/lib/samagotchi/bundles/skills/manifest.yml +4 -2
  61. data/lib/samagotchi/bundles/skills/plugin.rb +1 -1
  62. data/lib/samagotchi/bundles/slow-commands/manifest.yml +4 -2
  63. data/lib/samagotchi/bundles/slow-commands/plugin.rb +1 -1
  64. data/lib/samagotchi/bundles/source-links/manifest.yml +3 -1
  65. data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -3
  66. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  67. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  68. data/lib/samagotchi/bundles/system/self_map.md +3 -3
  69. data/lib/samagotchi/bundles/work-cap/manifest.yml +6 -3
  70. data/lib/samagotchi/bundles/work-cap/plugin.rb +11 -13
  71. data/lib/samagotchi/config.rb +74 -14
  72. data/lib/samagotchi/context_candidate_choice.rb +20 -3
  73. data/lib/samagotchi/context_candidates.rb +4 -1
  74. data/lib/samagotchi/context_candidates_log.rb +36 -1
  75. data/lib/samagotchi/context_judge.rb +6 -2
  76. data/lib/samagotchi/context_status.rb +1 -2
  77. data/lib/samagotchi/engine.rb +96 -70
  78. data/lib/samagotchi/extension_load.rb +11 -4
  79. data/lib/samagotchi/guardrail_wiring.rb +36 -31
  80. data/lib/samagotchi/guardrails/chi_dirs.rb +7 -4
  81. data/lib/samagotchi/guardrails/path_resolve.rb +1 -1
  82. data/lib/samagotchi/guardrails/protected_paths.rb +7 -4
  83. data/lib/samagotchi/guardrails_command.rb +3 -3
  84. data/lib/samagotchi/hook_routes.rb +37 -2
  85. data/lib/samagotchi/hooks/bundle_loader.rb +20 -4
  86. data/lib/samagotchi/hooks/registry.rb +23 -5
  87. data/lib/samagotchi/host_model.rb +3 -3
  88. data/lib/samagotchi/host_registry.rb +14 -7
  89. data/lib/samagotchi/judgments_command.rb +155 -0
  90. data/lib/samagotchi/kernel_loop.rb +20 -3
  91. data/lib/samagotchi/llm/chat_loop.rb +79 -10
  92. data/lib/samagotchi/llm/errors.rb +7 -5
  93. data/lib/samagotchi/llm/stray_tool_call.rb +68 -0
  94. data/lib/samagotchi/log_line.rb +1 -1
  95. data/lib/samagotchi/memory_bundle/builder.rb +4 -2
  96. data/lib/samagotchi/memory_bundle/entry_size.rb +100 -0
  97. data/lib/samagotchi/memory_bundle/installed_bundle.rb +9 -2
  98. data/lib/samagotchi/memory_bundle/installed_copy.rb +92 -0
  99. data/lib/samagotchi/memory_bundle/installer.rb +2 -0
  100. data/lib/samagotchi/memory_bundle/manifest.rb +23 -13
  101. data/lib/samagotchi/memory_bundle/provenance.rb +4 -1
  102. data/lib/samagotchi/memory_bundle/shipped_update.rb +13 -1
  103. data/lib/samagotchi/model_catalog.rb +3 -2
  104. data/lib/samagotchi/models_command.rb +1 -1
  105. data/lib/samagotchi/plugin/api.rb +41 -7
  106. data/lib/samagotchi/plugin/context.rb +59 -1
  107. data/lib/samagotchi/plugin/judgments.rb +72 -0
  108. data/lib/samagotchi/plugin/loader.rb +6 -3
  109. data/lib/samagotchi/plugin/peer.rb +17 -22
  110. data/lib/samagotchi/plugin_session_state.rb +32 -5
  111. data/lib/samagotchi/prompt_warmup.rb +1 -9
  112. data/lib/samagotchi/run_options.rb +1 -0
  113. data/lib/samagotchi/self_report.rb +36 -0
  114. data/lib/samagotchi/session_access/contract.rb +1 -1
  115. data/lib/samagotchi/session_access/local.rb +1 -1
  116. data/lib/samagotchi/session_access/remote.rb +3 -3
  117. data/lib/samagotchi/session_archive_command.rb +3 -3
  118. data/lib/samagotchi/session_commands.rb +7 -4
  119. data/lib/samagotchi/session_config.rb +9 -3
  120. data/lib/samagotchi/session_continue.rb +1 -1
  121. data/lib/samagotchi/session_manager.rb +25 -5
  122. data/lib/samagotchi/session_retention.rb +10 -0
  123. data/lib/samagotchi/side_judgments.rb +12 -1
  124. data/lib/samagotchi/system_one/answer.rb +63 -11
  125. data/lib/samagotchi/system_one/approval_label.rb +56 -0
  126. data/lib/samagotchi/system_one/approval_question.rb +2 -0
  127. data/lib/samagotchi/system_one/client.rb +43 -17
  128. data/lib/samagotchi/system_one/fake_backend.rb +1 -1
  129. data/lib/samagotchi/system_one/http_backend.rb +12 -4
  130. data/lib/samagotchi/system_one/judgment_settings.rb +61 -0
  131. data/lib/samagotchi/system_one/judgments.rb +243 -0
  132. data/lib/samagotchi/system_one/log.rb +25 -2
  133. data/lib/samagotchi/system_one/recording.rb +117 -0
  134. data/lib/samagotchi/system_one/runs.rb +179 -0
  135. data/lib/samagotchi/system_one/runs_store.rb +230 -0
  136. data/lib/samagotchi/system_one.rb +9 -0
  137. data/lib/samagotchi/terminal_ui/attach_link.rb +2 -2
  138. data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +1 -1
  139. data/lib/samagotchi/terminal_ui/attached_loop.rb +1 -1
  140. data/lib/samagotchi/terminal_ui/local_link.rb +1 -1
  141. data/lib/samagotchi/terminal_ui/remote_link.rb +1 -2
  142. data/lib/samagotchi/testing/bundle.rb +9 -2
  143. data/lib/samagotchi/testing/host.rb +2 -1
  144. data/lib/samagotchi/testing/judgments.rb +176 -0
  145. data/lib/samagotchi/testing/recorder.rb +5 -0
  146. data/lib/samagotchi/testing/recording_api.rb +1 -1
  147. data/lib/samagotchi/tool_output_cut.rb +26 -6
  148. data/lib/samagotchi/tool_response.rb +1 -1
  149. data/lib/samagotchi/tool_runner.rb +63 -4
  150. data/lib/samagotchi/toolkit_suggest.rb +11 -2
  151. data/lib/samagotchi/tools/memory.rb +28 -0
  152. data/lib/samagotchi/turn_note.rb +9 -0
  153. data/lib/samagotchi/update_command.rb +49 -16
  154. data/lib/samagotchi/version.rb +1 -1
  155. data/lib/samagotchi/web/app.rb +1 -0
  156. data/lib/samagotchi/web/auto_update.rb +79 -49
  157. data/lib/samagotchi/web/auto_update_record.rb +59 -0
  158. data/lib/samagotchi/web/public/app.js +8 -4
  159. data/lib/samagotchi/web/public/hash_intent.js +24 -0
  160. data/lib/samagotchi/web/public/index.html +3 -1
  161. data/lib/samagotchi/web/public/revise_stack.js +14 -5
  162. data/lib/samagotchi/web/session_control.rb +3 -3
  163. data/lib/samagotchi/web/update_check.rb +7 -6
  164. data/lib/samagotchi/web/update_ops.rb +80 -25
  165. metadata +23 -2
@@ -1,11 +1,12 @@
1
1
  # Participants: identity and routes
2
2
 
3
3
  A session's owner can invite **participants** to review it: people, or agents, who reach that one session through
4
- chi web with a link of their own. This page is the identity layer (the shared-sessions plan's P2): grants, the gate,
5
- the routes a participant reaches, and what is written where; and their **review turns** (P3b): queued, read-only,
6
- limited and capped; and what they **see** (P4a): their page and its reads, all through one filter. Participants are
7
- experimental. An agent participant waits on its own turns' replies through the API. The code and docs say
8
- "participants" and "review", never "shared": `session.shared` already means "attached by default".
4
+ chi web with a link of their own. This page is the identity layer: grants, the gate, the routes a participant
5
+ reaches, and what is written where; and their **turns**: review turns queued, read-only, limited and capped, pair
6
+ turns that act under the owner's guardrails; and what they **see**: their page and its reads, all through one
7
+ filter. Participants are experimental. An agent participant waits on its own turns' replies through the API. The
8
+ code and docs say "participants", "review" and "pair", never "shared": `session.shared` already means "attached by
9
+ default".
9
10
 
10
11
  Participants reach chi web on the **LAN** (`web.host: lan`), or **behind an auth proxy** with `web.public_url`
11
12
  (experimental; docs/configuration.md, "Behind an auth proxy"): there every request needs a token, this machine's too,
@@ -43,9 +44,9 @@ participant** and **one role**, until it expires or is revoked.
43
44
  newer chi's: read (its links work, but a grant with keys this chi doesn't know is left out: such a key may
44
45
  restrict it, so it fails closed) and never written (`Grants::Newer`, one message saying to update chi; a revoke
45
46
  the gate can't write still refuses the grant). A grant's keys this chi doesn't know are kept on a rewrite
46
- (`Grant#extra`), so a field a later chi adds the omit-if-default way survives. A chi from before the version check
47
- doesn't look at it: **mixed installs** on one state dir are a hazard, as a chi before 0.109 (no `identity`) rewriting
48
- the store unbinds every grant, and one before this change drops unknown keys. A stored `identity` that isn't one
47
+ (`Grant#extra`), so a field a later chi adds the omit-if-default way survives. **Mixed installs** on one state dir
48
+ are a hazard: an older chi that doesn't know `identity` unbinds every grant when it rewrites the store, and one
49
+ that doesn't keep a grant's unknown keys drops them. A stored `identity` that isn't one
49
50
  `#issue` would write (`false`, `""`, a list, upper case) makes the store Corrupt (fails closed). Readers follow its mtime (a `chi share revoke` in another process takes effect at chi web's next request).
50
51
  `last_used_at` is written at most once a minute per grant. **chi web refuses to start** when that folder or file can
51
52
  be read by another user, or isn't this user's (`Grants.check_permissions!`, one message naming the `chmod`). Only
@@ -648,7 +649,7 @@ invited to has none: its files stay byte for byte as they were.
648
649
  expires_at, removed_at, cap}]}`, rewritten by whoever changes a grant (chi web, `chi share`), inside the grants
649
650
  store's lock (`Grants#update`'s `on_write`), so two writers can't finish out of order and lose a revoke here. Never
650
651
  a token or a digest.
651
- The worker reads it with `Participants.read` (P3a's system-prompt line, `ctx`).
652
+ The worker reads it with `Participants.read` (`SessionParticipants#refresh!`, for the system prompt's participants line).
652
653
  - **`participants.jsonl`** (0600, append-only, `Participants.log`): one JSON line per event, with `at`: `issued`
653
654
  (participant, name, kind, role, expires_at), `revoked` (participant, by), `expired` (the first refused use after the
654
655
  expiry, once per chi web), `peer` (the first use and each new peer since chi web started: ip, user agent; behind a
@@ -668,8 +669,8 @@ invited to has none: its files stay byte for byte as they were.
668
669
 
669
670
  What a participant writes is marked as theirs everywhere it is kept or sent. The
670
671
  author is `"p:<id>"` (`Grant#author`), stamped by chi web from the principal and never taken from a body. Absent
671
- means the owner: old sessions and every owner path read as the owner's, byte for byte as before (a pre-P3 session
672
- fixture loads and saves unchanged, `spec/turn_author_record_spec.rb`).
672
+ means the owner: a turn or session with no author keys reads as the owner's and gains none on save
673
+ (`spec/turn_author_record_spec.rb`).
673
674
 
674
675
  - **Messages.** A participant's opening message is saved raw with `author`, `author_name` (the name the sidecar gave
675
676
  the participant when it was written) and `participant_mode` (`ParticipantMode.of` when `Engine#run_turn` builds it, before any request carries it). Merged input and
@@ -741,7 +742,7 @@ participant TUI in v1.
741
742
  worker announces `:participants_changed` (`Engine#announce_participants`: `{participants: [{id, name, kind}],
742
743
  joined: […], left: [{…, reason: revoked | expired}]}`) to every UI; its start announces nobody. "Joined" means
743
744
  invited: participants have no presence of their own yet. `session_state_snapshot`'s `participants` names who is in
744
- the review now, so a UI that joins has them. Owner frames only: no participant stream carries them (P4a's filter).
745
+ the review now, so a UI that joins has them. Owner frames only: no participant stream carries them (`Web::ParticipantView`).
745
746
  - **Lines.** A participant's prompt (`turn_enqueued`'s `author`, an origin's, a queued row's, a saved message's
746
747
  `author` / `author_name`) is led by their name (`Formatting#participant_prefix`: `Alex ▸ review>`, cleaned by
747
748
  `ParticipantName`, the id when the TUI knows no name), in a colour picked from their id. Each change is one line per
@@ -80,19 +80,18 @@ right-hand column happens.
80
80
  `toolkit.suggest.defer_turns` / `defer_tokens`) is engine-side, so `/toolkit`'s CacheConfirm doesn't apply: it
81
81
  changes the declared tools at the front of the request, and the server re-reads **system prompt + tools +
82
82
  conversation** on that turn. The defer bound keeps that conversation small; its notice says "from this turn".
83
- When System 1 runs on the model's own single-slot local server (`Engine#system_one_shares_local_host?`), any
84
- ask takes the slot and evicts its cached prefix: a first-turn ask costs that cache even when nothing switches
85
- (there is little cached on a session's first turn), so on mode asks only there, skips the deferred path, and
86
- sends no worker warm-up.
87
83
  - The candidate judge (`context.candidates.mode`, `ContextJudge`) adds nothing to the model's request: its asks
88
84
  go to System 1 only, and nothing it answers enters the running turn's history: on mode's attach writes only the
89
- session's context folder, and its note comes with the between-turn delivery (`ContextAbsorber`), never mid-turn. On a shared local server it asks after the turn's last request, not skipped: a
90
- completed turn starts the asks right before its turn-end warm-up, and the warm-up waits for them
91
- (`PromptWarmup#start`'s `after:`, `AFTER_SECONDS` at most), so it re-warms the prefix the asks evicted and the
92
- next turn prefills only its own message. A turn that ended otherwise (a Stop, a failure: no warm-up) starts them
93
- from `Engine#run_turn`'s ensure, and its next turn re-reads its prompt. Where no warm-up runs (`cache.warmup:
94
- off`, a chat host) the asks still evict the prefix. Bounded either way: at most two judged asks per candidate (one
95
- re-ask after `reask_after_turns` owner prompts), plus a deferred ask per owner prompt before a task is stated.
85
+ session's context folder, and its note comes with the between-turn delivery (`ContextAbsorber`), never mid-turn.
86
+ Its asks start beside the turn on any host. Bounded: at most two judged asks per candidate (one re-ask after
87
+ `reask_after_turns` owner prompts), plus a deferred ask per owner prompt before a task is stated.
88
+ - System 1's asks (`SystemOne::Client`, `/v1/systemone`) go beside the turn on any host, the model's own Splash too,
89
+ because they don't cost the session its cached prompt there. Measured 2026-11-01 on a local Splash with a 60k-token
90
+ session: the next request still had 60,064 tokens cached in 0.13 s after 3 asks (0.09 s with none), and 60,160 cached
91
+ in 0.18 s after 10 asks during generation; asks answered in 108-158 ms during a turn, and those 10 took the
92
+ generation from 2.87 s to 4.43 s. An earlier guard skipped or deferred them on the assumption that an ask takes the
93
+ slot and evicts the prefix, never measured there; don't rebuild it from that assumption. Not measured: a prefix past
94
+ Splash's cache capacity (262144 tokens).
96
95
  - A kit's prompt text (`toolkit.kits.<name>.prompt:`, `chi.toolkit … prompt:`) sits after the appended files and
97
96
  before the memory indexes (`SystemPrompt#kit_prompt_section`): one prefix per kit, never per-session text, so
98
97
  sessions on the kit share it; a memory write (the indexes after it) doesn't move it. It is part of the
@@ -275,7 +274,8 @@ is prefilled once. The native and the chat loop use the same plan.
275
274
  ## Server-side settings worth knowing
276
275
 
277
276
  - **Splash:** `--persistent-cache` (with `--max-cache-disk`) keeps restore points across restarts. Splash learns
278
- a new branch point on the 2nd session, so the 3rd one gets the full reuse.
277
+ a new branch point on the 2nd session, so the 3rd one gets the full reuse. System 1's asks on the same server leave
278
+ a session's cached prefix alone (measured 2026-11-01, above).
279
279
  - **llama.cpp:** `--cache-ram` (the host-memory prompt cache, 8 GiB by default in recent builds) is what keeps
280
280
  sessions apart: when a task starts on a slot, the idle slots' state goes to that cache, and an unpinned request
281
281
  restores the best match on whatever slot it gets. So a recap or another session between two turns costs
@@ -7,7 +7,7 @@ sessions, workers, memories and code stay on that machine; this one only sends r
7
7
  User-facing behaviour is in [cli.md](../cli.md#remote-sessions). This page is the code's shape, for changing it.
8
8
 
9
9
  **The one rule:** the remote side is that machine's **chi web**, never a worker's Bridge. Bridges stay on loopback,
10
- and answer only a request that names their secret from `bridge.json` ([architecture.md](../architecture.md)).
10
+ and answer only a request that names their secret from `bridge.json` ([bridge.md](bridge.md)).
11
11
  chi web answers each request by calling the same `SessionAccess::Local` operation the CLI would call there.
12
12
 
13
13
  ## The seam: `SessionAccess`
@@ -58,7 +58,7 @@ Implementation note:
58
58
 
59
59
  ## The runner's cut (`max_tool_output_chars`)
60
60
 
61
- After a tool's own limits, `ToolRunner` caps every result at `max_tool_output_chars` (default `10000`
61
+ After a tool's own limits, `ToolRunner` caps every result but a `memory_read`'s at `max_tool_output_chars` (default `10000`
62
62
  characters, both loops). How a longer one is cut is the tool's policy, `ToolOutputCut::POLICIES` (by the
63
63
  built-in name; a tool with none gets `:head`):
64
64
 
@@ -67,7 +67,26 @@ built-in name; a tool with none gets `:head`):
67
67
  | `execute` | `:head_tail_file` | a short head, a long tail, one gap line between them; the whole output saved in the session's folder |
68
68
  | `task_wait` | `:head_tail_ref` | the same head and tail, the gap line naming the task's log (`output_path`, `Tools::RefText`); nothing saved |
69
69
  | `delegate`, `delegate_result` | `:head_tail_ref` | the same, the gap line naming the child's reply file (its `output/<timestamp>.txt`); nothing saved |
70
- | `read` and every other tool (`task_get`/`task_list`/`task_create`/`task_stop`, `web_fetch`, `memory_read`, `context_read`, plugin and MCP tools) | `:head` | the first chars, then `[cut: N of M chars; read it in parts]` |
70
+ | `read` and every other tool (`task_get`/`task_list`/`task_create`/`task_stop`, `web_fetch`, `context_read`, plugin and MCP tools) | `:head` | the first chars, then `[cut: N of M chars; read it in parts]` |
71
+ | `memory_read` | `:head`, its own cap | not cut by `max_tool_output_chars`; see below |
72
+
73
+ `memory_read` is the one tool `max_tool_output_chars` does not cap, in any form (a name, a comma list, a blank name for the
74
+ indexes): a memory is read whole, since a model that needs the skill pays for all of it anyway. `ToolRunner#output_cap`
75
+ resolves the cap per call, so both loops get it (they both come through `ToolResponse.run_batch`). Two things can still
76
+ cut a `memory_read` result, both with the same `:head` cut and a line that says where the rest is, since `memory_read`
77
+ has no parts to read:
78
+
79
+ - `memory.read_max_chars` (default `0`: none). A positive value caps the result at that many characters.
80
+ - The overflow guard, with `memory.read_max_chars` at 0: a result over half the model's context window (in characters,
81
+ tokens x `context.chars_per_token`) is cut. A context overflow is a hard failure (`LLM::FailedTurn.keeps_work?` is
82
+ false), so the turn's work rolls back and a retry would repeat the same read. The window is the loop's own:
83
+ `KernelLoop#tool_output_window` (the client's probe, the turn's `window_tokens` setting), or for `LLM::ChatLoop` its
84
+ adapter's model list too (`ToolRunner.new(kernel, window:)`). An unknown window (`ContextWindow` falling back to
85
+ its default) has no guard. It checks this one result, not the context already used.
86
+
87
+ The line is `[cut: N of M chars; the rest is in the memory's .md file in <the memories dir(s) searched>: read it with
88
+ start_line]` ("the memories' .md files" for a comma list or the indexes). `ToolOutputCut::HEAD_CUT` does not match it.
89
+ `read` on a memory's file keeps the `max_tool_output_chars` cut: it has line ranges.
71
90
 
72
91
  `:head_tail_file` is for output whose end matters: a test run's summary, an error, the `exit:` line. The head is
73
92
  `execute.cut_head_chars` at most (default `1000`) and never more than a quarter of the room; the tail gets
@@ -188,7 +188,7 @@ from saying it twice across restarts). The key's `chi:` prefix keeps sw.js from
188
188
 
189
189
  `updated` is sent by the **restarted** chi web (its AutoUpdate finds the run at `phase: web`): the exec ends the
190
190
  old process, and `Notifier#stop` does not flush the shows still in their hold, so a push queued just before it would
191
- be lost. Sending from the new process also proves it came up.
191
+ be lost. Sending from the new process also proves it came up. The workers left then restart without another show.
192
192
 
193
193
  **Seeding**: `Notifier#start(hub)` subscribes with `snapshot: true` and takes the summaries as the seed, so what is
194
194
  open already never pushes. The hub's projection is empty before its first scan, so `Server.start` runs `hub.scan`
data/docs/memory.md CHANGED
@@ -182,7 +182,7 @@ Working habits for this model:
182
182
  `model_notes_deepseek` (`models: *deepseek-v4*`) and `model_notes_tone`
183
183
  (`models: *`, concise answers for every model). Each note's provenance
184
184
  and evidence is in the bundle's `notes/README.md`, and its spec pins which
185
- model ids load which note ([Plugins](plugins.md#the-model-notes-bundle)).
185
+ model ids load which note ([Bundles](bundles.md#the-model-notes-bundle)).
186
186
  - **Your own note of the same name wins.** The install skips a
187
187
  `model_notes_<name>.md` you already have and never owns it, so your note
188
188
  is what loads, the two never stack, and upgrades leave it alone.
@@ -225,7 +225,7 @@ memory's line are left out), so their size is paid on every request. Nothing cap
225
225
  or trims them; chi measures them (tokens estimated as characters /
226
226
  `context.chars_per_token`) and shows the size:
227
227
 
228
- - `chi self` has a `memory index` row:
228
+ - `chi self` has a `memory index` row (and a `big memories` row, see [A memory's size](#a-memorys-size)):
229
229
  `system ~2.0k tokens (54 lines), project ~1.4k tokens (35 lines)`, with
230
230
  `, over 2500` inside the parentheses for a scope over
231
231
  `memory.index_warn_tokens`.
@@ -252,6 +252,36 @@ may each get it. Bundle installs and uninstalls and hand edits
252
252
  change index lines without a note; `chi self`'s row shows where an index
253
253
  stands.
254
254
 
255
+ ## A memory's size
256
+
257
+ `memory_read` returns a memory whole: `max_tool_output_chars` does not cut it (see [Tool output
258
+ guardrails](internals/tool-output-limits.md#the-runners-cut-max_tool_output_chars)), so each read of a memory pays its
259
+ size. Two limits stay:
260
+
261
+ - `memory.read_max_chars` (default `0`, no fixed cap) caps a `memory_read` result at that many characters.
262
+ - An overflow guard: with that at 0 and a known context window, a result over half the window is cut. A context
263
+ overflow fails the turn and rolls its work back, and a retry repeats the read. With an unknown window there is no guard.
264
+ It checks one result, not the context already used.
265
+
266
+ A cut result ends with `[cut: N of M chars; the rest is in the memory's .md file in <dir>: read it with start_line]`;
267
+ `read` the file with `start_line` for the rest.
268
+
269
+ `memory.entry_warn_tokens` (default `5000`, about 20K characters at the default `context.chars_per_token`; `0` turns it
270
+ off) is the size a write may take one memory to before the model is told. When a `memory_write` with content, or a
271
+ `write`/`edit` on a memory file, takes a memory from at or under the limit to over it, the tool result ends with one note
272
+ (a memory that did not exist counts as 0, so a first big write gets it):
273
+
274
+ > Note: memory 'NAME' is now ~N tokens (over memory.entry_warn_tokens 5000), paid on each read. When you next have a moment, tighten it; split out only parts that are read separately, since each new memory adds an index line to every prompt. Don't remove or merge memories unless the user asks.
275
+
276
+ Once per crossing, as the index's note: a write while the memory is already over gets none. None for a
277
+ description-only write, a remove, the verbatim `index` write, a model overlay, a `model_notes_*` memory (it has its own
278
+ warning), a bundle install or a hand edit. It is independent of `memory.index_warn_tokens`: with that at 0 this note
279
+ still comes, and a write that crosses both has both notes. Nothing is said when a memory is read: that would repeat on
280
+ every read. `chi self` has a `big memories` row instead: the memories over the limit in both scopes, largest first
281
+ (5, then `+N more`), `none` when there are none, a bundle's tagged `from <bundle>` (the shipped
282
+ `config_modification_protocol` is over on every install; the fix is upstream's). This is how a
283
+ memory already over the limit gets seen.
284
+
255
285
  ## Skills
256
286
 
257
287
  A **skill** is a memory named `skill_<name>` that holds the steps of a
@@ -290,7 +320,7 @@ A skill is plain Markdown, no frontmatter:
290
320
 
291
321
  It is a memory like any other: `memory_read`, `chi --mute skill_release`,
292
322
  `chi bundle build` share it. The optional `skills` bundle (`chi bundle install
293
- skills`, [docs/plugins.md](plugins.md#the-skills-bundle)) adds `/skill save`,
323
+ skills`, [docs/bundles.md](bundles.md#the-skills-bundle)) adds `/skill save`,
294
324
  `/skill list`, `/skill show`, `/skill diff`, keeps older versions with a
295
325
  one-line diff after each update, and nudges a model that skips a failing
296
326
  step instead of fixing the skill.