samagotchi 0.40.0 → 0.42.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -1
  3. data/README.md +1 -1
  4. data/bin/chi +19 -2
  5. data/docs/cli.md +32 -7
  6. data/docs/configuration.md +34 -3
  7. data/docs/guardrails.md +15 -3
  8. data/docs/internals/prompt-caching.md +2 -1
  9. data/docs/memory.md +108 -1
  10. data/docs/sessions.md +5 -5
  11. data/docs/testing.md +16 -0
  12. data/lib/samagotchi/bridge/card_store.rb +6 -7
  13. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +23 -0
  14. data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -5
  15. data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
  16. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  17. data/lib/samagotchi/bundles/system/memory_guide.md +13 -0
  18. data/lib/samagotchi/bundles/system/self_map.md +4 -2
  19. data/lib/samagotchi/child_reports.rb +8 -4
  20. data/lib/samagotchi/config.rb +3 -0
  21. data/lib/samagotchi/context_command.rb +5 -2
  22. data/lib/samagotchi/engine.rb +121 -16
  23. data/lib/samagotchi/events.rb +8 -1
  24. data/lib/samagotchi/guardrails/model_size.rb +3 -4
  25. data/lib/samagotchi/guardrails/rules.rb +27 -17
  26. data/lib/samagotchi/guardrails/targets.rb +40 -1
  27. data/lib/samagotchi/guardrails/verdict.rb +6 -3
  28. data/lib/samagotchi/llm/errors.rb +33 -1
  29. data/lib/samagotchi/llm_context_command.rb +151 -0
  30. data/lib/samagotchi/llm_context_override.rb +180 -0
  31. data/lib/samagotchi/llm_context_strategy.rb +63 -13
  32. data/lib/samagotchi/memory_bundle/index_size.rb +103 -0
  33. data/lib/samagotchi/memory_bundle/index_sync.rb +3 -2
  34. data/lib/samagotchi/model_match.rb +54 -0
  35. data/lib/samagotchi/model_notes.rb +205 -0
  36. data/lib/samagotchi/parent_report.rb +12 -2
  37. data/lib/samagotchi/plugin/context.rb +3 -2
  38. data/lib/samagotchi/plugin/sessions.rb +3 -1
  39. data/lib/samagotchi/prompt_note.rb +53 -0
  40. data/lib/samagotchi/reply_wait.rb +3 -17
  41. data/lib/samagotchi/self_report.rb +47 -0
  42. data/lib/samagotchi/send_command.rb +22 -2
  43. data/lib/samagotchi/session.rb +44 -4
  44. data/lib/samagotchi/session_commands.rb +45 -6
  45. data/lib/samagotchi/session_manager.rb +5 -2
  46. data/lib/samagotchi/session_metrics.rb +47 -7
  47. data/lib/samagotchi/system_prompt.rb +79 -11
  48. data/lib/samagotchi/terminal_ui/attach_launcher.rb +13 -5
  49. data/lib/samagotchi/terminal_ui/attached_loop.rb +32 -12
  50. data/lib/samagotchi/terminal_ui/event_renderer.rb +2 -0
  51. data/lib/samagotchi/terminal_ui/formatting.rb +40 -0
  52. data/lib/samagotchi/terminal_ui.rb +16 -6
  53. data/lib/samagotchi/tool_runner.rb +26 -1
  54. data/lib/samagotchi/tools/memory.rb +70 -3
  55. data/lib/samagotchi/turn_flow.rb +44 -8
  56. data/lib/samagotchi/turn_note.rb +40 -1
  57. data/lib/samagotchi/version.rb +1 -1
  58. data/lib/samagotchi/web/app.rb +35 -6
  59. data/lib/samagotchi/web/public/app.js +47 -7
  60. data/lib/samagotchi/web/public/ctx.js +28 -5
  61. data/lib/samagotchi/web/public/format.js +29 -2
  62. data/lib/samagotchi/web/public/index.html +12 -0
  63. data/lib/samagotchi/web/public/live_turn.js +1 -0
  64. data/lib/samagotchi/web/public/llm_context_chip.js +194 -0
  65. data/lib/samagotchi/web/public/session_state.js +6 -0
  66. data/lib/samagotchi/web/public/timing.js +8 -2
  67. data/lib/samagotchi/web/public/turn_view.js +15 -2
  68. data/lib/samagotchi/web/session_summary.rb +4 -0
  69. data/lib/samagotchi/worker.rb +36 -18
  70. data/lib/samagotchi/worker_wakes.rb +2 -1
  71. metadata +8 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c41a6466e51c4da290f55cca9398a3221def773f8ee3235102e97322030c882
4
- data.tar.gz: 86bc79ce21478e0420e82fc47307fdf9934bee4a2642cda3c8bbf1c26a248c7e
3
+ metadata.gz: d41b91ef2514e877a756d7f66ba02f58b65c7d85f7446728b02ec06f652299b3
4
+ data.tar.gz: 37e0869508863e5db69395b847ac147101f60a79208a5ba9e789e3427eb9716e
5
5
  SHA512:
