samagotchi 0.104.0 → 0.106.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 +70 -1
- data/docs/architecture.md +6 -3
- data/docs/cli.md +62 -19
- data/docs/configuration.md +10 -6
- data/docs/guardrails.md +69 -0
- data/docs/hooks.md +9 -0
- data/docs/internals/anthropic.md +2 -1
- data/docs/internals/engine.md +37 -1
- data/docs/internals/participants.md +229 -21
- data/docs/internals/prompt-caching.md +9 -0
- data/docs/internals/working-set.md +12 -5
- data/docs/plugins.md +2 -0
- data/docs/testing.md +23 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +15 -4
- data/lib/samagotchi/bridge.rb +45 -8
- data/lib/samagotchi/bridge_client.rb +4 -1
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/client_id.rb +21 -9
- data/lib/samagotchi/config.rb +17 -2
- data/lib/samagotchi/continue_offer.rb +13 -3
- data/lib/samagotchi/delivery.rb +10 -0
- data/lib/samagotchi/engine.rb +98 -8
- data/lib/samagotchi/guardrail_wiring.rb +13 -3
- data/lib/samagotchi/guardrails/review_boundary.rb +148 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/hook_routes.rb +5 -2
- data/lib/samagotchi/hooks/registry.rb +12 -3
- data/lib/samagotchi/idle_recap.rb +9 -0
- data/lib/samagotchi/last_turn.rb +5 -2
- data/lib/samagotchi/llm/chat_loop.rb +1 -0
- data/lib/samagotchi/parent_report.rb +17 -5
- data/lib/samagotchi/participant_messages.rb +16 -0
- data/lib/samagotchi/participant_name.rb +21 -0
- data/lib/samagotchi/participants.rb +29 -6
- data/lib/samagotchi/participants_watch.rb +90 -0
- data/lib/samagotchi/plugin/context.rb +7 -1
- data/lib/samagotchi/plugin/side_question.rb +9 -1
- data/lib/samagotchi/queued_command.rb +9 -5
- data/lib/samagotchi/reply_wait/poll.rb +18 -3
- data/lib/samagotchi/run_options.rb +1 -1
- data/lib/samagotchi/send_command.rb +41 -3
- data/lib/samagotchi/session_access/contract.rb +2 -0
- data/lib/samagotchi/session_access/local.rb +31 -9
- data/lib/samagotchi/session_access/remote.rb +19 -2
- data/lib/samagotchi/session_access/web_bridge_client.rb +4 -2
- data/lib/samagotchi/session_access.rb +6 -3
- data/lib/samagotchi/session_commands.rb +6 -0
- data/lib/samagotchi/session_inbox.rb +31 -5
- data/lib/samagotchi/session_manager.rb +43 -3
- data/lib/samagotchi/session_metrics/side_totals.rb +21 -7
- data/lib/samagotchi/session_metrics.rb +94 -1
- data/lib/samagotchi/session_participants.rb +59 -0
- data/lib/samagotchi/sessions_command.rb +10 -7
- data/lib/samagotchi/share_command.rb +61 -22
- data/lib/samagotchi/side_usage.rb +4 -2
- data/lib/samagotchi/steer.rb +120 -11
- data/lib/samagotchi/system_prompt.rb +21 -2
- data/lib/samagotchi/terminal_ui/attach_link.rb +5 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +94 -22
- data/lib/samagotchi/terminal_ui/formatting.rb +66 -5
- data/lib/samagotchi/terminal_ui/local_link.rb +7 -0
- data/lib/samagotchi/terminal_ui/participant_commands.rb +77 -0
- data/lib/samagotchi/terminal_ui/reline_seam.rb +21 -1
- data/lib/samagotchi/terminal_ui/remote_link.rb +6 -2
- data/lib/samagotchi/terminal_ui/status_row.rb +11 -2
- data/lib/samagotchi/terminal_ui.rb +5 -0
- data/lib/samagotchi/testing/host.rb +4 -2
- data/lib/samagotchi/testing/recorder.rb +3 -1
- data/lib/samagotchi/toolkit.rb +21 -3
- data/lib/samagotchi/turn_checkpoint.rb +5 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +45 -20
- data/lib/samagotchi/web/grants.rb +89 -8
- data/lib/samagotchi/web/participant_access.rb +206 -35
- data/lib/samagotchi/web/participant_review.rb +165 -0
- data/lib/samagotchi/web/participant_stream_filter.rb +157 -0
- data/lib/samagotchi/web/participant_view.rb +415 -0
- data/lib/samagotchi/web/public/app.js +15 -1
- data/lib/samagotchi/web/public/bubbles.js +3 -3
- data/lib/samagotchi/web/public/data.js +20 -0
- data/lib/samagotchi/web/public/index.html +75 -30
- data/lib/samagotchi/web/public/info_bar.js +12 -5
- data/lib/samagotchi/web/public/info_bar_model.js +8 -5
- data/lib/samagotchi/web/public/participant.html +119 -0
- data/lib/samagotchi/web/public/participant.js +300 -0
- data/lib/samagotchi/web/public/participant_model.js +160 -0
- data/lib/samagotchi/web/public/participant_names.js +21 -0
- data/lib/samagotchi/web/public/participants_panel.js +311 -0
- data/lib/samagotchi/web/public/stream_handlers.js +8 -1
- data/lib/samagotchi/web/public/tokens.css +12 -0
- data/lib/samagotchi/web/public/turn_events.js +16 -5
- data/lib/samagotchi/web/public/turn_html.js +8 -4
- data/lib/samagotchi/web/public/turn_view.js +2 -1
- data/lib/samagotchi/web/public/working_set.js +6 -4
- data/lib/samagotchi/web/public/working_set_view.js +4 -4
- data/lib/samagotchi/web/qr.rb +14 -0
- data/lib/samagotchi/web/running.rb +52 -0
- data/lib/samagotchi/web/server.rb +13 -0
- data/lib/samagotchi/web/session_access_routes.rb +3 -0
- data/lib/samagotchi/worker.rb +125 -11
- data/lib/samagotchi/worker_inbound.rb +12 -1
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 13a61f40d26663d6ecd66b89b94d1406f8495e161a5bbdd33a319a41e46dbfb3
|
|
4
|
+
data.tar.gz: 2b0ab74468e6020dad2b60b12abba06809933621234080f0ef75f8fbc6243ccc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d53a1f7c2f4347d9688e3d5f4000400297b343d2089536dd9efececcaf30cacceec91834639fed0750848e098c4845e4622c44f6b1df49180a84422dfc153430
|
|
7
|
+
data.tar.gz: 83dce80d3f6ebaa03b1bc84c6d84f025bb6055a9125c0e6ade2a7ad427922c8c2dbeb631991500e9b3be9454161917229d2f0b5f27b55f0a36e7afd0f3ab76c2
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,73 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.106.0] - 2026-10-23
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Participants (experimental) can now ask questions and comment: a participant's message through chi web is a
|
|
16
|
+
review turn of its own, queued behind the running one, that the model answers read-only. It may read the session's
|
|
17
|
+
project (never its secret files, `~/.ssh` or chi's own state and config) and project memories, and proposes
|
|
18
|
+
changes as text for the owner to apply; writes, commands, fetches, notes, reminders, plugin and MCP tools are
|
|
19
|
+
denied, never asked about, and every approval or question stays the owner's. A line starting `!` or `/` and images
|
|
20
|
+
are refused. Each participant gets one running and 3 waiting turns (`web.participants.queue`) and a spending cap,
|
|
21
|
+
`chi share --cap DOLLARS` (default $2, `web.participants.cap`); at the cap their next comment is refused and the
|
|
22
|
+
owner gets a notice, and `chi share cap ID P DOLLARS` raises it. `/stats` shows each participant's spend, their
|
|
23
|
+
turns and the side requests they caused. Hooks see `author:` on a participant's turn and plugins
|
|
24
|
+
`ctx.turn_author`; a hook still runs with the owner's power. A participant now sees the conversation and comments
|
|
25
|
+
from their page (desktop and phone): prompts and replies with who wrote each, one-line tool rows (the tool and a
|
|
26
|
+
project file's path, never its arguments or output), the reply streaming in, their own queued comments (Take back),
|
|
27
|
+
"waiting for the owner" while you answer a question, and every refusal in words. They never see tool output,
|
|
28
|
+
thinking, approvals, questions, memories, notes, the working set, cost or host paths: one server-side filter covers
|
|
29
|
+
their stream and reads, and redacts secret-shaped text as well. On your page, a participant's prompts carry their
|
|
30
|
+
name, and the info bar's participants chip opens a panel to invite (a link to copy, share or scan), see each one's
|
|
31
|
+
spend, turns and last visit, change a cap, revoke, and open the participant view: their page for this session, as
|
|
32
|
+
they see it (an agent participant can wait on its turns' replies through chi web's API). The `review`
|
|
33
|
+
toolkit is new and reserved: config's `toolkit.kits.review` is refused. A session whose worker predates this
|
|
34
|
+
release takes no participant's turn until it is restarted. In the attached terminal a participant's message reads
|
|
35
|
+
`Alex ▸ review> …`, a line says when someone is invited, revoked or expires, the status row counts them, and
|
|
36
|
+
`/share` and `/participants` (list, revoke, cap) run `chi share` on the session there. See [docs/cli.md](docs/cli.md#participants) and
|
|
37
|
+
[docs/guardrails.md](docs/guardrails.md#a-participants-review-boundary).
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Web, dark theme: the working set's rail (its rule and count), the list's header rows (`files · 2`) and its ops
|
|
42
|
+
text and buttons (drop, pin, add) are easier to read.
|
|
43
|
+
- Web: `!rollback` (and a canceled continue) updates the working set at once; one that emptied it no longer leaves
|
|
44
|
+
the dropped items in the rail and the list until the next turn.
|
|
45
|
+
- `chi share`'s link names the port the running chi web listens on (recorded in `$XDG_STATE_HOME/samagotchi/web.json`),
|
|
46
|
+
not only config's `web.port`.
|
|
47
|
+
- A participant's `chi --remote NAME send -m` prints the review turn's id, and `send --wait -m` waits on it instead of
|
|
48
|
+
being refused as busy; a participant's turn held for the owner reads `waiting_for_owner` (exit 3).
|
|
49
|
+
- `chi send --wait --format json`: a turn still running names the wait on its turn id.
|
|
50
|
+
- The attached terminal no longer announces your own `/share` or `/participants revoke` a second time.
|
|
51
|
+
- Web: the session bar's first line keeps one width through a turn: the status word takes the same room for idle and
|
|
52
|
+
running, the working set's chip shows from the session's start (`set 0`), and every chip is one height. A short
|
|
53
|
+
folder path no longer hops between the bar's rows mid-turn and moves the stage.
|
|
54
|
+
|
|
55
|
+
Update with `chi update`.
|
|
56
|
+
|
|
57
|
+
## [0.105.0] - 2026-10-23
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Web: the stage view's **working set** is on by default (no longer experimental). Beside the running turn a rail
|
|
62
|
+
shows the files, memories and URLs it brought into the model's context; each finished turn gets a footprint line;
|
|
63
|
+
the session bar's `● set N` chip (in place of the mem chip) opens the session's list, where rows say whether the
|
|
64
|
+
model still has them (stale, forgotten, dropped) and offer drop, pin, add and refresh. Turn it off with
|
|
65
|
+
`web.working_set: false`, `chi web --no-web-working-set` or `?working_set=0` (the mem chip comes back). See
|
|
66
|
+
[docs/cli.md](docs/cli.md#the-working-set).
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- `chi --remote NAME sessions list` shows test sessions, marked `[test]`, as the local list does.
|
|
71
|
+
- The attached terminal echoes a command typed after `/unqueue`'s text (`fix it/unqueue`) as the command alone, not the
|
|
72
|
+
whole line.
|
|
73
|
+
- Attaching to a session whose last turn was stopped or canceled shows the turn's canceled line, not the text it
|
|
74
|
+
streamed before the stop as if it were the answer.
|
|
75
|
+
|
|
76
|
+
Update with `chi update`.
|
|
77
|
+
|
|
11
78
|
## [0.104.0] - 2026-10-23
|
|
12
79
|
|
|
13
80
|
### Added
|
|
@@ -3944,7 +4011,9 @@ and long-lived sessions.
|
|
|
3944
4011
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3945
4012
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3946
4013
|
|
|
3947
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
4014
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.106.0...HEAD
|
|
4015
|
+
[0.106.0]: https://github.com/dm1try/samagotchi/compare/v0.105.0...v0.106.0
|
|
4016
|
+
[0.105.0]: https://github.com/dm1try/samagotchi/compare/v0.104.0...v0.105.0
|
|
3948
4017
|
[0.104.0]: https://github.com/dm1try/samagotchi/compare/v0.103.0...v0.104.0
|
|
3949
4018
|
[0.103.0]: https://github.com/dm1try/samagotchi/compare/v0.102.0...v0.103.0
|
|
3950
4019
|
[0.102.0]: https://github.com/dm1try/samagotchi/compare/v0.101.0...v0.102.0
|
data/docs/architecture.md
CHANGED
|
@@ -470,7 +470,10 @@ The per-session port is OS-assigned (bound to `0`) and published to a `bridge.js
|
|
|
470
470
|
for client discovery. `chi web`'s `GET /api/sessions/:id/stream` proxies this bridge
|
|
471
471
|
(503 `not_live` when the worker is not running; full history of any session is served by
|
|
472
472
|
`GET /api/sessions/:id/output`), as a byte pipe for the owner: `App#proxy_stream_body` gives `ProxyStreamBody` the
|
|
473
|
-
request
|
|
473
|
+
request's frame filter (`App#stream_filter`: nil for the owner; for a participant, and the owner's participant view
|
|
474
|
+
`?as=participant`, a `Web::ParticipantStreamFilter`, which cuts the Bridge's chunks into frames and writes each again
|
|
475
|
+
from `Web::ParticipantView.event`'s allowlist, dropping every other event; only `from_seq` and `snapshot` reach the
|
|
476
|
+
Bridge; [internals/participants.md](internals/participants.md#what-a-participant-sees)). Resume/ring-buffer state is **in-memory**, per worker: a cursor from
|
|
474
477
|
an earlier worker gets the `reset` snapshot, not a replay.
|
|
475
478
|
|
|
476
479
|
**Session hub.** The cross-session layer (which sessions exist, who owns them, what changed)
|
|
@@ -513,8 +516,8 @@ calling the `SessionAccess::Local` operation of the same name, so a remote backe
|
|
|
513
516
|
token-gated like the rest (LAN mode: `Authorization: Bearer`), and one that takes an id is a `:session` route (an id
|
|
514
517
|
that isn't one, or no such session: 404 `not_found`). Every `ROUTES` row also names who may use it, checked against
|
|
515
518
|
the request's `Web::Principal` (`App#principal_for`): `owner`, or `participant` for a route a participant may use too.
|
|
516
|
-
A participant (a `Web::Grants` token for one session) may use
|
|
517
|
-
each answered as theirs; every other route is `owner` (Permission below, and the participants routes in
|
|
519
|
+
A participant (a `Web::Grants` token for one session) may use the page (`GET /`), `GET /api/info`, their session's
|
|
520
|
+
show, review and filtered stream, and their turns and waits, each answered as theirs; every other route is `owner` (Permission below, and the participants routes in
|
|
518
521
|
[internals/participants.md](internals/participants.md)); `spec/web/principal_spec.rb` fails on a row without one. How a request flows end to end, which checks run where and the trust
|
|
519
522
|
model: [internals/remote-sessions.md](internals/remote-sessions.md).
|
|
520
523
|
|
data/docs/cli.md
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
- `chi web --web-host lan` — the Web UI on your home network too, for your phone: a link with an access token and its QR code (see [chi web on your phone](#chi-web-on-your-phone)); `chi web --new-token` replaces the token
|
|
16
16
|
- `chi web --web-markdown` — opt in to sanitized Markdown rendering for completed assistant messages, the stage's asking text and a resolved question card's reply
|
|
17
17
|
- `chi web --web-view turn` — draw turns with the turn view (each turn as one block of steps, the running one at the bottom of the history) instead of the default stage view (the running turn pinned above the composer); `?view=stage|turn` on the page URL overrides it (see [Web views](#web-views))
|
|
18
|
-
- `chi web --web-working-set` — the stage view's working set (
|
|
18
|
+
- `chi web --no-web-working-set` — turns the stage view's working set off (on by default; `--web-working-set` turns a config's `web.working_set: false` back on): what each turn brought into the model's context, in the stage and under each finished turn; `?working_set=1|0` on the page URL overrides it (see [The working set](#the-working-set))
|
|
19
19
|
- `chi sessions list|stop|restart|archive|unarchive|delete|prune|clean|stats` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)). `stats ID` prints a session's cost, tokens and progress, and its own settings (`--set`; a `settings:` line), from a live worker's `GET stats`, else its saved `analytics.json` (`--format json` one object, with `settings`), without running a model turn. A usage error (an unknown subcommand or flag, a flag missing its value, `stop`, `restart` or `delete` with no ids, a bad `list --format` or `--scope`, a `--days`, `--keep` or `--limit` that isn't a whole number) exits 2; an unknown or refused session exits 1
|
|
20
20
|
- `chi note [--source NAME] [-m TEXT] (ID|PREFIX)... | --all` — add a context note (TEXT or stdin) to sessions: background the model sees on its next turn; it starts no turn (see [Sessions: Context notes](sessions.md#context-notes))
|
|
21
21
|
- `chi context <add|push|ls|show|refresh|rm|mute|unmute> …` — attach live external text to sessions (a command chi runs every so often, or text pushed in); the model gets a short note when it changes and reads it with `context_read`; `chi context add <URL>` attaches a GitHub PR with the github-pr bundle (see [Attached context](context.md))
|
|
22
22
|
- `chi send [-m TEXT] [--image PATH]... [--cut | --queue] (ID|PREFIX)...` — send a message to sessions as if typed there: a turn starts (or a running one takes it at its next step; `--cut` asks it to cut long thinking for it; `--queue` runs it as a turn of its own after the running one, and `--queue --wait` waits for that turn's own answer); piped stdin goes above `-m` as quoted context, and `--image` attaches images (see [Sessions: Sending a message](sessions.md#sending-a-message)); `--new` starts a session with it instead (`--new --continues ID|last:ID` the next link of that session's chain, see [Session chains](sessions.md#session-chains)), and `--wait` prints the answer (`--wait ID` with no message waits for the next reply without sending — to an idle session whose last turn already ended, that ended turn's reply or end comes back; `--wait --turn T ID` with no message waits for exactly turn T, whether running, queued or last ended; `--format json` includes its `turn_id` when known, for waiting again after exit 3 or 4; exit 3 a question waits, its options on stderr; exit 4 `--timeout` passed with the turn still running; see [Starting a session](sessions.md#starting-a-session))
|
|
23
23
|
- `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))
|
|
24
24
|
- `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))
|
|
25
|
-
- `chi share ID --name NAME [--agent] [--expires 3d]`, `chi share ls [ID]`, `chi share revoke ID [P] | --all` — invite a participant to review a session through chi web on the LAN (a link of their own, 7 days), list them, revoke them (see [Participants](#participants))
|
|
25
|
+
- `chi share ID --name NAME [--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 review a session through chi web on the LAN (a link of their own, 7 days, a $2 spending cap), list them, change one's cap, revoke them (see [Participants](#participants))
|
|
26
26
|
- `chi --remote NAME send|answer|note|context|sessions|share …` (or `CHI_REMOTE=NAME`) — those commands on that machine's sessions, through its chi web (see [Remote sessions](#remote-sessions))
|
|
27
27
|
- `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))
|
|
28
28
|
- `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))
|
|
@@ -760,7 +760,7 @@ REPL alike:
|
|
|
760
760
|
it is in the input history, ↑; not with input from a pipe; its images don't
|
|
761
761
|
come back), then the one before it on the next `/unqueue`. A command typed
|
|
762
762
|
after that text without clearing it (`fix it/unqueue`, `fix it/queue more`)
|
|
763
|
-
runs as the command, and the text stays in the input. `(nothing queued: …)` when none waits,
|
|
763
|
+
runs as the command (the scrollback shows the command alone), and the text stays in the input. `(nothing queued: …)` when none waits,
|
|
764
764
|
`(not removed: it already started)` when its turn won the race, `(not removed:
|
|
765
765
|
it is already gone)` when another client removed it. Other terminals that
|
|
766
766
|
showed it say `(a queued message was removed)`; the web drops its bubble. A
|
|
@@ -1010,12 +1010,12 @@ made (up to 120 lines or 8 KB), live and after a reload (see
|
|
|
1010
1010
|
|
|
1011
1011
|
### The working set
|
|
1012
1012
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
turn view ignores it.
|
|
1013
|
+
The stage view shows what a turn brought into the model's context: the files
|
|
1014
|
+
it read, edited or wrote, the memories it read or wrote and the URLs it
|
|
1015
|
+
fetched. It is on by default; `web.working_set: false` (or `chi web
|
|
1016
|
+
--no-web-working-set`, `SAMAGOTCHI_WEB_WORKING_SET=false`; `?working_set=0` on
|
|
1017
|
+
the page URL for one page load, `?working_set=1` back on) turns it off, and the
|
|
1018
|
+
session bar shows the `mem N` chip again. The turn view ignores it.
|
|
1019
1019
|
|
|
1020
1020
|
- **The rail**, on the right of the stage's live slots: the running turn's set
|
|
1021
1021
|
in four lines, folded as it grows (files under one folder become
|
|
@@ -1027,8 +1027,10 @@ turn view ignores it.
|
|
|
1027
1027
|
on a phone it isn't shown.
|
|
1028
1028
|
- **The footprint**, one line under each finished turn's block, live and after
|
|
1029
1029
|
a reload: `touched …/web/public/ · 4 files · ◆ 2 memories s2–10 · +1 url ·
|
|
1030
|
-
edited 1
|
|
1031
|
-
|
|
1030
|
+
edited 1`, then `n stale`, `n forgotten` or `n dropped` while some are (a file
|
|
1031
|
+
changed after the turn read it, even at its very end, says so here). A step
|
|
1032
|
+
tag (`s2`) goes to that call's row; the rest opens the turn's list. A turn
|
|
1033
|
+
that touched nothing has none.
|
|
1032
1034
|
- **The `set N` chip** in the session bar, in place of the `mem N` chip (whose
|
|
1033
1035
|
tooltip goes too): the session's whole set, preloaded (`--memory`) and muted
|
|
1034
1036
|
(`--mute`) memories included; its dot lights when the set changed since you
|
|
@@ -1235,7 +1237,7 @@ chi --remote studio --dir /Users/me/app -p "hi" # a new session there (--
|
|
|
1235
1237
|
`/detach`, `/exit`, `/exit --delete`, `/archive`, `/stats` and `/recap` work
|
|
1236
1238
|
as here, on that machine's session (`/exit --delete` deletes it there).
|
|
1237
1239
|
The status row's children segment counts its delegates there, and a join
|
|
1238
|
-
shows the steps' durations and a failed turn's line from its saved records
|
|
1240
|
+
shows the steps' durations and a failed or canceled turn's line from its saved records
|
|
1239
1241
|
there. A chi web too old for one of them says so in one line ("remote
|
|
1240
1242
|
studio runs chi X, too old for this command through --remote: update chi
|
|
1241
1243
|
there").
|
|
@@ -1272,10 +1274,11 @@ A session's owner can invite **participants**, people or agents, to review it th
|
|
|
1272
1274
|
|
|
1273
1275
|
```sh
|
|
1274
1276
|
chi share 3f9a1c2e --name Alex
|
|
1275
|
-
# p_7c01d2aa Alex (human, reviewer) · session 3f9a1c2e · expires 2026-10-30 14:02
|
|
1277
|
+
# p_7c01d2aa Alex (human, reviewer) · session 3f9a1c2e · expires 2026-10-30 14:02 · cap $2.00
|
|
1276
1278
|
# http://192.168.1.29:4567/?token=chp_…#s=3f9a1c2e-…
|
|
1277
1279
|
# (shown once: chi keeps only its digest; chi share revoke 3f9a1c2e p_7c01d2aa ends it)
|
|
1278
1280
|
chi share ls # every session's participants (--format json; never a token)
|
|
1281
|
+
chi share cap 3f9a1c2e p_7c01d2aa 5 # Alex's turns may cost you $5, from their next one
|
|
1279
1282
|
chi share revoke 3f9a1c2e # every link to that session; add a participant id for one
|
|
1280
1283
|
chi share revoke --all # every link to every session
|
|
1281
1284
|
```
|
|
@@ -1283,13 +1286,52 @@ chi share revoke --all # every link to every session
|
|
|
1283
1286
|
- The link is printed once (with a QR code at a terminal): chi keeps only a digest of its token. Send it to the
|
|
1284
1287
|
participant; opening it sets their browser's cookie, as the owner's LAN link does. `--agent` marks an agent
|
|
1285
1288
|
participant, which uses the token as `Authorization: Bearer` (e.g. `chi --remote` with that token).
|
|
1286
|
-
- It works for 7 days (`--expires Nh` or `Nd`, 1 hour to 30 days), until it is revoked,
|
|
1287
|
-
archived or deleted. A revoked or expired link shows "This review link has expired or was
|
|
1288
|
-
|
|
1289
|
-
|
|
1289
|
+
- It works for 7 days (`--expires Nh` or `Nd`, 1 hour to 30 days; `web.participants.expires`), until it is revoked,
|
|
1290
|
+
or until the session is archived or deleted. A revoked or expired link shows "This review link has expired or was
|
|
1291
|
+
revoked."
|
|
1292
|
+
- **Review turns (experimental).** A participant can ask questions and comment through chi web's `POST …/turn`: each
|
|
1293
|
+
message is a turn of its own, queued behind the running one, that the model answers **read-only**. It may read the
|
|
1294
|
+
project (never its secret files or chi's own), and proposes changes as text or a diff for you to apply; it can't
|
|
1295
|
+
write, run, fetch, send notes or set reminders, and any approval or question is yours. A line starting `!` or `/`
|
|
1296
|
+
and images are refused. One running and 3 waiting turns per participant (`web.participants.queue`). You pay: each
|
|
1297
|
+
participant has a spending cap (`--cap DOLLARS`, default $2, `web.participants.cap`), counted from their turns'
|
|
1298
|
+
cost and the side requests they cause; at the cap their next comment is refused and you get a notice, and `chi
|
|
1299
|
+
share cap` raises it. `/stats` shows each participant's spend. Their turns go only to a worker with the review
|
|
1300
|
+
boundary: restart an older one. While your turn waits at the step limit (Continue or Stop), their turns wait too
|
|
1301
|
+
(still queued, still counted): they run, in order, once you continue, stop or send a turn of your own.
|
|
1302
|
+
- **The participant page.** Their link opens a page of its own (never yours): the session's title and status, who
|
|
1303
|
+
else takes part, and the conversation as a participant sees it: every prompt and reply with who wrote it, each
|
|
1304
|
+
step's tools as one line (`read docs/plan.md`: the tool and a project file's path, never arguments or output), and
|
|
1305
|
+
the reply streaming in while a turn runs. A "Comment or ask" box sends their comment; their queued comments show
|
|
1306
|
+
with **Take back**; "waiting for the owner" shows while you answer a question or an approval of the turn; a refused
|
|
1307
|
+
comment (a `!` or `/` line, too many waiting, their cap reached) says why. On a phone it is the thread first with
|
|
1308
|
+
the box pinned, tool lines folded. They never see tool output or arguments, thinking, approvals or questions,
|
|
1309
|
+
memories, context notes, plugin and delegate input, cards, command output, the working set, cost, timing or host
|
|
1310
|
+
paths; secret-shaped text (API keys, tokens, private keys) is redacted too. An agent participant can wait on its
|
|
1311
|
+
turns' replies through the API, or with chi: its link's token in a remote, `chi --remote NAME send --wait -m "…" ID`,
|
|
1312
|
+
or `send -m`, which prints the turn id `--wait --turn T` takes. They can't reach anything else: every other route answers them 403, another
|
|
1313
|
+
session 404.
|
|
1314
|
+
- **Your side of it.** In the session bar, the participants chip (`invite` while nobody has a link, then `2
|
|
1315
|
+
participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
|
|
1316
|
+
spend against their cap, turns and expiry, with **Cap** and **Revoke** (revoking ends their page and stream and
|
|
1317
|
+
drops their queued comments). **Invite** takes a name, person or agent, the expiry and a cap, and shows the link
|
|
1318
|
+
once, with Copy, Share (a phone's share sheet) and its QR code. **Participant view ↗** opens their page for this
|
|
1319
|
+
session in a new tab, read-only, through the same server filter: exactly what they see. A participant's prompt
|
|
1320
|
+
shows their name in their colour, live and after a reload.
|
|
1290
1321
|
- `chi --remote studio share …` invites on studio: the link names studio's LAN address. Locally, the link names
|
|
1291
|
-
this machine's chi web
|
|
1322
|
+
this machine's running chi web, on the port it listens on (it records it in `$XDG_STATE_HOME/samagotchi/web.json`;
|
|
1323
|
+
`web.port` when none answers there); without one on the LAN, chi says the link opens on this machine only.
|
|
1292
1324
|
- The owner's own token and the `chi web --new-token` rotation are separate: rotating it ends no participant's link.
|
|
1325
|
+
- **In the attached terminal.** A participant's message reads `Alex ▸ review> is step 3 right?`, the name in that
|
|
1326
|
+
participant's own colour (queued, running, and the last exchange when you join); yours stay as they were. A line
|
|
1327
|
+
says when someone is invited, revoked or their link expires (`participants> Alex joined (p_7c01d2aa)`,
|
|
1328
|
+
`participants> Alex left: their link was revoked`), within a few seconds and between turns, and the status row
|
|
1329
|
+
counts who is in the review (`2 participants`). The same commands work from there, on the attached session:
|
|
1330
|
+
`/share NAME [--agent] [--expires 3d] [--cap 2]` (the link and its QR code, once), `/participants` (the list),
|
|
1331
|
+
`/participants revoke [P]` (one participant, or everyone) and `/participants cap P DOLLARS`. They are `chi share`'s
|
|
1332
|
+
own, so under `chi --remote NAME` they reach that machine's chi web, as `chi --remote NAME share` does; an invite or
|
|
1333
|
+
a revoke made with them isn't announced again by the `participants>` line. The REPL
|
|
1334
|
+
(`--no-shared`) answers them with a note: a participant's turns run on the session's worker.
|
|
1293
1335
|
|
|
1294
1336
|
How it works inside (grants, the gate, the routes, the audit trail):
|
|
1295
1337
|
[internals/participants.md](internals/participants.md).
|
|
@@ -1415,7 +1457,7 @@ changes:
|
|
|
1415
1457
|
|
|
1416
1458
|
```
|
|
1417
1459
|
status> Coordinator | model=Qwen3.6-35B | ↳ 3f2a1c9e | ctx=12.3% (under20) | mem: notes, cli_usage | muted: gh-helper
|
|
1418
|
-
status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | mem: skill_coordinator
|
|
1460
|
+
status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | 2 participants | ctx=8.1% (under20) | mem: skill_coordinator
|
|
1419
1461
|
```
|
|
1420
1462
|
|
|
1421
1463
|
- The session's title first, when it has one (`/title`; not the first message a session without one is named by),
|
|
@@ -1432,6 +1474,7 @@ status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | me
|
|
|
1432
1474
|
ends and when a delegate's report merges into a turn, the REPL whenever it draws the row. A
|
|
1433
1475
|
child that starts waiting on an approval (which doesn't wake the parent) shows at the next of
|
|
1434
1476
|
those.
|
|
1477
|
+
- `N participants`: who is in the session's review now ([Participants](#participants)), attached only.
|
|
1435
1478
|
- `ctx=`: the kernel's context estimate and its bucket, updated during a turn (on hosts that
|
|
1436
1479
|
report none, `api: openai`, at the turn's end).
|
|
1437
1480
|
- `mem:`: the memories the session read, with its `--memory` list; `muted:` its `--mute` list.
|
data/docs/configuration.md
CHANGED
|
@@ -157,7 +157,7 @@ or `chi web` restart.
|
|
|
157
157
|
| `toolkit.default`, `toolkit.child` | Session creation | The chosen toolkit is saved; changing the defaults does not move existing sessions. |
|
|
158
158
|
| `toolkit.kits`, `execute.description`, `llm_context.policy` | Worker start | These are held by the engine's toolkit or tool registry. |
|
|
159
159
|
| `skip_agents_md` | Prompt rebuild | A running engine caches its built prompt; a new worker or `/model` rebuild uses the changed value. |
|
|
160
|
-
| `context.*` except `context.every_seconds`, `llm_context.*` except `llm_context.policy`, `steer.cut_after`, `memory.index_warn_tokens`, `status.line` | 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. |
|
|
160
|
+
| `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. |
|
|
161
161
|
| `context.every_seconds` | Next context poll | The poller checks it for each source that has no explicit interval. |
|
|
162
162
|
| `guardrails.*` | Next tool call or approval | Guardrail rules and disable lists are reloaded when the config file changes; rule settings take effect on that evaluation. `guardrails.parent_answers` is checked when a parent answers a question. |
|
|
163
163
|
| `hooks`, `bundles` | Worker start | Hooks, plugins and their bundle settings load with the engine. |
|
|
@@ -165,7 +165,7 @@ or `chi web` restart.
|
|
|
165
165
|
| `session.shared` | Next `chi` invocation | It selects attached-worker or in-process mode for that invocation. |
|
|
166
166
|
| `session.idle_exit_minutes`, `session.keep_empty`, `session.max_children`, `session.delegate_reports`, `session.task_wakes`, `session.max_wakes` | Worker start or next matching lifecycle decision | Existing workers keep their idle-exit timer; the other values are read for the next corresponding decision. |
|
|
167
167
|
| `session.retention_*`, `session.max_count`, `session.keep_status`, `session.sweep_interval_hours` | Next retention sweep | `chi web` sweeps on its configured schedule and on `GET /api/sessions`; `chi sessions prune` uses the values for that invocation. The sweep interval is checked on the next hub tick. |
|
|
168
|
-
| `web.*` | `chi web` start | Restart `chi web` to change its bind address, port or page settings. |
|
|
168
|
+
| `web.*` except `web.participants.*` | `chi web` start | Restart `chi web` to change its bind address, port or page settings. |
|
|
169
169
|
| `log.*` | Process start | Each process holds its configured log sink and level after first use; restart that process. |
|
|
170
170
|
| `history.file` | Next history access | The TUI and web composer resolve its path when they read or append history. |
|
|
171
171
|
| `broadcast.*` | Next `chi broadcast` | Each broadcast invocation reads its targeting and triage settings. |
|
|
@@ -1391,7 +1391,7 @@ toolkit:
|
|
|
1391
1391
|
default: lean # the sessions you start (unset: full)
|
|
1392
1392
|
child: child # delegate children (unset: full)
|
|
1393
1393
|
kits: # your own, on a base
|
|
1394
|
-
|
|
1394
|
+
audit: { base: lean, add: [web_fetch, delegate], remove: [memory_write] }
|
|
1395
1395
|
terse: { base: lean, prompt: "- Be concise in your responses" }
|
|
1396
1396
|
```
|
|
1397
1397
|
|
|
@@ -1402,13 +1402,14 @@ toolkit:
|
|
|
1402
1402
|
| `child` | `lean` plus `send_note`, without `memory_write`, `delegate`, `delegate_result` |
|
|
1403
1403
|
| `coordinator` | `lean` plus `delegate`, `delegate_result`, `list_sessions` (declared by the coordinator bundle) |
|
|
1404
1404
|
| `coding` | `child`, with the prompt text `- Be concise in your responses` / `- Show file paths clearly when working with files` (declared by the coordinator bundle, for build and fix children) |
|
|
1405
|
+
| `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 |
|
|
1405
1406
|
|
|
1406
1407
|
- **Plugin tools** are declared in every toolkit unless its `remove:` names them, and so is `forget_outputs` under
|
|
1407
1408
|
the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:`, `remove:` and `prompt:` (any other
|
|
1408
|
-
key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name. A bundle declares its own with `chi.toolkit` ([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil)).
|
|
1409
|
+
key, a typo or a `tools:` list, is ignored with a warning); it can't reuse a built-in's name (`review`'s least of all: it is the participants' boundary). A bundle declares its own with `chi.toolkit` ([plugins](plugins.md#chitoolkitname-base-add--remove--prompt-nil)).
|
|
1409
1410
|
- **A kit's prompt text.** `prompt:` (text, several lines allowed) goes into the system prompt of the sessions on
|
|
1410
1411
|
that kit, verbatim, with no header: after AGENTS.md and the appended files, before the memory indexes. A kit
|
|
1411
|
-
without one inherits its base's (`
|
|
1412
|
+
without one inherits its base's (`audit: {base: terse, add: [web_fetch]}` stays concise); `prompt: ""` clears
|
|
1412
1413
|
it; a session's own `--tool` / `/toolkit add` never touch it. Sessions on the same kit share it in the prompt
|
|
1413
1414
|
cache; `full` and every kit without a prompt send the bytes they sent before. Keep it to a line or two that
|
|
1414
1415
|
fits the kit's work (the coordinator bundle's `coding`: be concise): one that isn't text or is over 2000
|
|
@@ -1716,9 +1717,12 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1716
1717
|
| `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token (`chi web --new-token` replaces it). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
|
|
1717
1718
|
| `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
|
|
1718
1719
|
| `web.view` | `stage` | yes | How `chi web` draws a turn: `stage` (the running turn pinned above the composer) or `turn` (one block per turn in the history). See [CLI](cli.md#web-views). |
|
|
1719
|
-
| `web.working_set` | `
|
|
1720
|
+
| `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). |
|
|
1720
1721
|
| `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). |
|
|
1721
1722
|
| `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). |
|
|
1723
|
+
| `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). |
|
|
1724
|
+
| `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. |
|
|
1725
|
+
| `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`). |
|
|
1722
1726
|
| `history.file` | state dir | | Prompt history path, shared by the TUI and the web composer. |
|
|
1723
1727
|
| `no_interrupt` | `false` | `--no-interrupt` | Raise the step limit of a turn to 1000 (or `turn.max_iterations`, when larger); a top-level key. |
|
|
1724
1728
|
| `no_default_input` | `false` | `--no-default-input` | Don't pre-fill `default.input`; a top-level key. |
|
data/docs/guardrails.md
CHANGED
|
@@ -298,6 +298,50 @@ card still names `child-boundary`. The parent (never a delegate), forks and
|
|
|
298
298
|
ordinary sessions don't get it. Like the rules it reads command text: a
|
|
299
299
|
`sh -c` body, a script or `$(…)` isn't looked into.
|
|
300
300
|
|
|
301
|
+
## A participant's review boundary
|
|
302
|
+
|
|
303
|
+
Built in, for a [participant's](internals/participants.md#turns) turns only (a
|
|
304
|
+
turn whose author is `p:<id>`), in every mode (also with
|
|
305
|
+
`guardrails.enabled: false`) and without the guardrails bundle:
|
|
306
|
+
`Guardrails::ReviewBoundary`, rule `review-boundary`, source `core`. It comes
|
|
307
|
+
first among the core checks, before the hooks' asks and the rules, and it
|
|
308
|
+
**denies, never asks**: nobody on the participant's side may approve, and the
|
|
309
|
+
owner's approval would give the participant's text the owner's tools. A
|
|
310
|
+
participant's turn may call only the `review` kit's tools:
|
|
311
|
+
|
|
312
|
+
- `read`, under the session's project root (the work tree top of its folder)
|
|
313
|
+
and never a secret: chi's state and config dirs (sessions, the grants, the
|
|
314
|
+
web token, memories and their `.bundles`, `config.yml`, hooks), `~/.ssh`,
|
|
315
|
+
`~/.gnupg`, `~/.aws`, and the project's `.env*`, `.netrc`, `*.pem`, `*.key`,
|
|
316
|
+
`*.p12`, anything named `credentials` (`.git-credentials` too), `.npmrc`,
|
|
317
|
+
`.pypirc`, ssh keys (`id_rsa`, `id_ed25519`, …), `*.tfstate`,
|
|
318
|
+
`secrets.yml`/`.json`, `.docker/config.json`, `.kube/config` and any config
|
|
319
|
+
under `.git` (a submodule's too). The path is checked as given and as it
|
|
320
|
+
resolves, so a symlink in the project to `~/.ssh` is `~/.ssh`. A session
|
|
321
|
+
whose folder is the home folder or above it reads nothing.
|
|
322
|
+
- `memory_read` with `scope: project` (a read without a scope would fall back
|
|
323
|
+
to the system memories). The prompt's preloaded memories and the memory
|
|
324
|
+
index are in the system prompt anyway: a review session should start
|
|
325
|
+
without personal ones.
|
|
326
|
+
|
|
327
|
+
Everything else is denied with "a participant's review turn may only read …",
|
|
328
|
+
and the model is told to propose the change as text or a diff for the owner
|
|
329
|
+
to apply: `write`, `edit`, `memory_write`, `execute`, the task tools,
|
|
330
|
+
`delegate`, `send_note`, `list_sessions`, `register_reminder` (it would fire
|
|
331
|
+
an owner turn later), `web_fetch`, `context_read`, `ask_user_question`,
|
|
332
|
+
`forget_outputs` (it would replace the owner's earlier tool outputs with the
|
|
333
|
+
participant's note, which later owner turns would read as their own), and
|
|
334
|
+
every plugin or MCP tool, including through `call_tool` (a built-in reached
|
|
335
|
+
through `call_tool` is checked as that built-in). The session's declared tools
|
|
336
|
+
stay its own (no prompt rebuild per author), so the model may still try a
|
|
337
|
+
write; the turn's header ("This turn may only read") keeps that rare.
|
|
338
|
+
|
|
339
|
+
The kit is `Toolkit::REVIEW`, a frozen built-in: config's `toolkit.kits.review`
|
|
340
|
+
is refused with one message ([configuration](configuration.md#toolkits)), so
|
|
341
|
+
config can't widen the boundary. Hooks still run on a participant's turn, with
|
|
342
|
+
the owner's power ([hooks](hooks.md#participants-turns)); a hook that rewrites a
|
|
343
|
+
call is checked on the call it leaves.
|
|
344
|
+
|
|
301
345
|
## Rules in config.yml
|
|
302
346
|
|
|
303
347
|
```yaml
|
|
@@ -691,6 +735,31 @@ else goes to chi's own rules. It needs nah installed; see its README for the set
|
|
|
691
735
|
- Every load failure is shown once, at the start of the first turn
|
|
692
736
|
(`guardrails> …` in the terminal, a red line on the web).
|
|
693
737
|
|
|
738
|
+
## Participants' text in later turns
|
|
739
|
+
|
|
740
|
+
A participant's message (a review contribution, see
|
|
741
|
+
[internals/participants.md](internals/participants.md#attribution)) stays in
|
|
742
|
+
the history, so later **owner** turns read it. chi marks it: the wire header
|
|
743
|
+
above it says it is a participant's and not the owner's instruction, and the
|
|
744
|
+
system prompt says the same while the session has participants. Owner turns
|
|
745
|
+
keep every guardrail. But an owner turn that says "do what Alex said" hands
|
|
746
|
+
Alex's text the owner's tools: the model will act on it as the owner asked.
|
|
747
|
+
That is the residual risk, documented rather than engineered away: on a
|
|
748
|
+
session with sensitive tools, quote the proposal you want applied in your own
|
|
749
|
+
turn instead.
|
|
750
|
+
|
|
751
|
+
Within its own turn a participant's text can't act: the review boundary
|
|
752
|
+
leaves it reads of the project only, and an injection that convinces the
|
|
753
|
+
model still meets the boundary (containment over detection). What it can do
|
|
754
|
+
is make the model *say* what the history holds: a review turn's model sees the
|
|
755
|
+
whole conversation, earlier tool outputs included, so start a review in a
|
|
756
|
+
fresh session (or a continue), not on a working session full of outputs. A
|
|
757
|
+
line of a participant's text that looks like one of chi's own headers goes
|
|
758
|
+
out quoted (`(quoted) [Steer from the user, …]`), under the real one, in any
|
|
759
|
+
case and after any spaces (chi's context-note and task frames too). The owner's
|
|
760
|
+
due reminders wait for the owner's next turn: they never go into a
|
|
761
|
+
participant's.
|
|
762
|
+
|
|
694
763
|
## Limits
|
|
695
764
|
|
|
696
765
|
Text matching on shell commands stops accidents, not a model set on getting
|
data/docs/hooks.md
CHANGED
|
@@ -74,6 +74,15 @@ of the hook about to run) and the callables `notify:`, `ask_user:`,
|
|
|
74
74
|
undefined behaviour. `:before_tool_call` carries no messages (the gate stays
|
|
75
75
|
cheap).
|
|
76
76
|
|
|
77
|
+
## Participants' turns
|
|
78
|
+
|
|
79
|
+
A [participant's](internals/participants.md#turns) review turn fires the same hooks, and every event of it carries
|
|
80
|
+
`author: "p:<id>"` (the owner's carry no `author:` key, as before); a plugin reads the same as `ctx.turn_author`
|
|
81
|
+
(nil for the owner's turns and between turns). **A hook runs with the owner's power whatever the author**: it may
|
|
82
|
+
read, write and run what the owner's chi may, and `notify`, `ask_user` and `steer` reach the owner. A bundle that
|
|
83
|
+
acts on a turn's text (a prompt rewrite, a fetch of what it names) should check `author:` first: the text may be a
|
|
84
|
+
participant's. The review boundary checks a tool call after the hooks, on the call they leave.
|
|
85
|
+
|
|
77
86
|
## What a hook can do: the runtime
|
|
78
87
|
|
|
79
88
|
Besides reading (and, on `:before_tool_call`, voting on) its event, a hook
|
data/docs/internals/anthropic.md
CHANGED
|
@@ -133,7 +133,8 @@ append-only within a process, so what changes the prefix is:
|
|
|
133
133
|
1. **A system-prompt rebuild** (`SystemPrompt#build` is cached per process and key; `PromptBuilder#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
|
-
overlays and notes); `/thinking` (another key); a toolkit or profile change; a plugin's `tools_changed
|
|
136
|
+
overlays and notes); `/thinking` (another key); a toolkit or profile change; a plugin's `tools_changed!`; the
|
|
137
|
+
session's first participant joining or last leaving (the tail's participants line, participants.md).
|
|
137
138
|
2. **A tool set change**: the same triggers, plus MCP lines that differ on a respawn.
|
|
138
139
|
3. **History edits by design**: the `llm_context` `stale`/`forget` stubs, and the image cut moving a batch.
|
|
139
140
|
|
data/docs/internals/engine.md
CHANGED
|
@@ -481,7 +481,9 @@ TUI. Errors in the sink or an observer are caught; they never break the turn.
|
|
|
481
481
|
|
|
482
482
|
`Engine#announce` puts an event that belongs to no turn (`ANNOUNCEABLE_EVENTS`: `turn_enqueued`, `turn_dequeued`
|
|
483
483
|
(a queued message taken back before it ran, `{enqueued_id, client_id}`: the remover's), `card`,
|
|
484
|
-
`hook_notice`, `guardrail_warning`, `plugin_init_started`,
|
|
484
|
+
`hook_notice`, `guardrail_warning`, `plugin_init_started`, `participants_changed` (`{participants, joined, left}`:
|
|
485
|
+
who is in the review now and who was invited, revoked or expired since the worker last looked; `ParticipantsWatch`,
|
|
486
|
+
[participants](participants.md#the-owners-terminal)), and more) to the observers only. `:side_usage`
|
|
485
487
|
(`{kind, side}`: a side request's usage, the recap's, `/btw`'s or `ask_model`'s, and the session's side sums) goes
|
|
486
488
|
to the observers the same way from `Engine#note_side_usage` (any thread), without being announceable.
|
|
487
489
|
|
|
@@ -523,6 +525,40 @@ owner wrote it (its input file's `author`, stamped by chi web from the request's
|
|
|
523
525
|
has no `author` anywhere: not in its input file, its origin or the session. `GuardrailWiring#begin_turn` gets the
|
|
524
526
|
origin, and `#turn_author` reads its author for the gate's checks.
|
|
525
527
|
|
|
528
|
+
Where a participant's author (`"p:<id>"`; `Steer.participant_author?`: any author but none or `"owner"`) goes:
|
|
529
|
+
|
|
530
|
+
- **The opening message**: `{role: "user", content:, turn_id:, author:, author_name:}` (`Engine#author_fields`;
|
|
531
|
+
the name from `participants.json` at the turn's start, `SessionParticipants`, left out when the sidecar doesn't
|
|
532
|
+
name the participant). Merged input and steers carry the same two keys (`Steer::Line#author`, grouped by sender
|
|
533
|
+
and author so a participant's lines never share the owner's message). Assistant and tool messages carry none:
|
|
534
|
+
`Steer.author_at(messages, index)` resolves the turn's author by position. `ChatLoop#plain` keeps the keys
|
|
535
|
+
(`Steer::AUTHOR_KEYS`).
|
|
536
|
+
- **The wire**: such a message goes out under the review-contribution header (`Steer.header` /
|
|
537
|
+
`Steer.review_header`, both loops through `Steer.wire_text`; see prompt-caching.md). Saved raw.
|
|
538
|
+
- **`Session#last_turn.author`** (`LastTurn#origin` stays the category) and the turn's analytics record's `author`
|
|
539
|
+
(`SessionMetrics`).
|
|
540
|
+
- **Queued turns**: `turn_enqueued`; the accumulator's queued rows. A participant's `/word` or `!line` is never a
|
|
541
|
+
command: `Bridge#handle_post_turn` answers it 403 and `WorkerInbound#queue_as_command` drops it (so
|
|
542
|
+
`QueuedCommand#author` stays nil on every path now).
|
|
543
|
+
- **Delivery**: a participant's input file always waits for a turn of its own (`SessionInbox.waits_for_turn_end?`),
|
|
544
|
+
so `Worker#pending_input_drain` never merges it; the Bridge queues their `POST /turn` whatever its delivery and
|
|
545
|
+
counts their running and queued turns against chi web's `pending_limit` (429 `too_many_turns`).
|
|
546
|
+
`Worker#run_prompt` checks their link and cap (`participant_refusal`: `participants.json`'s entry and cap against
|
|
547
|
+
`SessionMetrics#spend`) before their turn starts and after it ends (the owner's notice, once per cap). A Continue
|
|
548
|
+
keeps the offer turn's author (`ContinueOffer#run_continue_turn`, from `last_turn.author`).
|
|
549
|
+
- **The gate**: `GuardrailWiring#review_boundary` (`Guardrails::ReviewBoundary`) is the first core check; it reads
|
|
550
|
+
`#turn_author` and denies a participant's turn all but `Toolkit::REVIEW_TOOLS` (not `forget_outputs`)
|
|
551
|
+
(docs/guardrails.md). The declared tools stay the session's (D3: no rebuild per author).
|
|
552
|
+
- **Hooks and plugins**: `Engine#turn_author` (the running turn's participant author, nil otherwise) is the hook
|
|
553
|
+
runtime's `turn_author` (`Hooks::Registry#with_runtime` adds `author:` to every event of such a turn) and the
|
|
554
|
+
plugin host's (`ctx.turn_author`).
|
|
555
|
+
- **Spend**: the analytics turn record's `author` feeds `SessionMetrics#spend` / `spend_by_author` (the snapshot's
|
|
556
|
+
`spend_by_author`, `/stats`); `Engine#note_side_usage` stamps a side request's author (`#side_author`: the running
|
|
557
|
+
turn's, the recap's after it, else the owner's) into `SideTotals#by_author`.
|
|
558
|
+
- **Side models**: `IdleRecap::TranscriptFilter` and `Plugin::SideQuestion` name a participant's message as theirs.
|
|
559
|
+
- **The system prompt**: `Engine#refresh_participants!` at a turn's start (after `apply_toolkit!`) rebuilds the
|
|
560
|
+
prompt when the session gains its first active participant or loses its last (the participants line).
|
|
561
|
+
|
|
526
562
|
### Hook points (not events)
|
|
527
563
|
|
|
528
564
|
`:session_start`, `:before_turn`, `:after_turn` and `:session_end` are hook points: the Engine fires them on the
|