samagotchi 0.141.0 → 0.143.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 +111 -7
- data/README.md +2 -1
- data/bin/chi +2 -0
- data/docs/architecture.md +25 -289
- data/docs/bundle-authoring.md +7 -6
- data/docs/bundles.md +1232 -0
- data/docs/cli.md +51 -13
- data/docs/configuration.md +68 -30
- data/docs/context.md +12 -12
- data/docs/guardrails.md +27 -20
- data/docs/hooks.md +28 -9
- data/docs/internals/attached-context.md +225 -0
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/bridge.md +380 -0
- data/docs/internals/bundles.md +6 -3
- data/docs/internals/context-telemetry.md +1 -1
- data/docs/internals/engine.md +31 -19
- data/docs/internals/participants.md +13 -12
- data/docs/internals/prompt-caching.md +12 -12
- data/docs/internals/remote-sessions.md +1 -1
- data/docs/internals/{tool-guardrails.md → tool-output-limits.md} +21 -2
- data/docs/internals/web-push.md +1 -1
- data/docs/memory.md +33 -3
- data/docs/plugins.md +127 -1231
- data/docs/sessions.md +51 -27
- data/docs/sub-agent.md +6 -6
- data/docs/system-one.md +122 -0
- data/docs/testing.md +3 -1
- data/lib/samagotchi/bridge.rb +2 -0
- data/lib/samagotchi/bundle_command/api_calls.rb +132 -0
- data/lib/samagotchi/bundle_command/check.rb +50 -34
- data/lib/samagotchi/bundle_command/installed_check.rb +86 -0
- data/lib/samagotchi/bundle_command/scaffold.rb +1 -0
- data/lib/samagotchi/bundle_settings_check.rb +58 -0
- data/lib/samagotchi/bundles/agents-md/manifest.yml +4 -2
- data/lib/samagotchi/bundles/agents-md/plugin.rb +1 -1
- data/lib/samagotchi/bundles/btw/manifest.yml +4 -2
- data/lib/samagotchi/bundles/btw/plugin.rb +1 -1
- data/lib/samagotchi/bundles/check-in/manifest.yml +7 -3
- data/lib/samagotchi/bundles/check-in/plugin.rb +101 -11
- data/lib/samagotchi/bundles/claude-peer/manifest.yml +3 -3
- data/lib/samagotchi/bundles/claude-peer/plugin.rb +1 -1
- data/lib/samagotchi/bundles/claude-peer/scripts/pair +1 -1
- data/lib/samagotchi/bundles/codex-peer/manifest.yml +2 -2
- data/lib/samagotchi/bundles/codex-peer/plugin.rb +1 -1
- data/lib/samagotchi/bundles/coordinator/manifest.yml +2 -2
- data/lib/samagotchi/bundles/coordinator/plugin.rb +1 -1
- data/lib/samagotchi/bundles/core/manifest.yml +2 -2
- data/lib/samagotchi/bundles/cost-cap/manifest.yml +6 -3
- data/lib/samagotchi/bundles/cost-cap/plugin.rb +5 -7
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -1
- data/lib/samagotchi/bundles/known-names/manifest.yml +3 -1
- data/lib/samagotchi/bundles/loop-guard/manifest.yml +4 -2
- data/lib/samagotchi/bundles/loop-guard/plugin.rb +1 -1
- data/lib/samagotchi/bundles/mcp/manifest.yml +4 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +2 -2
- data/lib/samagotchi/bundles/ruby/manifest.yml +2 -2
- data/lib/samagotchi/bundles/ruby/plugin.rb +1 -1
- data/lib/samagotchi/bundles/skills/manifest.yml +4 -2
- data/lib/samagotchi/bundles/skills/plugin.rb +1 -1
- data/lib/samagotchi/bundles/slow-commands/manifest.yml +4 -2
- data/lib/samagotchi/bundles/slow-commands/plugin.rb +1 -1
- data/lib/samagotchi/bundles/source-links/manifest.yml +3 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +3 -3
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/bundles/system/self_map.md +3 -3
- data/lib/samagotchi/bundles/work-cap/manifest.yml +6 -3
- data/lib/samagotchi/bundles/work-cap/plugin.rb +11 -13
- data/lib/samagotchi/config.rb +74 -14
- data/lib/samagotchi/context_candidate_choice.rb +20 -3
- data/lib/samagotchi/context_candidates.rb +4 -1
- data/lib/samagotchi/context_candidates_log.rb +36 -1
- data/lib/samagotchi/context_judge.rb +6 -2
- data/lib/samagotchi/context_status.rb +1 -2
- data/lib/samagotchi/engine.rb +96 -70
- data/lib/samagotchi/extension_load.rb +11 -4
- data/lib/samagotchi/guardrail_wiring.rb +36 -31
- data/lib/samagotchi/guardrails/chi_dirs.rb +7 -4
- data/lib/samagotchi/guardrails/path_resolve.rb +1 -1
- data/lib/samagotchi/guardrails/protected_paths.rb +7 -4
- data/lib/samagotchi/guardrails_command.rb +3 -3
- data/lib/samagotchi/hook_routes.rb +37 -2
- data/lib/samagotchi/hooks/bundle_loader.rb +20 -4
- data/lib/samagotchi/hooks/registry.rb +23 -5
- data/lib/samagotchi/host_model.rb +3 -3
- data/lib/samagotchi/host_registry.rb +14 -7
- data/lib/samagotchi/judgments_command.rb +155 -0
- data/lib/samagotchi/kernel_loop.rb +20 -3
- data/lib/samagotchi/llm/chat_loop.rb +79 -10
- data/lib/samagotchi/llm/errors.rb +7 -5
- data/lib/samagotchi/llm/stray_tool_call.rb +68 -0
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/memory_bundle/builder.rb +4 -2
- data/lib/samagotchi/memory_bundle/entry_size.rb +100 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +9 -2
- data/lib/samagotchi/memory_bundle/installed_copy.rb +92 -0
- data/lib/samagotchi/memory_bundle/installer.rb +2 -0
- data/lib/samagotchi/memory_bundle/manifest.rb +23 -13
- data/lib/samagotchi/memory_bundle/provenance.rb +4 -1
- data/lib/samagotchi/memory_bundle/shipped_update.rb +13 -1
- data/lib/samagotchi/model_catalog.rb +3 -2
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/plugin/api.rb +41 -7
- data/lib/samagotchi/plugin/context.rb +59 -1
- data/lib/samagotchi/plugin/judgments.rb +72 -0
- data/lib/samagotchi/plugin/loader.rb +6 -3
- data/lib/samagotchi/plugin/peer.rb +17 -22
- data/lib/samagotchi/plugin_session_state.rb +32 -5
- data/lib/samagotchi/prompt_warmup.rb +1 -9
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/self_report.rb +36 -0
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +1 -1
- data/lib/samagotchi/session_access/remote.rb +3 -3
- data/lib/samagotchi/session_archive_command.rb +3 -3
- data/lib/samagotchi/session_commands.rb +7 -4
- data/lib/samagotchi/session_config.rb +9 -3
- data/lib/samagotchi/session_continue.rb +1 -1
- data/lib/samagotchi/session_manager.rb +25 -5
- data/lib/samagotchi/session_retention.rb +10 -0
- data/lib/samagotchi/side_judgments.rb +12 -1
- data/lib/samagotchi/system_one/answer.rb +63 -11
- data/lib/samagotchi/system_one/approval_label.rb +56 -0
- data/lib/samagotchi/system_one/approval_question.rb +2 -0
- data/lib/samagotchi/system_one/client.rb +43 -17
- data/lib/samagotchi/system_one/fake_backend.rb +1 -1
- data/lib/samagotchi/system_one/http_backend.rb +12 -4
- data/lib/samagotchi/system_one/judgment_settings.rb +61 -0
- data/lib/samagotchi/system_one/judgments.rb +243 -0
- data/lib/samagotchi/system_one/log.rb +25 -2
- data/lib/samagotchi/system_one/recording.rb +117 -0
- data/lib/samagotchi/system_one/runs.rb +179 -0
- data/lib/samagotchi/system_one/runs_store.rb +230 -0
- data/lib/samagotchi/system_one.rb +9 -0
- data/lib/samagotchi/terminal_ui/attach_link.rb +2 -2
- data/lib/samagotchi/terminal_ui/attached_loop/exit_commands.rb +1 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +1 -1
- data/lib/samagotchi/terminal_ui/local_link.rb +1 -1
- data/lib/samagotchi/terminal_ui/remote_link.rb +1 -2
- data/lib/samagotchi/testing/bundle.rb +9 -2
- data/lib/samagotchi/testing/host.rb +2 -1
- data/lib/samagotchi/testing/judgments.rb +176 -0
- data/lib/samagotchi/testing/recorder.rb +5 -0
- data/lib/samagotchi/testing/recording_api.rb +1 -1
- data/lib/samagotchi/tool_output_cut.rb +26 -6
- data/lib/samagotchi/tool_response.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +63 -4
- data/lib/samagotchi/toolkit_suggest.rb +11 -2
- data/lib/samagotchi/tools/memory.rb +28 -0
- data/lib/samagotchi/turn_note.rb +9 -0
- data/lib/samagotchi/update_command.rb +49 -16
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +1 -0
- data/lib/samagotchi/web/auto_update.rb +79 -49
- data/lib/samagotchi/web/auto_update_record.rb +59 -0
- data/lib/samagotchi/web/public/app.js +8 -4
- data/lib/samagotchi/web/public/hash_intent.js +24 -0
- data/lib/samagotchi/web/public/index.html +3 -1
- data/lib/samagotchi/web/public/revise_stack.js +14 -5
- data/lib/samagotchi/web/session_control.rb +3 -3
- data/lib/samagotchi/web/update_check.rb +7 -6
- data/lib/samagotchi/web/update_ops.rb +80 -25
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0403d20c85725cdd309a9b0ff24cc997c3effd60f37e42c9258de568b5e89944
|
|
4
|
+
data.tar.gz: aa1345c31e6d3ee3dbf578d6ed7298ec98f5c32caf67f304b75ac3caf3f2f886
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 528d2c989d71eb722a9e66ede6c2ddd4a57387e74842034bf019a671abb2f3b7d50cb759c8628f6d86d611434a6772a9a3fc75f5886d7f42eea98f3b54bcef1c
|
|
7
|
+
data.tar.gz: d5078148478d31d778cb973d8ae7dac52696e5cfa9909f96b5ea33185d3c920f067aeea64417b7f64de1785047fc74f9c24bca3f4d4bfda75b792342c712ae22
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,108 @@ and commands may change between minor versions. How releases are made:
|
|
|
7
7
|
[docs/releasing.md](docs/releasing.md).
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.143.0] - 2026-11-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `chi update` checks the bundles chi doesn't ship, such as one from GitHub, against the new chi, and adds a row for each warning or error: a `requires_chi` it fails, a plugin or
|
|
15
|
+
hook that no longer loads, a `ctx` call the API lacks, a reference to chi's internals. It never changes them, and these rows don't fail the update (`--json`: a new `level` field). `--no-bundles` skips
|
|
16
|
+
it, and `--dry-run` with a newer gem to install says `not checked`, as only that chi could answer. See [docs/cli.md](docs/cli.md#updating).
|
|
17
|
+
- `chi bundle check` warns (`api`) on a `ctx.*` or `chi.*` call, or a keyword, that this chi's API lacks, at its line:
|
|
18
|
+
`ctx.settingz`, `ctx.sessions.nope`, `ctx.card(titel:)`. It reads the plugin's source, so a path no test runs counts too.
|
|
19
|
+
- `ctx.session_dir`: a session's own folder for a bundle, created on use, removed with the session, so a bundle no longer
|
|
20
|
+
builds that path itself. cost-cap, work-cap and check-in now keep their state there (`state.json`); a running session's
|
|
21
|
+
file moves in by itself. `chi update` gets them (cost-cap 0.2.4, work-cap 0.1.4, check-in 0.3.1).
|
|
22
|
+
- A manifest may list `settings:`, the keys config.yml's `bundles: <name>:` takes, with `session_settings:`' types. A key it rules out warns
|
|
23
|
+
when a session starts, in every UI, and so does a `bundles:` name no installed bundle has. Only a warning: the session goes on.
|
|
24
|
+
The shipped bundles with settings list theirs (`chi update`).
|
|
25
|
+
- A plugin's `chi.on` handler that raises now tells you, once per event in a worker, in the TUI and the web
|
|
26
|
+
(`plugins> plugin.rb (bundle x) before_turn hook failed (…)`). Before, only the log knew. The hook is still skipped, and the log
|
|
27
|
+
still has each failure.
|
|
28
|
+
- A bundle's `hooks/*.rb` file that raises now tells you the same way, once per hook in a worker, whatever its `on_error` —
|
|
29
|
+
including the default `skip`, which logged nothing at all. A guard bundle that broke (`chi-nah`, say) used to fail quietly;
|
|
30
|
+
now `plugins> guard.rb (bundle chi-nah) before_tool_call hook failed (…)` says so. `on_error` still decides logging and
|
|
31
|
+
the deny; the notice is only added, and config.yml's own hooks are unchanged.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- With System 1 on the model's own local server, its asks now run during your turn as on any other host: toolkit suggestions (audit, the
|
|
36
|
+
deferred switch and the worker's warm-up, all skipped there before), context candidates (held to the turn's end before) and bundles' judgments (skipped before).
|
|
37
|
+
Measured on Splash, they don't push out the session's cached prompt. Each costs the running generation about 0.15 s.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
- `shared_host:` on `chi.judgment`, the `:shared_host` reason, and `shared_host_busy` in the bundle test kit: a bundle still passing `shared_host:` fails to load, with an
|
|
42
|
+
`ArgumentError`. check-in 0.3.1 no longer passes it, so run `chi update`.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- `chi sessions delete` now also drops the session's lines from `context_candidates.jsonl` (its `judged` lines keep 200
|
|
47
|
+
characters of its prompts), as it already purges that session's System 1 runs.
|
|
48
|
+
- A reply that is only a tool call the host could not read is now caught when the block has nothing inside it
|
|
49
|
+
(`<function=task_list></function>`, `<tool_call>{"name":"x"}</tool_call>`), and its close must be the tag that opened it.
|
|
50
|
+
- A Stop that arrives on a reply that is only a tool call the host could not read ends the turn cancelled. It used to fail
|
|
51
|
+
it with "the reply came back as a ... block the host could not read, twice in a row".
|
|
52
|
+
- With `update.auto: install`, "chi updated to X" counts the sessions the old chi web restarted (it said 0 of them), and a run
|
|
53
|
+
chi web is restarted in the middle of still tries the sessions it had left.
|
|
54
|
+
- With `update.auto: install`, an error in one step (a full disk, a hiccup reading the sessions) no longer stops automatic updates until
|
|
55
|
+
chi web restarts: the step is tried again 5 minutes later.
|
|
56
|
+
- With `update.auto: install` and `update.restart: idle`, sessions that were busy or recently used at the update really are tried again for
|
|
57
|
+
a day, as documented. They used to stay on the old chi once chi web had restarted. The extra tries respect the idle rule, and the page only hears of one that restarts a session.
|
|
58
|
+
- A hung `chi update` run by chi web (the **Update** button, `update.auto: install`) is stopped after 30 minutes and reported as failed. It used to answer "update running" to every later update until chi web restarted.
|
|
59
|
+
- Web: a hash that names no session (`#/`, or none) while one is open now leaves it, as the **+** button does. It used to change
|
|
60
|
+
only the URL: the page kept showing that session, and Send went into it.
|
|
61
|
+
- `chi bundle check`'s `requires-chi-floor` knows the calls and keywords it was missing: `ctx.context.propose`, `candidates`, `withdraw` and
|
|
62
|
+
`left_behind`, `ctx.review?`, `ctx.continues`, `ctx.context.attach(movable:)` and others, so a bundle using them with a lower `requires_chi` is told.
|
|
63
|
+
- `/context attach` and `/context decline`, typed in or from audit or off mode, no longer show a "closed" card when no offer card was open.
|
|
64
|
+
- Web: on a phone, a Revise stub's "superseded by vN" stays readable, so two stubs that open with the same line can be told apart.
|
|
65
|
+
|
|
66
|
+
Update with `chi update` (the core profile 0.4.2): check-in 0.3.1, cost-cap 0.2.4 and work-cap 0.1.4 keep their state in `ctx.session_dir`, and check-in no longer passes `shared_host:` (the older check-in 0.3.0 fails to load on this chi until it is updated). btw, agents-md, claude-peer, codex-peer, coordinator, github-pr, known-names, loop-guard, mcp, ruby, skills, slow-commands and source-links only list their settings or have small fixes.
|
|
67
|
+
|
|
68
|
+
## [0.142.0] - 2026-11-01
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- System 1's asks are saved, so a later comparison or fine-tune has what was sent, answered and done: approvals, toolkit
|
|
73
|
+
suggestions and context candidates, with the user's answers as labels, under `<state dir>/system_one/runs/`, 0600 and
|
|
74
|
+
local only. They hold prompts, commands and diffs, as sent. They last as long as sessions (`session.retention_days`, 100 MB
|
|
75
|
+
in all), `chi sessions delete` purges a session's, and `system_one.runs: off` stops it. `chi judgments ls` and
|
|
76
|
+
`chi judgments report NAME [--days N]` show what's there. See docs/system-one.md.
|
|
77
|
+
- Bundles can ask System 1 cheap questions of their own: `chi.judgment` declares one, `ctx.judge` and `ctx.judge_later` ask it,
|
|
78
|
+
`ctx.judgments.label` says what came of it, and each ask saves a run. Each is off until you set its mode under
|
|
79
|
+
`system_one.judgments`, which may also give it its own host and model (core's judgments take neither). Bundles' judgments stay out of
|
|
80
|
+
the session's spend, so no cost cap covers them on a hosted host. The test kit has `bundle.judgments`. See docs/plugins.md (Judgments).
|
|
81
|
+
- check-in asks System 1 `check-in.progress` at every check-in ("is this turn still making progress toward what the user asked?"),
|
|
82
|
+
off until you set its mode. The turn's prompt, the calls since the last check-in, the count and the elapsed time go, and what you
|
|
83
|
+
then did with the card is saved as a label on that run. Nothing is acted on: the card, the nudge and the notice are as they were.
|
|
84
|
+
It is the first use to send tool output to System 1 (docs/system-one.md). `chi update` gets it (check-in 0.3.0).
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- `memory_read` reads a memory whole: `max_tool_output_chars` no longer cuts it, so a skill or memory over 10,000 characters
|
|
89
|
+
arrives complete (before, the end was lost and "read it in parts" had no parts). `memory.read_max_chars` (default `0`, no
|
|
90
|
+
fixed cap) caps it if you want one. A result over half a known context window is still cut, since an overflow would roll
|
|
91
|
+
the turn back. A cut now says where the rest is: the memory's `.md` file, read with `start_line`.
|
|
92
|
+
- A write that takes a memory over `memory.entry_warn_tokens` (default `5000` tokens) ends with a note to tighten it, once
|
|
93
|
+
per crossing (`0` turns it off); `chi self` has a `big memories` row for the ones already over. See docs/memory.md.
|
|
94
|
+
- **Edit your config:** System 1's shared settings moved out of `guardrails.system_one`, with no alias:
|
|
95
|
+
`guardrails.system_one.host` → `system_one.host`, `.model` → `system_one.model`, `.timeout_ms` →
|
|
96
|
+
`system_one.timeout_ms`. `mode`, `rules`, `threshold` and `min_record` stay where they are. Until all of them have moved chi warns
|
|
97
|
+
at start, nothing is judged and nothing auto-allows: approvals ask you, and toolkit suggestions and context candidates
|
|
98
|
+
ask nothing. Move `model` rather than deleting it: with no model set, the host's first model would answer under approvals'
|
|
99
|
+
existing record. Restart workers on an older chi after the edit. See docs/configuration.md.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- A reply that was only a tool call written as text, which the host couldn't read, ended the turn as if it were the answer:
|
|
104
|
+
exit 0, work left uncommitted. The chat loop now asks again once with a hidden note; two in a row fail the turn visibly
|
|
105
|
+
and keep its earlier steps. Seen with Flash Next.
|
|
106
|
+
- A System 1 `score` answer was dropped: the host sends `score` as the probability-weighted level index (a fraction,
|
|
107
|
+
not a level number), and chi only accepted a whole number. The score is kept as sent and `answer.level` names the
|
|
108
|
+
level with the most probability. See docs/guardrails.md (System 1).
|
|
109
|
+
|
|
110
|
+
Update with `chi update` (check-in 0.3.0, the core profile 0.4.1).
|
|
111
|
+
|
|
10
112
|
## [0.141.0] - 2026-10-31
|
|
11
113
|
|
|
12
114
|
### Added
|
|
@@ -967,7 +1069,7 @@ Update with `chi update` (the system bundle 0.118.0), then restart `chi web`.
|
|
|
967
1069
|
- `/exit` in an attached terminal **asks what to do with what still runs** when only things you can stop keep the
|
|
968
1070
|
worker up (reminders, a watched background task, a delegate with a live worker): keep running in the background,
|
|
969
1071
|
stop them and exit, or go back. Anything else that holds the worker (a running turn, queued prompts, another UI)
|
|
970
|
-
still refuses as before, and a delegate never holds the idle exit. See [cli.md](docs/cli.md#
|
|
1072
|
+
still refuses as before, and a delegate never holds the idle exit. See [cli.md](docs/cli.md#sharing-a-session).
|
|
971
1073
|
- The web's `stop session` asks the same first: a sheet (a bottom sheet on a phone) counts what still runs and offers
|
|
972
1074
|
**Stop all**, **Stop session only** (the stop as it was: background tasks and delegates keep running) or
|
|
973
1075
|
**Cancel**; with nothing to stop it is one tap as before. See [Sessions](docs/sessions.md).
|
|
@@ -1606,7 +1708,7 @@ Update with `chi update`.
|
|
|
1606
1708
|
and active minutes across turns, the bound a session whose step-limit Continue keeps being answered otherwise
|
|
1607
1709
|
lacks. At a cap the turn pauses on Continue / Stop, the question naming the session's totals; `/workcap` shows or
|
|
1608
1710
|
sets it, `chi send --new --set work-cap.tool_calls=400` caps a child. Inert until a cap is set. See
|
|
1609
|
-
[docs/
|
|
1711
|
+
[docs/bundles.md](docs/bundles.md#the-work-cap-bundle).
|
|
1610
1712
|
|
|
1611
1713
|
### Fixed
|
|
1612
1714
|
|
|
@@ -1742,7 +1844,7 @@ Update with `chi update` (the system bundle 0.95.0), then restart `chi web`.
|
|
|
1742
1844
|
- The `model-notes` bundle (0.1.0, in the `dev` profile, now 0.7.0): reviewed model notes, for now
|
|
1743
1845
|
`model_notes_deepseek` for DeepSeek V4 / V4.1 (`models: *deepseek-v4*`). A same-name note you already have is
|
|
1744
1846
|
skipped and keeps loading; to take the bundle's, remove yours and install again. See
|
|
1745
|
-
[docs/
|
|
1847
|
+
[docs/bundles.md](docs/bundles.md#the-model-notes-bundle).
|
|
1746
1848
|
|
|
1747
1849
|
### Changed
|
|
1748
1850
|
|
|
@@ -3566,7 +3668,7 @@ Update with `chi update` (mcp 0.6.1).
|
|
|
3566
3668
|
instructions. Guardrail rules on `mcp_<server>_<tool>` names still match (`mcp_call` acts as that tool), and the
|
|
3567
3669
|
question names `<server>: <tool>`; approvals stored under the old names don't carry over. Hooks and loop-guard's
|
|
3568
3670
|
`ignore_tools` see `mcp_call`. `/mcp` lists the tools as `<server>/<tool>` and says what every request carries.
|
|
3569
|
-
Needs chi 0.37.0. See [docs/
|
|
3671
|
+
Needs chi 0.37.0. See [docs/bundles.md](docs/bundles.md#the-mcp-bundle).
|
|
3570
3672
|
|
|
3571
3673
|
### Fixed
|
|
3572
3674
|
|
|
@@ -3618,7 +3720,7 @@ Update with `chi update` (mcp 0.5.1).
|
|
|
3618
3720
|
rebuild (the split, your decisions, its verdicts, follow-ups), its status in the index description, and resumes
|
|
3619
3721
|
from it in a new session after checking git and the session list; `/coordinate resume` picks up an open handoff.
|
|
3620
3722
|
When the work is done it marks the handoff DONE and asks whether to remove it. See
|
|
3621
|
-
[docs/
|
|
3723
|
+
[docs/bundles.md](docs/bundles.md#the-coordinator-bundle).
|
|
3622
3724
|
- The guardrails bundle (0.8.0) asks before every memory removal (`memory-remove`, once at a time). Guardrail rules
|
|
3623
3725
|
take `memory: remove`.
|
|
3624
3726
|
- In the web, a file reference in an answer (`lib/foo.rb:28`, `lib/foo.rb:28-34`, in inline code too) links to that
|
|
@@ -3627,7 +3729,7 @@ Update with `chi update` (mcp 0.5.1).
|
|
|
3627
3729
|
at the PR's head. Display only; `bundles: github-pr: line_links: false` turns it off (github-pr 0.2.0; run
|
|
3628
3730
|
`chi update`). See [docs/context.md](docs/context.md#line-links).
|
|
3629
3731
|
- `/mcp` shows roughly how many tokens each MCP server's tool definitions take in every request, and the total
|
|
3630
|
-
(mcp 0.5.0; run `chi update`). See [docs/
|
|
3732
|
+
(mcp 0.5.0; run `chi update`). See [docs/bundles.md](docs/bundles.md#the-mcp-bundle).
|
|
3631
3733
|
|
|
3632
3734
|
### Fixed
|
|
3633
3735
|
|
|
@@ -5379,7 +5481,9 @@ and long-lived sessions.
|
|
|
5379
5481
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
5380
5482
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
5381
5483
|
|
|
5382
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
5484
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.143.0...HEAD
|
|
5485
|
+
[0.143.0]: https://github.com/dm1try/samagotchi/compare/v0.142.0...v0.143.0
|
|
5486
|
+
[0.142.0]: https://github.com/dm1try/samagotchi/compare/v0.141.0...v0.142.0
|
|
5383
5487
|
[0.141.0]: https://github.com/dm1try/samagotchi/compare/v0.140.0...v0.141.0
|
|
5384
5488
|
[0.140.0]: https://github.com/dm1try/samagotchi/compare/v0.139.1...v0.140.0
|
|
5385
5489
|
[0.139.1]: https://github.com/dm1try/samagotchi/compare/v0.139.0...v0.139.1
|
data/README.md
CHANGED
|
@@ -218,10 +218,11 @@ once, for the session, for the repo, or for the whole rule in the repo.
|
|
|
218
218
|
- [Guardrails](docs/guardrails.md): allow / ask / deny for tool calls, rules, approvals
|
|
219
219
|
- [Hooks](docs/hooks.md): plugin hooks and bundle hooks
|
|
220
220
|
- [Plugins](docs/plugins.md): bundle plugins (commands, tools, hooks)
|
|
221
|
+
- [Bundles](docs/bundles.md): what each shipped bundle does (btw, mcp, cost-cap, claude-peer, skills…), its commands and settings
|
|
221
222
|
- [Writing a bundle](docs/bundle-authoring.md): `chi bundle new`, `test`, `check`, `try`, `install --dev`
|
|
222
223
|
- [Testing](docs/testing.md): specs, affected specs, lint, worktrees
|
|
223
224
|
- [Architecture](docs/architecture.md): Engine, TerminalUI, bridge, web
|
|
224
|
-
- Internals: [Engine](docs/internals/engine.md), [Gemma 4 contract](docs/internals/gemma4-contract.md), [prompt caching](docs/internals/prompt-caching.md), [context telemetry](docs/internals/context-telemetry.md), [LLM context: forget layer](docs/internals/llm-context-forget.md), [replay benchmark](docs/internals/llm-context-bench.md), [live runs](docs/internals/llm-context-live.md), [tool output limits](docs/internals/tool-
|
|
225
|
+
- Internals: [Engine](docs/internals/engine.md), [Gemma 4 contract](docs/internals/gemma4-contract.md), [prompt caching](docs/internals/prompt-caching.md), [context telemetry](docs/internals/context-telemetry.md), [LLM context: forget layer](docs/internals/llm-context-forget.md), [replay benchmark](docs/internals/llm-context-bench.md), [live runs](docs/internals/llm-context-live.md), [tool output limits](docs/internals/tool-output-limits.md), [background tasks](docs/internals/background-tasks.md), [bundles](docs/internals/bundles.md), [Anthropic Messages API](docs/internals/anthropic.md)
|
|
225
226
|
|
|
226
227
|
## Development
|
|
227
228
|
|
data/bin/chi
CHANGED
|
@@ -97,6 +97,8 @@ SUBCOMMANDS = {
|
|
|
97
97
|
"guardrails" => ["samagotchi/guardrails_command", "GuardrailsCommand"],
|
|
98
98
|
# audit suggestions for which toolkit fits a session's first prompt
|
|
99
99
|
"toolkit" => ["samagotchi/toolkit_report_command", "ToolkitReportCommand"],
|
|
100
|
+
# System 1's saved asks on this machine (ls, report)
|
|
101
|
+
"judgments" => ["samagotchi/judgments_command", "JudgmentsCommand"],
|
|
100
102
|
# other machines' chi web, for chi --remote NAME (add, ls, rm)
|
|
101
103
|
"remote" => ["samagotchi/remote_command", "RemoteCommand"],
|
|
102
104
|
# the chi home: the Ruby every chi launch boots (set, doctor)
|
data/docs/architecture.md
CHANGED
|
@@ -209,7 +209,7 @@ agent logic and can be used without any terminal rendering; the UI is a thin lay
|
|
|
209
209
|
| Core | `Samagotchi::Engine` | System prompt, memory injection, tool declarations, session lifecycle, the model↔tool loop (`run_turn`). No terminal coupling. |
|
|
210
210
|
| UI | `Samagotchi::TerminalUI` | Interactive REPL (Reline), rendering (ANSI, spinner, status line), REPL commands. Delegates all core work to an `Engine`. |
|
|
211
211
|
| Model loops and adapters | `KernelLoop`, `LLM::ChatLoop`, `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | The model↔tool loops and the HTTP adapters they talk through (see "Model loops and adapters"). |
|
|
212
|
-
| Bridge (SSE/HTTP) | `Samagotchi::Bridge`, `SessionManager` | The **single live client transport**: an SSE read stream + HTTP POST turn/cancel/answer surface that attaches to a worker's existing `Engine` via `Engine#subscribe`. Every session worker starts it (bound `127.0.0.1`, localhost-only), and it answers only a request that names its secret (`X-Chi-Bridge`, from the worker's 0600 `bridge.json`). |
|
|
212
|
+
| Bridge (SSE/HTTP) ([internals/bridge.md](internals/bridge.md)) | `Samagotchi::Bridge`, `SessionManager` | The **single live client transport**: an SSE read stream + HTTP POST turn/cancel/answer surface that attaches to a worker's existing `Engine` via `Engine#subscribe`. Every session worker starts it (bound `127.0.0.1`, localhost-only), and it answers only a request that names its secret (`X-Chi-Bridge`, from the worker's 0600 `bridge.json`). |
|
|
213
213
|
| Web (Rack) | `Samagotchi::Web::App`, `SessionManager` | Single-port `127.0.0.1:4567` control plane via `rack`+`webrick` (serve `index.html` + `/api/*`). With `web.host: lan` (or one of this machine's IPv4 addresses) the server also listens on that address, where every request needs the access token, this machine's on loopback too (`App#token_gate`: the `chi_token` cookie, on loopback a value derived from the token (`Token.loopback_cookie`), or `Authorization: Bearer`; chi web prints a `Local:` link with a one-time code; [CLI](cli.md#chi-web-on-your-phone)). Experimental: behind an auth proxy (`web.public_url`, `Web::ProxyGate`) every request needs the token, this machine's too, and one that came through the proxy also the configured identity header for the owner ([configuration.md](configuration.md#behind-an-auth-proxy)). A request goes through, in order: the Host check (`local_host_header?`: loopback names, the LAN address, the public host), the cross-site check (`cross_site?`: Origin this server's or the public origin, no `Sec-Fetch-Site` cross-site/same-site on a POST or `/api`), the frame check (`framed?`: a frame/embed/object load from another origin, 403 `framed`; every HTML answer carries `X-Frame-Options: DENY` and `frame-ancestors 'none'`), the token gate (`token_gate` → `principal_for`, `proxied_principal` for a proxied request; the `?token=` / `POST /api/token` trade sets the cookie, `__Host-chi_token` through the proxy; with `web.public_url` a direct request takes Bearer only, and with it or LAN mode chi's token-less `/api/info` probe gets `probe_info`), then the route's permission (`Principal#permits?`). It talks to each worker's Bridge (`App#relay`, `bridge_get`; `/stream` proxies it) and reads the session files; no `Engine` of its own. Markdown (`web.markdown`) is rendered here only (`Web::MarkdownRenderer`): the answers it serves, card bodies, and `POST /api/render` for a text the page already shows. `App` keeps the gate, `ROUTES` and the helpers the handlers share; its handlers live in mixins it includes, each a `Web::` module with private methods: `SessionAccessRoutes` (`web/session_access_routes.rb`), `ParticipantAccess` (`web/participant_access.rb`), `ParticipantsAdmin` (`web/participants_admin.rb`), `ParticipantReview` (`web/participant_review.rb`), `MessageDisplay` (`web/message_display.rb`), `SessionShow` (`web/session_show.rb`), `SessionCreate` (`web/session_create.rb`), `SessionControl` (`web/session_control.rb`), `PushRoutes` (`web/push_routes.rb`) and `ProxyGate` (`web/proxy_gate.rb`). Exact paths in `GATE_FREE_PATHS` are served before the token gate (the fragment trade's script, and the Home Screen app's manifest, service worker and icons). `bin/chi web` entrypoint. |
|
|
214
214
|
| Sessions | `Samagotchi::Session`, `SessionManager` | File-based `~/.local/state/samagotchi/sessions/<uuid>.json` + sidecar `input/`/`output/`; retention (14d/500) + ordering (`updated_at desc`). |
|
|
215
215
|
|
|
@@ -315,286 +315,15 @@ engine.session_state_snapshot # => { status:, message_count:, last_prompt:, ev
|
|
|
315
315
|
|
|
316
316
|
`Engine#subscribe` is the seam the SSE bridge (`Samagotchi::Bridge`) rides on. The bridge
|
|
317
317
|
is the **single live transport** and runs **inside the forked session worker** (the same
|
|
318
|
-
process that already owns the `Engine`); every worker starts it
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
Each event goes out as clients get it (`Bridge::ClientEvent.project`, in `SSEWriter#write_event`, live
|
|
328
|
-
and replayed alike); `generation_chunk`, `tool_call_started` and `turn_completed` are projected to the
|
|
329
|
-
contracts below. The ring retains raw events for replay and `?raw=1`.
|
|
330
|
-
`?raw=1` writes events as the engine emitted them, for debugging on the worker's port: `chi web`'s
|
|
331
|
-
`/api/sessions/:id/stream` never passes it on (`Web::App#stream_query`).
|
|
332
|
-
An attached terminal's stream (`EventStream` with `rediscover:`) looks for the session's live
|
|
333
|
-
worker whenever it drops: a new one (a restart) is followed from its snapshot, and the terminal
|
|
334
|
-
sends its requests there (`worker_changed`); it gives up after ~12 s, or at once when the session
|
|
335
|
-
was stopped.
|
|
336
|
-
|
|
337
|
-
The wire chunk (the contract; the engine's own event keeps the provider's `payload` and the raw `content`):
|
|
338
|
-
|
|
339
|
-
```
|
|
340
|
-
event: generation_chunk
|
|
341
|
-
data: {"type":"generation_chunk","iteration":2,"text":"…","thinking":"",
|
|
342
|
-
"tool_call":true, // only when set
|
|
343
|
-
"counts":{"prompt_tokens":489,"completion_tokens":12,
|
|
344
|
-
"total_tokens":501,"context_window_tokens":32768}, // only when the server reported counts
|
|
345
|
-
"event_seq":812}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
A `tool_call_started` frame keeps its row fields but omits the model's `call`:
|
|
349
|
-
|
|
350
|
-
```
|
|
351
|
-
event: tool_call_started
|
|
352
|
-
data: {"type":"tool_call_started","iteration":2,"call_index":1,"tool":"write",
|
|
353
|
-
"params":"path=notes.txt","event_seq":812}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
A `turn_completed` frame omits `result`; `turn_summary.output` carries the answer:
|
|
357
|
-
|
|
358
|
-
```
|
|
359
|
-
event: turn_completed
|
|
360
|
-
data: {"type":"turn_completed","turn_summary":{"output":"Done."},
|
|
361
|
-
"display_pending":false,"event_seq":813}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
`counts` is `ContextUsage.normalize` of the provider's payload without `ctx_pct` (normalize computes it
|
|
365
|
-
without the session's LLM context budget; a client counts `total_tokens` against the window or budget, as
|
|
366
|
-
`ContextStatus#generation_done` does for the TUI) and with its nil keys left out: cumulative for the
|
|
367
|
-
request, a missing key means unknown (never 0), `prompt_tokens` includes cached tokens, and
|
|
368
|
-
`context_window_tokens` is there only when the payload reported a window itself (llama.cpp's `/completion`
|
|
369
|
-
doesn't: the client uses `generation_started`'s). Unknown provider fields never cross; a field is added here
|
|
370
|
-
when a client needs it, never as a catch-all bag. Details: [internals/context-telemetry.md](internals/context-telemetry.md).
|
|
371
|
-
- `POST /session/:id/turn` — fire-and-forget turn creation; returns `202` with an `enqueued_id`
|
|
372
|
-
(delivery is at-least-once via the worker's file-IPC input path — it never calls `run_turn`
|
|
373
|
-
across the HTTP boundary). Only the bridge's own session: another id, in the path or the body's
|
|
374
|
-
`session_id`, is `404 unknown_session`. Inspect results through the read surface, not the turn response.
|
|
375
|
-
An optional `deadline` (epoch seconds; `BridgeClient` sends 5/6 of its read timeout ahead) makes
|
|
376
|
-
a request read after it (a worker frozen by sleep or SIGSTOP) answer `408 deadline_passed` and
|
|
377
|
-
not run: a client that timed out has said the message was not sent. `/answer`,
|
|
378
|
-
`/question/dismiss`, `/command`, `/turn/dequeue` and `/exit` take the same `deadline` (a command is
|
|
379
|
-
checked with the event log held, as a turn is), and the Bridge logs `turn_expired`, `answer_expired`,
|
|
380
|
-
`dismiss_expired`, `command_expired`, `dequeue_expired` or `exit_expired`. The web app answers either
|
|
381
|
-
kind of timeout with `504 worker_timeout` ("… so the command was not run"). `/cancel` and `/recap` take none.
|
|
382
|
-
The web relays answer, dismiss, command and cancel the same way (`App#relay`): a 408 or a read
|
|
383
|
-
timeout is `504 worker_timeout`; a Bridge 404 for a route an older worker lacks is
|
|
384
|
-
`501 not_supported` with the restart hint; a Bridge `404 unknown_session`, no bridge or a
|
|
385
|
-
refused connection is `503 not_live`.
|
|
386
|
-
- `POST /session/:id/cancel` — cancel the running turn; `202`, or `409` when none runs.
|
|
387
|
-
- `POST /session/:id/recap` — `/recap` in an attached TUI: `200 {enabled, saved, request,
|
|
388
|
-
min_user_turns}` (`{enabled: false, session_off}` without recaps; `session_off` when the session's own
|
|
389
|
-
`/recap off` turned it off); a new recap is asked for at once and
|
|
390
|
-
arrives as `:recap_ready`. Answers mid-turn too.
|
|
391
|
-
- `POST /session/:id/answer` — answer the pending question; `200`, `409` when another client
|
|
392
|
-
answered first or it is gone, `400` for an invalid selection. With `client_id: "cli:answer"`
|
|
393
|
-
(`chi answer`, a parent agent) the answer is checked by `Guardrails::ParentPolicy` in
|
|
394
|
-
`QuestionDesk#answer`: the question carries each option's effect (`effects`, set by its asker:
|
|
395
|
-
`Approval.payload`, `ContinueOffer`, `Relay.card`; derived for an untagged one) and `user_only`,
|
|
396
|
-
and an option beyond the worker's `guardrails.parent_answers` (tightened by the session's own
|
|
397
|
-
`parent_answers`: `ParentPolicy.for_session`) is `403 parent_answer_refused` (with its `reason`),
|
|
398
|
-
the question still open. `chi answer` makes the same check first, from the session file.
|
|
399
|
-
- `POST /session/:id/question/dismiss` — leave the question unanswered (an approval: denied);
|
|
400
|
-
`200`, or `409` when it is no longer pending.
|
|
401
|
-
- `POST /session/:id/tasks/stop` — `{task_id}`: stop a background task as the user
|
|
402
|
-
(`TaskRuntime.stop_task by: "user"`, the web's stop-task button on a running `task_wait`;
|
|
403
|
-
web route `POST /api/sessions/:id/tasks/:task_id/stop`). Only a task this conversation's
|
|
404
|
-
`task_create` started, or an `execute` handed over as a task at its limit (`TaskRuntime.created_ids_in` on the
|
|
405
|
-
saved messages, or the running turn's accumulator); answers once the
|
|
406
|
-
task is gone: `200 {status, stop_reason, task_id}`, `404 task_not_found`, `409 not_running`.
|
|
407
|
-
The worker names it in its sidecar features (`task_stop`).
|
|
408
|
-
- `POST /session/:id/turn/dequeue` — `{enqueued_id, client_id?, deadline?}`: take back a queued message
|
|
409
|
-
(one that waits for the running turn's end: `--queue`, Alt+Enter, `/queue TEXT`, or a message
|
|
410
|
-
with images) before a turn takes it (web route `POST /api/sessions/:id/turn/:enqueued_id/dequeue`,
|
|
411
|
-
the × on a queued bubble; `/unqueue` in the attached TUI). Under the event log, like its enqueue:
|
|
412
|
-
`SessionInbox.take_queued_input` writes a tombstone (`input/removed/<enqueued_id>`, for `ReplyWait`'s
|
|
413
|
-
`turn:`), claims the file as the drain does (one rename wins; a lost claim takes the tombstone back),
|
|
414
|
-
deletes it, and the Bridge announces `:turn_dequeued`. `200 {status: "removed", enqueued_id, prompt}`, `409 already_started`
|
|
415
|
-
(a turn took it), `409 not_queued` (a next-step message: a drain may merge it any moment), `404
|
|
416
|
-
unknown_enqueued_id`. It never wakes the worker. The worker names it in its sidecar features (`unqueue`).
|
|
417
|
-
A worker's start prunes the `input/removed/` and `input/settled/` tombstones older than a week
|
|
418
|
-
(`SessionInbox::TOMBSTONE_MAX_AGE`, log `tombstones_pruned`); otherwise they go with the session dir.
|
|
419
|
-
- `POST /session/:id/relay` — the approval relay, on a delegated child's worker
|
|
420
|
-
(`{action, relay_id, question_id}`): `opened` / `closed` (with a `reason`) set or clear the
|
|
421
|
-
pending question's `relayed_to` mark (`QuestionDesk#annotate`, `:question_relay`); `answered`
|
|
422
|
-
makes the child ask its parent's Bridge for the answer (`RelayVerifier`) and take it only for
|
|
423
|
-
this child and the question pending now. `200`, `409` no longer pending, `403` a parent agent's
|
|
424
|
-
pick beyond the child's own `guardrails.parent_answers` and session `parent_answers` (`parent_answer_refused`; the
|
|
425
|
-
relay card in the parent was held to the parent's), `422 relay_unverified` (the question
|
|
426
|
-
stays open).
|
|
427
|
-
- `POST /session/:id/relay/status` — on a parent's worker, `{relay_id}`: what its `RelayDesk`
|
|
428
|
-
(memory only) holds for that relay (`child_id`, `child_question_id`, `state`, `answer`, `by`);
|
|
429
|
-
`404` for an unknown id. A POST with a body because routes match exact paths.
|
|
430
|
-
- `POST /session/:id/command` — a session command (`/model`, `/models`, `!rollback`, `!cmd`,
|
|
431
|
-
`/continue`) for the worker loop; `202` with a `command_id` its `:command_ran` names, `400` when
|
|
432
|
-
the line isn't one. A `:command_ran` carries `thinking`, `reasoning_history` and `llm_context`
|
|
433
|
-
(the summaries the next turn runs under) when the command changed them or the model, and
|
|
434
|
-
`confirm: {line, text}` when a `/thinking` or `/reasoning-history` change from a `web:` or `tui:`
|
|
435
|
-
client asks first (`CacheConfirm`; nothing changed, `line` applies it).
|
|
436
|
-
- `POST /session/:id/exit` — ask the worker to exit now (`{client_id:, delete?, restart?, deadline?}`; `delete`:
|
|
437
|
-
`/exit --delete`, no recap written; `restart: true`: hand the session to a new worker on the newest chi
|
|
438
|
-
installed, `200 {status: "restarting"}`; an empty session's `exiting` carries `discard: true`). The worker checks with
|
|
439
|
-
the event log held (`WorkerIdleExit#hold_for_request`): `200 {status: "exiting"}` and it leaves
|
|
440
|
-
like an idle exit, or `409 {status: "held", reason:}` with `turn_running`, `input_queued`,
|
|
441
|
-
`continue_offered`, `client_connected` (a stream not named by the asker), `reminders`,
|
|
442
|
-
`background_tasks` (with `tasks: [{id, command}]`: the running tasks a finish wake watches), `starting`, or
|
|
443
|
-
`leaving_exit` / `leaving_delete` / `leaving_restart` when another client's exit of another kind is already taken
|
|
444
|
-
(`Worker#claim_exit`, claimed before a stop-all; the words are `WorkerIdleExit::LEAVING_WORDS`). A second exit of
|
|
445
|
-
the kind already taken is answered `200` as that one.
|
|
446
|
-
- `GET /session/:id/state` — `session_state_snapshot` (JSON).
|
|
447
|
-
- `GET /session/:id/stats` — `Engine#stats_snapshot` for attached `/stats`: the metrics, with the context window and prompt profile asked from the server before the first turn.
|
|
448
|
-
- `GET /session/:id/snapshot` — the snapshot frame's content as one request (the web server renders
|
|
449
|
-
the messages itself, then streams from its `event_seq`). Read by the web's full show
|
|
450
|
-
(`GET /api/sessions/:id`, `?parts=1`) and the plugin's `list_sessions`; every message is copied.
|
|
451
|
-
- `GET /session/:id/tail` — the page's light re-read (`Bridge#tail_frame`): `session_state_snapshot`,
|
|
452
|
-
`working_directory` (the session's folder, which the web checks the answer's file refs against),
|
|
453
|
-
`answer` (the last message a UI shows as an answer, `AnswerTail`: the one message, copied alone),
|
|
454
|
-
`cards` (`CardStore#list`; a card an earlier worker showed during a turn comes back read-only, with no
|
|
455
|
-
actions: its turn no longer runs), `event_seq`, `event_id`, all taken in one event-log hold; no message
|
|
456
|
-
list, so it costs the same however long the session is. `?turn_id=` answers that turn's answer
|
|
457
|
-
(an unknown or missing id: the newest); the only route that reads its query.
|
|
458
|
-
- No browser: only chi's own clients talk to a Bridge. A request with an `Origin` header, a `Sec-Fetch-Site`
|
|
459
|
-
other than `none`, or a `Host` that isn't loopback (a DNS-rebound page) is `403 cross_origin`
|
|
460
|
-
(`Bridge#browser_request?`, before any route); there is no CORS and no `OPTIONS` route. The browser reaches a
|
|
461
|
-
worker only through `chi web`. A request carrying any trace a proxy adds (`ProxyTrace`: `X-Forwarded-*`,
|
|
462
|
-
`Forwarded`, `X-Real-IP`, `X-Auth-Request-*`, `Cf-*`…, underscore twins too) is `403 proxied`
|
|
463
|
-
(`Bridge#proxied_request?`): an auth proxy that forwards every local port and adds such a header doesn't reach a
|
|
464
|
-
worker.
|
|
465
|
-
- **The secret.** Every request, on every route (the stream, the reads such as `state` and `stats`, the writes, and
|
|
466
|
-
an unknown path), names the Bridge's secret in `X-Chi-Bridge`; one without it, or with another one, is
|
|
467
|
-
`403 bridge_secret` with one session-specific message (`BridgeSecret.refused`): "session ID's worker refused
|
|
468
|
-
this chi's request (it didn't name its secret): a chi web or terminal started before chi was updated can't
|
|
469
|
-
reach it; restart chi web, re-attach with `chi --attach ID`, or restart the worker with `chi sessions restart
|
|
470
|
-
ID`." `chi sessions restart` can also recover when the worker cannot be asked: TERM + resume if disk shows no
|
|
471
|
-
running turn, pending question or approval, queued input, or running background task. The check is after the
|
|
472
|
-
browser and proxy checks and before any route (`Bridge#secret_ok?`,
|
|
473
|
-
`OpenSSL.secure_compare`). No route is left open: what the reads show is the session itself. Only a request that
|
|
474
|
-
got past it counts as client activity, so a refused one (an older chi web's polls) never holds the idle exit. Each Bridge makes its own (`SecureRandom.hex(32)`, 64 hex
|
|
475
|
-
characters) when it starts and writes it to `bridge.json`, which is 0600 from the moment it exists
|
|
476
|
-
(`AtomicFile` creates the temporary file with that mode, then renames it), so only this machine's user can read
|
|
477
|
-
it: a forward that adds no proxy header, or another local account, reaches the port but not a session. Clients get
|
|
478
|
-
it only from the sidecar: `WorkerLink.client`/`.await` and `BridgeClient.for_sidecar` take the port and the secret from the
|
|
479
|
-
same read (chi web makes its client from the sidecar for every request, the attached terminal again on a
|
|
480
|
-
`worker_changed`), so a respawned worker's new one is used and nothing passes a port by hand. A worker from before
|
|
481
|
-
the secret writes none and asks for none; a chi web or attached terminal from before it sends none, so a worker
|
|
482
|
-
the newer chi started refuses it until it is restarted. A client tells that refusal from a route's own 403
|
|
483
|
-
(`BridgeClient::Response#secret_refused?`) and shows the one message: chi web's relays answer 502 `bridge_secret`,
|
|
484
|
-
`chi answer` and a parent's approval relay say it instead of "refused", and an event stream ends with reason
|
|
485
|
-
`bridge_secret` once rediscovery finds no newer worker (the attached terminal's closing line), never retrying in
|
|
486
|
-
silence; chi web's stream proxy answers 502 `bridge_secret`, never passing the refusal on as frames. The secret is never logged (a refusal logs
|
|
487
|
-
`secret_refused` with the path, at most once a minute), never in an event, a reply, `/api/info` or a session
|
|
488
|
-
file, and `WorkerSidecar`, `BridgeClient` and `Bridge` leave it out of `#inspect`.
|
|
489
|
-
|
|
490
|
-
The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` frames
|
|
491
|
-
(`SessionMetrics#snapshot`) carry the session's totals and only the recent timing records: the
|
|
492
|
-
newest turn the worker finished and any it hasn't saved yet (at most 20 turns), with their tool
|
|
493
|
-
calls and the running turn's finished ones. The full history is the session's `analytics.json`,
|
|
494
|
-
which `SessionMetrics#persist` rewrites after each turn; the web server merges the two by id.
|
|
495
|
-
The totals' `tokens` block adds the cached, cache-write and reasoning sums, the reported cost (`cost_sum`) and the
|
|
496
|
-
estimated one (`cost_estimate_sum`, from `hosts.<name>.models` prices; 0 for none), the decode time and tokens
|
|
497
|
-
behind the average speed (`avg_decode_tps`) and the last speeds (`last_decode_tps`,
|
|
498
|
-
`last_prefill_tps`, `tps_source`: `server` or `estimate`). `model_segments` (always present) splits those sums
|
|
499
|
-
by model after a `/model` switch: one row per run of consecutive turns on one model (`SessionMetrics::ModelSegment`,
|
|
500
|
-
keyed on the turn records' `model_ref`), in order, with its turns, token and cost sums, speeds and the switch's prefill
|
|
501
|
-
(`prefill_tokens`, `prefill_cost_estimate`: the sums of its turn records' `switch_prefill_tokens` and
|
|
502
|
-
`switch_prefill_cost_estimate`, [sessions.md](sessions.md)); `[]` with one model, the oldest rows folded into one (`folded_segments`)
|
|
503
|
-
past 6; a running turn counts in its model's row (`ended_at` null). The rows add up to `tokens`. They are derived from
|
|
504
|
-
the records, never read back. Each `generation_completed` event
|
|
505
|
-
carries the generation's `speed` (`{decode_tps, source}`, null without one), its own prompt-cache
|
|
506
|
-
counts (`prompt_tokens`, `cached_tokens`, `cache_write_tokens`: null when the server reported no writes) and
|
|
507
|
-
those running `tokens` and `model_segments`: the Engine closes the generation in `SessionMetrics#finish_generation` before relaying
|
|
508
|
-
it, so the web's info bar updates per generation without parsing chunk payloads.
|
|
509
|
-
Side requests (the idle recap, a plugin's `ctx.ask_model` such as `/btw`) are counted apart, in a `side` block
|
|
510
|
-
(only when there were any; `SessionMetrics::SideTotals`: requests, token sums, `cost_sum` null until reported,
|
|
511
|
-
`cost_estimate_sum` from the target host's price, `by_kind`): `IdleClient` hands each reply's usage to its
|
|
512
|
-
`on_usage:` callback, `Engine#note_side_usage`, which folds it (`SessionMetrics#add_side`) and announces
|
|
513
|
-
`:side_usage` (`kind`, the whole `side` block) under the event lock, so the blocks arrive in fold order, then saves
|
|
514
|
-
`analytics.json` between turns (a running turn's end saves it). `tokens`, `model_segments` and the turn records
|
|
515
|
-
stay the turns' only. `SessionMetrics#persist` is serialized (its own mutex, taken before the metrics one), so two
|
|
516
|
-
threads' saves land in snapshot order.
|
|
517
|
-
A bundle's `ctx.cost` reads the same sums (`SessionMetrics#cost`, a frozen `SessionMetrics::Cost`): the `tokens`
|
|
518
|
-
block's `cost_sum` and `cost_estimate_sum` with the running turn's finished generations folded in, plus the `side`
|
|
519
|
-
block's, so it counts what earlier workers spent too ([plugins.md](plugins.md)). Its `ctx.usage` reads the work
|
|
520
|
-
(`SessionMetrics#usage`, a frozen `SessionMetrics::Usage`): the snapshot's `turns`, `tool_calls_total` /
|
|
521
|
-
`tool_calls_by_tool` and `iterations_total`, and `active_ms`, the generations' `gen_ms` plus the finished tool
|
|
522
|
-
records' `duration_ms` (tallied per tool as they are counted; `ask_user_question`'s, a wait on the user, left out).
|
|
523
|
-
|
|
524
|
-
The page's frequent reads of `GET /api/sessions/:id` stay light too:
|
|
525
|
-
- `?tail=1&recent=1` (each turn's end, a cancel, an answer display): `{tail, session: {id, status,
|
|
526
|
-
used_memory_names}, messages: [the answer, with its display and display_refs (re-read on an `answer_display` with a display or refs)], markdown_warning, timing}`, from the worker's `/tail`
|
|
527
|
-
alone (no session file parsed, no `analytics.json`); `timing` carries the worker's recent records
|
|
528
|
-
and `turn_count` (finished turns). The page merges the records by id (`timing.js mergeTiming`) and
|
|
529
|
-
finishes the ended turn's own line by its `turn_id` (sent as `&turn_id=`, which also picks that
|
|
530
|
-
turn's answer). A tail read's timing is always the newest turn's; `recent=1` is accepted and not needed.
|
|
531
|
-
The page binds an answer's `display_refs` to its bubble (`answer_refs.js`: on a reload's render and on this
|
|
532
|
-
read's re-render) and pairs them with the bubble's links and text before the `path:line` scan. A question's
|
|
533
|
-
refs (`pending_question.refs`, `:question_asked`) pair the same way (`markdown_place.js` `linkRefsIn`/`pairRefsIn`):
|
|
534
|
-
`question_requested` (or a snapshot's pending question) keeps them on the asking step's gen
|
|
535
|
-
(`turn_model.js` `questionRefs`) before the card draws, so the stage's asking slot (`stage_slots.js`
|
|
536
|
-
`drawAsking(text, refs)`) pairs them on its raw draw and again after its markdown swap; the card pairs its
|
|
537
|
-
question text and its kept reply (`question_cards.js`); the step's closed `.gen-text` takes them
|
|
538
|
-
(`turn_block.js`; on a reload its `data-answer-at`, the message's `display_refs`) and `linkTextRefs` pairs it at
|
|
539
|
-
the turn's end and after a reload. A closed step's text is markdown too with `web.markdown` on: a reload's
|
|
540
|
-
from its message's `html` (`turn_block.js` `textTemplate`), a live turn's from `POST /api/render` on its step's
|
|
541
|
-
first open after the turn's end (`markdown_place.js` `renderStepTexts`, which pairs its refs again after the
|
|
542
|
-
swap); the text as written stays on it as `data-copy-source` (`stepText`, a reloaded card's kept reply). A file ref outside the
|
|
543
|
-
session's folder is dropped, as a reload drops it: the folder `/tail` names, else (an older worker) the hub's
|
|
544
|
-
session list entry's, else (refs to check, no hub entry) the session file's; with no folder known at all, every
|
|
545
|
-
file ref is dropped rather than shown unchecked.
|
|
546
|
-
A plugin tool's row refs (`refs`, `RowRefs`) ride its tool events and, on a reload, its part (saved as
|
|
547
|
-
`tool_refs`); `turn_block.js` binds them to the row's title (`refs.js bindRowRefs`: the first on the title,
|
|
548
|
-
the others in its menu).
|
|
549
|
-
- `?timing=1`: the whole timing (`analytics.json` merged with the worker's `/state`) and
|
|
550
|
-
`turn_count`, when the page's merge came up short.
|
|
551
|
-
- `?cards=1`: the cards, from `/tail`; the session file is only stat'ed for the 404.
|
|
552
|
-
|
|
553
|
-
The stage view's working set (`web.working_set`) has its own read, `GET /api/sessions/:id/working_set`: the
|
|
554
|
-
`WorkingSet` projection (`{items, at, running}`: each file, memory and URL live, stale or forgotten, with its tokens)
|
|
555
|
-
of the worker's `/snapshot` messages, else the session file, under the session's LLM context strategy; while a turn
|
|
556
|
-
runs, the snapshot's `working_set` (the running turn's last event, which the messages lag) when it has one. Its live form
|
|
557
|
-
is the `:working_set_updated` event, which always wins over a GET's answer it overtook. The page overlays it on the
|
|
558
|
-
set it builds from its tool rows ([internals/working-set.md](internals/working-set.md)).
|
|
559
|
-
|
|
560
|
-
A worker without `/tail` (an older chi; `404 not_found`) is read through `/snapshot` in the same
|
|
561
|
-
shape; any other failure (no worker, a 500, a timeout) reads the disk, trimmed to the newest
|
|
562
|
-
turn's records.
|
|
563
|
-
|
|
564
|
-
Every route goes through `Bridge#dispatch`: an id other than the bridge's own session is
|
|
565
|
-
`404 unknown_session`, and a handler that raises answers `500 bridge_error` (logged as
|
|
566
|
-
`handler_failed`) rather than dropping the connection.
|
|
567
|
-
|
|
568
|
-
The per-session port is OS-assigned (bound to `0`) and published to a `bridge.json` sidecar
|
|
569
|
-
for client discovery, with the Bridge's secret (see above).
|
|
570
|
-
|
|
571
|
-
**Worker link.** Every client asks `WorkerLink` (`worker_link.rb`) whether a session's worker can be reached, never
|
|
572
|
-
reading `bridge.json` or probing a port itself: `WorkerLink.resolve(session_dir, unlink:, owner:)` answers a typed
|
|
573
|
-
outcome. The rule is the owner lock plus the sidecar's `pid`; there is no port probe (a port can be reused by another
|
|
574
|
-
session's Bridge, a free lock is the kernel's proof the worker died). In order: no lock holder, or a chi REPL's: a
|
|
575
|
-
sidecar is `Stale(:lock_free)` (removed with `unlink: true`, only if it is still the file that was read and the lock,
|
|
576
|
-
read again just before, is still free: a worker that took it meanwhile is resolved as that worker), else `None`;
|
|
577
|
-
a holder with no kind yet: `Starting`; a leaving worker: `Leaving` (marked before its Bridge stops, on every way out:
|
|
578
|
-
`Worker#teardown` marks it too, with `TEARDOWN_LEAVE_WAIT`); a worker whose sidecar names no port or another
|
|
579
|
-
pid than the holder's (a worker that has not written its own yet): `Starting`, `stuck` once it has held the lock
|
|
580
|
-
past `START_LIMIT` (30 s) with none; a sidecar whose `protocol` is newer than `WorkerLink::PROTOCOL`: `Skew` (a
|
|
581
|
-
newer worker; never driven, `wake` answers 409 `worker_newer`); else `Live` (its sidecar; `#client` makes the one
|
|
582
|
-
`BridgeClient`). A sidecar with no `pid` is an older worker's: the lock alone says it is live. `owner:` is the
|
|
583
|
-
holder as the caller already read it (nil: free), so one read serves a summary and its status. `WorkerLink.client` is
|
|
584
|
-
the Live's client or nil; `WorkerLink.await` polls for a new worker's Live (an `until:` check tells a restart's
|
|
585
|
-
successor from the old worker); chi web's `App#worker_link` / `#await_worker_link` wrap them. The Bridge writes
|
|
586
|
-
`pid` and `protocol` with its port and removes its sidecar on stop only when the file still carries its epoch.
|
|
587
|
-
|
|
588
|
-
`chi web`'s `GET /api/sessions/:id/stream` proxies this bridge
|
|
589
|
-
(503 `not_live` for no worker, a leaving or stuck worker, or an unreachable Bridge; 502 `worker_newer` for skew and
|
|
590
|
-
`bridge_error` / `bridge_secret` for a refused Bridge; 200 only after the Bridge answers; full history of any session
|
|
591
|
-
is served by `GET /api/sessions/:id/output`). It opens the Bridge before answering and gives the one opened socket to
|
|
592
|
-
`App#proxy_stream_body` / `ProxyStreamBody`, which closes it when the body or hijacked client ends. The owner's stream
|
|
593
|
-
is a byte pipe; the body gives it the request's frame filter (`App#stream_filter`: nil for the owner; for a participant, and the owner's participant view
|
|
594
|
-
`?as=participant`, a `Web::ParticipantStreamFilter`, which cuts the Bridge's chunks into frames and writes each again
|
|
595
|
-
from `Web::ParticipantView.event`'s allowlist, dropping every other event; only `from_seq` and `snapshot` reach the
|
|
596
|
-
Bridge; [internals/participants.md](internals/participants.md#what-a-participant-sees)). Resume/ring-buffer state is **in-memory**, per worker: a cursor from
|
|
597
|
-
an earlier worker gets the `reset` snapshot, not a replay.
|
|
318
|
+
process that already owns the `Engine`); every worker starts it. It keeps what a client that joins late needs
|
|
319
|
+
(`RingBuffer` of recent events, `TurnAccumulator` for the turn in progress, `CardStore` for cards and notices), writes
|
|
320
|
+
each client's stream with an `SSEWriter` (`id: <event_seq>-<epoch>` cursors, a `snapshot` or `reset` frame when a
|
|
321
|
+
cursor cannot be replayed), and answers HTTP routes: `GET /session/:id/stream`, `state`, `snapshot`, `tail`, `stats`
|
|
322
|
+
and the writes `POST …/turn`, `cancel`, `answer`, `command`, `exit` and more. Only chi's own clients reach it (never a
|
|
323
|
+
browser; a proxy trace is refused), and every request names the Bridge's secret from the 0600 `bridge.json`. `chi web`
|
|
324
|
+
proxies the stream and relays the writes. The routes, wire shapes, replay rules, refusals and the worker link
|
|
325
|
+
(`WorkerLink`: whether a session's worker can be reached) are in [internals/bridge.md](internals/bridge.md).
|
|
326
|
+
|
|
598
327
|
|
|
599
328
|
**Session hub.** The cross-session layer (which sessions exist, who owns them, what changed)
|
|
600
329
|
never pulls from the page: `chi web` runs one `Samagotchi::Web::SessionHub` (a thread inside
|
|
@@ -636,7 +365,8 @@ The owner's update routes publish progress and results as `chi_update` events wi
|
|
|
636
365
|
`version` and may include a progress line. The hub retains the latest event for a reconnecting
|
|
637
366
|
tab's snapshot. `POST /api/update` decides itself whether this install can take a gem
|
|
638
367
|
(`UpdateCheck.blocked_reason`: a checkout, Bundler, an unwritable gem dir → 409 `update_blocked`), so
|
|
639
|
-
it holds with `update.notify: off`; a child that prints no rows fails with its own last line,
|
|
368
|
+
it holds with `update.notify: off`; a child that prints no rows fails with its own last line, a child still running after
|
|
369
|
+
30 minutes (`UpdateOps::UPDATE_TIMEOUT`; it has a process group of its own) is stopped with what it started and fails the same way, and a
|
|
640
370
|
done install refreshes the hub's installed version (`SessionHub#refresh_installed`) before its `done`.
|
|
641
371
|
Worker restarts target every worker older than the version installed now (refreshed the same way)
|
|
642
372
|
and publish per-session results. Only one of an install, a worker restart pass and a web restart runs
|
|
@@ -656,17 +386,23 @@ and installs it through `UpdateOps#install(wait: true, auto: true)` when the ins
|
|
|
656
386
|
runs, no hub summary has `status: running` or a `pending_question`, and the version isn't in `auto_failed` or
|
|
657
387
|
already in the `auto` record (one automatic try per version). Its frames carry `auto: true` (the page: "Automatic
|
|
658
388
|
chi update in progress", "(automatic)" in the sheet's title). Its record is `update_check.json`'s `auto`:
|
|
659
|
-
`{version, from, at, kept, phase: installed|workers|web|done|failed, workers: {restarted, left}, detail, pushed}
|
|
389
|
+
`{version, from, at, kept, phase: installed|workers|web|retrying|done|failed, workers: {restarted, left}, detail, pushed}`,
|
|
390
|
+
in Ruby the value object `Web::AutoUpdateRecord`, which owns that shape (`from_h`, `to_store`).
|
|
660
391
|
With `update.restart: never` the run ends at the install (`chi_notice` `installed`). With `idle` it goes on
|
|
661
|
-
(`phase` `installed` → `workers` → `web` → `done`): `UpdateOps#restart_workers(select:)` over the stale workers whose
|
|
392
|
+
(`phase` `installed` → `workers` → `web` → `retrying` → `done`): `UpdateOps#restart_workers(select:)` over the stale workers whose
|
|
662
393
|
summary is idle with no question and `updated_at` at least 10 minutes old (`QUIET`; the others are `skipped` rows;
|
|
663
|
-
the worker's own atomic refusal still applies)
|
|
394
|
+
the worker's own atomic refusal still applies); then, with no
|
|
664
395
|
turn running, `Server.restart_preflight` (the restart's command with `--version`, 20 s, killed on a timeout; it must
|
|
665
396
|
print a chi at least the release) and, if it passes, the record's `phase: web` is written **before**
|
|
666
397
|
`UpdateOps#restart_web`. A failed preflight, or a refused restart, publishes `restart_failed` and ends the run
|
|
667
398
|
(`failed`). `AutoUpdate#stop` waits `STOP_WAIT` (25 s, longer than the preflight's timeout) for its thread, and a
|
|
668
399
|
preflight that ends after a stop leads to nothing (no `web`, no exec): the next start takes the run up again. The restarted chi web's AutoUpdate (first step after 5 s) finds `phase: web`, checks it runs the
|
|
669
|
-
release, counts the workers once more and publishes `updated`, then `done
|
|
400
|
+
release, counts the workers once more and publishes `updated`, then `done`, or `retrying` while workers are left. Those are
|
|
401
|
+
retried each step until none is left or 24 h (`RETRY_FOR`) have passed since the install, under the same filter, and from
|
|
402
|
+
whichever chi web is up (`workers` before the restart, `retrying` after it, and a restart mid-retry reads it from the
|
|
403
|
+
record). The time is checked before each pass, so a run always ends. A later pass is `quiet:`: no `chi_update` frames (each
|
|
404
|
+
is a sticky toast) unless a worker restarts, and no further `chi_notice`. `retrying` isn't an open phase for the install
|
|
405
|
+
gate: a newer release installs under its usual gate, and its record replaces this one, its own passes taking the workers over. A step that raises is logged (`auto_update_failed`) and taken again at the next poll; each one saves the record before it publishes, so the owner hears it once. Results reach the phone as `chi_notice`
|
|
670
406
|
hub events (`SessionHub#publish_notice`), see [internals/web-push.md](internals/web-push.md).
|
|
671
407
|
|
|
672
408
|
The page's scope is in its URL, and the server picks the default: `?dir=<folder>` is that folder's
|
|
@@ -873,11 +609,11 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
873
609
|
| Bundle authoring | `bundle_command.rb` (`BundleCommand`), `bundle_command/{scaffold,check,test_run}.rb`, `bundle_command/try/{run,sandbox,trace}.rb`, `testing.rb` + `testing/` (the test kit) | `chi bundle new`, `check`, `test` and `try`, and the kit a bundle's tests load it with ([bundle-authoring.md](bundle-authoring.md), [internals/bundles.md](internals/bundles.md)). |
|
|
874
610
|
| Session config (`--set`) | `session_config.rb` (`SessionConfig`, `SessionConfig::Settings`) | A session's own settings as one value (`Session#settings`, `SessionSetup#settings`): `core`, the `--set KEY=VALUE` values of the core keys marked `session: true` (`Config::Entry`), typed by `Config.coerce!` and read first through `Config.get(key, session:)` (`RecapSetup`; `Engine#session=` rebuilds the recap job, `IdleScheduler#replace_job`), file key `config_settings`; `bundles`, the `--set BUNDLE.KEY=VALUE` values, checked against the manifest's `session_settings:`, laid over `bundles: <name>:` in `ctx.settings` ([plugins.md](plugins.md)), file key `bundle_settings`. `SessionConfig.clash` refuses a bundle setting named like a core one (install, `chi bundle check`). See [configuration.md](configuration.md#per-session-settings). |
|
|
875
611
|
| Plugin API | `plugin/api.rb` (`Plugin::Api`), `plugin/tool_spec.rb` (`Plugin::ToolSpec`: a checked tool declaration, how it is registered), `plugin/loader.rb`, `plugin/context.rb` (`ctx`), `plugin/http.rb` + `plugin/http_transport.rb` (`ctx.http`), `plugin/card.rb` (`Plugin::Card`) | What a bundle's plugin registers: tools, commands, annotation actions, hooks, services, toolkits, peer sources; what its handlers get as `ctx` ([plugins.md](plugins.md)). |
|
|
876
|
-
| Peer bundles' base | `plugin/peer.rb` (`Plugin::Peer`) | What claude-peer and codex-peer share (the send tool, the command, the cards, payloads); each bundle's subclass sets its strings and transport ([
|
|
612
|
+
| Peer bundles' base | `plugin/peer.rb` (`Plugin::Peer`) | What claude-peer and codex-peer share (the send tool, the command, the cards, payloads); each bundle's subclass sets its strings and transport ([bundles.md](bundles.md#the-claude-peer-bundle)). |
|
|
877
613
|
| Shipped bundles | `bundles/` (`mcp`, `github-pr`, `loop-guard`, `skills`, `source-links`, …) | Bundles that ship with chi. |
|
|
878
614
|
| Hooks | `hooks/registry.rb` (`Hooks::Registry`), `hooks/loader.rb`, `hook_routes.rb` (`HookRoutes`), `event_routes.rb` (`EventRoutes`) | Hook registration and dispatch; what a hook or plugin does beyond reading its event (notice, question, stop, steer) routed to the UIs (cards and notices through one set of routes) ([hooks.md](hooks.md)). |
|
|
879
615
|
| Guardrails | `guardrails.rb`, `guardrails/shell_lex.rb`, `guardrails/data_readers.rb`, `guardrails/rules.rb`, `guardrails/protected_paths.rb`, `guardrails/parent_policy.rb` + `guardrails/parent_approvals.rb`, `guardrail_wiring.rb` | The gate before every tool call: shell lexing (and the commands that only read a heredoc as data), YAML rules, the core checks on chi's own files, approvals, what a parent agent may answer (`guardrails.parent_answers`) ([guardrails.md](guardrails.md)). |
|
|
880
|
-
| System 1 | `system_one.rb`, `system_one/` (`SystemOne::Client`, `ApprovalQuestion`, `Log`, `Record`, `SelfProtect`, `Similar`, the backends), `guardrail_wiring.rb` (`GuardrailWiring#request_approval`), `guardrails_command.rb` (`chi guardrails replay`) | The approval judge: a model's `p` for each ask, logged to `judgments.jsonl` with the user's answer; in `on` mode an ask on a listed rule with a clean record is allowed once without the user. Labelled by `/guardrails wrong` (`SessionCommands`) and the web's **should have asked** (the Bridge's `POST guardrails/wrong`) ([guardrails.md](guardrails.md#system-1-experimental)). |
|
|
616
|
+
| System 1 | `system_one.rb`, `system_one/` (`SystemOne::Client`, `Recording`, `Runs`, `ApprovalQuestion`, `Log`, `Record`, `SelfProtect`, `Similar`, the backends), `guardrail_wiring.rb` (`GuardrailWiring#request_approval`), `guardrails_command.rb` (`chi guardrails replay`) | The approval judge: a model's `p` for each ask, logged to `judgments.jsonl` with the user's answer; in `on` mode an ask on a listed rule with a clean record is allowed once without the user. Labelled by `/guardrails wrong` (`SessionCommands`) and the web's **should have asked** (the Bridge's `POST guardrails/wrong`) ([guardrails.md](guardrails.md#system-1-experimental)). Every core ask goes through `SystemOne::Recording`, saved as a run under `<state dir>/system_one/runs/` (`Runs`, `RunsStore`, `ApprovalLabel`, `judgments_command.rb`: [system-one.md](system-one.md)). |
|
|
881
617
|
| Delegation | `tools/delegate.rb`, `tools/delegate_result.rb`, `tools/delegate_wait.rb`, `child_reports.rb`, `tools/delegate_relay.rb`, `relay_desk.rb`, `relay_verifier.rb`, `relay_watcher.rb` | Child sessions a session starts and waits on, and their approval relay ([sessions.md](sessions.md#delegating)). |
|
|
882
618
|
| send / broadcast | `send_command.rb` (`SendCommand`), `broadcast_command.rb` (`BroadcastCommand`), `broadcast/` | `chi send` and `chi broadcast` ([broadcast.md](broadcast.md), [sub-agent.md](sub-agent.md)). |
|
|
883
619
|
| Worker link | `worker_link.rb` (`WorkerLink`: `Live`, `Starting`, `Leaving`, `Skew`, `Stale`, `None`), `worker_sidecar.rb` (`WorkerSidecar`) | Whether a session's worker can be reached and the client for it: every Bridge client asks `WorkerLink.resolve` / `.client` / `.await` (*Worker link* above); `WorkerSidecar` is the `bridge.json` it reads. |
|
|
@@ -896,7 +632,7 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
896
632
|
| Images / vision | `image_store.rb` (`ImageStore`), `vision_support.rb`, `vision_context.rb` | Stored prompt images and whether a model takes them. |
|
|
897
633
|
| 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)). |
|
|
898
634
|
| 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)). |
|
|
899
|
-
| Tool output cut | `tool_output_cut.rb` (`ToolOutputCut`), `tool_output_files.rb` (`ToolOutputFiles`) | A tool's output over `max_tool_output_chars`, cut by the tool's policy; an execute's whole output saved in the session's `tool_output/` folder ([internals/tool-
|
|
635
|
+
| 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)). |
|
|
900
636
|
| 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)). |
|
|
901
637
|
| Prompt files | `prompt_files.rb` (`PromptFiles`) | The AGENTS.md chain (root → cwd) and the appended files the system prompt carries. |
|
|
902
638
|
| Model settings | `effective_model.rb` (`EffectiveModel`, `ModelChoice`), `model_profile.rb` (`ModelProfile`), `model_settings.rb`, `sampling_settings.rb`, `model_catalog.rb` (`ModelCatalog`), `model_notes.rb` (`ModelNotes`) | The effective model's settings as one snapshot per read, a model's prompt format, its `models:` entry, its request parameters, the pickable model rows (`chi models`, `/api/models`), the notes its prompt loads. |
|