samagotchi 0.154.0 → 0.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/docs/architecture.md +3 -2
  4. data/docs/bundles.md +21 -0
  5. data/docs/cli.md +10 -8
  6. data/docs/guardrails.md +2 -2
  7. data/docs/hooks.md +1 -1
  8. data/docs/internals/anthropic.md +4 -4
  9. data/docs/internals/attached-context.md +8 -6
  10. data/docs/internals/block-renderers.md +8 -1
  11. data/docs/internals/bridge.md +18 -5
  12. data/docs/internals/engine.md +221 -100
  13. data/docs/internals/inline-widgets.md +136 -0
  14. data/docs/internals/objectives.md +8 -6
  15. data/docs/internals/participants.md +5 -5
  16. data/docs/internals/prompt-caching.md +12 -6
  17. data/docs/internals/web-frontend.md +2 -2
  18. data/docs/internals/working-set.md +2 -1
  19. data/docs/plugins.md +2 -0
  20. data/docs/releasing.md +1 -1
  21. data/docs/sessions.md +3 -3
  22. data/docs/testing.md +16 -1
  23. data/lib/samagotchi/answer_text.rb +1 -2
  24. data/lib/samagotchi/block_renderers.rb +5 -0
  25. data/lib/samagotchi/bridge/card_places.rb +56 -7
  26. data/lib/samagotchi/bridge/card_store.rb +24 -9
  27. data/lib/samagotchi/bridge.rb +9 -0
  28. data/lib/samagotchi/bridge_client.rb +9 -5
  29. data/lib/samagotchi/bundle_command/check.rb +1 -1
  30. data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
  31. data/lib/samagotchi/bundles/btw/manifest.yml +2 -2
  32. data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
  33. data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
  34. data/lib/samagotchi/bundles/coordinator/plugin.rb +3 -2
  35. data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
  36. data/lib/samagotchi/bundles/github-pr/manifest.yml +2 -2
  37. data/lib/samagotchi/bundles/github-pr/plugin.rb +3 -3
  38. data/lib/samagotchi/bundles/inline-widgets/manifest.yml +11 -0
  39. data/lib/samagotchi/bundles/inline-widgets/plugin.rb +76 -0
  40. data/lib/samagotchi/bundles/skills/manifest.yml +2 -2
  41. data/lib/samagotchi/bundles/skills/plugin.rb +3 -2
  42. data/lib/samagotchi/bundles/system/manifest.yml +1 -1
  43. data/lib/samagotchi/desktop/macos/ChiRunner.swift +1 -1
  44. data/lib/samagotchi/engine/context_judge.rb +19 -125
  45. data/lib/samagotchi/engine/guardrails.rb +4 -2
  46. data/lib/samagotchi/engine/messages.rb +9 -6
  47. data/lib/samagotchi/engine/objectives.rb +22 -90
  48. data/lib/samagotchi/engine/participants.rb +15 -10
  49. data/lib/samagotchi/engine/plugins.rb +3 -1
  50. data/lib/samagotchi/engine/questions.rb +1 -1
  51. data/lib/samagotchi/engine/side_requests.rb +5 -2
  52. data/lib/samagotchi/engine/toolkit.rb +33 -190
  53. data/lib/samagotchi/engine/turn_end.rb +1 -1
  54. data/lib/samagotchi/engine/turn_helpers.rb +16 -39
  55. data/lib/samagotchi/engine/verify.rb +7 -143
  56. data/lib/samagotchi/engine.rb +162 -99
  57. data/lib/samagotchi/engine_peers.rb +1 -1
  58. data/lib/samagotchi/engine_snapshots.rb +9 -8
  59. data/lib/samagotchi/event_fanout.rb +18 -12
  60. data/lib/samagotchi/guardrail_wiring.rb +83 -82
  61. data/lib/samagotchi/guardrails/gate.rb +41 -40
  62. data/lib/samagotchi/guardrails/pair_boundary.rb +2 -1
  63. data/lib/samagotchi/guardrails/review_boundary.rb +2 -1
  64. data/lib/samagotchi/guardrails/targets.rb +6 -4
  65. data/lib/samagotchi/guardrails_command.rb +1 -1
  66. data/lib/samagotchi/idle_recap.rb +3 -1
  67. data/lib/samagotchi/inline_widgets.rb +325 -0
  68. data/lib/samagotchi/kernel_loop.rb +194 -216
  69. data/lib/samagotchi/llm/backend.rb +3 -1
  70. data/lib/samagotchi/llm/chat_loop.rb +66 -42
  71. data/lib/samagotchi/llm/native_backend.rb +3 -2
  72. data/lib/samagotchi/llm_context_forget.rb +5 -3
  73. data/lib/samagotchi/model_binding.rb +3 -3
  74. data/lib/samagotchi/plugin/prompt_sections.rb +7 -0
  75. data/lib/samagotchi/question_desk.rb +1 -1
  76. data/lib/samagotchi/revise_anchor.rb +1 -1
  77. data/lib/samagotchi/session_manager.rb +8 -13
  78. data/lib/samagotchi/session_retention.rb +24 -4
  79. data/lib/samagotchi/steer.rb +12 -1
  80. data/lib/samagotchi/stream_event.rb +22 -0
  81. data/lib/samagotchi/system_prompt.rb +84 -61
  82. data/lib/samagotchi/terminal_ui/attached_view.rb +0 -9
  83. data/lib/samagotchi/terminal_ui/input_support.rb +16 -15
  84. data/lib/samagotchi/terminal_ui/join_replay.rb +16 -12
  85. data/lib/samagotchi/terminal_ui/reline_seam.rb +0 -10
  86. data/lib/samagotchi/terminal_ui/status_row.rb +0 -7
  87. data/lib/samagotchi/testing/boundary.rb +7 -0
  88. data/lib/samagotchi/testing/bundle.rb +7 -5
  89. data/lib/samagotchi/testing/install.rb +3 -2
  90. data/lib/samagotchi/tool_activity.rb +8 -6
  91. data/lib/samagotchi/tool_declarations.rb +4 -4
  92. data/lib/samagotchi/tool_response.rb +4 -3
  93. data/lib/samagotchi/tool_runner.rb +93 -101
  94. data/lib/samagotchi/toolkit_binding.rb +40 -14
  95. data/lib/samagotchi/tools/deferred_tools.rb +17 -21
  96. data/lib/samagotchi/tools/registry.rb +43 -40
  97. data/lib/samagotchi/tools/tool_index.rb +135 -88
  98. data/lib/samagotchi/tools/tool_surface.rb +173 -0
  99. data/lib/samagotchi/turn_context.rb +67 -0
  100. data/lib/samagotchi/turn_flow.rb +7 -2
  101. data/lib/samagotchi/turn_stages/context_judge.rb +170 -0
  102. data/lib/samagotchi/turn_stages/objectives.rb +146 -0
  103. data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +261 -0
  104. data/lib/samagotchi/turn_stages/verify_anchors.rb +169 -0
  105. data/lib/samagotchi/turn_stages/warmup.rb +83 -0
  106. data/lib/samagotchi/turn_state.rb +46 -4
  107. data/lib/samagotchi/version.rb +1 -1
  108. data/lib/samagotchi/web/app.rb +3 -1
  109. data/lib/samagotchi/web/markdown_renderer.rb +39 -14
  110. data/lib/samagotchi/web/message_display.rb +8 -1
  111. data/lib/samagotchi/web/public/app.js +5 -1
  112. data/lib/samagotchi/web/public/block_host.js +74 -199
  113. data/lib/samagotchi/web/public/block_renderers.css +2 -0
  114. data/lib/samagotchi/web/public/block_source.js +7 -4
  115. data/lib/samagotchi/web/public/block_watch.js +256 -0
  116. data/lib/samagotchi/web/public/card_stamps.js +27 -0
  117. data/lib/samagotchi/web/public/cards_place.js +5 -3
  118. data/lib/samagotchi/web/public/composer_fold.js +3 -0
  119. data/lib/samagotchi/web/public/composer_mode.js +1 -1
  120. data/lib/samagotchi/web/public/format.js +3 -2
  121. data/lib/samagotchi/web/public/index.html +28 -9
  122. data/lib/samagotchi/web/public/inline_widgets.js +146 -0
  123. data/lib/samagotchi/web/public/owner_verify.js +1 -1
  124. data/lib/samagotchi/web/public/participant.js +1 -1
  125. data/lib/samagotchi/web/public/revise_stack.js +1 -1
  126. data/lib/samagotchi/web/public/stage_watch.js +6 -2
  127. data/lib/samagotchi/web/public/text_roots.js +1 -1
  128. data/lib/samagotchi/web/public/turn_html.js +8 -4
  129. data/lib/samagotchi/web/public/verify_target.js +1 -1
  130. data/lib/samagotchi/web/public/widget_picks.js +130 -0
  131. data/lib/samagotchi/web/public/widget_walk.js +230 -0
  132. data/lib/samagotchi/web/public/widgets.css +70 -0
  133. data/lib/samagotchi/web/session_access_routes.rb +8 -0
  134. data/lib/samagotchi/web/session_show.rb +4 -0
  135. data/lib/samagotchi/worker.rb +8 -5
  136. data/lib/samagotchi.rb +0 -1
  137. metadata +19 -2
  138. data/lib/samagotchi/pending_input_queue.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43fce920e24b60bc0bb9b5211dbc894f272e4f82ea55e6568698b533083f0c66
