samagotchi 0.105.0 → 0.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +168 -1
  3. data/bin/chi +2 -0
  4. data/docs/architecture.md +10 -3
  5. data/docs/cli.md +90 -9
  6. data/docs/configuration.md +9 -5
  7. data/docs/guardrails.md +63 -1
  8. data/docs/hooks.md +9 -0
  9. data/docs/internals/engine.md +22 -4
  10. data/docs/internals/participants.md +319 -45
  11. data/docs/internals/prompt-caching.md +2 -1
  12. data/docs/internals/remote-sessions.md +12 -5
  13. data/docs/internals/working-set.md +8 -2
  14. data/docs/plugins.md +31 -1
  15. data/docs/releasing.md +2 -0
  16. data/docs/sessions.md +36 -1
  17. data/docs/sub-agent.md +2 -0
  18. data/docs/testing.md +5 -1
  19. data/lib/samagotchi/bridge/turn_accumulator.rb +19 -3
  20. data/lib/samagotchi/bridge.rb +39 -1
  21. data/lib/samagotchi/bridge_client/stream_request.rb +10 -3
  22. data/lib/samagotchi/bridge_client.rb +4 -1
  23. data/lib/samagotchi/bundles/coordinator/manifest.yml +6 -5
  24. data/lib/samagotchi/bundles/coordinator/plugin.rb +85 -5
  25. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +18 -0
  26. data/lib/samagotchi/bundles/dev/manifest.yml +2 -2
  27. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  28. data/lib/samagotchi/client_id.rb +21 -9
  29. data/lib/samagotchi/config.rb +16 -1
  30. data/lib/samagotchi/continue_offer.rb +60 -3
  31. data/lib/samagotchi/delivery.rb +10 -0
  32. data/lib/samagotchi/engine.rb +62 -6
  33. data/lib/samagotchi/guardrail_wiring.rb +13 -3
  34. data/lib/samagotchi/guardrails/review_boundary.rb +97 -0
  35. data/lib/samagotchi/guardrails/review_paths.rb +93 -0
  36. data/lib/samagotchi/guardrails.rb +1 -0
  37. data/lib/samagotchi/hook_routes.rb +5 -2
  38. data/lib/samagotchi/hooks/registry.rb +12 -3
  39. data/lib/samagotchi/idle_recap.rb +9 -0
  40. data/lib/samagotchi/model_profile.rb +5 -3
  41. data/lib/samagotchi/parent_report.rb +39 -5
  42. data/lib/samagotchi/participant_messages.rb +27 -0
  43. data/lib/samagotchi/participants.rb +46 -9
  44. data/lib/samagotchi/participants_watch.rb +90 -0
  45. data/lib/samagotchi/plugin/api.rb +2 -1
  46. data/lib/samagotchi/plugin/context.rb +7 -1
  47. data/lib/samagotchi/plugin/sessions.rb +25 -0
  48. data/lib/samagotchi/plugin/side_question.rb +9 -1
  49. data/lib/samagotchi/remote_command.rb +1 -1
  50. data/lib/samagotchi/remotes.rb +23 -8
  51. data/lib/samagotchi/reply_wait/poll.rb +18 -3
  52. data/lib/samagotchi/review_apply.rb +123 -0
  53. data/lib/samagotchi/review_command.rb +121 -0
  54. data/lib/samagotchi/review_start.rb +161 -0
  55. data/lib/samagotchi/run_options.rb +18 -3
  56. data/lib/samagotchi/send_command/wait_target.rb +5 -0
  57. data/lib/samagotchi/send_command.rb +43 -3
  58. data/lib/samagotchi/session.rb +33 -5
  59. data/lib/samagotchi/session_access/contract.rb +12 -2
  60. data/lib/samagotchi/session_access/dispatch.rb +8 -2
  61. data/lib/samagotchi/session_access/http_transport.rb +10 -1
  62. data/lib/samagotchi/session_access/local.rb +50 -11
  63. data/lib/samagotchi/session_access/remote.rb +34 -3
  64. data/lib/samagotchi/session_access/web_bridge_client.rb +1 -1
  65. data/lib/samagotchi/session_access.rb +29 -13
  66. data/lib/samagotchi/session_chain.rb +10 -2
  67. data/lib/samagotchi/session_commands.rb +6 -0
  68. data/lib/samagotchi/session_continue.rb +4 -3
  69. data/lib/samagotchi/session_inbox.rb +31 -5
  70. data/lib/samagotchi/session_manager.rb +68 -6
  71. data/lib/samagotchi/session_metrics/side_totals.rb +21 -7
  72. data/lib/samagotchi/session_metrics.rb +88 -1
  73. data/lib/samagotchi/session_review.rb +28 -0
  74. data/lib/samagotchi/share_command.rb +68 -23
  75. data/lib/samagotchi/side_usage.rb +4 -2
  76. data/lib/samagotchi/steer.rb +37 -1
  77. data/lib/samagotchi/terminal_ui/attach_link.rb +5 -0
  78. data/lib/samagotchi/terminal_ui/attached_loop.rb +58 -8
  79. data/lib/samagotchi/terminal_ui/formatting.rb +66 -5
  80. data/lib/samagotchi/terminal_ui/local_link.rb +7 -0
  81. data/lib/samagotchi/terminal_ui/participant_commands.rb +77 -0
  82. data/lib/samagotchi/terminal_ui/remote_link.rb +6 -2
  83. data/lib/samagotchi/terminal_ui/status_row.rb +11 -2
  84. data/lib/samagotchi/terminal_ui.rb +5 -0
  85. data/lib/samagotchi/testing/host.rb +4 -2
  86. data/lib/samagotchi/testing/recorder.rb +3 -1
  87. data/lib/samagotchi/testing/sessions.rb +10 -2
  88. data/lib/samagotchi/toolkit.rb +21 -3
  89. data/lib/samagotchi/turn_checkpoint.rb +13 -0
  90. data/lib/samagotchi/version.rb +1 -1
  91. data/lib/samagotchi/web/app.rb +151 -41
  92. data/lib/samagotchi/web/grants.rb +172 -18
  93. data/lib/samagotchi/web/participant_access.rb +273 -46
  94. data/lib/samagotchi/web/participant_review.rb +208 -0
  95. data/lib/samagotchi/web/participant_stream_filter.rb +258 -0
  96. data/lib/samagotchi/web/participant_view.rb +426 -0
  97. data/lib/samagotchi/web/public/app.js +75 -6
  98. data/lib/samagotchi/web/public/bubbles.js +3 -3
  99. data/lib/samagotchi/web/public/chip_popover.js +10 -3
  100. data/lib/samagotchi/web/public/data.js +30 -2
  101. data/lib/samagotchi/web/public/delivery_line.js +79 -15
  102. data/lib/samagotchi/web/public/index.html +97 -27
  103. data/lib/samagotchi/web/public/info_bar.js +17 -6
  104. data/lib/samagotchi/web/public/info_bar_model.js +15 -6
  105. data/lib/samagotchi/web/public/live_turn.js +3 -0
  106. data/lib/samagotchi/web/public/participant.html +172 -0
  107. data/lib/samagotchi/web/public/participant.js +447 -0
  108. data/lib/samagotchi/web/public/participant_model.js +220 -0
  109. data/lib/samagotchi/web/public/participant_names.js +53 -0
  110. data/lib/samagotchi/web/public/participants_panel.js +379 -0
  111. data/lib/samagotchi/web/public/question_cards.js +1 -0
  112. data/lib/samagotchi/web/public/review_apply.js +34 -0
  113. data/lib/samagotchi/web/public/review_start.js +101 -0
  114. data/lib/samagotchi/web/public/send_turn.js +25 -1
  115. data/lib/samagotchi/web/public/session_actions.js +44 -3
  116. data/lib/samagotchi/web/public/session_state.js +3 -0
  117. data/lib/samagotchi/web/public/start_page.js +38 -2
  118. data/lib/samagotchi/web/public/stream_handlers.js +18 -1
  119. data/lib/samagotchi/web/public/tokens.css +12 -0
  120. data/lib/samagotchi/web/public/turn_events.js +16 -5
  121. data/lib/samagotchi/web/public/turn_html.js +9 -4
  122. data/lib/samagotchi/web/public/turn_view.js +7 -1
  123. data/lib/samagotchi/web/public/working_set_view.js +3 -3
  124. data/lib/samagotchi/web/qr.rb +14 -0
  125. data/lib/samagotchi/web/running.rb +52 -0
  126. data/lib/samagotchi/web/server.rb +13 -0
  127. data/lib/samagotchi/web/session_access_routes.rb +3 -0
  128. data/lib/samagotchi/web/session_summary.rb +3 -0
  129. data/lib/samagotchi/worker.rb +121 -7
  130. data/lib/samagotchi/worker_inbound.rb +12 -4
  131. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0920f477e95206617da7d0d43e39dc981b071b34f456c60a7b669e4836875be