6
- metadata.gz: 52bf3393d2a7ff228aba277aa7fea67b4f82619578ba25d28c061e2256c8ef9a36698a250ca928c1916a3ba7a19ffef56fd8eeda7c232d37661fcd1188d63895
7
- data.tar.gz: 40d4c0006a4ac0a9d07072f09d691169d7c8d765eed4be45ff2c0ff5acdb04d5dc2ff20e009429abbdd54822d4a91e212f8efbbca75b43f0671382a1fcf16768
6
+ metadata.gz: abb02fdd05aec927500fbb42a750b9da3dc9e1c8e24cbc55bfdc933e797e206ee8e3a3502d0a4074804b0342f856726d2b04d2eb15c2aa61682e23439bca38ea
7
+ data.tar.gz: 681406d11e96949fd168b252a6717fe2f6dcfaa765400bf66a43a7131dc4440ff7f077312f3ccd219ddce8c05e88f15e4bc808c52652d265beed59eed89c8c1c
data/CHANGELOG.md CHANGED
@@ -8,6 +8,85 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.42.0] - 2026-10-08
12
+
13
+ ### Added
14
+
15
+ - Which model notes a session's prompt carried: each prompt build (and `/model`) records them in the session file
16
+ (`prompt_notes`: name, scope, size and a short digest), and they are shown in `/stats` (`model notes:
17
+ model_notes_deepseek (system, 612 chars)`), at the end of `/model`'s line (`; notes: …`, after a switch the new
18
+ model's), as a `notes: deepseek` chip in the web's info bar and as a `model notes` row in `chi self`. Nothing shows
19
+ without notes. See [docs/memory.md](docs/memory.md) ("Model notes").
20
+ - For development: `script/model_notes_report.rb`, which groups stored sessions by model and by the model notes
21
+ their prompt carried and compares the calls to the first edit and commit, commits per 100 steps, bare `&` in
22
+ `execute`, the longest run without an edit and the Continues and steers each session needed (`--model`,
23
+ `--since`, `--min-steps`, `--json`). It isn't part of the gem. See [docs/testing.md](docs/testing.md).
24
+
25
+ ### Fixed
26
+
27
+ - The web showed no failure line after a reload for a failed turn whose steps stayed (new in 0.41.0), so the turn
28
+ looked unfinished; it ends with the same `✕ turn failed: <why>; partial progress kept (!rollback restores the
29
+ pre-turn state)` line as live (from the turn record, which keeps the failure now; a turn recorded before this
30
+ shows none).
31
+ - A delegate report could reach the parent's model twice: a reminder or continue turn that failed before any
32
+ progress kept the report it had read, and the next turn brought it again. A canceled continue turn's report was
33
+ never delivered. A report's ring now goes only when the turn that read it stays in the conversation.
34
+ - `/llm-context` (the web's `llm ctx` chip, `--llm-context` on `--attach`) against a worker on an older chi answered
35
+ `400 not a session command`; the web (`501 not_supported`) and the attached terminal say the worker runs an older
36
+ chi, with the line that restarts it.
37
+
38
+ ## [0.41.0] - 2026-10-08
39
+
40
+ ### Added
41
+
42
+ - Model notes: a memory named `model_notes_<name>` whose first line is `models: <globs>|small` goes into the system
43
+ prompt of every session on a matching model, in its own section after the identity memory (all matching notes,
44
+ system scope then project). The habits of one model (or a family, or every small model) no longer ride on an
45
+ identity overlay keyed to one provider's id. `--mute model_notes_<name>` drops one for a session; `memory_write`
46
+ refuses a note without the `models:` line. See [docs/memory.md](docs/memory.md) ("Model notes").
47
+ - The guardrails bundle (0.9.0) asks, once at a time and only the user may allow it, before a model writes a memory
48
+ that reaches the system prompt: the identity memory, a model note or an overlay of either, by `memory_write`,
49
+ `write` or `edit`, through a symlink or in any case of the name (`prompt-memory-write`), or a model overlay of any
50
+ memory (`model-overlay-write`). A rule's `memory:` takes `overlay` and `prompt`. Run `chi update` to get it.
51
+ - A session's own LLM context strategy, apply rule and budget, before the model's: `chi --llm-context stale,forget
52
+ --llm-context-apply turn_end --llm-context-budget 64k` at start (also `chi send --new`, and `--resume`),
53
+ `/llm-context` in the session (no arguments: each value and where it came from; `strategy`, `apply`, `budget`,
54
+ `default` to unset one, `reset`), and the web's `llm ctx` chip in the info bar. Saved with the session (kept by
55
+ `--resume` and forks, not by delegates); a change applies from the next turn's start, and its reply says what it
56
+ costs (forget on or off re-reads the prompt once; stale turned on stages the reads already superseded as one
57
+ batch; a layer turned off sends its stubs whole again). `/stats` and the turn log show the effective strategy and
58
+ its source. See [docs/configuration.md](docs/configuration.md) ("LLM context: a session's own strategy").
59
+ - The memory indexes' size, which every prompt carries in full: `chi self` has a `memory index` row (`system ~2.0k
60
+ tokens (54 lines), project ~1.4k tokens (35 lines)`), and `/stats` and the web's ctx tooltip show what the
61
+ session's prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`). A
62
+ `memory_write`, `write` or `edit` that takes a scope's index over `memory.index_warn_tokens` (new; default 2500 per
63
+ scope, `0` off) gets one note asking the model to tighten long index descriptions when it has a moment, and not to
64
+ remove or merge memories unless the user asks; a write while already over gets none. See
65
+ [docs/memory.md](docs/memory.md) ("The index's size").
66
+
67
+ ### Changed
68
+
69
+ - A guardrails rule's `models:` also takes a `|`-separated string (`"small|deepseek-*"`), as
70
+ `guardrails.small_models` does.
71
+ - The system prompt's `Model:` line says guidance for this model goes in a model note (it said a memory overlay).
72
+ - `memory_write` refuses a memory name with a comma (`memory_read` reads a comma list of names).
73
+
74
+ ### Fixed
75
+
76
+ - A turn that failed partway (out of credits, a server error, any provider error but a context overflow) after tool
77
+ calls lost them: the saved session held only the failure note, and the prompt went back for a retry that would
78
+ run the task again over files it had already changed. Such a turn now keeps its steps, as a cancel does: the model
79
+ reads that it failed after N tool steps and that its work stays, the prompt is not handed back, and `!rollback`
80
+ still erases the turn (`partial progress kept` in the terminal and on the web; `chi send --wait` and a delegating
81
+ parent read `the turn failed after N tool steps: <why>; its work so far stays`). A turn that failed before it got
82
+ anywhere, or on a context overflow (kept, the conversation would stay too long for the window), is rolled back and
83
+ its prompt restored, as before. Since 0.2.0.
84
+ - On a case-insensitive disk a write to the memories' `INDEX.md` (any case) was taken for a memory and added a bogus
85
+ line to the index.
86
+
87
+ Update with `chi update`: it updates guardrails (0.9.0: asks before a model writes a memory that reaches the system
88
+ prompt; needs chi 0.41.0).
89
+
11
90
  ## [0.40.0] - 2026-10-08
12
91
 
13
92
  ### Added
@@ -1903,7 +1982,9 @@ and long-lived sessions.
1903
1982
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
1904
1983
  hotkey panel that send selected text or the clipboard to your sessions.
1905
1984
 
1906
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.40.0...HEAD
1985
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.42.0...HEAD
1986
+ [0.42.0]: https://github.com/dm1try/samagotchi/compare/v0.41.0...v0.42.0
1987
+ [0.41.0]: https://github.com/dm1try/samagotchi/compare/v0.40.0...v0.41.0
1907
1988
  [0.40.0]: https://github.com/dm1try/samagotchi/compare/v0.39.0...v0.40.0
1908
1989
  [0.39.0]: https://github.com/dm1try/samagotchi/compare/v0.38.0...v0.39.0
1909
1990
  [0.38.0]: https://github.com/dm1try/samagotchi/compare/v0.37.0...v0.38.0
