samagotchi 0.41.0 → 0.43.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 +56 -1
- data/docs/cli.md +8 -6
- data/docs/configuration.md +8 -0
- data/docs/context.md +46 -0
- data/docs/memory.md +18 -0
- data/docs/plugins.md +27 -5
- data/docs/sessions.md +5 -5
- data/docs/testing.md +16 -0
- data/lib/samagotchi/bundles/github-pr/manifest.yml +4 -4
- data/lib/samagotchi/bundles/github-pr/plugin.rb +177 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_reports.rb +8 -4
- data/lib/samagotchi/context_command.rb +5 -2
- data/lib/samagotchi/context_sources.rb +40 -0
- data/lib/samagotchi/engine.rb +31 -1
- data/lib/samagotchi/model_notes.rb +7 -4
- data/lib/samagotchi/plugin/attached_context.rb +75 -11
- data/lib/samagotchi/prompt_note.rb +53 -0
- data/lib/samagotchi/self_report.rb +32 -0
- data/lib/samagotchi/session.rb +16 -1
- data/lib/samagotchi/session_commands.rb +24 -5
- data/lib/samagotchi/session_metrics.rb +21 -4
- data/lib/samagotchi/system_prompt.rb +14 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +12 -0
- data/lib/samagotchi/terminal_ui/formatting.rb +4 -0
- data/lib/samagotchi/tool_declarations.rb +2 -1
- data/lib/samagotchi/turn_flow.rb +8 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +55 -8
- data/lib/samagotchi/web/public/app.js +73 -11
- data/lib/samagotchi/web/public/ctx.js +27 -2
- data/lib/samagotchi/web/public/data.js +6 -3
- data/lib/samagotchi/web/public/format.js +24 -0
- data/lib/samagotchi/web/public/index.html +8 -0
- data/lib/samagotchi/web/public/llm_context_chip.js +151 -21
- data/lib/samagotchi/web/public/model_pick.js +3 -1
- data/lib/samagotchi/web/public/model_picker.js +2 -0
- data/lib/samagotchi/web/public/session_state.js +5 -0
- data/lib/samagotchi/web/public/timing.js +3 -1
- data/lib/samagotchi/web/public/turn_view.js +15 -2
- data/lib/samagotchi/web/session_summary.rb +2 -0
- data/lib/samagotchi/worker.rb +16 -4
- data/lib/samagotchi/worker_wakes.rb +2 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '019e204c3cd98489376bc61446efc6c576d5eb31f73ca97331c23b2d551b015f'
|
|
4
|
+
data.tar.gz: 20b4127aae83e29bfb00d67762ea443b78b2961622502df32458d9d6f9d89068
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdd1bbcef93ae1d57db0fde19927ac5b1c3dbb2e0fe36ff6bc93296044ce305fb2b75c1cc62b6a96c84720d9cb6e288135b69f2aaa624f949282355f360c2ab6
|
|
7
|
+
data.tar.gz: 91d1f3c0d5e2145135045e5f8fd79335142bc99bdccea708a608a541f72401812d1b965a4ec2e03e05a07d109664e40c9826d2573d51ec4cc918cc00b7d7400a
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,59 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.43.0] - 2026-10-08
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- A new web chat can start under its own LLM context: the start page has an `llm ctx` chip beside the model
|
|
16
|
+
picker that shows the picked model's strategy, and its form (as the info bar's, titled "new chat") sets a
|
|
17
|
+
strategy, apply rule and budget for that chat's first turn on (`llm ctx stale · new chat`). The choice is not
|
|
18
|
+
remembered: it resets after a create, on a reload and when you open a session, and an untouched chip sends
|
|
19
|
+
nothing. `POST /api/sessions` takes
|
|
20
|
+
`llm_context` (`{strategy, apply, budget}` as `/llm-context` words them; anything else answers
|
|
21
|
+
`400 invalid_llm_context`), and `GET /api/models` rows carry each model's `llm_context`. See
|
|
22
|
+
[docs/configuration.md](docs/configuration.md) ("LLM context: a session's own strategy").
|
|
23
|
+
- The web's `ctx` chip shows the context window and where it came from: its tooltip gains `context: ~41.0k of
|
|
24
|
+
128.0k tokens (server)`, and the chip reads `ctx ~12%` when the window is chi's 256k default, a guess no server,
|
|
25
|
+
model list or setting gave (the tooltip says to set `window_tokens` for the model).
|
|
26
|
+
- github-pr 0.3.0: `bundles: github-pr: auto_attach:` says what happens to the branch's open PR when a worker
|
|
27
|
+
starts: `attach` (the default, as before), `offer` (a card, "PR #42 for branch feat/x", with **Attach** and **Not
|
|
28
|
+
here**, shown once per session; `/pr-attach 42` and `/pr-decline 42`) or `off` (nothing). In `offer` mode the
|
|
29
|
+
offers, what you clicked and each session's first prompt go to a local log, `plugins/github-pr/offers.ndjson` in
|
|
30
|
+
the state dir. See [docs/context.md](docs/context.md) ("Attach, offer or off").
|
|
31
|
+
- For plugins: `ctx.context.decline(url:|name:)`, `declined?(name, url:)`, `attach(…, force: true)` (the user's
|
|
32
|
+
explicit choice, past a decline) and `mark_offered(name, hint)` / `offered(name)`, a per-session record that a
|
|
33
|
+
plugin offered a source. See [docs/plugins.md](docs/plugins.md) ("Attached context").
|
|
34
|
+
|
|
35
|
+
Update with `chi update`: it updates github-pr (0.3.0: `auto_attach`; needs chi 0.43.0).
|
|
36
|
+
|
|
37
|
+
## [0.42.0] - 2026-10-08
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Which model notes a session's prompt carried: each prompt build (and `/model`) records them in the session file
|
|
42
|
+
(`prompt_notes`: name, scope, size and a short digest), and they are shown in `/stats` (`model notes:
|
|
43
|
+
model_notes_deepseek (system, 612 chars)`), at the end of `/model`'s line (`; notes: …`, after a switch the new
|
|
44
|
+
model's), as a `notes: deepseek` chip in the web's info bar and as a `model notes` row in `chi self`. Nothing shows
|
|
45
|
+
without notes. See [docs/memory.md](docs/memory.md) ("Model notes").
|
|
46
|
+
- For development: `script/model_notes_report.rb`, which groups stored sessions by model and by the model notes
|
|
47
|
+
their prompt carried and compares the calls to the first edit and commit, commits per 100 steps, bare `&` in
|
|
48
|
+
`execute`, the longest run without an edit and the Continues and steers each session needed (`--model`,
|
|
49
|
+
`--since`, `--min-steps`, `--json`). It isn't part of the gem. See [docs/testing.md](docs/testing.md).
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- The web showed no failure line after a reload for a failed turn whose steps stayed (new in 0.41.0), so the turn
|
|
54
|
+
looked unfinished; it ends with the same `✕ turn failed: <why>; partial progress kept (!rollback restores the
|
|
55
|
+
pre-turn state)` line as live (from the turn record, which keeps the failure now; a turn recorded before this
|
|
56
|
+
shows none).
|
|
57
|
+
- A delegate report could reach the parent's model twice: a reminder or continue turn that failed before any
|
|
58
|
+
progress kept the report it had read, and the next turn brought it again. A canceled continue turn's report was
|
|
59
|
+
never delivered. A report's ring now goes only when the turn that read it stays in the conversation.
|
|
60
|
+
- `/llm-context` (the web's `llm ctx` chip, `--llm-context` on `--attach`) against a worker on an older chi answered
|
|
61
|
+
`400 not a session command`; the web (`501 not_supported`) and the attached terminal say the worker runs an older
|
|
62
|
+
chi, with the line that restarts it.
|
|
63
|
+
|
|
11
64
|
## [0.41.0] - 2026-10-08
|
|
12
65
|
|
|
13
66
|
### Added
|
|
@@ -1955,7 +2008,9 @@ and long-lived sessions.
|
|
|
1955
2008
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
1956
2009
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
1957
2010
|
|
|
1958
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
2011
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.43.0...HEAD
|
|
2012
|
+
[0.43.0]: https://github.com/dm1try/samagotchi/compare/v0.42.0...v0.43.0
|
|
2013
|
+
[0.42.0]: https://github.com/dm1try/samagotchi/compare/v0.41.0...v0.42.0
|
|
1959
2014
|
[0.41.0]: https://github.com/dm1try/samagotchi/compare/v0.40.0...v0.41.0
|
|
1960
2015
|
[0.40.0]: https://github.com/dm1try/samagotchi/compare/v0.39.0...v0.40.0
|
|
1961
2016
|
[0.39.0]: https://github.com/dm1try/samagotchi/compare/v0.38.0...v0.39.0
|
data/docs/cli.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`). An approval can be denied, not allowed, unless `guardrails.parent_approvals: once` (a convention, not a security boundary; see [Guardrails](guardrails.md#approvals-from-a-parent-agent))
|
|
23
23
|
- `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text or images to a session (live, stopped or new) as a message (⏎, a turn runs) or a context note (⌘⏎) (see [Desktop helper](desktop.md))
|
|
24
24
|
- `chi models [--format text|json] [--timeout S] [TEXT]` — list the models every configured host offers, as the names `--model` takes (see [Listing the models](#listing-the-models))
|
|
25
|
-
- `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
|
|
25
|
+
- `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`), and `model notes` the model notes a prompt of that model loads (`model_notes_deepseek (system, 612 chars)`, `none`; inside a session its `--mute` list is left out). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
|
|
26
26
|
- `chi update [--dry-run] [--no-gem] [--no-bundles] [--no-desktop]` — update an installed chi: the gem, the system bundle, the shipped bundles you installed and the desktop helper, in one table (see [Updating](#updating))
|
|
27
27
|
- `chi bundle install|upgrade|uninstall|status|diff|list|build|trash` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
|
|
28
28
|
|
|
@@ -340,7 +340,8 @@ note or image) is deleted as its worker exits, and `/exit` says so; set
|
|
|
340
340
|
|
|
341
341
|
`chi sessions stop ID...` stops each session's worker and waits for it to exit, so
|
|
342
342
|
a `chi --resume ID` after it starts a fresh one. A worker still running an
|
|
343
|
-
older chi (from before an upgrade) takes turns but not commands
|
|
343
|
+
older chi (from before an upgrade) takes turns, but not commands, or not the
|
|
344
|
+
ones newer than it (`/llm-context`, `--llm-context` on `--attach`); the attached
|
|
344
345
|
terminal and the Web UI say so, with that restart line.
|
|
345
346
|
|
|
346
347
|
`chi sessions restart ID...` hands each session to a new worker on the newest
|
|
@@ -428,7 +429,7 @@ request (with the cached tokens, the cache writes, the re-prefilled tokens and t
|
|
|
428
429
|
reports them),
|
|
429
430
|
the decode speed, generation latency, cancellations, retries (the network's),
|
|
430
431
|
thinking cuts (generations a plugin such as loop-guard cut) and output cap hits
|
|
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
|
|
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
|
|
432
433
|
cost when the provider reports one (OpenRouter's `usage.cost`; no prices are
|
|
433
434
|
guessed). The speed line is the last generation's and the session's average
|
|
434
435
|
(weighted by tokens): llama.cpp reports it exactly (`87 tok/s out, 1.9k tok/s
|
|
@@ -708,7 +709,7 @@ In interactive assist mode, you can switch the request model without restarting:
|
|
|
708
709
|
- `/model host:model` or `/model host:alias`: qualified host routing (only `:` names a host; `openai/gpt-4o` is a model id). `host:alias` applies the alias; an alias whose target names another host is refused (`alias 'tiny' names host 'box', not 'openrouter'`). Aliases apply once: an alias pointing to another alias sends that name as written.
|
|
709
710
|
- `/model --default <name>`: set session model and persist as new default (`default.model`) in `config.yml` for future sessions (supports `host:model` full ref). It writes only the file: an exported `SAMAGOTCHI_DEFAULT_MODEL` (or `--model`) still wins over it.
|
|
710
711
|
- `/model <name> --alias <alias>`: create alias for current effective model (alias value may be bare or `host:model`).
|
|
711
|
-
- `/model`: show the effective model (and default when diverged: `runtime model: <effective> (default: <default>, profile=<name>, <source>)`, e.g. `profile=qwen36, server (chat_template)`).
|
|
712
|
+
- `/model`: show the effective model (and default when diverged: `runtime model: <effective> (default: <default>, profile=<name>, <source>)`, e.g. `profile=qwen36, server (chat_template)`). When the session's prompt carries model notes the line ends with `; notes: model_notes_deepseek (system, 612 chars)`; a switch's `runtime model set to …` names the new model's ([Memory](memory.md#model-notes)).
|
|
712
713
|
- `/model clear` (or `default`/`none`/`off`): clear the session override, reverting to the configured default.
|
|
713
714
|
- `/guardrails`: the guardrail rules (by source), what failed to load, and your stored approvals, numbered; `/guardrails revoke N` removes approval N (see [Guardrails](guardrails.md)).
|
|
714
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.
|
|
@@ -885,8 +886,9 @@ Behavior notes:
|
|
|
885
886
|
tool steps keeps them, as a cancel does, with the dim `partial progress kept; !rollback restores the pre-turn state`
|
|
886
887
|
(the model reads a note saying how many steps ran and why it stopped; `chi send --wait` and a delegating parent
|
|
887
888
|
read `the turn failed after N tool steps: <why>; its work so far stays`). The
|
|
888
|
-
REPL and attached mode say the same; the web
|
|
889
|
-
|
|
889
|
+
REPL and attached mode say the same; the web ends a failed turn whose steps stayed with `✕ turn failed: <summary>;
|
|
890
|
+
partial progress kept (!rollback restores the pre-turn state)`, after a reload too, and says `■ canceled (Ctrl-C)`
|
|
891
|
+
(`stopped` for its Stop button, `by a hook` for a hook's). A turn a bundle's plugin stopped names it: `■ turn stopped by loop-guard` (the web:
|
|
890
892
|
`■ stopped by loop-guard`).
|
|
891
893
|
- Visible text the canceled request had streamed stays in the conversation, marked `[interrupted]`, so the next
|
|
892
894
|
message (or a continue) picks up from the half-finished reply; the canceled request's thinking and any unfinished
|
data/docs/configuration.md
CHANGED
|
@@ -909,6 +909,14 @@ its own). The first target is a long session such as a day-long coordinator, whe
|
|
|
909
909
|
- **Set it** at start with `chi --llm-context stale,forget --llm-context-apply turn_end --llm-context-budget 64k`
|
|
910
910
|
(the same flags on `chi send --new`), in the session with `/llm-context strategy … apply … budget …`
|
|
911
911
|
(docs/cli.md, "Session commands"), or from the web's `llm ctx` chip in the info bar.
|
|
912
|
+
- **A new web chat:** the start page's `llm ctx` chip, beside the model picker, shows the picked model's values
|
|
913
|
+
and follows a model pick. Its form (titled "new chat") sets a choice for that one chat: the chip reads
|
|
914
|
+
`llm ctx stale · new chat` in the accent colour, and the create sends it as `POST /api/sessions`'s
|
|
915
|
+
`llm_context` (`{"strategy": "stale,forget", "apply": "turn_end", "budget": "64k"}`, each a word as
|
|
916
|
+
`/llm-context` takes it, `default` for unset; anything else answers `400 invalid_llm_context` and starts
|
|
917
|
+
nothing), so the session file holds it before the first turn runs. It is not remembered: it resets after a
|
|
918
|
+
create, on a reload and when you open a session (a failed create keeps it), and an untouched chip sends nothing
|
|
919
|
+
(the model's values apply, as without the chip).
|
|
912
920
|
- **Values:** `none` and a budget of `off` are values the session sets on purpose (they win over the model's);
|
|
913
921
|
`default` (or `/llm-context reset` for all three) unsets one, back to following the model. A budget is 4k to
|
|
914
922
|
10M tokens (`4000`–`10000000`, or `4k`–`10000k`). The layers are kept in chi's order (`stale`, then `forget`).
|
data/docs/context.md
CHANGED
|
@@ -175,6 +175,7 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
|
|
|
175
175
|
the next start doesn't bring it back; `chi context add <PR URL>` or "+ URL"
|
|
176
176
|
does.
|
|
177
177
|
- `chi context add <PR URL>` and the web's "+ URL" attach any PR.
|
|
178
|
+
- `auto_attach` (below) can make it ask first, or attach nothing.
|
|
178
179
|
- The text: title, state, branch, description, reviews, comments (oldest
|
|
179
180
|
first) and checks, every 5 minutes.
|
|
180
181
|
- The summary says what changed in **counts, authors and states only**
|
|
@@ -184,6 +185,50 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
|
|
|
184
185
|
- It wakes for a review requesting changes, checks turning red, and the PR
|
|
185
186
|
merged or closed. Comments alone never wake.
|
|
186
187
|
|
|
188
|
+
### Attach, offer or off
|
|
189
|
+
|
|
190
|
+
```yaml
|
|
191
|
+
bundles:
|
|
192
|
+
github-pr:
|
|
193
|
+
auto_attach: offer # attach (the default) | offer | off
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- `attach`: the branch's open PR is attached when the worker starts, as
|
|
197
|
+
above.
|
|
198
|
+
- `offer`: nothing is attached; a card asks instead, once per session per
|
|
199
|
+
PR: **PR #42 for branch feat/x**, the PR's title and "Not attached: the
|
|
200
|
+
agent gets nothing until you attach it.", with **Attach** and **Not
|
|
201
|
+
here**. The terminal shows them as `→ /pr-attach 42` and
|
|
202
|
+
`→ /pr-decline 42`, to type. Attach attaches it as auto-attach would
|
|
203
|
+
(even after Not here: a click is your choice); Not here marks it removed
|
|
204
|
+
from the session, so `attach` mode won't attach it there either. Either
|
|
205
|
+
turns the card into a one-line notice. Not here on a PR that is attached
|
|
206
|
+
already changes nothing and says so: `chi context rm pr-42` removes it. A worker restart (idle exit,
|
|
207
|
+
resume) doesn't ask again; once the card has scrolled out of the last 20,
|
|
208
|
+
`/pr-attach 42` or "+ URL" still attaches it. `/pr-attach` and
|
|
209
|
+
`/pr-decline` act only on a PR offered in that session.
|
|
210
|
+
- `off`: nothing is attached or offered. "+ URL", `chi context add <PR URL>`
|
|
211
|
+
and line links work as before.
|
|
212
|
+
|
|
213
|
+
`true` and `false` (YAML reads a bare `on`/`off` as those) mean `attach` and
|
|
214
|
+
`off`; an unknown value is `attach`, with a warning in the log. The setting
|
|
215
|
+
is read when a worker starts.
|
|
216
|
+
|
|
217
|
+
A web chat is created with its first message, so in `offer` mode the card
|
|
218
|
+
arrives while that first turn runs: the first answer comes without the PR,
|
|
219
|
+
and Attach delivers it before the next turn. An offered PR that isn't
|
|
220
|
+
attached gets no line links (unless your message names its URL).
|
|
221
|
+
|
|
222
|
+
In `offer` mode the bundle keeps a local log,
|
|
223
|
+
`$XDG_STATE_HOME/samagotchi/plugins/github-pr/offers.ndjson`, to find out
|
|
224
|
+
later when an offer is wanted: one JSON line per event, `offered` (session,
|
|
225
|
+
project root, working directory, branch, PR URL, main checkout or worktree,
|
|
226
|
+
model), `attached` and `declined` (with the seconds since the offer), and
|
|
227
|
+
`first_prompt` (the first 160 characters of every session's first message,
|
|
228
|
+
offered or not; not scratch sessions or delegate children). Over 1 MB it is
|
|
229
|
+
renamed to `offers.ndjson.1` (one old file is kept). Only you can read it
|
|
230
|
+
(0600), and nothing leaves your machine.
|
|
231
|
+
|
|
187
232
|
### Line links
|
|
188
233
|
|
|
189
234
|
In the web, a file reference in an answer links to that line of the pull
|
|
@@ -256,6 +301,7 @@ sessions/<id>/<name>.json, …snapshot.json a session's own
|
|
|
256
301
|
sessions/<id>/subscriptions.json what the session has seen and read (its worker writes it)
|
|
257
302
|
sessions/<id>/muted/<name> a project source this session mutes
|
|
258
303
|
sessions/<id>/declined/<name> a source you removed (its hint inside): auto-attach skips it
|
|
304
|
+
sessions/<id>/offered/<name> a source a plugin offered here ({hint, why, at}): it isn't offered again
|
|
259
305
|
```
|
|
260
306
|
|
|
261
307
|
Deleting a session deletes its folder. A source and its snapshot go with
|
data/docs/memory.md
CHANGED
|
@@ -88,6 +88,24 @@ Working habits for this model:
|
|
|
88
88
|
- **Timing**: a note takes effect at the next prompt build: a session's start,
|
|
89
89
|
a resume, a web worker waking, `/model`. A note written mid-session doesn't
|
|
90
90
|
change the running prompt.
|
|
91
|
+
- **Recorded and shown**: each prompt build writes the notes it carried to the
|
|
92
|
+
session file (`prompt_notes`: name, scope, size in characters and a short
|
|
93
|
+
digest of the text), so a session says later what it ran with; `/model`
|
|
94
|
+
switches record the new model's. `/stats` has a
|
|
95
|
+
`model notes: model_notes_deepseek (system, 612 chars)` line, `/model`
|
|
96
|
+
ends its model line with `; notes: …` (after a switch, the new model's), the
|
|
97
|
+
web's info bar a `notes: deepseek` chip (the full line in its tooltip), and
|
|
98
|
+
`chi self` a `model notes` row after `model key`: the notes a prompt of the
|
|
99
|
+
reported model loads now, the session's `--mute` list left out when it runs
|
|
100
|
+
in one, `none` without any. None of the others shows a line without notes.
|
|
101
|
+
A resumed session, or a web worker that woke, shows the saved ones until its
|
|
102
|
+
first turn builds the prompt again.
|
|
103
|
+
- **Judged**: `ruby script/model_notes_report.rb` (a development script, not
|
|
104
|
+
shipped) groups stored sessions by model and recorded notes and compares the
|
|
105
|
+
calls to the first edit and commit, commits per 100 steps, bare `&`, the
|
|
106
|
+
longest run without an edit and the Continues and steers each needed; run a
|
|
107
|
+
note on and muted for the same kind of task. See
|
|
108
|
+
[testing.md](testing.md) ("The model notes report").
|
|
91
109
|
- Without a model note the system prompt is what it was.
|
|
92
110
|
|
|
93
111
|
|
data/docs/plugins.md
CHANGED
|
@@ -679,6 +679,9 @@ ctx.sessions.stop(id) # one of this session's own children
|
|
|
679
679
|
ctx.context.attach(url: "https://github.com/acme/app/pull/42", why: "branch feat/x has open PR #42")
|
|
680
680
|
ctx.context.attach(name: "ci", cmd: "bin/ci-status", why: "this branch's CI", every_seconds: 120)
|
|
681
681
|
ctx.context.list # => [{name:, scope:, why:, hint:, fetched_at:, error:, provider:}]
|
|
682
|
+
ctx.context.decline(url: "https://github.com/acme/app/pull/42") # => "pr-42"
|
|
683
|
+
ctx.context.mark_offered("pr-42", "https://github.com/acme/app/pull/42", why: "branch feat/x has open PR #42")
|
|
684
|
+
ctx.context.offered("pr-42") # => #<data Offer name="pr-42", hint="https://…", why="branch…", at="2026-10-08T…Z">, or nil
|
|
682
685
|
```
|
|
683
686
|
|
|
684
687
|
[Attached context](context.md) for the session the plugin runs in: chi runs
|
|
@@ -691,13 +694,30 @@ gets a note when its text changes.
|
|
|
691
694
|
command of the plugin's own. A source of that name already attached stays
|
|
692
695
|
as it is. It returns the name, or nil when the user removed that name (or
|
|
693
696
|
that URL) from the session: a plugin doesn't attach it again until the
|
|
694
|
-
user adds it (`ctx.context.declined?(name)` says so
|
|
697
|
+
user adds it (`ctx.context.declined?(name, url: nil)` says so, by the
|
|
698
|
+
name or by the URL). `force: true` is for the user's own explicit choice
|
|
699
|
+
(a click on the plugin's card): it attaches anyway and clears the
|
|
700
|
+
declined marker, by the name and by the URL.
|
|
701
|
+
- `decline(url:)` or `decline(name:)` marks the source declined in this
|
|
702
|
+
session, as the user's removal does: `attach` skips it from then on. With
|
|
703
|
+
`url:`, the name and hint come from the bundle's provider (`name:`
|
|
704
|
+
replaces the name). It returns the name. A source attached already stays
|
|
705
|
+
attached (`chi context rm` removes it).
|
|
706
|
+
- `mark_offered(name, hint, why: nil)` records that the plugin offered a
|
|
707
|
+
source here (the hint is usually its URL; `why:` is kept for the attach
|
|
708
|
+
that may follow); `offered(name)` returns that record
|
|
709
|
+
(`ContextSources::Offer`: `name`, `hint`, `why`, `at`) or nil. A plugin that asks
|
|
710
|
+
before it attaches uses it so a worker restart doesn't ask again. Neither
|
|
711
|
+
an attach nor a decline clears it; deleting the session does.
|
|
695
712
|
- The source is the session's (not the project's), marked
|
|
696
713
|
`added_by: plugin:<bundle>`. It is plugin code, so it isn't asked about as
|
|
697
714
|
the agent's `chi context add --cmd` is.
|
|
698
715
|
- `list` gives the session's sources, its own then its project's.
|
|
699
|
-
-
|
|
700
|
-
|
|
716
|
+
- Two attaches of one source at once (a double click) both return its
|
|
717
|
+
name.
|
|
718
|
+
- `attach`, `decline` and `mark_offered` raise
|
|
719
|
+
`Samagotchi::Plugin::AttachedContext::Error` with the reason (no session
|
|
720
|
+
yet, no provider for the URL, a bad name).
|
|
701
721
|
|
|
702
722
|
The [github-pr bundle](#the-github-pr-bundle) attaches the branch's PR from
|
|
703
723
|
a quiet `chi.init` task.
|
|
@@ -1219,8 +1239,10 @@ nothing without `gh`, a repository or an open PR), and resolves PR URLs for
|
|
|
1219
1239
|
and a summary of what changed in counts, authors and states only; it wakes
|
|
1220
1240
|
the session for a review requesting changes, checks turning red, or the PR
|
|
1221
1241
|
merged or closed. In the web, a `path:line` in an answer links to that line
|
|
1222
|
-
of the session's PR ([Line links](context.md#line-links)).
|
|
1223
|
-
|
|
1242
|
+
of the session's PR ([Line links](context.md#line-links)). `auto_attach:
|
|
1243
|
+
offer` asks with a card first (`/pr-attach N`, `/pr-decline N`), `off`
|
|
1244
|
+
attaches nothing ([Attach, offer or off](context.md#attach-offer-or-off)).
|
|
1245
|
+
See [Attached context](context.md#github-prs-the-github-pr-bundle).
|
|
1224
1246
|
|
|
1225
1247
|
## The coordinator bundle
|
|
1226
1248
|
|
data/docs/sessions.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sessions
|
|
2
2
|
|
|
3
|
-
Sessions are plain files — no DB. Each session is `~/.local/state/samagotchi/sessions/<uuid>.json` (XDG-aware via `XDG_STATE_HOME`) plus a sidecar dir `<uuid>/` with `input/`/`notes/`/`output/`/`owner.lock`/`bridge.json` (`Session.session_dir`). Besides the messages and turn state, the JSON keeps `used_memory_names` (what the session read), `preloaded_memory_names` (`--memory`) and `muted_memory_names` (`--mute`), the last two written before the worker starts so a respawn builds the same prompt, and `parent_id`, the session that delegated this one (see *Delegating*; `null` otherwise); files from before those keys read as empty lists and no parent. `model_name` is the resolved model ref (an alias applied, `host:id` when it names a host), so a resumed session keeps its model when an alias is retargeted; `model_typed` is the name it was given as when that differs (an alias), for the `models:` lookup after a resume (`null` otherwise).
|
|
3
|
+
Sessions are plain files — no DB. Each session is `~/.local/state/samagotchi/sessions/<uuid>.json` (XDG-aware via `XDG_STATE_HOME`) plus a sidecar dir `<uuid>/` with `input/`/`notes/`/`output/`/`owner.lock`/`bridge.json` (`Session.session_dir`). Besides the messages and turn state, the JSON keeps `used_memory_names` (what the session read), `preloaded_memory_names` (`--memory`) and `muted_memory_names` (`--mute`), the last two written before the worker starts so a respawn builds the same prompt, `prompt_notes`, the model notes the system prompt carried at its last build (`[{name, scope, chars, digest}]`, see [Memory](memory.md#model-notes)), and `parent_id`, the session that delegated this one (see *Delegating*; `null` otherwise); files from before those keys read as empty lists and no parent. `model_name` is the resolved model ref (an alias applied, `host:id` when it names a host), so a resumed session keeps its model when an alias is retargeted; `model_typed` is the name it was given as when that differs (an alias), for the `models:` lookup after a resume (`null` otherwise).
|
|
4
4
|
|
|
5
5
|
**Retention (file-based, opt-out via env):**
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ A session is deleted if **expired by age OR overflow by count** (unless `keep_st
|
|
|
21
21
|
|
|
22
22
|
**Which chi a worker runs:** a new or woken worker runs the newest chi installed for this Ruby (`gem 'samagotchi', '>= 0.a'`, prereleases count), whoever starts it: a `chi web` or terminal still on an older chi starts it on the newest one, so a stale process doesn't spread old code. A worker already running keeps its version until it is restarted. Should the newest install fail to load, the worker runs the starter's own version and logs `boot_fallback` (its stdio is `/dev/null`). Under `bundle exec` the worker drops Bundler's env first, so it activates the newest gem too. Two consequences: a wrapper pin (`chi _0.17.0_ web`) pins only that process, not the workers it starts; and a source checkout (`bin/chi` in a worktree, `bundle exec`) starts its workers on its own `lib/`, but a session it made that is later woken by an installed chi (`chi web`, `chi send`) runs the installed code. The names an older starter hands a newer worker (the boot line, its env keys, `input/` files, the session JSON fields and the Bridge routes sessions use on each other) are pinned in `spec/worker_contract_spec.rb`.
|
|
23
23
|
|
|
24
|
-
**Stopping a worker:** `chi sessions stop ID` marks the session stopped (an empty `<id>/stopped` file, not a session.json field, which a worker saving at that moment would write over; a resume removes it), sends its worker TERM and waits (up to 10 s) until the worker has let go of `owner.lock`, so a `chi --resume ID` right after spawns a fresh worker. That is also how to restart a worker still running an older chi, which the attached terminal reports when a command or a question dismiss gets a 404, and the web when an answer, a dismiss, a command or a cancel does (`501 not_supported`; `BridgeClient.stale_worker_message`). The web's `POST /api/sessions/:id/stop` waits the same way, for 2 s.
|
|
24
|
+
**Stopping a worker:** `chi sessions stop ID` marks the session stopped (an empty `<id>/stopped` file, not a session.json field, which a worker saving at that moment would write over; a resume removes it), sends its worker TERM and waits (up to 10 s) until the worker has let go of `owner.lock`, so a `chi --resume ID` right after spawns a fresh worker. That is also how to restart a worker still running an older chi, which the attached terminal reports when a command or a question dismiss gets a 404, and the web when an answer, a dismiss, a command or a cancel does (`501 not_supported`; `BridgeClient.stale_worker_message`). A command this chi has that the older worker doesn't (`/llm-context`, the web's `llm ctx` chip, against a 0.39.0 worker) says the same, not `not a session command` (`SessionCommands.builtin_name`); a typo keeps its `400 unknown_command`. The web's `POST /api/sessions/:id/stop` waits the same way, for 2 s.
|
|
25
25
|
|
|
26
26
|
**Deleting a session:** `chi sessions delete ID...`, `/exit --delete` in a terminal and the Web UI's delete all go through `SessionManager.delete_session`: it resolves a unique prefix, removes `<id>.json` and the whole `<id>/` dir, and returns what it removed. A session a plain REPL has open (`owner.lock` kind `tui`) is always refused. One a worker runs is refused unless the caller asks to stop it: then it stops the worker as `chi sessions stop` does and deletes once `owner.lock` is free (`--force` on the CLI, 10 s; the web always, 2 s; `/exit --delete` after the worker agreed to exit, 10 s). A worker that outlives the wait leaves the session in place. The web's route is `DELETE /api/sessions/:id` (200 `{status: "deleted", session_id, stopped}`; 409 `owned_by_tui` or `still_stopping`; 404).
|
|
27
27
|
|
|
@@ -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. 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
|
|
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. A session whose prompt carried model notes has a `notes: deepseek` chip there (the names without `model_notes_`; the tooltip lists each with its scope and size; `session.prompt_notes` in `GET /api/sessions/:id`, the live worker's, else the session file's; see [Memory](memory.md#model-notes)). 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?, llm_context?}], 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). Beside it, an `llm ctx` chip shows the LLM context the picked model starts under (each `GET /api/models` row's `llm_context`, as the info bar's); its form sets one for the new chat only, sent as `POST /api/sessions`'s `llm_context` and never remembered (see [configuration.md](configuration.md#llm-context-a-sessions-own-strategy)). 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 model picked; 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).
|
|
@@ -198,8 +198,8 @@ The `delegate` tool hands a task to a **child session**: an ordinary chi session
|
|
|
198
198
|
- **Delegate reports: a child's reply comes back by itself.** When a child ends a turn that was its parent's (its task, a follow-up, a continue the parent answered, a reminder: not a turn its user typed into it directly), it rings the parent: a small file in the parent's `children/` dir, written after the child's turn is saved. So does a question the parent's model decides (the child's own `ask_user_question`, the step-limit continue; not an approval or a hook's question, which the user answers on the child's card), the turn after such a question whoever starts it, and the child's worker crashing. The parent reads the child as `delegate_result` would and hands its model a **report**: the same text (`session:`, `status:`, the reply), under its own header (`[Delegate report, delivered by chi …; not your user's message. Tell your user what it found …]`), as an input message with `source: "delegate_report"`.
|
|
199
199
|
- Parent mid-turn: the report joins the turn at its next iteration boundary (a "delegate report" bubble in the web, `delegate report> ab12cd34 answered: …` in the attached terminal).
|
|
200
200
|
- Parent idle: it runs a turn for the report (a *wake turn*, `last_turn.origin: "delegate_report"`) within the worker's 5 s tick, or wakes a worker for it when the parent's has idle-exited. Several reports that wait together are one turn and one message. A stopped, archived or scratch parent, or one open in a `--no-shared` REPL, is not woken: the rings wait on disk and join the next turn it runs (the user's message first). A worker just started (`chi send` to a stopped parent) runs no wake turn in its first 2 s, so the message it was started for goes first.
|
|
201
|
-
- The parent's cursor per child (the newest reply it was given and the child as it was then) is in its `delegates.json`, shared with `delegate_result` and `wait: true`: a reply the model already had is never reported again, also after the parent's worker restarts. A ring is deleted only when the turn that took its report
|
|
202
|
-
- Limits: `session.max_wakes` (default `10`) wake turns in a row with no human input; past it the reports wait for the next message and the UIs show `N delegate reports waiting; they join your next message`. A wake turn that fails pauses wakes until the next human input; its reports' rings stay, so they join that input's turn, and
|
|
201
|
+
- The parent's cursor per child (the newest reply it was given and the child as it was then) is in its `delegates.json`, shared with `delegate_result` and `wait: true`: a reply the model already had is never reported again, also after the parent's worker restarts. A ring is deleted only when the turn that took its report stays in the conversation (completed, canceled, or failed but kept: one that got to tool steps, or a reminder or continue turn, which keep what they read); a turn rolled back (a failed one that got nowhere, a canceled continue turn) leaves it for the next one (and the report isn't handed back to the composer). So the model reads each report once, unless a later `!rollback` erases a kept turn (what it read goes with it).
|
|
202
|
+
- Limits: `session.max_wakes` (default `10`) wake turns in a row with no human input; past it the reports wait for the next message and the UIs show `N delegate reports waiting; they join your next message`. A wake turn that fails, or is stopped before the model call (a Stop, a `before_turn` hook's cancel), pauses wakes until the next human input; its reports' rings stay, so they join that input's turn, and a failure's note tells the model so. A continue offer waiting in the parent takes the reports into its continue turn instead.
|
|
203
203
|
- `session.delegate_reports`: `wake` (default), `queue` (no wake turns: reports join the parent's next turn) or `off` (no rings; only `delegate_result` asks, as before). Env `SAMAGOTCHI_SESSION_DELEGATE_REPORTS`, `SAMAGOTCHI_SESSION_MAX_WAKES`.
|
|
204
204
|
- The web's bell rings at a wake turn's end however short (nobody started it, so nobody watched it). The text a model writes about a report mid-turn, before its next tool call, stays visible after the report's bubble once the turn ends.
|
|
205
205
|
- Every child starts with the shipped `delegated` system memory preloaded (`preloaded_memory_names: ["system/delegated"]`; `--mute delegated` would hide it): put everything in the final reply, don't delegate further, don't write memories or change config, stay in the folder. Its system prompt also says `Delegated by session <parent id>`. The parent's own `--mute` list does not carry over.
|
data/docs/testing.md
CHANGED
|
@@ -71,3 +71,19 @@ strategies (freed tokens, wrongly forgotten outputs, one-step re-reads, re-prefi
|
|
|
71
71
|
model's answers to chi's offer, with its call rate and notes). It is opt-in: the suite
|
|
72
72
|
runs its code only on small synthetic sessions, never on yours. See
|
|
73
73
|
[internals/llm-context-bench.md](internals/llm-context-bench.md).
|
|
74
|
+
|
|
75
|
+
## The model notes report
|
|
76
|
+
|
|
77
|
+
`ruby script/model_notes_report.rb` reads a sessions folder (`--sessions DIR`, else chi's own) and groups the
|
|
78
|
+
sessions by model and by the model notes their prompt carried (the session file's `prompt_notes`, as
|
|
79
|
+
`name@digest`; `none` without notes, or in a file from before they were recorded). For each session and group it
|
|
80
|
+
prints the numbers a model note is meant to move: the place of the first edit (an `edit` or `write` call) and of the
|
|
81
|
+
first commit (an `execute` call that runs `git [options] commit`) among the tool calls, commits per 100 steps (model
|
|
82
|
+
requests), `execute` calls with a bare `&`, the longest run of calls with no edit, and what the session needed from
|
|
83
|
+
outside: Continues (the turns in its `analytics.json` that had no prompt of their own), steers (lines that reached a
|
|
84
|
+
running turn from the user, `chi send` or a parent, and a Continue's text), follow-up prompts and plugin nudges. A
|
|
85
|
+
group has the medians of the places and runs, commits per 100 steps pooled and the counts summed. `--help` defines
|
|
86
|
+
each column; `--model GLOB`, `--since DATE` and `--min-steps N` filter, `--json` gives JSON. It reads only and prints
|
|
87
|
+
no session text (ids, model names, note names and digests, dates, numbers), and the suite runs it only on small
|
|
88
|
+
synthetic sessions. It is how a note is judged on and off (`chi --mute model_notes_<name>`) for the same kind of
|
|
89
|
+
task: see [memory.md](memory.md) ("Model notes").
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: github-pr
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.3.0
|
|
4
4
|
scope: system
|
|
5
|
-
description: Attaches the branch's open GitHub pull request to a session as context (and any PR URL with chi context add or the web's "+ URL"); the agent gets a note when reviews, comments, checks or its state change; in the web, a path:line in an answer links to that line of the PR
|
|
5
|
+
description: Attaches the branch's open GitHub pull request to a session as context (and any PR URL with chi context add or the web's "+ URL"); the agent gets a note when reviews, comments, checks or its state change; in the web, a path:line in an answer links to that line of the PR; the auto_attach setting can offer the PR with a card first, or attach nothing
|
|
6
6
|
trust_level: reviewed
|
|
7
|
-
requires_chi: ">= 0.
|
|
7
|
+
requires_chi: ">= 0.43.0"
|
|
8
8
|
needs:
|
|
9
9
|
- command: gh
|
|
10
10
|
why: reads the pull request
|
|
11
11
|
hint: brew install gh, then gh auth login
|
|
12
12
|
plugin:
|
|
13
13
|
file: plugin.rb
|
|
14
|
-
sha256: sha256:
|
|
14
|
+
sha256: sha256:8c3b1b2de9112320d95d42d19b924a2a6bb0418715d3c78a24ac695c7b825487
|
|
15
15
|
scripts:
|
|
16
16
|
pr_context.rb: sha256:a883d2780e89c68c0d7b6d5e0fcb22c273f17bb167e79064e0e99db6bd2ee6f0
|
|
17
17
|
context_providers:
|