samagotchi 0.39.0 → 0.41.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -1
- data/README.md +1 -1
- data/bin/chi +19 -2
- data/docs/cli.md +25 -2
- data/docs/configuration.md +83 -4
- data/docs/guardrails.md +15 -3
- data/docs/hooks.md +1 -0
- data/docs/internals/context-telemetry.md +16 -0
- data/docs/internals/llm-context-bench.md +18 -4
- data/docs/internals/llm-context-forget.md +45 -0
- data/docs/internals/prompt-caching.md +2 -1
- data/docs/memory.md +90 -1
- data/docs/plugins.md +1 -1
- data/docs/sessions.md +1 -1
- data/docs/testing.md +2 -1
- data/lib/samagotchi/bridge/card_store.rb +6 -7
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +23 -0
- data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -5
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +1 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +13 -0
- data/lib/samagotchi/bundles/system/self_map.md +8 -2
- data/lib/samagotchi/config.rb +21 -5
- data/lib/samagotchi/context_status.rb +92 -9
- data/lib/samagotchi/engine.rb +106 -18
- data/lib/samagotchi/events.rb +8 -1
- data/lib/samagotchi/guardrails/model_size.rb +3 -4
- data/lib/samagotchi/guardrails/rules.rb +27 -17
- data/lib/samagotchi/guardrails/targets.rb +41 -2
- data/lib/samagotchi/guardrails/verdict.rb +6 -3
- data/lib/samagotchi/host_registry.rb +5 -3
- data/lib/samagotchi/kernel_loop.rb +33 -3
- data/lib/samagotchi/llm/chat_loop.rb +18 -4
- data/lib/samagotchi/llm/errors.rb +33 -1
- data/lib/samagotchi/llm_context_apply.rb +17 -5
- data/lib/samagotchi/llm_context_command.rb +151 -0
- data/lib/samagotchi/llm_context_edit.rb +35 -4
- data/lib/samagotchi/llm_context_forget.rb +258 -0
- data/lib/samagotchi/llm_context_override.rb +180 -0
- data/lib/samagotchi/llm_context_stale.rb +23 -7
- data/lib/samagotchi/llm_context_strategy.rb +112 -18
- data/lib/samagotchi/llm_context_view.rb +88 -11
- data/lib/samagotchi/memory_bundle/index_size.rb +103 -0
- data/lib/samagotchi/memory_bundle/index_sync.rb +3 -2
- data/lib/samagotchi/model_match.rb +54 -0
- data/lib/samagotchi/model_notes.rb +202 -0
- data/lib/samagotchi/parent_report.rb +12 -2
- data/lib/samagotchi/plugin/context.rb +3 -2
- data/lib/samagotchi/plugin/sessions.rb +3 -1
- data/lib/samagotchi/reply_wait.rb +3 -17
- data/lib/samagotchi/self_report.rb +15 -0
- data/lib/samagotchi/send_command.rb +22 -2
- data/lib/samagotchi/session.rb +29 -4
- data/lib/samagotchi/session_commands.rb +21 -1
- data/lib/samagotchi/session_manager.rb +5 -2
- data/lib/samagotchi/session_metrics.rb +26 -3
- data/lib/samagotchi/system_prompt.rb +83 -23
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +13 -5
- data/lib/samagotchi/terminal_ui/attached_loop.rb +20 -12
- data/lib/samagotchi/terminal_ui/event_renderer.rb +2 -0
- data/lib/samagotchi/terminal_ui/formatting.rb +36 -0
- data/lib/samagotchi/terminal_ui.rb +16 -6
- data/lib/samagotchi/tool_activity.rb +18 -0
- data/lib/samagotchi/tool_declarations.rb +60 -4
- data/lib/samagotchi/tool_runner.rb +26 -1
- data/lib/samagotchi/tools/builtin_calls.rb +3 -2
- data/lib/samagotchi/tools/builtins.rb +15 -5
- data/lib/samagotchi/tools/forget_outputs.rb +62 -0
- data/lib/samagotchi/tools/memory.rb +70 -3
- data/lib/samagotchi/tools/registry.rb +25 -8
- data/lib/samagotchi/turn_flow.rb +36 -8
- data/lib/samagotchi/turn_note.rb +40 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +20 -1
- data/lib/samagotchi/web/public/app.js +41 -7
- data/lib/samagotchi/web/public/ctx.js +28 -5
- data/lib/samagotchi/web/public/format.js +5 -2
- data/lib/samagotchi/web/public/index.html +12 -0
- data/lib/samagotchi/web/public/live_turn.js +1 -0
- data/lib/samagotchi/web/public/llm_context_chip.js +194 -0
- data/lib/samagotchi/web/public/session_state.js +3 -0
- data/lib/samagotchi/web/public/timing.js +5 -1
- data/lib/samagotchi/web/session_summary.rb +2 -0
- data/lib/samagotchi/worker.rb +24 -18
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0f829b7b7835aab33a8b0f8a69609acc2e1989743c5f77664ab54e76cee9ee2
|
|
4
|
+
data.tar.gz: d0b1ab5cd60519258beb069528aed7f50d7404d5aa8c28ff30a5a1c5b78e3418
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a0d363fc07a55b7bfbbdb4263ecb630554376e1cbd4bf641e914933046bfb136ecd36f85386bc9a328c63803db9a49a4c57a0a6425849b90c59f223888b68a1
|
|
7
|
+
data.tar.gz: ae547e2229da947e57095ac6730b5e0549e8e03633beb79ccd47922d836b2a2a00c04be3385000ef230161a74ac88a02621ef84152253d7c5cb017505cfd1853
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,87 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.41.0] - 2026-10-08
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Model notes: a memory named `model_notes_<name>` whose first line is `models: <globs>|small` goes into the system
|
|
16
|
+
prompt of every session on a matching model, in its own section after the identity memory (all matching notes,
|
|
17
|
+
system scope then project). The habits of one model (or a family, or every small model) no longer ride on an
|
|
18
|
+
identity overlay keyed to one provider's id. `--mute model_notes_<name>` drops one for a session; `memory_write`
|
|
19
|
+
refuses a note without the `models:` line. See [docs/memory.md](docs/memory.md) ("Model notes").
|
|
20
|
+
- The guardrails bundle (0.9.0) asks, once at a time and only the user may allow it, before a model writes a memory
|
|
21
|
+
that reaches the system prompt: the identity memory, a model note or an overlay of either, by `memory_write`,
|
|
22
|
+
`write` or `edit`, through a symlink or in any case of the name (`prompt-memory-write`), or a model overlay of any
|
|
23
|
+
memory (`model-overlay-write`). A rule's `memory:` takes `overlay` and `prompt`. Run `chi update` to get it.
|
|
24
|
+
- A session's own LLM context strategy, apply rule and budget, before the model's: `chi --llm-context stale,forget
|
|
25
|
+
--llm-context-apply turn_end --llm-context-budget 64k` at start (also `chi send --new`, and `--resume`),
|
|
26
|
+
`/llm-context` in the session (no arguments: each value and where it came from; `strategy`, `apply`, `budget`,
|
|
27
|
+
`default` to unset one, `reset`), and the web's `llm ctx` chip in the info bar. Saved with the session (kept by
|
|
28
|
+
`--resume` and forks, not by delegates); a change applies from the next turn's start, and its reply says what it
|
|
29
|
+
costs (forget on or off re-reads the prompt once; stale turned on stages the reads already superseded as one
|
|
30
|
+
batch; a layer turned off sends its stubs whole again). `/stats` and the turn log show the effective strategy and
|
|
31
|
+
its source. See [docs/configuration.md](docs/configuration.md) ("LLM context: a session's own strategy").
|
|
32
|
+
- The memory indexes' size, which every prompt carries in full: `chi self` has a `memory index` row (`system ~2.0k
|
|
33
|
+
tokens (54 lines), project ~1.4k tokens (35 lines)`), and `/stats` and the web's ctx tooltip show what the
|
|
34
|
+
session's prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`). A
|
|
35
|
+
`memory_write`, `write` or `edit` that takes a scope's index over `memory.index_warn_tokens` (new; default 2500 per
|
|
36
|
+
scope, `0` off) gets one note asking the model to tighten long index descriptions when it has a moment, and not to
|
|
37
|
+
remove or merge memories unless the user asks; a write while already over gets none. See
|
|
38
|
+
[docs/memory.md](docs/memory.md) ("The index's size").
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- A guardrails rule's `models:` also takes a `|`-separated string (`"small|deepseek-*"`), as
|
|
43
|
+
`guardrails.small_models` does.
|
|
44
|
+
- The system prompt's `Model:` line says guidance for this model goes in a model note (it said a memory overlay).
|
|
45
|
+
- `memory_write` refuses a memory name with a comma (`memory_read` reads a comma list of names).
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- A turn that failed partway (out of credits, a server error, any provider error but a context overflow) after tool
|
|
50
|
+
calls lost them: the saved session held only the failure note, and the prompt went back for a retry that would
|
|
51
|
+
run the task again over files it had already changed. Such a turn now keeps its steps, as a cancel does: the model
|
|
52
|
+
reads that it failed after N tool steps and that its work stays, the prompt is not handed back, and `!rollback`
|
|
53
|
+
still erases the turn (`partial progress kept` in the terminal and on the web; `chi send --wait` and a delegating
|
|
54
|
+
parent read `the turn failed after N tool steps: <why>; its work so far stays`). A turn that failed before it got
|
|
55
|
+
anywhere, or on a context overflow (kept, the conversation would stay too long for the window), is rolled back and
|
|
56
|
+
its prompt restored, as before. Since 0.2.0.
|
|
57
|
+
- On a case-insensitive disk a write to the memories' `INDEX.md` (any case) was taken for a memory and added a bogus
|
|
58
|
+
line to the index.
|
|
59
|
+
|
|
60
|
+
Update with `chi update`: it updates guardrails (0.9.0: asks before a model writes a memory that reaches the system
|
|
61
|
+
prompt; needs chi 0.41.0).
|
|
62
|
+
|
|
63
|
+
## [0.40.0] - 2026-10-08
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- Experimental: the `forget` layer of `llm_context.strategy` (`[stale, forget]`, per model or host as ever). That
|
|
68
|
+
model gets a tool, `forget_outputs`, to forget its own tool outputs: each output shows an id (`[#t41]`), and a
|
|
69
|
+
forget replaces the outputs named with a stub holding the model's note (required; the session keeps the outputs).
|
|
70
|
+
`keep` keeps lines of an output, `restore` brings a non-read one back; outputs of the last `protect_steps` steps,
|
|
71
|
+
and reads of files edited in them unless lines are kept, are refused. The stubs reach the prompt under
|
|
72
|
+
`llm_context.apply`. `llm_context.policy` is the sentence the tool's description carries. Off, the tool list and the
|
|
73
|
+
prompt are what they were. See [docs/configuration.md](docs/configuration.md) ("LLM context: the forget layer").
|
|
74
|
+
- `llm_context.budget_tokens` (and per model or host `llm_context_budget_tokens`), off by default: a soft context
|
|
75
|
+
budget the `[CONTEXT: …]` lines count against instead of the window. Under `forget` those lines show
|
|
76
|
+
`~N/M tokens in use` and offer the tool in tiers (a readout, then "tidy once", then "compact settled outputs now"),
|
|
77
|
+
at a turn's start, mid-turn only in the top tier.
|
|
78
|
+
- The replay bench's `forget_outputs` strategy asks a model at chi's forget offer and scores its picks, call rate and
|
|
79
|
+
notes (`--forget-model`, `--out`, `--budget`, `--max-cost`).
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
|
|
83
|
+
- Web: session cards no longer show the "mem N" chip, leaving room for the delegate chip; the card's tooltip and the
|
|
84
|
+
open session's info bar still list the memories used.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- Web: a session card's hover tooltip (id, update time, status, memories) was empty since 0.35.0.
|
|
89
|
+
|
|
90
|
+
Update with `chi update`: it updates loop-guard (0.3.6: ignores `forget_outputs`).
|
|
91
|
+
|
|
11
92
|
## [0.39.0] - 2026-10-07
|
|
12
93
|
|
|
13
94
|
### Added
|
|
@@ -1874,7 +1955,9 @@ and long-lived sessions.
|
|
|
1874
1955
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
1875
1956
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
1876
1957
|
|
|
1877
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
1958
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.41.0...HEAD
|
|
1959
|
+
[0.41.0]: https://github.com/dm1try/samagotchi/compare/v0.40.0...v0.41.0
|
|
1960
|
+
[0.40.0]: https://github.com/dm1try/samagotchi/compare/v0.39.0...v0.40.0
|
|
1878
1961
|
[0.39.0]: https://github.com/dm1try/samagotchi/compare/v0.38.0...v0.39.0
|
|
1879
1962
|
[0.38.0]: https://github.com/dm1try/samagotchi/compare/v0.37.0...v0.38.0
|
|
1880
1963
|
[0.37.0]: https://github.com/dm1try/samagotchi/compare/v0.36.0...v0.37.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
|
|
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
|
@@ -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. |
|
|
@@ -425,7 +428,7 @@ request (with the cached tokens, the cache writes, the re-prefilled tokens and t
|
|
|
425
428
|
reports them),
|
|
426
429
|
the decode speed, generation latency, cancellations, retries (the network's),
|
|
427
430
|
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
|
|
431
|
+
(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 prompt profile and the model the server says it ran, and the
|
|
429
432
|
cost when the provider reports one (OpenRouter's `usage.cost`; no prices are
|
|
430
433
|
guessed). The speed line is the last generation's and the session's average
|
|
431
434
|
(weighted by tokens): llama.cpp reports it exactly (`87 tok/s out, 1.9k tok/s
|
|
@@ -437,6 +440,22 @@ keeps its own numbers: a parent's tokens and cost leave them out. `/recap` is in
|
|
|
437
440
|
[Session recap](#session-recap), `/model` and `/models` in [Runtime Model
|
|
438
441
|
Switch](#runtime-model-switch-assist-mode).
|
|
439
442
|
|
|
443
|
+
`/llm-context` shows the LLM context strategy, apply rule and budget the next
|
|
444
|
+
turn runs under, each with where it came from (`the session`, `models: <key>`,
|
|
445
|
+
`hosts entry '<name>'`, `llm_context.*`), and the session's own values.
|
|
446
|
+
`/llm-context strategy <none|stale|stale,forget> apply <payoff|next_request|turn_end>
|
|
447
|
+
budget <tokens|64k|off>` sets the session's own (any of the three, in one line;
|
|
448
|
+
`default` unsets one), and `/llm-context reset` unsets them all. A change is
|
|
449
|
+
saved with the session and applies from the next turn's start; the reply says
|
|
450
|
+
what it costs: `forget` on or off re-reads the whole prompt once, `stale` turned
|
|
451
|
+
on stages the reads already superseded as one batch under the apply rule, and a
|
|
452
|
+
layer turned off sends its stubs whole again (turning it back on brings them
|
|
453
|
+
back). It runs between turns (a worker answers `busy` mid-turn). `/stats` shows
|
|
454
|
+
the same values on its `llm context:` line. The web's info bar shows them as the
|
|
455
|
+
`llm ctx` chip (`llm ctx stale,forget · session`), whose popover sets them
|
|
456
|
+
through `/llm-context`. See "LLM context: a session's own strategy" in
|
|
457
|
+
[configuration.md](configuration.md).
|
|
458
|
+
|
|
440
459
|
### Typing during a turn
|
|
441
460
|
|
|
442
461
|
The prompt stays open while a turn runs, in an attached terminal and in the plain
|
|
@@ -861,7 +880,11 @@ Behavior notes:
|
|
|
861
880
|
- Cancellation returns control to the prompt immediately; what you typed there stays.
|
|
862
881
|
- 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
882
|
`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
|
|
883
|
+
started). A failed turn ends with `✕ turn failed: <summary> · 2.0s` and a dim `prompt restored for retry` when it
|
|
884
|
+
failed before it got anywhere, or on a context overflow (the conversation goes back to before it); one that got to
|
|
885
|
+
tool steps keeps them, as a cancel does, with the dim `partial progress kept; !rollback restores the pre-turn state`
|
|
886
|
+
(the model reads a note saying how many steps ran and why it stopped; `chi send --wait` and a delegating parent
|
|
887
|
+
read `the turn failed after N tool steps: <why>; its work so far stays`). The
|
|
865
888
|
REPL and attached mode say the same; the web says `■ canceled (Ctrl-C)` (`stopped` for its Stop button,
|
|
866
889
|
`by a hook` for a hook's). A turn a bundle's plugin stopped names it: `■ turn stopped by loop-guard` (the web:
|
|
867
890
|
`■ stopped by loop-guard`).
|
data/docs/configuration.md
CHANGED
|
@@ -855,6 +855,81 @@ file is read.
|
|
|
855
855
|
To skip loading AGENT.md, set `skip_agent_md: true` at the top level of
|
|
856
856
|
`config.yml` (env `SAMAGOTCHI_SKIP_AGENT_MD=true`).
|
|
857
857
|
|
|
858
|
+
## LLM context: the forget layer
|
|
859
|
+
|
|
860
|
+
Experimental, off by default. With `forget` in a model's `llm_context.strategy` (next to `stale`), chi offers that
|
|
861
|
+
model a tool, `forget_outputs`, to free context by forgetting its own tool outputs. Nothing changes for a model
|
|
862
|
+
without it: no ids, no tool, the same prompt.
|
|
863
|
+
|
|
864
|
+
```yaml
|
|
865
|
+
llm_context:
|
|
866
|
+
budget_tokens: 64000 # optional: the [CONTEXT: …] lines count against 64k, not the window
|
|
867
|
+
models:
|
|
868
|
+
deepseek-v4.1-flash:
|
|
869
|
+
llm_context_strategy: [stale, forget]
|
|
870
|
+
llm_context_apply: next_request
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
- **Ids.** Every tool output the model sees starts with its id after the tool's name, `[read]` then `[#t41] …`.
|
|
874
|
+
Outputs a session saved before chi gave them ids show none and can't be forgotten.
|
|
875
|
+
- **Forgetting.** `forget_outputs(ids: ["t41", "t42"], note: "…")` replaces each output with a stub holding the note
|
|
876
|
+
(`[read] [#t41] (forgotten) …`); the call stays, and the session keeps the outputs. The note is required. Outputs
|
|
877
|
+
one call forgot that follow one another carry the note once: the rest say `(forgotten with t41: see its note)`.
|
|
878
|
+
`keep: ["t42:12-40"]` forgets t42 except those lines, which follow its stub: a read's lines are the file's line
|
|
879
|
+
numbers (a read from `start_line` 120 starts at 120), other outputs count from 1.
|
|
880
|
+
- **Refused, per id, with the reason in the result:** an output from the last `llm_context.protect_steps` steps
|
|
881
|
+
before the forget's own (1: the output the model just got); a read of a file edited or written in them, unless the
|
|
882
|
+
forget keeps lines of it; a keep outside the output's lines, of a big file's head/tail preview, or holding all of
|
|
883
|
+
it; an output too small for a stub to free anything; one already stubbed or forgotten; an unknown id, or one whose
|
|
884
|
+
output chi can't pair with its call for sure (it shows no id). The system prompt, user messages, steers and context
|
|
885
|
+
notes have no ids.
|
|
886
|
+
- **When stubs reach the prompt** follows `llm_context.apply`, as `stale`'s do: the result says
|
|
887
|
+
`applied, the next request sends the stubs (frees …)` or `staged until the turn ends`.
|
|
888
|
+
- **Restore.** `forget_outputs(restore: ["t45"])` brings a forgotten output back with the next request, whatever
|
|
889
|
+
`llm_context.apply` says (the model wants the text now; the result says what the server reads again); a non-read
|
|
890
|
+
stub says `[restore: t45]`. A read whose stub was sent isn't restored (read the file again: it may have changed); one
|
|
891
|
+
still staged is.
|
|
892
|
+
- **The description** carries the note contract (facts carried forward, quoted, marked VERIFIED or UNVERIFIED; what
|
|
893
|
+
was ruled out, with the reason and "do not retry"; the commands tried; a NEXT line), the cost of an edit (the
|
|
894
|
+
server reads everything after it again: forget in one batch, older outputs first) and `llm_context.policy`.
|
|
895
|
+
- **Offers.** Under `forget` the `[CONTEXT: …]` line becomes a readout, `~52k/64k tokens in use`, and offers the tool
|
|
896
|
+
in tiers, never "forget now": the readout alone in the lower guided buckets; "finish the unit of work in flight,
|
|
897
|
+
then tidy once" in the bucket under the top; "compact settled outputs now: keep what you'll still edit against;
|
|
898
|
+
don't wipe" in the top bucket or over `llm_context.budget_tokens`. They come at a turn's first request (the turn
|
|
899
|
+
before it answered); mid-turn only the top tier offers. Over the budget the compact offer comes every turn.
|
|
900
|
+
- `/stats` counts the re-prefilled tokens each applied batch costs; `script/llm_context_bench.rb --strategy
|
|
901
|
+
forget_outputs` replays the offer on stored sessions (docs/internals/llm-context-bench.md).
|
|
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
|
+
|
|
858
933
|
## All settings
|
|
859
934
|
|
|
860
935
|
Every setting below takes the three forms described in
|
|
@@ -917,13 +992,17 @@ described in their own sections.
|
|
|
917
992
|
| `context.status` | `true` | yes | Context-usage telemetry for the model. See [context telemetry](internals/context-telemetry.md). |
|
|
918
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". |
|
|
919
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. |
|
|
920
|
-
| `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`
|
|
921
|
-
| `models.<key>.llm_context_strategy`, `hosts.<name>.llm_context_strategy` | none | | A model's or host's `llm_context.strategy
|
|
922
|
-
| `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. |
|
|
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. |
|
|
996
|
+
| `models.<key>.llm_context_strategy`, `hosts.<name>.llm_context_strategy` | none | | A model's or host's `llm_context.strategy`. |
|
|
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. |
|
|
923
998
|
| `models.<key>.llm_context_apply`, `hosts.<name>.llm_context_apply` | none | | A model's or host's `llm_context.apply`. |
|
|
924
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. |
|
|
925
|
-
| `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. |
|
|
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. |
|
|
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. |
|
|
1002
|
+
| `models.<key>.llm_context_budget_tokens`, `hosts.<name>.llm_context_budget_tokens` | none | | A model's or host's `llm_context.budget_tokens`. |
|
|
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. |
|
|
926
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). |
|
|
927
1006
|
| `context.status_thresholds` | `20,40,60,80` | yes | Percentages that trigger a status. |
|
|
928
1007
|
| `context.status_cadence` | `0` | yes | Also every N rounds; `0` = thresholds only. |
|
|
929
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
|
|
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));
|
|
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.
|
data/docs/hooks.md
CHANGED
|
@@ -440,6 +440,7 @@ stripped of surrounding whitespace, except file text, an edit's
|
|
|
440
440
|
| `delegate` | `task` | — | `model`, `session`, `cwd`, `wait`, `timeout` |
|
|
441
441
|
| `delegate_result` | — | — | `session`, `timeout` |
|
|
442
442
|
| `ask_user_question` | `question` | — | `question`, `options`, `header`, `multi_select`, `allow_freeform` |
|
|
443
|
+
| `forget_outputs` | `note` | — | `ids`, `note`, `keep`, `restore` |
|
|
443
444
|
|
|
444
445
|
A `write`'s `content:` is the file text; an `edit` carries `old_text:` and
|
|
445
446
|
`new_text:` (its `content:` is `""`). A plugin or MCP tool's call has its
|
|
@@ -31,6 +31,22 @@ Emission behavior:
|
|
|
31
31
|
`context.status_thresholds` the top bucket reads critical, the one below it
|
|
32
32
|
elevated, the next moderate, and the first bucket (and the rest below) healthy.
|
|
33
33
|
|
|
34
|
+
Under an LLM context strategy (`ContextStatus.new(llm_context:)`, the turn's `LLMContextStrategy::Resolved`, given by
|
|
35
|
+
both loops):
|
|
36
|
+
|
|
37
|
+
- `llm_context.budget_tokens` (or a model's or host's `llm_context_budget_tokens`): the buckets count against the
|
|
38
|
+
budget instead of the window, the smaller of the two. The event's `window_tokens` and the status line's percentage
|
|
39
|
+
are of it too, and so is `payoff`'s top bucket.
|
|
40
|
+
- The forget layer replaces the guidance with tiered offers of `forget_outputs`, each line a readout,
|
|
41
|
+
`[CONTEXT: ~52k/64k tokens in use (bucket=60plus). …]`: the readout alone in the guided buckets below the top two
|
|
42
|
+
(CLM: how-to at low pressure makes a model wipe everything); "Finish the unit of work in flight, then tidy once
|
|
43
|
+
with forget_outputs: …" in the one under the top; "Compact settled outputs now with forget_outputs: keep what
|
|
44
|
+
you'll still edit against; don't wipe." in the top bucket, or over the budget (`observe`'s absolute check). They
|
|
45
|
+
come at a turn's first request (`iteration_index` 0: the turn before answered, its outputs are settled) on a rise,
|
|
46
|
+
again when the conversation's last line was the readout alone, and every turn while over the budget; mid-turn a
|
|
47
|
+
rise offers only in the top tier, a lower one gets the readout alone (the layout check: pushed mid-task, models
|
|
48
|
+
forget at the base rate). See internals/llm-context-forget.md.
|
|
49
|
+
|
|
34
50
|
Configuration:
|
|
35
51
|
|
|
36
52
|
- `SAMAGOTCHI_CONTEXT_STATUS` (`true` by default): set to `false` or `0` to disable telemetry.
|
|
@@ -54,7 +54,9 @@ The proxies are the context-edit spike's (lexical and lenient: a basename mentio
|
|
|
54
54
|
|
|
55
55
|
- `none`: nothing changes. The base.
|
|
56
56
|
- `forget_all`: at the turn's end, forget every output in context with an empty note. The spike's base rate; about
|
|
57
|
-
what a model told "free context now" does.
|
|
57
|
+
what a model told "free context now" does. Its stubs, and the picks', go through the view as chi sends forgets: a
|
|
58
|
+
call's note on the first of its outputs in a row and a pointer to it on the rest, and `[#tN]` ids on every output
|
|
59
|
+
with a stored id (P4).
|
|
58
60
|
- `stale`: chi's own stale layer (`Samagotchi::LLMContextStale`) as chi runs it: each read a later read covering its
|
|
59
61
|
lines superseded is stubbed from the request after that read (chi applies it at the next request), so a case's
|
|
60
62
|
numbers count every stub since the session began. An edit or write of the file supersedes nothing, nor does a read
|
|
@@ -67,13 +69,25 @@ The proxies are the context-edit spike's (lexical and lenient: a basename mentio
|
|
|
67
69
|
modelled (the bench has no window), so `payoff` applies at a request only when the freed tokens are at least the
|
|
68
70
|
tail. A turn-end batch's re-prefill is counted at the next turn's first request even where chi's warm-up would
|
|
69
71
|
prefill it while the user reads.
|
|
70
|
-
- `forget_outputs` (P4):
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
- `forget_outputs` (P4): chi's forget layer as chi offers it, at each case: the next turn's first request, after a
|
|
73
|
+
turn the model answered. The request is what chi sends there under `[stale, forget]`, built by chi's own code: the
|
|
74
|
+
conversation up to and with the next user message (outputs of a legacy entry get ids as chi gives new ones),
|
|
75
|
+
stale's stubs, every output led by its `[#tN]` id (`LLMContextView`), the `[CONTEXT: …]` line chi's `ContextStatus`
|
|
76
|
+
adds at a turn's start under `--budget N` (default 64000; no line below its guided buckets, so a small case may get
|
|
77
|
+
none), and the chat messages and tools of chi's `ChatLoop`, `forget_outputs` with its real description. The model
|
|
78
|
+
(`--forget-model MODEL`, a chi model ref on an `api: openai` host) answers once; a `forget_outputs` call among its
|
|
79
|
+
calls runs through chi's `LLMContextForget` (refusals, `protect_steps`, `keep`), and the row scores stale's stubs
|
|
80
|
+
plus the forgets. Each answer is saved in `--out DIR` (`<case>.forget_offer.json`) and read back, so a rerun asks
|
|
81
|
+
only for the rest (an error isn't saved); `--out` alone scores saved answers; `--max-cost USD` stops the asking
|
|
82
|
+
once the costs the server reports reach it (exit 1, the answers so far kept). The row's notes give the call rate
|
|
83
|
+
(`called`, `no_call`, `error`, `unasked`), the ids chi refused and the notes' mean length. Ids in this row cost
|
|
84
|
+
tokens too: its freed column is net of them.
|
|
73
85
|
- `--picks LABEL=DIR`: model picks recorded as responses, one file per pick, `<case>.<variant>.json` (the spike's
|
|
74
86
|
`out/<model>/` files, or what `--live` saves). The variant is the row's policy, the label its model.
|
|
75
87
|
|
|
76
88
|
```sh
|
|
89
|
+
ruby script/llm_context_bench.rb "$SESSIONS" --strategy none,stale,forget_outputs --forget-model splash:MODEL \
|
|
90
|
+
--out answers/splash --budget 48000 --cases cases.txt --per-case
|
|
77
91
|
ruby script/llm_context_bench.rb "$SESSIONS" --strategy none,forget_all,stale --per-case
|
|
78
92
|
ruby script/llm_context_bench.rb "$SESSIONS" --strategy none,stale,stale_edits_turn_end,stale_edits_payoff --ends any
|
|
79
93
|
ruby script/llm_context_bench.rb "$SESSIONS" --picks deepseek=picks/deepseek --picks splash=picks/splash --json
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# The forget layer: forget_outputs
|
|
2
|
+
|
|
3
|
+
`llm_context.strategy` with `forget` (experimental; user docs: configuration.md, "LLM context: the forget layer")
|
|
4
|
+
lets the model forget its own tool outputs. The pieces:
|
|
5
|
+
|
|
6
|
+
- **The tool list is per turn.** `Tools::Registry` entries may name an LLM context layer (`layer: :forget`);
|
|
7
|
+
`#entries/#names/#schemas(layers:)` and `#offered(name, layers:)` leave a gated tool out unless the turn runs under
|
|
8
|
+
its layer. The turn's layers are `LLMContextStrategy::Resolved#active_layers` (`[]` under none): the native system
|
|
9
|
+
prompt declares them (`SystemPrompt#build(layers:)`, `Engine#system_prompt` resolving the target's strategy), the
|
|
10
|
+
chat loop sends them (`ChatLoop#tool_definitions`), the kernel dispatches and lists them (`KernelLoop#dispatch`).
|
|
11
|
+
Under none and `[stale]` the tool list, the system prompt and the wire requests are what they were. A model switch
|
|
12
|
+
that changes the layers changes the tool list; it breaks the prompt cache anyway.
|
|
13
|
+
- **Ids** (`LLMContextView`, plan D2): with `:forget` among the view's layers every output with a stored id
|
|
14
|
+
(`ToolIds`, `tool_ids` on the entry) is sent as `[name]` lead, then `[#tN] `, then its text; derived ids of legacy
|
|
15
|
+
entries are never shown or forgettable.
|
|
16
|
+
- **The schema** is `ToolDeclarations.forget_outputs_schema(policy:)` (not in `TOOL_SCHEMAS`, which every turn
|
|
17
|
+
declares; `BUILTIN_SCHEMAS` adds it for `BuiltinCalls`). Its description (about 330 tokens by chars/4, 515 with the
|
|
18
|
+
parameters) carries the spike's refined text, `llm_context.policy` (read when the Engine builds its registry), the
|
|
19
|
+
note contract and the cost lesson. Parameters: `ids`, `note`, `keep` (`"t42:12-40"`: forget t42 except
|
|
20
|
+
those lines; a read's are file lines, kept with the edit's `keep_offset`, start_line - 1), `restore`, none required
|
|
21
|
+
(a restore needs no note); `Tools::ForgetOutputs.parse` reads ids and ranges from lists, JSON strings or words.
|
|
22
|
+
- **The call** (`KernelLoop#forget_outputs`, through the handler's kctx as ask_user_question does) runs
|
|
23
|
+
`LLMContextForget.call` on the running turn (`KernelLoop#llm_context_turn`, a `LLMContextForget::Turn` of the
|
|
24
|
+
turn's conversation and ContextStatus that both loops set as a turn starts). It names only outputs that pair with
|
|
25
|
+
their calls for sure (`LLMContextStale.paired_runs`: by tool_call_id, or a joined entry's runs by their calls'
|
|
26
|
+
`[name]` leads); the view shows ids only on those. The conversation's last step is the forget's own (both loops
|
|
27
|
+
add the model's entry before its calls run). Per id it refuses (with the reason in the result): an output of the
|
|
28
|
+
`protect_steps` steps before that one; a read `LLMContextStale.protected_ids` names (a file edited or written in
|
|
29
|
+
those steps, counted with the forget's own: `protect_steps + 1`) unless the forget keeps lines of it; a keep
|
|
30
|
+
outside the output's lines, of a preview, or holding all of it; an output of `SMALL` (80) chars or less; an output
|
|
31
|
+
with an edit already; an unknown id. What passes is saved as a staged `LLMContextEdit` of kind `:forget` (`by:
|
|
32
|
+
"model"`, the note, `keep`), and the apply rule runs at once as at a request (`KernelLoop#apply_llm_context!`), so
|
|
33
|
+
the result can say whether the batch goes with the next request or waits for the turn's end. `LLMContextApply`'s
|
|
34
|
+
protect_steps hold covers stale's stubs only: a forget was checked when it was made.
|
|
35
|
+
- **Restore** takes the forget off the entry (`LLMContextEdit.remove`, a new edits Hash); the next request sends the
|
|
36
|
+
output again, outside the apply rule (the result says the tail the server reads again, and `ContextStatus#edited!`
|
|
37
|
+
restarts the estimate). A read whose stub was sent isn't restored; a staged one is.
|
|
38
|
+
- **Stubs** (`LLMContextView`): `(forgotten) <note>` on the first output of a call's forgets in a row, `(forgotten
|
|
39
|
+
with tN: see its note)` on the rest (a run of the same note, author and stamp, no other output between them),
|
|
40
|
+
` [restore: tN]` on a non-read with a stored id, then each kept range under `lines A-B kept:`. The entry keeps its
|
|
41
|
+
role, tool_call_id and images bookkeeping as for stale.
|
|
42
|
+
- **Offers**: `ContextStatus` (internals/context-telemetry.md).
|
|
43
|
+
- **Persistence**: the edits ride on the entries (plan D3), so `--resume`, rollbacks and forks keep the stubs and the
|
|
44
|
+
restores; `/stats` counts each applied batch's re-prefill.
|
|
45
|
+
- **Measuring**: the bench's `forget_outputs` strategy (internals/llm-context-bench.md).
|
|
@@ -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
|
-
│
|
|
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 │
|