samagotchi 0.40.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 +54 -1
- data/README.md +1 -1
- data/bin/chi +19 -2
- data/docs/cli.md +25 -2
- data/docs/configuration.md +34 -3
- data/docs/guardrails.md +15 -3
- data/docs/internals/prompt-caching.md +2 -1
- data/docs/memory.md +90 -1
- data/docs/sessions.md +1 -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/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 +4 -2
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/engine.rb +91 -16
- 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 +40 -1
- data/lib/samagotchi/guardrails/verdict.rb +6 -3
- data/lib/samagotchi/llm/errors.rb +33 -1
- data/lib/samagotchi/llm_context_command.rb +151 -0
- data/lib/samagotchi/llm_context_override.rb +180 -0
- data/lib/samagotchi/llm_context_strategy.rb +63 -13
- 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 +66 -11
- 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_runner.rb +26 -1
- data/lib/samagotchi/tools/memory.rb +70 -3
- 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 +40 -6
- 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 +7 -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,58 @@ 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
|
+
|
|
11
63
|
## [0.40.0] - 2026-10-08
|
|
12
64
|
|
|
13
65
|
### Added
|
|
@@ -1903,7 +1955,8 @@ and long-lived sessions.
|
|
|
1903
1955
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
1904
1956
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
1905
1957
|
|
|
1906
|
-
[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
|
|
1907
1960
|
[0.40.0]: https://github.com/dm1try/samagotchi/compare/v0.39.0...v0.40.0
|
|
1908
1961
|
[0.39.0]: https://github.com/dm1try/samagotchi/compare/v0.38.0...v0.39.0
|
|
1909
1962
|
[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
|
|
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
|
@@ -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
|
|
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.
|
|
@@ -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 │
|
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,61 @@ 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
|
+
- Without a model note the system prompt is what it was.
|
|
92
|
+
|
|
93
|
+
|
|
39
94
|
At startup, the agent reads both scope indexes with blank-name memory reads
|
|
40
95
|
and injects them into the system prompt as `Project memories` and
|
|
41
96
|
`System memories`. A memory muted for the session (`chi --mute NAME`) has its
|
|
@@ -46,6 +101,40 @@ names of a comma list are read). Its file and index line are untouched.
|
|
|
46
101
|
These startup index reads are harness-injected context assembly and are not
|
|
47
102
|
rendered as `tool>` activity lines.
|
|
48
103
|
|
|
104
|
+
### The index's size
|
|
105
|
+
|
|
106
|
+
Both indexes go into every prompt in full (a model note's line and a muted
|
|
107
|
+
memory's line are left out), so their size is paid on every request. Nothing caps
|
|
108
|
+
or trims them; chi measures them (tokens estimated as characters /
|
|
109
|
+
`context.chars_per_token`) and shows the size:
|
|
110
|
+
|
|
111
|
+
- `chi self` has a `memory index` row:
|
|
112
|
+
`system ~2.0k tokens (54 lines), project ~1.4k tokens (35 lines)`, with
|
|
113
|
+
`, over 2500` inside the parentheses for a scope over
|
|
114
|
+
`memory.index_warn_tokens`.
|
|
115
|
+
- `/stats` has `memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`,
|
|
116
|
+
and the web's ctx tooltip (info bar and session card) the same line. It is
|
|
117
|
+
what this session's prompt holds, measured when the prompt was built (after
|
|
118
|
+
the session's mutes): a memory written during the session changes neither
|
|
119
|
+
the running prompt nor this figure until the prompt is built again (a
|
|
120
|
+
resume, a woken worker, `/model`).
|
|
121
|
+
|
|
122
|
+
`memory.index_warn_tokens` (default `2500`, per scope; `0` turns it off) is
|
|
123
|
+
the size a write may take an index to before the model is told. When a
|
|
124
|
+
`memory_write` (with content, or a description-only one) or a `write`/`edit` on
|
|
125
|
+
a memory file refreshes an index line and takes its scope's index from at or
|
|
126
|
+
under the limit to over it, the tool result ends with one note:
|
|
127
|
+
|
|
128
|
+
> 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.
|
|
129
|
+
|
|
130
|
+
The note comes once per crossing: a write while the index is already over gets
|
|
131
|
+
none (the tightening it asks for doesn't start a loop), nor does a remove, a
|
|
132
|
+
model overlay or the verbatim `index` write. Sessions sharing a project's folder
|
|
133
|
+
measure on their own, so writes in parallel sessions that race over the limit
|
|
134
|
+
may each get it. Bundle installs and uninstalls and hand edits
|
|
135
|
+
change index lines without a note; `chi self`'s row shows where an index
|
|
136
|
+
stands.
|
|
137
|
+
|
|
49
138
|
## Skills
|
|
50
139
|
|
|
51
140
|
A **skill** is a memory named `skill_<name>` that holds the steps of a
|
data/docs/sessions.md
CHANGED
|
@@ -65,7 +65,7 @@ chi sessions clean --dry-run --days 7 # test sessions older than 7 da
|
|
|
65
65
|
**Ordering:**
|
|
66
66
|
|
|
67
67
|
- `Session.list` / `SessionManager.list_sessions` / `GET /api/sessions?sort=&order=&limit=&offset=` default to `updated_at desc` (newest activity first). Also supports `created_at`, `asc`. `X-Total-Count` header when paginated.
|
|
68
|
-
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new chats start in that folder; the header chip says `<project> · all`, and `all` opens the same place without `?dir` (every session; a new chat there starts in the server's own folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the server's own project). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat (`hide ▴` puts them away behind a `▾ N sessions` pill, remembered; in a window under 900 px wide, where the strip scrolls sideways, `▴ hide` sits in the top bar where the pill comes back; a window under 700 px tall hides them by itself, so a question or approval card in the stage keeps room, and the pill shows them until the window is tall again). A parent and its delegates are one card there: its `▸ 2 delegates · 1 live · 1 waiting` chip (the waiting part in the attention colour, the card's border too) opens their rows in a popover under it (a row per delegate at any depth, indented; a click opens that one, Esc or a click outside closes it, and the strip's order holds while it is open). A fork is a card of its own, and so is a delegate whose parent isn't listed (archived, another project), with its `↳ <parent>` chip; the pill counts cards, and every live or waiting session. "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). There a parent's card holds its delegates the same way, the chip opening their rows inside the card (each with an archive button on hover), open by itself while one of them waits on you; the count says `14 (+7 delegates)`, a search that matches a delegate shows its parent's card with the list open and the matching rows marked, and select mode picks cards (archiving a parent takes its delegates). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new chat. The message box grows with its text; drag its top edge to keep it taller (double-click resets). The info bar copies `chi --attach <id>` for a terminal. It shows the context used (`ctx 42%`) and the last generation's decode speed (`87 tok/s`, `~64 tok/s` when estimated; see `/stats` in [cli.md](cli.md#session-commands)); hovering `ctx` there or on a session card lists the session's tokens in (summed over every request, with the cached share), out (with reasoning) and the cost when the provider reports one, for this session only. The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new chat starts on; it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first; typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id, sampling?}], warning?}`; `sampling` is the model's configured request parameters as `/model` words them, the row's tooltip in the list; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`), and `POST /api/sessions` takes `model` (blank means the default). The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last choice; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
68
|
+
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new chats start in that folder; the header chip says `<project> · all`, and `all` opens the same place without `?dir` (every session; a new chat there starts in the server's own folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the server's own project). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat (`hide ▴` puts them away behind a `▾ N sessions` pill, remembered; in a window under 900 px wide, where the strip scrolls sideways, `▴ hide` sits in the top bar where the pill comes back; a window under 700 px tall hides them by itself, so a question or approval card in the stage keeps room, and the pill shows them until the window is tall again). A parent and its delegates are one card there: its `▸ 2 delegates · 1 live · 1 waiting` chip (the waiting part in the attention colour, the card's border too) opens their rows in a popover under it (a row per delegate at any depth, indented; a click opens that one, Esc or a click outside closes it, and the strip's order holds while it is open). A fork is a card of its own, and so is a delegate whose parent isn't listed (archived, another project), with its `↳ <parent>` chip; the pill counts cards, and every live or waiting session. "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). There a parent's card holds its delegates the same way, the chip opening their rows inside the card (each with an archive button on hover), open by itself while one of them waits on you; the count says `14 (+7 delegates)`, a search that matches a delegate shows its parent's card with the list open and the matching rows marked, and select mode picks cards (archiving a parent takes its delegates). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new chat. The message box grows with its text; drag its top edge to keep it taller (double-click resets). The info bar copies `chi --attach <id>` for a terminal. Its `llm ctx` chip names the LLM context strategy the next turn runs under (`llm ctx stale,forget · session`, in the accent colour when the session set its own; the tooltip gives each value's source), and its popover sets the session's own strategy, apply rule and budget, or follows the model again, by running `/llm-context` in the worker (the reply shows as a command bubble); `GET /api/sessions/:id`'s `session.llm_context` carries the values (the live worker's, else resolved from the session file), and a `command_ran` that changed them carries the new ones. It shows the context used (`ctx 42%`) and the last generation's decode speed (`87 tok/s`, `~64 tok/s` when estimated; see `/stats` in [cli.md](cli.md#session-commands)); hovering `ctx` there or on a session card lists the session's tokens in (summed over every request, with the cached share), out (with reasoning) and the cost when the provider reports one, for this session only, and the memory indexes its prompt holds (`memory index: ~3.4k tokens in this session's prompt (system 2.0k, project 1.4k)`; see [Memory](memory.md#the-indexs-size)). The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new chat starts on; it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first; typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id, sampling?}], warning?}`; `sampling` is the model's configured request parameters as `/model` words them, the row's tooltip in the list; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`), and `POST /api/sessions` takes `model` (blank means the default). The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last choice; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
69
69
|
- Web notifications: when a session needs you while the tab is hidden or behind another window (a question or a guardrail approval, a plugin card with buttons during a turn such as check-in's, a failed turn, a turn done after 10 s or more; a delegated session only for its questions, a canceled turn never), the page title counts it, `(N) Chi`, until you come back to the tab. The bell at the right of the top bar also turns on OS notifications ("needs an answer", "needs approval", "needs you", "turn failed", "done in 42 s", under the session's first message; a click opens the session): its first click asks the browser for the permission, and the choice is kept per browser. Any session of the page's scope counts, not only the open one. What was already so when the page loaded never counts, and several chi tabs show one notification per event. A chi tab in front tells the others (a `BroadcastChannel`), so while you look at one, the ones behind neither notify nor count what it shows; coming back to a tab clears those events in the other tabs' titles too. The hub's `session` frames carry what this needs: `pending_question` (`{id, kind}`, kind `question` or `approval`), `pending_card` (`{id, bundle}`: the running turn's card with actions, from `pending_card.json` that the worker keeps in the session's folder while one is open; a card without actions, such as loop-guard's, never counts) and `last_turn` (`{outcome, ended_at, seconds, origin}`, saved in the session's JSON as the engine ends each turn).
|
|
70
70
|
- The web frontend is a zero-build ES-module stack in `lib/samagotchi/web/public/`: `data.js` (retrieval, typed SSE `openStream` for a session and `openEvents` for the session list), `sessions_list.js` (the list as a pure reducer over `GET /api/events`: a snapshot replaces it, an upsert keeps a known card in place, a removal drops it; `families()` groups delegates under their parent), `families_view.js` (a parent's family chip, its rows and the strip's popover), `notify.js` (which of those events needs the user, for the notifications), `app.js` (presentation/state; a session with no live stream gets one when its `session` event says `bridge_up`, after one re-read: `event_seq` starts over in each worker, so a dropped stream is never resumed with its old cursor), `format.js` (pure formatters such as `previewOf`), `prompt_history.js` (the composer's ↑/↓ over the shared prompt history). Unit-tested via `npm test` (`node --test spec/web/public/*.test.js`).
|
|
71
71
|
- Selecting a session in the web UI is read-only: `GET /api/sessions/:id` never spawns a worker (it reads a live worker's snapshot when one runs, else the session file). A prompt (`POST /turn`) or a command (`POST /command`) wakes the worker, and `/stream` briefly waits for a freshly-spawned bridge before answering. A caught-up SSE reconnect holds the stream open; `reset` markers are only sent for reconnects behind the ring window, or with a cursor from another worker (event ids are `<event_seq>-<epoch>`, one epoch per worker).
|
|
@@ -152,14 +152,13 @@ module Samagotchi
|
|
|
152
152
|
when :tool_call_started
|
|
153
153
|
@iteration = event[:iteration]
|
|
154
154
|
@calls = event[:call_index].to_i
|
|
155
|
-
|
|
155
|
+
# The turns that stay in the conversation (a failed one whose work
|
|
156
|
+
# stayed too); a failed one rolled back leaves none.
|
|
157
|
+
when *Events::TURN_END
|
|
156
158
|
@running = false
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
@running = false
|
|
161
|
-
@turns_done += 1
|
|
162
|
-
settle_during(after_turn: true)
|
|
159
|
+
kept = Events.kept_turn?(event)
|
|
160
|
+
@turns_done += 1 if kept
|
|
161
|
+
settle_during(after_turn: kept)
|
|
163
162
|
when :card then add_card(event)
|
|
164
163
|
when :hook_notice then add_notice(event)
|
|
165
164
|
# What failed to load (guardrails, plugins): before the first turn
|
|
@@ -71,6 +71,29 @@ rules:
|
|
|
71
71
|
scopes: [once]
|
|
72
72
|
reason: removes a memory (it goes to the bundle trash, which chi bundle trash lists)
|
|
73
73
|
|
|
74
|
+
# The identity memory and the model notes (model_notes_<name>,
|
|
75
|
+
# docs/memory.md), with their overlays, go into the system prompt of every
|
|
76
|
+
# later session (a note on the models it names), and a model overlay
|
|
77
|
+
# (memory_write current_model_only: true) is appended to its memory for
|
|
78
|
+
# this model: a model, maybe steered by text it read, would be writing its
|
|
79
|
+
# own system prompt. Every such write asks, once at a time, and only the
|
|
80
|
+
# user may allow it. memory: prompt is memory_write, write or edit of such a
|
|
81
|
+
# file right in a memories folder, resolved (symlinks, .., and any case of
|
|
82
|
+
# the name, as a case-insensitive disk reads it); a shell command naming
|
|
83
|
+
# chi's config dir asks under shell-touches-chi.
|
|
84
|
+
- id: prompt-memory-write
|
|
85
|
+
tool: [memory_write, write, edit]
|
|
86
|
+
memory: prompt
|
|
87
|
+
verdict: ask
|
|
88
|
+
scopes: [once]
|
|
89
|
+
reason: writes a memory that goes into the system prompt of later sessions (identity, a model note, or an overlay of one)
|
|
90
|
+
- id: model-overlay-write
|
|
91
|
+
tool: memory_write
|
|
92
|
+
memory: overlay
|
|
93
|
+
verdict: ask
|
|
94
|
+
scopes: [once]
|
|
95
|
+
reason: writes a model overlay, guidance added to that memory for this model in later sessions
|
|
96
|
+
|
|
74
97
|
# ── running code from the network ──────────────────────────────────────
|
|
75
98
|
- id: pipe-to-shell
|
|
76
99
|
tool: shell
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: guardrails
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.9.0
|
|
4
4
|
scope: system
|
|
5
|
-
description: Optional default guardrail rules (git push/rewrites, rm -rf wide, curl | sh, git hooks, chi's own config and attached context from the shell, chi context add --cmd, chi broadcast, answering chi around chi answer, removing a memory; in strict mode also git rebase and writes and git in another checkout outside the session's repo), asks only small models get (git checkout --/restore, git stash drop), and a note for the model
|
|
5
|
+
description: Optional default guardrail rules (git push/rewrites, rm -rf wide, curl | sh, git hooks, chi's own config and attached context from the shell, chi context add --cmd, chi broadcast, answering chi around chi answer, removing a memory, writing a memory that reaches the system prompt (identity, model notes) or a model overlay; in strict mode also git rebase and writes and git in another checkout outside the session's repo), asks only small models get (git checkout --/restore, git stash drop), and a note for the model
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
# models: in small-models.yml (guardrails.small_models) needs 0.8.1; git: in rules.yml needs 0.17.0;
|
|
8
|
-
# a rule's memory: key (memory-remove) needs 0.36.0
|
|
9
|
-
# guarded call would be refused
|
|
10
|
-
requires_chi: ">= 0.
|
|
8
|
+
# a rule's memory: key (memory-remove) needs 0.36.0, and its overlay and prompt values (model-overlay-write,
|
|
9
|
+
# prompt-memory-write) need 0.41.0: an older chi refuses the rule set, so every guarded call would be refused
|
|
10
|
+
requires_chi: ">= 0.41.0"
|
|
11
11
|
files:
|
|
12
12
|
guardrails.md: sha256:04207fd1738aeeeef3b7a8053fc57597565bddd7862f01acaba784419cbd909b
|
|
@@ -18,7 +18,7 @@ Single registry `Samagotchi::Config` (`Config::ENTRIES` in `lib/samagotchi/confi
|
|
|
18
18
|
|
|
19
19
|
Sections forbid `_`/`-` (`SECTION_RE` `/\A[a-z0-9]+\z/`); leaves keep `snake_case` in YAML (`base_url`) and become kebab in CLI (`base-url`) via registry derivation — no generic string split, registry lookup avoids flat vs nested collision.
|
|
20
20
|
|
|
21
|
-
**Universal entries** (`expose: [:env,:config,:cli]`): `default.model`, `server.host/port/transport/open_timeout/read_timeout`, `server.first_token_timeout` (env and config only), `recap.model/base_url/host_ref/inactivity/timeout/min_user_turns/sentences`, `session.retention_days/max_count/keep_status/sweep_interval_hours/idle_exit_minutes`, `session.shared/keep_empty/max_children` (env and config only), `log.file/disable`, `status.line`, `context.status/window_tokens/chars_per_token/status_thresholds/status_cadence`, `thinking.turn_preamble`, `default.max_tokens`, `max_tool_output_chars`, `retry.max/base_delay/max_delay`, `read.*`, `execute.*`, `web.port/host`, `no_interrupt`, `no_default_input` etc. (`Config::ENTRIES`; `expose` says which of env/config/cli each takes). Precedence is `CLI > ENV > file > default`.
|
|
21
|
+
**Universal entries** (`expose: [:env,:config,:cli]`): `default.model`, `server.host/port/transport/open_timeout/read_timeout`, `server.first_token_timeout` (env and config only), `recap.model/base_url/host_ref/inactivity/timeout/min_user_turns/sentences`, `session.retention_days/max_count/keep_status/sweep_interval_hours/idle_exit_minutes`, `session.shared/keep_empty/max_children` (env and config only), `log.file/disable`, `status.line`, `context.status/window_tokens/chars_per_token/status_thresholds/status_cadence`, `memory.index_warn_tokens`, `thinking.turn_preamble`, `default.max_tokens`, `max_tool_output_chars`, `retry.max/base_delay/max_delay`, `read.*`, `execute.*`, `web.port/host`, `no_interrupt`, `no_default_input` etc. (`Config::ENTRIES`; `expose` says which of env/config/cli each takes). Precedence is `CLI > ENV > file > default`.
|
|
22
22
|
|
|
23
23
|
Example `config.yml` (new nested form, preferred):
|
|
24
24
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.41.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
|
|
6
6
|
files:
|
|
7
7
|
identity.md: sha256:29c2f7df9f6c5acbc5ec17000a4490741e55e426af15ca0d81c8706edd9bd027
|
|
8
|
-
self_map.md: sha256:
|
|
9
|
-
config_modification_protocol.md: sha256:
|
|
10
|
-
memory_guide.md: sha256:
|
|
8
|
+
self_map.md: sha256:9d1b2ff7ff5f237c1b1beed2eb8ecc3069570705a2c1763e533dd15829ff309d
|
|
9
|
+
config_modification_protocol.md: sha256:2d4135ce4fafcd391d1782dc3a54f7cbf10cd35ebe9f004b2d6cb256517a8451
|
|
10
|
+
memory_guide.md: sha256:15cd3b24eda853cc458371c6c75809a2d4b5352311d075aaa4384926b5221c27
|
|
11
11
|
delegated.md: sha256:aedaa1e756a244904c5560fff0424e06fb468bbdcb0358bccefe321d67cd2614
|
|
@@ -47,6 +47,19 @@ This memory teaches you (the agent) how to use Samagotchi memories — persisten
|
|
|
47
47
|
**Placeholders:**
|
|
48
48
|
- Content may contain placeholder hints written as double-curly braces around a name (e.g., test_command, language). Detected by `Placeholder` (`Placeholder::PLACEHOLDER_RE`) — install warns but does not fail. Fill them when you write. The placeholder syntax is two opening braces, a name, two closing braces.
|
|
49
49
|
|
|
50
|
+
## Model notes
|
|
51
|
+
|
|
52
|
+
A model note is a memory named `model_notes_<name>` whose first line says which models it is for: `models: ` then `|`-separated globs on the model id or the model key (the `Model:` line names both), or `small`. It goes into the system prompt of every later session on a matching model, after the identity, so it holds habits of a model (or a family), not of a task.
|
|
53
|
+
|
|
54
|
+
```markdown
|
|
55
|
+
models: deepseek-*|*deepseek-v4*
|
|
56
|
+
- Explore briefly, then act.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Write one only when the user asks for guidance for a model; it changes that model's prompt for every later session. The guardrails ask the user first, as they do for a model overlay.
|
|
60
|
+
- No dot in the name (`model_notes_qwen3-6`, not `model_notes_qwen3.6`), and the `models:` line first: `memory_write` refuses either.
|
|
61
|
+
- It takes effect at the next session start, resume or `/model`, not in the running session.
|
|
62
|
+
|
|
50
63
|
## Skills
|
|
51
64
|
|
|
52
65
|
A skill is a memory named `skill_<name>` (`skill_release`, `skill_deploy_staging`) that holds the steps of a repeatable task done with the user. Next time, follow it; when a step turned out different, fix it in the same turn.
|