samagotchi 0.139.1 → 0.141.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -1
  3. data/README.md +2 -1
  4. data/docs/architecture.md +8 -6
  5. data/docs/cli.md +3 -1
  6. data/docs/configuration.md +33 -1
  7. data/docs/context.md +249 -46
  8. data/docs/guardrails.md +11 -0
  9. data/docs/internals/engine.md +7 -1
  10. data/docs/internals/prompt-caching.md +10 -1
  11. data/docs/internals/remote-sessions.md +4 -1
  12. data/docs/plugins.md +75 -18
  13. data/docs/sessions.md +3 -2
  14. data/lib/samagotchi/bridge/turn_accumulator.rb +4 -1
  15. data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
  16. data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +14 -0
  17. data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
  18. data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +15 -1
  19. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  20. data/lib/samagotchi/bundles/github-pr/manifest.yml +4 -4
  21. data/lib/samagotchi/bundles/github-pr/plugin.rb +72 -176
  22. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  23. data/lib/samagotchi/config.rb +8 -0
  24. data/lib/samagotchi/context_candidate_choice.rb +154 -0
  25. data/lib/samagotchi/context_candidates.rb +259 -0
  26. data/lib/samagotchi/context_candidates_log.rb +90 -0
  27. data/lib/samagotchi/context_command.rb +65 -17
  28. data/lib/samagotchi/context_judge.rb +194 -0
  29. data/lib/samagotchi/context_move.rb +31 -0
  30. data/lib/samagotchi/context_report_command.rb +161 -0
  31. data/lib/samagotchi/context_sources.rb +311 -29
  32. data/lib/samagotchi/engine.rb +189 -32
  33. data/lib/samagotchi/owner_prompt.rb +39 -0
  34. data/lib/samagotchi/plugin/api.rb +1 -0
  35. data/lib/samagotchi/plugin/attached_context.rb +105 -13
  36. data/lib/samagotchi/plugin/context.rb +12 -1
  37. data/lib/samagotchi/plugin/peer.rb +53 -0
  38. data/lib/samagotchi/plugin/sessions.rb +9 -2
  39. data/lib/samagotchi/prompt_warmup.rb +9 -1
  40. data/lib/samagotchi/send_command.rb +3 -1
  41. data/lib/samagotchi/session.rb +4 -0
  42. data/lib/samagotchi/session_access/contract.rb +4 -0
  43. data/lib/samagotchi/session_access/local.rb +34 -3
  44. data/lib/samagotchi/session_access/remote.rb +33 -8
  45. data/lib/samagotchi/session_access.rb +31 -6
  46. data/lib/samagotchi/session_commands.rb +28 -5
  47. data/lib/samagotchi/session_continue.rb +9 -2
  48. data/lib/samagotchi/side_judgments.rb +84 -0
  49. data/lib/samagotchi/terminal_ui.rb +15 -0
  50. data/lib/samagotchi/testing/boundary.rb +5 -1
  51. data/lib/samagotchi/testing/host.rb +4 -1
  52. data/lib/samagotchi/toolkit_report_command.rb +2 -1
  53. data/lib/samagotchi/toolkit_suggest.rb +3 -18
  54. data/lib/samagotchi/version.rb +1 -1
  55. data/lib/samagotchi/web/app.rb +24 -11
  56. data/lib/samagotchi/web/auto_update.rb +8 -1
  57. data/lib/samagotchi/web/public/app.js +8 -2
  58. data/lib/samagotchi/web/public/chains.js +10 -2
  59. data/lib/samagotchi/web/public/update_notice.js +21 -8
  60. data/lib/samagotchi/web/session_access_routes.rb +8 -3
  61. data/lib/samagotchi/web/session_create.rb +1 -0
  62. data/lib/samagotchi/web/session_hub.rb +5 -2
  63. data/lib/samagotchi/web/update_check.rb +14 -8
  64. data/lib/samagotchi/worker.rb +5 -0
  65. metadata +9 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ef79d43ec1d5fc441ca8892ceb240a42fca4629295e71debc495df44b5efde8
