samagotchi 0.121.0 → 0.122.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 +110 -1
- data/bin/chi +7 -3
- data/docs/architecture.md +37 -11
- data/docs/bundle-authoring.md +2 -1
- data/docs/cli.md +11 -4
- data/docs/configuration.md +36 -7
- data/docs/guardrails.md +63 -19
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/engine.md +2 -0
- data/docs/internals/participants.md +16 -8
- data/docs/internals/remote-sessions.md +15 -11
- data/docs/internals/web-push.md +22 -11
- data/docs/memory.md +33 -0
- data/docs/plugins.md +8 -3
- data/docs/sessions.md +21 -0
- data/docs/testing.md +41 -1
- data/lib/samagotchi/answer_command.rb +1 -1
- data/lib/samagotchi/bridge/turn_accumulator.rb +3 -1
- data/lib/samagotchi/bridge.rb +12 -11
- data/lib/samagotchi/bridge_client/event_stream.rb +14 -26
- data/lib/samagotchi/bridge_client/stream_open.rb +85 -0
- data/lib/samagotchi/bridge_client/stream_request.rb +8 -2
- data/lib/samagotchi/bridge_client.rb +52 -65
- data/lib/samagotchi/bridge_secret.rb +9 -7
- data/lib/samagotchi/bundle_command/check.rb +27 -2
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +20 -6
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/scripts/pair +14 -3
- data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
- data/lib/samagotchi/bundles/github-pr/plugin.rb +2 -6
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +13 -0
- data/lib/samagotchi/child_move.rb +5 -7
- data/lib/samagotchi/children_status.rb +2 -1
- data/lib/samagotchi/config.rb +5 -0
- data/lib/samagotchi/engine.rb +107 -1
- data/lib/samagotchi/event_fanout.rb +0 -1
- data/lib/samagotchi/guardrail_wiring.rb +24 -6
- data/lib/samagotchi/guardrails/parent_approvals.rb +9 -0
- data/lib/samagotchi/guardrails_command.rb +41 -6
- data/lib/samagotchi/image_store.rb +6 -3
- data/lib/samagotchi/interrupted_turn.rb +6 -3
- data/lib/samagotchi/live_versions.rb +6 -7
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/memory_bundle/profile.rb +1 -1
- data/lib/samagotchi/memory_bundle/uninstaller.rb +6 -2
- data/lib/samagotchi/memory_orphans.rb +11 -0
- data/lib/samagotchi/participant_mode.rb +11 -0
- data/lib/samagotchi/plugin/api.rb +14 -8
- data/lib/samagotchi/plugin/sessions.rb +13 -4
- data/lib/samagotchi/process_group.rb +13 -0
- data/lib/samagotchi/push_command.rb +1 -1
- data/lib/samagotchi/relay_verifier.rb +2 -3
- data/lib/samagotchi/relay_watcher.rb +2 -2
- data/lib/samagotchi/remote_attach_command.rb +1 -1
- data/lib/samagotchi/reply_wait/poll.rb +8 -2
- data/lib/samagotchi/reply_wait.rb +4 -3
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/review_start.rb +6 -2
- data/lib/samagotchi/run_options.rb +2 -0
- data/lib/samagotchi/session.rb +18 -1
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +18 -8
- data/lib/samagotchi/session_access/remote.rb +37 -12
- data/lib/samagotchi/session_access/web_bridge_client.rb +38 -19
- data/lib/samagotchi/session_access.rb +21 -9
- data/lib/samagotchi/session_continue.rb +2 -3
- data/lib/samagotchi/session_manager.rb +133 -29
- data/lib/samagotchi/session_setup.rb +8 -4
- data/lib/samagotchi/sessions_command.rb +10 -5
- data/lib/samagotchi/steer.rb +7 -6
- data/lib/samagotchi/system_one/client.rb +12 -6
- data/lib/samagotchi/system_one/fake_backend.rb +0 -2
- data/lib/samagotchi/system_one/log.rb +44 -9
- data/lib/samagotchi/system_one/self_protect.rb +39 -43
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +8 -3
- data/lib/samagotchi/terminal_ui/attached_loop.rb +4 -2
- data/lib/samagotchi/terminal_ui/local_link.rb +28 -19
- data/lib/samagotchi/terminal_ui.rb +2 -1
- data/lib/samagotchi/tool_runner.rb +2 -1
- data/lib/samagotchi/toolkit.rb +95 -10
- data/lib/samagotchi/toolkit_binding.rb +115 -7
- data/lib/samagotchi/toolkit_command.rb +15 -3
- data/lib/samagotchi/toolkit_report_command.rb +318 -0
- data/lib/samagotchi/toolkit_suggest.rb +211 -0
- data/lib/samagotchi/tools/delegate.rb +1 -1
- data/lib/samagotchi/tools/delegate_relay.rb +9 -7
- data/lib/samagotchi/tools/memory.rb +1 -1
- data/lib/samagotchi/turn_checkpoint.rb +15 -9
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +127 -69
- data/lib/samagotchi/web/participant_access.rb +30 -8
- data/lib/samagotchi/web/participant_review.rb +8 -5
- data/lib/samagotchi/web/participant_view.rb +7 -3
- data/lib/samagotchi/web/participants_admin.rb +13 -0
- data/lib/samagotchi/web/principal.rb +3 -4
- data/lib/samagotchi/web/public/app.js +1 -1
- data/lib/samagotchi/web/public/bubbles.js +3 -2
- data/lib/samagotchi/web/public/notifications.js +21 -1
- data/lib/samagotchi/web/public/participant.html +3 -2
- data/lib/samagotchi/web/public/participant.js +25 -10
- data/lib/samagotchi/web/public/participants_panel.js +46 -11
- data/lib/samagotchi/web/public/push.js +4 -3
- data/lib/samagotchi/web/public/push_devices.js +17 -3
- data/lib/samagotchi/web/public/review_history.js +14 -2
- data/lib/samagotchi/web/public/review_start.js +1 -3
- data/lib/samagotchi/web/public/session_actions.js +14 -2
- data/lib/samagotchi/web/public/storage.js +16 -0
- data/lib/samagotchi/web/public/turn_events.js +2 -1
- data/lib/samagotchi/web/public/turn_html.js +1 -1
- data/lib/samagotchi/web/push/client.rb +5 -0
- data/lib/samagotchi/web/push/store.rb +16 -3
- data/lib/samagotchi/web/push_routes.rb +3 -2
- data/lib/samagotchi/web/server.rb +2 -2
- data/lib/samagotchi/web/session_access_routes.rb +28 -20
- data/lib/samagotchi/web/session_control.rb +4 -2
- data/lib/samagotchi/web/session_create.rb +4 -4
- data/lib/samagotchi/web/session_hub.rb +6 -4
- data/lib/samagotchi/web/session_summary.rb +8 -16
- data/lib/samagotchi/web/start_dir.rb +10 -5
- data/lib/samagotchi/worker.rb +17 -3
- data/lib/samagotchi/worker_link.rb +214 -0
- data/lib/samagotchi/worker_sidecar.rb +15 -37
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ba0370c70db9c98a500766ef59cd8cedbf574852c60625679ed84c6a78d50d0
|
|
4
|
+
data.tar.gz: 937ffbf1066bc4acd34aaaf0ac19680c5099191f0bc87b8681863b362a280940
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c351af56a92b5df2cfabbd2d8a52ffefd76b1d7be74a92ffacd25f035d5ab6951214aa083a4d016beefd924cf2794d802c91ddf9ca4b437c860716f99e13ea76
|
|
7
|
+
data.tar.gz: f00a99184817060ca6d8741bee006bbdcacc3438896a48801a49e6694f4cb2c41868cbabca6f7cd27f807a475f752bf1a62acc580104159036dc033f7611965c
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,113 @@ 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.122.0] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Toolkit kits accept an optional `description:` (config `toolkit.kits` and `chi.toolkit`): what the kit is for, read
|
|
15
|
+
by the toolkit suggestion, never sent in a prompt.
|
|
16
|
+
- A memory named `toolkit_<name>` defines a kit (one YAML block with `base`, `add`, `remove`, `groups` and a required
|
|
17
|
+
`description`); project memories win over system ones, config and bundle kits over both, and `/toolkit` shows the
|
|
18
|
+
source. chi's memory guide says when to write one.
|
|
19
|
+
- Toolkit suggestion audit (`toolkit.suggest.mode: audit`, off by default): System 1 is asked which described kit fits
|
|
20
|
+
a new session's first prompt, in the background, without changing the session or delaying its turn; `chi toolkit
|
|
21
|
+
report` grades the suggestions against the tools each session then used.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- `chi sessions restart` on a stopped (SIGSTOPped, Ctrl-Z) worker refuses at once with its pid and `kill -CONT PID`,
|
|
26
|
+
instead of ~70 s of silence before "didn't take the restart".
|
|
27
|
+
- `chi --remote NAME sessions restart` refusals keep their reasons (what the worker still has running) and name the
|
|
28
|
+
remote `chi --remote NAME sessions stop` command.
|
|
29
|
+
- chi web's stream, and a remote terminal, say a running worker "didn't answer" instead of "no live worker".
|
|
30
|
+
- The model's hint about a same-named project's memory folder asks the user first instead of offering an `mv`.
|
|
31
|
+
- A participant's wait answers revoked (401) when their grant disappears mid-wait, not "chi web is stopping".
|
|
32
|
+
- A bad `web.start_dir` says it came from config.yml (not `--start-dir`), and a relative one resolves from home.
|
|
33
|
+
- A remote `chi note` whose reply has no status is a clear refusal instead of a crash.
|
|
34
|
+
|
|
35
|
+
Update with `chi update`, then restart `chi web` and the session workers (`chi sessions restart`).
|
|
36
|
+
|
|
37
|
+
## [0.121.1] - 2026-10-27
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `chi sessions restart` can recover an unreachable, secret-refusing, newer-protocol or stuck-start worker without a
|
|
42
|
+
stop marker, but refuses when disk shows a running turn, pending question, queued input or running background task.
|
|
43
|
+
- chi web now opens a session stream before answering: a missing, leaving or unreachable worker gets 503, a Bridge
|
|
44
|
+
refusal gets 502 instead of an empty 200 or SSE frame, and the opened socket closes when the stream ends. Remote
|
|
45
|
+
stream retries distinguish 503 from a 502 `bridge_secret`, and `/stats` reports secret refusals consistently.
|
|
46
|
+
- A plugin's `ctx.sessions.fork` reports a child chi won't start as `Samagotchi::Plugin::Sessions::Error` with chi's
|
|
47
|
+
own reason (the session's model is gone from the config, or its toolkit is a `toolkit.kits` kit with an invalid
|
|
48
|
+
`groups:`) instead of handing the bundle the raw `Toolkit::Refused` or `ModelProfile::MissingModel`.
|
|
49
|
+
- A `toolkit.kits` kit whose `groups:` isn't a name or a list (a map such as `{chrome: true}`) no longer loads
|
|
50
|
+
silently with its base's groups: chi, `chi send --new`, chi web and `delegate` refuse a new session on it, naming
|
|
51
|
+
the kit and the reason, and a session already saved on it reaches no tool-index group until config.yml is fixed.
|
|
52
|
+
- A worker's liveness is one rule everywhere, the owner lock plus the sidecar's pid, with no port probe: after a
|
|
53
|
+
`kill -9` whose port another session's Bridge took, `chi send`, `--attach` and chi web no longer take the dead
|
|
54
|
+
worker for live (they wake a new one). A worker on a newer sidecar protocol is not driven by an older chi: a wake
|
|
55
|
+
answers 409 `worker_newer`.
|
|
56
|
+
- System 1 no longer auto-allows a command that changes chi's own config through a spelling its check missed
|
|
57
|
+
(`$XDG_CONFIG_HOME/samagotchi/config.yml`, `$HOME/.config/…`, `memories/../config.yml`, `cd ~/.config && …`): it
|
|
58
|
+
refuses by the same test that makes an ask the user's alone, so such a call goes to the user. Memory files still
|
|
59
|
+
can be auto-allowed.
|
|
60
|
+
- `chi guardrails replay` no longer judges an ask again that System 1 already judged live (audit or on mode), which
|
|
61
|
+
counted each such decision twice and let a rule pass `min_record` early: a live label now carries the card's
|
|
62
|
+
`question_id`, and a live line without one is matched by its session and call digest.
|
|
63
|
+
- Stopping a turn while System 1 judges one of its approvals no longer opens an approval card for the stopped turn
|
|
64
|
+
(nor logs the cancel as a failed judgment): the call is denied with "The turn was stopped".
|
|
65
|
+
- A participant no longer reads a one-time link code (`chc_…`, as in an invite link a reply quotes) or a Bridge's
|
|
66
|
+
`X-Chi-Bridge` secret: both are redacted like `chp_` tokens.
|
|
67
|
+
- An agent participant's `chi --remote … send --wait` goes on after chi web restarts: chi web reads whose turn it is
|
|
68
|
+
from the claimed input file, the last turn or the refused turn's record, not only its memory (it answered 403).
|
|
69
|
+
- chi web's participants routes (list, invite, revoke, cap) answer a grants store chi can't write (unreadable, or a
|
|
70
|
+
newer chi's) with 409 `grants_unwritable` and the store's own message, not a 500 with a backtrace on every refresh.
|
|
71
|
+
- A participant name or an annotate preset holding `\&`, `` \` `` or `\'` no longer breaks chi web's page: it was read
|
|
72
|
+
as a backreference when the page's attributes went in.
|
|
73
|
+
- One push subscription with a `p256dh` key off the P-256 curve no longer stops every push: chi web refuses such a
|
|
74
|
+
subscribe (400 `invalid_subscription`), and one already on disk fails that device alone, so the other devices still
|
|
75
|
+
get the push, `POST /api/push/test` answers that device as failed instead of a 500, and `chi web push test` prints
|
|
76
|
+
its failure instead of a backtrace.
|
|
77
|
+
- The notifications sheet's switches always show what chi web has: "this device" no longer stays on after iOS's
|
|
78
|
+
Don't Allow, nor "Long turns done" moved after its change failed.
|
|
79
|
+
- Turning push off on a flaky connection no longer drops this phone's subscription while chi web keeps the device:
|
|
80
|
+
chi web is told first, and a failure leaves the bell on with the error. The Home Screen app whose first `GET
|
|
81
|
+
/api/push` failed (chi web restarting, a phone just woken) tries again when it comes back to the front instead of
|
|
82
|
+
staying in tab mode until a reload.
|
|
83
|
+
- `chi --help` lists `chi guardrails replay` (seeding System 1's record), the one subcommand it left out.
|
|
84
|
+
- The participants panel's Cap form no longer sets a $0 cap when its field is cleared, nor sends a negative or
|
|
85
|
+
over-$1000 one: it refuses as the Invite's cap field does ("A cap is dollars, 0 to 1000.").
|
|
86
|
+
- A double tap on the web's Stop no longer asks twice (two confirms, or two stops): a second Stop of the same session
|
|
87
|
+
while the first one asks the worker what still runs, or stops it, does nothing.
|
|
88
|
+
- A participant's comment box keeps what they typed while their last comment was on its way (it was cleared when the
|
|
89
|
+
comment was accepted), and an error under the box (a refusal, "Images can't go in a review comment yet") goes at
|
|
90
|
+
their next edit or send instead of staying until a comment went through.
|
|
91
|
+
- An invite whose participants panel closed before chi web answered no longer loses its one-time link (the grant was
|
|
92
|
+
made, so the owner had to revoke and invite again): a sticky toast names it with Copy link.
|
|
93
|
+
- A participant's thread no longer jumps when a read fails and again when it recovers: "Can't reach the session —
|
|
94
|
+
retrying…" (and "This session is gone.") shows over the thread's top instead of as a new row in the header.
|
|
95
|
+
- A participant's wait on a turn whose worker died in it is no longer refused (403) after chi web restarts: the turn's
|
|
96
|
+
canceled (`worker_stopped`) record keeps whose turn it was.
|
|
97
|
+
- claude-peer (0.4.5): `pair` run as `bash pair` or by zsh no longer refuses every call with its usage (only `sh
|
|
98
|
+
pair` kept the id and the `--inbox-socket` path), and a carriage return or another control character in what it
|
|
99
|
+
pushes (the cwd) is escaped, so chi's JSON read no longer refuses the push.
|
|
100
|
+
- codex-peer (0.3.4): a carriage return or another control character in what `pair` pushes (the cwd) is escaped, as
|
|
101
|
+
claude-peer's is, so chi's JSON read no longer refuses the push.
|
|
102
|
+
- A toolkit's `groups:` match source groups in any case and with `{a,b}` braces, as the guardrails rules' `models:`
|
|
103
|
+
do (`groups: [Chrome]` missed `chrome`), and a config kit's `groups:` that isn't a name or a list (a map) is refused
|
|
104
|
+
with a line, the kit loading without it, instead of matching its stringified pairs.
|
|
105
|
+
- A plugin's `chi.toolkit(groups:)` that isn't a name, a list or `:all` (a map) is a load error ("toolkit NAME: groups
|
|
106
|
+
must be a list of group names or globs"), as a config kit's is refused, instead of matching its stringified pairs.
|
|
107
|
+
- `chi bundle check`'s `requires-chi-floor` sees a keyword added later: `chi.toolkit(prompt:)` (0.72.0),
|
|
108
|
+
`chi.toolkit(groups:)` (0.121.0), `ctx.ask_model(model:)` (0.80.0) and `ctx.sessions.send(delivery:)` (0.64.0).
|
|
109
|
+
- A remote `chi share` (or `share ls`/`cap`/`revoke`) whose chi web can't read its grants store prints the store's own
|
|
110
|
+
line, as a local one does, instead of "… answered 409 grants_unwritable: …".
|
|
111
|
+
- `chi web push test`'s line for a push that failed before the push service answered reads "failed (12 ms)", not
|
|
112
|
+
"failed (12 ms)".
|
|
113
|
+
|
|
114
|
+
Update with `chi update` (claude-peer 0.4.5, codex-peer 0.3.4, github-pr 0.5.1), then restart `chi web` and
|
|
115
|
+
`chi sessions restart` the running sessions.
|
|
116
|
+
|
|
10
117
|
## [0.121.0] - 2026-10-26
|
|
11
118
|
|
|
12
119
|
### Added
|
|
@@ -4551,7 +4658,9 @@ and long-lived sessions.
|
|
|
4551
4658
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
4552
4659
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
4553
4660
|
|
|
4554
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
4661
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.122.0...HEAD
|
|
4662
|
+
[0.122.0]: https://github.com/dm1try/samagotchi/compare/v0.121.1...v0.122.0
|
|
4663
|
+
[0.121.1]: https://github.com/dm1try/samagotchi/compare/v0.121.0...v0.121.1
|
|
4555
4664
|
[0.121.0]: https://github.com/dm1try/samagotchi/compare/v0.120.0...v0.121.0
|
|
4556
4665
|
[0.120.0]: https://github.com/dm1try/samagotchi/compare/v0.119.0...v0.120.0
|
|
4557
4666
|
[0.119.0]: https://github.com/dm1try/samagotchi/compare/v0.118.0...v0.119.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.
|
|
@@ -391,7 +394,7 @@ if launch == :attached
|
|
|
391
394
|
toolkit_flags: options[:toolkit_flags] || [],
|
|
392
395
|
settings: options[:settings],
|
|
393
396
|
parent_answers: options[:parent_answers])
|
|
394
|
-
rescue Samagotchi::TerminalUI::AttachLauncher::Error => e
|
|
397
|
+
rescue Samagotchi::TerminalUI::AttachLauncher::Error, Samagotchi::Toolkit::Refused => e
|
|
395
398
|
warn "Error: #{e.message}"
|
|
396
399
|
exit 1
|
|
397
400
|
rescue Interrupt
|
|
@@ -427,7 +430,8 @@ begin
|
|
|
427
430
|
# limit, or Ctrl-C cancelled it.
|
|
428
431
|
exit 1 if %i[turn_failed empty_answer iteration_limit].include?(ended)
|
|
429
432
|
exit Samagotchi::CLI::Exit::INTERRUPTED if ended == :interrupted
|
|
430
|
-
rescue Samagotchi::TerminalUI::SessionBusy, Samagotchi::TerminalUI::SessionNotFound, Samagotchi::ModelProfile::MissingModel
|
|
433
|
+
rescue Samagotchi::TerminalUI::SessionBusy, Samagotchi::TerminalUI::SessionNotFound, Samagotchi::ModelProfile::MissingModel,
|
|
434
|
+
Samagotchi::Toolkit::Refused => e
|
|
431
435
|
warn "Error: #{e.message}"
|
|
432
436
|
exit 1
|
|
433
437
|
rescue Samagotchi::LLM::ProviderError => e
|
data/docs/architecture.md
CHANGED
|
@@ -393,14 +393,18 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
393
393
|
worker.
|
|
394
394
|
- **The secret.** Every request, on every route (the stream, the reads such as `state` and `stats`, the writes, and
|
|
395
395
|
an unknown path), names the Bridge's secret in `X-Chi-Bridge`; one without it, or with another one, is
|
|
396
|
-
`403 bridge_secret` with one message (`BridgeSecret
|
|
397
|
-
|
|
396
|
+
`403 bridge_secret` with one session-specific message (`BridgeSecret.refused`): "session ID's worker refused
|
|
397
|
+
this chi's request (it didn't name its secret): a chi web or terminal started before chi was updated can't
|
|
398
|
+
reach it; restart chi web, re-attach with `chi --attach ID`, or restart the worker with `chi sessions restart
|
|
399
|
+
ID`." `chi sessions restart` can also recover when the worker cannot be asked: TERM + resume if disk shows no
|
|
400
|
+
running turn, pending question or approval, queued input, or running background task. The check is after the
|
|
401
|
+
browser and proxy checks and before any route (`Bridge#secret_ok?`,
|
|
398
402
|
`OpenSSL.secure_compare`). No route is left open: what the reads show is the session itself. Only a request that
|
|
399
403
|
got past it counts as client activity, so a refused one (an older chi web's polls) never holds the idle exit. Each Bridge makes its own (`SecureRandom.hex(32)`, 64 hex
|
|
400
404
|
characters) when it starts and writes it to `bridge.json`, which is 0600 from the moment it exists
|
|
401
405
|
(`AtomicFile` creates the temporary file with that mode, then renames it), so only this machine's user can read
|
|
402
406
|
it: a forward that adds no proxy header, or another local account, reaches the port but not a session. Clients get
|
|
403
|
-
it only from the sidecar: `BridgeClient.
|
|
407
|
+
it only from the sidecar: `WorkerLink.client`/`.await` and `BridgeClient.for_sidecar` take the port and the secret from the
|
|
404
408
|
same read (chi web makes its client from the sidecar for every request, the attached terminal again on a
|
|
405
409
|
`worker_changed`), so a respawned worker's new one is used and nothing passes a port by hand. A worker from before
|
|
406
410
|
the secret writes none and asks for none; a chi web or attached terminal from before it sends none, so a worker
|
|
@@ -408,7 +412,7 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
408
412
|
(`BridgeClient::Response#secret_refused?`) and shows the one message: chi web's relays answer 502 `bridge_secret`,
|
|
409
413
|
`chi answer` and a parent's approval relay say it instead of "refused", and an event stream ends with reason
|
|
410
414
|
`bridge_secret` once rediscovery finds no newer worker (the attached terminal's closing line), never retrying in
|
|
411
|
-
silence; chi web's stream proxy
|
|
415
|
+
silence; chi web's stream proxy answers 502 `bridge_secret`, never passing the refusal on as frames. The secret is never logged (a refusal logs
|
|
412
416
|
`secret_refused` with the path, at most once a minute), never in an event, a reply, `/api/info` or a session
|
|
413
417
|
file, and `WorkerSidecar`, `BridgeClient` and `Bridge` leave it out of `#inspect`.
|
|
414
418
|
|
|
@@ -491,10 +495,30 @@ Every route goes through `Bridge#dispatch`: an id other than the bridge's own se
|
|
|
491
495
|
`handler_failed`) rather than dropping the connection.
|
|
492
496
|
|
|
493
497
|
The per-session port is OS-assigned (bound to `0`) and published to a `bridge.json` sidecar
|
|
494
|
-
for client discovery, with the Bridge's secret (see above).
|
|
495
|
-
|
|
496
|
-
`
|
|
497
|
-
|
|
498
|
+
for client discovery, with the Bridge's secret (see above).
|
|
499
|
+
|
|
500
|
+
**Worker link.** Every client asks `WorkerLink` (`worker_link.rb`) whether a session's worker can be reached, never
|
|
501
|
+
reading `bridge.json` or probing a port itself: `WorkerLink.resolve(session_dir, unlink:, owner:)` answers a typed
|
|
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
|
+
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
|
+
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` (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
|
|
507
|
+
pid than the holder's (a worker that has not written its own yet): `Starting`, `stuck` once it has held the lock
|
|
508
|
+
past `START_LIMIT` (30 s) with none; a sidecar whose `protocol` is newer than `WorkerLink::PROTOCOL`: `Skew` (a
|
|
509
|
+
newer worker; never driven, `wake` answers 409 `worker_newer`); else `Live` (its sidecar; `#client` makes the one
|
|
510
|
+
`BridgeClient`). A sidecar with no `pid` is an older worker's: the lock alone says it is live. `owner:` is the
|
|
511
|
+
holder as the caller already read it (nil: free), so one read serves a summary and its status. `WorkerLink.client` is
|
|
512
|
+
the Live's client or nil; `WorkerLink.await` polls for a new worker's Live (an `until:` check tells a restart's
|
|
513
|
+
successor from the old worker); chi web's `App#worker_link` / `#await_worker_link` wrap them. The Bridge writes
|
|
514
|
+
`pid` and `protocol` with its port and removes its sidecar on stop only when the file still carries its epoch.
|
|
515
|
+
|
|
516
|
+
`chi web`'s `GET /api/sessions/:id/stream` proxies this bridge
|
|
517
|
+
(503 `not_live` for no worker, a leaving or stuck worker, or an unreachable Bridge; 502 `worker_newer` for skew and
|
|
518
|
+
`bridge_error` / `bridge_secret` for a refused Bridge; 200 only after the Bridge answers; full history of any session
|
|
519
|
+
is served by `GET /api/sessions/:id/output`). It opens the Bridge before answering and gives the one opened socket to
|
|
520
|
+
`App#proxy_stream_body` / `ProxyStreamBody`, which closes it when the body or hijacked client ends. The owner's stream
|
|
521
|
+
is a byte pipe; the body gives it the request's frame filter (`App#stream_filter`: nil for the owner; for a participant, and the owner's participant view
|
|
498
522
|
`?as=participant`, a `Web::ParticipantStreamFilter`, which cuts the Bridge's chunks into frames and writes each again
|
|
499
523
|
from `Web::ParticipantView.event`'s allowlist, dropping every other event; only `from_seq` and `snapshot` reach the
|
|
500
524
|
Bridge; [internals/participants.md](internals/participants.md#what-a-participant-sees)). Resume/ring-buffer state is **in-memory**, per worker: a cursor from
|
|
@@ -531,7 +555,7 @@ moved through `Session.summary_from_file`. Liveness is probed, since a killed ow
|
|
|
531
555
|
file trace: the owner lock every tick for the sessions the projection believes owned, and every
|
|
532
556
|
session every 10 s, so a `kill -9` shows within a second. The summary (`Web::SessionSummary`,
|
|
533
557
|
shared with `/api/sessions` and the session view) carries `owner`, `project_root` and
|
|
534
|
-
`bridge_up` (
|
|
558
|
+
`bridge_up` (`WorkerLink` answers Live: a worker holds the lock and its sidecar names its pid; the page attaches its stream on it), and
|
|
535
559
|
from that sidecar `worker_version` (the chi the worker runs) and `worker_features`
|
|
536
560
|
(`Bridge::FEATURES`, what a client may ask of it beyond the routes: `restart`, `task_stop`, `reparent`, `unqueue`,
|
|
537
561
|
`delivery`, a worker that queues a message for after the turn; `SessionManager.deliver_turn` and
|
|
@@ -577,12 +601,12 @@ model: [internals/remote-sessions.md](internals/remote-sessions.md).
|
|
|
577
601
|
| `GET /api/sessions/:id/worker` | owner | | 200 `{session_id, live, stopped, owner: worker \| tui \| null, worker: {epoch, started_at, version, features} \| null, default_model, parent_strategy}` (`Local#worker`; never starts one; `default_model` is this machine's) | 404 |
|
|
578
602
|
| `POST /api/sessions/:id/exit` | owner | `client_id` (passed on only when it is an attached TUI's, `tui:…`; any other is dropped), `delete` | 200 the worker's `{status: exiting, session_id, discard}` (`BridgeClient#request_exit`; never wakes one) | 409 `held` with the worker's `{status, reason, tasks, hint}`; 503 `not_live`; 501 `not_supported`; 504 `worker_timeout` |
|
|
579
603
|
| `POST /api/sessions/:id/recap` | owner | | 200 the worker's `{enabled, saved, request, min_user_turns, session_off}` (`BridgeClient#request_recap`) | 503, 501, 504 as every relay |
|
|
580
|
-
| `GET /api/sessions/:id/stats` | owner | | 200 `{session_id, metrics}`: the worker's stats, its state's metrics for a worker from before the stats route | 503 `not_live`; 504 `worker_timeout`; 502 `bridge_error` |
|
|
604
|
+
| `GET /api/sessions/:id/stats` | owner | | 200 `{session_id, metrics}`: the worker's stats, its state's metrics for a worker from before the stats route | 503 `not_live`; 504 `worker_timeout`; 502 `bridge_secret` or `bridge_error` |
|
|
581
605
|
| `GET /api/sessions/:id/children/counts` | owner | | 200 `{running, waiting, unreported}` (`ChildrenStatus.counts`, the delegates' files here) | 404 |
|
|
582
606
|
| `GET /api/sessions/:id/turns/:turn_id/records` | owner | | 200 `{tool_records: […], turn_record: {…} \| null}` (`SessionMetrics.saved_tool_records` / `saved_turn_record`, as saved) | 400 `invalid_turn_id`; 404 |
|
|
583
607
|
| `POST /api/sessions/:id/turn` (`no_interrupt`) | owner | `no_interrupt: true`: the turn runs with the raised iteration limit (`SessionManager.deliver_turn(no_interrupt:)`, to the Bridge or the input file) | as before | |
|
|
584
608
|
| `POST /api/sessions/:id/command` (its 501) | owner | | | 501 `not_supported` for a builtin the worker lacks adds `command` (`"/title"`) and `message` |
|
|
585
|
-
| `GET`/`POST`/`DELETE /api/sessions/:id/participants`, `GET`/`DELETE /api/participants` | owner | `chi share`'s (`name`, `kind`, `ttl`; `?id=p_…`) | the grants, never a token but POST's own once ([internals/participants.md](internals/participants.md)) | 400 `invalid_participant`; 404 |
|
|
609
|
+
| `GET`/`POST`/`DELETE /api/sessions/:id/participants`, `GET`/`DELETE /api/participants` | owner | `chi share`'s (`name`, `kind`, `ttl`; `?id=p_…`) | the grants, never a token but POST's own once ([internals/participants.md](internals/participants.md)) | 400 `invalid_participant`; 404; 409 `grants_unwritable` (the store doesn't read: its words, which a remote `chi share` prints as a local one does) |
|
|
586
610
|
| `GET /api/sessions/:id/apply[?turn=T]` | owner | `turn`: a turn id or a prefix (none: the latest review turn with a reply) | 200 `{text, turn_id, author, author_name}` (`ReviewApply`): the owner's Apply of a participant's review turn, for the composer, never sent ([internals/participants.md](internals/participants.md)) | 404 `not_found`; 400 `not_review_turn`; 409 `no_reply` |
|
|
587
611
|
| `POST /api/sessions` (`review`) | owner | `review: {subjects: [project files], from: a session id}`; never with `prompt`, `preview`, `idle` or `continues` (a `from` takes the source's folder and setup) | 201 as a create's: an idle review session (`ReviewStart.start`, or `ReviewStart.fork` with `from`) | 400 `invalid_review` (`ReviewStart::Refused`'s words: a subject, a gone folder); with `from` also 404 `not_found`, 400 `ambiguous_id` |
|
|
588
612
|
|
|
@@ -723,8 +747,10 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
723
747
|
| Shipped bundles | `bundles/` (`mcp`, `github-pr`, `loop-guard`, `skills`, `source-links`, …) | Bundles that ship with chi. |
|
|
724
748
|
| Hooks | `hooks/registry.rb` (`Hooks::Registry`), `hooks/loader.rb`, `hook_routes.rb` (`HookRoutes`), `event_routes.rb` (`EventRoutes`) | Hook registration and dispatch; what a hook or plugin does beyond reading its event (notice, question, stop, steer) routed to the UIs (cards and notices through one set of routes) ([hooks.md](hooks.md)). |
|
|
725
749
|
| Guardrails | `guardrails.rb`, `guardrails/shell_lex.rb`, `guardrails/data_readers.rb`, `guardrails/rules.rb`, `guardrails/protected_paths.rb`, `guardrails/parent_policy.rb` + `guardrails/parent_approvals.rb`, `guardrail_wiring.rb` | The gate before every tool call: shell lexing (and the commands that only read a heredoc as data), YAML rules, the core checks on chi's own files, approvals, what a parent agent may answer (`guardrails.parent_answers`) ([guardrails.md](guardrails.md)). |
|
|
750
|
+
| System 1 | `system_one.rb`, `system_one/` (`SystemOne::Client`, `ApprovalQuestion`, `Log`, `Record`, `SelfProtect`, `Similar`, the backends), `guardrail_wiring.rb` (`GuardrailWiring#request_approval`), `guardrails_command.rb` (`chi guardrails replay`) | The approval judge: a model's `p` for each ask, logged to `judgments.jsonl` with the user's answer; in `on` mode an ask on a listed rule with a clean record is allowed once without the user. Labelled by `/guardrails wrong` (`SessionCommands`) and the web's **should have asked** (the Bridge's `POST guardrails/wrong`) ([guardrails.md](guardrails.md#system-1-experimental)). |
|
|
726
751
|
| Delegation | `tools/delegate.rb`, `tools/delegate_result.rb`, `tools/delegate_wait.rb`, `child_reports.rb`, `tools/delegate_relay.rb`, `relay_desk.rb`, `relay_verifier.rb`, `relay_watcher.rb` | Child sessions a session starts and waits on, and their approval relay ([sessions.md](sessions.md#delegating)). |
|
|
727
752
|
| send / broadcast | `send_command.rb` (`SendCommand`), `broadcast_command.rb` (`BroadcastCommand`), `broadcast/` | `chi send` and `chi broadcast` ([broadcast.md](broadcast.md), [sub-agent.md](sub-agent.md)). |
|
|
753
|
+
| 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. |
|
|
728
754
|
| 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). |
|
|
729
755
|
| Idle work | `idle_scheduler.rb` (`IdleScheduler`), `idle_recap.rb` (`IdleRecap`), `idle_reminders.rb` | One poller for an idle session's recap and reminders. |
|
|
730
756
|
| 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)). |
|
data/docs/bundle-authoring.md
CHANGED
|
@@ -130,7 +130,8 @@ class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather
|
|
|
130
130
|
after the last edit.
|
|
131
131
|
- **requires_chi.** Use the chi the user runs (`chi --version`). `check` warns when the plugin calls something
|
|
132
132
|
newer than the floor (`ctx.http` needs 0.80.0, `ctx.cost` 0.83.0, `ctx.usage` 0.100.0, `ctx.sessions.children` 0.34.0). A keyword added
|
|
133
|
-
later
|
|
133
|
+
later counts too (`ctx.ask_model(model:)` 0.80.0, `ctx.sessions.send(delivery:)` 0.64.0, `chi.toolkit(prompt:)`
|
|
134
|
+
0.72.0, `chi.toolkit(groups:)` 0.121.0), when the call is written on `ctx` / `chi` itself (not through a variable).
|
|
134
135
|
- **trust_level.** Leave it out. `reviewed` / `trusted` are for someone who reviewed the bundle, not its author.
|
|
135
136
|
- **Restart.** A session loads bundles when it starts: after an install or a settings change, the user starts a
|
|
136
137
|
new session (or `chi sessions restart ID`).
|
data/docs/cli.md
CHANGED
|
@@ -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 suggestion audits: the kit on mode would have run (the suggested kit when it would have applied it, else the default) 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)).
|
|
@@ -518,10 +519,16 @@ chi installed, without stopping it: an attached terminal and the web's tabs
|
|
|
518
519
|
move to the new worker. It is refused, with the reason, while something would
|
|
519
520
|
be lost: a turn running or queued, a question or approval waiting, reminders,
|
|
520
521
|
a `/btw` still running, an approval relayed from a delegate, or background
|
|
521
|
-
tasks the session started.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
522
|
+
tasks the session started. If the worker cannot be asked (a refused Bridge,
|
|
523
|
+
protocol mismatch or stuck start), chi sends it TERM and resumes only when
|
|
524
|
+
disk shows no running turn, pending question or approval, queued prompt, or
|
|
525
|
+
running background task; otherwise it says to run `chi sessions stop ID`, then
|
|
526
|
+
send any prompt. That fallback may lose reminders, a running `/btw`, and other
|
|
527
|
+
in-memory state. A session with no running worker needs no restart (its next
|
|
528
|
+
prompt starts one on the newest chi), and an older worker without restart
|
|
529
|
+
support needs `chi sessions stop ID`. The Web UI's
|
|
530
|
+
`POST /api/sessions/:id/restart` does the same (409 with `reason` and `detail`
|
|
531
|
+
when it can't). An attached
|
|
525
532
|
terminal says at the join, in one `chi>` line, when the worker or the terminal
|
|
526
533
|
runs an older chi than the newest installed (the worker moves with `chi
|
|
527
534
|
sessions restart ID`, the terminal with `/detach` and `chi --attach ID`), 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. |
|
|
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. |
|
|
@@ -1398,7 +1399,7 @@ toolkit:
|
|
|
1398
1399
|
default: lean # the sessions you start (unset: full)
|
|
1399
1400
|
child: child # delegate children (unset: full)
|
|
1400
1401
|
kits: # your own, on a base
|
|
1401
|
-
audit: { base: lean, add: [web_fetch, delegate], remove: [memory_write] }
|
|
1402
|
+
audit: { base: lean, add: [web_fetch, delegate], remove: [memory_write], description: "Code review and inspection" }
|
|
1402
1403
|
terse: { base: lean, prompt: "- Be concise in your responses" }
|
|
1403
1404
|
web: { base: lean, groups: [chrome] }
|
|
1404
1405
|
coding: { base: lean, groups: [] }
|
|
@@ -1414,12 +1415,36 @@ toolkit:
|
|
|
1414
1415
|
| `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
1416
|
|
|
1416
1417
|
- **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
|
|
1418
|
+
the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:`, `prompt:`, `groups:` and
|
|
1419
|
+
`description:`
|
|
1418
1420
|
(any other key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name
|
|
1419
1421
|
(`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)).
|
|
1421
|
-
|
|
1422
|
-
|
|
1422
|
+
([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil-groups-nil-description-nil)).
|
|
1423
|
+
`description:` is optional classifier input, up to 300 characters; it is not included in the session prompt.
|
|
1424
|
+
- **Memory kits.** A memory named `toolkit_<name>.md` defines kit `<name>` with one fenced YAML block containing
|
|
1425
|
+
optional `base:`, `add:`, `remove:` and `groups:`, plus a required `description:` (up to 300 characters).
|
|
1426
|
+
`prompt:` is refused. A memory kit may use a config or bundle kit as its base. Built-ins cannot be redefined.
|
|
1427
|
+
The precedence is built-ins, config, bundles, then memory kits; within memories, project scope wins over system
|
|
1428
|
+
scope. Muted memory kits are skipped, and `/toolkit` shows each one's `memory:project` or `memory:system` source.
|
|
1429
|
+
Kits are read once per worker, so changes take effect when a new worker starts. See
|
|
1430
|
+
[Toolkit memories](memory.md#toolkit-memories) for the format and when to create one.
|
|
1431
|
+
- **Audit suggestions.** `toolkit.suggest.mode: audit` asks System 1 which described kit fits an eligible new session's
|
|
1432
|
+
prompt, without changing its toolkit or delaying the turn. It offers the configured default as the fallback and
|
|
1433
|
+
`undecided` for greetings or check-ins. Only a default-origin session with at least two described candidates is
|
|
1434
|
+
asked; chosen or inherited toolkits, delegate children, review sessions and participant turns are skipped. After
|
|
1435
|
+
`undecided`, later owner prompts may be asked again within both defer limits. `toolkit.suggest.mode: on` is accepted
|
|
1436
|
+
but behaves exactly like `audit` in this build. The default is `off`. System 1's host, model and timeout come from
|
|
1437
|
+
`guardrails.system_one.*`; the suggestion threshold and defer limits are its own settings below. Suggestions are
|
|
1438
|
+
kept in `toolkit_suggestions.jsonl`; `chi toolkit report` grades them against the session's tool calls. One ask
|
|
1439
|
+
runs at a time (a turn that comes while one still runs goes unasked, with an info line), and a kit that doesn't
|
|
1440
|
+
resolve (a missing base, a cycle) is left out of the candidates. A worker that exits waits about a second for a
|
|
1441
|
+
running ask, so a one-shot `-p --non-interactive` run may end before its suggestion is logged.
|
|
1442
|
+
- **Tool-index groups.** `groups:` selects source groups by name or glob (`chrome`, `db*`; any case, `{a,b}` braces,
|
|
1443
|
+
as the guardrails rules' `models:`); for the MCP bundle each group is a server. Anything but a name or a list (a
|
|
1444
|
+
map such as `{chrome: true}`) refuses every new session on that kit or on a kit built on it, on every surface (chi,
|
|
1445
|
+
`chi send --new`, chi web, `delegate`, a continue or a fork): `toolkit.kits.NAME: groups must be a list of group
|
|
1446
|
+
names or globs; fix config.yml to start a session on toolkit KIT`. A session already saved on it keeps running,
|
|
1447
|
+
its kit reaching no group (with a `toolkit>` warning) until config.yml is fixed. Selectors are sorted and deduplicated and match the groups declared when plugins load.
|
|
1423
1448
|
An omitted key inherits the base's selectors; `full` and `lean` (and `child`, which is on `lean`) reach all
|
|
1424
1449
|
groups, `review` (the participants' kit) reaches none, `groups: ["*"]` matches every group, and `groups: []`
|
|
1425
1450
|
reaches none. A kit whose base doesn't resolve (an unknown name, a cycle) falls back to `full`, which reaches
|
|
@@ -1947,7 +1972,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1947
1972
|
| `guardrails.mode` | `auto` | | Which rules vote: `auto` leaves out the rules tagged `modes: [strict]` (in the guardrails bundle: `git rebase`, writes and git outside the session's repo); `strict` runs them all. An unknown value warns and is `auto`. config.yml only. See [Guardrails](guardrails.md#modes). |
|
|
1948
1973
|
| `guardrails.small_models` | `auto` | | Which models get the `models: small` rules: `auto` (32B or less by the size in the name, an MoE's active size), a list of globs, or `[]`. config.yml only. See [Guardrails](guardrails.md#rules-for-some-models). |
|
|
1949
1974
|
| `guardrails.parent_answers` | `soft` | | Which options a parent agent (`chi answer`) may pick on a session's question, by their effect: `hard` (a deny, a Stop, a plain answer), `standard` (+ Continue and other spending options), `soft` (+ "Allow once"); never a wider scope or an allow on chi's own config. An invalid value warns and is `hard`. A session's own `parent_answers` (`chi --parent-answers`, `/parent-answers`) may only tighten it. Replaces `guardrails.parent_approvals` and `turn.parent_continue`. config.yml only: no environment variable (a parent can still pick the config dir with `XDG_CONFIG_HOME`). A convention for an honest parent, not a security boundary. See [Guardrails](guardrails.md#answers-from-a-parent-agent). |
|
|
1950
|
-
| `guardrails.system_one.mode` | `off` | | System 1, the cheap judgment that answers an ask before the user sees it: `off` (the default), `audit` (judge every ask, log it, never decide), `on` (also allowed to allow a call once, behind the gates in [System 1](guardrails.md#system-1)). An unknown value warns and is `off`. Experimental. config.yml only. See [Guardrails](guardrails.md#system-1). |
|
|
1975
|
+
| `guardrails.system_one.mode` | `off` | | System 1, the cheap judgment that answers an ask before the user sees it: `off` (the default), `audit` (judge every ask, log it, never decide), `on` (also allowed to allow a call once, behind the gates in [System 1](guardrails.md#system-1-experimental)). An unknown value warns and is `off`. Experimental. config.yml only. See [Guardrails](guardrails.md#system-1-experimental). |
|
|
1951
1976
|
| `guardrails.system_one.host` | none | | The `hosts:` entry serving `/v1/systemone` (a local Splash, or the hosted Jev API); unset: no judgment. config.yml only. |
|
|
1952
1977
|
| `guardrails.system_one.model` | none | | The judgment model; unset uses the host's first model, and a host that names none has no judgment. config.yml only. |
|
|
1953
1978
|
| `guardrails.system_one.rules` | none | | The rule ids System 1 may answer for in `on` mode: `"git-push|git-rebase"` or a YAML list, parsed like `guardrails.small_models`. Nothing is auto-allowed while it is unset; the `chi-answer-piped`, `chi-answer-http` and `chi-broadcast` rules never are, however they are listed. config.yml only. |
|
|
@@ -1978,6 +2003,10 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1978
2003
|
| `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). |
|
|
1979
2004
|
| `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". |
|
|
1980
2005
|
| `toolkit.child` | `full` | | A delegate child's toolkit, unless its `delegate` call names one (`toolkit:`). See "Toolkits". |
|
|
2006
|
+
| `toolkit.suggest.mode` | `off` | | `audit` asks System 1 for a toolkit suggestion without changing the session; `on` currently behaves the same as `audit`. The System 1 host, model and timeout come from `guardrails.system_one.*`. config.yml only. |
|
|
2007
|
+
| `toolkit.suggest.threshold` | `0.5` | | Minimum confidence for a non-default toolkit suggestion. config.yml only. |
|
|
2008
|
+
| `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. |
|
|
2009
|
+
| `toolkit.suggest.defer_tokens` | `5000` | | Maximum estimated conversation size for another ask after `undecided` (characters / 4). config.yml only. |
|
|
1981
2010
|
| `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". |
|
|
1982
2011
|
| `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". |
|
|
1983
2012
|
| `models.<key>.thinking`, `hosts.<name>.thinking` | none | | A model's or host's level. See "Thinking". |
|
|
@@ -2023,7 +2052,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
2023
2052
|
| `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). |
|
|
2024
2053
|
| `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). |
|
|
2025
2054
|
| `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). |
|
|
2026
|
-
| `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). |
|
|
2055
|
+
| `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). |
|
|
2027
2056
|
| `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). |
|
|
2028
2057
|
| `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. |
|
|
2029
2058
|
| `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
|
@@ -243,6 +243,16 @@ with its `reason`; `chi answer` exits 1 with the line), checked against the
|
|
|
243
243
|
question pending at that moment. Answers from the web and an attached
|
|
244
244
|
terminal keep every option.
|
|
245
245
|
|
|
246
|
+
If `chi answer` reaches a worker that refuses its Bridge secret (`403
|
|
247
|
+
bridge_secret`), it prints a session-specific recovery message rather than
|
|
248
|
+
treating that as a refused answer: `"session ID's worker refused this chi's
|
|
249
|
+
request (it didn't name its secret): a chi web or terminal started before chi
|
|
250
|
+
was updated can't reach it; restart chi web, re-attach with chi --attach ID,
|
|
251
|
+
or restart the worker with chi sessions restart ID"`. `chi sessions restart
|
|
252
|
+
ID` can also recover when the worker cannot be asked, but only if disk shows
|
|
253
|
+
no running turn, pending question or approval, queued prompt, or running
|
|
254
|
+
background task; when one exists, stop it explicitly and then send a prompt.
|
|
255
|
+
|
|
246
256
|
A parent may also type its answer into chi itself: a piped `chi --attach ID`
|
|
247
257
|
or `printf '2\n' | chi --no-shared -p …`. So chi treats an answer as a
|
|
248
258
|
parent's, held to the same setting, when its input isn't a terminal or an
|
|
@@ -656,14 +666,29 @@ a body that isn't JSON, a question with no answer, a request longer than
|
|
|
656
666
|
`timeout_ms`. A nil means "ask the user", which is what the caller would
|
|
657
667
|
have done anyway.
|
|
658
668
|
|
|
669
|
+
Toolkit suggestions are a separate audit use of System 1. When
|
|
670
|
+
`toolkit.suggest.mode` is `audit` (or `on`, which is audit-only in this
|
|
671
|
+
build), the engine asks a `choice` question from a background thread after
|
|
672
|
+
the turn starts. This uses the same configured host, model and timeout, but
|
|
673
|
+
is gated by `toolkit.suggest.mode`, not `guardrails.system_one.mode`, and it
|
|
674
|
+
never changes the session's toolkit. Its audit records use the `toolkit` tag
|
|
675
|
+
and are written to `toolkit_suggestions.jsonl` beside the guardrail logs.
|
|
676
|
+
To avoid competing with the session's model, it skips (one info line) when
|
|
677
|
+
the System 1 host is the model's local host: the same `hosts:` name, or
|
|
678
|
+
another entry for the same server root (scheme, host and port). There is no
|
|
679
|
+
reliable single-slot signal, so this host-identity check is the available
|
|
680
|
+
contention guard. The session's own checks come first; the kits and the
|
|
681
|
+
suggestion log are read only for a session that can be asked, the log only
|
|
682
|
+
after an earlier `undecided`.
|
|
683
|
+
|
|
659
684
|
### What the audit writes
|
|
660
685
|
|
|
661
686
|
With a client, `GuardrailWiring#request_approval` asks System 1 one `noul`
|
|
662
687
|
question, `allow`, before the user is asked. Its instruction is a constant
|
|
663
688
|
in code (`SystemOne::ApprovalQuestion::INSTRUCTIONS`), reviewed like a
|
|
664
689
|
prompt; the state is the ask's own facts — the rule and its source, the
|
|
665
|
-
reason, the tool, the command (or the paths) cut to 2000 characters
|
|
666
|
-
edit's diff, the cwd, the repo and branch, and the session's title when it
|
|
690
|
+
reason, the tool, the command (or the paths) cut to 2000 characters
|
|
691
|
+
(`ApprovalQuestion::TEXT_CHARS`), the edit's diff, the cwd, the repo and branch, and the session's title when it
|
|
667
692
|
has one. A memory ask also names what it is about: the entry and its scope,
|
|
668
693
|
and for a removal the description that entry's index line carries
|
|
669
694
|
(`Targets#memory`, read into the card by `Guardrails::Approval`): a path
|
|
@@ -680,11 +705,13 @@ Each judgment is a line in `judgments.jsonl`, beside `approvals.json` in
|
|
|
680
705
|
`$XDG_STATE_HOME/samagotchi/guardrails/` (`SystemOne::Log`): 0600, appended
|
|
681
706
|
under an exclusive `flock` (several workers write it), and rewritten —
|
|
682
707
|
keeping the newest half of its lines — once it passes 5 MB. A line is never
|
|
683
|
-
rewritten to add its label:
|
|
708
|
+
rewritten to add its label. Its `call` is shorter than the state's: cut to 300
|
|
709
|
+
characters (`ApprovalQuestion::CALL_CHARS`), with `call_digest` the SHA256 of
|
|
710
|
+
the whole call:
|
|
684
711
|
|
|
685
712
|
```json
|
|
686
713
|
{"kind":"judgment","id":"…","at":"…","session_id":"…","rule":"git-push","source":"config","tool":"execute","call":"git push origin main","call_digest":"<sha256 of the whole call>","repo_name":"samagotchi","p":0.97,"threshold":0.8,"model":"Qwen3.8-Flash","latency_ms":180,"decision":"audit","origin":"live"}
|
|
687
|
-
{"kind":"label","id":"…","at":"…","answer":"allow_once"}
|
|
714
|
+
{"kind":"label","id":"…","at":"…","answer":"allow_once","question_id":"…"}
|
|
688
715
|
```
|
|
689
716
|
|
|
690
717
|
`decision` is what the judgment led to: `audit` (audit mode), `asked` (on
|
|
@@ -692,7 +719,9 @@ mode, the user was asked), `auto` (on mode, System 1 answered for the user).
|
|
|
692
719
|
A judgment that never arrived is logged too, with `p` null, so the failures
|
|
693
720
|
count as well. The label line, keyed by the judgment's id, is what the user
|
|
694
721
|
did: `allow_once`, `allow_wide` (any scope wider than once), `deny`, or
|
|
695
|
-
`none` for an ask that was cancelled.
|
|
722
|
+
`none` for an ask that was cancelled. A live label carries the card's
|
|
723
|
+
`question_id` when the user answered (the judgment is written before the card
|
|
724
|
+
exists), so a replay knows the ask was judged. **A parent agent's answer is not the
|
|
696
725
|
user's: its allow labels nothing. Its deny is a label only where the parent
|
|
697
726
|
could have allowed instead** — the ask is not `user_only` and the session's
|
|
698
727
|
parent level permits a permit (`Guardrails::ParentPolicy`); a parent that was
|
|
@@ -704,6 +733,10 @@ their last label.
|
|
|
704
733
|
Everything about the log is best-effort: a write that fails is one
|
|
705
734
|
`Log.warn(:guardrails, "system_one_log_failed", …)` and the approval goes on
|
|
706
735
|
as if System 1 were not there.
|
|
736
|
+
A turn stopped while the judge is asked is not a failed judgment: the cancel
|
|
737
|
+
(`LLM::RequestCancelled`) goes through `SystemOne::Client#ask`, no line is
|
|
738
|
+
written, and the call is denied without a card; a turn that stopped as the
|
|
739
|
+
judgment came back opens no card either.
|
|
707
740
|
|
|
708
741
|
`ruby script/system-one-probe HOST [MODEL]` asks three fixed approval-like
|
|
709
742
|
questions through the client and prints each probability and the latency of
|
|
@@ -722,13 +755,18 @@ gate below passes, in this order:
|
|
|
722
755
|
questions — `chi-answer-piped`, `chi-answer-http`, `chi-broadcast` — which
|
|
723
756
|
are never auto-allowed, even when listed
|
|
724
757
|
(`SystemOne::SelfProtect::NEVER_AUTO`);
|
|
725
|
-
4. the call does not change chi itself (`SystemOne::SelfProtect.refuse?`)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
`
|
|
730
|
-
|
|
731
|
-
|
|
758
|
+
4. the call does not change chi itself (`SystemOne::SelfProtect.refuse?`), by
|
|
759
|
+
the same test that makes an ask one only the user may answer
|
|
760
|
+
(`ParentApprovals.chi_files?`): no edit or write whose paths are in chi's
|
|
761
|
+
config dir (config.yml), the hooks dir, the installed bundles dir, the
|
|
762
|
+
guardrails store (where `approvals.json` and `judgments.jsonl` live) or the
|
|
763
|
+
attached context dir, and no `execute` or `task_create` command naming any
|
|
764
|
+
of them, however it spells them (`ParentApprovals::CHI_TEXT`:
|
|
765
|
+
`$XDG_CONFIG_HOME/samagotchi/config.yml`, `~/.config/samagotchi/…`, a
|
|
766
|
+
`cd ~/.config` then `samagotchi/config.yml`). The memories dir is
|
|
767
|
+
deliberately not one of these: reading or appending a memory file can be
|
|
768
|
+
auto-allowed, unless the command has a `..` segment (`memories/../config.yml`
|
|
769
|
+
climbs out of it). A protected rule
|
|
732
770
|
(`Verdict::PROTECTED_RULES`) may be listed by name and System 1 may answer
|
|
733
771
|
it, but a call that really writes config.yml or the hooks is refused here;
|
|
734
772
|
5. the judgment's `p` is at least `guardrails.system_one.threshold` (0.8);
|
|
@@ -744,11 +782,14 @@ Then the judgment's `decision` is `auto`, the verdict settles as
|
|
|
744
782
|
written to `approvals.json` — the next call is judged again. **No question is
|
|
745
783
|
opened, so no card appears, no `:question_asked` hook fires, and no
|
|
746
784
|
notification is sent.** The verdict's activity carries
|
|
747
|
-
`note: "auto-allowed by System 1 (p=0.97)"`, `judgment_id` and `p
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
the
|
|
785
|
+
`note: "auto-allowed by System 1 (p=0.97)"`, `judgment_id` and `p`, and the
|
|
786
|
+
call's tool row shows it: the terminal's tool line ends in
|
|
787
|
+
`· auto-allowed (System 1, p=0.97)`
|
|
788
|
+
(`TerminalUI::Formatting#format_tool_guardrail_suffix`, live and when
|
|
789
|
+
attaching to a saved turn), and the web's activity row carries
|
|
790
|
+
**auto-allowed · System 1** with `rule …, p=…, allowed once` on hover and the
|
|
791
|
+
owner's **should have asked** button (below). The stage trail shows no mark.
|
|
792
|
+
If the judgment line cannot be written to the log, the ask goes to the user.
|
|
752
793
|
|
|
753
794
|
A delegate child answers from its own session's settings: its System 1 can
|
|
754
795
|
auto-allow a call even when the child's `parent_answers: hard` bars a parent
|
|
@@ -778,8 +819,11 @@ whose `pending_question.approval` is there and that have an `answer` or were
|
|
|
778
819
|
`cancelled`. A label is the scope the user took — `allow_once` for "Allow once",
|
|
779
820
|
`allow_wide` for a wider one, `deny` for anything else, `none` for a cancelled
|
|
780
821
|
ask. Skipped: a question no one answered, a relay card, which carries no
|
|
781
|
-
approval of its own,
|
|
782
|
-
running it twice adds nothing
|
|
822
|
+
approval of its own, a question id a previous replay already logged — so
|
|
823
|
+
running it twice adds nothing — and an ask already judged live (audit or on
|
|
824
|
+
mode): by the `question_id` its label carries, or, for a live judgment with
|
|
825
|
+
none (a cancelled ask, an older line), by its session and `call_digest`, one
|
|
826
|
+
card per such line. A parent agent's answer is not the user's: its
|
|
783
827
|
allow is skipped, and its deny is replayed only where the parent could have
|
|
784
828
|
allowed (the card is not `user_only` and the session's parent level permits a
|
|
785
829
|
permit). A cards file that doesn't parse is skipped with one warning line,
|
data/docs/internals/anthropic.md
CHANGED
|
@@ -130,7 +130,7 @@ gets a 400 `messages.N.content.M: Invalid signature in thinking block. The block
|
|
|
130
130
|
conversation.`, and its last sentence says what changed ("The `system` prompt differs …"). chi's history is
|
|
131
131
|
append-only within a process, so what changes the prefix is:
|
|
132
132
|
|
|
133
|
-
1. **A system-prompt rebuild** (`SystemPrompt#build` is cached per process and key; `
|
|
133
|
+
1. **A system-prompt rebuild** (`SystemPrompt#build` is cached per process and key; `SystemPrompt#reset!` drops it):
|
|
134
134
|
a new process (`--resume`, a respawn after `session.idle_exit_minutes`, a crash) after a memory index line, an
|
|
135
135
|
AGENTS.md, an appended file, a model note or a bundle memory changed; `/model` (the tail's `Model:` line, its
|
|
136
136
|
overlays and notes); `/thinking` (another key); a toolkit or profile change; a plugin's `tools_changed!`; the
|