samagotchi 0.121.1 → 0.123.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +19 -7
- data/docs/cli.md +32 -1
- data/docs/configuration.md +55 -5
- data/docs/guardrails.md +119 -7
- data/docs/internals/engine.md +11 -2
- data/docs/internals/participants.md +76 -20
- data/docs/internals/prompt-caching.md +19 -7
- data/docs/internals/web-push.md +7 -3
- data/docs/memory.md +33 -0
- data/docs/plugins.md +3 -2
- data/docs/sessions.md +14 -4
- data/lib/samagotchi/bridge/turn_accumulator.rb +3 -1
- data/lib/samagotchi/bridge.rb +8 -4
- data/lib/samagotchi/bridge_client/event_stream.rb +5 -1
- data/lib/samagotchi/bridge_client/stream_open.rb +8 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +13 -0
- data/lib/samagotchi/config.rb +11 -0
- data/lib/samagotchi/delivery.rb +7 -0
- data/lib/samagotchi/engine.rb +289 -12
- data/lib/samagotchi/event_fanout.rb +0 -1
- data/lib/samagotchi/gem_update.rb +41 -4
- data/lib/samagotchi/guardrail_wiring.rb +46 -12
- data/lib/samagotchi/guardrails/approval.rb +42 -4
- data/lib/samagotchi/guardrails/gate.rb +1 -1
- data/lib/samagotchi/guardrails/pair_boundary.rb +172 -0
- data/lib/samagotchi/guardrails/pair_command.rb +365 -0
- data/lib/samagotchi/guardrails/review_paths.rb +41 -7
- data/lib/samagotchi/guardrails/shell_lex.rb +7 -5
- data/lib/samagotchi/guardrails/shell_paths.rb +10 -1
- data/lib/samagotchi/guardrails/verdict.rb +7 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/guardrails_command.rb +6 -1
- data/lib/samagotchi/kernel_loop.rb +20 -0
- data/lib/samagotchi/llm/chat_loop.rb +4 -0
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/memory_orphans.rb +11 -0
- data/lib/samagotchi/participant_mode.rb +85 -0
- data/lib/samagotchi/participants.rb +7 -5
- data/lib/samagotchi/plugin/api.rb +7 -4
- data/lib/samagotchi/process_group.rb +13 -0
- data/lib/samagotchi/review_apply.rb +10 -2
- data/lib/samagotchi/review_start.rb +42 -10
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/session.rb +14 -1
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -2
- data/lib/samagotchi/session_access/remote.rb +32 -12
- data/lib/samagotchi/session_access/web_bridge_client.rb +9 -2
- data/lib/samagotchi/session_manager.rb +21 -7
- data/lib/samagotchi/session_participants.rb +18 -3
- data/lib/samagotchi/session_setup.rb +8 -4
- data/lib/samagotchi/sessions_command.rb +1 -1
- data/lib/samagotchi/share_command.rb +2 -2
- data/lib/samagotchi/steer.rb +13 -6
- data/lib/samagotchi/system_one/client.rb +16 -7
- data/lib/samagotchi/system_one/log.rb +25 -5
- data/lib/samagotchi/system_prompt.rb +20 -4
- data/lib/samagotchi/terminal_ui/question_prompt.rb +17 -1
- data/lib/samagotchi/terminal_ui.rb +2 -1
- data/lib/samagotchi/tool_runner.rb +11 -1
- data/lib/samagotchi/toolkit.rb +27 -8
- data/lib/samagotchi/toolkit_binding.rb +149 -7
- data/lib/samagotchi/toolkit_command.rb +15 -3
- data/lib/samagotchi/toolkit_report_command.rb +320 -0
- data/lib/samagotchi/toolkit_suggest.rb +377 -0
- data/lib/samagotchi/tools/builtins.rb +14 -1
- data/lib/samagotchi/tools/memory.rb +1 -1
- data/lib/samagotchi/update_command.rb +7 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +12 -13
- data/lib/samagotchi/web/grants.rb +17 -5
- data/lib/samagotchi/web/message_display.rb +6 -1
- data/lib/samagotchi/web/participant_access.rb +4 -1
- data/lib/samagotchi/web/participant_review.rb +8 -4
- data/lib/samagotchi/web/participant_view.rb +12 -4
- data/lib/samagotchi/web/participants_admin.rb +10 -6
- data/lib/samagotchi/web/principal.rb +3 -4
- data/lib/samagotchi/web/public/app.js +34 -10
- data/lib/samagotchi/web/public/bubbles.js +3 -2
- data/lib/samagotchi/web/public/delivery_line.js +8 -6
- data/lib/samagotchi/web/public/participant.html +1 -1
- data/lib/samagotchi/web/public/participant.js +12 -13
- data/lib/samagotchi/web/public/participant_model.js +20 -8
- data/lib/samagotchi/web/public/participant_names.js +29 -8
- data/lib/samagotchi/web/public/participants_panel.js +17 -3
- data/lib/samagotchi/web/public/question_card.js +12 -2
- data/lib/samagotchi/web/public/question_cards.js +7 -0
- data/lib/samagotchi/web/public/stream_handlers.js +6 -2
- data/lib/samagotchi/web/public/tools_overview.js +42 -0
- data/lib/samagotchi/web/public/turn_events.js +7 -5
- data/lib/samagotchi/web/public/turn_html.js +2 -2
- data/lib/samagotchi/web/public/turn_view.js +6 -4
- data/lib/samagotchi/web/public/update_notice.js +31 -2
- data/lib/samagotchi/web/push/notifier.rb +28 -1
- data/lib/samagotchi/web/server.rb +7 -3
- data/lib/samagotchi/web/session_access_routes.rb +5 -4
- data/lib/samagotchi/web/session_control.rb +4 -2
- data/lib/samagotchi/web/session_hub.rb +24 -3
- data/lib/samagotchi/web/start_dir.rb +10 -5
- data/lib/samagotchi/web/update_check.rb +179 -0
- data/lib/samagotchi/worker.rb +60 -8
- data/lib/samagotchi/worker_link.rb +1 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b284b838ef7182e859040f2955be576abe43fda373a9a96c094bd701ef8837c
|
|
4
|
+
data.tar.gz: d776a399ac831dd9a62405f58f5eb9c9843b83b021f82a52361fe32d4f49aba9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a590e517591c610712641c7496a916afad37502d3784669bbb58a582a5e8522246dc9c6a2bdcaad8b5fc9ad4d8c7b7bab95e52eeea012bf075af9d481e52680
|
|
7
|
+
data.tar.gz: 4e7917cc27174b89e870f6f6d768001de752b35f332f942f7c9223fab22a0614c2bb32839e79c6038282ed5a86f994e038a4cd8b2a1a3a83b3c2f52320309827
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,70 @@ 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.123.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- chi web checks RubyGems daily and shows the newest aged, stable release newer than both the running and installed
|
|
15
|
+
chi, with its RubyGems page and a command to copy (`chi update`, or the blocker-specific command).
|
|
16
|
+
- chi web sends one 24-hour phone push per newly available chi; it skips the push when a chi web page is in front.
|
|
17
|
+
- `toolkit.suggest.mode: on` starts a new session on the kit System 1 suggests for its first prompt (or a later one
|
|
18
|
+
after `undecided`), waiting at most System 1's timeout before the first request, with a
|
|
19
|
+
`toolkit: NAME (suggested, p=…) · /toolkit to change` notice; anything uncertain keeps the default kit.
|
|
20
|
+
- Pair access for a shared working session: a participant invited to a working session (not a `chi review`
|
|
21
|
+
session) while its worker runs pair turns gets turns that act with the session's tools under the owner's
|
|
22
|
+
guardrails; the grant is stamped `mode: pair` only when the share warning said so. Existing grants, and any invited
|
|
23
|
+
while the worker was older, stay read-only review participants until they are invited again. A pair turn's asks
|
|
24
|
+
reach the owner's card naming who asks, with Allow once only, and System 1 never answers them; a write outside the
|
|
25
|
+
project always asks, whatever the owner approved before. A new core check (`pair-boundary`) denies reading or
|
|
26
|
+
writing secrets and chi's own files (keys, shell startup files, histories, credential files; by a file tool, a
|
|
27
|
+
command, a glob, a variable or a script it hands a shell), running chi, backgrounding commands, reading the
|
|
28
|
+
environment or the keychain, git settings that run commands, sweeping home with find/tar/cp/grep, the owner-voiced
|
|
29
|
+
tools (delegate, reminders, notes, background tasks,
|
|
30
|
+
forgetting outputs, loading tools, memories) and the model's own question. A pair turn stops mid-turn once the
|
|
31
|
+
participant's cap is passed or their link is revoked or expires. A pair turn's commands run without
|
|
32
|
+
secret-named environment variables and are killed at the execute limit, and one that writes outside the project
|
|
33
|
+
asks the owner. Workers name a `pair_turns` feature.
|
|
34
|
+
- Participant turns show a one-line tools overview with expandable, names-only rows that update as a turn runs.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Sharing a working session warns what a participant's turns may do there (writes included, asks to you) instead of
|
|
39
|
+
only how many tool outputs they can quote. Its participant messages carry a pair header, a `pair` badge and no
|
|
40
|
+
Apply; review sessions, their messages and their headers are unchanged. The system prompt's participants line,
|
|
41
|
+
the cap notice, the participant page's note and the participants panel follow the session's mode; the delivery
|
|
42
|
+
line says "after their turn".
|
|
43
|
+
|
|
44
|
+
Update with `chi update`, then restart `chi web` and the session workers (`chi sessions restart`); a participant
|
|
45
|
+
invited before this release stays a review participant until invited again.
|
|
46
|
+
|
|
47
|
+
## [0.122.0] - 2026-10-27
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Toolkit kits accept an optional `description:` (config `toolkit.kits` and `chi.toolkit`): what the kit is for, read
|
|
52
|
+
by the toolkit suggestion, never sent in a prompt.
|
|
53
|
+
- A memory named `toolkit_<name>` defines a kit (one YAML block with `base`, `add`, `remove`, `groups` and a required
|
|
54
|
+
`description`); project memories win over system ones, config and bundle kits over both, and `/toolkit` shows the
|
|
55
|
+
source. chi's memory guide says when to write one.
|
|
56
|
+
- Toolkit suggestion audit (`toolkit.suggest.mode: audit`, off by default): System 1 is asked which described kit fits
|
|
57
|
+
a new session's first prompt, in the background, without changing the session or delaying its turn; `chi toolkit
|
|
58
|
+
report` grades the suggestions against the tools each session then used.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- `chi sessions restart` on a stopped (SIGSTOPped, Ctrl-Z) worker refuses at once with its pid and `kill -CONT PID`,
|
|
63
|
+
instead of ~70 s of silence before "didn't take the restart".
|
|
64
|
+
- `chi --remote NAME sessions restart` refusals keep their reasons (what the worker still has running) and name the
|
|
65
|
+
remote `chi --remote NAME sessions stop` command.
|
|
66
|
+
- chi web's stream, and a remote terminal, say a running worker "didn't answer" instead of "no live worker".
|
|
67
|
+
- The model's hint about a same-named project's memory folder asks the user first instead of offering an `mv`.
|
|
68
|
+
- A participant's wait answers revoked (401) when their grant disappears mid-wait, not "chi web is stopping".
|
|
69
|
+
- A bad `web.start_dir` says it came from config.yml (not `--start-dir`), and a relative one resolves from home.
|
|
70
|
+
- A remote `chi note` whose reply has no status is a clear refusal instead of a crash.
|
|
71
|
+
|
|
72
|
+
Update with `chi update`, then restart `chi web` and the session workers (`chi sessions restart`).
|
|
73
|
+
|
|
10
74
|
## [0.121.1] - 2026-10-27
|
|
11
75
|
|
|
12
76
|
### Fixed
|
|
@@ -4631,7 +4695,9 @@ and long-lived sessions.
|
|
|
4631
4695
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
4632
4696
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
4633
4697
|
|
|
4634
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
4698
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.123.0...HEAD
|
|
4699
|
+
[0.123.0]: https://github.com/dm1try/samagotchi/compare/v0.122.0...v0.123.0
|
|
4700
|
+
[0.122.0]: https://github.com/dm1try/samagotchi/compare/v0.121.1...v0.122.0
|
|
4635
4701
|
[0.121.1]: https://github.com/dm1try/samagotchi/compare/v0.121.0...v0.121.1
|
|
4636
4702
|
[0.121.0]: https://github.com/dm1try/samagotchi/compare/v0.120.0...v0.121.0
|
|
4637
4703
|
[0.120.0]: https://github.com/dm1try/samagotchi/compare/v0.119.0...v0.120.0
|
data/bin/chi
CHANGED
|
@@ -70,6 +70,8 @@ SUBCOMMANDS = {
|
|
|
70
70
|
"bundle" => ["samagotchi/bundle_command", "BundleCommand"],
|
|
71
71
|
# the System 1 record: replay the asks this machine's sessions already answered
|
|
72
72
|
"guardrails" => ["samagotchi/guardrails_command", "GuardrailsCommand"],
|
|
73
|
+
# audit suggestions for which toolkit fits a session's first prompt
|
|
74
|
+
"toolkit" => ["samagotchi/toolkit_report_command", "ToolkitReportCommand"],
|
|
73
75
|
# other machines' chi web, for chi --remote NAME (add, ls, rm)
|
|
74
76
|
"remote" => ["samagotchi/remote_command", "RemoteCommand"]
|
|
75
77
|
}.freeze
|
|
@@ -230,7 +232,8 @@ if options[:web]
|
|
|
230
232
|
working_set: Samagotchi::Config.get("web.working_set"),
|
|
231
233
|
annotate_presets: Samagotchi::Config.get("web.annotate_presets"),
|
|
232
234
|
editor: Samagotchi::Config.get("web.editor"),
|
|
233
|
-
start_dir: options[:start_dir] || Samagotchi::Config.get("web.start_dir")
|
|
235
|
+
start_dir: options[:start_dir] || Samagotchi::Config.get("web.start_dir"),
|
|
236
|
+
start_dir_from: options[:start_dir] ? :flag : :config)
|
|
234
237
|
end
|
|
235
238
|
|
|
236
239
|
# --resume and --attach take a unique id prefix too, like git.
|
data/docs/architecture.md
CHANGED
|
@@ -502,7 +502,8 @@ reading `bridge.json` or probing a port itself: `WorkerLink.resolve(session_dir,
|
|
|
502
502
|
outcome. The rule is the owner lock plus the sidecar's `pid`; there is no port probe (a port can be reused by another
|
|
503
503
|
session's Bridge, a free lock is the kernel's proof the worker died). In order: no lock holder, or a chi REPL's: a
|
|
504
504
|
sidecar is `Stale(:lock_free)` (removed with `unlink: true`, only if it is still the file that was read), else `None`;
|
|
505
|
-
a holder with no kind yet: `Starting`; a leaving worker: `Leaving
|
|
505
|
+
a holder with no kind yet: `Starting`; a leaving worker: `Leaving` (marked before its Bridge stops, on every way out:
|
|
506
|
+
`Worker#teardown` marks it too, with `TEARDOWN_LEAVE_WAIT`); a worker whose sidecar names no port or another
|
|
506
507
|
pid than the holder's (a worker that has not written its own yet): `Starting`, `stuck` once it has held the lock
|
|
507
508
|
past `START_LIMIT` (30 s) with none; a sidecar whose `protocol` is newer than `WorkerLink::PROTOCOL`: `Skew` (a
|
|
508
509
|
newer worker; never driven, `wake` answers 409 `worker_newer`); else `Live` (its sidecar; `#client` makes the one
|
|
@@ -532,12 +533,23 @@ The snapshot names chi's version: a tab served by another one (its `<body data-v
|
|
|
532
533
|
reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade. It also names
|
|
533
534
|
`installed`, the newest chi on this machine (`InstalledVersions`: the highest `samagotchi-<v>.gemspec`
|
|
534
535
|
in RubyGems' specification dirs, or a checkout's `version.rb` on disk; looked at again on each full
|
|
535
|
-
probe, every 10 s)
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
536
|
+
probe, every 10 s). A `chi` frame and the `GET /api/events` snapshot carry the full state
|
|
537
|
+
`{version, installed, available, update_blocked}`; `/api/info` and the session-command wake response
|
|
538
|
+
carry the same fields. `SessionHub#update_available` publishes the checker result under the hub's
|
|
539
|
+
Monitor, but fetching never holds it.
|
|
540
|
+
|
|
541
|
+
`Web::UpdateCheck` runs its own thread beside the hub. When `update.notify: on` (the default), its
|
|
542
|
+
first RubyGems check is about 30 seconds after chi web starts, then it checks at `update.check_hours`
|
|
543
|
+
intervals. A stable release is available only after `update.min_age_hours` and when newer than both
|
|
544
|
+
the running and newest installed chi. Its atomic, mode-0600
|
|
545
|
+
`$XDG_STATE_HOME/samagotchi/update_check.json` keeps the check cooldown and versions already
|
|
546
|
+
announced across restarts. `update.notify: off` starts no checker thread and makes no network
|
|
547
|
+
request. `update_blocked` names why the local gem cannot be updated in place (checkout, Bundler, or
|
|
548
|
+
an unwritable gem directory). The page tells the owner to run `chi update`; a newer `installed`
|
|
549
|
+
version than the server itself still means restart chi web (its workers already run the newest chi,
|
|
550
|
+
[Sessions](sessions.md)). `GET /api/info` also names `newest_workers`; next to `cwd` (the folder chi
|
|
551
|
+
web runs in) it names `start_dir`, the folder it starts in (`web.start_dir`, `--start-dir`;
|
|
552
|
+
`Web::StartDir`), which a second `chi web` given another one mentions.
|
|
541
553
|
|
|
542
554
|
The page's scope is in its URL, and the server picks the default: `?dir=<folder>` is that folder's
|
|
543
555
|
project (the list, the events stream and a new chat follow it), `?scope=all` every session, and a bare
|
data/docs/cli.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
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))
|
|
28
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))
|
|
29
29
|
- `chi review FILE… [--dir DIR] [--model M]`, `chi review --from ID [FILE…]` — start a review session to invite participants to: for files of a project, or as a review of a working session that starts from its recap, not its transcript (see [Review sessions](#review-sessions))
|
|
30
|
-
- `chi share ID --name NAME [--identity EMAIL] [--agent] [--expires 3d] [--cap 2]`, `chi share ls [ID]`, `chi share cap ID P DOLLARS`, `chi share revoke ID [P] | --all` — invite a participant to
|
|
30
|
+
- `chi share ID --name NAME [--identity EMAIL] [--agent] [--expires 3d] [--cap 2]`, `chi share ls [ID]`, `chi share cap ID P DOLLARS`, `chi share revoke ID [P] | --all` — invite a participant to a session through chi web on the LAN (read-only review turns in a review session, pair turns that act under your guardrails in a working session) (a link of their own, 7 days, a $2 spending cap), list them, change one's cap, revoke them (see [Participants](#participants))
|
|
31
31
|
- `chi --remote NAME send|answer|note|context|sessions|share|review …` (or `CHI_REMOTE=NAME`) — those commands on that machine's sessions, through its chi web (see [Remote sessions](#remote-sessions))
|
|
32
32
|
- `chi --remote NAME --attach ID` (`--resume ID`, `--dir DIR [-p TEXT]`) — the attached TUI on a session of that machine (see [Remote sessions](#remote-sessions))
|
|
33
33
|
- `chi memory ls [--scope project|system] [--format text|json]` — the memories of both scopes (this folder's project first), one row each: scope, name, size, date, `from <bundle>`, the index description and its model overlays; `chi memory rm NAME [--scope project|system]` removes one as `memory_write` with `remove: true` does: its file and model overlays go to the bundle trash (the line names the dir; `chi bundle trash` lists and empties it) and its index line is dropped. NAME in both scopes without `--scope` exits 2 naming both; a missing NAME or a bundle's memory (`chi bundle uninstall` removes it) exits 1 (see [Memory: Listing and removing from the shell](memory.md#listing-and-removing-from-the-shell))
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
- `chi bundle install|upgrade|uninstall|status|diff|list|build|trash|new|test|check` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument. `new`, `test` and `check` are for writing one: see [Writing a bundle](#writing-a-bundle-new-test-check)
|
|
39
39
|
|
|
40
40
|
- `chi guardrails replay [--dry-run] [--threshold T] [--model M]` — seed System 1's record from the approvals this machine's sessions already answered: each ask is judged by the configured model and written to `guardrails/judgments.jsonl` with the answer the user gave, then a per-rule table prints (asks, allowed, denied, would-be auto-allows, false allows, asks with no judgment). A parent agent's answer is not the user's: only its deny is replayed, and only where the parent could have allowed. `--dry-run` judges without writing, `--threshold` overrides `guardrails.system_one.threshold` for the table, `--model` the configured model for this run. Exit 2 when `guardrails.system_one.mode` is off or no `host` is set; a usage error exits 2 (see [System 1](guardrails.md#system-1-experimental))
|
|
41
|
+
- `chi toolkit report [--days N]` — grade toolkit suggestions: the kit on mode would have run (the suggested kit when it would have applied it, else the default; an on-mode line's `did` says what it ran) against the tools the session called from that turn on (only that turn when the toolkit was chosen since), `call_tool` targets included, for both the chat and the raw-prompt loop. Each is *covers*, *short of a tool* (naming the tools), *wider than needed* (a narrower kit offered in the same ask would also have covered) or *ungradable* (calls it can't read back); then a per-kit tally with what on mode would have done. Includes archived sessions and caps the report at 14 days and 500 sessions. Reads `toolkit_suggestions.jsonl` (each ask logs every offered kit's rules, expanded against the called tools here); it never runs the model or changes a session. Local only; `--days` must be a positive whole number.
|
|
41
42
|
|
|
42
43
|
- `chi bundle try PATH -p TEXT [options]` — install a bundle in a temporary, filtered sandbox, run one real model turn,
|
|
43
44
|
and print its trace (see [Trying a bundle](#trying-a-bundle)).
|
|
@@ -232,6 +233,17 @@ Also shipped, not installed: dev (mcp, skills, source-links) (chi bundle install
|
|
|
232
233
|
done
|
|
233
234
|
```
|
|
234
235
|
|
|
236
|
+
- **chi web update notices.** By default, chi web checks RubyGems about 30
|
|
237
|
+
seconds after it starts and every 24 hours after that. Once a stable release
|
|
238
|
+
is at least 24 hours old and newer than both the running chi and the newest
|
|
239
|
+
installed one, the page shows its version, a link to that RubyGems release,
|
|
240
|
+
and a button to copy `chi update`. The notice is once per version per
|
|
241
|
+
browser: a reload or a chi web restart doesn't repeat it.
|
|
242
|
+
`update.notify: off` disables checking; `update.check_hours` and
|
|
243
|
+
`update.min_age_hours` tune the interval and release age (see
|
|
244
|
+
[configuration](configuration.md#all-settings)). If the gem can't be updated
|
|
245
|
+
in place, the notice gives the blocker and its command instead.
|
|
246
|
+
|
|
235
247
|
- **The gem.** It asks rubygems.org for the newest samagotchi (5 s timeout)
|
|
236
248
|
and, when that's newer, runs `gem install samagotchi` with the gem command
|
|
237
249
|
of the Ruby chi runs on (the real one, not a mise/rbenv/asdf shim; a chi
|
|
@@ -1422,6 +1434,25 @@ chi share revoke --all # every link to every session
|
|
|
1422
1434
|
share cap` raises it. `/stats` shows each participant's spend. Their turns go only to a worker with the review
|
|
1423
1435
|
boundary: restart an older one. While your turn waits at the step limit (Continue or Stop), their turns wait too
|
|
1424
1436
|
(still queued, still counted): they run, in order, once you continue, stop or send a turn of your own.
|
|
1437
|
+
- **Pair turns: sharing a working session.** The read-only review turn is a review session's (`chi review`). Invite
|
|
1438
|
+
someone to a working session instead and their turns **act** in it with its tools, writes included, under your
|
|
1439
|
+
guardrails: what your rules allow runs, what they ask about comes to you as "Sam's turn asks: approve tool call?"
|
|
1440
|
+
with **Allow once** only (nothing session-wide or repo-wide is approved from their calls), and System 1 never
|
|
1441
|
+
answers for you. A write outside the project always asks you, whatever you approved before. It can't read or
|
|
1442
|
+
write secrets or chi's own files (keys, shell startup files, histories, credential files; by a file tool, a
|
|
1443
|
+
command, a glob or a variable), run chi, leave a command running in the background, read the environment or the
|
|
1444
|
+
keychain, sweep your home with find/tar/cp/grep, delegate, set or cancel reminders, send notes, start or stop
|
|
1445
|
+
background tasks, load tools, forget outputs or write memories, and it asks its questions in its reply, not on a
|
|
1446
|
+
card. Its commands run without your secret-named environment variables (`KEY`, `TOKEN`, `SECRET`, `PASS`,
|
|
1447
|
+
`CREDENTIAL`, `AUTH` in the name) and are killed at the execute limit rather than left running; a command that
|
|
1448
|
+
writes outside the project asks you, as a write does (the tmp dirs aside). A pair turn stops mid-turn once their
|
|
1449
|
+
cap is passed or their link ends. `chi share` and the panel's Invite
|
|
1450
|
+
warn you with what their turns may do and how many tool outputs the history holds, and only a link issued with
|
|
1451
|
+
that warning acts: links from before, or issued while the session's worker predated pair turns (the warning says
|
|
1452
|
+
so), stay read-only until you restart the worker and invite them again. Their messages carry a `pair` badge and no
|
|
1453
|
+
Apply (they acted already). This is an accident guard for a trusted team, not containment: a pair turn can still
|
|
1454
|
+
send what it reads out through `web_fetch` or your MCP tools. A link without `--identity` gets the same access:
|
|
1455
|
+
share it only with people you'd let type in your session.
|
|
1425
1456
|
- **The participant page.** Their link opens a page of its own (never yours): the session's title and status, who
|
|
1426
1457
|
else takes part, and the conversation as a participant sees it: every prompt and reply with who wrote it, each
|
|
1427
1458
|
step's tools as one line (`read docs/plan.md`: the tool and a project file's path, never arguments or output), and
|
data/docs/configuration.md
CHANGED
|
@@ -163,6 +163,7 @@ or `chi web` restart.
|
|
|
163
163
|
| `append_system_prompt` | Worker start for the file list | The config list is not saved in the session file. File text is read when the prompt is built; `/model` rebuilds it, while an already-built prompt is cached. CLI `--append-system-prompt` files are saved in the session. |
|
|
164
164
|
| `toolkit.default`, `toolkit.child` | Session creation | The chosen toolkit is saved; changing the defaults does not move existing sessions. |
|
|
165
165
|
| `toolkit.kits`, `execute.description`, `llm_context.policy` | Worker start | These are held by the engine's toolkit or tool registry. |
|
|
166
|
+
| `toolkit.suggest.mode`, `toolkit.suggest.threshold`, `toolkit.suggest.defer_turns`, `toolkit.suggest.defer_tokens` | Worker start | Suggestion settings are config-only and are resolved when the worker runs its audit or on-mode ask. |
|
|
166
167
|
| `skip_agents_md` | Prompt rebuild | A running engine caches its built prompt; a new worker or `/model` rebuild uses the changed value. |
|
|
167
168
|
| `context.*` except `context.every_seconds`, `llm_context.*` except `llm_context.policy`, `steer.cut_after`, `memory.index_warn_tokens`, `status.line`, `web.participants.*` | Next matching turn, display, poll or write | Readers resolve these settings when they perform that operation. Session-specific LLM context and thinking values remain saved in the session. A participant's grant keeps the expiry and cap it was issued with; `web.participants.*` is what the next invite gets, and the cap and queue of a grant from before caps. |
|
|
168
169
|
| `context.every_seconds` | Next context poll | The poller checks it for each source that has no explicit interval. |
|
|
@@ -176,7 +177,8 @@ or `chi web` restart.
|
|
|
176
177
|
| `log.*` | Process start | Each process holds its configured log sink and level after first use; restart that process. |
|
|
177
178
|
| `history.file` | Next history access | The TUI and web composer resolve its path when they read or append history. |
|
|
178
179
|
| `broadcast.*` | Next `chi broadcast` | Each broadcast invocation reads its targeting and triage settings. |
|
|
179
|
-
| `update
|
|
180
|
+
| `update.gem`, `update.bundles`, `update.desktop` | Next `chi update` | The command reads which components to update when it runs. |
|
|
181
|
+
| `update.notify`, `update.check_hours`, `update.min_age_hours` | `chi web` start | The update checker starts with chi web; restart it to apply changes. |
|
|
180
182
|
| `kitty.*`, `tmux.*` | `chi desktop upgrade` or desktop update | These settings are copied into the desktop helper's launch file. |
|
|
181
183
|
| `model.profile`, `reasoning_history` | Not from config.yml | These are CLI/environment settings; `models:` and `hosts:` provide their model- and host-specific forms. |
|
|
182
184
|
|
|
@@ -1398,7 +1400,7 @@ toolkit:
|
|
|
1398
1400
|
default: lean # the sessions you start (unset: full)
|
|
1399
1401
|
child: child # delegate children (unset: full)
|
|
1400
1402
|
kits: # your own, on a base
|
|
1401
|
-
audit: { base: lean, add: [web_fetch, delegate], remove: [memory_write] }
|
|
1403
|
+
audit: { base: lean, add: [web_fetch, delegate], remove: [memory_write], description: "Code review and inspection" }
|
|
1402
1404
|
terse: { base: lean, prompt: "- Be concise in your responses" }
|
|
1403
1405
|
web: { base: lean, groups: [chrome] }
|
|
1404
1406
|
coding: { base: lean, groups: [] }
|
|
@@ -1414,10 +1416,51 @@ toolkit:
|
|
|
1414
1416
|
| `review` | `read`, `memory_read` (not `forget_outputs`, even under the `forget` layer): what a [participant's](internals/participants.md#turns) turn may call, whatever the session declares (the review boundary, [guardrails](guardrails.md#a-participants-review-boundary)). Reserved: `toolkit.kits.review` is refused ("review is reserved for participants' turns"), and a bundle can't declare it. A session may run on it too, as any kit |
|
|
1415
1417
|
|
|
1416
1418
|
- **Plugin tools** are declared in every toolkit unless its `remove:` names them, and so is `forget_outputs` under
|
|
1417
|
-
the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:`, `prompt
|
|
1419
|
+
the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:`, `prompt:`, `groups:` and
|
|
1420
|
+
`description:`
|
|
1418
1421
|
(any other key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
|
|
1419
1422
|
(`review`'s least of all: it is the participants' boundary). A bundle declares its own with `chi.toolkit`
|
|
1420
|
-
([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil-groups-nil)).
|
|
1423
|
+
([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil-groups-nil-description-nil)).
|
|
1424
|
+
`description:` is optional classifier input, up to 300 characters; it is not included in the session prompt.
|
|
1425
|
+
- **Memory kits.** A memory named `toolkit_<name>.md` defines kit `<name>` with one fenced YAML block containing
|
|
1426
|
+
optional `base:`, `add:`, `remove:` and `groups:`, plus a required `description:` (up to 300 characters).
|
|
1427
|
+
`prompt:` is refused. A memory kit may use a config or bundle kit as its base. Built-ins cannot be redefined.
|
|
1428
|
+
The precedence is built-ins, config, bundles, then memory kits; within memories, project scope wins over system
|
|
1429
|
+
scope. Muted memory kits are skipped, and `/toolkit` shows each one's `memory:project` or `memory:system` source.
|
|
1430
|
+
Kits are read once per worker, so changes take effect when a new worker starts. See
|
|
1431
|
+
[Toolkit memories](memory.md#toolkit-memories) for the format and when to create one.
|
|
1432
|
+
- **Audit suggestions.** `toolkit.suggest.mode: audit` asks System 1 which described kit fits an eligible new session's
|
|
1433
|
+
prompt, without changing its toolkit or delaying the turn. It offers the configured default as the fallback and
|
|
1434
|
+
`undecided` for greetings or check-ins. Only a default-origin session with at least two described candidates is
|
|
1435
|
+
asked; chosen or inherited toolkits, delegate children, review sessions and participant turns are skipped. After
|
|
1436
|
+
`undecided`, later owner prompts may be asked again within both defer limits. The default is `off`. System 1's
|
|
1437
|
+
host, model and timeout come from `guardrails.system_one.*`; the suggestion threshold and defer limits are its own
|
|
1438
|
+
settings below. Suggestions are
|
|
1439
|
+
kept in `toolkit_suggestions.jsonl`; `chi toolkit report` grades them against the session's tool calls. One ask
|
|
1440
|
+
runs at a time (a turn that comes while one still runs goes unasked, with an info line), and a kit that doesn't
|
|
1441
|
+
resolve (a missing base, a cycle) is left out of the candidates. A worker that exits waits about a second for a
|
|
1442
|
+
running ask, so a one-shot `-p --non-interactive` run may end before its suggestion is logged.
|
|
1443
|
+
- **On mode.** `toolkit.suggest.mode: on` asks the same question on the same eligible turns, but the turn waits for
|
|
1444
|
+
the answer before it declares any tool, for at most `guardrails.system_one.timeout_ms` from the ask's start (it
|
|
1445
|
+
starts right after the turn does, so plugin and MCP start-up hide most of it). A described kit picked with
|
|
1446
|
+
confidence at or above `toolkit.suggest.threshold` becomes the session's toolkit, saved with `toolkit_origin:
|
|
1447
|
+
suggested`, and the turn's first request declares it; the notice reads
|
|
1448
|
+
`toolkit: web (suggested, p=0.71) · /toolkit to change`. Anything else (the default, a low-confidence or unknown
|
|
1449
|
+
pick, `undecided`, no answer, a failing System 1, which warns once as `[samagotchi:toolkit]`) keeps the default
|
|
1450
|
+
kit. A Stop during the wait (or before it, even with the answer in) ends the turn before its first request, and
|
|
1451
|
+
nothing switches. After an `undecided`, a later prompt within the defer limits may switch the session; that notice
|
|
1452
|
+
says `from this turn`, and that request re-reads the whole prompt (system prompt, tools and conversation, which
|
|
1453
|
+
the defer limits keep small) without `/toolkit`'s cache confirmation. `/toolkit` or `ctx.use_toolkit` marks the
|
|
1454
|
+
toolkit chosen, and nothing is suggested after that; a `/toolkit` sent during a turn runs after it and replaces
|
|
1455
|
+
that turn's suggestion. When System 1's host is the model's own local server (the same `hosts:` name or server
|
|
1456
|
+
root, as audit checks), on mode asks only on a session's first turn, never on the deferred path, and sends no
|
|
1457
|
+
warm-up (one info line). Otherwise an idle worker for an eligible session sends one warm-up ask with the real
|
|
1458
|
+
question and a stand-in prompt so the server has its prefix cached; its answer is dropped and never logged, and
|
|
1459
|
+
the first real ask stops a warm-up still running. Each answered ask's line in `toolkit_suggestions.jsonl` adds
|
|
1460
|
+
`did`: `suggested`, `default`, `defer`, `chosen` (the toolkit was no longer the default), `unavailable` (the
|
|
1461
|
+
picked kit was gone), `timeout` or `canceled` (written when the late answer arrives; a Stop after a timeout
|
|
1462
|
+
doesn't cut it), or `failed` (the turn failed or the switch couldn't be saved); `chi toolkit report` grades
|
|
1463
|
+
on-mode lines by what they did.
|
|
1421
1464
|
- **Tool-index groups.** `groups:` selects source groups by name or glob (`chrome`, `db*`; any case, `{a,b}` braces,
|
|
1422
1465
|
as the guardrails rules' `models:`); for the MCP bundle each group is a server. Anything but a name or a list (a
|
|
1423
1466
|
map such as `{chrome: true}`) refuses every new session on that kit or on a kit built on it, on every surface (chi,
|
|
@@ -1982,6 +2025,10 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1982
2025
|
| `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). |
|
|
1983
2026
|
| `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". |
|
|
1984
2027
|
| `toolkit.child` | `full` | | A delegate child's toolkit, unless its `delegate` call names one (`toolkit:`). See "Toolkits". |
|
|
2028
|
+
| `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. |
|
|
2029
|
+
| `toolkit.suggest.threshold` | `0.5` | | Minimum confidence for a non-default toolkit suggestion. config.yml only. |
|
|
2030
|
+
| `toolkit.suggest.defer_turns` | `3` | | Maximum owner-prompt number for another ask after `undecided` (the owner's own prompts: not steers, notes, reminders or `!` command output). config.yml only. |
|
|
2031
|
+
| `toolkit.suggest.defer_tokens` | `5000` | | Maximum estimated conversation size for another ask after `undecided` (characters / 4). config.yml only. |
|
|
1985
2032
|
| `reasoning_history` | `turn` | | Which of its reasoning a chat host's model gets back: `drop`, `turn`, `all`; environment only (`SAMAGOTCHI_REASONING_HISTORY`), below a session's own level (`/reasoning-history`), above the `models:`/`hosts:` entries. See "Reasoning history". |
|
|
1986
2033
|
| `thinking.level` | `default` | `--thinking` | `off`, `low`, `medium`, `high` or `default` for every model; the flag and env outrank the `models:`/`hosts:` entries, the file's value doesn't; a session's own level outranks them all, and `--thinking` sets it on a session run (on `chi web` it sets this). See "Thinking". |
|
|
1987
2034
|
| `models.<key>.thinking`, `hosts.<name>.thinking` | none | | A model's or host's level. See "Thinking". |
|
|
@@ -2003,6 +2050,9 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
2003
2050
|
| `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
|
|
2004
2051
|
| `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
|
|
2005
2052
|
| `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
|
|
2053
|
+
| `update.notify` | `on` | | `on`: chi web checks RubyGems once a day and shows aged stable releases newer than both this chi and the newest installed chi; `off`: no thread or network check. |
|
|
2054
|
+
| `update.check_hours` | `24` | | Hours between RubyGems checks (at least 1). The first check starts about 30 seconds after chi web starts. |
|
|
2055
|
+
| `update.min_age_hours` | `24` | | A release must be this old before chi web offers it; `0` disables the age delay. |
|
|
2006
2056
|
| `kitty.listen_on` | none | | The desktop helper's kitty socket, copied from `kitty.conf`; unset = no kitty targets. See "Desktop helper: agents in kitty". |
|
|
2007
2057
|
| `kitty.binary` | `/Applications/kitty.app/Contents/MacOS/kitty` | | The kitty the helper runs `kitty @` with. |
|
|
2008
2058
|
| `kitty.agents` | `claude\|codex\|gemini\|aider\|opencode\|cursor-agent\|amp\|goose` | | Foreground programs whose kitty windows the helper lists, `\|`-separated (a YAML list works too); `*` lists every window. |
|
|
@@ -2027,7 +2077,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
2027
2077
|
| `web.working_set` | `true` | yes | The stage view's working set: the files, memories and URLs a turn brought into the model's context, in a rail in the stage, a footprint line under each finished turn and a `set N` chip in place of the mem chip; `false` brings the mem chip back. `?working_set=1` or `?working_set=0` on the page URL overrides it for that page load; the turn view ignores it. See [CLI: the working set](cli.md#the-working-set). |
|
|
2028
2078
|
| `web.annotate_presets` | `Agreed\|Could you please elaborate?` | yes | Quick replies next to Annotate in `chi web`, `\|`-separated (a YAML list works too); `""` in the file or on the CLI leaves only Annotate (an empty env value means the default). See [CLI](cli.md#web-annotate-presets). |
|
|
2029
2079
|
| `web.editor` | `vscode` | yes | What a file ref in `chi web` (a file tool row's title, a diff line) opens, for a viewer on this machine: a preset (`vscode`, `vscode-insiders`, `cursor`, `zed`) or a URL template with `{path}` and an optional `{line}`; `none` turns refs off. See [Opening files from chi web](#opening-files-from-chi-web). |
|
|
2030
|
-
| `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a repo on every session, and a new session from the all view starts there; `/?scope=all` is every project. `~` and a relative path work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
|
|
2080
|
+
| `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a repo on every session, and a new session from the all view starts there; `/?scope=all` is every project. `~` and a relative path (resolved from your home folder) work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
|
|
2031
2081
|
| `web.participants.expires` | `7d` | | How long a participant's link lasts unless `chi share --expires` says otherwise: `Nh` or `Nd`, 1h to 30d. See [participants](internals/participants.md). |
|
|
2032
2082
|
| `web.participants.cap` | `2.0` | | What a participant's turns, and the side calls they cause, may cost you in dollars unless `chi share --cap` says otherwise ($0 to $1000); at the cap their next comment is refused (429 `cap_reached`) and you get a notice. `chi share cap ID P DOLLARS` changes one grant's. |
|
|
2033
2083
|
| `web.participants.queue` | `3` | | How many of a participant's turns may wait behind their running one (0 to 20); one more is refused (429 `too_many_turns`). |
|
data/docs/guardrails.md
CHANGED
|
@@ -315,8 +315,11 @@ ordinary sessions don't get it. Like the rules it reads command text: a
|
|
|
315
315
|
|
|
316
316
|
## A participant's review boundary
|
|
317
317
|
|
|
318
|
-
Built in, for a [participant's](internals/participants.md#turns) turns
|
|
319
|
-
turn whose author is `p:<id
|
|
318
|
+
Built in, for a [participant's](internals/participants.md#turns) turns in a
|
|
319
|
+
**review session** only (a turn whose author is `p:<id>`, in a session with a
|
|
320
|
+
`review:` key: `ParticipantMode.of` is `:review`; a working session's
|
|
321
|
+
participant turns get the [pair boundary](#a-participants-pair-boundary)
|
|
322
|
+
instead), in every mode (also with
|
|
320
323
|
`guardrails.enabled: false`) and without the guardrails bundle:
|
|
321
324
|
`Guardrails::ReviewBoundary`, rule `review-boundary`, source `core`. It comes
|
|
322
325
|
right after the load-failure check (rules that don't load deny every call) and before the hooks' asks, the
|
|
@@ -359,6 +362,96 @@ config can't widen the boundary. Hooks still run on a participant's turn, with
|
|
|
359
362
|
the owner's power ([hooks](hooks.md#participants-turns)); a hook that rewrites a
|
|
360
363
|
call is checked on the call it leaves.
|
|
361
364
|
|
|
365
|
+
## A participant's pair boundary
|
|
366
|
+
|
|
367
|
+
A participant's turn in a **working session** acts with the session's tools
|
|
368
|
+
under the owner's guardrails, as the owner's own turns do: the rules, the
|
|
369
|
+
protected paths and every approval the owner already granted apply, and an ask
|
|
370
|
+
goes to the owner. It is a pair turn only when all three hold
|
|
371
|
+
(`ParticipantMode.for_turn`, decided once at the turn's start:
|
|
372
|
+
`GuardrailWiring#turn_mode`): the session has no `review:` key, its worker
|
|
373
|
+
names the `pair_turns` feature, and the participant's grant was issued with
|
|
374
|
+
`mode: pair`, which chi share and the web's Invite stamp only when the warning
|
|
375
|
+
they showed said their turns act. A grant from before, or one issued while an
|
|
376
|
+
older worker held the session, keeps review turns until it is invited again.
|
|
377
|
+
|
|
378
|
+
`Guardrails::PairBoundary` (rule `pair-boundary`, source `core`) sits in the
|
|
379
|
+
review boundary's seat in `GuardrailWiring#checks`, in every mode (also with
|
|
380
|
+
`guardrails.enabled: false`), and **denies, never asks**, each with its own
|
|
381
|
+
message:
|
|
382
|
+
|
|
383
|
+
- secrets, read or written: the review boundary's denylist (chi's state and
|
|
384
|
+
config dirs, so `grants.json`, the web token and every `participants.json`;
|
|
385
|
+
`~/.ssh`, `~/.gnupg`, `~/.aws`, `~/.config/gh`, `~/.config/fish`,
|
|
386
|
+
`~/.config/git`, `~/Library/LaunchAgents`, `~/.local/bin`; the home's shell
|
|
387
|
+
startup files, histories, `.gitconfig`, `.netrc`, `.npmrc`, `.pypirc`, `.claude.json`;
|
|
388
|
+
the project's secret files, any config under `.git`) on every file tool's
|
|
389
|
+
path (`read`, `write`, `edit`, a plugin's `targets:` paths) and on the paths
|
|
390
|
+
a command names (`execute`, and any call whose targets carry a command),
|
|
391
|
+
each word resolved as `touches: chi_dirs` resolves it; a glob that matches a
|
|
392
|
+
secret or whose fixed part leads into a denied folder (`~/.ss?/id_*`,
|
|
393
|
+
`~/.config/*/config.yml`, `.en?`), a `$var` word whose fixed part does
|
|
394
|
+
(`~/.ss${d}h`), and a text match for the rest;
|
|
395
|
+
- sweeping a folder that holds secrets: `find`, `tar`, `cp`, `rsync`, `scp`,
|
|
396
|
+
`zip`, `ditto`, `grep`/`rg`/`ag` and the like given home or any folder above
|
|
397
|
+
a denied one;
|
|
398
|
+
- the environment and the keychain: `env` or `printenv` alone, `set`,
|
|
399
|
+
`export`, `declare` or `typeset` that list, `launchctl getenv`, `ps e`,
|
|
400
|
+
`security`, `/proc/*/environ`. On top of it, a pair turn's `execute` runs
|
|
401
|
+
without every variable whose name matches `KEY|TOKEN|SECRET|PASS|CREDENTIAL|AUTH`
|
|
402
|
+
(`PairBoundary::SECRET_ENV_NAME`, case-insensitive), and a command still
|
|
403
|
+
running at the limit is killed, never handed over as a background task;
|
|
404
|
+
- git settings that make git run a command (`core.hooksPath`,
|
|
405
|
+
`core.fsmonitor`, `core.sshCommand`, `core.pager`, `core.editor`, a `!`
|
|
406
|
+
alias) by `git config` or `git -c`, and `git config --global`/`--system`;
|
|
407
|
+
- running chi, by any wrapper (`bin/chi`, `bundle exec chi`, `ruby … bin/chi`,
|
|
408
|
+
`env … chi`, `watch`, `npx`, `sh -c 'chi …'`, `$(chi …)`), in an assignment's
|
|
409
|
+
value or an alias, a function, after `-exec`/`-c`/`-e`, but `chi --version` and
|
|
410
|
+
`chi --help`: its commands act in the owner's name (shares, caps, sends,
|
|
411
|
+
answers, sessions);
|
|
412
|
+
- leaving something running: `&` at a command's end, `nohup`, `setsid`,
|
|
413
|
+
`disown`, `screen -d…`, `tmux new -d`;
|
|
414
|
+
- owner-voiced tools, which act in the owner's name beyond the turn:
|
|
415
|
+
`delegate` and any `delegate_*` tool, `register_reminder`,
|
|
416
|
+
`cancel_reminder`, `send_note`, `forget_outputs`, `task_create`,
|
|
417
|
+
`task_stop`, `find_tools` (it loads tools into the session), and
|
|
418
|
+
`memory_write` (a memory reaches the owner's later sessions);
|
|
419
|
+
- `ask_user_question`, with the advice to ask in the reply: nobody on the
|
|
420
|
+
participant's side answers a card.
|
|
421
|
+
|
|
422
|
+
A script a command hands on is read too (`Guardrails::PairCommand`): one a
|
|
423
|
+
shell runs (`sh -c`, `eval`, a heredoc or here-string) as a command of its
|
|
424
|
+
own, one another interpreter runs (`ruby -e`, `python - <<EOF`) by its text
|
|
425
|
+
(chi, secrets, the environment, detaching). A heredoc written as data (`cat`,
|
|
426
|
+
`tee`, `git commit -F -`) is not looked into: where it goes is checked. A
|
|
427
|
+
command the checks can't read is refused, never let through on a text match.
|
|
428
|
+
|
|
429
|
+
And it **asks the owner** before a `write` or `edit` outside the session's
|
|
430
|
+
project root, and before a command that writes there (a redirection's target,
|
|
431
|
+
`tee`'s files, a `cp`/`mv`/`ln`/`install`/`rsync`/`ditto` destination, or a
|
|
432
|
+
target it can't resolve), every time: the ask is fresh (`Verdict#fresh_ask`: no
|
|
433
|
+
stored approval answers it, and no rule lowers it), so it comes even in auto
|
|
434
|
+
mode. The tmp dirs (`Dir.tmpdir`, `$TMPDIR`, `/tmp`, `/private/tmp`) and
|
|
435
|
+
`/dev/null` neither ask nor deny.
|
|
436
|
+
|
|
437
|
+
A participant's ask names them on the owner's card ("Sam's turn asks: approve
|
|
438
|
+
tool call?", `approval.author`/`author_name`, `approval.asked_by`, the same
|
|
439
|
+
line in the plain question: `Approval::ASKED_BY`) and offers **Allow once**
|
|
440
|
+
only: no session, repo or rule approval is made from a participant's call.
|
|
441
|
+
System 1 never judges it, and `chi guardrails replay` skips it. A pair turn
|
|
442
|
+
stops before its next generation once the participant passed their spending
|
|
443
|
+
cap or their link was revoked or expired
|
|
444
|
+
([participants](internals/participants.md#limits-and-the-cap)).
|
|
445
|
+
|
|
446
|
+
The trust model is a team's (everyone invited is trusted): the boundary is an
|
|
447
|
+
**accident guard** for secrets and the owner's voice, not containment of a
|
|
448
|
+
hostile participant; a review session is. A pair turn can still reach, through
|
|
449
|
+
the owner's rules: `web_fetch` (an open egress path: what the turn can read it
|
|
450
|
+
can send out in a URL), plugin and MCP tools, `memory_read` and `context_read`,
|
|
451
|
+
`list_sessions` (the owner's other sessions' titles), and anything a script
|
|
452
|
+
does that the command checks don't read (a script file's body, an interpreter's
|
|
453
|
+
`-e` code). Share a working session only with people you would let type in it.
|
|
454
|
+
|
|
362
455
|
## Rules in config.yml
|
|
363
456
|
|
|
364
457
|
```yaml
|
|
@@ -666,6 +759,23 @@ a body that isn't JSON, a question with no answer, a request longer than
|
|
|
666
759
|
`timeout_ms`. A nil means "ask the user", which is what the caller would
|
|
667
760
|
have done anyway.
|
|
668
761
|
|
|
762
|
+
Toolkit suggestions are a second use of System 1. When
|
|
763
|
+
`toolkit.suggest.mode` is `audit` or `on`, the engine asks a `choice`
|
|
764
|
+
question from a background thread after the turn starts. This uses the same
|
|
765
|
+
configured host, model and timeout, but is gated by `toolkit.suggest.mode`,
|
|
766
|
+
not `guardrails.system_one.mode`. Audit never changes the session's toolkit;
|
|
767
|
+
on mode's turn waits for the answer (at most `timeout_ms`, cut by a Stop)
|
|
768
|
+
and may start the session on the suggested kit (configuration.md, Toolkits).
|
|
769
|
+
Its warnings use the `toolkit` tag and its records are written to
|
|
770
|
+
`toolkit_suggestions.jsonl` beside the guardrail logs. To avoid competing
|
|
771
|
+
with the session's model, audit skips (one info line) when
|
|
772
|
+
the System 1 host is the model's local host: the same `hosts:` name, or
|
|
773
|
+
another entry for the same server root (scheme, host and port). There is no
|
|
774
|
+
reliable single-slot signal, so this host-identity check is the available
|
|
775
|
+
contention guard. The session's own checks come first; the kits and the
|
|
776
|
+
suggestion log are read only for a session that can be asked, the log only
|
|
777
|
+
after an earlier `undecided`.
|
|
778
|
+
|
|
669
779
|
### What the audit writes
|
|
670
780
|
|
|
671
781
|
With a client, `GuardrailWiring#request_approval` asks System 1 one `noul`
|
|
@@ -682,8 +792,8 @@ user's **earlier asks they answered** (`SystemOne::Similar`) — this rule in
|
|
|
682
792
|
this repository first, then this rule anywhere, then any answered ask, ranked
|
|
683
793
|
by how many words the logged call shares with this one, ties going to the
|
|
684
794
|
newest. Reading the log is best-effort, so a log that cannot be read simply
|
|
685
|
-
offers no examples. A participant's review
|
|
686
|
-
user there to ask. The judgment runs before the card appears, so an ask is
|
|
795
|
+
offers no examples. A participant's turn (review or pair) is never judged:
|
|
796
|
+
there is no user there to ask, and its asks go to the owner. The judgment runs before the card appears, so an ask is
|
|
687
797
|
slower by up to `timeout_ms` (2000 ms by default).
|
|
688
798
|
|
|
689
799
|
Each judgment is a line in `judgments.jsonl`, beside `approvals.json` in
|
|
@@ -970,9 +1080,11 @@ send; [internals/participants.md](internals/participants.md#apply)). A quote
|
|
|
970
1080
|
lowers the risk, it doesn't remove it: your line still asks the model to act
|
|
971
1081
|
on what it quotes, with your tools, so read the quote before you send it.
|
|
972
1082
|
|
|
973
|
-
Within its own turn a participant's text can't act: the
|
|
974
|
-
leaves it reads of the project only, and an injection that
|
|
975
|
-
model still meets the boundary (containment over detection).
|
|
1083
|
+
Within its own turn in a review session a participant's text can't act: the
|
|
1084
|
+
review boundary leaves it reads of the project only, and an injection that
|
|
1085
|
+
convinces the model still meets the boundary (containment over detection). In
|
|
1086
|
+
a working session it acts, by design ([the pair boundary](#a-participants-pair-boundary)):
|
|
1087
|
+
invite only people you would let type in your session, under your guardrails. What it can do
|
|
976
1088
|
is make the model *say* what the history holds: a review turn's model sees the
|
|
977
1089
|
whole conversation, earlier tool outputs included, so start a review in a
|
|
978
1090
|
fresh session (or a continue), not on a working session full of outputs. A
|
data/docs/internals/engine.md
CHANGED
|
@@ -35,19 +35,28 @@ Engine#run_turn(session, prompt, on_event:, max_iterations:, cancel_controller:,
|
|
|
35
35
|
│ # the user message's and the reminders' stamp too), continue: true (only on a continue),
|
|
36
36
|
│ # resumed: true (only on a step-limit Continue: resumed: given),
|
|
37
37
|
│ # images (only when there are any), cut_after (the model's cut wait, s)}
|
|
38
|
+
│ # start_toolkit_suggestion: ToolkitSuggest's ask on its own thread from a copied snapshot (audit and on
|
|
39
|
+
│ # mode: one eligibility, one thread). Audit never blocks the turn or changes the session; on mode
|
|
40
|
+
│ # returns a ToolkitSuggest::Pending the turn joins below. See configuration.md (Toolkits) and
|
|
41
|
+
│ # guardrails.md (System 1).
|
|
38
42
|
│ # refresh_profile! (may ask the server's /props once)
|
|
39
43
|
│ # turn.settings = LLM::TurnSettings (vision, sampling, thinking; model_name, loads_tools … are set in
|
|
40
44
|
│ # generate, after the hooks, from the final model),
|
|
41
45
|
│ # from one EffectiveModel; ThinkingNotices#turn_started keeps the level and says one the host can't do
|
|
42
46
|
│ # announces guardrail load failures, starts and awaits plugin init tasks, applies staged tools,
|
|
47
|
+
│ # then (on mode) settle_toolkit_suggestion: waits for the ask up to System 1's timeout from its
|
|
48
|
+
│ # start, or a Stop (which wins over an answer already in, and cuts the ask's request through
|
|
49
|
+
│ # Pending#cancel_controller; a Stop after a timeout doesn't), and on a candidate picked ≥ threshold
|
|
50
|
+
│ # ToolkitBinding#suggest sets the session's toolkit, origin suggested, saved, with the notice;
|
|
43
51
|
│ # then the session's toolkit (apply_toolkit!: Registry#toolkit, ToolIndex#refresh! for find_tools'
|
|
44
|
-
│ # chi line, a prompt rebuild when it changed)
|
|
52
|
+
│ # chi line, a prompt rebuild when it changed). A Stop during the probes or the waits returns false
|
|
53
|
+
│ # (the ask's line says canceled; an ensure marks one the turn never settled failed).
|
|
45
54
|
│ # fires the :session_start hook on the Engine's first turn, then the :before_turn hook
|
|
46
55
|
│ # (a frozen copy of the history, the prompt, and resumed)
|
|
47
56
|
│ # turn_messages: system head + history + due reminders (:reminder_injected) + the user message
|
|
48
57
|
│ # + the plugins' staged tool loads (ctx.load_tools: a kind "tool_change" system entry per bundle,
|
|
49
58
|
│ # only when the turn's host takes tool changes; #append_tool_loads)
|
|
50
|
-
│ # Returns false when a Stop came during the probes or the
|
|
59
|
+
│ # Returns false when a Stop came during the probes, the init wait or the toolkit ask, else true
|
|
51
60
|
│
|
|
52
61
|
├── generate(turn, max_iterations:, max_tool_output_chars:, pending_input:)
|
|
53
62
|
│ # one EffectiveModel (after the hooks, which may switch the model): sync_kernel_client!, then
|