4
- data.tar.gz: 39b1d809ff216fcd1a1af146dc7858b45e80f94a8018e00841839573b753be0d
3
+ metadata.gz: 4d6c521351f86a38243b9757eff32df1745e88d2be1158684ba029e85f16b9e1
4
+ data.tar.gz: 99c0f5f7b2356a58a36f4d38330a17d95decdc64446e26ee55f38d3b5794b3ba
5
5
  SHA512:
6
- metadata.gz: 228b7f233e3d6fd752ba0dbad22f4fed1e27910eb97a37190ec9172db94d1369dffe13f64389b3e1f77914bc845d2625c273c7569594fe76ffc4aa18edf749f6
7
- data.tar.gz: f6439358124c6785a2ab54947e97928f725a817146bcd24e2263734e48bd2718b693e6573d4253fc64bbfe660a390125720f6a71662f31400bb7fbd39a4dd9da
6
+ metadata.gz: 7b735c84997b1a4c035c6d3c0f7baf6035f4f29bd400e6bce116ecaf5fe06c0d2b304fe19a8447a523833bf71cfe9829193d6c8fcb136f42e7a5f900e4f7a30f
7
+ data.tar.gz: 0705cdf1abb1716a40f38c396b020442b44c2d985911f4d945362f0d2a83bde088c0699f660c8dce791edd666b83529e7d991e642703ceb023558caaa128c394
data/CHANGELOG.md CHANGED
@@ -7,6 +7,59 @@ 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.141.0] - 2026-10-31
11
+
12
+ ### Added
13
+
14
+ - Movable context: a continue (`chi send --new --continues`, the web's Continue →) and a bundle's fork take the
15
+ session's attached context with them, with a note in the new session naming what moved and what stayed. A command
16
+ source moves, a pushed one stays; `chi context add --movable / --no-movable` (and `ctx.context.attach(movable:)`)
17
+ say it outright, and `chi context ls` marks one that stays. A push into a continued session is refused with the
18
+ next link's id. See docs/context.md (Movable context).
19
+ - A paired Claude Code or Codex session follows a continue: the new session asks it once to pair again with the new
20
+ id (a `moved` message to the inbox the old session had), and a reply pushed to the old id is refused with the new
21
+ one, so the peer pairs again then (claude-peer 0.4.6, codex-peer 0.3.5: install the chi-peer skill again with
22
+ `/claude install-skill` or `/codex install-skill`). Bundles get `ctx.continues` and `ctx.context.left_behind`. See
23
+ docs/context.md (Peers and a continue).
24
+
25
+ Update with `chi update` (claude-peer 0.4.6, codex-peer 0.3.5).
26
+
27
+ ## [0.140.0] - 2026-10-31
28
+
29
+ ### Added
30
+
31
+ - Candidates for attached context: a bundle proposes a source (`ctx.context.propose`, `candidates`, `withdraw`),
32
+ and chi attaches it when the session's owner mentions it; `chi context ls` and `/context` list them with their
33
+ state (proposed, attached, declined). See docs/context.md (Candidates).
34
+ - The candidate judge, audit only and opt-in (`context.candidates.mode: audit`; off by default): System 1 is asked
35
+ whether the session's work is about each proposed candidate, and the answers go to `context_candidates.jsonl`;
36
+ nothing is attached. `chi context report [--days N]` counts them against what you attached or declined. Jev
37
+ through OpenRouter works as the System 1 host. See docs/context.md (The judge).
38
+ - The candidate judge's on mode (`context.candidates.mode: on`): a candidate judged at `threshold` (0.8) or above
39
+ is attached quietly (its note comes before your next turn, never mid-turn; removing it declines it), one from
40
+ `offer_threshold` (0.6) gets a card once (Attach / Not here), lower ones stay proposed. `/context attach NAME` and
41
+ `/context decline NAME` choose a candidate yourself; `chi context report` counts the judge's attaches you removed
42
+ later. See docs/context.md (On mode).
43
+
44
+ ### Changed
45
+
46
+ - github-pr no longer attaches the branch's PR by itself: mention it (#N, the branch, the URL), or set
47
+ `auto_attach: attach` for the old behaviour. It proposes the PR as a candidate when the worker starts (a delegate
48
+ child too), and a prompt of yours that names it attaches it before that turn runs, its note in that turn.
49
+ `auto_attach: offer` (the card with Attach and Not here, `/pr-attach`, `/pr-decline`) is gone and reads as
50
+ `propose`, as does an unknown value. (github-pr 0.6.0)
51
+
52
+ ### Fixed
53
+
54
+ - chi web's update check no longer holds up the page, the API or the automatic install while it fetches from
55
+ RubyGems (~10 s): reading and saving the auto-update record take their own lock.
56
+ - Stopping chi web while the automatic update's preflight runs waits for it (up to 25 s) instead of abandoning it,
57
+ and a preflight that ends after the stop no longer restarts chi web.
58
+ - An automatic install with `update.restart: idle` no longer offers the "installed" toast's Restart buttons (a
59
+ failed run, `restart: never` or a manual install still do).
60
+
61
+ Update with `chi update` (the dev profile 0.7.4).
62
+
10
63
  ## [0.139.1] - 2026-10-31
