samagotchi 0.139.0 → 0.140.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/README.md +2 -1
  4. data/docs/architecture.md +6 -4
  5. data/docs/cli.md +4 -2
  6. data/docs/configuration.md +36 -2
  7. data/docs/context.md +185 -44
  8. data/docs/guardrails.md +11 -0
  9. data/docs/internals/background-tasks.md +1 -1
  10. data/docs/internals/engine.md +7 -1
  11. data/docs/internals/prompt-caching.md +10 -1
  12. data/docs/internals/tool-guardrails.md +1 -1
  13. data/docs/plugins.md +35 -17
  14. data/docs/sessions.md +3 -2
  15. data/lib/samagotchi/bridge/turn_accumulator.rb +4 -1
  16. data/lib/samagotchi/bundle_command.rb +21 -21
  17. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  18. data/lib/samagotchi/bundles/github-pr/manifest.yml +4 -4
  19. data/lib/samagotchi/bundles/github-pr/plugin.rb +72 -176
  20. data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
  21. data/lib/samagotchi/bundles/mcp/plugin.rb +8 -3
  22. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  23. data/lib/samagotchi/bundles/system/self_map.md +2 -2
  24. data/lib/samagotchi/cancellation_controller.rb +4 -4
  25. data/lib/samagotchi/config.rb +8 -0
  26. data/lib/samagotchi/context_candidate_choice.rb +154 -0
  27. data/lib/samagotchi/context_candidates.rb +259 -0
  28. data/lib/samagotchi/context_candidates_log.rb +90 -0
  29. data/lib/samagotchi/context_command.rb +33 -8
  30. data/lib/samagotchi/context_fetch.rb +2 -2
  31. data/lib/samagotchi/context_judge.rb +194 -0
  32. data/lib/samagotchi/context_report_command.rb +161 -0
  33. data/lib/samagotchi/context_sources.rb +67 -23
  34. data/lib/samagotchi/cut.rb +21 -0
  35. data/lib/samagotchi/cut_policy.rb +6 -5
  36. data/lib/samagotchi/engine.rb +189 -32
  37. data/lib/samagotchi/hook_routes.rb +3 -2
  38. data/lib/samagotchi/kernel_loop.rb +8 -6
  39. data/lib/samagotchi/launch_mode.rb +5 -2
  40. data/lib/samagotchi/llm/chat_loop.rb +3 -2
  41. data/lib/samagotchi/llm/openai_chat.rb +1 -1
  42. data/lib/samagotchi/memory_bundle/status.rb +13 -3
  43. data/lib/samagotchi/owner_prompt.rb +39 -0
  44. data/lib/samagotchi/plugin/api.rb +1 -0
  45. data/lib/samagotchi/plugin/attached_context.rb +91 -11
  46. data/lib/samagotchi/plugin/context.rb +7 -1
  47. data/lib/samagotchi/project_env.rb +16 -2
  48. data/lib/samagotchi/prompt_warmup.rb +9 -1
  49. data/lib/samagotchi/session_access/contract.rb +2 -0
  50. data/lib/samagotchi/session_access/local.rb +10 -0
  51. data/lib/samagotchi/session_access/remote.rb +14 -0
  52. data/lib/samagotchi/session_access.rb +5 -0
  53. data/lib/samagotchi/session_commands.rb +28 -5
  54. data/lib/samagotchi/side_judgments.rb +84 -0
  55. data/lib/samagotchi/steer_cut.rb +2 -1
  56. data/lib/samagotchi/terminal_ui.rb +15 -0
  57. data/lib/samagotchi/testing/boundary.rb +3 -1
  58. data/lib/samagotchi/testing/host.rb +4 -1
  59. data/lib/samagotchi/toolkit_report_command.rb +2 -1
  60. data/lib/samagotchi/toolkit_suggest.rb +3 -18
  61. data/lib/samagotchi/turn_note.rb +6 -6
  62. data/lib/samagotchi/version.rb +1 -1
  63. data/lib/samagotchi/web/app.rb +10 -3
  64. data/lib/samagotchi/web/auto_update.rb +8 -1
  65. data/lib/samagotchi/web/public/app.js +11 -3
  66. data/lib/samagotchi/web/public/composer.css +1 -1
  67. data/lib/samagotchi/web/public/index.html +10 -9
  68. data/lib/samagotchi/web/public/notifications.js +10 -1
  69. data/lib/samagotchi/web/public/revise_stack.js +6 -5
  70. data/lib/samagotchi/web/public/update_notice.js +21 -8
  71. data/lib/samagotchi/web/session_access_routes.rb +1 -1
  72. data/lib/samagotchi/web/session_hub.rb +5 -2
  73. data/lib/samagotchi/web/update_check.rb +14 -8
  74. data/lib/samagotchi/worker.rb +5 -0
  75. metadata +9 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4acefa9a1809c64422fa70d4cc0603318d37ed5405ba73729b6f1c6e98c79c54
4
- data.tar.gz: 5f4f9b8181e0490272402c570f6bd1c70f72e6432fc380a383b6ff5d83a3e6fb
3
+ metadata.gz: 74f14fd45c9fe7ee5c7ff6a7169a49b983309d7ab37a4af0530de063eb64a0ef
4
+ data.tar.gz: b7674ded1f9a41c094e0daf2220c2836d66e7df8f880d1cdecee4da566ff5b7b
5
5
  SHA512:
