samagotchi 0.156.0 → 0.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -1
- data/README.md +1 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +18 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +4 -1
- data/docs/cli.md +29 -24
- data/docs/configuration.md +43 -9
- data/docs/context.md +5 -3
- data/docs/guardrails.md +12 -7
- data/docs/hooks.md +53 -83
- data/docs/internals/bridge.md +84 -7
- data/docs/internals/bundles.md +69 -46
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +23 -7
- data/docs/internals/participants.md +3 -2
- data/docs/internals/remote-sessions.md +5 -2
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +1 -1
- data/docs/memory.md +51 -10
- data/docs/plugins.md +48 -11
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +13 -13
- data/docs/sub-agent.md +17 -2
- data/docs/testing.md +56 -5
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +5 -7
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +113 -10
- data/lib/samagotchi/bridge/turn_drawings.rb +271 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/bundle_command/check.rb +41 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +20 -45
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +7 -7
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +6 -6
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +8 -22
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +9 -6
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine.rb +30 -9
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +26 -44
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_registry.rb +57 -45
- data/lib/samagotchi/kernel_loop.rb +7 -3
- data/lib/samagotchi/llm/chat_loop.rb +6 -3
- data/lib/samagotchi/llm_context_strategy.rb +27 -63
- data/lib/samagotchi/memory_bundle/asset_installer.rb +92 -177
- data/lib/samagotchi/memory_bundle/builder.rb +12 -35
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +13 -19
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +10 -17
- data/lib/samagotchi/memory_bundle/installer.rb +212 -178
- data/lib/samagotchi/memory_bundle/manifest.rb +51 -58
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +26 -52
- data/lib/samagotchi/memory_bundle/remerge.rb +31 -14
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -11
- data/lib/samagotchi/memory_bundle/status.rb +26 -30
- data/lib/samagotchi/memory_bundle/system_bundle.rb +39 -10
- data/lib/samagotchi/memory_bundle/trash.rb +28 -18
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_profile.rb +1 -0
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +42 -10
- data/lib/samagotchi/plugin/loader.rb +75 -15
- data/lib/samagotchi/plugin/sessions.rb +1 -1
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +26 -0
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +34 -35
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +349 -441
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -6
- data/lib/samagotchi/session_access/remote.rb +5 -3
- data/lib/samagotchi/session_field.rb +134 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/emptiness.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +29 -100
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_metrics.rb +7 -4
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/join_replay.rb +91 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -4
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +18 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +60 -51
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +66 -21
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +1 -1
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +64 -2
- data/lib/samagotchi/web/session_summary.rb +36 -24
- data/lib/samagotchi/worker.rb +28 -5
- data/lib/samagotchi/worker_link.rb +3 -0
- metadata +22 -5
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
data/docs/configuration.md
CHANGED
|
@@ -105,7 +105,9 @@ Behavior:
|
|
|
105
105
|
- A key chi doesn't read warns at start, with the closest known key:
|
|
106
106
|
`config: unknown key 'default.modle' (did you mean 'default.model'?)`.
|
|
107
107
|
Names you choose under the maps below (host names, model ids) don't warn.
|
|
108
|
-
- Environment variables and CLI flags win over config-file values.
|
|
108
|
+
- Environment variables and CLI flags win over config-file values. A model's
|
|
109
|
+
own settings (`models:`, `hosts:`) layer in between: see
|
|
110
|
+
[Where a model setting comes from](#where-a-model-setting-comes-from).
|
|
109
111
|
- An edit to the file needs no restart of `chi web`. A new worker reads the
|
|
110
112
|
file when it starts; a running worker sees only settings whose readers check
|
|
111
113
|
the file again (see [When a change applies](#when-a-change-applies)). The web
|
|
@@ -148,7 +150,7 @@ or `chi web` restart.
|
|
|
148
150
|
| `default.model` | New session | Existing sessions keep their saved model; change one with `/model`. |
|
|
149
151
|
| `default.input`, `no_default_input`, `no_interrupt` | Next CLI run | These set up that run's first prompt or turn limit. |
|
|
150
152
|
| `default.max_tokens`, `max_tool_output_chars`, `memory.read_max_chars`, `read.*`, `execute.*` except `execute.description`, `image.*` | Next matching request or tool use | An operation already in progress keeps its resolved limits. |
|
|
151
|
-
| `server.host`, `server.port`, `hosts.*` | Worker start | Includes host addresses, APIs, declared models and host-level settings.
|
|
153
|
+
| `server.host`, `server.port`, `hosts.*` | Worker start | Includes host addresses, APIs, declared models and host-level settings. Commands (`chi self`, `chi models`, the session lists) and chi web (its model picker, the session cards) read `hosts:` as it is now; a running worker keeps the hosts it started with until it restarts. |
|
|
152
154
|
| `server.open_timeout`, `server.read_timeout`, `server.first_token_timeout` | Worker start | The worker's clients retain the timeouts set when they were built. |
|
|
153
155
|
| `model_aliases` | Next model lookup | A session keeps its resolved model unless it is explicitly switched. |
|
|
154
156
|
| `models.<key>.profile` | Session start or `/model` | It is resolved when that model is selected; editing it does not switch the current model's profile by itself. |
|
|
@@ -229,6 +231,35 @@ A few environment variables are not settings (no config.yml key, no flag):
|
|
|
229
231
|
- `SAMAGOTCHI_LOG_STRICT=1`: an unknown log tag or a malformed log event raises instead of being dropped. The spec
|
|
230
232
|
suite sets it, and a bundle's tests keep it (`Samagotchi::Testing::Sandbox`).
|
|
231
233
|
|
|
234
|
+
## Where a model setting comes from
|
|
235
|
+
|
|
236
|
+
A model's settings come in layers, and the first one set wins. Every surface reads them the same way: a turn,
|
|
237
|
+
`/model` and `/stats`, `chi self`, the web's chips and the start page's ⚙ panel, and a session list's ctx %. A
|
|
238
|
+
`models:` entry is looked up under each name the model goes by, the most specific first: the name as typed (an
|
|
239
|
+
alias), the alias's target (`host:id`), the part after a host prefix, then the id the host is asked for.
|
|
240
|
+
|
|
241
|
+
| Setting | Order, first set wins |
|
|
242
|
+
|---|---|
|
|
243
|
+
| `thinking` | the session's own (`/thinking`, `chi --thinking`) → `chi web --thinking` or `SAMAGOTCHI_THINKING_LEVEL` → `models:` → `hosts.<name>.thinking` → `thinking.level` in config.yml → `default` |
|
|
244
|
+
| `reasoning_history` | the session's own (`/reasoning-history`) → `SAMAGOTCHI_REASONING_HISTORY` → `models:` → `hosts.<name>` → the api's default (`all` on `api: anthropic`, else `turn`) |
|
|
245
|
+
| `steer_cut_after` | `SAMAGOTCHI_STEER_CUT_AFTER` → `models:` → `hosts.<name>` → `steer.cut_after` in config.yml → 20 |
|
|
246
|
+
| `profile` | `--profile` or `SAMAGOTCHI_MODEL_PROFILE` → `models:` (an unknown profile warns, and the next name is tried) → `hosts.<name>.profile` → the server's chat template → the name → `qwen36` |
|
|
247
|
+
| `sampling` | merged: `hosts.<name>.sampling`, then `models:` over it, key by key |
|
|
248
|
+
| `vision` | `models:` → `hosts.<name>.vision` → what the server or the host's model list says |
|
|
249
|
+
| `window_tokens` | the server's `n_ctx` → the host's model list → `models:` → `hosts.<name>.window_tokens` → `context.window_tokens` (`--context-window-tokens`, `SAMAGOTCHI_CONTEXT_WINDOW_TOKENS` or config.yml) → 256000 |
|
|
250
|
+
| `llm_context_strategy`, `llm_context_apply`, `llm_context_budget_tokens` | each on its own: the session's own (`/llm-context`, `chi --llm-context…`) → `models:` → `hosts.<name>` → `llm_context.*` (`SAMAGOTCHI_LLM_CONTEXT_…` or config.yml) → the default |
|
|
251
|
+
|
|
252
|
+
Two rules, both intended:
|
|
253
|
+
|
|
254
|
+
- For thinking, reasoning_history, steer_cut_after and the profile, a process-wide override (a flag or an
|
|
255
|
+
environment variable) beats the model's own setting: it runs every model one way for a while.
|
|
256
|
+
- For the window and the LLM context, the global key is the fallback whatever sets it: an environment variable
|
|
257
|
+
or a flag there doesn't beat a model's or a host's setting. (`steer.cut_after` from config.yml, too, comes after
|
|
258
|
+
the host's.)
|
|
259
|
+
|
|
260
|
+
Where a value came from is named as `session`, `models: KEY`, `hosts.NAME`, or the config key or environment
|
|
261
|
+
variable that set it.
|
|
262
|
+
|
|
232
263
|
## Model Server API
|
|
233
264
|
|
|
234
265
|
Chi talks to a model server over HTTP in one of two ways, set by the host's `api:`:
|
|
@@ -1440,10 +1471,12 @@ test run (`SAMAGOTCHI_ENV`, `RACK_ENV`, `CI`). The day is stamped in `$XDG_STATE
|
|
|
1440
1471
|
If an AGENTS.md file is present in the project root, samagotchi injects its
|
|
1441
1472
|
contents into the system prompt under a "Project specific description:" section.
|
|
1442
1473
|
The project root is the top of the git work tree chi runs in (a linked
|
|
1443
|
-
worktree's own checkout), or the
|
|
1474
|
+
worktree's own checkout), or of the folder marked by `chi project init` it runs
|
|
1475
|
+
in (see [Projects without git](memory.md#projects-without-git)); outside both,
|
|
1476
|
+
the current directory.
|
|
1444
1477
|
|
|
1445
|
-
Started in a subdirectory, chi loads the chain: every AGENTS.md from the
|
|
1446
|
-
|
|
1478
|
+
Started in a subdirectory, chi loads the chain: every AGENTS.md from the
|
|
1479
|
+
project's top down to the current directory, outermost first. The first keeps the
|
|
1447
1480
|
plain header; each further one is headed with its directory, e.g. "Project
|
|
1448
1481
|
specific description for pkg/:". Outside a repository only the current
|
|
1449
1482
|
directory's AGENTS.md is read. Edits are picked up by a new process or a
|
|
@@ -1774,8 +1807,8 @@ its own). The first target is a long session such as a day-long coordinator, whe
|
|
|
1774
1807
|
budget don't ask: they only decide when a batch goes in, and each batch's break comes as designed (`next_request`
|
|
1775
1808
|
with staged edits pending moves a break the old rule makes by the end of the next turn anyway). The chip's Set and
|
|
1776
1809
|
`--llm-context` on an attached or resumed session send `--yes`.
|
|
1777
|
-
- **Where to see it:** `/llm-context` (each value and where it came from: `
|
|
1778
|
-
`hosts
|
|
1810
|
+
- **Where to see it:** `/llm-context` (each value and where it came from: `session`, `models: <key>`,
|
|
1811
|
+
`hosts.<name>`, `llm_context.*`), `/stats` (`llm context:`), the turn log (an `llm_context` record per
|
|
1779
1812
|
turn that runs a layer or whose session set its strategy) and the web's chip. A woken worker's status line counts
|
|
1780
1813
|
its saved context against the session's budget.
|
|
1781
1814
|
|
|
@@ -2094,7 +2127,7 @@ are never sent to; `chi web push rm ID` removes them. From the Mac: `chi web pus
|
|
|
2094
2127
|
shows a recent delivery (Apple keeps accepting pushes for it): if one stops getting notifications, switch it off and on
|
|
2095
2128
|
there; the new subscription replaces the dead one.
|
|
2096
2129
|
|
|
2097
|
-
- **What a tap opens**: the session, in its own project's view (the all view for a session in no
|
|
2130
|
+
- **What a tap opens**: the session, in its own project's view (the all view for a session in no project: no repository or marked folder), whichever
|
|
2098
2131
|
project the app was showing; an open app window moves there.
|
|
2099
2132
|
- **What is sent**: the session's name and the reason ("needs approval", "needs an answer", "hit its step limit",
|
|
2100
2133
|
"needs you", "turn failed", "done in N s"), never the question or the command, as the tab's notification on the
|
|
@@ -2336,9 +2369,10 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
2336
2369
|
| `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token, asked of this machine's 127.0.0.1 too (its `Local:` link carries a one-time code; `chi web --new-token` replaces the token). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
|
|
2337
2370
|
| `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
|
|
2338
2371
|
| `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 as each finished turn's block summary 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. See [CLI: the working set](cli.md#the-working-set). |
|
|
2372
|
+
| `web.reload_check` | `false` | yes | Opt-in: once each finished turn has moved into the history, the page reads the session again (one full read per turn end), draws that turn as a reload would and compares the two, strictly: a known divergence counts as a mismatch too. `chi web` logs one `reload_checked` line per check and a `reload_mismatch` line naming the kinds that differed, `excused` (the known divergences, `history_shape.js` `ACCEPTED`, that would explain them) and `explained` (all of them had one), with `turn_id`, `drawn` (the turn came back from its drawing, so a mismatch with `drawn: true` is a drawing bug) and the worker's version: counts only, never content. The count is `grep -c reload_mismatch` over `grep -c reload_checked` in chi web's log, split by `excused`. Cards are left out: a reload places them after the render, which the check doesn't run (the e2e reload checks compare them). Every open owner tab runs the check; `chi web` logs a turn's first report only, and starts over when it restarts. |
|
|
2339
2373
|
| `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). |
|
|
2340
2374
|
| `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). |
|
|
2341
|
-
| `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a
|
|
2375
|
+
| `web.start_dir` | the folder `chi web` runs in | | The folder `chi web` starts in (`chi web --start-dir DIR` for one run): its page (the printed link, a bare `/`, the LAN link and QR code, the Home Screen app) opens on that folder's project, outside a project (no repository or marked folder) on every session, and a new session from the all view starts there; the header's project picker switches projects. `~` and a relative path (resolved from your home folder) work; one that isn't a folder stops `chi web` from starting. `chi web` never changes its own folder. See [CLI](cli.md). |
|
|
2342
2376
|
| `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). |
|
|
2343
2377
|
| `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. |
|
|
2344
2378
|
| `web.participants.queue` | `3` | | How many of a participant's turns may wait behind their running one (0 to 20); one more is refused (429 `too_many_turns`). |
|
data/docs/context.md
CHANGED
|
@@ -71,10 +71,12 @@ A source belongs to one session, or to a project:
|
|
|
71
71
|
|
|
72
72
|
- A **session** source (`chi context add NAME … <id>`) is that session's
|
|
73
73
|
alone and runs in the session's folder (the worktree it started in).
|
|
74
|
-
- A **project** source (`--project`, inside a git repository
|
|
75
|
-
|
|
74
|
+
- A **project** source (`--project`, inside a git repository or a folder
|
|
75
|
+
marked by `chi project init`) has **one snapshot shared by every session of
|
|
76
|
+
the project**: all worktrees of a repo (or subfolders of a marked folder)
|
|
76
77
|
share the project, and the command runs in the project root (the main
|
|
77
|
-
checkout; the session's folder when the root is a bare
|
|
78
|
+
checkout, or the marked folder; the session's folder when the root is a bare
|
|
79
|
+
git dir). Whichever
|
|
78
80
|
worker is due runs it; the others read the snapshot.
|
|
79
81
|
|
|
80
82
|
So anything that depends on the branch or the checkout (a PR, `git status`,
|
data/docs/guardrails.md
CHANGED
|
@@ -1392,7 +1392,8 @@ denies every call until the bundle is reinstalled.
|
|
|
1392
1392
|
chi bundle install known-names # or chi bundle install dev
|
|
1393
1393
|
```
|
|
1394
1394
|
|
|
1395
|
-
installs one `before_tool_call` hook
|
|
1395
|
+
installs a plugin with one `before_tool_call` hook (`chi.on`, priority 50; a
|
|
1396
|
+
`hooks/known_names.rb` file before known-names 0.2.0, which chi no longer loads) and a short memory. A local model that
|
|
1396
1397
|
once misspells a name inside a path (`jonathandoe` → `jonathndoe`)
|
|
1397
1398
|
keeps copying the wrong spelling from its context, and every call after
|
|
1398
1399
|
that fails. The hook knows the right names and compares strings: the
|
|
@@ -1432,7 +1433,7 @@ run*, *Run as is*, *Deny*; with no one to ask (`--non-interactive`) or a
|
|
|
1432
1433
|
dismissed question it rejects. A real near name (a folder `jondoe` next to
|
|
1433
1434
|
user `johndoe`, a login one letter from another) is caught too: list it under
|
|
1434
1435
|
`ignore:`. The hook is `on_error: log`: a bug in it warns and lets the call
|
|
1435
|
-
through. As with every
|
|
1436
|
+
through. As with every plugin, a running worker picks it up after its
|
|
1436
1437
|
next start.
|
|
1437
1438
|
|
|
1438
1439
|
The system prompt names the home directory once, with the advice to write
|
|
@@ -1441,7 +1442,8 @@ it as `~` or `$HOME`, so the model rarely has to spell it.
|
|
|
1441
1442
|
## An external guard: chi-nah
|
|
1442
1443
|
|
|
1443
1444
|
[chi-nah](https://github.com/dm1try/chi-nah) is a bundle kept in its own repo (`chi bundle install
|
|
1444
|
-
https://github.com/dm1try/chi-nah`). Its `before_tool_call` hook
|
|
1445
|
+
https://github.com/dm1try/chi-nah`). Its plugin's `before_tool_call` hook (`chi.on`, chi-nah 0.2.0;
|
|
1446
|
+
0.1.x shipped it as a class-file hook, which chi no longer loads: `chi bundle upgrade chi-nah`) asks
|
|
1445
1447
|
[nah](https://github.com/manuelschipper/nah)'s deterministic guards about every `execute`, `task_create`,
|
|
1446
1448
|
`read`, `write` and `edit`: a call nah blocks (secret exfiltration, `rm -rf ~`, `git reset --hard`,
|
|
1447
1449
|
`curl | sh`…) is denied with the guard's name and how the user pauses it (`nah nap <guard>`); everything
|
|
@@ -1449,10 +1451,13 @@ else goes to chi's own rules. It needs nah installed; see its README for the set
|
|
|
1449
1451
|
|
|
1450
1452
|
## Failing closed
|
|
1451
1453
|
|
|
1452
|
-
- A config hook with `required: true`, or a bundle `
|
|
1453
|
-
`
|
|
1454
|
-
|
|
1455
|
-
|
|
1454
|
+
- A config hook with `required: true`, or a bundle plugin its manifest marks `required: true`
|
|
1455
|
+
(`plugin:` `required: true`, for a `chi.on(:before_tool_call, on_error:
|
|
1456
|
+
:fail_closed)` guard, chi 0.157.0 or newer, [plugins.md](plugins.md#a-guard-that-fails-closed-on_error-fail_closed)),
|
|
1457
|
+
that fails to load (missing, syntax error, an unmet `requires_chi`, or for a
|
|
1458
|
+
plugin a sha256 that differs from the installed one) makes chi deny
|
|
1459
|
+
every tool call. (An older chi's class-file bundle hooks don't load at all,
|
|
1460
|
+
`fail_closed` or not: a not-required failure, [hooks.md](hooks.md#class-file-hooks-are-retired).) A fail-closed hook that raises when called denies that call.
|
|
1456
1461
|
- Other hooks stay fail-open; a load failure is a warning.
|
|
1457
1462
|
- Every load failure is shown once, at the start of the first turn
|
|
1458
1463
|
(`guardrails> …` in the terminal, a red line on the web).
|
data/docs/hooks.md
CHANGED
|
@@ -145,8 +145,9 @@ they must never spend or permit. See
|
|
|
145
145
|
[Guardrails](guardrails.md#answers-from-a-parent-agent). `effects:` needs a
|
|
146
146
|
chi newer than 0.85.0 (an older one raises on the keyword).
|
|
147
147
|
|
|
148
|
-
`event[:hook]` is the label the notices carry: `
|
|
149
|
-
known-names)` for a
|
|
148
|
+
`event[:hook]` is the label the notices carry: `plugin.rb (bundle
|
|
149
|
+
known-names)` for a plugin's `chi.on` hook,
|
|
150
|
+
`audit.rb (config)` for a config hook,
|
|
150
151
|
`turn hook` for one registered at runtime.
|
|
151
152
|
|
|
152
153
|
`event[:notify]` also takes `fallback_for:`, what the line stands in for, so
|
|
@@ -437,13 +438,13 @@ sources' refs in the reply that asks, and in the question's text and options).
|
|
|
437
438
|
|
|
438
439
|
## Settings
|
|
439
440
|
|
|
440
|
-
A hook class whose `initialize` takes an argument gets its settings: **one
|
|
441
|
+
A hook class (a config hook's; a plugin's class the same way, [plugins.md](plugins.md)) whose `initialize` takes an argument gets its settings: **one
|
|
441
442
|
positional Hash with string keys** (`def initialize(settings = {})`;
|
|
442
443
|
`initialize(**kw)` is not supported). A class whose `initialize` takes none
|
|
443
444
|
is built bare. Defaults belong in the hook.
|
|
444
445
|
|
|
445
446
|
```yaml
|
|
446
|
-
bundles: # per bundle, by name, for its
|
|
447
|
+
bundles: # per bundle, by name, for its plugin
|
|
447
448
|
known-names:
|
|
448
449
|
names: [jonathandoe]
|
|
449
450
|
mode: reject
|
|
@@ -465,6 +466,10 @@ change), as for every hook.
|
|
|
465
466
|
(missing file, syntax error), chi denies every tool call and says why; if it
|
|
466
467
|
raises as a `before_tool_call` hook, that call is denied. See
|
|
467
468
|
[Guardrails](guardrails.md#failing-closed).
|
|
469
|
+
- A plugin's `chi.on(:before_tool_call, on_error: :fail_closed)` (chi 0.157.0 or newer) is
|
|
470
|
+
the same for a bundle: a raise denies that call, and a plugin its manifest
|
|
471
|
+
marks `required: true` that fails to load denies every call. See
|
|
472
|
+
[plugins.md](plugins.md#a-guard-that-fails-closed-on_error-fail_closed).
|
|
468
473
|
|
|
469
474
|
A hook that fails to load is reported as a `[samagotchi:hooks]` warning and
|
|
470
475
|
once in the UI.
|
|
@@ -472,22 +477,10 @@ once in the UI.
|
|
|
472
477
|
Hook failures never break the engine loop — each hook is wrapped in its own
|
|
473
478
|
try/catch.
|
|
474
479
|
|
|
475
|
-
A
|
|
476
|
-
worker, on the plugins' surface
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
```
|
|
480
|
-
plugins> guardrail guardrails.rb (bundle known-names) before_tool_call hook failed (RuntimeError: boom):
|
|
481
|
-
it denies every tool call it guards, and its next failures are in each denial's reason
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
The label names the file, the bundle and the event, so one line per hook: a
|
|
485
|
-
hook on a stream can't nag. `on_error` still decides what happens at every
|
|
486
|
-
firing — `skip` logs nothing, `log` warns each time (a stream hook at most
|
|
487
|
-
once a minute), a fail_closed `before_tool_call` hook denies the call — the
|
|
488
|
-
notice is only added on top. A config hook (config.yml's own files) keeps its
|
|
489
|
-
behaviour: no notice, just the log. A UI that joins later reads the same lines
|
|
490
|
-
from `Engine#plugin_warning`.
|
|
480
|
+
A plugin's `chi.on` handler that **raises** tells you once per hook in a
|
|
481
|
+
worker, on the plugins' surface ([plugins.md](plugins.md)). A config hook
|
|
482
|
+
(config.yml's own files) keeps its behaviour: no notice, just the log. A UI
|
|
483
|
+
that joins later reads the same lines from `Engine#plugin_warning`.
|
|
491
484
|
|
|
492
485
|
In the [bundle test kit](plugins.md#testing-a-bundle-experimental) a hook that raises raises
|
|
493
486
|
`Testing::HookFailed` out of the call that fired it, under every `on_error`.
|
|
@@ -655,70 +648,46 @@ declared tool's call, with no `via:`; a direct `mcp_fs_read` fires as the `call_
|
|
|
655
648
|
stands for (`tool: "call_tool"`, `args: {"tool" => "fs/read", "args" => {…}}`), as if the model
|
|
656
649
|
had called `call_tool`.
|
|
657
650
|
|
|
658
|
-
## Bundle
|
|
659
|
-
|
|
660
|
-
Bundles can ship executable guardrails alongside memories. A bundle with hooks lives as a directory with a `hooks/` subdirectory (flat, basename-keyed):
|
|
661
|
-
|
|
662
|
-
```
|
|
663
|
-
my-bundle/
|
|
664
|
-
manifest.yml
|
|
665
|
-
identity.md
|
|
666
|
-
hooks/
|
|
667
|
-
guardrails.rb # class Guardrails; def call(event); ...; end; end
|
|
668
|
-
audit.rb
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
`manifest.yml` may carry an optional `hooks:` map (both `files:` and `hooks:` are optional; a bundle may carry only one):
|
|
651
|
+
## Bundle hooks
|
|
672
652
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
identity.md: sha256:abc...
|
|
679
|
-
hooks:
|
|
680
|
-
guardrails.rb:
|
|
681
|
-
sha256: 1234...
|
|
682
|
-
event: before_tool_call
|
|
683
|
-
on_error: fail_closed # skip (default) | log | fail_closed (before_tool_call only)
|
|
684
|
-
priority: 10
|
|
685
|
-
audit.rb:
|
|
686
|
-
sha256: 5678...
|
|
687
|
-
event: after_tool_call
|
|
688
|
-
on_error: log
|
|
689
|
-
priority: 100
|
|
690
|
-
trust_level: reviewed # reviewed | trusted | experimental (default)
|
|
691
|
-
requires_chi: ">= 0.12.0" # optional: a gem-style requirement, as for plugins
|
|
692
|
-
needs: [gh] # optional: outside commands the memories use (docs/memory.md#bundles-that-need-outside-commands)
|
|
693
|
-
```
|
|
653
|
+
A bundle hooks events from its `plugin.rb` with `chi.on` (next to its
|
|
654
|
+
commands and tools): see [Plugins](plugins.md). There `ctx.notify`,
|
|
655
|
+
`ctx.steer` and the other `ctx` helpers act as this page's `event[:notify]`…
|
|
656
|
+
do for the event the block runs for, and `chi.on(:before_tool_call,
|
|
657
|
+
on_error: :fail_closed)` makes a guard that fails closed.
|
|
694
658
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
- Hook key = basename (flat under `hooks/`). No subdirs in v1.
|
|
698
|
-
- `event` is required for auto-registration; a hook with no event is skipped.
|
|
699
|
-
- `events: [after_turn, question_asked]` (a list, in place of `event:`) registers the hook for each event it
|
|
700
|
-
names: **one** instance serves them all (its state is shared), and `#call` tells them apart by
|
|
701
|
-
`event[:type]`. `event:` and `events:` together, or an `events:` that isn't a non-empty list of event names,
|
|
702
|
-
is refused at install (`hooks: <file>: …`). `on_error: fail_closed` applies to its `before_tool_call`
|
|
703
|
-
registration only. Needs chi newer than 0.97.0 (an older chi installs the hook with no event and never loads
|
|
704
|
-
it): set `requires_chi`.
|
|
705
|
-
- `sha256` is integrity (not authenticity). No signing in v1. Install records the sha256 of the copied file; at `Engine.new` a hook whose file differs is not loaded (reinstall the bundle after editing one by hand).
|
|
706
|
-
- Hook code is the bundle author's source of truth: on upgrade, hooks are overwritten; if the installed file was locally modified, a warning is emitted (`was locally modified; overwriting`).
|
|
707
|
-
- `on_error` defaults to `skip` for every event, `:before_tool_call` included: a raising hook is silent and the call goes ahead. `log` warns. `fail_closed` (only on `:before_tool_call`; on any other event it acts as `skip`) denies the call the hook raised on. Choose `fail_closed` for a hook that is a guardrail, whose failure should stop tool calls rather than let them through.
|
|
708
|
-
- `requires_chi`: when this chi doesn't meet it, none of the bundle's hooks load (each is reported, `not loaded: it requires chi …`), as for a [plugin](plugins.md#loading-and-when-it-fails).
|
|
709
|
-
- A `fail_closed` `:before_tool_call` hook is required: if it is missing, fails to load, its sha256 differs or chi doesn't meet the bundle's `requires_chi`, chi denies every tool call until it is fixed.
|
|
659
|
+
- Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order. Bundle hooks are process-scoped: they survive the per-turn `clear_hooks` (only plain hooks are cleared).
|
|
660
|
+
- Settings: the plugin's class gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
|
|
710
661
|
- A bundle can also ship YAML rules in `guardrails/*.yml`; see [Guardrails](guardrails.md#the-guardrails-bundle).
|
|
711
|
-
-
|
|
712
|
-
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
-
|
|
721
|
-
|
|
662
|
+
- Shipped bundles (each but web-search in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a plugin's `before_tool_call` hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a plugin's `after_turn` and `question_asked` hook: links source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Bundles](bundles.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Bundles](bundles.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Bundles](bundles.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Bundles](bundles.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Bundles](bundles.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Bundles](bundles.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Bundles](bundles.md#the-cost-cap-bundle)), `chi bundle install work-cap` (a plugin: a per-session cap on tool calls and active minutes, `/workcap`, see [Bundles](bundles.md#the-work-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Bundles](bundles.md#the-ruby-bundle)) `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Bundles](bundles.md#the-skills-bundle)), `chi bundle install github-pr` (attached context: the branch's open PR, see [Bundles](bundles.md#the-github-pr-bundle)), `chi bundle install coordinator` (a plugin: parallel work in worktrees and child sessions, see [Bundles](bundles.md#the-coordinator-bundle)), `chi bundle install claude-peer` / `codex-peer` (a plugin: talk to a Claude Code or Codex session, see [Bundles](bundles.md#the-claude-peer-bundle)), `chi bundle install model-notes` (memories: reviewed model notes, see [Bundles](bundles.md#the-model-notes-bundle)), `chi bundle install decision-card` (a plugin: a long answer's question as a card, see [Bundles](bundles.md#the-decision-card-bundle)), `chi bundle install inline-widgets` (a plugin: prompt sections that teach the `chi-ui` blocks chi web draws, see [Bundles](bundles.md#the-inline-widgets-bundle)) and `chi bundle install web-search` (a plugin: a `web_search` tool through your SearXNG, in no profile, see [Bundles](bundles.md#the-web-search-bundle)).
|
|
663
|
+
- Installing a bundle runs its plugin at `Engine` startup. Only install bundles you trust, as you would a gem.
|
|
664
|
+
|
|
665
|
+
### Class-file hooks are retired
|
|
666
|
+
|
|
667
|
+
Bundles used to ship hooks as class files: `hooks/*.rb` with a `hooks:` map in
|
|
668
|
+
`manifest.yml` (`event:`, `on_error:`, `priority:`, `sha256:`). chi no longer
|
|
669
|
+
installs or loads them:
|
|
670
|
+
|
|
671
|
+
- Installing or upgrading a source that has a `hooks:` entry or a `hooks/*.rb`
|
|
672
|
+
file is refused (`hooks: class-file bundle hooks are retired: …`), its
|
|
673
|
+
`--dry-run` too; `chi bundle check` reports it as `hooks-retired`. An empty
|
|
674
|
+
`hooks: {}` is fine.
|
|
675
|
+
- A bundle an older chi installed with hooks keeps its memories, rules, plugin
|
|
676
|
+
and scripts. Its hooks don't load: the first turn says so (`hooks (bundle X)
|
|
677
|
+
failed to load (class-file bundle hooks are retired: …)`, not a required
|
|
678
|
+
failure, so tool calls still run), `chi bundle status X` lists them under
|
|
679
|
+
`Hooks (N): not loaded`, and `chi update` adds a `hooks-retired` row for a
|
|
680
|
+
bundle chi doesn't ship.
|
|
681
|
+
- The next install or upgrade from a source without hooks removes the old
|
|
682
|
+
`hooks/` (an edited hook, or a file the record doesn't know, is copied to
|
|
683
|
+
the trash first; a dev link never): `Removed (chi no longer loads
|
|
684
|
+
class-file bundle hooks): hooks/x.rb`, and the dry run and `chi update`'s
|
|
685
|
+
plan say so. `chi bundle uninstall` still removes them the same way.
|
|
686
|
+
- To port one, move the class's `#call` into a `chi.on(event)` block of the
|
|
687
|
+
bundle's `plugin.rb` (one block per event; `on_error: :fail_closed` for a
|
|
688
|
+
fail_closed guard, with `plugin: required: true` in the manifest), then drop
|
|
689
|
+
`hooks/` and `hooks:`. known-names 0.2.0, source-links 0.7.0 and chi-nah 0.2.0
|
|
690
|
+
are ported examples.
|
|
722
691
|
|
|
723
692
|
## The source-links bundle
|
|
724
693
|
|
|
@@ -726,7 +695,8 @@ Lifecycle:
|
|
|
726
695
|
chi bundle install source-links # or chi bundle install dev
|
|
727
696
|
```
|
|
728
697
|
|
|
729
|
-
installs one `after_turn`
|
|
698
|
+
installs a plugin with one hook on `after_turn` and `question_asked` (`chi.on`, priority 90; a
|
|
699
|
+
`hooks/source_links.rb` file before source-links 0.7.0) and a short memory. When the model's answer
|
|
730
700
|
mentions a known source ref — a JIRA ticket, a GitHub issue, an internal
|
|
731
701
|
wiki page — the web links it in the answer (below), and every UI shows one
|
|
732
702
|
line right after the message:
|
|
@@ -801,7 +771,7 @@ source is skipped whole (its partial matches are discarded) with a warning,
|
|
|
801
771
|
and the others still report. An entry with neither `prefix:` nor `pattern:`,
|
|
802
772
|
or a pattern that does not compile, is skipped with a warning at load. The
|
|
803
773
|
hook is `on_error: log`: a bug in it warns and the turn is unaffected. As with
|
|
804
|
-
every
|
|
774
|
+
every plugin, a running worker picks it up after its next start.
|
|
805
775
|
|
|
806
776
|
### Placeholders, and the project's own repo
|
|
807
777
|
|
data/docs/internals/bridge.md
CHANGED
|
@@ -16,6 +16,7 @@ rescues its own errors: an observer must never break the running turn.
|
|
|
16
16
|
| `RingBuffer` | `ring_buffer.rb` | The last `ring_capacity` (256) raw events by `event_seq`, in memory, for a reconnect's replay. The capture observer feeds it. |
|
|
17
17
|
| `TurnAccumulator` | `turn_accumulator.rb` | The turn in progress, folded from events. |
|
|
18
18
|
| `CardStore` | `card_store.rb` | The last cards and notices, saved in `cards.json`, each anchored to its turn. |
|
|
19
|
+
| `TurnDrawings` | `turn_drawings.rb` | Each finished turn as live drew it (`turn_drawings.jsonl`): written by the accumulator's `on_turn_end` hook, read by chi web's show. |
|
|
19
20
|
| `CardPlaces` | `card_places.rb` | Pure: where `CardStore`'s entries sit in the messages the page draws (`turns_since`, and the `opening_id` stamp). |
|
|
20
21
|
| `PendingCard` | `pending_card.rb` | `pending_card.json` `{id:, bundle:}`: the running turn's open card with actions (check-in's Nudge), written and removed with that card, so chi web's hub, which watches files and not workers, can notify. |
|
|
21
22
|
|
|
@@ -27,12 +28,19 @@ whose seq a cursor is) and its **secret**; both are published in `bridge.json` (
|
|
|
27
28
|
|
|
28
29
|
A joining UI must draw a turn already half done, and the ring may have dropped its early events. The accumulator
|
|
29
30
|
folds the event log into one hash, `current_turn`: the prompt, `turn_id`, origin, `at`, and ordered `parts`
|
|
30
|
-
(generation, thinking, text, tool, input, steer, reminder, notice), plus `pending_question`. A tool part keeps its
|
|
31
|
-
row fields (`ToolRowFields`), its output cut head-and-tail at `ToolOutputCut::WEB_PREVIEW_CHARS` so a
|
|
32
|
-
row a reload draws,
|
|
31
|
+
(generation, thinking, text, tool, input, steer, reminder, notice, card), plus `pending_question`. A tool part keeps its
|
|
32
|
+
row fields (`ToolRowFields`), its `tool_id`, its output cut head-and-tail at `ToolOutputCut::WEB_PREVIEW_CHARS` so a
|
|
33
|
+
joined row is the row a reload draws, `elapsed_ms` while it runs (the accumulator times running calls itself, `clock:`)
|
|
34
|
+
and, once finished, the event's `duration_ms` (its own clock only for an older worker's event that has none). A
|
|
33
35
|
`notice` part holds the event of a row that is neither text nor a tool call (`TurnNotice::FIELDS`: a hook's notice,
|
|
34
36
|
the "asking again" row, a steer's cut, a ✂ row, a question and its answer), so a join replays it through the handler
|
|
35
|
-
that drew it live.
|
|
37
|
+
that drew it live. A `card` part is a card the turn showed in its step (`in_turn`), its latest version in its first
|
|
38
|
+
one's place; `replay_events` and `join_events` skip it (a running turn's join takes its cards from CardStore), and
|
|
39
|
+
only `replay_events(turn, cards: true)` (a finished turn's drawing) replays it as its `card` event.
|
|
40
|
+
|
|
41
|
+
At the terminal event, before clearing, the accumulator hands a deep copy of the turn and the end event to its
|
|
42
|
+
`on_turn_end:` hook (the Bridge's `TurnDrawings#append`). The hook runs under the accumulator's mutex and the
|
|
43
|
+
observer lock, so it never calls back in; an error is logged (`turn_end_hook_failed`) and the fold goes on.
|
|
36
44
|
|
|
37
45
|
The turn is cleared on its terminal event (`Events::TURN_END`), the same event that brings its messages into the
|
|
38
46
|
session, so a snapshot shows a turn in exactly one of the two places. Beside the turn it keeps what a snapshot also
|
|
@@ -40,10 +48,58 @@ needs and the messages lag: `queued` turns (`:turn_enqueued` until started, merg
|
|
|
40
48
|
the idle `recap` (until a turn starts), a pending `continue_offer`, and the running turn's `working_set`.
|
|
41
49
|
|
|
42
50
|
Three other users: `replay_events(turn)` turns a `current_turn` back into the live events that would have drawn it
|
|
43
|
-
(the
|
|
51
|
+
(the attached TUI's join); `join_events` is the page's unfold of the same turn plus the queue behind it (its
|
|
52
|
+
`turn_started` also carries `at`, `cut_after` and every author key, then a `turn_enqueued` per queued prompt and a
|
|
53
|
+
`command_queued` per waiting command), which chi web's `GET /api/sessions/:id` sends as `replay` from the worker's
|
|
54
|
+
snapshot (`from_wire` symbolizes it), so any worker's snapshot gives the page the same events
|
|
55
|
+
(`spec/shared/turn_snapshot.json` pins it against turn_events.js `snapshotEvents`, the page's own fallback for one
|
|
56
|
+
release); `messages_of` gives a
|
|
44
57
|
plugin's `ctx.messages` the turn so far (`Engine#running_turn_messages`); `participant_turn?` tells `POST /turn` that
|
|
45
58
|
a participant's review turn runs, so the owner's input queues behind it.
|
|
46
59
|
|
|
60
|
+
## TurnDrawings
|
|
61
|
+
|
|
62
|
+
A reload or a reconnect draws a finished turn from what live drew, not rebuilt from its saved messages: the
|
|
63
|
+
accumulator's final fold of the turn (its parts in order, in-turn cards included) and the end event's display keys
|
|
64
|
+
(`TurnDrawings::END_KEYS`), one JSON line per turn in `<session dir>/turn_drawings.jsonl`
|
|
65
|
+
(`{v: 1, turn_id, kind, from, to, from_digest, to_digest, drawing, end}`, `TurnDrawings::Line`).
|
|
66
|
+
|
|
67
|
+
- **The range comes from the Engine.** The end event's `kept_from`/`kept_to` (`Engine#end_turn`) name the turn's own
|
|
68
|
+
messages, `kept[from...to]`. No range (an older worker, a turn that kept nothing) or an empty one writes no line,
|
|
69
|
+
and the turn draws as before.
|
|
70
|
+
- **Validity.** A line holds while the messages reach `to` and `messages[from]` and `messages[to - 1]` digest as the
|
|
71
|
+
line says (`TurnDrawings.digest`: role, content and time, one canonical form for the live symbol-keyed messages
|
|
72
|
+
and the saved, scrubbed ones). A rollback, a rolled-back failure or a prompt handed back drops it. Of overlapping
|
|
73
|
+
valid lines (a rollback, then a re-run that said the same) and of one turn id's lines, the newest wins
|
|
74
|
+
(`TurnDrawings.valid`).
|
|
75
|
+
- **Writing.** The accumulator calls `TurnDrawings#append` at the end event, inside the observer delivery, so the
|
|
76
|
+
line is on disk before a stream writer (subscribed later) gets the end frame. That departs from SessionObserver's
|
|
77
|
+
"enqueue-only" note, as CardStore's save does: one append per turn end. It writes `"\n"` first when the file's last
|
|
78
|
+
byte isn't one (a torn line from a crash), then the line in one write; a failure is logged (`turn_drawing_failed`).
|
|
79
|
+
A part's output is the accumulator's cut (`WEB_PREVIEW_CHARS`); a diff is the one the saved `tool_diffs` have
|
|
80
|
+
(`TextDiff`'s cut); images are refs.
|
|
81
|
+
- **Reading.** `TurnDrawings.read(dir)` skips an unknown `v`, a torn or broken line and one missing a field, and is
|
|
82
|
+
cached by the file's path, size and mtime. `#last` is the newest line this worker wrote or started from.
|
|
83
|
+
- **The snapshot.** `Bridge#snapshot` (the snapshot and reset frames, `GET /snapshot`) adds `last_turn_drawing:
|
|
84
|
+
{drawing, end}`, `#last` as the file has it, not unfolded, when it holds against the snapshot's messages and its `to`
|
|
85
|
+
is their size (the last exchange); it is absent otherwise (no turn yet, a rollback, a Continue's note after it, a line
|
|
86
|
+
of another version, one over the 1 MB a restart reads back: `TurnDrawings.sendable?`). An attaching TUI unfolds it (`TerminalUI::JoinReplay`, `TurnAccumulator.replay_events`) and draws
|
|
87
|
+
that turn's rows through its `EventRenderer`, as a running turn's join does, then the end event's cancel or failure
|
|
88
|
+
line, else the saved answer; without it (or for a drawing that isn't the last prompt's turn: a reminder's, a
|
|
89
|
+
Continue's) the rows are rebuilt from the messages as before. `chi --remote` attaches through chi web's stream proxy,
|
|
90
|
+
which relays an owner's frames byte for byte, the field included. A participant never gets it: its stream's snapshot
|
|
91
|
+
is bare (`ParticipantView`). An older TUI ignores the field; an older worker sends none.
|
|
92
|
+
- **The show.** chi web's full read with `?parts=1&drawings=1` (the page's) stamps each display item of a kind a
|
|
93
|
+
drawing draws (the opening, steps, steers, merged lines, the empty answer) in a valid range with `drawn: <turn_id>`
|
|
94
|
+
and drops its `parts` (its ✂ marks stay as `marks: {tool_id => edit}`), and sends `turn_drawings: {turn_id =>
|
|
95
|
+
{events, end}}`: `TurnAccumulator.replay_events(drawing, cards: true)`, the one unfold, in Ruby. A context note in
|
|
96
|
+
a range and a `!cmd`'s record are not stamped. Without `drawings=1` (an older page) the read is what it was.
|
|
97
|
+
CardStore lists an in-turn entry's turn as `of_turn`, so the page leaves a drawn turn's own rows and cards to its
|
|
98
|
+
drawing (`cards_place.js`). A live worker whose sidecar features lack `turn_drawings` (`TurnDrawings::FEATURE`:
|
|
99
|
+
an older one lists no `of_turn`) gets no drawings in the show: its turns all draw as before.
|
|
100
|
+
- **Compat.** An older worker writes nothing (its turns fall back); an older chi web never opens the file. A fork
|
|
101
|
+
or a continue copies no drawings: its turns fall back. A one-shot run (no Bridge) writes none.
|
|
102
|
+
|
|
47
103
|
## CardStore
|
|
48
104
|
|
|
49
105
|
The cards a plugin or the Engine shows (`Engine#show_card`), hook notices, load warnings (`:guardrail_warning`),
|
|
@@ -69,7 +125,7 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
|
|
|
69
125
|
comes mid-turn but isn't the turn's (an anytime command's, such as `/btw`) goes after that turn (`during`), and so does a
|
|
70
126
|
between-turns notice. A card with an earlier card's id replaces it in place and keeps its anchor. A `list` with no messages
|
|
71
127
|
(`?cards=1`'s bodies, `chi bundle try`'s trace) has no `turns_since` and no `opening_id`.
|
|
72
|
-
- **The stamp.** Next to `turns_since`, an entry lists `opening_id` (`CardPlaces#opening_of`): the id of the opening its count
|
|
128
|
+
- **The stamp.** Next to `turns_since`, an entry lists `opening_id` (`CardPlaces#opening_of`, one `Steer.opening_stamp` rule with the recap's): the id of the opening its count
|
|
73
129
|
reaches, the one it comes after: a prompt's `turn_id` (a wake note's too), or `/continue no`'s reason line's `opening_id`. It is
|
|
74
130
|
resolved at `list` time from the same anchor (an entry's own turn often has no bubble: a Continue, a failed or rolled-back turn),
|
|
75
131
|
so the stamp and the count always name one place, and the page finds it by the bubble (`card_stamps.js`: `data-turn-id`, or `data-opening-id` on a wake's note or a reason line), not by counting
|
|
@@ -77,6 +133,9 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
|
|
|
77
133
|
first while that line is still in the run, so a later release can drop the count. No stamp (the page counts `turns_since`) for
|
|
78
134
|
an entry before every opening, after a line an older chi wrote with no id, or from a version 1 file (`base_since`). A rollback
|
|
79
135
|
keeps today's rule: a rolled-back turn's cards name the opening before it.
|
|
136
|
+
The saved recap carries the same stamp: `saved_recap`'s `opening_id` (`RecapPlace.opening_id`, the id of the last
|
|
137
|
+
opening its `covered` messages hold, read at read time, so `recap.json` is unchanged), which the page places it after
|
|
138
|
+
(`recap_stamp.js`); an older worker's `saved_recap` has none, and the page counts `turns_since`.
|
|
80
139
|
- **A turn that leaves nothing** is read, not told: an in-turn entry not `current` whose turn has no opening in the messages
|
|
81
140
|
(a `!rollback`, a failed turn with nothing kept, a prompt stopped before the model) lists as a between-turns entry at its
|
|
82
141
|
place, a card without its actions and a hook's notice without its step; its other rows, having no step to sit in, aren't
|
|
@@ -195,6 +254,22 @@ The Bridge exposes:
|
|
|
195
254
|
"params":"path=notes.txt","event_seq":812}
|
|
196
255
|
```
|
|
197
256
|
|
|
257
|
+
Both tool events carry the run's `tool_id` (`ToolIds`, the id its output is saved under in the session's
|
|
258
|
+
`tool_ids`, handed out before the batch runs), and `tool_call_completed` carries `duration_ms`: the call's time from
|
|
259
|
+
the `tool_call_started` emit to its own, less an approval's `waited_ms`, rounded first (SessionMetrics' tool record
|
|
260
|
+
definition). It is the one duration every UI shows: the web's live row, the tool record a reload draws, the attached
|
|
261
|
+
terminal's line; each keeps its own clock only for an older worker's event without it. A long output's
|
|
262
|
+
`tool_call_completed` also carries `preview`: `ToolOutputCut.preview` of the capped output at `WEB_PREVIEW_CHARS`
|
|
263
|
+
(a `read`'s head, any other tool's head and tail), the text a reload's row has from the saved output
|
|
264
|
+
(`MessageParts#tool_part`; spec/tool_preview_parity_spec.rb pins the two). The web row and the accumulator's part
|
|
265
|
+
take it; the TUI keeps `output`.
|
|
266
|
+
|
|
267
|
+
An end event (`turn_completed`, `turn_canceled`, `turn_failed`) of a turn that kept messages carries `kept_from` and
|
|
268
|
+
`kept_to`: the turn's own messages are `messages[kept_from...kept_to]` of the session it leaves (`Engine#end_turn`;
|
|
269
|
+
`kept_from` is the conversation's size under the system head the turn may have added, before its reminders and
|
|
270
|
+
prompt). Small integers on the event only, never on a message; no client draws them (`Bridge::TurnDrawings` reads
|
|
271
|
+
them). An older worker's end event has neither.
|
|
272
|
+
|
|
198
273
|
A `turn_completed` frame omits `result`; `turn_summary.output` carries the answer:
|
|
199
274
|
|
|
200
275
|
```
|
|
@@ -256,7 +331,9 @@ The Bridge exposes:
|
|
|
256
331
|
deletes it, and the Bridge announces `:turn_dequeued`. `200 {status: "removed", enqueued_id, prompt}`, `409 already_started`
|
|
257
332
|
(a turn took it), `409 not_queued` (a next-step message: a drain may merge it any moment), `404
|
|
258
333
|
unknown_enqueued_id`. It never wakes the worker. The worker names it in its sidecar features (`unqueue`).
|
|
259
|
-
A worker's start prunes the `input/removed/` and `input/settled/` tombstones
|
|
334
|
+
A worker's start prunes the `input/removed/` and `input/settled/` tombstones and the drain's merge records
|
|
335
|
+
(`input/merged/<enqueued_id>` = `{into, at}`, written by `Worker#pending_input_drain` before it deletes a file it
|
|
336
|
+
merges; their own directory, which a 0.156 waiter never reads) older than a week
|
|
260
337
|
(`SessionInbox::TOMBSTONE_MAX_AGE`, log `tombstones_pruned`); otherwise they go with the session dir.
|
|
261
338
|
- `POST /session/:id/relay` — the approval relay, on a delegated child's worker
|
|
262
339
|
(`{action, relay_id, question_id}`): `opened` / `closed` (with a `reason`) set or clear the
|