11
64
 
12
65
  ### Fixed
@@ -5326,7 +5379,9 @@ and long-lived sessions.
5326
5379
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5327
5380
  hotkey panel that send selected text or the clipboard to your sessions.
5328
5381
 
5329
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.139.1...HEAD
5382
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.141.0...HEAD
5383
+ [0.141.0]: https://github.com/dm1try/samagotchi/compare/v0.140.0...v0.141.0
5384
+ [0.140.0]: https://github.com/dm1try/samagotchi/compare/v0.139.1...v0.140.0
5330
5385
  [0.139.1]: https://github.com/dm1try/samagotchi/compare/v0.139.0...v0.139.1
5331
5386
  [0.139.0]: https://github.com/dm1try/samagotchi/compare/v0.138.0...v0.139.0
5332
5387
  [0.138.0]: https://github.com/dm1try/samagotchi/compare/v0.137.0...v0.138.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
 
@@ -717,8 +719,8 @@ model: [internals/remote-sessions.md](internals/remote-sessions.md).
717
719
  | `POST /api/sessions/:id/wait/prepare` | owner | `question_id` (absent: the pending one; `null`: none), `turn`, `created`, `busy` (+ `queue`) | 200 `{session_id, cursor, baseline: {messages, question_id, last_turn}, own_turn, known_turn, running, live, settled, last_turn_id}` (`WaitStart`) | 409 `busy`, `waiting` (`SendCommand::Busy`'s words, and its fields: `question`, `parent_strategy`) |
718
720
  | `POST /api/sessions/:id/wait` | owner | `cursor`, `baseline`, `turn`, `queued`, `ended_before`, `owner_grace`, `timeout` (capped at 30 s) | 200: `ReplyWait::Result`'s fields (`status`: `done`, `waiting_for_answer`, `no_reply`, `error`, `stopped`, `worker_gone`, or `timeout`: ask again; `text`, `file`, `question` as `PendingQuestion#to_file`, `outcome`, `limit`, `error_kind`, `retryable`, `cancel_reason`, `stopped_by`, `kept_steps`, `turn_id`, `origin`; never the session) and `parent_strategy` `{name, allowed, origin}` (`ParentReport.strategy_for`, this server's config.yml) | 400 `invalid_wait`; 503 `server_stopping` |
719
721
  | `POST /api/sessions/:id/notes` | owner | `text`, `source` (default `cli`, never `broadcast`) | 200 `{status: queued \| waits, session_id, queued}` (`NoteDelivery`) | 409 `owned_by_tui` (`status: refused`); 400 `note_rejected`, `missing_fields`, `invalid_source` |