4
- data.tar.gz: 5af718499f366587969cef9318c16e271fa343a69362f1ba5ed227992d2ab4d8
3
+ metadata.gz: 55c4e338780fd58cf97835620377051e5de8efc5c487f42bdcc9de6c3a71a5b1
4
+ data.tar.gz: d6f3a9f49b39b72b11a6b70c5715142ee2f54d5e6b1f94a4eb9695f3fedefd0a
5
5
  SHA512:
6
- metadata.gz: 5f3a52420f92d4335f0c156cd208b5b40d8b952a692861d2ca0e6672caea3d66bb033f6e04f0c3288fe6415ae48c63d8489ef8be6cd24c57086861b3117c7746
7
- data.tar.gz: d9a8a9c3cc79549bf476f43900669ac68e212ed419c76ec362b8e9483d937eb70537f00b9d63a3e904417205dd42db069662e13127ee3cadf710b18bdc4fa058
6
+ metadata.gz: c91dd76d8d7a3bb4cc3c4432162ac91528aa8329585cc373ad6c811c68be9ff0ccf47ed12913ce19bb87edd59930ae699cf19fa5cc8128eb92f5f62cecc6624d
7
+ data.tar.gz: 99dd1a2761d327300d3459407c09935aa4d8f3d2826e7db5a29087013071998f0fb1e6787d520a8d19c27b0a5ec67bf64b53aa177d17fe9a003cf3dc8190aa84
data/CHANGELOG.md CHANGED
@@ -8,6 +8,33 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.155.0] - 2026-11-07
12
+
13
+ ### Added
14
+
15
+ - The inline-widgets bundle (in the `dev` profile, `chi update`): it teaches the model the `chi-ui` blocks chi web draws, so an answer that leaves you several things to decide comes with picks that fill your reply, and a "how does this work" answer with a walk you step through. The terminal shows the block's YAML; a delegate child and a `-p --non-interactive` run don't get it.
16
+ - `chi bundle check` lists a bundle's prompt sections among what it registers.
17
+
18
+ ### Fixed
19
+
20
+ - At the very start of a turn, a plugin's `ctx.turn_author`, a hook's `author:` and `/objectives` could still see the previous turn (its participant, its conversation); they now see the turn that just started.
21
+ - chi web's session bar no longer slides its time and speed chips under the buttons with a long model name: the model name shortens, the buttons move to their own line together, and on a narrow window the ctx/speed/time chips get a line of their own instead of being cut off.
22
+
23
+ Update with `chi update` (the dev profile 0.7.8, inline-widgets 0.1.0 new).
24
+
25
+ ## [0.154.1] - 2026-11-07
26
+
27
+ ### Fixed
28
+
29
+ - chi web refuses a scratch session's page and stream (409 `scratch`, in the words every other client gets) when a tab is opened at its id by hand, and a scratch worker counts only its terminal's stream: such a tab no longer keeps the worker up after its terminal is killed.
30
+ - A card in chi web comes back where it was after a reload: the page finds the turn it came after (or `/continue no`'s reason line) by its id instead of counting the turns drawn after it, so the page and the worker can no longer disagree on the count.
31
+ - A Qwen model's turn that was asked to finish an open tool call no longer splits in two after a reload of chi web: the request to finish it is part of the turn, drawn as nothing, as it was live.
32
+ - chi web starting just as `chi scratch` did no longer deletes the new scratch session before its worker takes it (the worker crashed with "Session not found" and the terminal said the Bridge did not start in time): the retention sweep, `chi sessions prune` and `clean` leave an unowned scratch session alone for 30 s after its last save.
33
+ - A terminal joining a session (`chi --attach`, `--resume`) shows the last turn's own prompt instead of a Qwen model's request to finish an open tool call, with the input merged into that turn on its `input>` lines; the idle recap no longer counts either as a turn of its own.
34
+ - A turn no longer fails when a plugin changes its tools or prompt section (or a profile change drops the system prompt) at the moment the system prompt is built.
35
+
36
+ Update with `chi update` (btw 0.1.5, coordinator 0.8.1, github-pr 0.6.4, skills 0.1.10: comments only; the desktop helper rebuilds once).
37
+
11
38
  ## [0.154.0] - 2026-11-07
12
39
 
13
40
  ### Changed
@@ -5759,7 +5786,9 @@ and long-lived sessions.
5759
5786
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
5760
5787
  hotkey panel that send selected text or the clipboard to your sessions.
5761
5788
 
5762
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.154.0...HEAD
5789
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.155.0...HEAD
5790
+ [0.155.0]: https://github.com/dm1try/samagotchi/compare/v0.154.1...v0.155.0
5791
+ [0.154.1]: https://github.com/dm1try/samagotchi/compare/v0.154.0...v0.154.1
5763
5792
  [0.154.0]: https://github.com/dm1try/samagotchi/compare/v0.153.0...v0.154.0
5764
5793
  [0.153.0]: https://github.com/dm1try/samagotchi/compare/v0.152.0...v0.153.0
5765
5794
  [0.152.0]: https://github.com/dm1try/samagotchi/compare/v0.151.0...v0.152.0
data/docs/architecture.md CHANGED
@@ -101,6 +101,7 @@ terminal renders the events it gets over the Bridge.
101
101
  | Core | `Samagotchi::Engine` | System prompt, memory injection, tool declarations, session lifecycle, model↔tool loop (`run_turn`). No terminal coupling. |
102
102
  | UI | `Samagotchi::TerminalUI`, `TerminalUI::AttachLauncher` / `AttachedLoop` | `TerminalUI`: the `--non-interactive` one-shot host, its own `Engine`. The attached TUI (every interactive `chi`): a client of the session worker's Bridge, no `Engine`. |
103
103
  | Model loops | `KernelLoop` (via `LLM::NativeBackend`), `LLM::ChatLoop` | The model↔tool loop: raw prompt, or a chat API (OpenAI chat, Anthropic Messages), chosen per host (see below). |
104
+ | Turn | `TurnContext`, `TurnState`, `Engine::Turn` | One turn's frozen `TurnContext` (id, session, origin, author, participant mode, controller, folders, git facts, settings, model key/name and the tool surface (`Tools::ToolSurface`) from `generate`): built by `Engine#begin_turn`, published with the running flag by `TurnState` (the only way another thread reads the running turn: `Engine#turn_context` / `#last_turn_context`), handed explicitly down the tool path (`ModelBackend#complete(context:)`, `ToolRunner#run`, `Guardrails::Gate#evaluate`, `KernelLoop#dispatch_tool_call`). `Engine::Turn` keeps only the lifecycle state ([internals/engine.md](internals/engine.md), "Turn and TurnContext"). The turn stages (`TurnStages::ToolkitSuggestion`, `TurnStages::ContextJudge`, `TurnStages::Objectives`, `TurnStages::Warmup`, `TurnStages::VerifyAnchors`, `lib/samagotchi/turn_stages/`): the steps that keep state across the turn, built from constructor arguments and called by name from the turn's skeleton (internals/engine.md, "Turn stages"). |
104
105
  | Adapters | `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | Raw-prompt servers, the OpenAI chat API, Anthropic's Messages API, and the HTTP they share. |
105
106
  | Tools | `lib/samagotchi/tools/*` | Execute, read, edit, write, memory, task_*, web_fetch, delegate/delegate_result, ask_user_question, reminders, send_note, list_sessions, context_read, objectives, forget_outputs, find_tools/call_tool (the tool index), plus runtime/output-guardrails. |
106
107
  | Bundles | `lib/samagotchi/memory_bundle/*`, `BundleCommand` | `chi bundle`: install/upgrade (3-way, with a text merge of edited memories), build, uninstall; the install record under `.bundles/<name>/` that the hook, guardrail and plugin loaders read ([internals/bundles.md](internals/bundles.md)). |
@@ -519,7 +520,7 @@ on every read, never kept, since config.yml and a model listing can move it with
519
520
 
520
521
  Both return an `LLM::ModelResult` and emit the same stream events; tool calls in
521
522
  both go through `ToolRunner` (events, hooks, veto, output cap) and
522
- `KernelLoop#dispatch_tool_call`. Every format's built-in calls (Gemma, Qwen,
523
+ `KernelLoop#dispatch_tool_call`, given the turn's `TurnContext` as an argument. Every format's built-in calls (Gemma, Qwen,
523
524
  chat `tool_calls`) are built by one table, `Tools::BuiltinCalls`, from the
524
525
  parsed `{name, args}` (derived from `TOOL_SCHEMAS`), so a call looks the same to
525
526
  hooks and tools whichever model made it. Both loops' `generation_chunk` carries
@@ -636,7 +637,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
636
637
  | Participants | `participants.rb` (`Participants`, the `participants.json` sidecar), `share_command.rb` (`ShareCommand`, `chi share`), `session_participants.rb`, `participants_watch.rb`, `grants.rb` (`Grants`), `participant_exposure.rb` (`ParticipantExposure`), `web/principal.rb` (`Web::Principal`), `web/participant_access.rb` (`Web::ParticipantAccess`: the participant's gate and routes), `web/participants_admin.rb` (`Web::ParticipantsAdmin`: the owner's invite, list, revoke, cap and Apply routes), `web/participant_review.rb` + `web/participant_view.rb` (`Web::ParticipantReview`, `Web::ParticipantView`), `redact.rb` (`Redact`, `Redact::Viewer`: the redaction every string a participant gets passes, also the Engine's Verify quote check), `web/participant_stream_filter.rb`, `guardrails/review_boundary.rb` + `guardrails/review_paths.rb` | The people or agents an owner invites to review one session through chi web: their grants and gate, what they read, their read-only review turns ([internals/participants.md](internals/participants.md), [guardrails.md](guardrails.md)). |
637
638
  | Analytics | `analytics_file.rb` (`AnalyticsFile`), `session_metrics/totals.rb` (`SessionMetrics::Totals`), `session_metrics/model_segment.rb` (`SessionMetrics::ModelSegment`) | A session's `analytics.json` (`SessionMetrics#persist`); the sums over its turn records (`Totals`: tokens, cost, speeds, kept as running counters) and their split per model after a `/model` switch (`ModelSegment`: a run of turns on one model, the `model_segments` rows); `session_metrics/cost.rb` (`SessionMetrics::Cost`): the spend `ctx.cost` reads; `session_metrics/usage.rb` (`SessionMetrics::Usage`): the work `ctx.usage` reads. `session_metrics/side_totals.rb` (`SessionMetrics::SideTotals`) and `side_usage.rb` (`SideUsage`): the side requests' sums, apart from the turns'. |
638
639
  | Images / vision | `image_store.rb` (`ImageStore`), `vision_support.rb`, `vision_context.rb` | Stored prompt images and whether a model takes them. |
639
- | Toolkits | `toolkit.rb` (`Toolkit`), `toolkit_binding.rb` (`ToolkitBinding`, the Engine's seam), `toolkit_command.rb` (`/toolkit`), `tools/registry.rb` (`Registry#toolkit`, `#left_out_names`), `web/public/toolkit_chip.js` | Which registered tools a session's prompts declare: saved in the session, resolved at a turn's start (`Engine#apply_toolkit!`), named in the prompt's not-loaded line, a kit's prompt text after the appended files; `chi.toolkit` and `ctx.use_toolkit` for plugins ([configuration.md](configuration.md#toolkits)). |
640
+ | Toolkits | `toolkit.rb` (`Toolkit`), `toolkit_binding.rb` (`ToolkitBinding`, the Engine's seam), `toolkit_command.rb` (`/toolkit`), `tools/registry.rb` (`Registry#toolkit`), `tools/tool_surface.rb` (`ToolSurface#left_out`, read by the prompt and `/toolkit`), `web/public/toolkit_chip.js` | Which registered tools a session's prompts declare: saved in the session, resolved at a turn's start (`Engine#apply_toolkit!`), named in the prompt's not-loaded line, a kit's prompt text after the appended files; `chi.toolkit` and `ctx.use_toolkit` for plugins ([configuration.md](configuration.md#toolkits)). |
640
641
  | Tool index | `tools/tool_index.rb` (`Tools::ToolIndex`), `tools/deferred_tools.rb` | `find_tools` and `call_tool`: the tools a session knows but doesn't declare one by one (an mcp bundle's servers through `chi.tool_source`, the built-ins its toolkit leaves out), searched and called by name ([plugins.md](plugins.md)). On `api: anthropic` they also go out deferred, a hit loads its matches and the model calls them by name ([internals/anthropic.md](internals/anthropic.md)). |
641
642
  | Tool output cut | `tool_output_cut.rb` (`ToolOutputCut`), `tool_output_files.rb` (`ToolOutputFiles`) | A tool's output over `max_tool_output_chars` (a `memory_read`'s: `memory.read_max_chars`, 0 = none, plus a guard at half a known window), cut by the tool's policy; an execute's whole output saved in the session's `tool_output/` folder ([internals/tool-output-limits.md](internals/tool-output-limits.md)). |
642
643
  | Steer delivery | `steer.rb` (`Steer`), `delivery.rb` (`Delivery`), `steer_cut.rb` (`SteerCut`), `generation_phase.rb`, `waiting_steer.rb`, `steer_cut_after.rb` (`SteerCutAfter`), `cut_policy.rb` (`CutPolicy`) | A message for a running turn: at the next boundary, cutting a thinking-only generation, or queued for the turn's end ([internals/engine.md](internals/engine.md)). |
data/docs/bundles.md CHANGED
@@ -1358,3 +1358,24 @@ bundles:
1358
1358
  timeout: 120 # seconds the side request may take (a number)
1359
1359
  max_tokens: 300 # the side answer's limit
1360
1360
  ```
1361
+
1362
+ ## The inline-widgets bundle
1363
+
1364
+ `chi bundle install inline-widgets` (or the `dev` profile) installs the bundle shipped with chi. It is written only against
1365
+ this API (`lib/samagotchi/bundles/inline-widgets/plugin.rb`) and has no settings, commands or memories: it adds two
1366
+ [prompt sections](plugins.md#chiprompt_sectionname--ctx---and-chiprompt_changed) that tell the model chi web draws
1367
+ ```` ```chi-ui ```` blocks ([the blocks](internals/inline-widgets.md)).
1368
+
1369
+ - **picks**: when an answer leaves you two or more things to decide (review findings, a plan's open questions), a row
1370
+ per item with its options as buttons, the recommended one picked to start with, a note per row, and **Fill reply**,
1371
+ which puts your picks in the composer. Nothing is sent until you send it.
1372
+ - **walk**: a scenario you step through: who does what, what you see, what happens behind it, with a fork's cases to
1373
+ choose from.
1374
+ - **Where it draws**: chi web, in the answers. The terminal shows the block's YAML, and a block chi can't read stays
1375
+ YAML with a short note under it.
1376
+ - **Where the prompt has none**: a delegate child (its parent reads the report, nobody sees it drawn) and a one-shot
1377
+ `chi -p … --non-interactive`. `/stats` lists the sections a session's prompt holds: `picks (inline-widgets), walk
1378
+ (inline-widgets)`.
1379
+ - **Cost**: about 2,900 characters in the stable part of the prompt, the same at every build, so the prompt cache keeps.
1380
+ - **Uninstall**: `chi bundle uninstall inline-widgets` takes the sections out of new sessions' prompts (a session you
1381
+ have open needs `chi sessions restart ID`); blocks already in a history still draw, since the drawing is chi web's own.
data/docs/cli.md CHANGED
@@ -158,7 +158,7 @@ The loop for a bundle you're writing (the whole path: [bundle-authoring.md](bund
158
158
  bundle chi doesn't ship), `clash` (warn: a name chi ships, or one installed from elsewhere; the sandboxed install
159
159
  then forces), `install` (each Installer warning, a `needs` one as warn, and each plugin or hook load failure at
160
160
  the source `file:line`; skipped while a placeholder sha is unfilled), `registered` (info: each command, tool,
161
- hook with its priority, init, toolkit, tool source and peer source), `hook-event` (warn: a `chi.on` event chi
161
+ hook with its priority, init, toolkit, tool source, peer source and prompt section), `hook-event` (warn: a `chi.on` event chi
162
162
  never fires), `settings` (warn: a `--set` the manifest's `settings:` rules out, as config.yml's `bundles:` is told), `api` (warn: a `ctx.*` / `chi.*` call or a keyword this chi's API lacks, read from the source with
163
163
  Prism against the API's own classes, on a path that never runs too; written on a local `ctx` / `chi` itself, so a
164
164
  `s = ctx.sessions` variable isn't followed; a name the plugin guards with `respond_to?` is left), `boundary` (warn: a `Samagotchi::` name outside "Stable for bundles"; a shipped bundle's listed
@@ -460,7 +460,7 @@ invalid option (exit 1).
460
460
  | `--llm-context-budget TOKENS` | This session's own context budget: `64000`, `64k` (4k to 10M), or `off` (wins over the model's budget). |
461
461
  | `--no-interrupt` | Raise the tool-call limit to 1000 iterations for long tasks. |
462
462
  | `--no-default-input` | Skip the default input (`SAMAGOTCHI_DEFAULT_INPUT`) typed into a new session's first prompt. |
463
- | `-v`, `--verbose` | Log at debug level (raw LLM responses, tool call/result payloads). Attached, the terminal shows this session's debug records above the prompt as they come, and a worker this chi starts logs at debug too; a worker that was already running keeps its own level (a one-line note says so). With `--non-interactive`, every log record goes to stderr too. This machine only (exit 2 with `--remote`). |
463
+ | `-v`, `--verbose` | Log at debug level (raw LLM responses, tool call/result payloads). Attached, the terminal shows this session's debug records above the prompt as they come, and a worker this chi starts logs at debug too; a worker that was already running keeps its own level (a one-line note says so). `chi web -v` logs at debug too (its `/api/` requests among them) and so does every worker it starts from then on (a new session, a wake, a restart: they all take `SAMAGOTCHI_LOG_LEVEL=debug`, `Config.cli_env`), into the same log; a worker already running keeps its level. With `--non-interactive`, every log record goes to stderr too. This machine only (exit 2 with `--remote`). |
464
464
  | `--version` | Print `chi <version>` and exit (`chi self` shows it with the paths). |
465
465
 
466
466
  Every setting in the config registry (`lib/samagotchi/config.rb`) that exposes a CLI
@@ -537,13 +537,15 @@ refused. Its first line says it is a scratch session.
537
537
  session (`Scratch session deleted.`). `/archive` is refused. The lines typed
538
538
  are not added to the prompt history. With `-p … --non-interactive` it is one
539
539
  turn in this process, and the session is deleted as the run ends.
540
- - The worker deletes the session too once its terminal is gone (no stream
541
- left for about 25 s, twice the terminal's reconnect retries, so a dropped
542
- stream that reconnects keeps it; or none came within 30 s), so a closed
543
- terminal window or a `kill -9` of the terminal leaves nothing: a turn still
544
- running then is cancelled first. It refuses `chi sessions restart`.
540
+ - The worker deletes the session too once its terminal is gone (no terminal
541
+ stream left for about 28 s, twice the terminal's reconnect retries, so a
542
+ dropped stream that reconnects keeps it; or none came within 30 s), so a
543
+ closed terminal window or a `kill -9` of the terminal leaves nothing: a turn
544
+ still running then is cancelled first. Another client's stream never keeps
545
+ it. It refuses `chi sessions restart`.
545
546
  - It never shows in `chi web`, and only its terminal uses it: `chi send`, the
546
- web's routes, `chi --resume` and `--attach` refuse it (exit 1), in one
547
+ web's routes (a tab opened at its id too: its page and stream answer 409
548
+ `scratch`), `chi --resume` and `--attach` refuse it (exit 1), in one
547
549
  wording: "that's a chi scratch session: only the terminal that started it
548
550
  uses it (a leftover one is deleted at the next sweep: chi sessions clean)".
549
551
  A session left behind when both died is marked `"scratch": true` in its
data/docs/guardrails.md CHANGED
@@ -410,7 +410,7 @@ under the owner's guardrails, as the owner's own turns do: the rules, the
410
410
  protected paths and every approval the owner already granted apply, and an ask
411
411
  goes to the owner. It is a pair turn only when all three hold
412
412
  (`ParticipantMode.for_turn`, decided once at the turn's start:
413
- `GuardrailWiring#turn_mode`): the session has no `review:` key, its worker
413
+ `GuardrailWiring#begin_turn`, into the turn's `TurnContext#participant_mode`): the session has no `review:` key, its worker
414
414
  names the `pair_turns` feature, and the participant's grant was issued with
415
415
  `mode: pair`, which chi share and the web's Invite stamp only when the warning
416
416
  they showed said their turns act. A grant from before, or one issued while an
@@ -690,7 +690,7 @@ runs with the owner's rules or System 1's auto-allows. The mode is decided once,
690
690
  at the start, from their grant's mode there (`ParticipantMode.live_for`) and
691
691
  saved on the child (`verify_fresh.mode`): `review` keeps every turn of it
692
692
  inside `ReviewBoundary` (read only), `pair` inside `PairBoundary`.
693
- `GuardrailWiring#turn_mode` reads the saved mode and never looks it up again,
693
+ `GuardrailWiring#begin_turn` reads the saved mode and never looks it up again,
694
694
  and every turn of the child carries the participant as its author, whoever
695
695
  started it, so the boundary can't fall away for a turn the owner typed into it
696
696
  or for an author lookup that finds nobody (the child has no
data/docs/hooks.md CHANGED
@@ -711,7 +711,7 @@ Notes:
711
711
  - Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order.
712
712
  - Settings: a hook class with `initialize(settings = {})` gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
713
713
  - A bundle can also ship a `plugin.rb` whose `chi.on(event)` blocks are bundle hooks too, next to commands and tools; see [Plugins](plugins.md). There `ctx.notify`, `ctx.steer` and the other `ctx` helpers act as this page's `event[:notify]`… do for the event the block runs for.
714
- - 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 hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces 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)) 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)).
714
+ - 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 hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces 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)).
715
715
  - Installing a bundle executes its hook code at `Engine` startup. Only install bundles you trust, as you would a gem. Hooks are **not** executed at install time (copy-only); they are `module_eval`'d at `Engine.new` inside per-bundle `Samagotchi::Bundles::<name>` namespaces (no top-level `require` collisions). Keep hook files side-effect-free at load time; do work in `#call` — top-level side effects (require, IO, `at_exit`, global assignment) run once per `Engine.new` (class redefinition is idempotent).
716
716
 
717
717
  Lifecycle:
@@ -36,7 +36,7 @@ Paths are under `lib/samagotchi/llm/`.
36
36
  | `anthropic_wire.rb` | `AnthropicWire`: ChatLoop's wire messages as Messages' `system` and `messages`, the two breakpoints, tool ids and names |
37
37
  | `anthropic_assembly.rb` | `AnthropicAssembly`: a response from a whole body (`.from_message`) or stream events (`#add`), its usage in the OpenAI shape |
38
38
  | `dialect.rb` | `Dialect` (`STANDARD`, `FIREWORKS`): what a provider's `/v1/messages` does differently |
39
- | `../tools/deferred_tools.rb` | `Tools::DeferredTools.list`: the tools a request declares deferred (Deferred tools) |
39
+ | `../tools/deferred_tools.rb` | `Tools::DeferredTools.list`: the tools a request declares deferred, from the turn's `ToolSurface` (Deferred tools) |
40
40
  | `prompt_cache.rb` | `PromptCache.control`: the `cache_control` a breakpoint carries (`cache.ttl`) |
41
41
  | `chat_loop.rb` | `ChatLoop#replay_blocks`, `#reasoning_history`'s floor, `Run#with_thinking` (saves `anthropic_content`), `#settle_dropped_thinking!` |
42
42
 
@@ -222,10 +222,10 @@ ones: the model sees their names only when something loads them, and the API acc
222
222
  `AnthropicMessages#loads_tools?(model)` says whether a request does this: the dialect's `tool_loading`, unless the
223
223
  host refused it for that model.
224
224
 
225
- `ChatLoop#tool_definitions(conversation, model:)` appends them (`Tools::DeferredTools.list`) only on such an
225
+ `ChatLoop#tool_definitions(conversation, model:)` appends them (`ToolSurface#deferred_for`) only on such an
226
226
  adapter, and only with at least one tool declared (Messages wants one). The list, per request:
227
227
 
228
- 1. the built-ins and plugin tools the toolkit reaches (`Registry#reachable_names`), in registry order;
228
+ 1. the built-ins and plugin tools the toolkit reaches (`ToolSurface#index_only`), in registry order;
229
229
  2. the tool index's source tools while `call_tool` is declared (`ToolIndex#deferrable`, via
230
230
  `Registry::Index#deferrable`): callable, the only tool with its acts_as, a name Messages takes, no registered
231
231
  tool's name; by acts_as (`mcp_<server>_<tool>`), sorted by name;
@@ -270,7 +270,7 @@ sets `:tool_changes` for the model: the retry and every later request drop the s
270
270
 
271
271
  A direct call of a deferred tool runs whether something loaded it or not (the API doesn't check): the Engine sets
272
272
  `TurnSettings#loads_tools` from the turn's final model (after the before_turn hooks), and then `ToolRunner#run` and
273
- `KernelLoop#dispatch` take a reachable built-in or plugin tool by its own name (`Registry#indexed`) through the normal gate, and wrap
273
+ `KernelLoop#dispatch` take a reachable built-in or plugin tool by its own name (`ToolSurface#entry_for`) through the normal gate, and wrap
274
274
  an MCP tool's acts_as into the `call_tool` call it stands for (`ToolIndex#wrap`): `call_tool`'s row, rules and
275
275
  approvals. A reload rebuilds that wrapped call from the saved one (`BuiltinCalls.wrap_saved`). Each such call is
276
276
  logged (`tool_direct_deferred`, with `loaded:` whether the conversation had loaded it). On `api: openai` and the
@@ -144,8 +144,10 @@ source's why. `task_p < TASK_MIN` (0.5) leaves the candidate unjudged; `due?` de
144
144
  lines (never twice at one owner-prompt count; one re-ask after `reask_after_turns`; then done). No answer writes
145
145
  nothing, so the next owner prompt asks again.
146
146
 
147
- The driver is the Engine, not the judge: `Engine#start_context_judge` (an owner turn's start; skipped for participants,
148
- review sessions and a Continue) and `judge_late_candidates` (a proposal after the prompts) call `context_judge_pass`.
147
+ The driver is the turn stage `TurnStages::ContextJudge` (`turn_stages/context_judge.rb`), not the judge:
148
+ `Engine#prepare_turn` calls its `start(turn)` after the toolkit suggestion settled and before `apply_toolkit!` (an owner
149
+ turn's start; skipped for participants, review sessions and a Continue), and `Engine#judge_late_candidates` (a proposal
150
+ after the prompts) forwards to its `late_candidates`; both make one pass (docs/internals/engine.md, "Turn stages").
149
151
  It first logs status changes (`ContextCandidatesLog.look`), returns at `mode: off`, builds the due `Job`s, and hands
150
152
  them to `SideJudgments` (`:context` kind: one thread, one batch per session at a time), asked at once on any host,
151
153
  the model's own server too.
@@ -153,10 +155,10 @@ the model's own server too.
153
155
  `ContextCandidateChoice.after_judged` acts on `fields["would"]` in `on` mode only: *attach* → `judge_attach` (a
154
156
  session source with `JUDGE_WHY`, no probability in the model-visible why), *offer* → one card (`offer_card`, buttons
155
157
  `/context attach NAME` and `/context decline NAME`, guarded by a `carded` log line). It re-checks that the candidate
156
- is still proposed, since the user may have chosen while it was asked. Cards are shown through the Engine's
157
- `context_choice` lambda, which holds them while a turn runs (`@context_cards_after_turn`). `choose` is the
158
- `/context attach|decline` command's body (`SessionCommands`), shared by the card's buttons; it refuses in a review
159
- session. An attach only writes the session folder: its note comes with the next between-turn delivery, never into a
158
+ is still proposed, since the user may have chosen while it was asked. Cards are shown through the stage's choice
159
+ lambda, which holds them while a turn runs; `run_turn`'s ensure shows them after `release_turn`
160
+ (`show_cards_after_turn`). `choose` is the `/context attach|decline` command's body (`SessionCommands`), shared by the
161
+ card's buttons; it refuses in a review session. An attach only writes the session folder: its note comes with the next between-turn delivery, never into a
160
162
  running turn.
161
163
 
162
164
  `ContextCandidatesLog` is `context_candidates.jsonl` beside the sessions folder (a `SideJudgments.log`: 0600, one
@@ -16,7 +16,14 @@ model. The TUI is untouched: it shows a fence as source.
16
16
  who draws what: `GET /api/block_renderers`, `:owner`. `GET /api/block_renderers/:bundle/:file` serves a file, `:owner`:
17
17
  the URL's names go through `BlockRenderers.script_path` (membership in a listed renderer's `load` before any path is
18
18
  made), then `Provenance.sha_matches?`; a changed file is 409 `renderer_changed`. ETag + `no-cache`, as static files.
19
- 3. `block_host.js` (`createBlockHost`, wired in `app.js`; `participant.js` isn't): one `MutationObserver` per turn root,
19
+ 3. `block_watch.js` (`createBlockWatch`, made by `block_host.js`'s `createBlockHost`, wired in `app.js`; `participant.js`
20
+ isn't) finds and watches the blocks; `block_host.js` is its frame drawer (`createFrameDrawer`). The watcher asks an
21
+ ordered list of drawers, core ones first (`inline_widgets.js` for `chi-ui`: [inline-widgets.md](inline-widgets.md)), then the frame drawer, which waits for `/api/block_renderers`;
22
+ the first that `claims` the block's language draws it, and a language none claims is left alone. The contract
23
+ (`claims`, `needsDraw`, `draw`, its `reconsider`, `sessionChanged`) is in the module's header. The shared DOM
24
+ helpers live there too: `failBlock` and its `.block-note` (a click copies it), `holdingView`, `rejoin`, the
25
+ toggle's listener; frames, the cache, hang marks, size checks, the zoom and the theme redraw stay in
26
+ `block_host.js`. One `MutationObserver` per turn root,
20
27
  filtered to `TEXTS` (`text_roots.js`), so cards, the asking slot, question replies and delegate reports never draw, and
21
28
  every path that adds a block to a root is seen the same way. One `IntersectionObserver` per scroller, with an explicit `root`
22
29
  (with `root: null`, `rootMargin` doesn't pass an `overflow: auto` in between). Closed `<details>` fire on open. **A
@@ -16,7 +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
- | `CardPlaces` | `card_places.rb` | Pure: where `CardStore`'s entries sit in the messages the page draws (`turns_since`). |
19
+ | `CardPlaces` | `card_places.rb` | Pure: where `CardStore`'s entries sit in the messages the page draws (`turns_since`, and the `opening_id` stamp). |
20
20
  | `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
21
 
22
22
  Per connection there is an `SSEWriter` (with a `BoundedQueue`); `ClientEvent` and `EventId` are pure helpers. The
@@ -57,7 +57,9 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
57
57
  the page's `TURN_PROMPT` and the recap count) after the last one whose id is in the log at or before the entry's, else from
58
58
  the first one whose id is later in the log, else 0. The log only orders; the messages count. So a turn with no opening (a
59
59
  reminder, a Continue, a failed or rolled-back one, a prompt stopped before the model) moves nothing, a wake's note does, and
60
- an opening with no id (`/continue no`'s reason, Qwen's recovery line) counts as on the page. Nothing orders such an opening
60
+ an opening with no turn id (`/continue no`'s reason, which carries an `opening_id` of its own instead, `TurnFlow#abort_continue!`)
61
+ counts as on the page. KernelLoop's Qwen recovery line (kind `recovery`, `Steer.recovery?`) is a row of its turn, no opening:
62
+ the page draws nothing for it (`MessageDisplay`), as live. Nothing orders such an opening
61
63
  against an entry, so the store reads the session's messages (its `messages:` lambda) when an entry is shown between turns, and
62
64
  at the turn's end for what a running turn showed that isn't its own: `past_idless` is how many of them, right after the opening
63
65
  it counts from, the entry already came after, and they don't count for it. A `!rollback` usually only shortens that run, and the count
@@ -66,7 +68,15 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
66
68
  anchored to T2, now counts from T1 and reads above the reason and the first card). That rare inversion is accepted. A turn's own rows don't read it: they count such a line, which sits below them. `current` is a card of the running turn. A card that
67
69
  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
68
70
  between-turns notice. A card with an earlier card's id replaces it in place and keeps its anchor. A `list` with no messages
69
- (`?cards=1`'s bodies, `chi bundle try`'s trace) has no `turns_since`.
71
+ (`?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
73
+ 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
+ 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
+ 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
76
+ bubbles. An entry shown after reason lines also saves which one it came after (`after_opening`, beside `past_idless`): read
77
+ 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
+ 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
+ keeps today's rule: a rolled-back turn's cards name the opening before it.
70
80
  - **A turn that leaves nothing** is read, not told: an in-turn entry not `current` whose turn has no opening in the messages
71
81
  (a `!rollback`, a failed turn with nothing kept, a prompt stopped before the model) lists as a between-turns entry at its
72
82
  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
@@ -84,7 +94,9 @@ the "asking again" and steer-cut rows, ✂ rows and questions (with how they wer
84
94
  is what chi web reads for a session no worker runs. The file is `{version: 2, turn_log:, entries:}`. The log is pruned at
85
95
  a turn's start and end and at an idle show (a running turn's rows reuse the start's, as finding it walks the messages) to start at the opening the earliest anchor counts from (all of it while an entry has none, or for a store with no
86
96
  `messages:`), so a restart reads an entry as the live store did; an old card therefore keeps it whole. `seed` drops an entry
87
- whose `turn_id`, `base_since` or `past_idless` isn't an id or a non-negative integer. Load warnings
97
+ whose `turn_id`, `after_opening`, `base_since` or `past_idless` isn't an id or a non-negative integer. A file 0.154.0 wrote
98
+ (no `after_opening`) lists the same stamps, from its counts; an older chi reading this one keeps `after_opening` (it seeds
99
+ entries whole) and lists it, unused. Load warnings
88
100
  are the worker's own (each start announces them) and an unanswered question waits for no one: neither is saved or seeded. An
89
101
  entry from an earlier worker lists with `earlier: true`; one shown during a turn comes back **without its actions** (their
90
102
  turn no longer runs), while a between-turns card keeps its buttons.
@@ -152,7 +164,8 @@ The Bridge exposes:
152
164
  `?from_seq=` (a plain `event_seq` is still accepted); a `: ping` heartbeat keeps idle proxies alive;
153
165
  too-old reconnects, and cursors from another worker's epoch, receive a `reset` marker carrying
154
166
  `session_state_snapshot`. `?snapshot=1` joins with a snapshot frame instead of a replay;
155
- `?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`).
167
+ `?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`; `Bridge#open_streams_of`,
168
+ by a scratch worker, which counts only `tui:` streams: a web tab's, through chi web, names none).
156
169
  Each event goes out as clients get it (`Bridge::ClientEvent.project`, in `SSEWriter#write_event`, live
157
170
  and replayed alike); `generation_chunk`, `tool_call_started` and `turn_completed` are projected to the
158
171
  contracts below. The ring retains raw events for replay and `?raw=1`.