data/README.md CHANGED
@@ -200,7 +200,7 @@ once, for the session, for the repo, or for the whole rule in the repo.
200
200
 
201
201
  - [CLI and REPL](docs/cli.md): flags, sharing a session, web UI, `/model`, status line
202
202
  - [Configuration](docs/configuration.md): `config.yml`, hosts, model server transports, timeouts, retries, logs
203
- - [Memory](docs/memory.md): scopes and model-specific overlays
203
+ - [Memory](docs/memory.md): scopes, model-specific overlays and model notes
204
204
  - [Sessions](docs/sessions.md): storage, retention, `chi sessions`
205
205
  - [Attached context](docs/context.md): `chi context`, sources, scopes, waking, the github-pr bundle, safety
206
206
  - [Broadcast](docs/broadcast.md): `chi broadcast`, who a note reaches, tags, triage, what a session gets, the triage log
data/bin/chi CHANGED
@@ -82,7 +82,7 @@ ARGV.each do |arg|
82
82
  base = arg.split("=", 2).first
83
83
  # If dashed version is a known flag (registry or hardcoded), reject underscore variant
84
84
  dashed = base.tr("_", "-")
85
- known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --memory --mute --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
85
+ known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --memory --mute --llm-context --llm-context-apply --llm-context-budget --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
86
86
  if known.include?(dashed) || known.include?(base)
87
87
  warn "Unknown option: #{arg} (did you mean #{dashed}?)"
88
88
  exit 1
@@ -120,6 +120,10 @@ parser = OptionParser.new do |opts|
120
120
  "sessions already running keep theirs) [env SAMAGOTCHI_THINKING_LEVEL]") { |l| cli_overrides["thinking.level"] = l }
121
121
  opts.on("--memory NAME", "Preload a memory entry into the system prompt (repeatable; a comma list too)") { |m| (options[:memories] ||= []) << m }
122
122
  opts.on("--mute NAME", "Hide a memory from this session: not in the prompt, refused by memory_read (repeatable; a comma list too)") { |m| (options[:muted] ||= []) << m }
123
+ opts.on("--llm-context LAYERS", "This session's own LLM context strategy: none, or layers such as stale,forget (saved in the session, " \
124
+ "before the model's; /llm-context changes it)") { |v| (options[:llm_context] ||= {})[:strategy] = v }
125
+ opts.on("--llm-context-apply RULE", "This session's own apply rule for it: payoff|next_request|turn_end") { |v| (options[:llm_context] ||= {})[:apply] = v }
126
+ opts.on("--llm-context-budget TOKENS", "This session's own context budget: tokens (64000, 64k) or off") { |v| (options[:llm_context] ||= {})[:budget_tokens] = v }
123
127
  opts.on("--no-interrupt", "Raise the tool call limit to 1000 iterations (useful for long tasks)") { options[:no_interrupt] = true; cli_overrides["no_interrupt"] = true }
124
128
  opts.on("--non-interactive", "Run a single prompt and exit (no REPL, implies --no-interrupt)") { options[:non_interactive] = true }
125
129
  opts.on("-v", "--verbose", "Print raw LLM responses and tool calls/results (runs a plain REPL, not attached)") { options[:verbose] = true }
@@ -286,6 +290,17 @@ if options[:memories] || options[:muted]
286
290
  end
287
291
  end
288
292
 
293
+ # --llm-context*: checked here, before anything starts.
294
+ if options[:llm_context]
295
+ require "samagotchi/llm_context_override"
296
+ begin
297
+ Samagotchi::LLMContextOverride.update(nil, options[:llm_context])
298
+ rescue ArgumentError => e
299
+ warn "Error: #{e.message}"
300
+ exit 1
301
+ end
302
+ end
303
+
289
304
  chi_update_hint.call
290
305
  require "samagotchi/launch_mode"
291
306
  launch, launch_note = if scratch
@@ -317,7 +332,8 @@ if launch == :attached
317
332
  no_interrupt: !!options[:no_interrupt],
318
333
  default_input: !options[:no_default_input],
319
334
  memories: options[:memories] || [],
320
- muted_memories: options[:muted] || [])
335
+ muted_memories: options[:muted] || [],
336
+ llm_context: options[:llm_context] || {})
321
337
  rescue Samagotchi::TerminalUI::AttachLauncher::Error => e
322
338
  warn "Error: #{e.message}"
323
339
  exit 1
@@ -334,6 +350,7 @@ begin
334
350
  no_default_input: options[:no_default_input],
335
351
  memories: options[:memories] || [],
336
352
  muted_memories: options[:muted] || [],
353
+ llm_context: options[:llm_context] || {},
337
354
  non_interactive: options[:non_interactive],
338
355
  model_name: options[:model],
339
356
  scratch: scratch