720
- | `POST /api/sessions/:id/context` (more bodies) | owner | `{name, push: true, why, hint}` or `{name, cmd, every, why, hint}` beside the page's `{url, why, hint}`; `added_by` `cli`/`agent` (else `web`) | 201 `{status: attached, name}` | 400 `invalid_source`; 409 `exists` |
721
- | `POST /api/sessions/:id/context/:name/push` | owner | `text`, `summary`, `wake`, `hint` (a source's JSON output, checked the same way) | 200 `{status: pushed, name, before, revision, changed}` | 404 `not_found`; 409 `not_push`; 400 `invalid_text` |
722
+ | `POST /api/sessions/:id/context` (more bodies) | owner | `{name, push: true, why, hint}` or `{name, cmd, every, why, hint}` beside the page's `{url, why, hint}`; `added_by` `cli`/`agent` (else `web`); `movable` (any body) | 201 `{status: attached, name, movable}` | 400 `invalid_source`; 409 `exists` |
723
+ | `POST /api/sessions/:id/context/:name/push` | owner | `text`, `summary`, `wake`, `hint` (a source's JSON output, checked the same way) | 200 `{status: pushed, name, before, revision, changed}` | 404 `not_found`; 409 `not_push`; 410 `context_continued` (`next_id`); 400 `invalid_text` |
722
724
  | `DELETE /api/sessions/:id/context/:name?own=1` | owner | | 200 `{status: detached}`: the session's own only | 409 `project_source` (chi context rm's words); 404 |
723
725
  | `POST /api/sessions/:id/stop` | owner | `wait?` (seconds, capped at `SessionManager::STOP_WAIT`, 10: `chi --remote NAME sessions stop` sends it) | 200 `{status: stopped, session_id, released}`: `released`, whether the worker let go within the wait (without one `STOP_WAIT_SECONDS`, 2 s: the page's Stop) | 409 `owned_by_tui` |
724
726
  | `DELETE /api/sessions/:id?force=` | owner | `force=true` stops a live worker first (the page sends it) | as before | 409 `worker_running` without it |
@@ -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.
@@ -1758,6 +1758,33 @@ its own). The first target is a long session such as a day-long coordinator, whe
1758
1758
  turn that runs a layer or whose session set its strategy) and the web's chip. A woken worker's status line counts
1759
1759
  its saved context against the session's budget.
1760
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
+
1761
1788
  ## Opening files from chi web
1762
1789
 
1763
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
@@ -2082,7 +2109,8 @@ Both are read when chi web starts (restart it to apply a change). What it does,
2082
2109
  the command to run). `chi update` also merges the shipped bundles that live sessions read, hence "no turn anywhere".
2083
2110
  2. **Installs** exactly the release the notice named, as the **Update** button does (the system bundle and shipped
2084
2111
  bundles are merged three ways; files you edited are kept and counted in the push). The page's sticky toast says
2085
- "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**:
2086
2114
  it pushes the failed row's reason and stays quiet until a newer release (or you, with the button) tries again.
2087
2115
  3. **Restarts** (`update.restart: idle`). Stale workers first, only those whose session is idle and untouched for
2088
2116
  10 minutes (so nobody who is about to type loses their worker), and each worker still refuses by itself when
@@ -2190,6 +2218,10 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
2190
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). |
2191
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). |
2192
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. |
2193
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". |
2194
2226
  | `toolkit.child` | `full` | | A delegate child's toolkit, unless its `delegate` call names one (`toolkit:`). See "Toolkits". |
2195
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
@@ -89,8 +89,8 @@ web) without touching the other sessions.
89
89
  ## The commands
90
90
 