6
- metadata.gz: 0aaf6e6d8ee1cdfd0b3001beb44f884614cf45373a1cf8c0f566d7419407b01818ae3155bcfe368f485f657985fd5177aa897174567df9c0a7efcb8ba44b0504
7
- data.tar.gz: d53d479d947de0f63b78dfd5c67fecc41cf6cface32d6ffad6cb64a743fb9e0ba08dc5115746e6513d0bb7f11fac21b3c350215dbc9ba8544385b7606de19acf
6
+ metadata.gz: 877da3c7a446f711d3fe42e3f3388109f9c4f2cedde21f41292a48ea829904101982e68b697a5f6bf0729119df9c528361af4965e8e2c805357ddb49e19f6fe0
7
+ data.tar.gz: 7e77427d1782222f5876527e2095fc53e7e8d35c1c7b8d3a484c9bbb4640f482656d70fb9b948cc541a6b5c843c9764fd179131972786a3c7ed5f55971b67a64
data/CHANGELOG.md CHANGED
@@ -7,6 +7,65 @@ and commands may change between minor versions. How releases are made:
7
7
  [docs/releasing.md](docs/releasing.md).
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.140.0] - 2026-10-31
11
+
12
+ ### Added
13
+
14
+ - Candidates for attached context: a bundle proposes a source (`ctx.context.propose`, `candidates`, `withdraw`),
15
+ and chi attaches it when the session's owner mentions it; `chi context ls` and `/context` list them with their
16
+ state (proposed, attached, declined). See docs/context.md (Candidates).
17
+ - The candidate judge, audit only and opt-in (`context.candidates.mode: audit`; off by default): System 1 is asked
18
+ whether the session's work is about each proposed candidate, and the answers go to `context_candidates.jsonl`;
19
+ nothing is attached. `chi context report [--days N]` counts them against what you attached or declined. Jev
20
+ through OpenRouter works as the System 1 host. See docs/context.md (The judge).
21
+ - The candidate judge's on mode (`context.candidates.mode: on`): a candidate judged at `threshold` (0.8) or above
22
+ is attached quietly (its note comes before your next turn, never mid-turn; removing it declines it), one from
23
+ `offer_threshold` (0.6) gets a card once (Attach / Not here), lower ones stay proposed. `/context attach NAME` and
24
+ `/context decline NAME` choose a candidate yourself; `chi context report` counts the judge's attaches you removed
25
+ later. See docs/context.md (On mode).
26
+
27
+ ### Changed
28
+
29
+ - github-pr no longer attaches the branch's PR by itself: mention it (#N, the branch, the URL), or set
30
+ `auto_attach: attach` for the old behaviour. It proposes the PR as a candidate when the worker starts (a delegate
31
+ child too), and a prompt of yours that names it attaches it before that turn runs, its note in that turn.
32
+ `auto_attach: offer` (the card with Attach and Not here, `/pr-attach`, `/pr-decline`) is gone and reads as
33
+ `propose`, as does an unknown value. (github-pr 0.6.0)
34
+
35
+ ### Fixed
36
+
37
+ - chi web's update check no longer holds up the page, the API or the automatic install while it fetches from
38
+ RubyGems (~10 s): reading and saving the auto-update record take their own lock.
39
+ - Stopping chi web while the automatic update's preflight runs waits for it (up to 25 s) instead of abandoning it,
40
+ and a preflight that ends after the stop no longer restarts chi web.
41
+ - An automatic install with `update.restart: idle` no longer offers the "installed" toast's Restart buttons (a
42
+ failed run, `restart: never` or a manual install still do).
43
+
44
+ Update with `chi update` (the dev profile 0.7.4).
45
+
46
+ ## [0.139.1] - 2026-10-31
47
+
48
+ ### Fixed
49
+
50
+ - A project's mise.toml can no longer set `TMPDIR` for the session's tools: the guardrails judge `$TMPDIR` paths
51
+ by chi's own, so a write there could have been taken for a tmp write while it landed in the project.
52
+ - MCP servers (mcp 0.7.4) and context sources start like `execute`'s commands: without chi's own Bundler setup
53
+ (`RUBYOPT=-rbundler/setup`, `BUNDLE_GEMFILE`) or the project's `RUBYOPT`/`RUBYLIB`/Bundler variables, so a
54
+ chi run from a checkout no longer hands them its Gemfile.
55
+ - A worker that never starts its Bridge (`chi --attach`, the web's wake) now points at `chi home doctor`, which names
56
+ a worker that failed to boot the chi home's chi.
57
+ - The system bundle's self-knowledge names `execute`'s early hand-over to a task (a message for the turn), not only
58
+ its time limit.
59
+ - Web: a Revise stack's version chip names the version you are looking at ("v1 of 3"), and an earlier version you
60
+ switched to has no Copy link (it linked the newest turn with the earlier version's quote).
61
+ - Web: the bell is one control in each place: a toggle in a browser tab, the notifications sheet's opener in the
62
+ Home Screen app.
63
+ - Web: a markdown report in the stage's one-line rows is exactly a plain row's height.
64
+ - Web: on a phone, Revise, the stack switcher, Copy, the composer's Revise ✕, a fold's size and the activity
65
+ summaries no longer stay tinted after a tap.
66
+
67
+ Update with `chi update` (system, mcp 0.7.4).
68
+
10
69
  ## [0.139.0] - 2026-10-30
11
70
 
12
71
  ### Added
@@ -5303,7 +5362,9 @@ and long-lived sessions.
5303
5362
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5304
5363
  hotkey panel that send selected text or the clipboard to your sessions.
5305
5364
 
5306
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.139.0...HEAD
5365
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.140.0...HEAD
5366
+ [0.140.0]: https://github.com/dm1try/samagotchi/compare/v0.139.1...v0.140.0
5367
+ [0.139.1]: https://github.com/dm1try/samagotchi/compare/v0.139.0...v0.139.1
5307
5368
  [0.139.0]: https://github.com/dm1try/samagotchi/compare/v0.138.0...v0.139.0
5308
5369
  [0.138.0]: https://github.com/dm1try/samagotchi/compare/v0.137.0...v0.138.0
5309
5370
  [0.137.0]: https://github.com/dm1try/samagotchi/compare/v0.136.1...v0.137.0
data/README.md CHANGED
@@ -176,7 +176,8 @@ stopped on, which the agent answers with `chi answer`. See
176
176
  `chi context` attaches outside text that keeps changing to a session: a
177
177
  command chi runs every few minutes, text you push, or a URL. With the
178
178
  `github-pr` bundle (in `dev`; needs `gh`) a session on a branch with an open
179
- pull request gets it attached by itself. The agent gets a short note when it
179
+ pull request gets it proposed, and attached once you mention it (#N, the branch
180
+ or its URL). The agent gets a short note when it
180
181
  changes (a summary of what changed, never the comments' words) and reads the
181
182
  text with `context_read` when your request is about it; a review requesting
182
183
  changes, red checks or a merge can start a turn in a live, idle session that
data/docs/architecture.md CHANGED
@@ -606,8 +606,9 @@ reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade
606
606
  `installed`, the newest chi on this machine (`InstalledVersions`: the highest `samagotchi-<v>.gemspec`
607
607
  in RubyGems' specification dirs, or a checkout's `version.rb` on disk; looked at again on each full
608
608
  probe, every 10 s). A `chi` frame and the `GET /api/events` snapshot carry the full state
609
- `{version, installed, available, update_blocked, auto}` (`auto`: `update.auto`, `off` or `install`, which the page's
610
- "available" wording follows); `/api/info` and the session-command wake response carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
609
+ `{version, installed, available, update_blocked, auto, restart}` (`auto`: `update.auto`, `off` or `install`, which the page's
610
+ "available" wording follows; `restart`: `update.restart`, with which the page offers no Restart toast for the
611
+ version an automatic install's frames name unless it is `never` or the run failed: that run restarts by itself); `/api/info` and the session-command wake response carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
611
612
  Monitor, but fetching never holds it.
612
613
 
613
614
  `Web::UpdateCheck` runs its own thread beside the hub. When `update.notify: on` (the default) or
@@ -663,7 +664,8 @@ the worker's own atomic refusal still applies), the left ones retried each step
663
664
  turn running, `Server.restart_preflight` (the restart's command with `--version`, 20 s, killed on a timeout; it must
664
665
  print a chi at least the release) and, if it passes, the record's `phase: web` is written **before**
665
666
  `UpdateOps#restart_web`. A failed preflight, or a refused restart, publishes `restart_failed` and ends the run
666
- (`failed`). The restarted chi web's AutoUpdate (first step after 5 s) finds `phase: web`, checks it runs the
667
+ (`failed`). `AutoUpdate#stop` waits `STOP_WAIT` (25 s, longer than the preflight's timeout) for its thread, and a
668
+ preflight that ends after a stop leads to nothing (no `web`, no exec): the next start takes the run up again. The restarted chi web's AutoUpdate (first step after 5 s) finds `phase: web`, checks it runs the
667
669
  release, counts the workers once more and publishes `updated`, then `done`. Results reach the phone as `chi_notice`
668
670
  hub events (`SessionHub#publish_notice`), see [internals/web-push.md](internals/web-push.md).
669
671
 
@@ -881,7 +883,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
881
883
  | Worker link | `worker_link.rb` (`WorkerLink`: `Live`, `Starting`, `Leaving`, `Skew`, `Stale`, `None`), `worker_sidecar.rb` (`WorkerSidecar`) | Whether a session's worker can be reached and the client for it: every Bridge client asks `WorkerLink.resolve` / `.client` / `.await` (*Worker link* above); `WorkerSidecar` is the `bridge.json` it reads. |
882
884
  | Session access | `session_access.rb` (`SessionAccess`: `NewSession`, `SetupWords`, `WaitStart`, `SessionView`, `Created`, `ContextEntry`, `ContextPush`, the errors `RemoteFailed`, `RemoteTooOld`, `Refused`, `BadRequest`), `session_access/local.rb` (`SessionAccess::Local`), `session_access/remote.rb` (`SessionAccess::Remote`), `session_access/http_transport.rb`, `session_access/web_bridge_client.rb` (`SessionAccess::WebBridgeClient`, a remote attach's worker through chi web), `session_access/dispatch.rb` (`SessionAccess::Dispatch`), `session_access/contract.rb` (`SessionAccess::Contract`), `remotes.rb` (`Remotes`), `remote_command.rb` (`chi remote`), `web/session_access_routes.rb` (`Web::SessionAccessRoutes`) | The seam between the session commands and where the sessions live: `Local` is this machine's (the `SessionManager`, `ReplyWait`, `NoteDelivery`, `ContextSources` and Bridge calls the commands made); `Remote` is another machine's, the same methods as requests to its chi web's routes of the same name (Bearer token, `HTTPTransport`), results as Local's value objects and refusals as the classes Local's callees raise, built from the fields chi web sends; waits and prefixes run on the server, a wait asked again per 30 s slice. `Dispatch` is bin/chi's front for `--remote NAME` / `CHI_REMOTE` (taken off before the subcommand lookup): it refuses what the contract table lists `local_only` (exit 2, one message) and runs the command with a `Remote` on the `Remotes` entry (`$XDG_CONFIG_HOME/samagotchi/remotes.yml`, 0600). chi web's routes answer from `Local` (*chi web's routes for the session commands* above). Checks that read this machine's config or files (vision, parent answers, the cwd's project, `SAMAGOTCHI_PARENT_SESSION`) stay in the commands and are skipped under a `Remote`, where chi web's create, turn and wait routes run the vision, parent-answers and busy checks with the server's config and the worker checks a parent's answer. See [internals/remote-sessions.md](internals/remote-sessions.md). |
883
885
  | Idle work | `idle_scheduler.rb` (`IdleScheduler`), `idle_recap.rb` (`IdleRecap`), `idle_reminders.rb` | One poller for an idle session's recap and reminders. |
884
- | Attached context | `context_sources.rb` (`ContextSources`), `context_poller.rb`, `context_absorber.rb`, `peer_sources.rb` (`PeerSources`: the sources peer bundles declare) | External sources a session keeps fresh, and the peer loop's wakes ([context.md](context.md)). |
886
+ | Attached context | `context_sources.rb` (`ContextSources`), `context_poller.rb`, `context_absorber.rb`, `context_candidates.rb` (`ContextCandidates`: proposed sources, attached on the owner's mention at a turn's start, before the worker's absorb, or in the REPL's turn; `owner_prompt.rb` decides whose words count), `peer_sources.rb` (`PeerSources`: the sources peer bundles declare) | External sources a session keeps fresh, and the peer loop's wakes ([context.md](context.md)). |
885
887
  | LLM context strategies | `llm_context_strategy.rb`, `llm_context_view.rb`, `llm_context_stale.rb`, `llm_context_forget.rb` | What a request sends of the history (none, stale, forget) ([internals/llm-context-forget.md](internals/llm-context-forget.md)). |
886
888
  | Session chains | `session_continue.rb` (`SessionContinue`), `session_chain.rb` (`SessionChain`) | A session that continues an earlier one. `SessionChain.opener_for` gives the text web `continue →` prefills in the new link: the previous link's own first typed message when that link continues one itself, else nil (the 201's `opener`; not saved) ([sessions.md](sessions.md)). |
887
889
  | Review sessions | `review_start.rb` (`ReviewStart`), `session_review.rb` (`SessionReview`, the session's `review` field), `review_command.rb` (`ReviewCommand`, `chi review`), `review_apply.rb` (`ReviewApply`) | A session made to be shared: idle, its subjects project files checked by `Guardrails::ReviewPaths`, a fork of a working session from its recap (`--from`); Apply quotes a participant's review turn into the owner's composer ([sessions.md](sessions.md#review-sessions), [internals/participants.md](internals/participants.md)). |
data/docs/cli.md CHANGED
@@ -22,6 +22,7 @@
22
22
  - `chi note [--source NAME] [-m TEXT] (ID|PREFIX)... | --all` — add a context note (TEXT or stdin) to sessions: background the model sees on its next turn; it starts no turn (see [Sessions: Context notes](sessions.md#context-notes))
23
23
  - `chi broadcast [-m TEXT] [--all] [--dry-run]`, `chi broadcast log [--last N] [--format json]`, `chi broadcast deliver BROADCAST_ID (ID|PREFIX)...` — share one piece of information (TEXT or stdin) with every session it may concern, as a context note from `broadcast` (see [Broadcast](broadcast.md))
24
24
  - `chi context <add|push|ls|show|refresh|rm|mute|unmute> …` — attach live external text to sessions (a command chi runs every so often, or text pushed in); the model gets a short note when it changes and reads it with `context_read`; `chi context add <URL>` attaches a GitHub PR with the github-pr bundle (see [Attached context](context.md))
25
+ - `chi context report [--days N]` — the candidate judge's counts from `context_candidates.jsonl`: candidates proposed, mentioned, attached or declined by you; the judge's asks; what it would attach per threshold, and how that agrees with what you did (this machine only; see [The log and the report](context.md#the-log-and-the-report))
25
26
  - `chi send [-m TEXT] [--image PATH]... [--cut | --queue] (ID|PREFIX)...` — send a message to sessions as if typed there: a turn starts (or a running one takes it at its next step; `--cut` asks it to cut long thinking for it; `--queue` runs it as a turn of its own after the running one, and `--queue --wait` waits for that turn's own answer); piped stdin goes above `-m` as quoted context, and `--image` attaches images (see [Sessions: Sending a message](sessions.md#sending-a-message)); `--new` starts a session with it instead (`--new --continues ID|last:ID` the next link of that session's chain, see [Session chains](sessions.md#session-chains)), and `--wait` prints the answer (`--wait ID` with no message waits for the next reply without sending — to an idle session whose last turn already ended, that ended turn's reply or end comes back; `--wait --turn T ID` with no message waits for exactly turn T, whether running, queued or last ended; `--format json` includes its `turn_id` when known, for waiting again after exit 3 or 4; exit 3 a question waits, its options on stderr; exit 4 `--timeout` passed with the turn still running; see [Starting a session](sessions.md#starting-a-session))
26
27
  - `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`). What a parent may pick follows `guardrails.parent_answers`, or the session's own `parent_answers` when stricter (`soft`, the default: a deny, a plain answer, Continue and "Allow once"; `standard`: no allow; `hard`: no Continue either), never a wider scope or an allow on chi's own config; a refused pick exits 1 with what to answer instead (a convention, not a security boundary; see [Guardrails](guardrails.md#answers-from-a-parent-agent))
27
28
  - `chi remote add NAME URL [--token-file F | --token-stdin]`, `chi remote ls`, `chi remote rm NAME` — the other machines whose chi web the session commands reach with `--remote NAME` (see [Remote sessions](#remote-sessions))
@@ -901,7 +902,8 @@ REPL alike:
901
902
  `cutting in…`, then `steered`. A phone shows the short form (`↳ after this
902
903
  thinking · 0:42`) with `cut in` and `queue` chips.
903
904
  - Commands answer, wait or are refused, by what they do:
904
- - At once, beside the turn: `/stats`, `/recap` (and `/recap on|off|default`), `/help`, `/context`, a
905
+ - At once, beside the turn: `/stats`, `/recap` (and `/recap on|off|default`), `/help`, `/context` (and
906
+ `/context attach|decline NAME`: a candidate's note still waits for the next turn), a
905
907
  plugin's anytime command (`/btw`), and the commands that only show
906
908
  something: `/models` (with a filter too), and `/model`, `/llm-context`,
907
909
  `/thinking`, `/reasoning-history` and `/guardrails` alone.
@@ -1061,7 +1063,7 @@ moves into the later stub. The newest version has a chip, "v3 · 2 earlier ▾",
1061
1063
  listing every version by its prompt; pick one to jump to its stub. Before
1062
1064
  that chip, a "‹ 3/3 ›" switcher reads an earlier version's answer in the
1063
1065
  newest one's place (its left edge dashed while it shows; Copy copies the
1064
- version on view); › back to the newest. Switching only changes what this
1066
+ version on view, and the chip names it, "v1 of 3"); › back to the newest. Switching only changes what this
1065
1067
  page shows, and a reload shows the newest again. Revise
1066
1068
  again on the newest version adds to the stack. Stacks are session data (the
1067
1069
  stamp is on the Revise prompt, see [sessions.md](sessions.md), Revise
@@ -1402,7 +1402,9 @@ launches it. It is recorded in `~/.config/samagotchi/home.json` (`$XDG_CONFIG_HO
1402
1402
  `home_older_than_spawner`), but chi web's restart runs the newest chi it knows outside the home instead
1403
1403
  (`restart_home_older`): its flags may be ones the older chi doesn't know.
1404
1404
  - **A home that still fails at boot** (a broken install): the worker writes one `boot_failed` line to the debug log
1405
- (`log.file`) and exits, rather than leave the spawner waiting; `chi home doctor` reports the newest one.
1405
+ (`log.file`) and exits, rather than leave the spawner waiting; `chi home doctor` reports the newest one, and an
1406
+ attach whose worker never starts its Bridge says so ("… did not start its Bridge in time (chi home doctor shows a
1407
+ worker that failed to boot)").
1406
1408
  - Each machine has its own home (a remote's is that machine's).
1407
1409
 
1408
1410
  ### Checking it: chi home doctor
@@ -1756,6 +1758,33 @@ its own). The first target is a long session such as a day-long coordinator, whe
1756
1758
  turn that runs a layer or whose session set its strategy) and the web's chip. A woken worker's status line counts
1757
1759
  its saved context against the session's budget.
1758
1760
 
1761
+ ## The candidate judge
1762
+
1763
+ `context.candidates.mode: audit` asks System 1 whether a session's work is about each candidate a bundle proposed
1764
+ (see [The judge](context.md#the-judge)); `on` also acts on the answer: attaches at `threshold`, a card from
1765
+ `offer_threshold` ([On mode](context.md#on-mode)). It uses `guardrails.system_one.host` and `.model`, but its own mode: System
1766
+ 1's `mode` (approvals) can stay `off`. Jev, the hosted judgment model, is served through OpenRouter
1767
+ (`/api/v1/systemone`), so OpenRouter can be the System 1 host:
1768
+
1769
+ ```yaml
1770
+ hosts:
1771
+ openrouter:
1772
+ url: https://openrouter.ai/api/v1
1773
+ api: openai
1774
+ api_key_env: OPENROUTER_API_KEY
1775
+ guardrails:
1776
+ system_one:
1777
+ host: openrouter
1778
+ model: typesafe/jev-1.14
1779
+ context:
1780
+ candidates:
1781
+ mode: audit
1782
+ ```
1783
+
1784
+ A remote host is asked beside the turn. A System 1 host that is the model's own local server is asked after the
1785
+ turn's last request, and the turn-end warm-up follows them, re-warming the prefix they evicted (see
1786
+ [prompt caching](internals/prompt-caching.md)).
1787
+
1759
1788
  ## Opening files from chi web
1760
1789
 
1761
1790
  In `chi web`, a file tool's row (read, edit, write) and the lines of its diff are refs to that file. They look like
@@ -2080,7 +2109,8 @@ Both are read when chi web starts (restart it to apply a change). What it does,
2080
2109
  the command to run). `chi update` also merges the shipped bundles that live sessions read, hence "no turn anywhere".
2081
2110
  2. **Installs** exactly the release the notice named, as the **Update** button does (the system bundle and shipped
2082
2111
  bundles are merged three ways; files you edited are kept and counted in the push). The page's sticky toast says
2083
- "Automatic chi update in progress" and its sheet shows the rows. A failed install is tried **once per version**:
2112
+ "Automatic chi update in progress" and its sheet shows the rows; with `update.restart: idle` no "installed"
2113
+ toast with **Restart** buttons follows (the run restarts by itself). A failed install is tried **once per version**:
2084
2114
  it pushes the failed row's reason and stays quiet until a newer release (or you, with the button) tries again.
2085
2115
  3. **Restarts** (`update.restart: idle`). Stale workers first, only those whose session is idle and untouched for
2086
2116
  10 minutes (so nobody who is about to type loses their worker), and each worker still refuses by itself when
@@ -2188,6 +2218,10 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2188
2218
  | `context.every_seconds` | `300` | | How often an attached context source's command runs when it has no `--every` (seconds, at least 30). See [Attached context](context.md). |
2189
2219
  | `context.wake` | `true` | yes | A source whose update says `wake: true` may start a turn in a live, idle session (one per source per 10 minutes, a peer source up to `context.peer_turns` in a row, within `session.max_wakes`); so may a session's pushed source's first push. `false`: updates wait as notes for the next turn. See [Waking](context.md#waking). |
2190
2220
  | `context.peer_turns` | `3` | yes | Wake turns in a row a peer agent's answers may start (a peer bundle's outbox source, such as `codex-outbox`), with no message typed in the web or a TUI in between (`chi send` doesn't count); within `session.max_wakes`. Then answers wait as notes, and the UIs hear it once. An answer whose summary ends its verdicts with `VERDICT OK` wakes once to close the round, counted toward `session.max_wakes` only. `0`: a peer source wakes like any other. See [Waking](context.md#waking). |
2221
+ | `context.candidates.mode` | `off` | | The candidate judge: `off` (the default: no conversation text goes to a System 1 host), `audit` (asks System 1 whether the session's work is about each proposed candidate and logs it in `context_candidates.jsonl`; attaches nothing), `on` (the same asks, acted on: attached at `threshold`, a card from `offer_threshold`; see [On mode](context.md#on-mode)). Its host and model are `guardrails.system_one.host` / `.model`. config.yml only. See [The judge](context.md#the-judge). |
2222
+ | `context.candidates.threshold` | `0.8` | | The probability at which the judge attaches a candidate in on mode (a judged line's `would: attach`). config.yml only. |
2223
+ | `context.candidates.offer_threshold` | `0.6` | | The probability from which on mode shows the candidate's card, once (`would: offer`, below `threshold`). config.yml only. |
2224
+ | `context.candidates.reask_after_turns` | `3` | | Owner prompts after a judged ask before the judge's one re-ask of that candidate. config.yml only. |
2191
2225
  | `toolkit.default` | `full` | | The toolkit a new session is stored on: the tools its prompts declare (`full`, `lean`, `child`, one of `toolkit.kits` or a bundle's). `chi --toolkit` picks one per session. See "Toolkits". |
2192
2226
  | `toolkit.child` | `full` | | A delegate child's toolkit, unless its `delegate` call names one (`toolkit:`). See "Toolkits". |
2193
2227
  | `toolkit.suggest.mode` | `off` | | `audit` asks System 1 for a toolkit suggestion without changing the session; `on` waits for it (up to `guardrails.system_one.timeout_ms`) on an eligible turn and starts the session on a confidently suggested kit (Toolkits, On mode). The System 1 host, model and timeout come from `guardrails.system_one.*`. config.yml only. |
data/docs/context.md CHANGED
@@ -105,7 +105,9 @@ and `-`, up to 40 characters, starting with a letter or digit
105
105
  (`subscriptions` is taken).
106
106
 
107
107
  In a session, `/context` lists what is attached (as `context_read` does
108
- with no name).
108
+ with no name), then the session's [candidates](#candidates). `chi context ls`
109
+ lists them too (`--format json`: rows with `"candidate": true` and their
110
+ `status`), from another machine's chi web as well.
109
111
 
110
112
  ## Recipes
111
113
 
@@ -311,6 +313,157 @@ lower.) Every other source keeps the line above. `context.peer_turns: 0` turns
311
313
  the loop off: a peer source then wakes like any other. `context_read` names a
312
314
  peer source in its header (`Peer: your peer agent Codex's answers …`).
313
315
 
316
+ ## Candidates
317
+
318
+ A bundle can **propose** a source instead of attaching it
319
+ ([`ctx.context.propose`](plugins.md#attached-context-ctxcontext)): a
320
+ candidate, with a title and the words that name it (its *mentions*). Nothing
321
+ reaches the agent until you name it in one of your own prompts; then chi
322
+ attaches it before that turn runs, gives it one first fetch (up to 10
323
+ seconds), and the turn starts with its attached note, whose why names what
324
+ you wrote ("Why: you mentioned #412"). A source whose first text takes longer
325
+ gets its note with a later turn.
326
+
327
+ How a mention counts:
328
+
329
+ - whole words, any case;
330
+ - `#N` only for N of 10 or more, or right after "PR" or "pull" ("PR #4");
331
+ - a branch name only when it is at least 6 characters and holds a `/`, `-`
332
+ or `_` (`feat/login`, not `web`);
333
+ - a URL exactly as proposed (`…/pull/42/files` is another URL);
334
+ - nothing inside a fenced code block: a pasted log's `#412` isn't you asking.
335
+
336
+ Only the session's owner counts: a participant's message, a steer, a note or
337
+ a reminder never attaches anything, and a review session gets no candidates.
338
+ A bundle usually proposes from an init task that finishes while your first
339
+ turn runs; when it does, the prompts you already wrote count too, and a hit
340
+ is attached at once (its note comes with your next turn).
341
+
342
+ A candidate's state is never stored: it is *attached* while a source of its
343
+ name (or its URL) is, *declined* once you removed that source from the
344
+ session or answered its card "Not here" (it isn't proposed, judged or
345
+ attached again), else *proposed*. `chi context ls` and `/context` list them
346
+ under the sources:
347
+
348
+ ```
349
+ $ chi context ls
350
+ no attached context
351
+ candidates (attached when the session's owner mentions one):
352
+ pr-412 proposed #412 Fix the login redirect
353
+ ```
354
+
355
+ You can also choose yourself: `/context attach NAME` attaches a proposed
356
+ candidate (its why: "you attached it"), `/context decline NAME` declines it.
357
+ Both are the session owner's (a participant can't send a `/` line, and a
358
+ review session refuses them); an unknown name, or one attached or declined
359
+ already, is an error that says so. They are what the judge's card's buttons
360
+ run (see [On mode](#on-mode)).
361
+
362
+ Forks and continues start without candidates: the bundle proposes again in
363
+ the new session.
364
+
365
+ ### The judge
366
+
367
+ A mention is the only thing that attaches a candidate by itself. With
368
+ `context.candidates.mode: audit` chi also asks [System 1](guardrails.md#system-1-experimental)
369
+ about each proposed candidate — "Is this session's work about the
370
+ candidate?" — and logs the answer, so you can see how it would do before
371
+ anything depends on it; `audit` attaches nothing. With `on` the answer acts
372
+ ([On mode](#on-mode)). The default is `off`: no conversation text goes to a System 1 host unless you
373
+ turn this on, whatever `guardrails.system_one` says. The host and model are
374
+ `guardrails.system_one.host` / `.model` (Jev through OpenRouter: see
375
+ [The candidate judge](configuration.md#the-candidate-judge)).
376
+
377
+ ```yaml
378
+ context:
379
+ candidates:
380
+ mode: audit # off (default) | audit | on
381
+ threshold: 0.8 # on: attach at or above this
382
+ offer_threshold: 0.6 # on: a card from this, below threshold
383
+ reask_after_turns: 3 # owner prompts before the one re-ask
384
+ ```
385
+
386
+ What it asks, and when:
387
+
388
+ - **One ask per candidate**, at the start of each of your own turns (never a
389
+ participant's, never in a review session or on a Continue), after the
390
+ toolkit suggestion. A candidate proposed while your turn runs (github-pr's
391
+ usual case) is asked about right then, on your prompts so far.
392
+ - **What it sees:** the candidate's title (and description, when the bundle
393
+ gave one) and your own prompts: the first one with a task in it and the
394
+ latest, each cut at 1500 characters. Never the branch, the checkout or the
395
+ source's "why". For a delegate child, also the titles of what its parent
396
+ session has attached, and the question says the first message is the
397
+ parent's request.
398
+ - **Has a task been stated yet?** The same ask has a second question; below
399
+ 0.5 the candidate stays unjudged and is asked again at your next prompt
400
+ (a first "hi" doesn't count against it).
401
+ - **After a judged ask:** one re-ask once `reask_after_turns` more prompts of
402
+ yours came, with the conversation grown; then it is done.
403
+ - **Where it runs:** on a remote System 1 host (Jev) beside your turn; on the
404
+ model's own local server (System 1 on the same Splash) after your turn
405
+ ends, so it never competes with the turn's prefill; the turn-end warm-up
406
+ waits for those asks and restores the cached prompt they pushed out. One
407
+ session's asks run one batch at a time.
408
+
409
+ ### On mode
410
+
411
+ With `context.candidates.mode: on` the judge's answer acts:
412
+
413
+ - **p ≥ `threshold` (0.8): attached quietly.** The source goes into the
414
+ session's own folder, its why "it looks related to your task" (the agent
415
+ never sees a probability), and it shows as a chip like any attached source.
416
+ Its note reaches the agent only at the next between-turn delivery: an
417
+ answer that comes while your turn runs waits for that turn to end, so the
418
+ running turn's history, cached prompt and reasoning stay as they were.
419
+ Removing it — the chip's ×, or `chi context rm NAME` — declines it: it is
420
+ never proposed or judged again in that session.
421
+ - **`offer_threshold` (0.6) ≤ p < `threshold`: one card**, from `context`,
422
+ with the candidate's title and two buttons: **Attach** (`/context attach
423
+ NAME`) and **Not here** (`/context decline NAME`). The card shows once per
424
+ candidate, also across a worker restart (a `carded` line in the log
425
+ remembers it), and never inside a running turn: an answer that comes
426
+ mid-turn shows its card when the turn ends. In the terminal the card lists
427
+ the two commands to type. Answering it replaces the card with what
428
+ happened; a later re-ask at or above the threshold attaches it and closes
429
+ the card.
430
+ - **Below `offer_threshold`: nothing.** The candidate stays proposed (a
431
+ mention still attaches it).
432
+
433
+ ### The log and the report
434
+
435
+ Everything about candidates goes to `context_candidates.jsonl` in the state
436
+ folder (0600, local; written in every mode, `off` too, since nothing in it
437
+ leaves the machine): `proposed`, `mentioned`, `judged` (the probability `p`,
438
+ the task-stated probability `task_p`, `stated`, `would`: attach / offer /
439
+ none at the thresholds then, with your prompts cut to 200 characters),
440
+ `judge_attached` (on mode's attach, with `p`), `carded` (on mode's card, with
441
+ `p`), `attached` / `declined` with `by: "command"` (`/context attach|decline`,
442
+ the card's buttons), `attached` / `declined` / `detached` with `by: "user"`
443
+ (what you did otherwise, seen at your next turn's start; removing a source
444
+ declines it) and `withdrawn`.
445
+
446
+ `chi context report [--days N]` counts it, with no prompts in the output
447
+ (this machine only):
448
+
449
+ ```
450
+ $ chi context report
451
+ chi context report: the last 14 days, 12 sessions (mode audit)
452
+ candidates: proposed 14 · mentioned 3 · attached by you 1 · declined 2 · detached 0 · withdrawn 0
453
+ on mode: judge attached 0 · you removed 0 of them later · carded 0 · /context attach 0 · /context decline 0 (the card's buttons)
454
+ judge: 19 asks · judged 15 · no task yet 4 · 11 candidates judged
455
+ would attach (each candidate's latest judged ask):
456
+ ≥ 0.5 5
457
+ ≥ 0.6 4 offer_threshold
458
+ ≥ 0.7 3
459
+ ≥ 0.8 3 threshold
460
+ ≥ 0.9 1
461
+ against your own choice (6 judged candidates you attached, mentioned or declined), at 0.8: agree 5 · would attach what you declined 0 · would miss what you attached 1
462
+ ```
463
+
464
+ The on mode line's "you removed N of them later" is the judge's false
465
+ positives: sources it attached that you then took off.
466
+
314
467
  ## GitHub PRs: the github-pr bundle
315
468
 
316
469
  ```sh
@@ -320,13 +473,19 @@ chi bundle install github-pr # or the dev profile: chi bundle install dev
320
473
  It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
321
474
 
322
475
  - When a session's worker starts on a branch with an open pull request, the
323
- bundle attaches it as `pr-<number>` (a session source: "branch feat/x has
324
- open PR #42"). Not in a `chi scratch` session or a delegate child, and
325
- quietly nothing without `gh`, a login, a repository or an open PR. Once
326
- you remove it (`chi context rm`, the chip's detach) it stays removed:
327
- the next start doesn't bring it back; `chi context add <PR URL>` does.
476
+ bundle proposes it as a [candidate](#candidates) `pr-<number>`, titled
477
+ "#42 <the PR's title>": nothing reaches the agent until you mention it
478
+ in a prompt (`#42`, the branch's name, or the PR's URL). Then it is
479
+ attached in that same turn ("Why: you mentioned #42"). Not in a `chi
480
+ scratch` session or a review session, and quietly nothing without `gh`, a
481
+ login, a repository or an open PR. A delegate child gets the proposal too
482
+ (its task may name the PR). At a turn's start more than 5 minutes after
483
+ its last look, the bundle looks again, beside the turn (a branch switched
484
+ mid-session). Once you remove an attached PR (`chi context rm`, the chip's
485
+ detach) it stays removed: it isn't proposed or attached again; `chi context
486
+ add <PR URL>` brings it back.
328
487
  - `chi context add <PR URL>` attaches any PR.
329
- - `auto_attach` (below) can make it ask first, or attach nothing.
488
+ - `auto_attach` (below) can make it attach the PR at once, or do nothing.
330
489
  - The text: title, state, branch, description, reviews, comments (oldest
331
490
  first) and checks, every 5 minutes.
332
491
  - The summary says what changed in **counts, authors and states only**
@@ -336,50 +495,28 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
336
495
  - It wakes for a review requesting changes, checks turning red, and the PR
337
496
  merged or closed. Comments alone never wake.
338
497
 
339
- ### Attach, offer or off
498
+ ### Propose, attach or off
340
499
 
341
500
  ```yaml
342
501
  bundles:
343
502
  github-pr:
344
- auto_attach: offer # attach (the default) | offer | off
503
+ auto_attach: propose # propose (the default) | attach | off
345
504
  ```
346
505
 
347
- - `attach`: the branch's open PR is attached when the worker starts, as
348
- above.
349
- - `offer`: nothing is attached; a card asks instead, once per session per
350
- PR: **PR #42 for branch feat/x**, the PR's title and "Not attached: the
351
- agent gets nothing until you attach it.", with **Attach** and **Not
352
- here**. The terminal shows them as `→ /pr-attach 42` and
353
- `→ /pr-decline 42`, to type. Attach attaches it as auto-attach would
354
- (even after Not here: a click is your choice); Not here marks it removed
355
- from the session, so `attach` mode won't attach it there either. Either
356
- turns the card into a one-line notice. Not here on a PR that is attached
357
- already changes nothing and says so: `chi context rm pr-42` removes it. A worker restart (idle exit,
358
- resume) doesn't ask again; once the card has scrolled out of the last 20,
359
- `/pr-attach 42` or `chi context add <PR URL>` still attaches it. `/pr-attach` and
360
- `/pr-decline` act only on a PR offered in that session.
361
- - `off`: nothing is attached or offered. `chi context add <PR URL>` and line
362
- links work as before.
506
+ - `propose`: the branch's open PR is a candidate, attached when you mention
507
+ it, as above.
508
+ - `attach`: it is attached when the worker starts, mention or not (the
509
+ default before candidates; not in a delegate child).
510
+ - `off`: nothing is proposed or attached. `chi context add <PR URL>` and
511
+ line links work as before.
363
512
 
364
513
  `true` and `false` (YAML reads a bare `on`/`off` as those) mean `attach` and
365
- `off`; an unknown value is `attach`, with a warning in the log. The setting
366
- is read when a worker starts.
367
-
368
- A web chat is created with its first message, so in `offer` mode the card
369
- arrives while that first turn runs: the first answer comes without the PR,
370
- and Attach delivers it before the next turn. An offered PR that isn't
371
- attached gets no line links (unless your message names its URL).
372
-
373
- In `offer` mode the bundle keeps a local log,
374
- `$XDG_STATE_HOME/samagotchi/plugins/github-pr/offers.ndjson`, to find out
375
- later when an offer is wanted: one JSON line per event, `offered` (session,
376
- project root, working directory, branch, PR URL, main checkout or worktree,
377
- model), `attached` and `declined` (with the seconds since the offer), and
378
- `first_prompt` (the first 160 characters of every session's first message,
379
- offered or not; a fork's first after the conversation it started from, once;
380
- not scratch sessions or delegate children). Over 1 MiB it is
381
- renamed to `offers.ndjson.1` (one old file is kept). Only you can read it
382
- (0600), and nothing leaves your machine.
514
+ `off`. `offer`, which once posted a card with Attach and Not here, now reads
515
+ as `propose`, and so does an unknown value, each with a warning in the log.
516
+ The setting is read when the bundle looks.
517
+
518
+ A proposed PR that isn't attached gets no line links (unless your message
519
+ names its URL).
383
520
 
384
521
  ### Line links
385
522
 
@@ -468,8 +605,12 @@ sessions/<id>/<name>.json, …snapshot.json a session's own
468
605
  sessions/<id>/subscriptions.json what the session has seen and read (its worker writes it)
469
606
  sessions/<id>/muted/<name> a project source this session mutes
470
607
  sessions/<id>/declined/<name> a source you removed (its hint inside): auto-attach skips it
471
- sessions/<id>/offered/<name> a source a plugin offered here ({hint, why, at}): it isn't offered again
608
+ sessions/<id>/candidates/<name>.json a source a plugin proposed here ({url or cmd, title, describe, mentions, proposed_by, at}; no status)
472
609
  ```
473
610
 
611
+ The candidates' log, `context_candidates.jsonl`, sits one level up, beside
612
+ the sessions folder (`$XDG_STATE_HOME/samagotchi/`; see
613
+ [The log and the report](#the-log-and-the-report)).
614
+
474
615
  Deleting a session deletes its folder. A source and its snapshot go with
475
616
  `chi context rm`; its `.lock` stays (a fetch may still hold it).
data/docs/guardrails.md CHANGED
@@ -1087,6 +1087,17 @@ contention guard. The session's own checks come first; the kits and the
1087
1087
  suggestion log are read only for a session that can be asked, the log only
1088
1088
  after an earlier `undecided`.
1089
1089
 
1090
+ The candidate judge is a third use (`context.candidates.mode`, off by
1091
+ default; [context.md, The judge](context.md#the-judge)): two `noul`
1092
+ questions per proposed candidate, "Is this session's work about the
1093
+ candidate?" and "Has the user stated a task yet?", on the same host and
1094
+ model, gated by its own mode alone. It logs to `context_candidates.jsonl`
1095
+ with the `context` tag; in on mode `ContextCandidateChoice` acts on the
1096
+ answer (attach, card, or nothing). Unlike the toolkit's audit it doesn't skip a shared
1097
+ local host: it asks after the turn ends there. Both side judgments run
1098
+ through `SideJudgments` (one ask in flight per kind, a failure is one warn,
1099
+ shutdown waits up to a second).
1100
+
1090
1101
  ### What the audit writes
1091
1102
 
1092
1103
  With a client, `GuardrailWiring#request_approval` asks System 1 one `noul`
@@ -45,7 +45,7 @@ Behavior:
45
45
  - `execute` and `task_create` both spawn with chi's own Ruby/Bundler setup scrubbed (`TaskRuntime.sanitized_spawn_env`): `RUBYOPT` loses its `-r…bundler/setup` flags (others stay) and `RUBYLIB`, `BUNDLE_GEMFILE`, `BUNDLE_BIN_PATH`, `BUNDLER_VERSION` are unset, so a project's `ruby -e` or `bundle exec` uses its own Gemfile when chi runs under `bundle exec`. `task_create` refuses those keys in the model's `env`; `execute`'s `env` comes from chi's callers (`Builtins`, `!cmd`), not the model, and is set over the scrub. An `execute` handed over at its limit keeps the process it spawned, so the task runs with the same env.
46
46
  - `execute` and `task_create` export chi's own markers into the command, set over the model's `env` (which can't set them: `Error: env key is reserved`): `SAMAGOTCHI_PARENT_SESSION` (the session's id, `chi` without one; a chi started there answers as a parent agent), `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown) `SAMAGOTCHI_SESSION_THINKING` (the session's own thinking level; unset when it has none) and `SAMAGOTCHI_SESSION_REASONING_HISTORY` (its own reasoning_history level, the same way). `chi self` reads the last three for its model, thinking level and reasoning history. The four are `ChildEnv::SESSION_MARKERS` (`TaskRuntime::MARKER_ENV_KEYS`), which a spawned worker unsets (`ChildEnv.unset`, merged into its spawn env). A session's worker never inherits them from whoever started it (a `chi send` run by another session's `execute`): its other commands (a user's `!cmd`, an MCP server, a context command) have none.
47
47
  - They also pass on a set `XDG_STATE_HOME` / `XDG_CONFIG_HOME` made absolute (`Builtins.xdg_absolute`, via `Paths`), so a chi the command runs in another `cwd` (`chi send`) reads this chi's state and config. These aren't reserved: an XDG dir the model sets in `task_create`'s `env` stays its own.
48
- - Every runner starts from the session's project env, `ProjectEnv.tools_env`: the overlay a spawned worker computed once at boot (`SessionManager.apply_project_env` → `ProjectEnv.compute` → `ProjectEnv.install!`; its folder's `mise env`, and the chi shim's dir first on PATH so `chi` is the chi home's), less `ProjectEnv::TOOLS_DROP` (`RUBYOPT`, `RUBYLIB`, Bundler's: the scrub above sets those). Outside a worker (`ProjectEnv::NONE`) it is `{}`. The sites: `execute` and `task_create` (`Tools::Builtins#project_env`, under the markers and the model's `env`; a participant's turn gets `{}`), the user's `!cmd` (`SessionCommands#shell`) and a context source's command (`ContextFetch#env_for`). MCP servers take `ProjectEnv.current.env` whole under the server's own keys (`bundles/mcp/plugin.rb` `server_env`: no `TOOLS_DROP`, no participant rule). chi's own process env is never changed, and the project env never enters the prompt. User docs: [Project env](../sessions.md#project-env); the launch side: [How chi launches itself](../architecture.md#how-chi-launches-itself).
48
+ - Every runner starts from the session's project env, `ProjectEnv.tools_env`: the overlay a spawned worker computed once at boot (`SessionManager.apply_project_env` → `ProjectEnv.compute` → `ProjectEnv.install!`; its folder's `mise env`, and the chi shim's dir first on PATH so `chi` is the chi home's), less `ProjectEnv::TOOLS_DROP` (`RUBYOPT`, `RUBYLIB`, Bundler's: the scrub above sets those). Outside a worker (`ProjectEnv::NONE`) it is `{}`. The sites: `execute` and `task_create` (`Tools::Builtins#project_env`, under the markers and the model's `env`; a participant's turn gets `{}`), the user's `!cmd` (`SessionCommands#shell`), and through `ProjectEnv.spawn_env` (the scrub above, then `tools_env`: what `execute` spawns with, for the runners that don't go through it) a context source's command (`ContextFetch#env_for`) and an MCP server, under the server's own keys (`bundles/mcp/plugin.rb` `server_env`, from mcp 0.7.4; no participant rule: the server is the session's). chi's own process env is never changed, and the project env never enters the prompt. User docs: [Project env](../sessions.md#project-env); the launch side: [How chi launches itself](../architecture.md#how-chi-launches-itself).
49
49
 
50
50
  ## Finish wake
51
51
 
@@ -38,7 +38,7 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
38
38
  │ # resumed: true (only on a step-limit Continue: resumed: given),
39
39
  │ # images (only when there are any), cut_after (the model's cut wait, s)}
40
40
  │ # start_toolkit_suggestion: ToolkitSuggest's ask on its own thread from a copied snapshot (audit and on
41
- │ # mode: one eligibility, one thread). Audit never blocks the turn or changes the session; on mode
41
+ │ # mode: one eligibility; SideJudgments' :toolkit kind: one in flight, warn-not-raise). Audit never blocks the turn or changes the session; on mode
42
42
  │ # returns a ToolkitSuggest::Pending the turn joins below. See configuration.md (Toolkits) and
43
43
  │ # guardrails.md (System 1).
44
44
  │ # refresh_profile! (may ask the server's /props once)
@@ -50,6 +50,12 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
50
50
  │ # start, or a Stop (which wins over an answer already in, and cuts the ask's request through
51
51
  │ # Pending#cancel_controller; a Stop after a timeout doesn't), and on a candidate picked ≥ threshold
52
52
  │ # ToolkitBinding#suggest sets the session's toolkit, origin suggested, saved, with the notice;
53
+ │ # then start_context_judge (owner turns only: ContextCandidatesLog#look, and unless
54
+ │ # context.candidates.mode is off, ContextJudge's asks on SideJudgments' :context kind, each answer to
55
+ │ # ContextCandidateChoice#after_judged (on mode: attach to the session's folder, or a card held while a
56
+ │ # turn runs and shown from run_turn's ensure, show_context_cards_after_turn); on a shared
57
+ │ # local System 1 host they wait for the turn's end: started before warm_up_next_turn, whose warm-up
58
+ │ # waits for them, or from run_turn's ensure; docs/context.md, The judge);
53
59
  │ # then the session's toolkit (apply_toolkit!: Registry#toolkit, ToolIndex#refresh! for find_tools'
54
60
  │ # chi line, a prompt rebuild when it changed). A Stop during the probes or the waits returns false
55
61
  │ # (the ask's line says canceled; an ensure marks one the turn never settled failed).
@@ -80,10 +80,19 @@ right-hand column happens.
80
80
  `toolkit.suggest.defer_turns` / `defer_tokens`) is engine-side, so `/toolkit`'s CacheConfirm doesn't apply: it
81
81
  changes the declared tools at the front of the request, and the server re-reads **system prompt + tools +
82
82
  conversation** on that turn. The defer bound keeps that conversation small; its notice says "from this turn".
83
- When System 1 runs on the model's own single-slot local server (`Engine#toolkit_judge_shares_local_host?`), any
83
+ When System 1 runs on the model's own single-slot local server (`Engine#system_one_shares_local_host?`), any
84
84
  ask takes the slot and evicts its cached prefix: a first-turn ask costs that cache even when nothing switches
85
85
  (there is little cached on a session's first turn), so on mode asks only there, skips the deferred path, and
86
86
  sends no worker warm-up.
87
+ - The candidate judge (`context.candidates.mode`, `ContextJudge`) adds nothing to the model's request: its asks
88
+ go to System 1 only, and nothing it answers enters the running turn's history: on mode's attach writes only the
89
+ session's context folder, and its note comes with the between-turn delivery (`ContextAbsorber`), never mid-turn. On a shared local server it asks after the turn's last request, not skipped: a
90
+ completed turn starts the asks right before its turn-end warm-up, and the warm-up waits for them
91
+ (`PromptWarmup#start`'s `after:`, `AFTER_SECONDS` at most), so it re-warms the prefix the asks evicted and the
92
+ next turn prefills only its own message. A turn that ended otherwise (a Stop, a failure: no warm-up) starts them
93
+ from `Engine#run_turn`'s ensure, and its next turn re-reads its prompt. Where no warm-up runs (`cache.warmup:
94
+ off`, a chat host) the asks still evict the prefix. Bounded either way: at most two judged asks per candidate (one
95
+ re-ask after `reask_after_turns` owner prompts), plus a deferred ask per owner prompt before a task is stated.
87
96
  - A kit's prompt text (`toolkit.kits.<name>.prompt:`, `chi.toolkit … prompt:`) sits after the appended files and
88
97
  before the memory indexes (`SystemPrompt#kit_prompt_section`): one prefix per kit, never per-session text, so
89
98
  sessions on the kit share it; a memory write (the indexes after it) doesn't move it. It is part of the
@@ -50,7 +50,7 @@ Configuration (config.yml `execute.*` keys and CLI flags too, as for `read`):
50
50
  - `SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` (`execute.timeout_sec`, default `120`): what a command still running then gets is `execute.on_timeout`.
51
51
  - `SAMAGOTCHI_EXECUTE_ON_TIMEOUT` (`execute.on_timeout`, default `task`): `task` hands the command over as a background task, not killed; the result starts `Still running at the 120s limit: moved to a background task, not killed.` (`Execute::HANDED_OVER`), then its `task_id:` and the tails of stdout and stderr so far, and `task_wait` waits for it. `kill` stops it and answers `Error: command timed out after …s` (`Execute::TIMEOUT_PREFIX`) with the hint to use `task_create`. The `!cmd` bang always kills. With `task`, a message from the user waiting for the turn hands a command over early, after 3 s, with the first line `Still running at the 4s mark: a message from the user is waiting, …` (same `Execute::HANDED_OVER` start; never under `kill`). See [Background tasks](background-tasks.md).
52
52
 
53
- The env a command runs in (the session's project env, `ProjectEnv.tools_env`, then the Ruby/Bundler scrub and chi's markers) is the same for `execute`, `task_create`, `!cmd` and context sources: see [Background tasks](background-tasks.md).
53
+ The env a command runs in (the session's project env, `ProjectEnv.tools_env`, then the Ruby/Bundler scrub and chi's markers) is the same for `execute`, `task_create`, `!cmd`, context sources and MCP servers (`ProjectEnv.spawn_env`): see [Background tasks](background-tasks.md).
54
54
 
55
55
  Implementation note:
56
56