samagotchi 0.121.1 → 0.123.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +19 -7
- data/docs/cli.md +32 -1
- data/docs/configuration.md +55 -5
- data/docs/guardrails.md +119 -7
- data/docs/internals/engine.md +11 -2
- data/docs/internals/participants.md +76 -20
- data/docs/internals/prompt-caching.md +19 -7
- data/docs/internals/web-push.md +7 -3
- data/docs/memory.md +33 -0
- data/docs/plugins.md +3 -2
- data/docs/sessions.md +14 -4
- data/lib/samagotchi/bridge/turn_accumulator.rb +3 -1
- data/lib/samagotchi/bridge.rb +8 -4
- data/lib/samagotchi/bridge_client/event_stream.rb +5 -1
- data/lib/samagotchi/bridge_client/stream_open.rb +8 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +13 -0
- data/lib/samagotchi/config.rb +11 -0
- data/lib/samagotchi/delivery.rb +7 -0
- data/lib/samagotchi/engine.rb +289 -12
- data/lib/samagotchi/event_fanout.rb +0 -1
- data/lib/samagotchi/gem_update.rb +41 -4
- data/lib/samagotchi/guardrail_wiring.rb +46 -12
- data/lib/samagotchi/guardrails/approval.rb +42 -4
- data/lib/samagotchi/guardrails/gate.rb +1 -1
- data/lib/samagotchi/guardrails/pair_boundary.rb +172 -0
- data/lib/samagotchi/guardrails/pair_command.rb +365 -0
- data/lib/samagotchi/guardrails/review_paths.rb +41 -7
- data/lib/samagotchi/guardrails/shell_lex.rb +7 -5
- data/lib/samagotchi/guardrails/shell_paths.rb +10 -1
- data/lib/samagotchi/guardrails/verdict.rb +7 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/guardrails_command.rb +6 -1
- data/lib/samagotchi/kernel_loop.rb +20 -0
- data/lib/samagotchi/llm/chat_loop.rb +4 -0
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/memory_orphans.rb +11 -0
- data/lib/samagotchi/participant_mode.rb +85 -0
- data/lib/samagotchi/participants.rb +7 -5
- data/lib/samagotchi/plugin/api.rb +7 -4
- data/lib/samagotchi/process_group.rb +13 -0
- data/lib/samagotchi/review_apply.rb +10 -2
- data/lib/samagotchi/review_start.rb +42 -10
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/session.rb +14 -1
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -2
- data/lib/samagotchi/session_access/remote.rb +32 -12
- data/lib/samagotchi/session_access/web_bridge_client.rb +9 -2
- data/lib/samagotchi/session_manager.rb +21 -7
- data/lib/samagotchi/session_participants.rb +18 -3
- data/lib/samagotchi/session_setup.rb +8 -4
- data/lib/samagotchi/sessions_command.rb +1 -1
- data/lib/samagotchi/share_command.rb +2 -2
- data/lib/samagotchi/steer.rb +13 -6
- data/lib/samagotchi/system_one/client.rb +16 -7
- data/lib/samagotchi/system_one/log.rb +25 -5
- data/lib/samagotchi/system_prompt.rb +20 -4
- data/lib/samagotchi/terminal_ui/question_prompt.rb +17 -1
- data/lib/samagotchi/terminal_ui.rb +2 -1
- data/lib/samagotchi/tool_runner.rb +11 -1
- data/lib/samagotchi/toolkit.rb +27 -8
- data/lib/samagotchi/toolkit_binding.rb +149 -7
- data/lib/samagotchi/toolkit_command.rb +15 -3
- data/lib/samagotchi/toolkit_report_command.rb +320 -0
- data/lib/samagotchi/toolkit_suggest.rb +377 -0
- data/lib/samagotchi/tools/builtins.rb +14 -1
- data/lib/samagotchi/tools/memory.rb +1 -1
- data/lib/samagotchi/update_command.rb +7 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +12 -13
- data/lib/samagotchi/web/grants.rb +17 -5
- data/lib/samagotchi/web/message_display.rb +6 -1
- data/lib/samagotchi/web/participant_access.rb +4 -1
- data/lib/samagotchi/web/participant_review.rb +8 -4
- data/lib/samagotchi/web/participant_view.rb +12 -4
- data/lib/samagotchi/web/participants_admin.rb +10 -6
- data/lib/samagotchi/web/principal.rb +3 -4
- data/lib/samagotchi/web/public/app.js +34 -10
- data/lib/samagotchi/web/public/bubbles.js +3 -2
- data/lib/samagotchi/web/public/delivery_line.js +8 -6
- data/lib/samagotchi/web/public/participant.html +1 -1
- data/lib/samagotchi/web/public/participant.js +12 -13
- data/lib/samagotchi/web/public/participant_model.js +20 -8
- data/lib/samagotchi/web/public/participant_names.js +29 -8
- data/lib/samagotchi/web/public/participants_panel.js +17 -3
- data/lib/samagotchi/web/public/question_card.js +12 -2
- data/lib/samagotchi/web/public/question_cards.js +7 -0
- data/lib/samagotchi/web/public/stream_handlers.js +6 -2
- data/lib/samagotchi/web/public/tools_overview.js +42 -0
- data/lib/samagotchi/web/public/turn_events.js +7 -5
- data/lib/samagotchi/web/public/turn_html.js +2 -2
- data/lib/samagotchi/web/public/turn_view.js +6 -4
- data/lib/samagotchi/web/public/update_notice.js +31 -2
- data/lib/samagotchi/web/push/notifier.rb +28 -1
- data/lib/samagotchi/web/server.rb +7 -3
- data/lib/samagotchi/web/session_access_routes.rb +5 -4
- data/lib/samagotchi/web/session_control.rb +4 -2
- data/lib/samagotchi/web/session_hub.rb +24 -3
- data/lib/samagotchi/web/start_dir.rb +10 -5
- data/lib/samagotchi/web/update_check.rb +179 -0
- data/lib/samagotchi/worker.rb +60 -8
- data/lib/samagotchi/worker_link.rb +1 -0
- metadata +8 -1
|
@@ -157,7 +157,7 @@ row answers another session's id 404, so a new route is covered without a new ex
|
|
|
157
157
|
| `POST /api/sessions/:id/turn` | participant | a queued review turn of their own ([Turns](#turns)) |
|
|
158
158
|
| `POST /api/sessions/:id/turn/:eid/dequeue` | participant | their own queued turn back; another's (the owner's included) is 403 `not_permitted` |
|
|
159
159
|
| `GET /api/sessions/resolve?id=` | participant | their session for its id or a prefix of it; anything else 404 as no session (never an ambiguity listing others) |
|
|
160
|
-
| `POST /api/sessions/:id/wait/prepare`, `…/wait` | participant | only for a turn of theirs (`turn`: the enqueued id `POST …/turn` answered; else 403); `wait/prepare` answers only what that wait needs, `{session_id, baseline: {last_turn}, known_turn, live, last_turn_id}` (`last_turn_id` only when it is their turn), never the owner's cursor, message count, question or running turn; the answer trimmed to `{status, text, turn_id, cancel_reason}`, the reply's `text` through `ParticipantView.reply` (no thinking or tool call, redacted) and any other text (an error's exception, a failed turn's summary) `ParticipantMessages::TURN_FAILED`; a question or approval their turn raised, or their turn held by the owner's step-limit Continue offer ([Turns](#turns)), is `{status: "waiting_for_owner", detail: "waiting for the session's owner"}` with nothing of it; a revoke or the expiry ends a `wait` already running (401 `token_revoked` / `token_expired`), as it ends their stream |
|
|
160
|
+
| `POST /api/sessions/:id/wait/prepare`, `…/wait` | participant | only for a turn of theirs (`turn`: the enqueued id `POST …/turn` answered; else 403); `wait/prepare` answers only what that wait needs, `{session_id, baseline: {last_turn}, known_turn, live, last_turn_id}` (`last_turn_id` only when it is their turn), never the owner's cursor, message count, question or running turn; the answer trimmed to `{status, text, turn_id, outcome, cancel_reason}`, the reply's `text` through `ParticipantView.reply` (no thinking or tool call, redacted) and any other text (an error's exception, a failed turn's summary) `ParticipantMessages::TURN_FAILED`; a question or approval their turn raised, or their turn held by the owner's step-limit Continue offer ([Turns](#turns)), is `{status: "waiting_for_owner", detail: "waiting for the session's owner"}` with nothing of it; a revoke or the expiry ends a `wait` already running (401 `token_revoked` / `token_expired`), as it ends their stream |
|
|
161
161
|
| `GET /` | participant | the participant page's shell (`participant.html`, its body's `data-session` and `data-name` the grant's; `participant.js` reads the rest through the two routes below). Never the owner's page, where every route would refuse them. |
|
|
162
162
|
| `GET /api/sessions/:id/review` | participant | the page's data, filtered ([What a participant sees](#what-a-participant-sees)): `{viewer, session: {id, title, status}, subjects, participants, messages, current_turn, waiting_for_owner, queued: {mine, others}, last_event_id}`; a participant's `viewer` has their `usage` |
|
|
163
163
|
| `GET /api/sessions/:id/stream` | participant | the session's stream through `ParticipantStreamFilter` (only `from_seq` and `snapshot` reach the Bridge); it ends within a poll of a revoke or the expiry |
|
|
@@ -169,8 +169,36 @@ Everything else is `:owner`: `/answer`, `/command`, `/images` (an image a prompt
|
|
|
169
169
|
|
|
170
170
|
## Turns
|
|
171
171
|
|
|
172
|
+
`ParticipantMode` is the single resolver for a participant turn's rules:
|
|
173
|
+
|
|
174
|
+
- **`:review`**, a review session (its `review:` key; `chi review`), and any participant whose grant wasn't issued
|
|
175
|
+
to act: the turn only reads (`Guardrails::ReviewBoundary`), and proposes what the owner applies ([Apply](#apply)).
|
|
176
|
+
- **`:pair`**, a working session's participant invited to act: the turn acts with the session's tools under the
|
|
177
|
+
owner's guardrails, its asks go to the owner and offer once only, and `Guardrails::PairBoundary` denies secrets,
|
|
178
|
+
chi itself, background jobs, the environment, owner-voiced tools and the model's own question, and asks before a
|
|
179
|
+
write outside the project ([guardrails](../guardrails.md#a-participants-pair-boundary)). No Apply: it acted itself.
|
|
180
|
+
|
|
181
|
+
`ParticipantMode.of(session, features:)` is the session's: what a participant invited now gets. No session is
|
|
182
|
+
`:review`. A worker names `pair_turns` (`Delivery::PAIR_FEATURE`) when it runs pair turns; one without it runs
|
|
183
|
+
every participant turn as a review turn, so chi web and chi share ask `ParticipantMode.live`, which reads the
|
|
184
|
+
session's live worker's features (no live worker: this chi's, the one a delivery starts). `ReviewStart.share_terms`
|
|
185
|
+
reads it once for an invite: the warning (a working session's says their turns act, writes included, asks to you;
|
|
186
|
+
while an older worker holds it, that a participant invited now only reads, and to invite again after a restart),
|
|
187
|
+
the list's `mode` (the panel's words) and the grant's stamp: `Grant#mode` / the `participants.json` entry's
|
|
188
|
+
`mode` is `"pair"` only when the warning said their turns act. A grant without it (every grant from before pair
|
|
189
|
+
turns) stays review until the owner invites them again.
|
|
190
|
+
|
|
191
|
+
A turn's mode is `ParticipantMode.for_turn(session, entry)`: `:pair` only for a stamped entry in a pair session.
|
|
192
|
+
The worker decides it once at the turn's start (`GuardrailWiring#begin_turn` → `#turn_mode`, from the entry
|
|
193
|
+
`SessionParticipants#entry_for` reads), and everything in the turn reads that: the stamp on the opening message
|
|
194
|
+
(`participant_mode`), the boundary in `GuardrailWiring#checks`, `ToolRunner#unreached_group_refusal` (through
|
|
195
|
+
`KernelLoop#participant_mode`: a review turn's boundary words the refusal; a pair turn gets the owner's), the
|
|
196
|
+
mid-turn stop and the cap notice's words. The system prompt's line is the pair one once an active participant is
|
|
197
|
+
stamped pair (`SessionParticipants#prompt_mode`); the participant page's `viewer.mode` is their own grant's
|
|
198
|
+
(`ParticipantMode.live_for`).
|
|
199
|
+
|
|
172
200
|
A participant's `POST /api/sessions/:id/turn` (`ParticipantAccess#handle_participant_turn`) takes only the body's
|
|
173
|
-
`prompt`. Three layers keep it a
|
|
201
|
+
`prompt`. Three layers keep it a participant's own turn, inside its mode's boundary.
|
|
174
202
|
|
|
175
203
|
**chi web.** The author is the grant's (`p:<id>`) and the client id `review:<id>` (`ClientId.review`: a human's
|
|
176
204
|
input, origin `review`, source `participant`, labelled `review`); the body's `client_id`, `author`, `delivery`,
|
|
@@ -232,9 +260,17 @@ command that waits for it, and `send --wait -m` skips the owner's busy check (it
|
|
|
232
260
|
doesn't refuse it; that check's `wait/prepare` without a turn would be 403 `not_permitted`) and waits on the turn it
|
|
233
261
|
queued, as `--turn T` does.
|
|
234
262
|
|
|
235
|
-
**The tool calls.** `Guardrails::ReviewBoundary` denies a participant's turn everything but
|
|
236
|
-
([guardrails](../guardrails.md#a-participants-review-boundary)).
|
|
237
|
-
|
|
263
|
+
**The tool calls.** In a review session `Guardrails::ReviewBoundary` denies a participant's turn everything but
|
|
264
|
+
reads of the project ([guardrails](../guardrails.md#a-participants-review-boundary)). In a working session
|
|
265
|
+
`Guardrails::PairBoundary` takes its seat: the owner's checks and rules decide the rest, as for the owner's turns
|
|
266
|
+
([guardrails](../guardrails.md#a-participants-pair-boundary)). Approvals and questions are owner-only: either
|
|
267
|
+
boundary denies the model's own question, and `/answer` is an owner route. A pair turn's ask reaches the owner's card
|
|
268
|
+
naming who asks (`approval.author`, `author_name`; `Guardrails::Approval::PARTICIPANT_HEADER`) with Allow once as its
|
|
269
|
+
only scope, and System 1 never judges it (`GuardrailWiring#system_one_judge`). A grant without `--identity` (a LAN
|
|
270
|
+
token link) gets the same access: a per-grant trust level, if one is ever needed, would go on the grant and narrow
|
|
271
|
+
`ParticipantMode` there. Toolkit suggestions (audit and on mode) skip review
|
|
272
|
+
sessions and participant-authored turns; their input is never sent to the separate System 1 classifier, and a
|
|
273
|
+
participant's turn never switches the session's toolkit.
|
|
238
274
|
|
|
239
275
|
**Hooks and plugins** run on a participant's turn with the owner's power; every event of the turn carries `author:`
|
|
240
276
|
and `ctx.turn_author` names it ([hooks](../hooks.md#participants-turns)).
|
|
@@ -248,9 +284,17 @@ and `ctx.turn_author` names it ([hooks](../hooks.md#participants-turns)).
|
|
|
248
284
|
participant caused (their turn's `ctx.ask_model`, the idle recap after their turn; `SideTotals#by_author`). An
|
|
249
285
|
owner command's side request is the owner's. `/stats` shows each participant's spend by name. At the cap the
|
|
250
286
|
participant's next turn is 429 `cap_reached`, and the owner gets one notice per participant and cap: "Alex reached
|
|
251
|
-
their $2.00 review cap: their comments are refused until you raise it (chi share cap ID p_… AMOUNT)."
|
|
252
|
-
|
|
253
|
-
|
|
287
|
+
their $2.00 review cap: their comments are refused until you raise it (chi share cap ID p_… AMOUNT)." (a working
|
|
288
|
+
session's: "Alex reached their $2.00 cap: their turns stop and are refused until you raise it (…)"). A review
|
|
289
|
+
turn's spend counts from its end, so a review turn can finish past the cap. A pair turn stops mid-turn: before each
|
|
290
|
+
generation (`KernelLoop#generation_guard`, both loops) `Engine#guard_participant_spend` asks the worker's check
|
|
291
|
+
(`Worker#participant_turn_stop`: `metrics.spend(author)` counts the running turn's finished generations) and, past
|
|
292
|
+
the cap, stops the turn on the plugins' stop path (`HookRoutes#stop_turn`: "stopped the turn: Alex reached their
|
|
293
|
+
$2.00 cap"), with the owner's notice once per cap. A generation's cost is known only when it ends, so a pair turn
|
|
294
|
+
can overshoot the cap by its last generation (and the side requests it caused); there is no projection of the
|
|
295
|
+
next one. The same check stops a pair turn whose link ended mid-turn (revoked, expired, the session archived):
|
|
296
|
+
"stopped the turn: Alex's link was revoked or expired" (`Worker::REVOKED_STOP`). The session-wide `cost-cap` bundle still applies, and its
|
|
297
|
+
question in a participant's turn goes to the owner.
|
|
254
298
|
|
|
255
299
|
**Streams.** A participant's stream is filtered ([below](#what-a-participant-sees)), and it ends with their link:
|
|
256
300
|
`App#stream_running(principal)` is the proxy's running check (asked every `BridgeClient::STREAM_POLL`), false for a
|
|
@@ -385,7 +429,9 @@ message's `author_name`, `turn_started`'s, or the panel's list (`participant_nam
|
|
|
385
429
|
|
|
386
430
|
### Apply
|
|
387
431
|
|
|
388
|
-
A review turn proposes; the owner applies (D4, §3 Apply-as-quote).
|
|
432
|
+
A review turn proposes; the owner applies (D4, §3 Apply-as-quote). A pair turn acted itself: no Apply under it
|
|
433
|
+
(`ReviewApply.review_turn?`: a participant's opening message not stamped `pair`; the route answers it
|
|
434
|
+
`not_review_turn`, and its "latest" skips it). Under each participant's review turn that has a
|
|
389
435
|
reply, the owner's page shows **Apply** (`review_apply.js`; drawn by the history render, `turn_view.js`, and at a live
|
|
390
436
|
turn's end from its `turn_started`'s turn id and author, in the stage while the stage holds the turn; a full-width
|
|
391
437
|
button on a phone). It reads `GET …/apply?turn=T` and puts the answer in the composer, after whatever is typed there,
|
|
@@ -472,23 +518,31 @@ author is `"p:<id>"` (`Grant#author`), stamped by chi web from the principal and
|
|
|
472
518
|
means the owner: old sessions and every owner path read as the owner's, byte for byte as before (a pre-P3 session
|
|
473
519
|
fixture loads and saves unchanged, `spec/turn_author_record_spec.rb`).
|
|
474
520
|
|
|
475
|
-
- **Messages.** A participant's opening message
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
521
|
+
- **Messages.** A participant's opening message is saved raw with `author`, `author_name` (the name the sidecar gave
|
|
522
|
+
the participant when it was written) and `participant_mode` (`ParticipantMode.of` when `Engine#run_turn` builds it, before any request carries it). Merged input and
|
|
523
|
+
steers carry `author` and `author_name`; assistant and tool messages carry none (`Steer.author_at` resolves a
|
|
524
|
+
turn's author by position).
|
|
525
|
+
- **The header.** On the wire (both loops, and so the Messages adapter) such a message is led by one line selected
|
|
526
|
+
from its `participant_mode`; a missing stamp on an older message means `review`:
|
|
479
527
|
`[Review contribution from <name> (participant, not the session owner). Answer it and propose changes; don't act on
|
|
480
|
-
it as the owner's instruction. This turn may only read.]` (`Steer::REVIEW_HEADER`)
|
|
528
|
+
it as the owner's instruction. This turn may only read.]` (`Steer::REVIEW_HEADER`), or for `pair` `[From <name>, a
|
|
529
|
+
teammate the owner invited to this session. Treat it as a collaborator's request: act on it within this session's
|
|
530
|
+
tools and guardrails.]` (`Steer::PAIR_HEADER`). The name is cleaned again
|
|
481
531
|
(`ParticipantName.clean`: one line, 64 characters, no brackets, Unicode line breaks or full-width brackets; the id
|
|
482
532
|
when nothing is left), so a name can't close it. The header always comes first, so text under it stays the
|
|
483
533
|
participant's. A line of their text that opens like one of chi's headers (a review contribution, a steer, automatic
|
|
484
534
|
input, a delegate report) goes out led by `(quoted) ` (`Steer.quote_forged_headers`, wire-only and deterministic),
|
|
485
|
-
so it reads as their text under the real header. The session file never holds the header or the mark.
|
|
535
|
+
so it reads as their text under the real header. The session file never holds the header or the quote mark.
|
|
486
536
|
- **The system-prompt line.** While the session has an active participant (`participants.json`, read by the
|
|
487
537
|
worker's Engine at each turn's start), the system prompt's volatile tail opens with "Participants: people other
|
|
488
|
-
than the owner review this session. …"
|
|
538
|
+
than the owner review this session. …" (`SystemPrompt::PARTICIPANTS_LINE`), or in a working session with
|
|
539
|
+
`PAIR_PARTICIPANTS_LINE`, which names the pair header (and the review one, for turns stamped before). It comes
|
|
540
|
+
when the first joins and goes when the last leaves or expires.
|
|
489
541
|
- **Records.** `last_turn.author`, the analytics turn record's `author`, `turn_enqueued`'s `author`, and the
|
|
490
|
-
Bridge's queued rows. `turn_started` carries `author` and `
|
|
491
|
-
|
|
542
|
+
Bridge's queued rows. `turn_started` carries `author`, `author_name` and `participant_mode` on a participant's turn
|
|
543
|
+
(the owner's carries none), and so does the Bridge's turn snapshot and its replay. The owner's history
|
|
544
|
+
(`MessageDisplay`) and the participant view pass a participant message's `participant_mode` on: the author badge
|
|
545
|
+
reads "Alex · review" or "Alex · pair" (a prompt drawn while queued takes its mode at `turn_started`).
|
|
492
546
|
- **Side models' transcripts.** The idle recap's (`IdleRecap::TranscriptFilter`) leads a participant's message with
|
|
493
547
|
"(a review contribution from Alex, a participant; not the user's words)", and `ctx.ask_model`'s names its paragraph
|
|
494
548
|
`Participant`. The UI's readers name them: `TurnAccumulator.messages_of` gives a participant's prompt and merged
|
|
@@ -557,8 +611,10 @@ participant TUI in v1.
|
|
|
557
611
|
- Everything a participant reads (their stream, the review, the show) is built by `ParticipantView` from named fields;
|
|
558
612
|
an event type it doesn't name is dropped. The owner's participant view reads through the same filter.
|
|
559
613
|
- A participant's turn is always a queued turn of its own (chi web, the Bridge, the inbox), never a command or a
|
|
560
|
-
shell line (chi web, the Bridge, the worker), and runs only on a worker with the review boundary
|
|
561
|
-
everything but reads of the project
|
|
614
|
+
shell line (chi web, the Bridge, the worker), and runs only on a worker with the review boundary: in a review
|
|
615
|
+
session it denies everything but reads of the project; in a working session (a worker with `pair_turns`) the pair
|
|
616
|
+
boundary denies secrets and owner-voiced tools, the owner's guardrails decide the rest, and every ask is the
|
|
617
|
+
owner's, once only.
|
|
562
618
|
- A participant's message is never sent without its review header, and never merged into the owner's message.
|
|
563
619
|
- The owner's input is never merged into a participant's running turn (the Bridge queues it, the drain takes none):
|
|
564
620
|
it runs as the owner's own turn after it.
|
|
@@ -73,6 +73,16 @@ right-hand column happens.
|
|
|
73
73
|
beside `apply_staged_tools!`, then `reset!`): the chat loop builds `tools:` per request, so a mid-turn change
|
|
74
74
|
would differ from the turn's prompt. Under `full` nothing is filtered and no line is added: the bytes are what
|
|
75
75
|
they were before toolkits.
|
|
76
|
+
- A toolkit suggestion (`toolkit.suggest.mode: on`) is set before `apply_toolkit!` of the turn it was asked on
|
|
77
|
+
(`Engine#settle_toolkit_suggestion` → `ToolkitBinding#suggest`), and no request goes out before that, so on a
|
|
78
|
+
session's first turn it costs nothing. Its **deferred switch** (a later prompt after `undecided`, within
|
|
79
|
+
`toolkit.suggest.defer_turns` / `defer_tokens`) is engine-side, so `/toolkit`'s CacheConfirm doesn't apply: it
|
|
80
|
+
changes the declared tools at the front of the request, and the server re-reads **system prompt + tools +
|
|
81
|
+
conversation** on that turn. The defer bound keeps that conversation small; its notice says "from this turn".
|
|
82
|
+
When System 1 runs on the model's own single-slot local server (`Engine#toolkit_judge_shares_local_host?`), any
|
|
83
|
+
ask takes the slot and evicts its cached prefix: a first-turn ask costs that cache even when nothing switches
|
|
84
|
+
(there is little cached on a session's first turn), so on mode asks only there, skips the deferred path, and
|
|
85
|
+
sends no worker warm-up.
|
|
76
86
|
- A kit's prompt text (`toolkit.kits.<name>.prompt:`, `chi.toolkit … prompt:`) sits after the appended files and
|
|
77
87
|
before the memory indexes (`SystemPrompt#kit_prompt_section`): one prefix per kit, never per-session text, so
|
|
78
88
|
sessions on the kit share it; a memory write (the indexes after it) doesn't move it. It is part of the
|
|
@@ -119,13 +129,15 @@ right-hand column happens.
|
|
|
119
129
|
dropped or brought back) is a tail system message after the history, one per turn with ops, escaped as context
|
|
120
130
|
notes are (`Prompt::ESCAPED_KINDS`). The turn-end warm-up never warms a drop sent after the turn: it misses once.
|
|
121
131
|
- **Participants** ([participants.md](participants.md#attribution)). A participant's message carries a wire-only
|
|
122
|
-
header line (`Steer.review_header`), a pure function of the saved message (its `author_name`
|
|
123
|
-
replay sends the same bytes; the owner's
|
|
124
|
-
and on a Messages host). The
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
header line (`Steer.review_header`), a pure function of the saved message (its `author_name` and its
|
|
133
|
+
`participant_mode` stamp included: a review or a pair header), so a replay sends the same bytes; the owner's
|
|
134
|
+
messages are sent as before (`spec/review_contribution_spec.rb`, per loop and on a Messages host). The
|
|
135
|
+
participants line opens the system prompt's volatile tail, after breakpoint #1: a new session's cache is
|
|
136
|
+
untouched. Its words follow the session's `ParticipantMode` (a review or a working session's line), which never
|
|
137
|
+
changes for a session. The Engine checks the sidecar at each turn's start and rebuilds (`reset!`) only when the
|
|
138
|
+
first participant joins and when the last leaves or expires: one history re-read each, then stable. Under D3(a)
|
|
139
|
+
the declared tools stay the session's, so an author switch costs nothing: the review and pair boundaries are
|
|
140
|
+
gates on the calls, not a toolkit switch. The forged-header mark (`(quoted) `) is a pure function of the text too.
|
|
129
141
|
- Side requests (idle recap, `/btw`) go through `IdleClient` with their own short system prompt (the recap's also
|
|
130
142
|
asks for its auto title line, `RecapPrompt::TITLE`: static text, still that one request). On a current
|
|
131
143
|
llama.cpp they don't evict the session (see the server settings below); chi never pins them to a slot.
|
data/docs/internals/web-push.md
CHANGED
|
@@ -101,10 +101,12 @@ The service worker is the only reader of the payload (JSON, at most `Notifier::O
|
|
|
101
101
|
|
|
102
102
|
- `{"v":1,"kind":"show","tag":KEY,"title":…,"body":…,"url":URL,"badge":N,"open":[KEY…]}`: a waiting
|
|
103
103
|
reason's (approval, question, continue, card) TTL 12 h (`SHOW_TTL`), `Urgency: high`; a failed or done turn's
|
|
104
|
-
TTL 10 min (`EVENT_TTL`), `Urgency: normal
|
|
104
|
+
TTL 10 min (`EVENT_TTL`), `Urgency: normal`; an available chi update uses a 24 h TTL (`UPDATE_TTL`) and
|
|
105
|
+
`Urgency: normal`. `open` only while the whole list fits the limit; the test push
|
|
105
106
|
(`chi:test`) has neither `open` nor `badge`. URL (`Notifier.session_url`) is the session in its own project's
|
|
106
107
|
view, `/?dir=<project root>#/s/<id>` (the folder URL-encoded, its slashes kept), or `/?scope=all#/s/<id>` for a
|
|
107
|
-
session in no repo: never the bare `/`, which is chi web's start project
|
|
108
|
+
session in no repo: never the bare `/`, which is chi web's start project (except the update show, which names no
|
|
109
|
+
session: `/`).
|
|
108
110
|
|
|
109
111
|
`badge` and `open` are the waiting keys still open in the Notifier's summaries (`Attention.open_keys`: a pending
|
|
110
112
|
question of any reason, a hook's and a continue's too, not relayed, and a card with actions; never a failed or done
|
|
@@ -160,7 +162,9 @@ the top and in `pending_question` / `pending_card`, strings in `last_turn`), `no
|
|
|
160
162
|
from chi web's request threads (`SessionHub#touch`, from the stop/archive/delete routes), so it only enqueues: a push
|
|
161
163
|
is a 0.5 s https call. `emit` already rescues a raising sink. The sink keeps the last summary per session and turns
|
|
162
164
|
each `session` / `session_gone` event into a `Show` (with its text) or a `Close` (it was answered anywhere) on a
|
|
163
|
-
Queue.
|
|
165
|
+
Queue. A `chi` event marked `update_notice` becomes a `chi:update:<version>` show for the bare page; the checker
|
|
166
|
+
sets that marker only for a newly selected version not already in its persistent `notified` list. It uses the same
|
|
167
|
+
Presence skip and hourly CAP as session shows, and the 24-hour TTL lets an offline phone receive it later.
|
|
164
168
|
|
|
165
169
|
**Seeding**: `Notifier#start(hub)` subscribes with `snapshot: true` and takes the summaries as the seed, so what is
|
|
166
170
|
open already never pushes. The hub's projection is empty before its first scan, so `Server.start` runs `hub.scan`
|
data/docs/memory.md
CHANGED
|
@@ -281,6 +281,39 @@ skills`, [docs/plugins.md](plugins.md#the-skills-bundle)) adds `/skill save`,
|
|
|
281
281
|
one-line diff after each update, and nudges a model that skips a failing
|
|
282
282
|
step instead of fixing the skill.
|
|
283
283
|
|
|
284
|
+
## Toolkit memories
|
|
285
|
+
|
|
286
|
+
A memory named `toolkit_<name>` defines a toolkit named `<name>`. Its body is
|
|
287
|
+
one fenced YAML block; `description` is required and is classification input
|
|
288
|
+
only, not prompt text:
|
|
289
|
+
|
|
290
|
+
```yaml
|
|
291
|
+
base: staging
|
|
292
|
+
add: [web_fetch]
|
|
293
|
+
remove: [memory_write]
|
|
294
|
+
groups: [browser]
|
|
295
|
+
description: Inspect browser behavior and verify web changes
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`base`, `add`, `remove` and `groups` are optional; without a base the kit uses
|
|
299
|
+
`full`. A kit can build on a config or bundle kit. `prompt:` is refused, and
|
|
300
|
+
the built-in names (`full`, `lean`, `child`, `review`) cannot be redefined.
|
|
301
|
+
Only registered tools can be declared.
|
|
302
|
+
|
|
303
|
+
Chi reads project-scope kits before system-scope kits; a project kit with a
|
|
304
|
+
name wins over a system kit. Config and bundle kits also win over memories.
|
|
305
|
+
`/toolkit` lists memory kits with their source (`memory:project` or
|
|
306
|
+
`memory:system`). `--mute toolkit_<name>` skips that memory kit for the
|
|
307
|
+
session. Kits are loaded once when a worker starts, so edits take effect on a
|
|
308
|
+
new worker.
|
|
309
|
+
|
|
310
|
+
Write one when the user asks for a kit, when they choose the same tools with
|
|
311
|
+
`/toolkit add` for the same kind of work more than once, or when
|
|
312
|
+
`chi toolkit report` shows a kit repeatedly short of a tool for one kind of
|
|
313
|
+
prompt. To adjust a config or bundle kit, create a memory kit on that base
|
|
314
|
+
(for example, `base: staging`); do not edit `config.yml` or the bundle. Tell
|
|
315
|
+
the user when you create or update a toolkit memory.
|
|
316
|
+
|
|
284
317
|
## What a bundle owns
|
|
285
318
|
|
|
286
319
|
A bundle owns the memory files it wrote, nothing else. `chi bundle install`
|
data/docs/plugins.md
CHANGED
|
@@ -579,7 +579,7 @@ end
|
|
|
579
579
|
- Each task runs once per session start. A `-p … --non-interactive` run
|
|
580
580
|
starts them with its turn and shows nothing but the answer.
|
|
581
581
|
|
|
582
|
-
### `chi.toolkit(name, base:, add: [], remove: [], prompt: nil, groups: nil)`
|
|
582
|
+
### `chi.toolkit(name, base:, add: [], remove: [], prompt: nil, groups: nil, description: nil)`
|
|
583
583
|
|
|
584
584
|
A toolkit sessions can be put on: `base`'s tools (`full`, `lean`, `child`, a
|
|
585
585
|
config or bundle toolkit) with `add` declared and `remove` left out, and
|
|
@@ -588,7 +588,8 @@ inherits the base's, `""` clears it; at most 2000 characters, and one that
|
|
|
588
588
|
isn't a String or is longer is a load error). `groups` selects the source
|
|
589
589
|
groups the toolkit reaches (names or globs, sorted and deduplicated; nil
|
|
590
590
|
inherits the base's, `[]` reaches none, and `:all` reaches every group; anything
|
|
591
|
-
else, a map say, is a load error).
|
|
591
|
+
else, a map say, is a load error). `description` is optional classifier text
|
|
592
|
+
(up to 300 characters); it is not included in the session prompt.
|
|
592
593
|
Groups match against the tool index's load-time names. Users pick
|
|
593
594
|
it like chi's own (`chi --toolkit NAME`, `toolkit.default`, `/toolkit NAME`),
|
|
594
595
|
and the plugin with `ctx.use_toolkit` (below). Names are `a-z`, `0-9`, `_` and
|
data/docs/sessions.md
CHANGED
|
@@ -264,7 +264,9 @@ A session can **continue** an earlier one: "the next run of the same routine" (a
|
|
|
264
264
|
## Review sessions
|
|
265
265
|
|
|
266
266
|
A **review session** is one made to be shared with participants (the shared-sessions plan's D2: a review is its own
|
|
267
|
-
session, so a participant's review turn can't quote a working session's tool outputs
|
|
267
|
+
session, so a participant's review turn can't quote a working session's tool outputs, and only reads). Sharing a
|
|
268
|
+
working session itself is pairing: a participant's turns there act with its tools under the owner's guardrails
|
|
269
|
+
([CLI: Participants](cli.md#participants), [guardrails](guardrails.md#a-participants-pair-boundary)). `chi review FILE…` starts one
|
|
268
270
|
in a project, `chi review --from ID` as a review fork of a working session (see [CLI: Review
|
|
269
271
|
sessions](cli.md#review-sessions)); both start idle and print the `chi share` line to invite someone.
|
|
270
272
|
|
|
@@ -292,7 +294,8 @@ sessions](cli.md#review-sessions)); both start idle and print the `chi share` li
|
|
|
292
294
|
space-separated, relative to the page's folder; a path with a space in double quotes, `"docs/my plan.md"`): its
|
|
293
295
|
chip then reads `review: plan.md +1`, and Start makes a review session (the message typed, if any, is its first
|
|
294
296
|
turn; with none it starts idle). A refused file shows the server's one message in the chip's form, opened again. On a session that isn't a review session the participants panel (the session bar's
|
|
295
|
-
participants chip) says so before its Invite, with the share warning (
|
|
297
|
+
participants chip) says so before its Invite, with the share warning (that a participant's turns act there, writes
|
|
298
|
+
included and asks to the owner, or only read while an older worker holds it; how many tool outputs its history holds)
|
|
296
299
|
and **Start a review of it**: a review fork of it, opened, with a toast (`Review 4b7e9a01 started from 3f9a1c2e's
|
|
297
300
|
recap; 3f9a1c2e keeps running.`) whose **Invite** opens the new session's panel. Every summary carries `review`
|
|
298
301
|
(null for other sessions).
|
|
@@ -309,6 +312,11 @@ declares every tool (full). The file stores only the name: a kit's tools and pro
|
|
|
309
312
|
`/toolkit` changes it, saved at once, from the next turn's start. See
|
|
310
313
|
[Toolkits](configuration.md#toolkits).
|
|
311
314
|
|
|
315
|
+
`toolkit_origin` records why the session is on that kit: `default` for the configured kit, `chosen` for a
|
|
316
|
+
user-selected kit (including `/toolkit` and `ctx.use_toolkit`), `inherited` for a continue or fork, or `suggested`
|
|
317
|
+
when `toolkit.suggest.mode: on` switched it ([Toolkits](configuration.md#toolkits)). A session file from before the
|
|
318
|
+
field reads as `chosen`. Only a `default` session is ever suggested a kit.
|
|
319
|
+
|
|
312
320
|
A `find_tools` result on a chat host saves the tools its hit loads as `loaded: ["list_sessions", "mcp_fs_read"]` on
|
|
313
321
|
its `tool_response` entry (wire names: a built-in's, an MCP tool's `mcp_<server>_<tool>`), whatever the host: a
|
|
314
322
|
later turn on `api: anthropic` (after a `/model` switch, a resume) sends the load as references, any other host
|
|
@@ -390,5 +398,7 @@ not touched. It refuses first if disk shows a running turn, pending
|
|
|
390
398
|
question or approval, queued input, or a running background task started by
|
|
391
399
|
the session; the message directs the user to `chi sessions stop ID`, then any prompt.
|
|
392
400
|
Reminders, a running `/btw`, and other in-memory worker state cannot be
|
|
393
|
-
checked and may be lost in this fallback.
|
|
394
|
-
|
|
401
|
+
checked and may be lost in this fallback. A stopped worker (SIGSTOP or
|
|
402
|
+
Ctrl-Z: `ps` state `T`) answers neither the ask nor a TERM, so a restart
|
|
403
|
+
refuses it at once, before asking, with its pid and `kill -CONT PID`.
|
|
404
|
+
The web restart route behaves the same way.
|
|
@@ -45,7 +45,7 @@ module Samagotchi
|
|
|
45
45
|
nil # never break the running turn
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# @return [Hash, nil] a copy of the turn in progress: prompt, origin,
|
|
48
|
+
# @return [Hash, nil] a copy of the turn in progress: prompt, turn_id, origin,
|
|
49
49
|
# continue, at (its wall time), ordered parts (generation / thinking / text / tool /
|
|
50
50
|
# input / steer / reminder / notice), pending_question and the last event_seq folded
|
|
51
51
|
# in. A finished tool part keeps the live row's action and duration_ms
|
|
@@ -136,6 +136,7 @@ module Samagotchi
|
|
|
136
136
|
return [] unless turn
|
|
137
137
|
|
|
138
138
|
started = { type: :turn_started, prompt: turn[:prompt], origin: turn[:origin], continue: !!turn[:continue] }
|
|
139
|
+
started[:turn_id] = turn[:turn_id] if turn[:turn_id]
|
|
139
140
|
started[:resumed] = true if turn[:resumed]
|
|
140
141
|
started[:started_at] = started_at if started_at
|
|
141
142
|
started.merge!(author_of(turn))
|
|
@@ -275,6 +276,7 @@ module Samagotchi
|
|
|
275
276
|
dequeue([event[:origin]])
|
|
276
277
|
@turn = { prompt: event[:prompt], origin: event[:origin], continue: !!event[:continue],
|
|
277
278
|
parts: [], pending_question: nil }
|
|
279
|
+
@turn[:turn_id] = event[:turn_id] if event[:turn_id]
|
|
278
280
|
@turn[:resumed] = true if event[:resumed]
|
|
279
281
|
# Its wall time (MessageTime): a joining page's gap divider.
|
|
280
282
|
@turn[:at] = event[:at] if event[:at]
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -629,8 +629,11 @@ module Samagotchi
|
|
|
629
629
|
# review_turns: a participant's turn runs as its own, inside the review
|
|
630
630
|
# boundary (Guardrails::ReviewBoundary); a worker without it has none,
|
|
631
631
|
# so chi web sends it no participant's turn (Delivery::REVIEW_FEATURE).
|
|
632
|
+
# pair_turns: a participant's turn in a working session runs as a pair
|
|
633
|
+
# turn (Guardrails::PairBoundary); a worker without it runs it as a
|
|
634
|
+
# review turn, and chi web says so (Delivery::PAIR_FEATURE).
|
|
632
635
|
FEATURES = ["restart", "task_stop", "reparent", Delivery::QUEUE_FEATURE, ReplyWait::TURN_IDS_FEATURE,
|
|
633
|
-
SessionInbox::UNQUEUE_FEATURE, Delivery::REVIEW_FEATURE].freeze
|
|
636
|
+
SessionInbox::UNQUEUE_FEATURE, Delivery::REVIEW_FEATURE, Delivery::PAIR_FEATURE].freeze
|
|
634
637
|
|
|
635
638
|
# The routes, by method and what follows /session/:id/: a handler takes
|
|
636
639
|
# the session id and the request body and returns [headers, status, body].
|
|
@@ -816,8 +819,8 @@ module Samagotchi
|
|
|
816
819
|
# What a parent's relay may say about this session's approval
|
|
817
820
|
# (BridgeClient#relay): opened and closed only change how the question
|
|
818
821
|
# shows (QuestionDesk#annotate); answered makes this worker ask the
|
|
819
|
-
# parent for the answer (RelayVerifier).
|
|
820
|
-
#
|
|
822
|
+
# parent for the answer (RelayVerifier). Like every Bridge route, this
|
|
823
|
+
# request requires the Bridge's secret.
|
|
821
824
|
RELAY_ACTIONS = %w[opened closed answered].freeze
|
|
822
825
|
# Why a relay closed, as a parent says it; anything else is "closed".
|
|
823
826
|
RELAY_CLOSE_REASONS = %w[stopped closed child_gone answered_on_child].freeze
|
|
@@ -1446,7 +1449,8 @@ module Samagotchi
|
|
|
1446
1449
|
|
|
1447
1450
|
# POST /session/:id/relay/status {relay_id}: what this (the parent's)
|
|
1448
1451
|
# worker's relay holds, for the child that verifies an answer
|
|
1449
|
-
# (RelayVerifier). Read-only
|
|
1452
|
+
# (RelayVerifier). Read-only; like every Bridge route, it requires the
|
|
1453
|
+
# Bridge's secret. 404 for an unknown id.
|
|
1450
1454
|
def handle_relay_status(_session_id, body)
|
|
1451
1455
|
parsed = parse_json(body)
|
|
1452
1456
|
return [{ "Allow" => "POST" }, 400, { error: "invalid_json" }] unless parsed.is_a?(Hash)
|
|
@@ -164,7 +164,11 @@ module Samagotchi
|
|
|
164
164
|
sock = opened_sock if attach(opened_sock)
|
|
165
165
|
end
|
|
166
166
|
return :closed if closed?
|
|
167
|
-
|
|
167
|
+
|
|
168
|
+
if outcome.is_a?(StreamOpen::Unreachable)
|
|
169
|
+
@reason = outcome.detail if outcome.detail
|
|
170
|
+
return :unreachable
|
|
171
|
+
end
|
|
168
172
|
return :unknown_session if outcome.is_a?(StreamOpen::Refused) && outcome.reason == :unknown_session
|
|
169
173
|
return :secret_refused if outcome.is_a?(StreamOpen::Refused) && outcome.reason == :bridge_secret
|
|
170
174
|
return :unreachable unless outcome.is_a?(StreamOpen::Opened)
|
|
@@ -10,7 +10,9 @@ module Samagotchi
|
|
|
10
10
|
module StreamOpen
|
|
11
11
|
Opened = Data.define(:sock, :status, :headers)
|
|
12
12
|
Refused = Data.define(:reason, :detail, :status)
|
|
13
|
-
Unreachable = Data.define(:attempts)
|
|
13
|
+
Unreachable = Data.define(:attempts, :detail) do
|
|
14
|
+
def initialize(attempts:, detail: nil) = super
|
|
15
|
+
end
|
|
14
16
|
|
|
15
17
|
BODY_LIMIT = 16_384
|
|
16
18
|
BODY_TIMEOUT = 5.0
|
|
@@ -23,7 +25,11 @@ module Samagotchi
|
|
|
23
25
|
def classify(sock, status, headers)
|
|
24
26
|
return Unreachable.new(attempts: 0) unless sock
|
|
25
27
|
return Opened.new(sock: sock, status: 200, headers: headers) if status.to_i == 200
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
if status.to_i == 503
|
|
30
|
+
json = parse(refusal_body(sock, headers))
|
|
31
|
+
return Unreachable.new(attempts: 1, detail: json&.[]("detail"))
|
|
32
|
+
end
|
|
27
33
|
|
|
28
34
|
body = refusal_body(sock, headers)
|
|
29
35
|
json = parse(body)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.123.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
|
|
6
6
|
files:
|
|
7
7
|
identity.md: sha256:29c2f7df9f6c5acbc5ec17000a4490741e55e426af15ca0d81c8706edd9bd027
|
|
8
8
|
self_map.md: sha256:df3f8da539a9f5812f79e089c2c8b9894da6124a89654f292160046a6bb060da
|
|
9
9
|
config_modification_protocol.md: sha256:ff4efccf2bdde453fc4e6741df8c18f78da78e353f6b1a5a64d0e7a631aea258
|
|
10
|
-
memory_guide.md: sha256:
|
|
10
|
+
memory_guide.md: sha256:3b9ff779ee962f1307d56937c826d5e581923114f3f39455b779ca618b363cd7
|
|
11
11
|
delegated.md: sha256:a0d6ddcd4a8be06526d964898611775fd251cfd9d145a9d16afaccaa503597bd
|
|
@@ -86,6 +86,19 @@ Shape (plain Markdown, no frontmatter):
|
|
|
86
86
|
- **Updating**: when a step turned out different, update the skill in the same turn: `edit` those steps in its file (`<scope dir>/skill_<name>.md`), or rewrite it with `memory_write`; keep the rest as it was, add a dated Changelog line. No confirmation needed.
|
|
87
87
|
- **The `skills` bundle** (`chi bundle install skills`, optional) adds `/skill save [name] [--system]`, `/skill list`, `/skill show <name>`, `/skill diff <name> [N]`; it keeps older versions and shows a short diff line after each update.
|
|
88
88
|
|
|
89
|
+
## Toolkit memories
|
|
90
|
+
|
|
91
|
+
A memory named `toolkit_<name>` defines a kit named `<name>`. Its body is one fenced YAML block with `base`, `add`,
|
|
92
|
+
`remove`, `groups` and a required `description`; never include `prompt:`. Only registered tools are declared, and
|
|
93
|
+
the built-in names (`full`, `lean`, `child`, `review`) are reserved. Project kits take precedence over system kits;
|
|
94
|
+
config and bundle kits take precedence over both. `/toolkit` lists a memory kit's source, and `--mute
|
|
95
|
+
toolkit_<name>` skips it for the session.
|
|
96
|
+
|
|
97
|
+
Write a kit when the user asks for one, when they use `/toolkit add` to choose the same tools for the same kind of
|
|
98
|
+
work twice, or when `chi toolkit report` shows a kit repeatedly short of a tool for one kind of prompt. A tweak to a
|
|
99
|
+
config or bundle kit is a new memory kit based on it (`base: staging`, for example), never a `config.yml` edit.
|
|
100
|
+
Writing or updating a toolkit memory is free like writing a skill; say so in the reply.
|
|
101
|
+
|
|
89
102
|
## Memory Bundles — shareable packs
|
|
90
103
|
|
|
91
104
|
Bundles are versioned directories/zips/tar.gz/git URLs with a `manifest.yml` and any of: memories (`*.md`), `hooks/*.rb` (bundle hooks, `docs/hooks.md`), `guardrails/*.yml` (rules, `docs/guardrails.md`), a `plugin.rb` (commands, tools, hooks, services; `docs/plugins.md`). They are shareable and installable.
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -249,6 +249,12 @@ module Samagotchi
|
|
|
249
249
|
# delegate child's. toolkit.kits (the user's own) is read by Toolkit.kits.
|
|
250
250
|
Entry.new(key: "toolkit.default", yaml_path: %w[toolkit default], type: :string, default: nil, expose: %i[env config], lifecycle: :session_creation),
|
|
251
251
|
Entry.new(key: "toolkit.child", yaml_path: %w[toolkit child], type: :string, default: nil, expose: %i[env config], lifecycle: :session_creation),
|
|
252
|
+
# Toolkit suggestion (ToolkitSuggest): off; audit (logs, never blocks or changes a session); on (the first eligible
|
|
253
|
+
# turn waits up to guardrails.system_one.timeout_ms and may start on the suggested kit).
|
|
254
|
+
Entry.new(key: "toolkit.suggest.mode", yaml_path: %w[toolkit suggest mode], type: :enum, default: "off", expose: %i[config], enum_values: %w[off audit on], lifecycle: :worker_start),
|
|
255
|
+
Entry.new(key: "toolkit.suggest.threshold", yaml_path: %w[toolkit suggest threshold], type: :float, default: 0.5, expose: %i[config], lifecycle: :worker_start),
|
|
256
|
+
Entry.new(key: "toolkit.suggest.defer_turns", yaml_path: %w[toolkit suggest defer_turns], type: :integer, default: 3, expose: %i[config], lifecycle: :worker_start),
|
|
257
|
+
Entry.new(key: "toolkit.suggest.defer_tokens", yaml_path: %w[toolkit suggest defer_tokens], type: :integer, default: 5000, expose: %i[config], lifecycle: :worker_start),
|
|
252
258
|
|
|
253
259
|
Entry.new(key: "default.max_tokens", yaml_path: %w[default max_tokens], type: :integer, default: nil, expose: %i[env config cli], lifecycle: :next_matching_use),
|
|
254
260
|
Entry.new(key: "max_tool_output_chars", yaml_path: %w[max_tool_output_chars], type: :integer, default: 10_000, expose: %i[env config cli], lifecycle: :next_matching_use),
|
|
@@ -284,6 +290,11 @@ module Samagotchi
|
|
|
284
290
|
Entry.new(key: "update.gem", yaml_path: %w[update gem], type: :bool, default: true, expose: %i[env config], lifecycle: :next_update),
|
|
285
291
|
Entry.new(key: "update.bundles", yaml_path: %w[update bundles], type: :bool, default: true, expose: %i[env config], lifecycle: :next_update),
|
|
286
292
|
Entry.new(key: "update.desktop", yaml_path: %w[update desktop], type: :bool, default: true, expose: %i[env config], lifecycle: :next_update),
|
|
293
|
+
Entry.new(key: "update.notify", yaml_path: %w[update notify], type: :enum, default: "on", expose: %i[env config], enum_values: %w[on off], lifecycle: :web_start),
|
|
294
|
+
Entry.new(key: "update.check_hours", yaml_path: %w[update check_hours], type: :integer, default: 24, expose: %i[env config], lifecycle: :web_start,
|
|
295
|
+
check: ->(value) { "at least 1" unless value >= 1 }),
|
|
296
|
+
Entry.new(key: "update.min_age_hours", yaml_path: %w[update min_age_hours], type: :integer, default: 24, expose: %i[env config], lifecycle: :web_start,
|
|
297
|
+
check: ->(value) { "at least 0" unless value >= 0 }),
|
|
287
298
|
# The desktop helper's kitty targets (Desktop::MacOS.kitty_settings, one of its terminal_settings): listen_on copied from kitty.conf (unset = none),
|
|
288
299
|
# the kitty binary, and the foreground programs listed, "|"-separated (a YAML list works too; "*" = every window).
|
|
289
300
|
Entry.new(key: "kitty.listen_on", yaml_path: %w[kitty listen_on], type: :string, default: nil, expose: %i[env config], lifecycle: :desktop_update),
|
data/lib/samagotchi/delivery.rb
CHANGED
|
@@ -58,6 +58,13 @@ module Samagotchi
|
|
|
58
58
|
"the owner must restart it (chi sessions restart ID, or the web's update badge)"
|
|
59
59
|
}.freeze
|
|
60
60
|
|
|
61
|
+
# A worker that runs a participant's turn in a working session as a pair
|
|
62
|
+
# turn (ParticipantMode :pair, Guardrails::PairBoundary). One without it
|
|
63
|
+
# still runs it as a review turn (ReviewBoundary), so chi web and chi
|
|
64
|
+
# share treat the session as :review (ParticipantMode.live) until it
|
|
65
|
+
# restarts.
|
|
66
|
+
PAIR_FEATURE = "pair_turns"
|
|
67
|
+
|
|
61
68
|
# Whether it is the one every sender defaulting to a step boundary uses.
|
|
62
69
|
def self.next_step?(value) = parse(value) == NEXT_STEP
|
|
63
70
|
|