data/docs/cli.md CHANGED
@@ -22,7 +22,7 @@
22
22
  - `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`). An approval can be denied, not allowed, unless `guardrails.parent_approvals: once` (a convention, not a security boundary; see [Guardrails](guardrails.md#approvals-from-a-parent-agent))
23
23
  - `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text or images to a session (live, stopped or new) as a message (⏎, a turn runs) or a context note (⌘⏎) (see [Desktop helper](desktop.md))
24
24
  - `chi models [--format text|json] [--timeout S] [TEXT]` — list the models every configured host offers, as the names `--model` takes (see [Listing the models](#listing-the-models))
25
- - `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
25
+ - `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`), and `model notes` the model notes a prompt of that model loads (`model_notes_deepseek (system, 612 chars)`, `none`; inside a session its `--mute` list is left out). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
26
26
  - `chi update [--dry-run] [--no-gem] [--no-bundles] [--no-desktop]` — update an installed chi: the gem, the system bundle, the shipped bundles you installed and the desktop helper, in one table (see [Updating](#updating))
27
27
  - `chi bundle install|upgrade|uninstall|status|diff|list|build|trash` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
28
28
 
@@ -184,6 +184,9 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
184
184
  | `--profile NAME` | Prompt profile (`qwen36` or `gemma4`) for every model in this run, over config and the server's template (same as `--model-profile`, env `SAMAGOTCHI_MODEL_PROFILE`). See "Prompt profile" in configuration.md. |
185
185
  | `--memory NAME` | Preload a memory entry into the system prompt (repeatable; a comma list too). Merged under the config.yml `memories:` baseline. Works attached: the list is stored on the session, so its worker builds the same prompt on every respawn. |
186
186
  | `--mute NAME` | Hide a memory from this session (repeatable; a comma list too): its index line is not in the prompt, `memory_read` refuses it, the identity auto-load skips it, and it is dropped from the preloads (config baseline or `--memory`). A name matches in both scopes (`gh-helper`, `project/gh-helper` and `gh-helper.md` all hide `gh-helper`). Nothing on disk changes. See [Muting a memory](#muting-a-memory). |
187
+ | `--llm-context LAYERS` | This session's own LLM context strategy: `none`, or layers such as `stale` or `stale,forget` (comma, space or `\|` separated). Saved in the session, before the model's `llm_context_strategy`. With `--resume` it is set on the resumed session; on an attached or resumed worker it runs as `/llm-context` before the first prompt (after `--model`'s `/model`). A value that isn't one is an error before anything starts. See "LLM context: a session's own strategy" in configuration.md. |
188
+ | `--llm-context-apply RULE` | This session's own apply rule: `payoff`, `next_request` or `turn_end`. |
189
+ | `--llm-context-budget TOKENS` | This session's own context budget: `64000`, `64k` (4k to 10M), or `off` (wins over the model's budget). |
187
190
  | `--no-interrupt` | Raise the tool-call limit to 1000 iterations for long tasks. |
188
191
  | `--no-default-input` | Skip prefilling the first REPL line from `SAMAGOTCHI_DEFAULT_INPUT`. |
189
192
  | `-v`, `--verbose` | Log at debug level (raw LLM responses, tool call/result payloads) and print every log record to stderr too. |
@@ -337,7 +340,8 @@ note or image) is deleted as its worker exits, and `/exit` says so; set
337
340
 
338
341
  `chi sessions stop ID...` stops each session's worker and waits for it to exit, so
339
342
  a `chi --resume ID` after it starts a fresh one. A worker still running an
340
- older chi (from before an upgrade) takes turns but not commands; the attached
343
+ older chi (from before an upgrade) takes turns, but not commands, or not the
344
+ ones newer than it (`/llm-context`, `--llm-context` on `--attach`); the attached
341
345
  terminal and the Web UI say so, with that restart line.
342
346
 
343
347
  `chi sessions restart ID...` hands each session to a new worker on the newest
@@ -425,7 +429,7 @@ request (with the cached tokens, the cache writes, the re-prefilled tokens and t
425
429
  reports them),
426
430
  the decode speed, generation latency, cancellations, retries (the network's),
427
431
  thinking cuts (generations a plugin such as loop-guard cut) and output cap hits
428
- (generations that ended at the provider's cap), context used and window, the prompt profile and the model the server says it ran, and the
432
+ (generations that ended at the provider's cap), context used and window, the memory indexes the session's prompt holds ([Memory](memory.md#the-indexs-size)), the model notes it carried (`model notes: model_notes_deepseek (system, 612 chars)`, none without any; [Memory](memory.md#model-notes)), the prompt profile and the model the server says it ran, and the
429
433
  cost when the provider reports one (OpenRouter's `usage.cost`; no prices are
430
434
  guessed). The speed line is the last generation's and the session's average
431
435
  (weighted by tokens): llama.cpp reports it exactly (`87 tok/s out, 1.9k tok/s
@@ -437,6 +441,22 @@ keeps its own numbers: a parent's tokens and cost leave them out. `/recap` is in
437
441
  [Session recap](#session-recap), `/model` and `/models` in [Runtime Model
438
442
  Switch](#runtime-model-switch-assist-mode).
439
443
 
444
+ `/llm-context` shows the LLM context strategy, apply rule and budget the next
445
+ turn runs under, each with where it came from (`the session`, `models: <key>`,
446
+ `hosts entry '<name>'`, `llm_context.*`), and the session's own values.
447
+ `/llm-context strategy <none|stale|stale,forget> apply <payoff|next_request|turn_end>
448
+ budget <tokens|64k|off>` sets the session's own (any of the three, in one line;
449
+ `default` unsets one), and `/llm-context reset` unsets them all. A change is
450
+ saved with the session and applies from the next turn's start; the reply says
451
+ what it costs: `forget` on or off re-reads the whole prompt once, `stale` turned
452
+ on stages the reads already superseded as one batch under the apply rule, and a
453
+ layer turned off sends its stubs whole again (turning it back on brings them
454
+ back). It runs between turns (a worker answers `busy` mid-turn). `/stats` shows
455
+ the same values on its `llm context:` line. The web's info bar shows them as the
456
+ `llm ctx` chip (`llm ctx stale,forget · session`), whose popover sets them
457
+ through `/llm-context`. See "LLM context: a session's own strategy" in
458
+ [configuration.md](configuration.md).
459
+
440
460
  ### Typing during a turn
441
461
 
442
462
  The prompt stays open while a turn runs, in an attached terminal and in the plain
@@ -689,7 +709,7 @@ In interactive assist mode, you can switch the request model without restarting:
689
709
  - `/model host:model` or `/model host:alias`: qualified host routing (only `:` names a host; `openai/gpt-4o` is a model id). `host:alias` applies the alias; an alias whose target names another host is refused (`alias 'tiny' names host 'box', not 'openrouter'`). Aliases apply once: an alias pointing to another alias sends that name as written.
690
710
  - `/model --default <name>`: set session model and persist as new default (`default.model`) in `config.yml` for future sessions (supports `host:model` full ref). It writes only the file: an exported `SAMAGOTCHI_DEFAULT_MODEL` (or `--model`) still wins over it.
691
711
  - `/model <name> --alias <alias>`: create alias for current effective model (alias value may be bare or `host:model`).
692
- - `/model`: show the effective model (and default when diverged: `runtime model: <effective> (default: <default>, profile=<name>, <source>)`, e.g. `profile=qwen36, server (chat_template)`).
712
+ - `/model`: show the effective model (and default when diverged: `runtime model: <effective> (default: <default>, profile=<name>, <source>)`, e.g. `profile=qwen36, server (chat_template)`). When the session's prompt carries model notes the line ends with `; notes: model_notes_deepseek (system, 612 chars)`; a switch's `runtime model set to …` names the new model's ([Memory](memory.md#model-notes)).
693
713
  - `/model clear` (or `default`/`none`/`off`): clear the session override, reverting to the configured default.
694
714
  - `/guardrails`: the guardrail rules (by source), what failed to load, and your stored approvals, numbered; `/guardrails revoke N` removes approval N (see [Guardrails](guardrails.md)).
695
715
  - `/models`: list model ids aggregated across all `hosts:` (an alias shows next to its id: a bare alias on every host listing the id, a `host:model` one only under its host; grouped `host (host:port):` with per-host `unreachable` warnings, e.g. an unset `api_key_env`; lists cached 60s, 10 minutes for a remote host; lazy — no startup prefill). At most 20 ids per host, then `… and N more`; `/models <text>` lists every id containing `<text>` (any case), e.g. `/models qwen` on OpenRouter.
@@ -861,9 +881,14 @@ Behavior notes:
861
881
  - Cancellation returns control to the prompt immediately; what you typed there stays.
862
882
  - The turn ends with one line, `■ turn canceled (Ctrl-C) · 3.1s` (`■`, a stop someone chose, in amber), and for a prompt turn a dim
863
883
  `partial progress kept; !rollback restores the pre-turn state` under it (a canceled continue is back where it
864
- started). A failed turn ends with `✕ turn failed: <summary> · 2.0s` and a dim `prompt restored for retry`. The
865
- REPL and attached mode say the same; the web says `■ canceled (Ctrl-C)` (`stopped` for its Stop button,
866
- `by a hook` for a hook's). A turn a bundle's plugin stopped names it: `■ turn stopped by loop-guard` (the web:
884
+ started). A failed turn ends with `✕ turn failed: <summary> · 2.0s` and a dim `prompt restored for retry` when it
885
+ failed before it got anywhere, or on a context overflow (the conversation goes back to before it); one that got to
886
+ tool steps keeps them, as a cancel does, with the dim `partial progress kept; !rollback restores the pre-turn state`
887
+ (the model reads a note saying how many steps ran and why it stopped; `chi send --wait` and a delegating parent
888
+ read `the turn failed after N tool steps: <why>; its work so far stays`). The
889
+ REPL and attached mode say the same; the web ends a failed turn whose steps stayed with `✕ turn failed: <summary>;
890
+ partial progress kept (!rollback restores the pre-turn state)`, after a reload too, and says `■ canceled (Ctrl-C)`
891
+ (`stopped` for its Stop button, `by a hook` for a hook's). A turn a bundle's plugin stopped names it: `■ turn stopped by loop-guard` (the web:
867
892
  `■ stopped by loop-guard`).
868
893
  - Visible text the canceled request had streamed stays in the conversation, marked `[interrupted]`, so the next
869
894
  message (or a continue) picks up from the half-finished reply; the canceled request's thinking and any unfinished
@@ -900,6 +900,36 @@ models:
900
900
  - `/stats` counts the re-prefilled tokens each applied batch costs; `script/llm_context_bench.rb --strategy
901
901
  forget_outputs` replays the offer on stored sessions (docs/internals/llm-context-bench.md).
902
902
 
903
+ ## LLM context: a session's own strategy
904
+
905
+ A session can set its own `llm_context` strategy, apply rule and budget, which come before the model's, its host's
906
+ and the global ones (resolution: the session, `models.<key>`, `hosts.<name>`, then `llm_context.*`; each value on
907
+ its own). The first target is a long session such as a day-long coordinator, where `stale` pays off across many turns.
908
+
909
+ - **Set it** at start with `chi --llm-context stale,forget --llm-context-apply turn_end --llm-context-budget 64k`
910
+ (the same flags on `chi send --new`), in the session with `/llm-context strategy … apply … budget …`
911
+ (docs/cli.md, "Session commands"), or from the web's `llm ctx` chip in the info bar.
912
+ - **Values:** `none` and a budget of `off` are values the session sets on purpose (they win over the model's);
913
+ `default` (or `/llm-context reset` for all three) unsets one, back to following the model. A budget is 4k to
914
+ 10M tokens (`4000`–`10000000`, or `4k`–`10000k`). The layers are kept in chi's order (`stale`, then `forget`).
915
+ A value in the session file chi can't read (an unknown layer) follows the model and is saved back as written.
916
+ - **Kept:** in the session file (`llm_context`), so `--resume` and a respawned worker keep it; a plugin's fork
917
+ copies its parent's; a delegate child starts without one (it follows its own model).
918
+ - **When it applies:** from the next turn's start; a running turn keeps the strategy it started with. The command
919
+ runs between turns, like `/model`.
920
+ - **Switching on `stale`:** the reads the conversation already holds that a later read superseded go in as one
921
+ batch under the session's apply rule (`next_request`: the next request; `turn_end`: the end of the next turn;
922
+ `payoff`: as for any batch). `/llm-context` says how many.
923
+ - **Switching `forget` on or off** changes the tool list (`forget_outputs`), the ids on outputs and the system
924
+ prompt: the next request re-reads the whole prompt once (a full cache break). Turned off, its past `forget_outputs` calls
925
+ and their results stay in the history.
926
+ - **Switching a layer off:** its stubs go out whole again from the next request (a cache break from the first). The
927
+ session keeps the originals and the edits, so turning the layer back on brings the same stubs back.
928
+ - **Where to see it:** `/llm-context` (each value and where it came from: `the session`, `models: <key>`,
929
+ `hosts entry '<name>'`, `llm_context.*`), `/stats` (`llm context:`), the turn log (an `llm_context` record per
930
+ turn that runs a layer or whose session set its strategy) and the web's chip. A woken worker's status line counts
931
+ its saved context against the session's budget.
932
+
903
933
  ## All settings
904
934
 
905
935
  Every setting below takes the three forms described in
@@ -962,16 +992,17 @@ described in their own sections.
962
992
  | `context.status` | `true` | yes | Context-usage telemetry for the model. See [context telemetry](internals/context-telemetry.md). |
963
993
  | `context.window_tokens` | server's, else 256000 | yes | Context window when the server doesn't report one. `models.<key>.window_tokens` and `hosts.<name>.window_tokens` come first; see "The context window". |
964
994
  | `models.<key>.window_tokens`, `hosts.<name>.window_tokens` | none | | A model's or host's context window when the server and its model list report none. |
965
- | `llm_context.strategy` | `none` | | What the model is sent of the conversation: `none` (everything, unchanged), or a list of layers (`stale`, `forget`; `"\|"`-separated or a YAML list). `stale`: a file read that a later read covering its lines superseded (a read that came back whole: not the head/tail preview of a big file, nor cut at `max_tool_output_chars`) is sent as a stub (`[read] lib/x.rb lines 1-200: superseded by a later read`). With `llm_context.stale_edits: true` (opt-in, experimental) so is a read a later successful edit or write of the file superseded (`superseded by a later edit`); on the replay bench these stubs cost about 5x the tokens they free in re-prefill, and 26% of them were needed again (against a 6% base rate). No read of a file edited or written in the last `llm_context.protect_steps` steps is stubbed. When stubs reach the prompt is `llm_context.apply`: `next_request` sends a read a later read superseded from the next request, and an edit-driven stub at the end of a turn the model answered; `turn_end` sends them all at the end of a turn the model answered (a turn that ran out of steps keeps them staged; on the native loop against a local llama.cpp the turn-end warm-up prefills the edited prompt, elsewhere the next turn's first request carries the cache break); `payoff` sends the batch at a request when the tokens it frees are at least the tail after its earliest stub (what the server reads again), or the context is in the top `context.status_thresholds` bucket (which needs `context.status` on), else at the end of a turn the model answered. A re-read's tail holds the later read, so in practice payoff usually waits for the turn's end. Each batch breaks the prompt cache from its earliest stub; `/stats` counts the re-prefilled tokens. The session keeps the outputs and the stubs it sent, so a `--resume` sends the same. `forget` (experimental) offers the model the `forget_outputs` tool; see "LLM context: the forget layer". An unknown name warns and is `none`. `models.<key>.llm_context_strategy` and `hosts.<name>.llm_context_strategy` come first. |
995
+ | `llm_context.strategy` | `none` | | What the model is sent of the conversation: `none` (everything, unchanged), or a list of layers (`stale`, `forget`; `"\|"`-separated or a YAML list). `stale`: a file read that a later read covering its lines superseded (a read that came back whole: not the head/tail preview of a big file, nor cut at `max_tool_output_chars`) is sent as a stub (`[read] lib/x.rb lines 1-200: superseded by a later read`). With `llm_context.stale_edits: true` (opt-in, experimental) so is a read a later successful edit or write of the file superseded (`superseded by a later edit`); on the replay bench these stubs cost about 5x the tokens they free in re-prefill, and 26% of them were needed again (against a 6% base rate). No read of a file edited or written in the last `llm_context.protect_steps` steps is stubbed. When stubs reach the prompt is `llm_context.apply`: `next_request` sends a read a later read superseded from the next request, and an edit-driven stub at the end of a turn the model answered; `turn_end` sends them all at the end of a turn the model answered (a turn that ran out of steps keeps them staged; on the native loop against a local llama.cpp the turn-end warm-up prefills the edited prompt, elsewhere the next turn's first request carries the cache break); `payoff` sends the batch at a request when the tokens it frees are at least the tail after its earliest stub (what the server reads again), or the context is in the top `context.status_thresholds` bucket (which needs `context.status` on), else at the end of a turn the model answered. A re-read's tail holds the later read, so in practice payoff usually waits for the turn's end. Each batch breaks the prompt cache from its earliest stub; `/stats` counts the re-prefilled tokens. The session keeps the outputs and the stubs it sent, so a `--resume` sends the same. `forget` (experimental) offers the model the `forget_outputs` tool; see "LLM context: the forget layer". An unknown name warns and is `none`. A session's own (`chi --llm-context`, `/llm-context`; "LLM context: a session's own strategy"), then `models.<key>.llm_context_strategy` and `hosts.<name>.llm_context_strategy` come first. |
966
996
  | `models.<key>.llm_context_strategy`, `hosts.<name>.llm_context_strategy` | none | | A model's or host's `llm_context.strategy`. |
967
- | `llm_context.apply` | `payoff` | | When a layer's edits reach the prompt (each batch breaks the prompt cache from its earliest edit): `payoff`, `next_request` or `turn_end`. An unknown value warns and is `payoff`. `models.<key>.llm_context_apply` and `hosts.<name>.llm_context_apply` come first. |
997
+ | `llm_context.apply` | `payoff` | | When a layer's edits reach the prompt (each batch breaks the prompt cache from its earliest edit): `payoff`, `next_request` or `turn_end`. An unknown value warns and is `payoff`. A session's own (`--llm-context-apply`, `/llm-context apply`), then `models.<key>.llm_context_apply` and `hosts.<name>.llm_context_apply` come first. |
968
998
  | `models.<key>.llm_context_apply`, `hosts.<name>.llm_context_apply` | none | | A model's or host's `llm_context.apply`. |
969
999
  | `llm_context.stale_edits` | `false` | | Opt-in, experimental: `stale` also stubs a read that a later successful edit or write of the file superseded. |
970
1000
  | `llm_context.protect_steps` | `3` | | `stale` never stubs a read of a file edited or written in the last N steps (tool batches; counted across the whole conversation, not only the turn). `0` turns the protection off. Under `forget`, `forget_outputs` refuses an output from the last N steps, and a read of a file edited or written in them unless the forget keeps some of its lines. |
971
- | `llm_context.budget_tokens` | none | | A soft context budget in tokens (e.g. `64000`); off when unset or `0`. When set, the context status buckets (`context.status_thresholds`) count against it instead of the window (the smaller of the two), so the `[CONTEXT: …]` lines, `payoff`'s top bucket and, under `forget`, the `forget_outputs` offers come under it. `models.<key>.llm_context_budget_tokens` and `hosts.<name>.llm_context_budget_tokens` come first. |
1001
+ | `llm_context.budget_tokens` | none | | A soft context budget in tokens (e.g. `64000`); off when unset or `0`. When set, the context status buckets (`context.status_thresholds`) count against it instead of the window (the smaller of the two), so the `[CONTEXT: …]` lines, `payoff`'s top bucket and, under `forget`, the `forget_outputs` offers come under it. A session's own (`--llm-context-budget`, `/llm-context budget`; `off` there wins over the model's), then `models.<key>.llm_context_budget_tokens` and `hosts.<name>.llm_context_budget_tokens` come first. |
972
1002
  | `models.<key>.llm_context_budget_tokens`, `hosts.<name>.llm_context_budget_tokens` | none | | A model's or host's `llm_context.budget_tokens`. |
973
1003
  | `llm_context.policy` | the subtask-boundaries line | | The sentence `forget_outputs`' description carries (the `forget` layer): "Tidy at subtask boundaries: once a subtask is done, forget its tool outputs and note what it established; keep anything you'll still edit against." Blank: none. Read when a session starts. |
974
1004
  | `context.chars_per_token` | `4.0` | yes | Estimate ratio when the server reports no usage. |
1005
+ | `memory.index_warn_tokens` | `2500` | yes | The tokens one scope's memory index (project or system; sent with every prompt) may hold before a `memory_write`, `write` or `edit` that takes it over gets a note asking the model to tighten long index descriptions; `0`: no note. Estimated as characters / `context.chars_per_token`. `memory:` is its own section, not the `memories:` preload list. See [Memory](memory.md). |
975
1006
  | `context.status_thresholds` | `20,40,60,80` | yes | Percentages that trigger a status. |
976
1007
  | `context.status_cadence` | `0` | yes | Also every N rounds; `0` = thresholds only. |
977
1008
  | `context.every_seconds` | `300` | | How often an attached context source's command runs when it has no `--every` (seconds, at least 30). See [Attached context](context.md). |
data/docs/guardrails.md CHANGED
@@ -249,7 +249,12 @@ A rule gives at least one of `tool`, `command`, `path`, `git`, `touches`, `rm` a
249
249
  `memory: remove` matches a `memory_write` with `remove: true` (it moves a
250
250
  memory into the bundle trash). Nothing else asks about it: a memory's file is
251
251
  never outside the repo (below), so without such a rule any session, a delegate
252
- child included, removes a memory unasked.
252
+ child included, removes a memory unasked. `memory: overlay` matches a
253
+ `memory_write` of a model overlay (`current_model_only: true`). `memory:
254
+ prompt` matches a `memory_write`, `write` or `edit` of a memory file that
255
+ reaches the system prompt (`identity.md`, `model_notes_*.md` and their
256
+ overlays) right in a memories folder (the system scope's or a project's),
257
+ resolved: symlinks, `..` and the case the disk has; the name in any case.
253
258
 
254
259
  `rm: outside_tmp` (shell tools) matches a command whose `rm -r -f` reaches
255
260
  outside the tmp folders (`$TMPDIR`, `/tmp`, `/var/tmp`): every such `rm` in a
@@ -359,7 +364,9 @@ match no rule. `disable:` only removes rules; hooks and the core checks still vo
359
364
 
360
365
  `models:` makes a rule vote only for some models: `small`, or a glob on the
361
366
  model's name (`Qwen3.6-*`, matched on the bare name without the host prefix and
362
- on the model key `qwen3-6-27b`, case-insensitively), or a list of them. Without
367
+ on the model key `qwen3-6-27b`, case-insensitively), or a list of them (a YAML list or
368
+ `|`-separated, `"small|deepseek-*"`; model notes take the same grammar, see
369
+ [Memory](memory.md#model-notes)). Without
363
370
  `models:` a rule is for every model. With no model name set, a `models:` rule
364
371
  doesn't vote.
365
372
 
@@ -452,7 +459,12 @@ once at a time and only by the user: it is the user's command, which also
452
459
  refuses to run inside a session; see [Broadcast](broadcast.md)); removing a
453
460
  memory with `memory_write remove: true` (`memory-remove`, once at a time:
454
461
  a delegate child could otherwise remove its parent's handoff unasked; see
455
- [Memory](memory.md)); and answering chi's
462
+ [Memory](memory.md)); writing the identity memory, a model note or an overlay
463
+ of either with `memory_write`, `write` or `edit` (`prompt-memory-write`), or a
464
+ model overlay of any memory (`model-overlay-write`), each once at a time and
465
+ only by the user: these change a model's system prompt for later sessions (see
466
+ [Model notes](memory.md#model-notes); a shell command reaches them only under
467
+ `shell-touches-chi`'s text heuristic); and answering chi's
456
468
  questions around `chi answer`: `chi --attach` or `chi -p` with stdin from a
457
469
  pipe, a here-string or a file (`chi-answer-piped`), and `curl`/`wget` to a
458
470
  session's `/answer` route (`chi-answer-http`), both once or for the session.
@@ -29,7 +29,8 @@ right-hand column happens.
29
29
  │ [native thinking token] base prompt │
30
30
  │ rg guidance │ /thinking level,
31
31
  │ system identity (+ model overlay) │ /model, editing a
32
- │ explicit (preloaded) memories │ memory / AGENT.md
32
+ │ model notes (model_notes_* matching the model) │ memory / AGENT.md
33
+ │ explicit (preloaded) memories │
33
34
  │ AGENT.md │ (picked up by a new
34
35
  │ project + system memory indexes │ process or a rebuild)
35
36
  │ ◄── remote Claude: cache_control breakpoint #1 │
data/docs/memory.md CHANGED
@@ -25,7 +25,7 @@ Tool behavior:
25
25
  Each memory entry may have a companion file named `<name>.<model-key>.md` in the same scope directory. When the entry is read under a matching model, the overlay body is appended automatically, separated by the standard `---` separator with a `Model-specific guidance (<key>):` header.
26
26
 
27
27
  - **Key derivation**: The harness normalizes the model name sent to the server, without its host prefix (`box:qwen3.6-35b-a3b` keys as `qwen3.6-35b-a3b`; an alias keys as its target, and the alias's own key is read when the target has no overlay) (lowercase, replace non-alphanumeric with `-`, squeeze dashes) to derive the file key. For example, `qwen3.6-35b-a3b` → `qwen3-6-35b-a3b`.
28
- - **Saving overlays**: Pass `current_model_only: true` to `memory_write` (the harness resolves the model key automatically). This writes the content as `<name>.<model-key>.md` and skips index maintenance.
28
+ - **Saving overlays**: Pass `current_model_only: true` to `memory_write` (the harness resolves the model key automatically). This writes the content as `<name>.<model-key>.md` and skips index maintenance. The guardrails bundle asks before a model writes one (`model-overlay-write`). Habits of a model in general go in a [model note](#model-notes), which can name a family or all small models.
29
29
  - **Which key is mine**: the system prompt's `Model:` line names the session's model key, and `chi self` (via `execute`) has a `model key` row; both follow `/model`.
30
30
  - **In the system prompt**: the identity memory and the preloaded memories (`--memory`, config `memories:`) get their overlays too, as `memory_read` gives them; after `/model` the rebuilt prompt carries the new model's.
31
31
  - **Dormancy**: Overlays are only active under the matching model key; other models see the base entry only.
@@ -36,6 +36,79 @@ Each memory entry may have a companion file named `<name>.<model-key>.md` in the
36
36
  - The overlay is read in the scope its base is found in: a project memory with the same name shadows a system-scope bundle's base and overlay.
37
37
 
38
38
 
39
+ ## Model notes
40
+
41
+ A **model note** is a memory named `model_notes_<name>` whose first line says
42
+ which models it is for. It goes into the system prompt of every session on a
43
+ matching model, so it is where a model's own habits go ("explore briefly, then
44
+ act"), independent of the identity memory:
45
+
46
+ ```markdown
47
+ models: deepseek-*|*deepseek-v4*
48
+ Working habits for this model:
49
+ - Explore briefly, then act.
50
+ ```
51
+
52
+ - **`models:`**: `|`-separated entries, each `small` (a small model per
53
+ `guardrails.small_models`, see [Guardrails](guardrails.md)) or a glob
54
+ (case-insensitive, `*`, `?`, `{a,b}`) on the model id sent to the server
55
+ (no host prefix) or on its model key. Any entry matching loads the note. The
56
+ grammar is the guardrails rules' `models:`.
57
+ - **Stacked**: every matching note loads, the system scope's first, then the
58
+ project's, by name within a scope. Notes add habits; none overrides another.
59
+ - **In the prompt**: their own section right after the identity memory,
60
+ `Model notes (for <model>, scope=…):`, each note under its name, the
61
+ `models:` line left out. A note's index line is left out of the prompt's
62
+ memory indexes (its body is already there, or it isn't for this model); the
63
+ tools still see it.
64
+ - **Overlays**: a note is read like any memory, so its own
65
+ `model_notes_<name>.<key>.md` overlay is appended.
66
+ - **Muting**: `chi --mute model_notes_<name>` drops one for a session. Muting
67
+ `identity` no longer drops a model note.
68
+ - **Names**: no dot after `model_notes_` (`memory_write` refuses one: next to a
69
+ note of the stem's name it would read as a model overlay); a dotted file
70
+ made by hand is skipped with a warning. No memory name takes a comma
71
+ (`memory_read` reads a comma list of names).
72
+ - **The `models:` line**: `memory_write` refuses a `model_notes_` memory whose
73
+ first line isn't one (`models:`, any case, with at least one entry). A file
74
+ made by hand without one is skipped with a warning (once, in the debug log)
75
+ naming the file, and keeps its index line in the prompt.
76
+ - **Asked first**: with the guardrails bundle, a model's write of a memory that
77
+ reaches the system prompt (identity, a model note, or an overlay of either)
78
+ with `memory_write`, `write` or `edit`, through a symlink or in any case of
79
+ the name, asks the user, once at a time (`prompt-memory-write`); so does a
80
+ model overlay of any memory (`model-overlay-write`). Only the user may allow
81
+ either, not a parent session. The prompt's `Model:` line tells the model that
82
+ guidance for it goes in a model note.
83
+ - **Exact prefix**: only files named `model_notes_…` as written load; a
84
+ `MODEL_NOTES_x.md` (which a case-insensitive disk's listing also returns)
85
+ doesn't.
86
+ - **Size**: they cost every request. A note over 1,500 characters, or all of a
87
+ model's notes over 3,000, still loads but warns once.
88
+ - **Timing**: a note takes effect at the next prompt build: a session's start,
89
+ a resume, a web worker waking, `/model`. A note written mid-session doesn't
90
+ change the running prompt.
91
+ - **Recorded and shown**: each prompt build writes the notes it carried to the
92
+ session file (`prompt_notes`: name, scope, size in characters and a short
93
+ digest of the text), so a session says later what it ran with; `/model`
94
+ switches record the new model's. `/stats` has a
95
+ `model notes: model_notes_deepseek (system, 612 chars)` line, `/model`
96
+ ends its model line with `; notes: …` (after a switch, the new model's), the
97
+ web's info bar a `notes: deepseek` chip (the full line in its tooltip), and
98
+ `chi self` a `model notes` row after `model key`: the notes a prompt of the
99
+ reported model loads now, the session's `--mute` list left out when it runs
100
+ in one, `none` without any. None of the others shows a line without notes.
101
+ A resumed session, or a web worker that woke, shows the saved ones until its
102
+ first turn builds the prompt again.
103
+ - **Judged**: `ruby script/model_notes_report.rb` (a development script, not
104
+ shipped) groups stored sessions by model and recorded notes and compares the
105
+ calls to the first edit and commit, commits per 100 steps, bare `&`, the
106
+ longest run without an edit and the Continues and steers each needed; run a
107
+ note on and muted for the same kind of task. See
108
+ [testing.md](testing.md) ("The model notes report").
109
+ - Without a model note the system prompt is what it was.
110
+
111
+
39
112
  At startup, the agent reads both scope indexes with blank-name memory reads
40
113
  and injects them into the system prompt as `Project memories` and
41
114
  `System memories`. A memory muted for the session (`chi --mute NAME`) has its
@@ -46,6 +119,40 @@ names of a comma list are read). Its file and index line are untouched.
46
119
  These startup index reads are harness-injected context assembly and are not
47
120
  rendered as `tool>` activity lines.
48
121
 
122
+ ### The index's size
123
+
124
+ Both indexes go into every prompt in full (a model note's line and a muted
125
+ memory's line are left out), so their size is paid on every request. Nothing caps
126
+ or trims them; chi measures them (tokens estimated as characters /
127
+ `context.chars_per_token`) and shows the size:
128
+
129
+ - `chi self` has a `memory index` row:
130
+ `system ~2.0k tokens (54 lines), project ~1.4k tokens (35 lines)`, with
131
+ `, over 2500` inside the parentheses for a scope over
132
+ `memory.index_warn_tokens`.
133
+ - `/stats` has `memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`,
134
+ and the web's ctx tooltip (info bar and session card) the same line. It is
135
+ what this session's prompt holds, measured when the prompt was built (after
136
+ the session's mutes): a memory written during the session changes neither
137
+ the running prompt nor this figure until the prompt is built again (a
138
+ resume, a woken worker, `/model`).
139
+
140
+ `memory.index_warn_tokens` (default `2500`, per scope; `0` turns it off) is
141
+ the size a write may take an index to before the model is told. When a
142
+ `memory_write` (with content, or a description-only one) or a `write`/`edit` on
143
+ a memory file refreshes an index line and takes its scope's index from at or
144
+ under the limit to over it, the tool result ends with one note:
145
+
146
+ > Note: the project memory index is now ~2510 tokens (over memory.index_warn_tokens 2500) and is sent with every prompt. When you next have a moment, tighten long index descriptions (memory_write with name, scope and description only). Don't remove or merge memories unless the user asks.
147
+
148
+ The note comes once per crossing: a write while the index is already over gets
149
+ none (the tightening it asks for doesn't start a loop), nor does a remove, a
150
+ model overlay or the verbatim `index` write. Sessions sharing a project's folder
151
+ measure on their own, so writes in parallel sessions that race over the limit
152
+ may each get it. Bundle installs and uninstalls and hand edits
153
+ change index lines without a note; `chi self`'s row shows where an index
154
+ stands.
155
+
49
156
  ## Skills
50
157
 
51
158
  A **skill** is a memory named `skill_<name>` that holds the steps of a