samagotchi 0.44.0 → 0.46.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 +120 -1
- data/docs/architecture.md +2 -1
- data/docs/cli.md +43 -12
- data/docs/configuration.md +102 -5
- data/docs/context.md +2 -1
- data/docs/internals/background-tasks.md +1 -0
- data/docs/internals/engine.md +1 -1
- data/docs/internals/llm-context-live.md +111 -0
- data/docs/plugins.md +4 -2
- data/docs/sessions.md +9 -5
- data/lib/samagotchi/bridge/turn_accumulator.rb +19 -0
- data/lib/samagotchi/bridge.rb +22 -10
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
- data/lib/samagotchi/bundles/github-pr/plugin.rb +22 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +2 -1
- data/lib/samagotchi/commands/registry.rb +54 -9
- data/lib/samagotchi/config.rb +57 -4
- data/lib/samagotchi/desktop/macos/Panel.swift +7 -3
- data/lib/samagotchi/engine.rb +47 -9
- data/lib/samagotchi/host_model.rb +116 -0
- data/lib/samagotchi/host_registry.rb +43 -12
- data/lib/samagotchi/kernel_loop.rb +2 -1
- data/lib/samagotchi/llm/chat_loop.rb +89 -7
- data/lib/samagotchi/llm/turn_settings.rb +5 -3
- data/lib/samagotchi/llm_context_strategy.rb +37 -0
- data/lib/samagotchi/model_catalog.rb +17 -12
- data/lib/samagotchi/model_notes.rb +8 -6
- data/lib/samagotchi/model_overlay.rb +3 -2
- data/lib/samagotchi/model_price.rb +54 -0
- data/lib/samagotchi/model_profile.rb +74 -3
- data/lib/samagotchi/plugin/context.rb +19 -7
- data/lib/samagotchi/process_group.rb +56 -9
- data/lib/samagotchi/recap_setup.rb +1 -1
- data/lib/samagotchi/self_report.rb +33 -3
- data/lib/samagotchi/session_commands.rb +47 -21
- data/lib/samagotchi/session_manager.rb +19 -1
- data/lib/samagotchi/session_metrics.rb +79 -11
- data/lib/samagotchi/sessions_command.rb +15 -5
- data/lib/samagotchi/terminal_ui/attached_loop.rb +85 -3
- data/lib/samagotchi/terminal_ui/attached_view.rb +2 -0
- data/lib/samagotchi/terminal_ui/formatting.rb +45 -10
- data/lib/samagotchi/terminal_ui/status_row.rb +12 -6
- data/lib/samagotchi/terminal_ui.rb +97 -12
- data/lib/samagotchi/tools/delegate_wait.rb +5 -4
- data/lib/samagotchi/tools/task_runtime.rb +11 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +12 -11
- data/lib/samagotchi/web/public/app.js +30 -18
- data/lib/samagotchi/web/public/command_complete.js +3 -1
- data/lib/samagotchi/web/public/ctx.js +62 -22
- data/lib/samagotchi/web/public/format.js +7 -2
- data/lib/samagotchi/web/public/index.html +3 -1
- data/lib/samagotchi/web/public/llm_context_chip.js +2 -4
- data/lib/samagotchi/web/public/model_pick.js +23 -3
- data/lib/samagotchi/web/public/model_picker.js +5 -3
- data/lib/samagotchi/web/public/stage_model.js +18 -4
- data/lib/samagotchi/web/public/stage_view.js +4 -3
- data/lib/samagotchi/web/public/turn_events.js +19 -6
- data/lib/samagotchi/web/public/turn_model.js +16 -0
- data/lib/samagotchi/web/public/turn_view.js +19 -0
- data/lib/samagotchi/web/session_hub.rb +9 -1
- data/lib/samagotchi/web/session_summary.rb +19 -2
- data/lib/samagotchi/worker.rb +172 -21
- data/lib/samagotchi/worker_inbound.rb +6 -5
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fb03f917133e78236dfb0bf16eae6c21b00ec2fb3720501d6c1982aeb9dc220
|
|
4
|
+
data.tar.gz: b3edff20b87e1f954c6079cd0315ab64559a5814f9a64e92e215fedc745a2778
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96b8d90741656cae236454b5f1b56f49e77ad1a511eb81cdc31594b311031b57363993091ae0df70cf78c6f346a2b6b867896b53e0093ae2e59671146cd12eb6
|
|
7
|
+
data.tar.gz: 6041ee413db16da9147f763f83cfabcf91e0c62ea95380fa8ae454aab9e242b646501fafdd2ed5071008b4b218ad740c585517ee1e3cd0fb84531cb36a43d59d
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,123 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.46.0] - 2026-10-09
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `hosts.<name>.models:` declares the model ids a host serves whatever its `/v1/models` lists (a gateway's
|
|
16
|
+
round-robin ids), as a map of ids or a plain list. A declared `host:id` is never warned about or re-listed at
|
|
17
|
+
`chi send --new` and delegate spawns, and a bare id routes to its declaring host as if listed, before any
|
|
18
|
+
`/models` and in workers too. Two hosts declaring one id warn at start. See "Models a host serves but doesn't
|
|
19
|
+
list" in [docs/configuration.md](docs/configuration.md).
|
|
20
|
+
- Declared `hosts.<name>.models` ids show first under their host in `/models` (as `rr/x (config)`, outside the 20
|
|
21
|
+
per host, also on a host that lists nothing), `chi models` (plain names; `--format json` adds `configured: true`)
|
|
22
|
+
and `GET /api/models` (`configured: true`). A host that doesn't answer shows none of them.
|
|
23
|
+
- `hosts.<name>.models.<id>.price: {input, output, cache_read?, cache_write?}` (USD per 1M tokens) estimates the
|
|
24
|
+
cost of a generation whose provider reports none, or reports 0. A reported non-zero cost always wins. The estimate
|
|
25
|
+
is saved apart (`cost_estimate` per turn, `cost_estimate_sum` in the totals). See "Prices" in
|
|
26
|
+
[docs/configuration.md](docs/configuration.md).
|
|
27
|
+
- `hosts.<name>.models.<id>.served: [ids]` (or `any`) names the models a gateway may answer a round-robin id with.
|
|
28
|
+
Those are no longer marked as served by another model (the web's `⚠`, the status line's `(served; asked …)`,
|
|
29
|
+
`/model`'s `served:`, `/stats`' "(asked for …)"); one it doesn't name still is.
|
|
30
|
+
- An estimated cost shows with `~`: `/stats` (`$0.42 reported + ~$0.12 from hosts.<name>.models prices`) and the
|
|
31
|
+
ctx tooltip on the info bar and the session cards (`cost: ~$0.54 ($0.42 reported, ~$0.12 estimated)`).
|
|
32
|
+
- The web model picker lists a host's declared ids first under it, noted `config` (the default model keeps
|
|
33
|
+
`default`), with `served by config (hosts.<name>.models)` in the row's tooltip.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- A chat generation whose stream dropped mid-answer (a read timeout or a reset after deltas had shown) failed the
|
|
38
|
+
whole turn: the chat loop now drops the partial reply and asks again for the same step, from the same
|
|
39
|
+
conversation, at most twice, reporting each ask-again as `generation_retrying`, as the transport's retries do.
|
|
40
|
+
A cancel, a provider error, a first-token timeout and a connection refused still fail as before, and a step whose
|
|
41
|
+
retries run out fails the turn with the same error. Each ask-again waits the transport's backoff (`retry.*`),
|
|
42
|
+
which a cancel cuts short, and `retry.max` caps the asks-again (`retry.max: 0` turns them off). Its
|
|
43
|
+
`generation_retrying` carries `restarted: true`: the web's live step, the TUI's activity line, a reload in the
|
|
44
|
+
middle of the turn and the stream hooks drop the partial reply, so the answer shows once.
|
|
45
|
+
- `task_stop` could signal an unrelated process group whose leader reused a finished task's pid. A task now records
|
|
46
|
+
its process's start time, and a pid that started at another time is not signalled (the task ends as failed).
|
|
47
|
+
- A delegate's report merged into a reminder or continue turn was lost when the turn failed past its loop (the
|
|
48
|
+
turn's own end, say): the turn kept only its start, yet the report counted as read. Its ring now stays, and the
|
|
49
|
+
next turn brings it.
|
|
50
|
+
- A delegate wait that relayed a child's approval ran to its timeout when the child ended its turn with no reply
|
|
51
|
+
while the answer was being posted; it now ends there.
|
|
52
|
+
- Chi Helper: a send that ended after the panel was reopened wrote its "sent"/"failed" line into the new panel, and
|
|
53
|
+
a success could close it; like a broadcast, it now leaves the new open alone.
|
|
54
|
+
- `chi -p … --non-interactive` hung forever when the model asked a question (`ask_user_question`) while its stdin
|
|
55
|
+
was a pipe that stayed open (`sleep 30 | chi -p …`, a parent holding stdin): it waited for an answer line. Nobody
|
|
56
|
+
can answer there, as for an approval: the question is dismissed at once (one line on stderr), the model finishes
|
|
57
|
+
its reply, and stdin is never read. Piped answers without `--non-interactive` (`printf '2\n' | chi --no-shared -p
|
|
58
|
+
…`) work as before.
|
|
59
|
+
|
|
60
|
+
## [0.45.0] - 2026-10-08
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- Plugins: `ctx.fork?` says whether the session is a fork (`ctx.sessions.fork`: it has a parent and isn't a
|
|
65
|
+
delegate child). See [docs/plugins.md](docs/plugins.md).
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- Commands sent while a turn runs no longer all answer `busy`. Those that only show something answer at once
|
|
70
|
+
(`/models`, and `/model`, `/llm-context` and `/guardrails` alone). Those that change something (`/model X`,
|
|
71
|
+
`/llm-context strategy …`, `/guardrails revoke N`) and `!cmd` run after the turn, in the order sent with the
|
|
72
|
+
prompts: the terminals say `(queued: runs after this turn)`, the web draws the command's bubble dashed until it
|
|
73
|
+
ran (after a reload too), and the web's `llm ctx` chip can Set mid-turn. A `!cmd` waiting after a canceled or
|
|
74
|
+
failed turn is dropped, so the turn's `!rollback` stays open, and one still waiting when the worker stops is
|
|
75
|
+
answered `dropped`. `!rollback` and `/continue` stay refused (`busy: Ctrl-C the turn first, then !rollback`).
|
|
76
|
+
`/archive` typed during a turn archives once it ends, in the REPL and an attached terminal. `chi --attach ID
|
|
77
|
+
--model X` on a running turn waits for its end. See "Typing during a turn" in [docs/cli.md](docs/cli.md).
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- `/stats`, `/model` and the web named no model notes for a fresh session (a REPL's before the first turn, a new
|
|
82
|
+
web or worker session), though its first turn's prompt carries them; they now name the notes that prompt will
|
|
83
|
+
load. A session whose prompt carried none keeps saying so.
|
|
84
|
+
- `chi self`'s `model notes` row resolved a note's model overlay from the process's `XDG_CONFIG_HOME` while it
|
|
85
|
+
read the note itself from the env it was given, so the row could count no overlay (or another config's); it now
|
|
86
|
+
reads both from the same env, and passes the fallback key the session's prompt does, so a model typed as an
|
|
87
|
+
alias whose note overlay is keyed by the alias's name shows it — the row agrees with the notes the session's prompt loads.
|
|
88
|
+
- In the plain REPL a session command typed at a continue offer (other than `/continue`) was read as an invalid
|
|
89
|
+
answer and lost; it now runs and the offer stays open, as in a worker.
|
|
90
|
+
- A session command sent as a message behind queued prompts (`chi send -m "/model x"` after `chi send -m "task"`)
|
|
91
|
+
could be answered `busy` by the next queued prompt's turn instead of running before it; it now runs once its
|
|
92
|
+
own message's turn comes.
|
|
93
|
+
- The web's ctx meter under an LLM context budget smaller than the window (`llm_context.budget_tokens`) read the
|
|
94
|
+
percentage of the window after a reload (`ctx 12%`) where live it read the budget's (`ctx 25%`); it now counts
|
|
95
|
+
against the budget both ways, as the `[CONTEXT: …]` line and the status line do, and a generation's final counts
|
|
96
|
+
no longer flip it back to the window mid-turn. The ctx tooltip's context line says so: `context: ~16.0k of the
|
|
97
|
+
64.0k budget, 128.0k window (server)`.
|
|
98
|
+
- The web's strip of latest sessions, back from All sessions with the pointer still on it, could keep a waiting
|
|
99
|
+
family behind other cards until the pointer moved: an update that came while All sessions opened was drawn in
|
|
100
|
+
the order held for the pointer.
|
|
101
|
+
- github-pr 0.3.1: in `auto_attach: offer` mode a forked session (`/btw keep`, a plugin's fork) now gets its
|
|
102
|
+
`first_prompt` row in `offers.ndjson` (its first prompt after the conversation it started from), as a new
|
|
103
|
+
session does; it got none.
|
|
104
|
+
- A resumed or attached session (`chi --resume`, `chi --attach`) drew a turn that failed with its steps kept as its
|
|
105
|
+
prompt and tool rows followed by an earlier step's text, as if that were the answer; the join now draws the line
|
|
106
|
+
the live view left (`✕ turn failed: HTTP 500: boom · 2.0s`, then ` partial progress kept; !rollback restores the
|
|
107
|
+
pre-turn state` when the steps stayed) after the rows, with no answer text, as the web does.
|
|
108
|
+
- The web's stage trail: when the ✂ row of an applied LLM context edit landed in its step inside the
|
|
109
|
+
closed cloud it was out of sight and, unlike a hook's notice, flashed nothing. It now flashes in the
|
|
110
|
+
trail for a few seconds, as its row reads (`✂ forgot 2 outputs, stubbed 1 stale read · frees ~4.1k
|
|
111
|
+
tokens (paid off)`), as a hook notice does.
|
|
112
|
+
- The web's stage trail: when a message cut a generation that was still thinking, its row (`↪ cut in
|
|
113
|
+
for your message`, `… a message sent with chi send`, `… the parent agent's message`) landed in its
|
|
114
|
+
step inside the closed cloud and, out of sight, flashed nothing. It now flashes in the trail for a
|
|
115
|
+
few seconds, as its row reads, as the ✂ row and a hook notice do.
|
|
116
|
+
- `chi send --new --model` and `delegate` warned `host 'h' doesn't list model 'x'` for an id the host serves, when
|
|
117
|
+
the on-disk model list still named the model a one-model server served before it was reloaded: a saved list
|
|
118
|
+
older than 10 minutes that misses the id now re-lists that one host once (bounded at 5 s) and judges again
|
|
119
|
+
against what it lists now, saving the fresh ids. A list from the last 10 minutes is taken at its word, so a host
|
|
120
|
+
that serves ids it never lists (a gateway's round-robin aliases) is not asked on every launch; a re-list that
|
|
121
|
+
fails, times out or lists nothing warns from the saved list.
|
|
122
|
+
- The session lists' `ctx %` (the web's session cards, `chi sessions list` and its `ctx 12%` column, the
|
|
123
|
+
`list_sessions` tool's `ctx_pct`) and `/stats`' `context used:` still divided by the window when the session's
|
|
124
|
+
LLM context budget (`llm_context.budget_tokens`) was smaller, where the live meter counts the smaller of the
|
|
125
|
+
two; they now count the same way, and `/stats` says so: `context used: 16000 tokens (25.0% of the 64000
|
|
126
|
+
budget)`.
|
|
127
|
+
|
|
11
128
|
## [0.44.0] - 2026-10-08
|
|
12
129
|
|
|
13
130
|
### Added
|
|
@@ -2026,7 +2143,9 @@ and long-lived sessions.
|
|
|
2026
2143
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
2027
2144
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
2028
2145
|
|
|
2029
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
2146
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.46.0...HEAD
|
|
2147
|
+
[0.46.0]: https://github.com/dm1try/samagotchi/compare/v0.45.0...v0.46.0
|
|
2148
|
+
[0.45.0]: https://github.com/dm1try/samagotchi/compare/v0.44.0...v0.45.0
|
|
2030
2149
|
[0.44.0]: https://github.com/dm1try/samagotchi/compare/v0.43.0...v0.44.0
|
|
2031
2150
|
[0.43.0]: https://github.com/dm1try/samagotchi/compare/v0.42.0...v0.43.0
|
|
2032
2151
|
[0.42.0]: https://github.com/dm1try/samagotchi/compare/v0.41.0...v0.42.0
|
data/docs/architecture.md
CHANGED
|
@@ -287,7 +287,8 @@ The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` fr
|
|
|
287
287
|
newest turn the worker finished and any it hasn't saved yet (at most 20 turns), with their tool
|
|
288
288
|
calls and the running turn's finished ones. The full history is the session's `analytics.json`,
|
|
289
289
|
which `SessionMetrics#persist` rewrites after each turn; the web server merges the two by id.
|
|
290
|
-
The totals' `tokens` block adds the cached, cache-write and reasoning sums, the cost
|
|
290
|
+
The totals' `tokens` block adds the cached, cache-write and reasoning sums, the reported cost (`cost_sum`) and the
|
|
291
|
+
estimated one (`cost_estimate_sum`, from `hosts.<name>.models` prices; 0 for none), the decode time and tokens
|
|
291
292
|
behind the average speed (`avg_decode_tps`) and the last speeds (`last_decode_tps`,
|
|
292
293
|
`last_prefill_tps`, `tps_source`: `server` or `estimate`). Each `generation_completed` event
|
|
293
294
|
carries the generation's `speed` (`{decode_tps, source}`, null without one), its own prompt-cache
|
data/docs/cli.md
CHANGED
|
@@ -85,12 +85,15 @@ small -> box:gemma-small # an alias and the ref it resolves to
|
|
|
85
85
|
$ chi models qwen # only the names containing "qwen" (any case)
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
- A host's ids declared under `hosts.<name>.models` (see
|
|
89
|
+
[configuration](configuration.md#models-a-host-serves-but-doesnt-list)) come first under that host, listed or not,
|
|
90
|
+
as plain names like any other.
|
|
88
91
|
- A default-host id is bare, except one with a `:` (`qwen3:8b`), which is written `<host>:qwen3:8b` so it can't
|
|
89
92
|
read as a host name. An id that an alias of the same name hides is left out.
|
|
90
93
|
- Each run lists anew (no cache); `--timeout S` caps the wait for the hosts (default 4 s). A host that fails or doesn't
|
|
91
94
|
answer in time is noted on stderr (`chi models: box: no answer in 4 s`).
|
|
92
95
|
- `--format json` prints one object: `default` (and `default_typed` when it was set as an alias), `default_host`,
|
|
93
|
-
`models` (`name`, `host`, `id`), `aliases` (`name`, `ref`, `host`) and `warnings`. The desktop helper's model
|
|
96
|
+
`models` (`name`, `host`, `id`, and `configured: true` on an id declared under `hosts.<name>.models`), `aliases` (`name`, `ref`, `host`) and `warnings`. The desktop helper's model
|
|
94
97
|
chooser reads it.
|
|
95
98
|
- Exit 0 when any host listed its models, 1 when none did (the default is still printed), 2 on a usage error.
|
|
96
99
|
|
|
@@ -245,6 +248,9 @@ Notes:
|
|
|
245
248
|
then `chi: the session is kept with your prompt; continue it with: chi
|
|
246
249
|
--resume ID` (exit 1). Ctrl-C saves the session with the prompt and a
|
|
247
250
|
cancel note and exits 130.
|
|
251
|
+
Nobody can answer there: an approval is denied, and a question the model
|
|
252
|
+
asks (`ask_user_question`) is dismissed at once (one line on stderr), never
|
|
253
|
+
read from stdin, so the model finishes its reply.
|
|
248
254
|
|
|
249
255
|
### Scratch sessions
|
|
250
256
|
|
|
@@ -429,9 +435,11 @@ request (with the cached tokens, the cache writes, the re-prefilled tokens and t
|
|
|
429
435
|
reports them),
|
|
430
436
|
the decode speed, generation latency, cancellations, retries (the network's),
|
|
431
437
|
thinking cuts (generations a plugin such as loop-guard cut) and output cap hits
|
|
432
|
-
(generations that ended at the provider's cap), context used and window, the memory indexes the session's prompt holds ([Memory](memory.md#the-indexs-size)), the model notes it carried (`model notes: model_notes_deepseek (system, 612 chars)`, none without any; [Memory](memory.md#model-notes)), the prompt profile and the model the server says it ran, and the
|
|
433
|
-
cost when the provider reports one (OpenRouter's `usage.cost
|
|
434
|
-
|
|
438
|
+
(generations that ended at the provider's cap), context used and window (the used figure is counted against the smaller of the window and the session's LLM context budget, and says so: `context used: 16000 tokens (25.0% of the 64000 budget)`), the memory indexes the session's prompt holds ([Memory](memory.md#the-indexs-size)), the model notes it carried (`model notes: model_notes_deepseek (system, 612 chars)`, none without any; [Memory](memory.md#model-notes)), the prompt profile and the model the server says it ran, and the
|
|
439
|
+
cost when the provider reports one (OpenRouter's `usage.cost`), and the cost
|
|
440
|
+
estimated from a price under `hosts.<name>.models` when it reports none or 0,
|
|
441
|
+
marked `~` (`cost: $0.42 reported + ~$0.12 from hosts.<name>.models prices (this session only)`;
|
|
442
|
+
see "Prices" in [configuration](configuration.md#prices)). The speed line is the last generation's and the session's average
|
|
435
443
|
(weighted by tokens): llama.cpp reports it exactly (`87 tok/s out, 1.9k tok/s
|
|
436
444
|
prompt (last, server)`); for other servers it is estimated from the first
|
|
437
445
|
streamed chunk to the end and marked `~` (`~64 tok/s out (last, estimate)`).
|
|
@@ -451,7 +459,8 @@ saved with the session and applies from the next turn's start; the reply says
|
|
|
451
459
|
what it costs: `forget` on or off re-reads the whole prompt once, `stale` turned
|
|
452
460
|
on stages the reads already superseded as one batch under the apply rule, and a
|
|
453
461
|
layer turned off sends its stubs whole again (turning it back on brings them
|
|
454
|
-
back).
|
|
462
|
+
back). `/llm-context` alone answers mid-turn too; a change sent while a turn runs
|
|
463
|
+
waits for the turn's end (see *Typing during a turn*). `/stats` shows
|
|
455
464
|
the same values on its `llm context:` line. The web's info bar shows them as the
|
|
456
465
|
`llm ctx` chip (`llm ctx stale,forget · session`), whose popover sets them
|
|
457
466
|
through `/llm-context`. See "LLM context: a session's own strategy" in
|
|
@@ -468,11 +477,28 @@ REPL alike:
|
|
|
468
477
|
A line that comes after the turn's last step runs as the next turn, and so
|
|
469
478
|
does one sent after Ctrl-C: it doesn't merge into the turn being cancelled.
|
|
470
479
|
Reminder turns take merged lines too.
|
|
471
|
-
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
480
|
+
- Commands answer, wait or are refused, by what they do:
|
|
481
|
+
- At once, beside the turn: `/stats`, `/recap`, `/help`, `/context`, a
|
|
482
|
+
plugin's anytime command (`/btw`), and the commands that only show
|
|
483
|
+
something: `/models` (with a filter too), and `/model`, `/llm-context` and
|
|
484
|
+
`/guardrails` alone.
|
|
485
|
+
- After the turn: the ones that change something (`/model X`, `/model clear`,
|
|
486
|
+
`/llm-context strategy …`, `/guardrails revoke N`) and `!cmd`, whose output
|
|
487
|
+
joins the conversation. `(queued: runs after this turn)` says so at once (the
|
|
488
|
+
web draws the command's bubble dashed, with the same words, until it ran);
|
|
489
|
+
when the turn ends they run in the order sent, a prompt sent before one
|
|
490
|
+
running first and one sent after it running after. A `/model X` sent
|
|
491
|
+
mid-turn doesn't change the running turn, nor a line merged into it later.
|
|
492
|
+
After a canceled or failed turn a waiting `!cmd` is dropped
|
|
493
|
+
(`turn canceled: !ls not run; send it again`): running it would close the
|
|
494
|
+
`!rollback` the turn left; the others still run. A command still waiting
|
|
495
|
+
when the session's worker stops is answered `dropped` too.
|
|
496
|
+
- Refused: `!rollback` (`busy: Ctrl-C the turn first, then !rollback`),
|
|
497
|
+
`/continue` and a plugin's command that isn't anytime
|
|
498
|
+
(`busy: wait for the turn to end`). The line goes back into the prompt, so
|
|
499
|
+
Enter runs it once the turn ends.
|
|
500
|
+
|
|
501
|
+
A one-word `/word` no command answers (`/modle`) is not steering text either:
|
|
476
502
|
the hint prints and the line is dropped.
|
|
477
503
|
- A question (`ask_user_question`, a guardrails approval) turns the prompt into
|
|
478
504
|
a yellow `? ` and lists its choices under it, fitted to the terminal; only a
|
|
@@ -486,6 +512,10 @@ REPL alike:
|
|
|
486
512
|
(`/exit --delete` deletes the session then too). In an
|
|
487
513
|
attached terminal it detaches at once and the turn goes on in the worker
|
|
488
514
|
(`/exit` then says the worker stays up: a turn is running).
|
|
515
|
+
- `/archive` mid-turn archives once the turn ends:
|
|
516
|
+
`(archives after this turn; Ctrl-C cancels it)` in the plain REPL,
|
|
517
|
+
`(archives after this turn)` in an attached terminal, which also waits for
|
|
518
|
+
the commands queued behind the turn.
|
|
489
519
|
|
|
490
520
|
With stdin that isn't a terminal (a pipe), the REPL reads a line only between
|
|
491
521
|
turns.
|
|
@@ -712,7 +742,7 @@ In interactive assist mode, you can switch the request model without restarting:
|
|
|
712
742
|
- `/model`: show the effective model (and default when diverged: `runtime model: <effective> (default: <default>, profile=<name>, <source>)`, e.g. `profile=qwen36, server (chat_template)`). When the session's prompt carries model notes the line ends with `; notes: model_notes_deepseek (system, 612 chars)`; a switch's `runtime model set to …` names the new model's ([Memory](memory.md#model-notes)).
|
|
713
743
|
- `/model clear` (or `default`/`none`/`off`): clear the session override, reverting to the configured default.
|
|
714
744
|
- `/guardrails`: the guardrail rules (by source), what failed to load, and your stored approvals, numbered; `/guardrails revoke N` removes approval N (see [Guardrails](guardrails.md)).
|
|
715
|
-
- `/models`: list model ids aggregated across all `hosts:` (an alias shows next to its id: a bare alias on every host listing the id, a `host:model` one only under its host; grouped `host (host:port):` with per-host `unreachable` warnings, e.g. an unset `api_key_env`; lists cached 60s, 10 minutes for a remote host; lazy — no startup prefill). At most 20 ids per host, then `… and N more`; `/models <text>` lists every id containing `<text>` (any case), e.g. `/models qwen` on OpenRouter.
|
|
745
|
+
- `/models`: list model ids aggregated across all `hosts:` (an alias shows next to its id: a bare alias on every host listing the id, a `host:model` one only under its host; grouped `host (host:port):` with per-host `unreachable` warnings, e.g. an unset `api_key_env`; lists cached 60s, 10 minutes for a remote host; lazy — no startup prefill). Ids the host declares under `hosts.<name>.models` come first, as `rr/x (config)`, and don't count toward the cap. At most 20 ids per host, then `… and N more`; `/models <text>` lists every id containing `<text>` (any case), e.g. `/models qwen` on OpenRouter.
|
|
716
746
|
|
|
717
747
|
Notes:
|
|
718
748
|
|
|
@@ -815,7 +845,8 @@ status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | me
|
|
|
815
845
|
```
|
|
816
846
|
|
|
817
847
|
- `model=`: the model in use, `(default: …)` beside it when it isn't the config's default, and
|
|
818
|
-
`model=<served> (served; asked <name>)` when the server said it served another model
|
|
848
|
+
`model=<served> (served; asked <name>)` when the server said it served another model (not for one its host's
|
|
849
|
+
`hosts.<name>.models.<id>.served` names; see [configuration](configuration.md#models-a-host-serves-but-doesnt-list)).
|
|
819
850
|
- `↳ <id>`: the session that delegated this one.
|
|
820
851
|
- `⑂ …`: this session's delegate children (forks aren't counted): how many run and how many wait
|
|
821
852
|
for an answer; only the counts there are, nothing when none does (`/children` has the rest:
|
data/docs/configuration.md
CHANGED
|
@@ -413,6 +413,41 @@ models:
|
|
|
413
413
|
`SAMAGOTCHI_HOSTS_JSON`, and reads `models:` from the config file each turn: after changing a host's `sampling:`,
|
|
414
414
|
stop the session's worker (`chi sessions stop`) for it to take effect.
|
|
415
415
|
|
|
416
|
+
## Prices
|
|
417
|
+
|
|
418
|
+
A provider that reports no cost (`usage.cost`), or reports 0, can still be priced: give the model a price under
|
|
419
|
+
the host that serves it, in USD per 1M tokens.
|
|
420
|
+
|
|
421
|
+
```yaml
|
|
422
|
+
hosts:
|
|
423
|
+
work:
|
|
424
|
+
url: https://gateway.example/v1
|
|
425
|
+
api: openai
|
|
426
|
+
models:
|
|
427
|
+
rr/deepseek-v4.1:
|
|
428
|
+
price: {input: 0.27, cache_read: 0.07, output: 1.10}
|
|
429
|
+
openrouter:
|
|
430
|
+
url: https://openrouter.ai/api/v1
|
|
431
|
+
api: openai
|
|
432
|
+
models:
|
|
433
|
+
qwen/qwen3.8-27b: {price: {input: 0.10, output: 0.40}} # a listed id, here only for its price
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- `input` and `output` are required; `cache_read` and `cache_write` are optional. Each is a number >= 0; a price
|
|
437
|
+
missing one of the required two, or with a value that isn't a number >= 0, warns at start and is ignored.
|
|
438
|
+
- An unset rate is the input rate: that overstates cached reads, and understates Anthropic cache writes (billed
|
|
439
|
+
1.25x input for the 5 min cache, 2x for 1 h). Set them when it matters.
|
|
440
|
+
- A price is per host, since one model can cost differently on two hosts. It is looked up by the model the session
|
|
441
|
+
asked for (its host and id), never by the model the host says it served.
|
|
442
|
+
- Each generation with the server's token counts is estimated as uncached prompt × `input` + cached reads ×
|
|
443
|
+
`cache_read` + cache writes × `cache_write` + completion × `output`, over 1M. A generation with only estimated
|
|
444
|
+
counts (characters / 4, no usage from the server) isn't priced.
|
|
445
|
+
- A reported cost wins: a generation with a non-zero `usage.cost` is never estimated. A reported 0 with a price is
|
|
446
|
+
estimated (a gateway that bills elsewhere reports 0); the 0 is kept as the reported cost.
|
|
447
|
+
- The estimate is saved apart from the reported cost (`cost_estimate` per turn, `cost_estimate_sum` in the
|
|
448
|
+
session's totals in `analytics.json`) and shows with a `~`.
|
|
449
|
+
- A server that sends no cached counts has its whole prompt priced at `input`: an overestimate.
|
|
450
|
+
|
|
416
451
|
## Thinking
|
|
417
452
|
|
|
418
453
|
How much a model thinks before it answers. One level, `off`, `low`, `medium`, `high` or `default`, set per model,
|
|
@@ -555,11 +590,12 @@ When `default.model` is unset or blank, Samagotchi fails fast with a clear start
|
|
|
555
590
|
With several `hosts:`, `host:model` (or an alias naming a host) pins the host.
|
|
556
591
|
An unqualified model name goes to the default host when its `/models` list has
|
|
557
592
|
that exact id, else to the first host in `hosts:` order whose list has it, else
|
|
558
|
-
to the default host.
|
|
593
|
+
to the default host. A host's declared ids (`hosts.<name>.models`, below) count
|
|
594
|
+
as listed. Only exact ids count, never a substring: `/model gemma` on
|
|
559
595
|
a box that lists `gemma-4-26b` needs `box:gemma` or the exact id. The lists are
|
|
560
596
|
known only after `/models` ran (nothing is fetched before the first turn), so
|
|
561
|
-
until then an unqualified name goes to the default host
|
|
562
|
-
one. A **remote** host (an `https` url or a public address; see "Remote or local") keeps its model
|
|
597
|
+
until then an unqualified name goes to the default host, unless a host declares
|
|
598
|
+
it; use `host:model` to pin one. A **remote** host (an `https` url or a public address; see "Remote or local") keeps its model
|
|
563
599
|
list for 10 minutes (60s for local hosts).
|
|
564
600
|
|
|
565
601
|
**The context window** (what the context status and `ctx=` count against) comes from,
|
|
@@ -608,6 +644,59 @@ alias, `/model`, `chi send --new --model`, the web's new-session model and a
|
|
|
608
644
|
delegate's model. Any other unknown prefix (`nosuch:x`) is sent to the default
|
|
609
645
|
host as the model id.
|
|
610
646
|
|
|
647
|
+
### Models a host serves but doesn't list
|
|
648
|
+
|
|
649
|
+
A gateway can serve ids its `/v1/models` never lists (round-robin aliases such
|
|
650
|
+
as `rr/deepseek-v4.1`). Declare them under the host:
|
|
651
|
+
|
|
652
|
+
```yaml
|
|
653
|
+
hosts:
|
|
654
|
+
work:
|
|
655
|
+
url: https://gateway.example/v1
|
|
656
|
+
api: openai
|
|
657
|
+
api_key_env: WORK_API_KEY
|
|
658
|
+
models:
|
|
659
|
+
rr/deepseek-v4.1:
|
|
660
|
+
rr/qwen3.8-27b:
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
A plain list says the same: `models: [rr/deepseek-v4.1, rr/qwen3.8-27b]`. Ids are
|
|
664
|
+
full ids (no globs), matched case-insensitively. A declared id is known on its
|
|
665
|
+
host whatever the host lists:
|
|
666
|
+
|
|
667
|
+
- `work:rr/deepseek-v4.1` never gets the "host 'work' doesn't list model" warning,
|
|
668
|
+
and the host isn't re-listed for it (`chi send --new --model`, delegates).
|
|
669
|
+
- A bare `rr/deepseek-v4.1` goes to `work` as if `work` listed it, also before
|
|
670
|
+
any `/models` and in a session's worker. When several hosts list or declare an
|
|
671
|
+
id, the default host wins if it is one of them, else the first in `hosts:`
|
|
672
|
+
order; two hosts declaring one id get a warning at start saying which one a
|
|
673
|
+
bare id goes to. `host:id` still pins a host.
|
|
674
|
+
|
|
675
|
+
**A gateway that answers with another model.** A round-robin id is served by one
|
|
676
|
+
of its targets, and the reply names that one (`fireworks/deepseek-v4.1`), so chi
|
|
677
|
+
marks every turn as served by another model than asked (the web's `⚠`, the status
|
|
678
|
+
line's `(served; asked …)`, `/model`'s `served:`). `served:` lists the models the
|
|
679
|
+
host may answer with, as exact ids (any case), or `any`:
|
|
680
|
+
|
|
681
|
+
```yaml
|
|
682
|
+
hosts:
|
|
683
|
+
work:
|
|
684
|
+
models:
|
|
685
|
+
rr/deepseek-v4.1:
|
|
686
|
+
served: [fireworks/deepseek-v4.1, baseten/deepseek-v4.1]
|
|
687
|
+
rr/qwen3.8-27b:
|
|
688
|
+
served: any
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
A served model it names is no mismatch: the web and the status line keep the name
|
|
692
|
+
you asked for, and `/stats` names the served model without "(asked for …)". One it
|
|
693
|
+
doesn't name is still marked. `any` gives that check up for the id.
|
|
694
|
+
|
|
695
|
+
**Root `models:` vs `hosts.<name>.models:`.** The root `models:` holds settings
|
|
696
|
+
by model name (sampling, thinking, window, profile…), on whatever host the model
|
|
697
|
+
runs on; it says nothing about which host serves a model. `hosts.<name>.models:`
|
|
698
|
+
says which models that host serves. Keep settings in the root one.
|
|
699
|
+
|
|
611
700
|
### Model aliases
|
|
612
701
|
|
|
613
702
|
`model_aliases:` maps a name to a model id or `host:model`. An alias works
|
|
@@ -641,12 +730,17 @@ Transient network failures are retried automatically with exponential backoff.
|
|
|
641
730
|
budget, so it fails at once (lower `default.max_tokens` or raise the key's limit).
|
|
642
731
|
- A refused connection (nothing listening) is not retried: the turn fails at once with `can't reach host <name> at
|
|
643
732
|
<address> (connection refused) — is the server running?`.
|
|
644
|
-
- A stream that has already produced output is never retried (the retry would repeat it)
|
|
733
|
+
- A stream that has already produced output is never retried at the transport (the retry would repeat it). The
|
|
734
|
+
chat loop (an OpenAI-compatible host) drops the partial reply and asks again for the whole step, from the same
|
|
735
|
+
conversation, at most twice (at most `retry.max` times when that is smaller: `retry.max: 0` turns it off), after
|
|
736
|
+
the same backoff, reported as a retry (`↻ retrying (stream dropped) …`); once those run out the turn fails with
|
|
737
|
+
the error, as before. A refusal, a first-token timeout, a provider error and a cancel are never retried this way.
|
|
645
738
|
- Cancellation (`Ctrl-C`) is never retried.
|
|
646
739
|
|
|
647
740
|
Configuration:
|
|
648
741
|
|
|
649
|
-
- `retry.max` (default `5`, env `SAMAGOTCHI_RETRY_MAX`): number of retries after the first failed attempt
|
|
742
|
+
- `retry.max` (default `5`, env `SAMAGOTCHI_RETRY_MAX`): number of retries after the first failed attempt; it also
|
|
743
|
+
caps a dropped stream's asks-again (at most 2), and `0` turns those off too.
|
|
650
744
|
- `retry.base_delay` (default `0.5`, env `SAMAGOTCHI_RETRY_BASE_DELAY`): backoff base delay in seconds.
|
|
651
745
|
- `retry.max_delay` (default `8.0`, env `SAMAGOTCHI_RETRY_MAX_DELAY`): cap for backoff delay in seconds.
|
|
652
746
|
|
|
@@ -1036,6 +1130,9 @@ described in their own sections.
|
|
|
1036
1130
|
| `thinking.turn_preamble` | `true` | yes | Ask a `qwen36` model to open its thinking with a short `TURN:` line (the step label). |
|
|
1037
1131
|
| `thinking.level` | `default` | `--thinking` | `off`, `low`, `medium`, `high` or `default` for every model; the flag and env outrank the `models:`/`hosts:` entries, the file's value doesn't. See "Thinking". |
|
|
1038
1132
|
| `models.<key>.thinking`, `hosts.<name>.thinking` | none | | A model's or host's level. See "Thinking". |
|
|
1133
|
+
| `hosts.<name>.models` | none | | The model ids a host serves whatever its `/v1/models` lists: a map of ids, or a plain list. See "Models a host serves but doesn't list". |
|
|
1134
|
+
| `hosts.<name>.models.<id>.served` | none | | The models the host may answer that id with (exact ids, any case), or `any`: no served-model warning for those. See "Models a host serves but doesn't list". |
|
|
1135
|
+
| `hosts.<name>.models.<id>.price` | none | | The model's price on that host, USD per 1M tokens: `input`, `output`, optional `cache_read` and `cache_write`. Estimates the cost a provider doesn't report. See "Prices". |
|
|
1039
1136
|
| `hosts.<name>.remote` | by address | | `true`/`false`: treat the host as a remote provider or a local server. See "Remote or local". |
|
|
1040
1137
|
| `max_tool_output_chars` | `10000` | yes | Characters of each tool output kept in the conversation (both loops); a longer one is cut and ends with `[cut: N of M chars; read it in parts]`. A top-level key (see below). |
|
|
1041
1138
|
| `cache.warmup` | `auto` | | `auto`: after a turn, send the next turn's prompt (up to the next message) to a local llama.cpp host on the native loop, so the next turn prefills only its message; never a remote or `api: openai` host. `off`: never. See [prompt caching](internals/prompt-caching.md#the-turn-end-warm-up). |
|
data/docs/context.md
CHANGED
|
@@ -225,7 +225,8 @@ later when an offer is wanted: one JSON line per event, `offered` (session,
|
|
|
225
225
|
project root, working directory, branch, PR URL, main checkout or worktree,
|
|
226
226
|
model), `attached` and `declined` (with the seconds since the offer), and
|
|
227
227
|
`first_prompt` (the first 160 characters of every session's first message,
|
|
228
|
-
offered or not;
|
|
228
|
+
offered or not; a fork's first after the conversation it started from, once;
|
|
229
|
+
not scratch sessions or delegate children). Over 1 MB it is
|
|
229
230
|
renamed to `offers.ndjson.1` (one old file is kept). Only you can read it
|
|
230
231
|
(0600), and nothing leaves your machine.
|
|
231
232
|
|
|
@@ -19,6 +19,7 @@ Behavior:
|
|
|
19
19
|
|
|
20
20
|
- `execute`, `task_create` and the `!cmd` shell bang run the command in a non-login shell: `/bin/sh -c` on Linux; on macOS, when `/bin/zsh` is there, `/bin/zsh --emulate sh +o ignore_braces +o sh_glob -o bash_rematch +o bsd_echo -c` (macOS's `/bin/sh` is bash 3.2, which can't parse an apostrophe in a heredoc inside `"$( )"`, the usual commit message shape). That mode reads no rc files and keeps POSIX word splitting, unmatched globs left as is, `{1..3}`, `[[ … =~ (…) ]]` with `BASH_REMATCH`, arrays, `$'…'`, `&>` and echo expanding `\t`. Bash-only builtins and variables (`shopt`, `BASH_VERSION`, `${!prefix*}`, `read -p`) are not there.
|
|
21
21
|
- Task metadata and output are persisted under `tmp/tasks/`.
|
|
22
|
+
- A task record keeps its process's pid and start time (`pid_started`). Before `task_stop` signals, the pid must still lead its own process group and must not have started at another time; otherwise the pid was reused by another process, nothing is signalled and the task ends as failed. A start time that can't be read at that moment (a failed `ps`) falls back to the group check, as does a record without `pid_started` (from an older chi). `task_get`, `task_list` and `task_wait`'s polls check the group only (no `ps` per poll).
|
|
22
23
|
- Task listing is workspace-scoped (current project only).
|
|
23
24
|
- `task_get` returns metadata and `output_path`; use `read` for output contents.
|
|
24
25
|
- `task_wait` accepts `timeout`, `tail_lines` (maximum 100), and `done_pattern` (a regular expression string).
|
data/docs/internals/engine.md
CHANGED
|
@@ -293,7 +293,7 @@ Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
|
|
|
293
293
|
| `:context_status` | the window's fill crossed a bucket | `{iteration, ...}` |
|
|
294
294
|
| `:generation_started` | a request begins | `{iteration, context_window_tokens, context_window_source}` (native adds `profile`, `profile_source`) |
|
|
295
295
|
| `:generation_chunk` | each streamed chunk | `{iteration, content, text, thinking, payload, tool_call?}` |
|
|
296
|
-
| `:generation_retrying` | the transport retries the request
|
|
296
|
+
| `:generation_retrying` | the transport retries the request, or the chat loop asks again for a step whose stream dropped mid-answer (`restarted: true`: what the step streamed is void, and the web's live step, the TUI's activity line, the Bridge's TurnAccumulator and the stream hooks' watch start it over) | `{iteration, attempt, max_retries, next_delay, error_class, error_message, status, restarted?}` |
|
|
297
297
|
| `:generation_completed` | a generation ended, or was cut | `{iteration, content_length, thinking_chars, served_model, requested_model, finish_reason, ...}`; a cut adds `stopped_by`, `stop_reason`; the Engine adds `speed`, `tokens` |
|
|
298
298
|
| `:generation_cancelled` | the turn was cancelled mid-loop | `{iteration, reason, stopped_by}` |
|
|
299
299
|
| `:empty_answer_retry` | an empty, cut or malformed answer is asked again | `{iteration, attempt, of, finish_reason, thinking_chars, stopped_by?, malformed?}` |
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# The llm_context live runs
|
|
2
|
+
|
|
3
|
+
`script/llm_context_live.rb` runs chi itself on real coding tasks under each LLM context strategy and scores the
|
|
4
|
+
sessions. The replay benchmark ([llm-context-bench.md](llm-context-bench.md)) scores stored sessions offline. These
|
|
5
|
+
runs close the loop: the model lives with the strategy for a whole task, so they measure what the replay can't, such
|
|
6
|
+
as whether a forgotten output comes back as a re-read and whether a note narrows the scope (context strategies P6).
|
|
7
|
+
It is a development tool under `script/` and isn't shipped with the gem. It calls a paid model, so it is opt-in: the
|
|
8
|
+
specs drive it with fakes on synthetic data.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
ruby script/llm_context_live.rb validate TASKS ROOT # no model
|
|
12
|
+
ruby script/llm_context_live.rb run TASKS ROOT --model openrouter:deepseek/deepseek-v4.1-flash \
|
|
13
|
+
[--tasks T1,T3] [--arms none,stale,stale_forget] [--samples 3] [--budget 64000|off] [--cap 8] [--jobs 3] [--limit 9]
|
|
14
|
+
ruby script/llm_context_live.rb report ROOT [--json]
|
|
15
|
+
ruby script/llm_context_live.rb grade|collect TASKS ROOT RUN... # again, for runs already done
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`TASKS` (a YAML file) and `ROOT` (where the runs and results go) live outside the repo. They hold task prompts and
|
|
19
|
+
session files, and no session text goes into the repo, scrubbed or not.
|
|
20
|
+
|
|
21
|
+
## Tasks
|
|
22
|
+
|
|
23
|
+
A task is a real merged fix, replayed from its parent commit. The fix's spec changes are held back and graded
|
|
24
|
+
afterwards.
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
source_repo: /path/to/samagotchi
|
|
28
|
+
tasks:
|
|
29
|
+
T1:
|
|
30
|
+
fix: 545a3562 # the run's repo is its parent
|
|
31
|
+
size: S
|
|
32
|
+
turns: # one chi turn each; {repo} is the run's repo path
|
|
33
|
+
- "In {repo} … find the cause and plan the fix. Don't edit yet."
|
|
34
|
+
- "Implement the plan and run the specs."
|
|
35
|
+
hidden: [spec/broadcast/triage_spec.rb] # optional: the fix's *_spec.rb
|
|
36
|
+
regression: [spec/broadcast/triage_spec.rb] # optional: the hidden ones' paths
|
|
37
|
+
fix_files: [lib/samagotchi/broadcast/triage.rb, spec/broadcast/triage_spec.rb] # optional: what the fix changed
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A file at `hidden/<task id>/<path>` beside the tasks file stands in for that hidden spec. Use it when the fix's spec
|
|
41
|
+
pins a detail that another correct fix would get differently, such as an exact page size; the override keeps only the
|
|
42
|
+
contract.
|
|
43
|
+
|
|
44
|
+
`validate` checks each task's hidden specs: they must fail at the fix's parent and pass at the fix.
|
|
45
|
+
|
|
46
|
+
## A run
|
|
47
|
+
|
|
48
|
+
Each task × arm × sample is a run in `ROOT/runs/<task>-<arm>-s<n>/`:
|
|
49
|
+
|
|
50
|
+
1. **prepare:** `git archive <fix>^` into `repo/`, then a fresh git repo with one base commit. It writes chi's config
|
|
51
|
+
in `config/` (the model's host copied from `--hosts-config`, default the user's config.yml; `turn.max_iterations:
|
|
52
|
+
150`; `recap: false`; `llm_context.apply: payoff`, no `stale_edits`). State goes in `state/`, and it runs `chi
|
|
53
|
+
bundle install loop-guard`. Every chi and rspec command gets `SAMAGOTCHI_ENV=test`, the run's `XDG_STATE_HOME`
|
|
54
|
+
and `XDG_CONFIG_HOME`, and no inherited `SAMAGOTCHI_HOSTS_JSON` (the smoke-run isolation).
|
|
55
|
+
2. **run (Driver):** `chi send --new --wait --format json --dir repo --model M --llm-context <arm>
|
|
56
|
+
--llm-context-budget <N|off>` with the first turn, then each later turn into the same session. The arms are
|
|
57
|
+
`none`, `stale` and `stale_forget` (`stale,forget`). The tasks use two turns, because forget is offered at turn
|
|
58
|
+
ends. The driver answers questions as a parent agent would:
|
|
59
|
+
- the model's first question gets "use your judgement" (option 1 when it takes no text), and later ones are
|
|
60
|
+
dismissed;
|
|
61
|
+
- an approval is denied;
|
|
62
|
+
- the step-limit question gets Stop: there is no Continue, since the limit is part of the task.
|
|
63
|
+
|
|
64
|
+
A turn waits at most 45 minutes. Afterwards the session is stopped with `chi sessions stop`, then `pkill -f
|
|
65
|
+
<the run's state dir>`. Nothing is ever killed by a pid read from a file.
|
|
66
|
+
3. **collect:** the numbers below, read before grading, and `diff.patch` (the work against the base commit).
|
|
67
|
+
4. **grade (Grader):** the hidden specs are copied in as `<name>_p6hidden_spec.rb` and run in one `bundle exec rspec`
|
|
68
|
+
with the regression specs (the model's own copies), then removed. Pass means rspec exits 0 with at least one
|
|
69
|
+
example. The working tree is graded: nothing needs committing.
|
|
70
|
+
|
|
71
|
+
The result goes to `ROOT/results/<run>.json`. A rerun skips runs that have a result, and starts over any run that
|
|
72
|
+
has none.
|
|
73
|
+
|
|
74
|
+
## Cost
|
|
75
|
+
|
|
76
|
+
Each run's cost (OpenRouter's reported `cost`, summed over the turn records) goes to `ROOT/ledger.jsonl`. No run
|
|
77
|
+
starts once the ledger reaches `--cap` (default $8). With `--jobs N` the runs in flight can pass the cap by what they
|
|
78
|
+
spend. A payment error (chi send's `error_kind` `credits` or `credits_held`, or a 402 / over_budget in the detail)
|
|
79
|
+
ends that run at once and writes `ROOT/STOP`, and no further run starts until someone removes it. The account's own
|
|
80
|
+
key limit stays the hard stop.
|
|
81
|
+
|
|
82
|
+
## What it scores
|
|
83
|
+
|
|
84
|
+
Per run (`Collector`), from the session file, `analytics.json` and the repo:
|
|
85
|
+
|
|
86
|
+
- **success:** the grade, plus a manual 0/1/2 in `ROOT/grades.yml` (`T1-none-s1: {grade: 2, note: …}`);
|
|
87
|
+
- **steps, tool calls, edits, commits:** `ModelNotesReport::SessionReader`;
|
|
88
|
+
- **reads, re-reads, re-reads after a stub** (by kind): `LLMContextBench::Replay#read_counts`. A re-read reads a
|
|
89
|
+
path the session read before. It counts as after a stub when an earlier read of that path had an applied stub
|
|
90
|
+
whose cause came before it: a forget's cause is its `forget_outputs` call, a stale stub's the read that superseded
|
|
91
|
+
it;
|
|
92
|
+
- **stale stubs, forget calls, forgotten ids and their tokens:** `Replay#stubs` (chars/4);
|
|
93
|
+
- **tokens:** prompt tokens sent (summed over requests), cached, re-prefilled and completion tokens, and the cost,
|
|
94
|
+
from the turn records;
|
|
95
|
+
- **peak context:** the largest prompt of any turn (`prompt_tokens_max` on the turn record);
|
|
96
|
+
- **scope:** distinct files read, files read and files changed outside the fix's set, and diff lines;
|
|
97
|
+
- **wall time.**
|
|
98
|
+
|
|
99
|
+
`report` gives each task × arm its pass count, manual grades, step-limit stops, and every metric's median with its
|
|
100
|
+
min–max spread. It also gives the gate: `stale_forget` against `none`. Per task, a metric is better when
|
|
101
|
+
`stale_forget`'s median is below `none`'s whole spread, worse when above it, and the same otherwise. The gate passes
|
|
102
|
+
when, on both re-reads and scope (files read + files changed outside), the better tasks outnumber the worse, and
|
|
103
|
+
`stale_forget` passed at least as many runs as `none`.
|
|
104
|
+
|
|
105
|
+
## Limits
|
|
106
|
+
|
|
107
|
+
- Three samples a cell is little. Read nothing into a difference inside one arm's spread.
|
|
108
|
+
- Tokens from `Replay` are chars/4, and those from the turn records are the server's.
|
|
109
|
+
- A Splash host may not report cached tokens or cost (they read 0).
|
|
110
|
+
- The fix's spec file is copied in whole, so its old examples run twice: once hidden and once as the model left
|
|
111
|
+
them.
|
data/docs/plugins.md
CHANGED
|
@@ -94,8 +94,9 @@ commands it knows.
|
|
|
94
94
|
|
|
95
95
|
#### `anytime: true`
|
|
96
96
|
|
|
97
|
-
A normal command waits for its turn: typed while a turn runs, it is
|
|
98
|
-
as busy (the REPL puts it back into the prompt
|
|
97
|
+
A plugin's normal command waits for its turn: typed while a turn runs, it is
|
|
98
|
+
refused as busy (the REPL puts it back into the prompt; a plugin command can't
|
|
99
|
+
ask to run after the turn, as the built-in `/model X` does). An `anytime: true` command
|
|
99
100
|
runs **at once, on its own thread, beside the running turn**, and the turn
|
|
100
101
|
goes on:
|
|
101
102
|
|
|
@@ -504,6 +505,7 @@ full label, `plugin.rb (bundle my-bundle)`.
|
|
|
504
505
|
| `ctx.repo_root` | the git checkout holding `cwd`, or nil |
|
|
505
506
|
| `ctx.scratch?` | whether the session is a `chi scratch` one (deleted when it ends) |
|
|
506
507
|
| `ctx.delegate?` | whether the session is a delegate child: a task another session handed over |
|
|
508
|
+
| `ctx.fork?` | whether the session is a fork (`ctx.sessions.fork`): it has a parent but isn't a delegate child, and started from a conversation, so its first prompt isn't its first user message |
|
|
507
509
|
| `ctx.model` | the model the session runs on now: its resolved ref (`host:id`), right after `/model` too |
|
|
508
510
|
| `ctx.model_key` | that model's memory overlay key (`<name>.<key>.md`, what `memory_write current_model_only` writes) |
|
|
509
511
|
| `ctx.settings` | the bundle's settings, frozen |
|