4
- data.tar.gz: 1f9219bb12b92c2178ab15d57fddfaaee3654972d9b90e2ce11b8265ddab90cf
3
+ metadata.gz: 58e0f763c1974428c1144b884f63a9529fe5b1a973b1443e44b0a4e1a3f3cf48
4
+ data.tar.gz: 6c6a8a429e01a837329065239819bf69568e1455425bcf60e1badae8208553be
5
5
  SHA512:
6
- metadata.gz: 5c7625cff1523fe72e997ec3265465f9385b7a7fe59e8a3f6b097f8015e84f1b32441582218c75b4145f2f8ffb2b58ea7909c30f65efd4dbac114650c4b65f55
7
- data.tar.gz: 3f2afdc87fa1a48efebbc14138016f2da28502e74b3a69d59ddd2c1ae5c37041af2192a0a071cf78325a14c2f630afe28e42cfed03b625f103fbf88f2037533c
6
+ metadata.gz: b0fe1b18a4b7defe96fc787ab753e9cfbf6c62eebddac74d05d74b972439b87737a9b5e6192c1d026dc2add3702b1659be6846ebaa894bd87e65dc5ccdffd823
7
+ data.tar.gz: 7a74e55c15487d3ccdac35ecaaec567b5feefab39312712b2ed07befdbfd9def1b5e4685170cc28550fd89f47eca839e5aa06a3c4128ae426e89962f69f0c1db
data/CHANGELOG.md CHANGED
@@ -8,6 +8,171 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.107.0] - 2026-10-24
12
+
13
+ ### Added
14
+
15
+ - Review sessions for participants (experimental). `chi review docs/plan.md …` starts a session made to be shared:
16
+ its subjects are project files (a secret such as `.env` or a key, a file outside the project or chi's own is
17
+ refused with one message), it starts idle, and it prints the `chi share` line to invite someone. `chi review --from
18
+ ID` starts a review of a working session from its recap, never its transcript; that session keeps running,
19
+ unarchived. Both work with `chi --remote NAME` (`--dir` there). On the web, the start page's `review…` chip takes
20
+ the files, and the participants panel's **Start a review of it** starts a review of the session. Sharing a session that isn't a review
21
+ session still works, with a warning naming how many tool outputs its history holds (`chi share`, the Invite
22
+ dialog). Participants read the subjects as the **Plan**: rendered beside the thread on a wide screen, a Plan |
23
+ Thread toggle on a phone, checked again on every read and redacted. Under each participant's review turn the owner
24
+ gets **Apply**: it puts an owner's turn in the composer, not sent, that quotes the proposal line by line, so the
25
+ participant's words never reach the model as the owner's instruction; sent while a turn runs, it queues as the
26
+ owner's own turn (the delivery line says so; during the owner's own turn Esc steers it in instead), never merged into the running one. Plugins get `ctx.sessions.review(subjects:,
27
+ seed:)`. The coordinator bundle (0.6.0) adds an experimental sprint review: `/coordinate review [handoff]` opens a
28
+ review of the coordinator session seeded with its handoff, where a participant's new context gets a hand-off diff
29
+ and the owner's Apply updates the handoff. See [docs/cli.md](docs/cli.md#review-sessions) and
30
+ [docs/sessions.md](docs/sessions.md#review-sessions).
31
+
32
+ ### Changed
33
+
34
+ - `chi`'s run flags take their whole name only, as the subcommands' flags always have: an abbreviation such as
35
+ `--verb` or `--mem x` is now an invalid option (exit 1) instead of `--verbose` / `--memory x`. Under `chi --remote`,
36
+ an abbreviated local-only flag could slip past the "runs on this machine only" check and be dropped silently.
37
+
38
+ ### Fixed
39
+
40
+ - The owner's Continue on the step-limit offer of a participant whose turn is refused since (revoked, expired, at
41
+ their cap) ends the offer as a Stop does, with a notice: no "resumed" for a turn that never runs, and idle exit,
42
+ `/exit` and that participant's queued turns no longer wait on an offer nobody can answer.
43
+ - A participant's queued turn the worker refuses (revoked, expired, at their cap) ends their `chi send --wait` (and
44
+ chi web's participant wait) at once: status `refused`, exit 1, `cancel_reason` `revoked` or `cap_reached`, instead
45
+ of waiting to the timeout (none by default).
46
+ - The owner's input while a participant's review turn runs queues as the owner's own turn after it, from every
47
+ client (the web, the attached TUI, `chi send` without `--queue`), never steered or cut into their read-only turn;
48
+ each says it was queued (the web's delivery line, the TUI's note, `chi send`'s line), and `chi send --wait` waits
49
+ for that turn's own answer.
50
+ - Participants: an agent participant's `chi send --wait` no longer reads its reply unfiltered: the reply is
51
+ redacted as the page shows it (secret shapes, the owner's token, host paths, no thinking or tool calls), and a
52
+ failed turn says "this turn failed: the session's owner can see why", never the error's own words.
53
+ - Participants: a secret on a long streamed line (`password: …`, an `Authorization:` header, an `sk-…` key, a
54
+ private key's BEGIN line) is no longer sent unredacted when the participant's stream cuts the line at 400
55
+ characters; a line with no space for 1600 characters ends in `[redacted]`. A private key block that begins on the
56
+ line where another one ends is withheld too.
57
+ - Participants: a participant's link opened on chi web's own machine (or `chi share` on a chi web not on the LAN,
58
+ whose link opens on that machine only) is the participant, no longer the owner: a `chp_` token is the participant's
59
+ from any peer, an ended one is refused, and the owner's browser that opened a participant link is the owner's again
60
+ once that link ends.
61
+ - Participants: each refusal has one wording wherever it is met: a participant's `chi --remote` refused an
62
+ owner's command says "a participant can't do that: only the session's owner can", as chi web does, and an ended
63
+ link's page says whether it was revoked or expired.
64
+ - Participants: chi web and `chi share` revoking (inviting, changing a cap) at the same moment can no longer leave
65
+ `participants.json` reading a revoked participant as active, so the worker refuses their queued turns: the file
66
+ is written under the grants store's lock.
67
+ - Participants: a grants store (`web-grants/grants.json`) that doesn't read is no longer overwritten by the next
68
+ invite, which kept only the new grant and dropped every other link's; `chi share` (exit 1) and chi web refuse invite,
69
+ ls, cap and revoke with one line naming the file, and a grant row without its digest or expiry no longer
70
+ makes every participant request a 500.
71
+ - Participants: revoking a participant (or their link expiring) ends their `chi send --wait` already waiting at chi
72
+ web, refused as their link's end, instead of handing them the reply that came within the next 30 s.
73
+ - Participants: an agent participant's `chi send --wait` no longer gets the owner's wait state from chi web (the
74
+ owner's newest reply file, message count, pending question, running turn), only what waiting on its own turn needs.
75
+ - Participants: a reply that opens a private key block and never closes it is withheld to that turn's end, no
76
+ longer hiding every later turn's reply from the participant's stream until they reload.
77
+ - Participants: two of a participant's turns sent at once while no worker runs the session can no longer both pass
78
+ their queue limit.
79
+ - `chi --remote NAME` words a value the remote's chi web refuses (`sessions list --cwd rel/dir`, `note --source " "`)
80
+ as one line naming the command, exit 2, instead of a Ruby backtrace; its other refusals are one line, exit 1.
81
+ - A remote whose address answers something other than HTTP (another server on that port, an SSH banner), or a
82
+ request that can't be written in time, is the one "remote NAME (URL): …" failure line, exit 1 (`chi --remote`,
83
+ `chi remote add`'s check; the attached TUI reads it as unreachable), instead of a Ruby backtrace.
84
+ - The attached TUI on a remote whose host went away (no answer at all, not a refused connection) gives up each
85
+ stream reconnect after 5 s, so it ends within about two minutes instead of about seventeen.
86
+ - `chi --remote NAME context push` with a name that isn't a source's (`a/b`, `Bad Name`) gives the name rule, as
87
+ it does locally, instead of "too old for this command … update chi there" or a URI error.
88
+ - A remote at an IPv6 address (`chi remote add box http://[::1]:4567`) connects: its commands and its attached
89
+ TUI's stream used the bracketed host as a name to look up ("can't find host").
90
+ - Two `chi remote add` / `chi remote rm` at once keep each other's change: `remotes.yml` is rewritten under a lock.
91
+ - `chi --help` and `chi remote --help` list `review` (and `share`) among the commands `chi --remote NAME` runs, and
92
+ `-p` says it stays in the session (the attached TUI by default, the REPL with `--no-shared`), not "in the REPL".
93
+ - The participant page sends a comment once: a second Enter (or Send) while it is being posted no longer sends it
94
+ again as a second review turn.
95
+ - The participant page that can't reach chi web (a network blip, a 5xx) says "Can't reach the session — retrying…" and
96
+ reads again with a backoff (3 s, doubling, at most 30 s), at open and after a turn boundary, instead of a blank or
97
+ silently stale page; a comment that can't be sent says so and stays in the box.
98
+ - The participant page's live reply no longer loses the text that streams while the page re-reads the review (another
99
+ participant's comment queuing mid-turn left a hole until the turn ended): it streams again from the re-read's
100
+ cursor, and an older re-read answering last no longer overwrites a newer one.
101
+ - The participant page shows chi web's own words for a refused comment or take-back ("A participant can't do that:
102
+ only the session's owner can.", too many turns, the spending cap) and for an ended link (revoked or expired),
103
+ instead of its own second wording.
104
+ - The participant page keeps a tool list the reader opened or closed as they left it when the thread is read again or
105
+ the reply streams, and no longer redraws the messages it already shows (a text selection in them stays).
106
+ - The participant page on a phone is the visible viewport's height (100dvh), so its composer sits above the browser's
107
+ URL bar instead of under it.
108
+ - The participant page no longer adds the next turn's first words to the reply of the turn that just ended while it
109
+ reads the review again (a queued turn starting): the live reply waits for the re-read, whose stream replays them.
110
+ - The web info bar's session clock, speed and ctx chips keep one width while a turn runs (tabular digits in a
111
+ reserved width), and the speed and the clock show from the session's start as muted placeholders: a tick or the
112
+ first generation's speed no longer widens the line and wraps a chip onto a new row.
113
+ - The participants panel drops an Invite, Cap or Revoke answered after it closed or another session opened: a
114
+ one-time link no longer shows the next time a panel opens, under another session, and a failed list no longer
115
+ keeps the previous session's working-session warning.
116
+ - The participants panel's **Start a review of it** closes the panel only once the review is made; a refusal stays in
117
+ the open panel, said there, instead of the panel closing on a toast.
118
+ - The participants panel's Revoke asks first (`Revoke Alex's link? …`), as a delete does: it can't be undone, and on
119
+ a phone's sheet it sat one tap away beside Cap.
120
+ - A participant's older prompt drawn before the participants list came (no name saved with it) no longer says
121
+ "a participant" until the page is reloaded: its badge takes the name when the list comes.
122
+ - The start page's `review…` chip can name a file with a space in its path: in double quotes (`"docs/my plan.md"`);
123
+ an unclosed quote is said in its form. A file Start refuses (a secret, outside the project) is said in that form,
124
+ opened again at the files chosen, instead of a blocking alert.
125
+ - The participants panel no longer closes on its own Cap or Invite another (the button, redrawn away by its own
126
+ click, read as a click outside the panel).
127
+
128
+ Update with `chi update` (the system bundle 0.107.0, the coordinator bundle 0.6.0, the dev profile 0.7.2), then restart `chi web`.
129
+
130
+ ## [0.106.0] - 2026-10-23
131
+
132
+ ### Added
133
+
134
+ - Participants (experimental) can now ask questions and comment: a participant's message through chi web is a
135
+ review turn of its own, queued behind the running one, that the model answers read-only. It may read the session's
136
+ project (never its secret files, `~/.ssh` or chi's own state and config) and project memories, and proposes
137
+ changes as text for the owner to apply; writes, commands, fetches, notes, reminders, plugin and MCP tools are
138
+ denied, never asked about, and every approval or question stays the owner's. A line starting `!` or `/` and images
139
+ are refused. Each participant gets one running and 3 waiting turns (`web.participants.queue`) and a spending cap,
140
+ `chi share --cap DOLLARS` (default $2, `web.participants.cap`); at the cap their next comment is refused and the
141
+ owner gets a notice, and `chi share cap ID P DOLLARS` raises it. `/stats` shows each participant's spend, their
142
+ turns and the side requests they caused. Hooks see `author:` on a participant's turn and plugins
143
+ `ctx.turn_author`; a hook still runs with the owner's power. A participant now sees the conversation and comments
144
+ from their page (desktop and phone): prompts and replies with who wrote each, one-line tool rows (the tool and a
145
+ project file's path, never its arguments or output), the reply streaming in, their own queued comments (Take back),
146
+ "waiting for the owner" while you answer a question, and every refusal in words. They never see tool output,
147
+ thinking, approvals, questions, memories, notes, the working set, cost or host paths: one server-side filter covers
148
+ their stream and reads, and redacts secret-shaped text as well. On your page, a participant's prompts carry their
149
+ name, and the info bar's participants chip opens a panel to invite (a link to copy, share or scan), see each one's
150
+ spend, turns and last visit, change a cap, revoke, and open the participant view: their page for this session, as
151
+ they see it (an agent participant can wait on its turns' replies through chi web's API). The `review`
152
+ toolkit is new and reserved: config's `toolkit.kits.review` is refused. A session whose worker predates this
153
+ release takes no participant's turn until it is restarted. In the attached terminal a participant's message reads
154
+ `Alex ▸ review> …`, a line says when someone is invited, revoked or expires, the status row counts them, and
155
+ `/share` and `/participants` (list, revoke, cap) run `chi share` on the session there. See [docs/cli.md](docs/cli.md#participants) and
156
+ [docs/guardrails.md](docs/guardrails.md#a-participants-review-boundary).
157
+
158
+ ### Fixed
159
+
160
+ - Web, dark theme: the working set's rail (its rule and count), the list's header rows (`files · 2`) and its ops
161
+ text and buttons (drop, pin, add) are easier to read.
162
+ - Web: `!rollback` (and a canceled continue) updates the working set at once; one that emptied it no longer leaves
163
+ the dropped items in the rail and the list until the next turn.
164
+ - `chi share`'s link names the port the running chi web listens on (recorded in `$XDG_STATE_HOME/samagotchi/web.json`),
165
+ not only config's `web.port`.
166
+ - A participant's `chi --remote NAME send -m` prints the review turn's id, and `send --wait -m` waits on it instead of
167
+ being refused as busy; a participant's turn held for the owner reads `waiting_for_owner` (exit 3).
168
+ - `chi send --wait --format json`: a turn still running names the wait on its turn id.
169
+ - The attached terminal no longer announces your own `/share` or `/participants revoke` a second time.
170
+ - Web: the session bar's first line keeps one width through a turn: the status word takes the same room for idle and
171
+ running, the working set's chip shows from the session's start (`set 0`), and every chip is one height. A short
172
+ folder path no longer hops between the bar's rows mid-turn and moves the stage.
173
+
174
+ Update with `chi update`.
175
+
11
176
  ## [0.105.0] - 2026-10-23
12
177
 
13
178
  ### Changed
@@ -3965,7 +4130,9 @@ and long-lived sessions.
3965
4130
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
3966
4131
  hotkey panel that send selected text or the clipboard to your sessions.
3967
4132
 
3968
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.105.0...HEAD
4133
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.107.0...HEAD
4134
+ [0.107.0]: https://github.com/dm1try/samagotchi/compare/v0.106.0...v0.107.0
4135
+ [0.106.0]: https://github.com/dm1try/samagotchi/compare/v0.105.0...v0.106.0
3969
4136
  [0.105.0]: https://github.com/dm1try/samagotchi/compare/v0.104.0...v0.105.0
3970
4137
  [0.104.0]: https://github.com/dm1try/samagotchi/compare/v0.103.0...v0.104.0
3971
4138
  [0.103.0]: https://github.com/dm1try/samagotchi/compare/v0.102.0...v0.103.0
data/bin/chi CHANGED
@@ -54,6 +54,8 @@ SUBCOMMANDS = {
54
54
  "answer" => ["samagotchi/answer_command", "AnswerCommand"],
55
55
  # invite participants to review a session, list them, revoke them
56
56
  "share" => ["samagotchi/share_command", "ShareCommand"],
57
+ # a review session for files, or of a session (--from, --dir, --model)
58
+ "review" => ["samagotchi/review_command", "ReviewCommand"],
57
59
  # find a model server and write config.yml (--model, --name)
58
60
  "bootstrap" => ["samagotchi/bootstrap_command", "BootstrapCommand"],
59
61
  # bring an installed chi up to date
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 principal's frame filter (`App#stream_filter`, nil for the owner, the only principal today). Resume/ring-buffer state is **in-memory**, per worker: a cursor from
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 three: `GET /`, `GET /api/info` and `GET /api/sessions/:id`,
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
 
@@ -545,6 +548,8 @@ model: [internals/remote-sessions.md](internals/remote-sessions.md).
545
548
  | `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 | |
546
549
  | `POST /api/sessions/:id/command` (its 501) | owner | | | 501 `not_supported` for a builtin the worker lacks adds `command` (`"/title"`) and `message` |
547
550
  | `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 |
551
+ | `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` |
552
+ | `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` |
548
553
 
549
554
  `GET /api/info` names the feature `session_commands` (`SessionAccessRoutes::FEATURE`): `SessionAccess::Remote` asks it
550
555
  once before its first request, and a chi web without it (or `serve_static`'s 404 for a path no route takes) is "remote NAME runs chi X, too
@@ -689,6 +694,8 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
689
694
  | 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)). |
690
695
  | LLM context strategies | `llm_context_strategy.rb`, `llm_context_view.rb`, `llm_context_stale.rb`, `llm_context_forget.rb` | What a request sends of the history (none, stale, forget) ([internals/llm-context-forget.md](internals/llm-context-forget.md)). |
691
696
  | Session chains | `session_continue.rb` (`SessionContinue`), `session_chain.rb` (`SessionChain`) | A session that continues an earlier one. `SessionChain.opener_for` gives the text web `continue →` prefills in the new link: the previous link's own first typed message when that link continues one itself, else nil (the 201's `opener`; not saved) ([sessions.md](sessions.md)). |
697
+ | Review sessions | `review_start.rb` (`ReviewStart`), `session_review.rb` (`SessionReview`, the session's `review` field), `review_command.rb` (`ReviewCommand`, `chi review`), `review_apply.rb` (`ReviewApply`) | A session made to be shared: idle, its subjects project files checked by `Guardrails::ReviewPaths`, a fork of a working session from its recap (`--from`); Apply quotes a participant's review turn into the owner's composer ([sessions.md](sessions.md#review-sessions), [internals/participants.md](internals/participants.md)). |
698
+ | Participants | `participants.rb` (`Participants`, the `participants.json` sidecar), `share_command.rb` (`ShareCommand`, `chi share`), `session_participants.rb`, `participants_watch.rb`, `web/grants.rb` (`Web::Grants`), `web/principal.rb` (`Web::Principal`), `web/participant_access.rb` (`Web::ParticipantAccess`), `web/participant_review.rb` + `web/participant_view.rb` (`Web::ParticipantReview`, `Web::ParticipantView`), `web/participant_stream_filter.rb`, `guardrails/review_boundary.rb` + `guardrails/review_paths.rb` | The people or agents an owner invites to review one session through chi web: their grants and gate, what they read, their read-only review turns ([internals/participants.md](internals/participants.md), [guardrails.md](guardrails.md)). |
692
699
  | Analytics | `analytics_file.rb` (`AnalyticsFile`), `session_metrics/totals.rb` (`SessionMetrics::Totals`), `session_metrics/model_segment.rb` (`SessionMetrics::ModelSegment`) | A session's `analytics.json` (`SessionMetrics#persist`); the sums over its turn records (`Totals`: tokens, cost, speeds, kept as running counters) and their split per model after a `/model` switch (`ModelSegment`: a run of turns on one model, the `model_segments` rows); `session_metrics/cost.rb` (`SessionMetrics::Cost`): the spend `ctx.cost` reads; `session_metrics/usage.rb` (`SessionMetrics::Usage`): the work `ctx.usage` reads. `session_metrics/side_totals.rb` (`SessionMetrics::SideTotals`) and `side_usage.rb` (`SideUsage`): the side requests' sums, apart from the turns'. |
693
700
  | Images / vision | `image_store.rb` (`ImageStore`), `vision_support.rb`, `vision_context.rb` | Stored prompt images and whether a model takes them. |
694
701
  | Toolkits | `toolkit.rb` (`Toolkit`), `toolkit_binding.rb` (`ToolkitBinding`, the Engine's seam), `toolkit_command.rb` (`/toolkit`), `tools/registry.rb` (`Registry#toolkit`, `#left_out_names`), `web/public/toolkit_chip.js` | Which registered tools a session's prompts declare: saved in the session, resolved at a turn's start (`Engine#apply_toolkit!`), named in the prompt's not-loaded line, a kit's prompt text after the appended files; `chi.toolkit` and `ctx.use_toolkit` for plugins ([configuration.md](configuration.md#toolkits)). |
data/docs/cli.md CHANGED
@@ -18,12 +18,14 @@
18
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
+ - `chi broadcast [-m TEXT] [--all] [--dry-run]`, `chi broadcast log [--last N] [--format json]`, `chi broadcast deliver BROADCAST_ID (ID|PREFIX)...` — share one piece of information (TEXT or stdin) with every session it may concern, as a context note from `broadcast` (see [Broadcast](broadcast.md))
21
22
  - `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
23
  - `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
24
  - `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
25
  - `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))
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))
26
+ - `chi review FILE… [--dir DIR] [--model M]`, `chi review --from ID [FILE…]` — start a review session to invite participants to: for files of a project, or as a review of a working session that starts from its recap, not its transcript (see [Review sessions](#review-sessions))
27
+ - `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))
28
+ - `chi --remote NAME send|answer|note|context|sessions|share|review …` (or `CHI_REMOTE=NAME`) — those commands on that machine's sessions, through its chi web (see [Remote sessions](#remote-sessions))
27
29
  - `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
30
  - `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))
29
31
  - `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text or images to a session (live, stopped or new) as a message (⏎, a turn runs) or a context note (⌘⏎) (see [Desktop helper](desktop.md))
@@ -273,6 +275,8 @@ bundles or the helper can be updated.
273
275
 
274
276
  Samagotchi exposes one flag that feeds a prompt (`-p`, `--prompt`) and one that
275
277
  controls exit behavior (`--non-interactive`); `--resume` composes with both.
278
+ Every flag takes its whole name, as the subcommands' flags do: an abbreviation (`--verb`, `--mem x`) is an
279
+ invalid option (exit 1).
276
280
 
277
281
  | Flag | Purpose |
278
282
  |------|---------|
@@ -1274,10 +1278,11 @@ A session's owner can invite **participants**, people or agents, to review it th
1274
1278
 
1275
1279
  ```sh
1276
1280
  chi share 3f9a1c2e --name Alex
1277
- # p_7c01d2aa Alex (human, reviewer) · session 3f9a1c2e · expires 2026-10-30 14:02
1281
+ # p_7c01d2aa Alex (human, reviewer) · session 3f9a1c2e · expires 2026-10-30 14:02 · cap $2.00
1278
1282
  # http://192.168.1.29:4567/?token=chp_…#s=3f9a1c2e-…
1279
1283
  # (shown once: chi keeps only its digest; chi share revoke 3f9a1c2e p_7c01d2aa ends it)
1280
1284
  chi share ls # every session's participants (--format json; never a token)
1285
+ chi share cap 3f9a1c2e p_7c01d2aa 5 # Alex's turns may cost you $5, from their next one
1281
1286
  chi share revoke 3f9a1c2e # every link to that session; add a participant id for one
1282
1287
  chi share revoke --all # every link to every session
1283
1288
  ```
@@ -1285,13 +1290,88 @@ chi share revoke --all # every link to every session
1285
1290
  - The link is printed once (with a QR code at a terminal): chi keeps only a digest of its token. Send it to the
1286
1291
  participant; opening it sets their browser's cookie, as the owner's LAN link does. `--agent` marks an agent
1287
1292
  participant, which uses the token as `Authorization: Bearer` (e.g. `chi --remote` with that token).
1288
- - It works for 7 days (`--expires Nh` or `Nd`, 1 hour to 30 days), until it is revoked, or until the session is
1289
- archived or deleted. A revoked or expired link shows "This review link has expired or was revoked."
1290
- - For now a participant sees a page with the session's title and status; comments and the review itself come in
1291
- a later chi. They can't reach anything else: every other route answers them 403, another session 404.
1293
+ - It works for 7 days (`--expires Nh` or `Nd`, 1 hour to 30 days; `web.participants.expires`), until it is revoked,
1294
+ or until the session is archived or deleted. A revoked link shows "This review link was revoked by the session's
1295
+ owner.", an expired one "This review link expired: ask the session's owner for a new one."
1296
+ - **Review turns (experimental).** A participant can ask questions and comment through chi web's `POST …/turn`: each
1297
+ message is a turn of its own, queued behind the running one, that the model answers **read-only**. It may read the
1298
+ project (never its secret files or chi's own), and proposes changes as text or a diff for you to apply; it can't
1299
+ write, run, fetch, send notes or set reminders, and any approval or question is yours. A line starting `!` or `/`
1300
+ and images are refused. One running and 3 waiting turns per participant (`web.participants.queue`). You pay: each
1301
+ participant has a spending cap (`--cap DOLLARS`, default $2, `web.participants.cap`), counted from their turns'
1302
+ cost and the side requests they cause; at the cap their next comment is refused and you get a notice, and `chi
1303
+ share cap` raises it. `/stats` shows each participant's spend. Their turns go only to a worker with the review
1304
+ boundary: restart an older one. While your turn waits at the step limit (Continue or Stop), their turns wait too
1305
+ (still queued, still counted): they run, in order, once you continue, stop or send a turn of your own.
1306
+ - **The participant page.** Their link opens a page of its own (never yours): the session's title and status, who
1307
+ else takes part, and the conversation as a participant sees it: every prompt and reply with who wrote it, each
1308
+ step's tools as one line (`read docs/plan.md`: the tool and a project file's path, never arguments or output), and
1309
+ the reply streaming in while a turn runs. A "Comment or ask" box sends their comment; their queued comments show
1310
+ with **Take back**; "waiting for the owner" shows while you answer a question or an approval of the turn; a refused
1311
+ comment (a `!` or `/` line, too many waiting, their cap reached) says why. On a phone it is the thread first with
1312
+ the box pinned, tool lines folded. They never see tool output or arguments, thinking, approvals or questions,
1313
+ memories, context notes, plugin and delegate input, cards, command output, the working set, cost, timing or host
1314
+ paths; secret-shaped text (API keys, tokens, private keys) is redacted too. An agent participant can wait on its
1315
+ turns' replies through the API, or with chi: its link's token in a remote, `chi --remote NAME send --wait -m "…" ID`,
1316
+ or `send -m`, which prints the turn id `--wait --turn T` takes; that wait exits 3 with `waiting_for_owner` while
1317
+ their turn waits on you (a question, an approval, your step-limit Continue) and 1 with `refused` (`cancel_reason`
1318
+ `revoked` or `cap_reached`) when their queued turn is refused before it runs. They can't reach anything else: every other route answers them 403, another
1319
+ session 404.
1320
+ - **Apply.** Under each of a participant's review turns that has a reply, your page shows **Apply** (full width on a
1321
+ phone): it puts an Apply turn in your composer, not sent, `Apply this proposal from Alex's review turn 3f2a1c0d, as
1322
+ far as it fits the plan. It's quoted: …` and the reply quoted line by line. Read it, edit it, send it: the
1323
+ participant's text reaches the model as a quote under your line, never as your instruction. Don't send "do what
1324
+ Alex said" instead: that hands their words your tools.
1325
+ - **Your side of it.** In the session bar, the participants chip (`invite` while nobody has a link, then `2
1326
+ participants`, with a green dot while one is on their page) opens the panel: each participant's state, last visit,
1327
+ spend against their cap, turns and expiry, with **Cap** and **Revoke** (revoking ends their page and stream and
1328
+ drops their queued comments). **Invite** takes a name, person or agent, the expiry and a cap, and shows the link
1329
+ once, with Copy, Share (a phone's share sheet) and its QR code. **Participant view ↗** opens their page for this
1330
+ session in a new tab, read-only, through the same server filter: exactly what they see. A participant's prompt
1331
+ shows their name in their colour, live and after a reload.
1292
1332
  - `chi --remote studio share …` invites on studio: the link names studio's LAN address. Locally, the link names
1293
- this machine's chi web; without one on the LAN, chi says the link opens on this machine only.
1333
+ this machine's running chi web, on the port it listens on (it records it in `$XDG_STATE_HOME/samagotchi/web.json`;
1334
+ `web.port` when none answers there); without one on the LAN, chi says the link opens on this machine only (as
1335
+ the participant there too: a participant's link never opens as the owner).
1294
1336
  - The owner's own token and the `chi web --new-token` rotation are separate: rotating it ends no participant's link.
1337
+ - **In the attached terminal.** A participant's message reads `Alex ▸ review> is step 3 right?`, the name in that
1338
+ participant's own colour (queued, running, and the last exchange when you join); yours stay as they were. A line
1339
+ says when someone is invited, revoked or their link expires (`participants> Alex joined (p_7c01d2aa)`,
1340
+ `participants> Alex left: their link was revoked`), within a few seconds and between turns, and the status row
1341
+ counts who is in the review (`2 participants`). The same commands work from there, on the attached session:
1342
+ `/share NAME [--agent] [--expires 3d] [--cap 2]` (the link and its QR code, once), `/participants` (the list),
1343
+ `/participants revoke [P]` (one participant, or everyone) and `/participants cap P DOLLARS`. They are `chi share`'s
1344
+ own, so under `chi --remote NAME` they reach that machine's chi web, as `chi --remote NAME share` does; an invite or
1345
+ a revoke made with them isn't announced again by the `participants>` line. The REPL
1346
+ (`--no-shared`) answers them with a note: a participant's turns run on the session's worker.
1347
+
1348
+ #### Review sessions
1349
+
1350
+ Share a **review session**, not a working one: a working session's history holds its tool outputs, and a review
1351
+ turn's model may quote them. `chi review` starts one, idle:
1352
+
1353
+ ```sh
1354
+ chi review docs/plan.md docs/spec.md # a fresh session in this project for those files
1355
+ # 4b7e9a01-… review · docs/plan.md, docs/spec.md · idle
1356
+ # open it: chi --attach 4b7e9a01 or the web
1357
+ # invite someone: chi share 4b7e9a01 --name NAME
1358
+ chi review --from 3f9a1c2e docs/plan.md # a review of a working session: its recap, not its transcript
1359
+ ```
1360
+
1361
+ - Each FILE is a **subject**: a file in the session's project (the git work tree of its folder, `--dir`, else
1362
+ here), relative to that folder or absolute. A missing file, a folder, a file outside the project, a secret
1363
+ (`.env*`, keys and certificates, credentials files, a git config) or chi's own refuses the review with one line:
1364
+ `chi review: .env can't be a review subject: …`. At most 20.
1365
+ - `--from ID` (an id, a prefix, `last:ID`) starts a **review fork**: a new session in ID's folder, on its model
1366
+ (`--model` to change it) and setup, seeded with a fresh recap of ID (its live worker is asked first, as a continue
1367
+ asks), never its transcript. ID keeps running, unarchived, its delegates its own. Its FILEs are relative to ID's
1368
+ folder.
1369
+ - `chi --remote NAME review … --dir /path/there` starts it on that machine; its chi web checks the files there.
1370
+ - Sharing a working session (one `chi review` didn't start) still works, with a warning on stderr that says how many
1371
+ tool outputs its history holds: `chi share: warning: 3f9a1c2e is a working session, not a review session: a
1372
+ participant's review turn can quote what its history holds (42 tool outputs so far). … (chi review --from 3f9a1c2e
1373
+ starts one)`. On the web the participants panel shows the same words before its Invite, with **Start a review of
1374
+ it**, and again above the link an invite gives.
1295
1375
 
1296
1376
  How it works inside (grants, the gate, the routes, the audit trail):
1297
1377
  [internals/participants.md](internals/participants.md).
@@ -1417,7 +1497,7 @@ changes:
1417
1497
 
1418
1498
  ```
1419
1499
  status> Coordinator | model=Qwen3.6-35B | ↳ 3f2a1c9e | ctx=12.3% (under20) | mem: notes, cli_usage | muted: gh-helper
1420
- status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | mem: skill_coordinator
1500
+ status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | 2 participants | ctx=8.1% (under20) | mem: skill_coordinator
1421
1501
  ```
1422
1502
 
1423
1503
  - The session's title first, when it has one (`/title`; not the first message a session without one is named by),
@@ -1434,6 +1514,7 @@ status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | me
1434
1514
  ends and when a delegate's report merges into a turn, the REPL whenever it draws the row. A
1435
1515
  child that starts waiting on an approval (which doesn't wake the parent) shows at the next of
1436
1516
  those.
1517
+ - `N participants`: who is in the session's review now ([Participants](#participants)), attached only.
1437
1518
  - `ctx=`: the kernel's context estimate and its bucket, updated during a turn (on hosts that
1438
1519
  report none, `api: openai`, at the turn's end).
1439
1520
  - `mem:`: the memories the session read, with its `--memory` list; `muted:` its `--mute` list.
@@ -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
- review: { base: lean, add: [web_fetch, delegate], remove: [memory_write] }
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 (`review: {base: terse, add: [web_fetch]}` stays concise); `prompt: ""` clears
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
@@ -1719,6 +1720,9 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
1719
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,52 @@ 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
+ right after the load-failure check (rules that don't load deny every call) and before the hooks' asks, the
308
+ protected paths and the rules (`GuardrailWiring#checks`; a deny is sticky, so no later vote turns it into a
309
+ question), and it
310
+ **denies, never asks**: nobody on the participant's side may approve, and the
311
+ owner's approval would give the participant's text the owner's tools. A
312
+ participant's turn may call only the `review` kit's tools:
313
+
314
+ - `read`, under the session's project root (the work tree top of its folder)
315
+ and never a secret: chi's state and config dirs (sessions, the grants, the
316
+ web token, memories and their `.bundles`, `config.yml`, hooks), `~/.ssh`,
317
+ `~/.gnupg`, `~/.aws`, and the project's `.env*`, `.netrc`, `*.pem`, `*.key`,
318
+ `*.p12`, anything named `credentials` (`.git-credentials` too), `.npmrc`,
319
+ `.pypirc`, ssh keys (`id_rsa`, `id_ed25519`, …), `*.tfstate`,
320
+ `secrets.yml`/`.json`, `.docker/config.json`, `.kube/config` and any config
321
+ under `.git` (a submodule's too). The path is checked as given and as it
322
+ resolves, so a symlink in the project to `~/.ssh` is `~/.ssh`. A session
323
+ whose folder is the home folder or above it reads nothing.
324
+ - `memory_read` with `scope: project` (a read without a scope would fall back
325
+ to the system memories). The prompt's preloaded memories and the memory
326
+ index are in the system prompt anyway: a review session should start
327
+ without personal ones.
328
+
329
+ Everything else is denied with "a participant's review turn may only read …",
330
+ and the model is told to propose the change as text or a diff for the owner
331
+ to apply: `write`, `edit`, `memory_write`, `execute`, the task tools,
332
+ `delegate`, `send_note`, `list_sessions`, `register_reminder` (it would fire
333
+ an owner turn later), `web_fetch`, `context_read`, `ask_user_question`,
334
+ `forget_outputs` (it would replace the owner's earlier tool outputs with the
335
+ participant's note, which later owner turns would read as their own), and
336
+ every plugin or MCP tool, including through `call_tool` (a built-in reached
337
+ through `call_tool` is checked as that built-in). The session's declared tools
338
+ stay its own (no prompt rebuild per author), so the model may still try a
339
+ write; the turn's header ("This turn may only read") keeps that rare.
340
+
341
+ The kit is `Toolkit::REVIEW`, a frozen built-in: config's `toolkit.kits.review`
342
+ is refused with one message ([configuration](configuration.md#toolkits)), so
343
+ config can't widen the boundary. Hooks still run on a participant's turn, with
344
+ the owner's power ([hooks](hooks.md#participants-turns)); a hook that rewrites a
345
+ call is checked on the call it leaves.
346
+
301
347
  ## Rules in config.yml
302
348
 
303
349
  ```yaml
@@ -702,7 +748,23 @@ keep every guardrail. But an owner turn that says "do what Alex said" hands
702
748
  Alex's text the owner's tools: the model will act on it as the owner asked.
703
749
  That is the residual risk, documented rather than engineered away: on a
704
750
  session with sensitive tools, quote the proposal you want applied in your own
705
- turn instead.
751
+ turn instead: the web's **Apply** under a review turn does that (it puts your
752
+ line and the reply quoted line by line in your composer, for you to read and
753
+ send; [internals/participants.md](internals/participants.md#apply)). A quote
754
+ lowers the risk, it doesn't remove it: your line still asks the model to act
755
+ on what it quotes, with your tools, so read the quote before you send it.
756
+
757
+ Within its own turn a participant's text can't act: the review boundary
758
+ leaves it reads of the project only, and an injection that convinces the
759
+ model still meets the boundary (containment over detection). What it can do
760
+ is make the model *say* what the history holds: a review turn's model sees the
761
+ whole conversation, earlier tool outputs included, so start a review in a
762
+ fresh session (or a continue), not on a working session full of outputs. A
763
+ line of a participant's text that looks like one of chi's own headers goes
764
+ out quoted (`(quoted) [Steer from the user, …]`), under the real one, in any
765
+ case and after any spaces (chi's context-note and task frames too). The owner's
766
+ due reminders wait for the owner's next turn: they never go into a
767
+ participant's.
706
768
 
707
769
  ## Limits
708
770
 
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