91
91
  ```
92
- chi context add NAME (--cmd CMD | --push) [--every SECONDS] [--why TEXT] [--hint TEXT] TARGET
93
- chi context add URL [--why TEXT] [--hint TEXT] TARGET a URL an installed bundle's provider knows
92
+ chi context add NAME (--cmd CMD | --push) [--every SECONDS] [--why TEXT] [--hint TEXT] [--[no-]movable] TARGET
93
+ chi context add URL [--why TEXT] [--hint TEXT] [--[no-]movable] TARGET a URL an installed bundle's provider knows
94
94
  chi context push NAME [-m TEXT] [TARGET] new text for a --push source (stdin without -m)
95
95
  chi context ls [TARGET] [--format json]
96
96
  chi context show NAME [--json] [TARGET]
@@ -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,219 @@ 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
+ ## Movable context
317
+
318
+ A session's own sources go with it to the next session of its work: the next
319
+ link of a [chain](sessions.md#session-chains) (`chi send --new --continues`,
320
+ the web's Continue →) and a bundle's fork
321
+ ([`ctx.sessions.fork`](plugins.md#other-sessions-ctxsessions)). A topic switch goes
322
+ to a fresh session with its own context, never through a continue.
323
+
324
+ Each source says whether it moves:
325
+
326
+ - **A command source moves** (`--cmd`, a URL's source): chi runs its command
327
+ itself, in the new session too.
328
+ - **A pushed source stays** (`--push`): its writer (`pbpaste | chi context
329
+ push notes <id>`, a peer agent) holds the old session's id.
330
+ - `chi context add … --movable` / `--no-movable` (either kind) says it
331
+ outright: `--movable` on a pushed source means its writer finds the new id
332
+ itself. A bundle passes `movable:` to `ctx.context.attach`.
333
+
334
+ What moves is copied before the new session's worker starts: the source, its
335
+ last text (so its first note quotes it without waiting for a fetch, and its
336
+ interval holds), and the session's choices: every removed (declined) and
337
+ muted marker. Candidates don't move. The new link gets the usual *Attached*
338
+ note for each source; a fork starts where its seed's notes left off, so a
339
+ source that changed since gets an *Updated* note. A second chi note, after
340
+ the chain's, names what moved and what stayed:
341
+
342
+ > Context moved with this session: pr-412, ci. Left with 3f2a9c1e: notes (pushed).
343
+
344
+ `chi send --new --continues` and the web's Continue toast say the same in a
345
+ few words, and `chi context ls` marks a session's source that stays
346
+ (`· stays`; `--format json` has the derived `movable`). Project sources are
347
+ shared already, and a review fork (`chi review --from`) and a delegate take none.
348
+
349
+ A push into a continued session is refused, nothing written, with the next
350
+ link's id (`chi context push` exits 1; chi web answers 410
351
+ `context_continued`):
352
+
353
+ ```
354
+ chi context push: 3f2a9c1e was continued as 7b1d…: notes stays with the session it was attached to; attach it to 7b1d… (or pair again with 7b1d…) and push there
355
+ ```
356
+
357
+ An archived session that nobody continued takes pushes as before.
358
+
359
+ ### Peers and a continue
360
+
361
+ A peer's pairing ([claude-peer, codex-peer](plugins.md#the-claude-peer-bundle):
362
+ `<peer>-inbox` and `<peer>-outbox`, both pushed) stays with the old session,
363
+ and the peer pairs again with the new one. The continue keeps what stayed in
364
+ the new session (`left.json` beside its sources: each name, its last pushed
365
+ hint, the old id; `ctx.context.left_behind`), and the new session's worker
366
+ posts the peer one `moved` message to the inbox left behind, once:
367
+ `chi <old> · moved · continued as <new id>`. The peer's chi-peer skill pairs
368
+ again with the new id, the same way it paired before. Without an inbox (an
369
+ outbox-only pairing through a remote), or when the message didn't arrive, the
370
+ peer's next push to the old id is refused with the new id, and the skill pairs
371
+ again and pushes there. The second note says which, not whether it worked:
372
+
373
+ > Left with 3f2a9c1e: claude-inbox, claude-outbox (Claude's pairing: chi asks Claude to pair again with this session).
374
+
375
+ or, for an outbox alone, `codex-outbox (Codex pairs again when it next
376
+ answers)`. A fork takes no pairing and posts nothing.
377
+
378
+ ## Candidates
379
+
380
+ A bundle can **propose** a source instead of attaching it
381
+ ([`ctx.context.propose`](plugins.md#attached-context-ctxcontext)): a
382
+ candidate, with a title and the words that name it (its *mentions*). Nothing
383
+ reaches the agent until you name it in one of your own prompts; then chi
384
+ attaches it before that turn runs, gives it one first fetch (up to 10
385
+ seconds), and the turn starts with its attached note, whose why names what
386
+ you wrote ("Why: you mentioned #412"). A source whose first text takes longer
387
+ gets its note with a later turn.
388
+
389
+ How a mention counts:
390
+
391
+ - whole words, any case;
392
+ - `#N` only for N of 10 or more, or right after "PR" or "pull" ("PR #4");
393
+ - a branch name only when it is at least 6 characters and holds a `/`, `-`
394
+ or `_` (`feat/login`, not `web`);
395
+ - a URL exactly as proposed (`…/pull/42/files` is another URL);
396
+ - nothing inside a fenced code block: a pasted log's `#412` isn't you asking.
397
+
398
+ Only the session's owner counts: a participant's message, a steer, a note or
399
+ a reminder never attaches anything, and a review session gets no candidates.
400
+ A bundle usually proposes from an init task that finishes while your first
401
+ turn runs; when it does, the prompts you already wrote count too, and a hit
402
+ is attached at once (its note comes with your next turn).
403
+
404
+ A candidate's state is never stored: it is *attached* while a source of its
405
+ name (or its URL) is, *declined* once you removed that source from the
406
+ session or answered its card "Not here" (it isn't proposed, judged or
407
+ attached again), else *proposed*. `chi context ls` and `/context` list them
408
+ under the sources:
409
+
410
+ ```
411
+ $ chi context ls
412
+ no attached context
413
+ candidates (attached when the session's owner mentions one):
414
+ pr-412 proposed #412 Fix the login redirect
415
+ ```
416
+
417
+ You can also choose yourself: `/context attach NAME` attaches a proposed
418
+ candidate (its why: "you attached it"), `/context decline NAME` declines it.
419
+ Both are the session owner's (a participant can't send a `/` line, and a
420
+ review session refuses them); an unknown name, or one attached or declined
421
+ already, is an error that says so. They are what the judge's card's buttons
422
+ run (see [On mode](#on-mode)).
423
+
424
+ Forks and continues take no candidates: the bundle proposes again in the
425
+ new session (see [Movable context](#movable-context) for the sources).
426
+
427
+ ### The judge
428
+
429
+ A mention is the only thing that attaches a candidate by itself. With
430
+ `context.candidates.mode: audit` chi also asks [System 1](guardrails.md#system-1-experimental)
431
+ about each proposed candidate — "Is this session's work about the
432
+ candidate?" — and logs the answer, so you can see how it would do before
433
+ anything depends on it; `audit` attaches nothing. With `on` the answer acts
434
+ ([On mode](#on-mode)). The default is `off`: no conversation text goes to a System 1 host unless you
435
+ turn this on, whatever `guardrails.system_one` says. The host and model are
436
+ `guardrails.system_one.host` / `.model` (Jev through OpenRouter: see
437
+ [The candidate judge](configuration.md#the-candidate-judge)).
438
+
439
+ ```yaml
440
+ context:
441
+ candidates:
442
+ mode: audit # off (default) | audit | on
443
+ threshold: 0.8 # on: attach at or above this
444
+ offer_threshold: 0.6 # on: a card from this, below threshold
445
+ reask_after_turns: 3 # owner prompts before the one re-ask
446
+ ```
447
+
448
+ What it asks, and when:
449
+
450
+ - **One ask per candidate**, at the start of each of your own turns (never a
451
+ participant's, never in a review session or on a Continue), after the
452
+ toolkit suggestion. A candidate proposed while your turn runs (github-pr's
453
+ usual case) is asked about right then, on your prompts so far.
454
+ - **What it sees:** the candidate's title (and description, when the bundle
455
+ gave one) and your own prompts: the first one with a task in it and the
456
+ latest, each cut at 1500 characters. Never the branch, the checkout or the
457
+ source's "why". For a delegate child, also the titles of what its parent
458
+ session has attached, and the question says the first message is the
459
+ parent's request.
460
+ - **Has a task been stated yet?** The same ask has a second question; below
461
+ 0.5 the candidate stays unjudged and is asked again at your next prompt
462
+ (a first "hi" doesn't count against it).
463
+ - **After a judged ask:** one re-ask once `reask_after_turns` more prompts of
464
+ yours came, with the conversation grown; then it is done.
465
+ - **Where it runs:** on a remote System 1 host (Jev) beside your turn; on the
466
+ model's own local server (System 1 on the same Splash) after your turn
467
+ ends, so it never competes with the turn's prefill; the turn-end warm-up
468
+ waits for those asks and restores the cached prompt they pushed out. One
469
+ session's asks run one batch at a time.
470
+
471
+ ### On mode
472
+
473
+ With `context.candidates.mode: on` the judge's answer acts:
474
+
475
+ - **p ≥ `threshold` (0.8): attached quietly.** The source goes into the
476
+ session's own folder, its why "it looks related to your task" (the agent
477
+ never sees a probability), and it shows as a chip like any attached source.
478
+ Its note reaches the agent only at the next between-turn delivery: an
479
+ answer that comes while your turn runs waits for that turn to end, so the
480
+ running turn's history, cached prompt and reasoning stay as they were.
481
+ Removing it — the chip's ×, or `chi context rm NAME` — declines it: it is
482
+ never proposed or judged again in that session.
483
+ - **`offer_threshold` (0.6) ≤ p < `threshold`: one card**, from `context`,
484
+ with the candidate's title and two buttons: **Attach** (`/context attach
485
+ NAME`) and **Not here** (`/context decline NAME`). The card shows once per
486
+ candidate, also across a worker restart (a `carded` line in the log
487
+ remembers it), and never inside a running turn: an answer that comes
488
+ mid-turn shows its card when the turn ends. In the terminal the card lists
489
+ the two commands to type. Answering it replaces the card with what
490
+ happened; a later re-ask at or above the threshold attaches it and closes
491
+ the card.
492
+ - **Below `offer_threshold`: nothing.** The candidate stays proposed (a
493
+ mention still attaches it).
494
+
495
+ ### The log and the report
496
+
497
+ Everything about candidates goes to `context_candidates.jsonl` in the state
498
+ folder (0600, local; written in every mode, `off` too, since nothing in it
499
+ leaves the machine): `proposed`, `mentioned`, `judged` (the probability `p`,
500
+ the task-stated probability `task_p`, `stated`, `would`: attach / offer /
501
+ none at the thresholds then, with your prompts cut to 200 characters),
502
+ `judge_attached` (on mode's attach, with `p`), `carded` (on mode's card, with
503
+ `p`), `attached` / `declined` with `by: "command"` (`/context attach|decline`,
504
+ the card's buttons), `attached` / `declined` / `detached` with `by: "user"`
505
+ (what you did otherwise, seen at your next turn's start; removing a source
506
+ declines it) and `withdrawn`.
507
+
508
+ `chi context report [--days N]` counts it, with no prompts in the output
509
+ (this machine only):
510
+
511
+ ```
512
+ $ chi context report
513
+ chi context report: the last 14 days, 12 sessions (mode audit)
514
+ candidates: proposed 14 · mentioned 3 · attached by you 1 · declined 2 · detached 0 · withdrawn 0
515
+ on mode: judge attached 0 · you removed 0 of them later · carded 0 · /context attach 0 · /context decline 0 (the card's buttons)
516
+ judge: 19 asks · judged 15 · no task yet 4 · 11 candidates judged
517
+ would attach (each candidate's latest judged ask):
518
+ ≥ 0.5 5
519
+ ≥ 0.6 4 offer_threshold
520
+ ≥ 0.7 3
521
+ ≥ 0.8 3 threshold
522
+ ≥ 0.9 1
523
+ 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
524
+ ```
525
+
526
+ The on mode line's "you removed N of them later" is the judge's false
527
+ positives: sources it attached that you then took off.
528
+
314
529
  ## GitHub PRs: the github-pr bundle
315
530
 
316
531
  ```sh
@@ -320,13 +535,19 @@ chi bundle install github-pr # or the dev profile: chi bundle install dev
320
535
  It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
321
536
 
322
537
  - 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.
538
+ bundle proposes it as a [candidate](#candidates) `pr-<number>`, titled
539
+ "#42 <the PR's title>": nothing reaches the agent until you mention it
540
+ in a prompt (`#42`, the branch's name, or the PR's URL). Then it is
541
+ attached in that same turn ("Why: you mentioned #42"). Not in a `chi
542
+ scratch` session or a review session, and quietly nothing without `gh`, a
543
+ login, a repository or an open PR. A delegate child gets the proposal too
544
+ (its task may name the PR). At a turn's start more than 5 minutes after
545
+ its last look, the bundle looks again, beside the turn (a branch switched
546
+ mid-session). Once you remove an attached PR (`chi context rm`, the chip's
547
+ detach) it stays removed: it isn't proposed or attached again; `chi context
548
+ add <PR URL>` brings it back.
328
549
  - `chi context add <PR URL>` attaches any PR.
329
- - `auto_attach` (below) can make it ask first, or attach nothing.
550
+ - `auto_attach` (below) can make it attach the PR at once, or do nothing.
330
551
  - The text: title, state, branch, description, reviews, comments (oldest
331
552
  first) and checks, every 5 minutes.
332
553
  - The summary says what changed in **counts, authors and states only**
@@ -336,50 +557,28 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
336
557
  - It wakes for a review requesting changes, checks turning red, and the PR
337
558
  merged or closed. Comments alone never wake.
338
559
 
339
- ### Attach, offer or off
560
+ ### Propose, attach or off
340
561
 
341
562
  ```yaml
342
563
  bundles:
343
564
  github-pr:
344
- auto_attach: offer # attach (the default) | offer | off
565
+ auto_attach: propose # propose (the default) | attach | off
345
566
  ```
346
567
 
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.
568
+ - `propose`: the branch's open PR is a candidate, attached when you mention
569
+ it, as above.
570
+ - `attach`: it is attached when the worker starts, mention or not (the
571
+ default before candidates; not in a delegate child).
572
+ - `off`: nothing is proposed or attached. `chi context add <PR URL>` and
573
+ line links work as before.
363
574
 
364
575
  `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.
576
+ `off`. `offer`, which once posted a card with Attach and Not here, now reads
577
+ as `propose`, and so does an unknown value, each with a warning in the log.
578
+ The setting is read when the bundle looks.
579
+
580
+ A proposed PR that isn't attached gets no line links (unless your message
581
+ names its URL).
383
582
 
384
583
  ### Line links
385
584
 
@@ -468,8 +667,12 @@ sessions/<id>/<name>.json, …snapshot.json a session's own
468
667
  sessions/<id>/subscriptions.json what the session has seen and read (its worker writes it)
469
668
  sessions/<id>/muted/<name> a project source this session mutes
470
669
  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
670
+ sessions/<id>/candidates/<name>.json a source a plugin proposed here ({url or cmd, title, describe, mentions, proposed_by, at}; no status)
472
671
  ```
473
672
 
673
+ The candidates' log, `context_candidates.jsonl`, sits one level up, beside
674
+ the sessions folder (`$XDG_STATE_HOME/samagotchi/`; see
675
+ [The log and the report](#the-log-and-the-report)).
676
+
474
677
  Deleting a session deletes its folder. A source and its snapshot go with
475
678
  `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`
@@ -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).