samagotchi 0.90.0 → 0.92.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 +79 -1
- data/README.md +5 -2
- data/bin/chi +4 -4
- data/docs/architecture.md +17 -13
- data/docs/bundle-authoring.md +6 -11
- data/docs/cli.md +7 -4
- data/docs/configuration.md +8 -4
- data/docs/context.md +5 -3
- data/docs/hooks.md +3 -3
- data/docs/internals/background-tasks.md +27 -2
- data/docs/internals/bundles.md +4 -4
- data/docs/internals/engine.md +21 -8
- data/docs/internals/prompt-caching.md +4 -0
- data/docs/internals/tool-guardrails.md +10 -2
- data/docs/memory.md +6 -0
- data/docs/plugins.md +30 -13
- data/docs/releasing.md +3 -0
- data/docs/sessions.md +4 -4
- data/lib/samagotchi/bridge.rb +11 -7
- data/lib/samagotchi/bundle_command/check.rb +43 -12
- data/lib/samagotchi/bundle_command/scaffold.rb +12 -0
- data/lib/samagotchi/bundle_command/test_run.rb +12 -0
- data/lib/samagotchi/bundle_command/try/command.rb +164 -0
- data/lib/samagotchi/bundle_command/try/render.rb +80 -0
- data/lib/samagotchi/bundle_command/try/run.rb +35 -17
- data/lib/samagotchi/bundle_command/try/sandbox.rb +113 -34
- data/lib/samagotchi/bundle_command/try/trace.rb +26 -15
- data/lib/samagotchi/bundle_command.rb +11 -213
- data/lib/samagotchi/bundle_session_settings.rb +82 -45
- data/lib/samagotchi/bundles/core/manifest.yml +2 -2
- data/lib/samagotchi/bundles/cost-cap/manifest.yml +6 -5
- data/lib/samagotchi/bundles/cost-cap/plugin.rb +57 -12
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/self_map.md +5 -0
- data/lib/samagotchi/child_env.rb +50 -0
- data/lib/samagotchi/client_id.rb +3 -0
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_note.rb +13 -6
- data/lib/samagotchi/engine.rb +31 -15
- data/lib/samagotchi/hooks/registry.rb +2 -0
- data/lib/samagotchi/kernel_loop.rb +14 -3
- data/lib/samagotchi/last_turn.rb +3 -2
- data/lib/samagotchi/llm/chat_loop.rb +3 -1
- data/lib/samagotchi/llm/errors.rb +4 -4
- data/lib/samagotchi/memory_bundle/builder.rb +3 -2
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +7 -2
- data/lib/samagotchi/memory_bundle/installer.rb +1 -0
- data/lib/samagotchi/memory_bundle/manifest.rb +42 -8
- data/lib/samagotchi/memory_bundle/provenance.rb +2 -1
- data/lib/samagotchi/parent_report.rb +12 -0
- data/lib/samagotchi/reply_wait.rb +6 -2
- data/lib/samagotchi/sandbox_layout.rb +66 -0
- data/lib/samagotchi/send_command.rb +3 -3
- data/lib/samagotchi/session_manager.rb +12 -20
- data/lib/samagotchi/task_finish_note.rb +90 -0
- data/lib/samagotchi/task_watch.rb +228 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +19 -4
- data/lib/samagotchi/terminal_ui/formatting.rb +7 -2
- data/lib/samagotchi/testing/bundle.rb +32 -19
- data/lib/samagotchi/testing/install.rb +26 -17
- data/lib/samagotchi/testing/sandbox.rb +45 -37
- data/lib/samagotchi/testing/waiting.rb +5 -7
- data/lib/samagotchi/tool_declarations.rb +16 -0
- data/lib/samagotchi/tool_response.rb +28 -6
- data/lib/samagotchi/tools/builtins.rb +5 -1
- data/lib/samagotchi/tools/task_record.rb +9 -0
- data/lib/samagotchi/tools/task_runtime.rb +2 -2
- data/lib/samagotchi/tools/task_wait.rb +10 -3
- data/lib/samagotchi/turn_checkpoint.rb +23 -10
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/app.js +24 -14
- data/lib/samagotchi/web/public/cards_place.js +4 -1
- data/lib/samagotchi/web/public/images.js +6 -0
- data/lib/samagotchi/web/public/model_pick.js +10 -0
- data/lib/samagotchi/web/public/notify.js +4 -4
- data/lib/samagotchi/web/public/toasts.js +17 -9
- data/lib/samagotchi/web/public/turn_events.js +13 -1
- data/lib/samagotchi/web/public/unqueue.js +20 -4
- data/lib/samagotchi/worker.rb +104 -42
- data/lib/samagotchi/worker_idle_exit.rb +26 -3
- data/lib/samagotchi/worker_inbound.rb +18 -4
- data/lib/samagotchi/worker_task_wakes.rb +160 -0
- data/lib/samagotchi/worker_wakes.rb +11 -4
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6797c1728cd7acf0fc6d82e0aa5a1e6f6b65d6af6b5c69869f34c722d2176b0
|
|
4
|
+
data.tar.gz: a140559d9438ab5f16a6096b1f949280d67bfc062026c334544b9b312f9b9656
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 28133e65ee4aed6e07f685a2b65045c0c157306e406eb02836c8e9549c80d9d99391edc087d6eb711e0f522c21be3d0c83276804a73d0f4f03e6d91c379b569c
|
|
7
|
+
data.tar.gz: fdc100fd5b8f528b3f2fbce1e4ca25c1817a811c1fb0d32369e7c0ceb36e426c546f0819e41fefea3b7ddfdebfe7e9de0d9209faefe2177421f41b4acb30c701
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,82 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.92.0] - 2026-10-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- A bundle's manifest can type its session settings (`session_settings: {cap: number}`: `number`, `boolean`,
|
|
16
|
+
`string` or `any`); `--set` and `POST /api/sessions` refuse a value of another type (exit 2 / 400), and a
|
|
17
|
+
`string` key keeps its text as given.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `--set BUNDLE.KEY=VALUE` (and `bundle_settings` strings in `POST /api/sessions`) type values as config.yml does:
|
|
22
|
+
for a bundle that lists its keys plainly (or types them `any`), `yes`/`on` are now `true`, `no` is now `false`
|
|
23
|
+
(any case: `No`, `ON`), and `.5`, `5.`, `1e3` are now numbers; before they reached the plugin as text. A bundle
|
|
24
|
+
that wants the text types the key `string`.
|
|
25
|
+
- Test kit: `Samagotchi::Testing::Install.run(...).registered` is an `Install::Registered` value
|
|
26
|
+
(`registered.commands`, `.tools`, `.hooks`, `.inits`, `.toolkits`, `.tool_sources`, `.peer_sources`), its commands
|
|
27
|
+
and hooks `Install::Command` / `Install::Hook` values, not a Hash of arrays: a bundle spec reading
|
|
28
|
+
`registered[:commands]` needs `registered.commands`.
|
|
29
|
+
- Test kit: `Install::Result#ok?` is gone; `result.failures.empty?` says the same.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- A Ctrl-C during a turn keeps what the turn did, as a Stop does: its finished tool calls and their results stay in
|
|
34
|
+
the session with the cancel note (a call it cut short reads as interrupted), so the next turn knows what already
|
|
35
|
+
ran and the note lists the background tasks the turn started. `chi bundle try --timeout` now shows that turn's
|
|
36
|
+
tool calls with their arguments.
|
|
37
|
+
- `chi --help` lists `chi sessions restart`, `chi bundle trash` and the bundle authoring subcommands (`new`, `test`,
|
|
38
|
+
`check`, `try`).
|
|
39
|
+
- Web: removing a queued message (its ×) and opening another session before the worker answered no longer puts the
|
|
40
|
+
removed text into that session's composer; a toast names the session it came from and stays until
|
|
41
|
+
dismissed, with Copy for the full text.
|
|
42
|
+
- Web: images added to the composer while a message with images was still uploading or sending stay after it is sent,
|
|
43
|
+
instead of vanishing with the sent ones.
|
|
44
|
+
- Web: when the model list can't be read, the start page says so in a toast (a new session starts on the default
|
|
45
|
+
model) instead of just leaving out the model picker.
|
|
46
|
+
- `--set cost-cap.cap=.5` and `=1e1` set a cap (they were read as text and turned the cap off); `--set
|
|
47
|
+
cost-cap.cap=five` is refused (exit 2) instead of silently running without a cap. A `cap:`, `child:` or `default:`
|
|
48
|
+
in config.yml that isn't a number (or is infinite, `1e400`) now warns once, and a bad `cap:`/`child:` falls back to
|
|
49
|
+
the next one. Run `chi update` (cost-cap 0.2.1).
|
|
50
|
+
- An attached-context wake turn stopped before the model call keeps its note as a plain one and pauses wakes until
|
|
51
|
+
you write, as delegate and task wake turns do; it left the note marked as a turn's start.
|
|
52
|
+
- A background task that finished just as the idle worker was about to exit still wakes the session (the worker
|
|
53
|
+
stays up for its wake); one that finished before the worker left for another reason is a note for the next turn.
|
|
54
|
+
A task whose turn you stopped no longer keeps the worker up or holds `/exit`.
|
|
55
|
+
- `chi send --wait` on a session command delivered as a file reads the command's real outcome (failed, or canceled
|
|
56
|
+
when it was refused mid-turn or dropped), not always completed, and says so as the command's ("the command
|
|
57
|
+
failed"), not a turn's.
|
|
58
|
+
- `chi bundle try` no longer signals its child's process group after the child exited (the group id may name
|
|
59
|
+
another process by then), and no longer waits 0.1 s after every run. A `--timeout` stops the background tasks the
|
|
60
|
+
turn started along with the child, and the delegate workers and tasks a run leaves behind are stopped before the
|
|
61
|
+
sandbox is deleted.
|
|
62
|
+
- `chi bundle check` warns (`clash`) about an installed bundle of the same name whose record can't be read, instead
|
|
63
|
+
of skipping the clash check without a word.
|
|
64
|
+
|
|
65
|
+
Update with `chi update` (the system bundle 0.92.0, cost-cap 0.2.1, core 0.3.4), then restart `chi web`.
|
|
66
|
+
|
|
67
|
+
## [0.91.0] - 2026-10-19
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- A background task the model left running (`task_create`, an `execute` handed over at its limit) that finishes
|
|
72
|
+
unseen wakes the idle session for a short turn opened by a `TASK FINISHED` note (exit code, duration, log tail).
|
|
73
|
+
`session.task_wakes`: `wake` (default), `queue` (a note for the next turn) or `off`.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- The bundle-authoring doc says `chi bundle try` is the live check; its `chi send` recipe for checking the installed
|
|
78
|
+
copy is gone.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- `chi bundle try --json` reports always carry `timed_out`; a run cut by `--timeout` lists the tool calls it made
|
|
83
|
+
(name, status and duration; their arguments aren't saved) instead of an empty `tools`.
|
|
84
|
+
|
|
85
|
+
Update with `chi update` (the system bundle 0.91.0), then restart `chi web`.
|
|
86
|
+
|
|
11
87
|
## [0.90.0] - 2026-10-19
|
|
12
88
|
|
|
13
89
|
### Changed
|
|
@@ -3529,7 +3605,9 @@ and long-lived sessions.
|
|
|
3529
3605
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3530
3606
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3531
3607
|
|
|
3532
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
3608
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.92.0...HEAD
|
|
3609
|
+
[0.92.0]: https://github.com/dm1try/samagotchi/compare/v0.91.0...v0.92.0
|
|
3610
|
+
[0.91.0]: https://github.com/dm1try/samagotchi/compare/v0.90.0...v0.91.0
|
|
3533
3611
|
[0.90.0]: https://github.com/dm1try/samagotchi/compare/v0.89.0...v0.90.0
|
|
3534
3612
|
[0.89.0]: https://github.com/dm1try/samagotchi/compare/v0.88.0...v0.89.0
|
|
3535
3613
|
[0.88.0]: https://github.com/dm1try/samagotchi/compare/v0.87.0...v0.88.0
|
data/README.md
CHANGED
|
@@ -80,7 +80,8 @@ the config. With a config already there, it adds a `hosts:` entry and keeps
|
|
|
80
80
|
the rest of the file. Then it installs the `core` bundles (loop-guard,
|
|
81
81
|
check-in, guardrails, slow-commands, agents-md, cost-cap: the safety set for a
|
|
82
82
|
small local model, the cost cap inert until you set one) and, on a terminal, offers `dev` (known-names, mcp, btw, skills,
|
|
83
|
-
source-links
|
|
83
|
+
source-links, github-pr, coordinator, claude-peer, codex-peer, ruby; see
|
|
84
|
+
[Bundle profiles](docs/memory.md#bundle-profiles-core-and-dev)). Edit the
|
|
84
85
|
file later as in [Configure](#configure); `chi bootstrap --help` has the
|
|
85
86
|
options.
|
|
86
87
|
|
|
@@ -216,8 +217,10 @@ once, for the session, for the repo, or for the whole rule in the repo.
|
|
|
216
217
|
- [Guardrails](docs/guardrails.md): allow / ask / deny for tool calls, rules, approvals
|
|
217
218
|
- [Hooks](docs/hooks.md): plugin hooks and bundle hooks
|
|
218
219
|
- [Plugins](docs/plugins.md): bundle plugins (commands, tools, hooks)
|
|
220
|
+
- [Writing a bundle](docs/bundle-authoring.md): `chi bundle new`, `test`, `check`, `try`, `install --dev`
|
|
221
|
+
- [Testing](docs/testing.md): specs, affected specs, lint, worktrees
|
|
219
222
|
- [Architecture](docs/architecture.md): Engine, TerminalUI, bridge, web
|
|
220
|
-
- 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-guardrails.md), [background tasks](docs/internals/background-tasks.md)
|
|
223
|
+
- 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-guardrails.md), [background tasks](docs/internals/background-tasks.md), [bundles](docs/internals/bundles.md), [Anthropic Messages API](docs/internals/anthropic.md)
|
|
221
224
|
|
|
222
225
|
## Development
|
|
223
226
|
|
data/bin/chi
CHANGED
|
@@ -95,13 +95,13 @@ parser = OptionParser.new do |opts|
|
|
|
95
95
|
chi bootstrap [HOST[:PORT]|URL] first setup: find the model server, write config.yml
|
|
96
96
|
chi scratch [options] a one-time session in this terminal: nothing is kept
|
|
97
97
|
chi web [--port PORT] [--open] [--scope=all] the web UI
|
|
98
|
-
chi sessions <list|stop|archive|unarchive|delete|prune|clean|stats>
|
|
98
|
+
chi sessions <list|stop|restart|archive|unarchive|delete|prune|clean|stats>
|
|
99
99
|
chi note [--source NAME] [-m TEXT] ID...|--all background context for sessions
|
|
100
100
|
chi broadcast [-m TEXT] [--all] [--dry-run] | log | deliver a note for every session it may concern
|
|
101
101
|
chi context <add|push|ls|show|refresh|rm|mute|unmute> live external text attached to sessions
|
|
102
102
|
chi send [-m TEXT] ID...|--new a message into sessions, as if typed there
|
|
103
103
|
chi answer ID --question QID --option N answer a question a session waits on
|
|
104
|
-
chi bundle <install|upgrade|uninstall|status|diff|list|build>
|
|
104
|
+
chi bundle <install|upgrade|uninstall|status|diff|list|build|trash|new|test|check|try>
|
|
105
105
|
chi desktop <install|upgrade|uninstall|status> macOS "Send to chi" helper
|
|
106
106
|
chi models [--format json] [TEXT] the models --model takes, from every host
|
|
107
107
|
chi self version, paths, model and bundles
|
|
@@ -126,8 +126,8 @@ parser = OptionParser.new do |opts|
|
|
|
126
126
|
# as --tool's negation); this is its help line.
|
|
127
127
|
opts.separator " --no-tool NAME Leave this tool out of the toolkit (repeatable; a comma list too)"
|
|
128
128
|
opts.on("--set BUNDLE.KEY=VALUE", "A bundle setting for this session, over config.yml's bundles: (repeatable; a key the " \
|
|
129
|
-
"bundle's manifest lists in session_settings:; numbers
|
|
130
|
-
"saved in the session: --set cost-cap.cap=3)") do |v|
|
|
129
|
+
"bundle's manifest lists in session_settings:; numbers and booleans (true, off, no) are typed, the rest is " \
|
|
130
|
+
"text; a value of the wrong type is refused; saved in the session: --set cost-cap.cap=3)") do |v|
|
|
131
131
|
(options[:sets] ||= []) << v
|
|
132
132
|
end
|
|
133
133
|
opts.on("--parent-answers LEVEL", "What a parent agent may answer on this session's questions: " \
|
data/docs/architecture.md
CHANGED
|
@@ -301,10 +301,10 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
301
301
|
An optional `deadline` (epoch seconds; `BridgeClient` sends 5/6 of its read timeout ahead) makes
|
|
302
302
|
a request read after it (a worker frozen by sleep or SIGSTOP) answer `408 deadline_passed` and
|
|
303
303
|
not run: a client that timed out has said the message was not sent. `/answer`,
|
|
304
|
-
`/question/dismiss` and `/
|
|
305
|
-
log held, as a turn is), and the Bridge logs `turn_expired`, `answer_expired`,
|
|
306
|
-
or `
|
|
307
|
-
("… so the command was not run"). `/cancel
|
|
304
|
+
`/question/dismiss`, `/command`, `/turn/dequeue` and `/exit` take the same `deadline` (a command is
|
|
305
|
+
checked with the event log held, as a turn is), and the Bridge logs `turn_expired`, `answer_expired`,
|
|
306
|
+
`dismiss_expired`, `command_expired`, `dequeue_expired` or `exit_expired`. The web app answers either
|
|
307
|
+
kind of timeout with `504 worker_timeout` ("… so the command was not run"). `/cancel` and `/recap` take none.
|
|
308
308
|
The web relays answer, dismiss, command and cancel the same way (`App#relay`): a 408 or a read
|
|
309
309
|
timeout is `504 worker_timeout`; a Bridge 404 for a route an older worker lacks is
|
|
310
310
|
`501 not_supported` with the restart hint; a Bridge `404 unknown_session`, no bridge or a
|
|
@@ -330,7 +330,7 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
330
330
|
saved messages, or the running turn's accumulator); answers once the
|
|
331
331
|
task is gone: `200 {status, stop_reason, task_id}`, `404 task_not_found`, `409 not_running`.
|
|
332
332
|
The worker names it in its sidecar features (`task_stop`).
|
|
333
|
-
- `POST /session/:id/turn/dequeue` — `{enqueued_id, client_id?}`: take back a queued message
|
|
333
|
+
- `POST /session/:id/turn/dequeue` — `{enqueued_id, client_id?, deadline?}`: take back a queued message
|
|
334
334
|
(one that waits for the running turn's end: `--queue`, Alt+Enter, `/queue TEXT`, or a message
|
|
335
335
|
with images) before a turn takes it (web route `POST /api/sessions/:id/turn/:enqueued_id/dequeue`,
|
|
336
336
|
the × on a queued bubble; `/unqueue` in the attached TUI). Under the event log, like its enqueue:
|
|
@@ -356,11 +356,13 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
356
356
|
(the summaries the next turn runs under) when the command changed them or the model, and
|
|
357
357
|
`confirm: {line, text}` when a `/thinking` or `/reasoning-history` change from a `web:` or `tui:`
|
|
358
358
|
client asks first (`CacheConfirm`; nothing changed, `line` applies it).
|
|
359
|
-
- `POST /session/:id/exit` — ask the worker to exit now (`{client_id
|
|
359
|
+
- `POST /session/:id/exit` — ask the worker to exit now (`{client_id:, delete?, restart?, deadline?}`; `delete`:
|
|
360
|
+
`/exit --delete`, no recap written; `restart: true`: hand the session to a new worker on the newest chi
|
|
361
|
+
installed, `200 {status: "restarting"}`; an empty session's `exiting` carries `discard: true`). The worker checks with
|
|
360
362
|
the event log held (`WorkerIdleExit#hold_for_request`): `200 {status: "exiting"}` and it leaves
|
|
361
363
|
like an idle exit, or `409 {status: "held", reason:}` with `turn_running`, `input_queued`,
|
|
362
|
-
`continue_offered`, `client_connected` (a stream not named by the asker), `reminders
|
|
363
|
-
`starting`.
|
|
364
|
+
`continue_offered`, `client_connected` (a stream not named by the asker), `reminders`,
|
|
365
|
+
`background_tasks` (with `tasks: [{id, command}]`: the running tasks a finish wake watches) or `starting`.
|
|
364
366
|
- `GET /session/:id/state` — `session_state_snapshot` (JSON).
|
|
365
367
|
- `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.
|
|
366
368
|
- `GET /session/:id/snapshot` — the snapshot frame's content as one request (the web server renders
|
|
@@ -581,22 +583,24 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
|
|
|
581
583
|
|
|
582
584
|
| Component | Main file(s) | What it is |
|
|
583
585
|
|---|---|---|
|
|
584
|
-
| Worker | `worker.rb` (`worker_idle_exit.rb`, `worker_inbound.rb`, `worker_signals.rb`) | A session's background process: builds the `Engine` and `Bridge`, runs queued turns, exits when idle. A spawned one traps TERM, HUP and INT (`WorkerSignals`, installed by `run_session_loop` when `SAMAGOTCHI_WORKER_PROCESS=1`): the running turn stops as a Stop with reason `signal`, is saved, and the worker leaves (`:signal`, exit 0; a new worker only for input already queued). A running turn is checkpointed at its iteration boundaries (`TurnCheckpoint`, the `interrupted_turn` mark), and the next worker settles a turn its predecessor died in (`TurnCheckpoint.settle`). The session is saved from the turn's thread, Bridge threads and a signal's thread; `Session#save` takes a per-session lock around its snapshot and write, so an older snapshot never lands over a newer one. |
|
|
586
|
+
| Worker | `worker.rb` (`worker_idle_exit.rb`, `worker_inbound.rb`, `worker_signals.rb`, `worker_wakes.rb`, `worker_task_wakes.rb`, `task_watch.rb`) | A session's background process: builds the `Engine` and `Bridge`, runs queued turns and the wake turns nobody typed (delegate reports, a background task that finished unseen: [Finish wake](internals/background-tasks.md#finish-wake), attached context), exits when idle. A spawned one traps TERM, HUP and INT (`WorkerSignals`, installed by `run_session_loop` when `SAMAGOTCHI_WORKER_PROCESS=1`): the running turn stops as a Stop with reason `signal`, is saved, and the worker leaves (`:signal`, exit 0; a new worker only for input already queued). A running turn is checkpointed at its iteration boundaries (`TurnCheckpoint`, the `interrupted_turn` mark), and the next worker settles a turn its predecessor died in (`TurnCheckpoint.settle`). The session is saved from the turn's thread, Bridge threads and a signal's thread; `Session#save` takes a per-session lock around its snapshot and write, so an older snapshot never lands over a newer one. |
|
|
585
587
|
| Config | `config.rb` | Every setting as an `Entry`: env, config.yml and CLI layers ([configuration.md](configuration.md)). |
|
|
586
588
|
| Paths | `paths.rb` (`Paths`) | The XDG base dirs chi's files live under, read from the env on every call: `Paths.state_home` (`$XDG_STATE_HOME`, else `~/.local/state`) and `Paths.config_home` (`$XDG_CONFIG_HOME`, else `~/.config`), both defaults under `Dir.home`; a blank variable counts as unset, a relative one is made absolute from the current directory. `Paths.tilde` shows a path with `~` for the home folder. `SessionManager` hands a spawned worker the spawner's `XDG_STATE_HOME` / `XDG_CONFIG_HOME`, when set, made absolute, so a relative one isn't read from the session's folder. |
|
|
587
589
|
| MemoryBundle | `memory_bundle.rb`, `memory_bundle/{installer,builder,installed_bundle,plugin_ref,bundle_hook}.rb` (`Installer`, `Builder`, `InstalledBundle`, `PluginRef`, `BundleHook`) | Installing, building and reading bundles of memories, hooks and plugins. |
|
|
588
|
-
|
|
|
590
|
+
| 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)). |
|
|
591
|
+
| Bundle session settings | `bundle_session_settings.rb` (`BundleSessionSettings`) | A session's own `--set BUNDLE.KEY=VALUE` values, checked against the manifest's `session_settings:`, laid over `bundles: <name>:` in `ctx.settings` ([plugins.md](plugins.md)). |
|
|
592
|
+
| 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)). |
|
|
589
593
|
| 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 ([plugins.md](plugins.md#the-claude-peer-bundle)). |
|
|
590
594
|
| Shipped bundles | `bundles/` (`mcp`, `github-pr`, `loop-guard`, `skills`, `source-links`, …) | Bundles that ship with chi. |
|
|
591
595
|
| 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)). |
|
|
592
|
-
| Guardrails | `guardrails.rb`, `guardrails/shell_lex.rb`, `guardrails/rules.rb`, `guardrail_wiring.rb` | The gate before every tool call: shell lexing, YAML rules, approvals ([guardrails.md](guardrails.md)). |
|
|
593
|
-
| Delegation | `tools/delegate.rb`, `tools/delegate_result.rb`, `tools/delegate_wait.rb`, `child_reports.rb`, `relay_desk.rb` | Child sessions a session starts and waits on, and their approval relay ([sessions.md](sessions.md#delegating)). |
|
|
596
|
+
| 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)). |
|
|
597
|
+
| 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)). |
|
|
594
598
|
| 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)). |
|
|
595
599
|
| Idle work | `idle_scheduler.rb` (`IdleScheduler`), `idle_recap.rb` (`IdleRecap`), `idle_reminders.rb` | One poller for an idle session's recap and reminders. |
|
|
596
600
|
| Attached context | `context_sources.rb` (`ContextSources`), `context_poller.rb`, `context_absorber.rb`, `peer_sources.rb` (`PeerSources`: the sources peer bundles declare) | External sources a session keeps fresh, and the peer loop's wakes ([context.md](context.md)). |
|
|
597
601
|
| LLM context strategies | `llm_context_strategy.rb`, `llm_context_view.rb`, `llm_context_stale.rb`, `llm_context_forget.rb` | What a request sends of the history (none, stale, forget) ([internals/llm-context-forget.md](internals/llm-context-forget.md)). |
|
|
598
602
|
| Session chains | `session_continue.rb` (`SessionContinue`), `session_chain.rb` (`SessionChain`) | A session that continues an earlier one. `SessionChain.opener_for` gives the text web `continue →` prefills in the new link: the previous link's own first typed message when that link continues one itself, else nil (the 201's `opener`; not saved) ([sessions.md](sessions.md)). |
|
|
599
|
-
| 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/side_totals.rb` (`SessionMetrics::SideTotals`) and `side_usage.rb` (`SideUsage`): the side requests' sums, apart from the turns'. |
|
|
603
|
+
| 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/side_totals.rb` (`SessionMetrics::SideTotals`) and `side_usage.rb` (`SideUsage`): the side requests' sums, apart from the turns'. |
|
|
600
604
|
| Images / vision | `image_store.rb` (`ImageStore`), `vision_support.rb`, `vision_context.rb` | Stored prompt images and whether a model takes them. |
|
|
601
605
|
| 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)). |
|
|
602
606
|
| Tool index | `tools/tool_index.rb` (`Tools::ToolIndex`) | `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)). |
|
data/docs/bundle-authoring.md
CHANGED
|
@@ -57,7 +57,8 @@ plugin:
|
|
|
57
57
|
|
|
58
58
|
`plugin.rb` defines `class Plugin` with `register(chi)`; `initialize(settings)` (optional) gets config.yml
|
|
59
59
|
`bundles: my-bundle:` as a Hash with string keys, once, when the plugin loads. A value a session may set
|
|
60
|
-
(`chi --set my-bundle.KEY=V`, listed in the manifest's `session_settings
|
|
60
|
+
(`chi --set my-bundle.KEY=V`, listed in the manifest's `session_settings:`, typed with `{KEY: number}` so a
|
|
61
|
+
value of another type is refused) is read from `ctx.settings` at call
|
|
61
62
|
time. `chi bundle check --set KEY=VALUE` gives the sandboxed load a setting.
|
|
62
63
|
|
|
63
64
|
## What a hook sees
|
|
@@ -138,13 +139,7 @@ class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather
|
|
|
138
139
|
|
|
139
140
|
## Verifying it live
|
|
140
141
|
|
|
141
|
-
`chi bundle try . -p "
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
grep plugin_loaded ~/.local/state/samagotchi/samagotchi.log | tail -1 # bundle=my-bundle commands=1 hooks=1
|
|
146
|
-
chi sessions delete --force SESSION_ID # leave no test session behind
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
(`$XDG_STATE_HOME/samagotchi/samagotchi.log` when `XDG_STATE_HOME` is set.) Then tell the user what it adds,
|
|
150
|
-
how to use it, and that a new session picks it up.
|
|
142
|
+
`chi bundle try . -p "..."` **is** the live check: a real model turn on the installed bundle, with its trace
|
|
143
|
+
(tools, plugin, notices, cards). Don't repeat it with `chi send` sessions on the user's own install. After the
|
|
144
|
+
final `chi bundle install .`, `chi bundle list` showing the bundle and its version is enough. Then tell the user
|
|
145
|
+
what it adds, how to use it, and that a new session picks it up.
|
data/docs/cli.md
CHANGED
|
@@ -83,9 +83,9 @@ chi bootstrap # try localhost 8080, 11434, 1234, 8000
|
|
|
83
83
|
or with anchors gets the lines printed to paste instead. `--dry-run` shows
|
|
84
84
|
what it would write.
|
|
85
85
|
- **The bundles.** Unless writing the config failed, it installs the system
|
|
86
|
-
bundle and the `core` profile (loop-guard, check-in, guardrails, slow-commands), one line
|
|
86
|
+
bundle and the `core` profile (loop-guard, check-in, guardrails, slow-commands, agents-md, cost-cap), one line
|
|
87
87
|
each; on a terminal it then asks `Also install dev (known-names, mcp, btw,
|
|
88
|
-
skills, source-links)? [y/N]`. Run again, it installs nothing new: a bundle
|
|
88
|
+
skills, source-links, github-pr, coordinator, claude-peer, codex-peer, ruby)? [y/N]`. Run again, it installs nothing new: a bundle
|
|
89
89
|
you uninstalled stays out ([Bundle profiles](memory.md#bundle-profiles-core-and-dev)).
|
|
90
90
|
A bundle that fails to install makes the exit 1. `--dry-run` says what it
|
|
91
91
|
would install.
|
|
@@ -149,7 +149,10 @@ non-interactive mode. `--keep` retains the temporary sandbox and prints its path
|
|
|
149
149
|
one experimental JSON object to stdout and human trace lines to stderr; its keys are `session_id`, `exit_code`,
|
|
150
150
|
`answer`, `tools` (`index`, `name`, `args`, `status`, `duration_ms`), `notices`, `cards`, `questions`,
|
|
151
151
|
`interactive_blocked`, `used_memories`, `tokens` (`prompt`, `completion`), `cost`, `cost_is_estimate`, `model`,
|
|
152
|
-
`sandbox`, `denied` (1-based tool indexes)
|
|
152
|
+
`sandbox`, `denied` (1-based tool indexes), `stderr_tail` (last 4,000 characters) and `timed_out`. A run past
|
|
153
|
+
`--timeout` is interrupted like a Ctrl-C and still prints the trace it got to, with `timed_out: true` (text output:
|
|
154
|
+
the trace lines, then `timed out after N seconds` on stderr). That turn keeps its tool calls, as a Ctrl-C does, so
|
|
155
|
+
they show with their `args`; a call the interrupt cut short has status `interrupted`. Exit status: 2 for usage,
|
|
153
156
|
1 for install/turn failure or timeout, 130 for Ctrl-C, otherwise the child's status.
|
|
154
157
|
|
|
155
158
|
`try` runs the bundle's code and the model's tools with your real HOME, account and keys. Its temporary working
|
|
@@ -277,7 +280,7 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
|
|
|
277
280
|
| `--thinking LEVEL` | This session's own thinking level: `off`, `low`, `medium` or `high`, saved in the session and first in the order (`default` unsets it). On `--resume` or `--attach` it is set at once, through a `/thinking` on an attached worker. With `chi web` it is instead the default for the sessions its workers run, below a session's own (as env `SAMAGOTCHI_THINKING_LEVEL` is). See "Thinking" in configuration.md. |
|
|
278
281
|
| `--toolkit NAME`, `--tool NAME`, `--no-tool NAME` | A new session's toolkit, the tools its prompts declare: `full` (every built-in), `lean`, `child`, one of config's `toolkit.kits` or a bundle's (without `--toolkit`: config's `toolkit.default`), with tools declared on top of it (`--tool`) or left out (`--no-tool`); both repeatable, a comma list too. Saved in the session as given; a respawned worker, a fork and a continue keep it. Ignored with `--attach`/`--resume` (one line, as `--memory`); `/toolkit` changes it. Not on `chi web` (config's `toolkit.default` covers its sessions). See [Toolkits](configuration.md#toolkits). |
|
|
279
282
|
| `--parent-answers LEVEL` | A new session's own `guardrails.parent_answers` (`hard`, `standard` or `soft`): what a parent agent may answer on its questions. It may only tighten config.yml's: the stricter of the two applies, and a looser one is saved as given with a warning (`--parent-answers soft is looser than config.yml's guardrails.parent_answers: standard; standard applies`). Saved in the session; a respawned worker, a fork and a continue keep it, a `delegate` child takes its parent's. Ignored with `--attach`/`--resume` (one line, as `--toolkit`); refused with `chi web`. Only you loosen it: `/parent-answers` in the terminal or the web. See [Guardrails](guardrails.md#answers-from-a-parent-agent). |
|
|
280
|
-
| `--set BUNDLE.KEY=VALUE` | A bundle setting for a new session (repeatable; a later one of the same key wins), laid over config.yml's `bundles: <bundle>:` in what the bundle's plugin reads (`ctx.settings`): `--set cost-cap.cap=3`. The value is typed simply: an integer (`3`, `-1`) or a decimal (`2.5`) is a number, `true`
|
|
283
|
+
| `--set BUNDLE.KEY=VALUE` | A bundle setting for a new session (repeatable; a later one of the same key wins), laid over config.yml's `bundles: <bundle>:` in what the bundle's plugin reads (`ctx.settings`): `--set cost-cap.cap=3`. The value is typed simply: an integer (`3`, `-1`) or a decimal (`2.5`, `.5`, `1e1`) is a number, `true`, `yes` and `on` are true, `false`, `no` and `off` are false (any case, as in config.yml), anything else stays the text as given (`010`, `0x10`, `3:30`, `y`, `null`), and so does every value of a key the manifest types `string`. For cost-cap, `0`, a negative number or `off` is no cap. Only a key the installed bundle's manifest lists in `session_settings:` is taken, with a value of the type it gives the key: a bundle that isn't installed (or whose plugin doesn't load), a key it doesn't list (a record installed before the bundle listed it says to run `chi update` or `chi bundle upgrade NAME`), a value of another type (`--set cost-cap.cap=five`), or another shape (`--set foo`, `--set a.b.c=1`) is an error naming what it takes, exit 2, nothing started. Saved in the session; a respawned worker, a fork and a continue keep it, a delegate child doesn't. Ignored with `--attach`/`--resume` (one line, as `--toolkit`); refused with `chi web`. See [Per-session settings](plugins.md#per-session-settings). |
|
|
281
284
|
| `--profile NAME` | Prompt profile (`qwen36` or `gemma4`) for every model in this run, over config and the server's template (same as `--model-profile`, env `SAMAGOTCHI_MODEL_PROFILE`). See "Prompt profile" in configuration.md. |
|
|
282
285
|
| `--memory NAME` | Preload a memory entry into the system prompt (repeatable; a comma list too). Merged under the config.yml `memories:` baseline. Works attached: the list is stored on the session, so its worker builds the same prompt on every respawn. |
|
|
283
286
|
| `--append-system-prompt FILE` | Put `FILE`'s text into the system prompt, after AGENTS.md and before the memory indexes (repeatable; relative to the shell's directory). Added after the config.yml `append_system_prompt:` list. A missing or unreadable file is an error, exit 1. Saved in the session, so a respawned worker, a fork and a continue keep it; ignored with `--attach`/`--resume` (one line, as `--memory`). See [Appended prompt files](configuration.md#appended-prompt-files). |
|
data/docs/configuration.md
CHANGED
|
@@ -73,7 +73,8 @@ session:
|
|
|
73
73
|
# keep_empty: false # true keeps sessions nothing happened in (default: deleted when left)
|
|
74
74
|
# max_children: 4 # running sessions one session may have delegated at a time (the delegate tool)
|
|
75
75
|
# delegate_reports: wake # a child's reply reaches its parent by itself: wake (an idle parent runs a turn), queue, off
|
|
76
|
-
#
|
|
76
|
+
# task_wakes: wake # a background task finishing unseen: wake (an idle session runs a turn), queue, off
|
|
77
|
+
# max_wakes: 10 # wake turns in a row (delegate reports, finished tasks, context, peers) with no human input
|
|
77
78
|
|
|
78
79
|
# Baseline memories preloaded into the system prompt (same shape as --memory);
|
|
79
80
|
# name entries you have, or chi warns at each start.
|
|
@@ -117,7 +118,9 @@ Besides the settings sections, the file holds maps that are read by their
|
|
|
117
118
|
own subsystems: `hosts:` (below), `models:` (see "Prompt profile" and
|
|
118
119
|
"Images"), `model_aliases:`, `hooks:` (see [Hooks](hooks.md)),
|
|
119
120
|
`guardrails:` (see [Guardrails](guardrails.md)), `bundles:` (a bundle's
|
|
120
|
-
settings for its hooks, see [Hooks: Settings](hooks.md#settings)
|
|
121
|
+
settings for its hooks, see [Hooks: Settings](hooks.md#settings); a session's own
|
|
122
|
+
`--set BUNDLE.KEY=VALUE`, for a key the bundle's manifest lists in `session_settings:`, lays over them, see
|
|
123
|
+
[Per-session settings](plugins.md#per-session-settings)),
|
|
121
124
|
`memories:` and `append_system_prompt:` (see "Appended prompt files"). Their entry names (host names, model ids, aliases) are yours to
|
|
122
125
|
choose. The `memories:` list is the persistent baseline for preloaded memory entries —
|
|
123
126
|
the same name shape as `--memory` (bare name or `scope/name`), merged under
|
|
@@ -160,7 +163,7 @@ or `chi web` restart.
|
|
|
160
163
|
| `hooks`, `bundles` | Worker start | Hooks, plugins and their bundle settings load with the engine. |
|
|
161
164
|
| `recap.*` | Worker start | The engine builds its recap job once. |
|
|
162
165
|
| `session.shared` | Next `chi` invocation | It selects attached-worker or in-process mode for that invocation. |
|
|
163
|
-
| `session.idle_exit_minutes`, `session.keep_empty`, `session.max_children`, `session.delegate_reports`, `session.max_wakes` | Worker start or next matching lifecycle decision | Existing workers keep their idle-exit timer; the other values are read for the next corresponding decision. |
|
|
166
|
+
| `session.idle_exit_minutes`, `session.keep_empty`, `session.max_children`, `session.delegate_reports`, `session.task_wakes`, `session.max_wakes` | Worker start or next matching lifecycle decision | Existing workers keep their idle-exit timer; the other values are read for the next corresponding decision. |
|
|
164
167
|
| `session.retention_*`, `session.max_count`, `session.keep_status`, `session.sweep_interval_hours` | Next retention sweep | `chi web` sweeps on its configured schedule and on `GET /api/sessions`; `chi sessions prune` uses the values for that invocation. The sweep interval is checked on the next hub tick. |
|
|
165
168
|
| `web.*` | `chi web` start | Restart `chi web` to change its bind address, port or page settings. |
|
|
166
169
|
| `log.*` | Process start | Each process holds its configured log sink and level after first use; restart that process. |
|
|
@@ -1622,7 +1625,8 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1622
1625
|
| `session.keep_empty` | `false` | | Keep sessions nothing happened in. |
|
|
1623
1626
|
| `session.max_children` | `4` | | Running delegated sessions one session may have. |
|
|
1624
1627
|
| `session.delegate_reports` | `wake` | | When a delegate child ends a turn, its reply reaches the parent: `wake` runs an idle parent's turn for it, `queue` waits for the parent's next turn, `off` leaves it to `delegate_result`. |
|
|
1625
|
-
| `session.
|
|
1628
|
+
| `session.task_wakes` | `wake` | | When a background task the session started (`task_create`, an `execute` handed over at its limit) finishes and the model hasn't seen it finish: `wake` runs an idle session's turn opened by a `TASK FINISHED` note, `queue` leaves the note for the next turn, `off` leaves it to `task_wait`. See [Finish wake](internals/background-tasks.md#finish-wake). |
|
|
1629
|
+
| `session.max_wakes` | `10` | | Wake turns a session runs in a row with no human input: for delegate reports, finished background tasks, attached context and peer answers; past it reports wait for the next turn and task finishes and context updates arrive as notes. |
|
|
1626
1630
|
| `session.retention_days` | `14` | yes | Delete sessions not updated for N days; `0` = forever. See [Sessions](sessions.md). |
|
|
1627
1631
|
| `session.max_count` | `500` | yes | Keep the newest N; `0` = uncapped. |
|
|
1628
1632
|
| `session.keep_status` | none | yes | Comma list of statuses never pruned (a session a worker or `chi` has open is never pruned anyway). |
|
data/docs/context.md
CHANGED
|
@@ -249,8 +249,8 @@ you're away from the terminal. All of these must hold:
|
|
|
249
249
|
- the session's worker is up and idle: no turn running, nothing queued, no
|
|
250
250
|
question or continue offer waiting, not just started;
|
|
251
251
|
- under `session.max_wakes` turns in a row with no message from you (the
|
|
252
|
-
budget delegate reports use too; a turn that
|
|
253
|
-
write);
|
|
252
|
+
budget delegate reports and finished background tasks use too; a turn that
|
|
253
|
+
failed, or was stopped before the model call, pauses wakes until you write);
|
|
254
254
|
- the source hasn't woken the session in the last 10 minutes (a peer
|
|
255
255
|
source has its own limit instead: below).
|
|
256
256
|
|
|
@@ -266,7 +266,9 @@ notifications count it as a finished turn however short.
|
|
|
266
266
|
A wake turn that fails goes back to before it and keeps its note as a
|
|
267
267
|
plain one; the model reads `[SYSTEM: the previous turn failed before any
|
|
268
268
|
answer: … The wake turn for the change in attached context <name> was not
|
|
269
|
-
answered; chi starts no other wake turn until the user writes.]`.
|
|
269
|
+
answered; chi starts no other wake turn until the user writes.]`. One
|
|
270
|
+
stopped before the model call (a Stop, a `before_turn` hook's cancel) goes
|
|
271
|
+
back the same way, its note plain, and pauses wakes too.
|
|
270
272
|
|
|
271
273
|
### Peer sources: the peer loop
|
|
272
274
|
|
data/docs/hooks.md
CHANGED
|
@@ -590,7 +590,7 @@ hooks:
|
|
|
590
590
|
event: after_tool_call
|
|
591
591
|
on_error: log
|
|
592
592
|
priority: 100
|
|
593
|
-
trust_level: reviewed # reviewed | experimental (default)
|
|
593
|
+
trust_level: reviewed # reviewed | trusted | experimental (default)
|
|
594
594
|
requires_chi: ">= 0.12.0" # optional: a gem-style requirement, as for plugins
|
|
595
595
|
needs: [gh] # optional: outside commands the memories use (docs/memory.md#bundles-that-need-outside-commands)
|
|
596
596
|
```
|
|
@@ -608,12 +608,12 @@ Notes:
|
|
|
608
608
|
- Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order.
|
|
609
609
|
- Settings: a hook class with `initialize(settings = {})` gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
|
|
610
610
|
- 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.
|
|
611
|
-
- Shipped bundles (each 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 [Plugins](plugins.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Plugins](plugins.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Plugins](plugins.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Plugins](plugins.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 [Plugins](plugins.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 [Plugins](plugins.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Plugins](plugins.md#the-cost-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Plugins](plugins.md#the-ruby-bundle))
|
|
611
|
+
- Shipped bundles (each 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 [Plugins](plugins.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Plugins](plugins.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Plugins](plugins.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Plugins](plugins.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 [Plugins](plugins.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 [Plugins](plugins.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Plugins](plugins.md#the-cost-cap-bundle)), `chi bundle install ruby` (a plugin: an RSpec run with no examples at all, a hint, see [Plugins](plugins.md#the-ruby-bundle)) `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Plugins](plugins.md#the-skills-bundle)), `chi bundle install github-pr` (attached context: the branch's open PR, see [Plugins](plugins.md#the-github-pr-bundle)), `chi bundle install coordinator` (a plugin: parallel work in worktrees and child sessions, see [Plugins](plugins.md#the-coordinator-bundle)) and `chi bundle install claude-peer` / `codex-peer` (a plugin: talk to a Claude Code or Codex session, see [Plugins](plugins.md#the-claude-peer-bundle)).
|
|
612
612
|
- 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).
|
|
613
613
|
|
|
614
614
|
Lifecycle:
|
|
615
615
|
|
|
616
|
-
- `chi bundle install <source>` copies the hook files its manifest's `hooks:` map lists (only those; with no `hooks:` map, every `hooks/*.rb`) to `$XDG_CONFIG_HOME/samagotchi/memories/.bundles/<name>/hooks/` (`$XDG_CONFIG_HOME` defaults to `~/.config`) and persists metadata + `trust_level` + `source_commit` (git HEAD) to provenance.
|
|
616
|
+
- `chi bundle install <source>` copies the hook files its manifest's `hooks:` map lists (only those; with no `hooks:` map, every `hooks/*.rb`) to `$XDG_CONFIG_HOME/samagotchi/memories/.bundles/<name>/hooks/` (`$XDG_CONFIG_HOME` defaults to `~/.config`) and persists metadata + `trust_level` + `source_commit` (git HEAD) to provenance. `install --dev <dir>` links them to `<dir>`'s files instead, with no sha256 check ([cli.md](cli.md#writing-a-bundle-new-test-check)).
|
|
617
617
|
- `Engine.new` loads `config.yml` hooks first, then bundle hooks via `MemoryBundle::Provenance.each_installed(holding: :hooks)` → `Hooks::BundleLoader.load`. Bundle hooks are process-scoped (they survive the per-turn `clear_hooks`; only plain hooks are cleared). Experimental bundles emit a one-line startup warning.
|
|
618
618
|
- `chi bundle status`, `diff`, `uninstall`, `build` are hook-aware (counts, metadata, removal).
|
|
619
619
|
|
|
@@ -19,7 +19,7 @@ Behavior:
|
|
|
19
19
|
|
|
20
20
|
- `execute`, `task_create` and the `!cmd` shell bang run the command in a non-login shell: `/bin/sh -c` on Linux; on macOS, when `/bin/zsh` is there, `/bin/zsh --emulate sh +o ignore_braces +o sh_glob -o bash_rematch +o bsd_echo -c` (macOS's `/bin/sh` is bash 3.2, which can't parse an apostrophe in a heredoc inside `"$( )"`, the usual commit message shape). That mode reads no rc files and keeps POSIX word splitting, unmatched globs left as is, `{1..3}`, `[[ … =~ (…) ]]` with `BASH_REMATCH`, arrays, `$'…'`, `&>` and echo expanding `\t`. Bash-only builtins and variables (`shopt`, `BASH_VERSION`, `${!prefix*}`, `read -p`) are not there.
|
|
21
21
|
- Task metadata and output are persisted under `tmp/tasks/`: each task's `tmp/tasks/<id>/task.json` is a `TaskRecord` (`lib/samagotchi/tools/task_record.rb`, a value object: `.from_file` reads it, `#to_file` is the JSON object written, an update is a `#with`). Its keys keep their order; `stderr_path` and `handed_over_by` (a handed-over execute) and `stop_requested_by` (a stop under way or done) are left out until set. `bundles/slow-commands` reads these files on its own, so the shape is a contract.
|
|
22
|
-
- A task record keeps its process's pid and start time (`pid_started`). Before `task_stop` signals, the pid must still lead its own process group and must not have started at another time; otherwise the pid was reused by another process, nothing is signalled and the task ends as failed. A start time that can't be read at that moment (a failed `ps`) falls back to the group check, as does a record without `pid_started` (from an older chi). `task_get`, `task_list`, `task_wait`'s polls and the running-task lists (a cancelled turn's note, a worker's
|
|
22
|
+
- A task record keeps its process's pid and start time (`pid_started`). Before `task_stop` signals, the pid must still lead its own process group and must not have started at another time; otherwise the pid was reused by another process, nothing is signalled and the task ends as failed. A start time that can't be read at that moment (a failed `ps`) falls back to the group check, as does a record without `pid_started` (from an older chi). `task_get`, `task_list`, `task_wait`'s polls and the running-task lists (a cancelled turn's note, a worker's restart hold, the finish wake's watch) make the same check, so a task whose pid was reused reads as exited (failed) at once, not as running until a `task_stop`; on macOS that is one `ps` per running task per check (~5 ms).
|
|
23
23
|
- A stop (`task_stop`, the user's Stop of an `execute`, the execute limit in kill mode) TERMs the task's process group, then KILLs it after a grace. The groups its processes started are stopped too: a GNU `timeout N cmd` (without `--foreground`) moves itself and its command to a group of its own. `ProcessGroup.stop` finds them with one `ps -A -o pid=,ppid=,pgid=` (~40 ms on macOS) before the TERM and again before the KILL, walking down from the group's members. A process whose parent already exited (reparented to init, e.g. a setsid'd `cmd &`, or a backgrounded `timeout N cmd &` after its shell exited) is not found and runs on.
|
|
24
24
|
- Task listing is workspace-scoped (current project only).
|
|
25
25
|
- `execute` runs its command with stdout and stderr in two logs under `$XDG_STATE_HOME/samagotchi/execute/<id>/` (always an absolute path: a relative `XDG_STATE_HOME` is taken from the directory chi started in; outside the workspace: `git status` and `rm -rf tmp` never see them), stdin `/dev/null` and the same `exit_code` wrapper as a task. Its own copies of the logs hold a shared `flock` across the spawn: while a fresh exclusive `flock` fails, some process the command started still has them open. When the shell exits, a background child still holding them (`server &`) gets 1.5 s, then its group is stopped; one holding none (`nohup server >log 2>&1 &`) is left alone. A holder that left the group (setsid) is out of the stop's reach: it runs on, the result ends with the "left running" hint, and its dir stays for it to write to (the sweep removes it once no one holds the logs). Otherwise a finished command's dir is removed. Known gap on Linux: a command that writes through `/dev/stdout` or `/dev/stderr` (`echo b >/dev/stdout`) reopens the log file itself with `O_TRUNC`, so the output before it is lost (macOS dups the descriptor; the fix would be a relay pipe). The check needs the state dir on a local file system: over NFS, `flock` is emulated with `fcntl` locks, which children don't inherit.
|
|
@@ -27,11 +27,36 @@ Behavior:
|
|
|
27
27
|
- A stop of an `execute` (the user's Stop, the limit in kill mode) is done once the shell is reaped and nothing holds the logs: a child that ignores TERM after its shell died gets the KILL.
|
|
28
28
|
- A command still running at `execute.timeout_sec` is handed over as a task (`execute.on_timeout: task`, the default; `kill` stops it instead, and the `!cmd` bang always kills): the result starts `Still running at the 120s limit: moved to a background task, not killed.`, then `task_id: …` and the tails of both logs so far. A timeout during the 1.5 s background grace is a kill (the shell is gone, nothing to hand over). If the record can't be written, the command is killed. `TaskRuntime.adopt` writes a record for the process execute already started, with `handed_over_by: "execute"`. Its logs stay where execute wrote them, so the record's paths are absolute: `output_path` (stdout), `stderr_path` (stderr, a log of its own) and `exit_code_path` (a `task_create` record's `exit_code_path` is absolute too; its `output_path` is relative). `started_at` (and `created_at`) is the spawn time, not the hand-over's; `pid_started` is read at the hand-over, while execute's detach waiter is still running. From there it is an ordinary task: `task_wait`, `task_get`, `task_list` and `task_stop` treat it as any other. Its dir gets a `task` back-pointer file holding the record's absolute path. Once per process, the first `execute` sweeps `…/execute/`: a dir whose logs no process holds is removed when it has no back-pointer and is a day old (chi died mid-command), when its record is gone, or when it is 14 days old. The records live in each project's `tmp/tasks`, which the state dir can't list: hence the back-pointer.
|
|
29
29
|
- `task_wait` and `task_get` show `stderr_path` when the record has one; `task_wait`'s output tail is followed by a `stderr_tail:` of at most 10 lines when that log isn't empty. A `task_create` task has one log (stdout and stderr together) and no `stderr_path`. A `task_wait` result longer than `max_tool_output_chars` keeps its header and the tail's end, with a gap line naming `output_path`, where the whole log already is (and `stderr_path`, when the record has one) (`ToolOutputCut`'s `:head_tail_ref`; nothing is copied; see [Tool output guardrails](tool-guardrails.md#the-runners-cut-max_tool_output_chars)).
|
|
30
|
-
- Which tasks are a session's own (a cancelled turn's note,
|
|
30
|
+
- Which tasks are a session's own (a cancelled turn's note, a worker's restart hold, the finish wake below, the web's stop-task button): the ids its `task_create` results and its handed-over executes name (`TaskRuntime.created_ids_in`, `created_id`). An execute's id is read only after its first line (`Execute::HANDED_OVER`, through `TaskRuntime.handed_over_id`; the web has a JS twin in `task_stop.js`), not from a `task_id:` line inside a run, which the command can print. (A command that prints the `---` separator chi joins a turn's results with, then a hand-over line, still fakes one: a known gap.) Either result may start with the `ran as: …` line of a gate-corrected call.
|
|
31
31
|
- `task_get` returns metadata and `output_path`; use `read` for output contents.
|
|
32
32
|
- `task_wait` accepts `timeout`, `tail_lines` (maximum 100), and `done_pattern` (a regular expression string).
|
|
33
33
|
- `task_create` accepts `env` as a JSON object string for deterministic overrides such as `PATH`; use an absolute interpreter path when that is simpler. Ruby/Bundler isolation variables remain protected.
|
|
34
34
|
- `execute` and `task_create` export chi's own markers into the command, set over the model's `env` (which can't set them: `Error: env key is reserved`): `SAMAGOTCHI_PARENT_SESSION` (the session's id, `chi` without one; a chi started there answers as a parent agent), `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown) `SAMAGOTCHI_SESSION_THINKING` (the session's own thinking level; unset when it has none) and `SAMAGOTCHI_SESSION_REASONING_HISTORY` (its own reasoning_history level, the same way). `chi self` reads the last three for its model, thinking level and reasoning history. The four are `TaskRuntime::MARKER_ENV_KEYS` (and `SessionManager::SESSION_MARKER_ENV`, which a spawned worker unsets). A session's worker never inherits them from whoever started it (a `chi send` run by another session's `execute`): its other commands (a user's `!cmd`, an MCP server, a context command) have none.
|
|
35
35
|
- They also pass on a set `XDG_STATE_HOME` / `XDG_CONFIG_HOME` made absolute (`Builtins.xdg_absolute`, via `Paths`), so a chi the command runs in another `cwd` (`chi send`) reads this chi's state and config. These aren't reserved: an XDG dir the model sets in `task_create`'s `env` stays its own.
|
|
36
36
|
|
|
37
|
+
## Finish wake
|
|
38
|
+
|
|
39
|
+
A task the model leaves running (it ends its turn: "I'll check when it's finished") is reported when it ends, so the result doesn't wait for the user's next message. `session.task_wakes` (env `SAMAGOTCHI_SESSION_TASK_WAKES`): `wake` (default), `queue` or `off` (no watch, as before).
|
|
40
|
+
|
|
41
|
+
- **The watch** (`TaskWatch`, `task_watch.rb`) is the Worker's (`WorkerTaskWakes`, `worker_task_wakes.rb`, which writes the notes; the Worker runs the wake turn), in memory: the in-process REPL (`--no-shared`) has none, and a crash loses it (those tasks behave as before). The Worker's Engine observer feeds it the raw `:tool_call_completed` events, the input `Bridge#own_task_ids` reads too. A `task_create` result and an `execute` hand-over add the task (`TaskRuntime.created_id`: a `task_id:` the command printed doesn't count), the hand-over's limit kept for the note. What shows the model the end removes it: a `task_wait` with a final status, or with `wait_result: pattern_matched` (the model chose its own "done" marker); a `task_get` with a final status; any `task_stop`. `task_list` and a `read` of `output.log` don't: a model that read the log still gets the wake once. Tasks a tool starts inside an anytime command (`/btw` and the like) reach the same observer, so they are watched too.
|
|
42
|
+
- **The idle tick**: with something watched, each idle pass (after delegate reports, before due reminders) refreshes the watched records (`TaskRuntime.get_record`, one `ps` per running task) and takes the finished ones, keeping only the ids the conversation still holds (`TaskRuntime.created_ids_in`: a `!rollback` or a restored turn may have dropped the result that started one; an LLM context strategy can't, since its stale and forget stubs are drawn in the request, never written over the saved result). A note that can't be written puts its task back for the next pass. An empty watch costs nothing. A task that finishes during a turn and is still unseen when the turn ends wakes on the next idle pass. One the user stopped with the web's stop-task button is dropped without a note.
|
|
43
|
+
- **The note** is a context note under its own header (`ContextNote::TASK_HEADER`; `TaskFinishNote`), `note_id: task-<id>`, `source: task <id8>`:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
[TASK FINISHED from task 3f9a2c1d, 14:20]
|
|
47
|
+
Background task 3f9a2c1d finished: exit 0 after 6m12s (handed over by execute at its 120s limit).
|
|
48
|
+
command: bundle exec rspec spec
|
|
49
|
+
<task_wait's own text (TaskWait.finished_text): status, the log's last 10 lines, a stderr_tail>
|
|
50
|
+
chi started this turn because a background task you started finished; your user didn't write anything.
|
|
51
|
+
Carry on with what you planned to do with its result. If you planned nothing, tell your user in a sentence or two how it went.
|
|
52
|
+
[END NOTE]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The duration runs from `started_at` to the `exit_code` file's mtime (the shell's EXIT trap wrote it as it ended; `finished_at` is when a refresh first noticed the end), else `finished_at`. The body is cut to `max_tool_output_chars` as a `task_wait` result is (`ToolOutputCut`'s `:head_tail_ref`, naming the logs).
|
|
56
|
+
- **The wake turn**: the first finished task runs a continue turn from client `task:<id>` (`last_turn.origin: "task"`, labelled `task <id8> finished` in the TUI and the web; the web's bell rings at its end however short), whose first message is the note (`turn_start`, `turn_id`, written before the turn's checkpoint so a restore keeps it). It counts toward `session.max_wakes` with the other wakes (`WorkerWakes#task_open?`: no continue offer, not paused, past the 2 s start grace). Several tasks finishing together: one wake turn, the others plain notes before it.
|
|
57
|
+
- **A plain note instead** (the same text without the last two lines, no turn start), written at once, with `queue`, the budget spent (the UIs hear `N finished background tasks noted; the model reads them with your next message` once per human input, the flag shared with the other wakes), wakes paused, a continue offer waiting, input queued, an exit asked for, or a **note-only** task: one started or waited on in a turn the user stopped (reasons `user`, `manual`, `ctrl_c`), since a wake telling the model to carry on would undo that Stop. A prompt's turn takes the finished ones as plain notes first.
|
|
58
|
+
- **Failure**: a wake turn that fails and kept nothing, or is stopped and restored, turns its note back into the plain one (`WorkerInbound#unmark_wake`, as for a context wake) and pauses wakes until the user writes.
|
|
59
|
+
- **Idle exit**: with `wake`, a watched task holds the worker's idle exit (`WorkerIdleExit`'s `:background_tasks`) until its wake is taken: while it runs, and once it finished until the next idle pass takes it (`TaskWatch#holding`, in memory, no `ps`), so one that ends between the pass's look and the exit's check still wakes the session. A still-running one holds `/exit` too (`TaskWatch#running`; the 409 lists the tasks, `{id, command}`, with a `hint` on stopping them, and the attached terminal names them and says to use the web's stop-task button or ask for `task_stop`), as reminders do. A note-only task holds neither (no wake would come for it), nor does one whose note, or the look for it (the conversation's task ids unreadable), failed 3 times (`TaskWatch::MAX_FAILURES`: it is still tried at each look, but can't keep the worker up for good). With `queue` nothing holds: nothing would wake for the end, so keeping the worker up isn't worth it. As the worker leaves (any exit but a stop, a signal or an `/exit --delete`), the tasks that finished since the last look are written as plain notes; a task that ends after the worker left gets no note (the model sees its end at its next look, as before). A forgotten dev server keeps the worker up until it is stopped; a crash of that server wakes the session.
|
|
60
|
+
- **The tool descriptions**: a worker's `execute` and `task_create` say "if you end your turn before it finishes, chi tells you when it does" (left out under `off` and in the REPL), fixed when the worker builds its Engine.
|
|
61
|
+
|
|
37
62
|
Work that needs a model, not a shell command, goes to a child chi session instead: `delegate` / `delegate_result` have the same shape (start, then wait) and return only the child's final reply; see [Sessions: Delegating](../sessions.md#delegating).
|
data/docs/internals/bundles.md
CHANGED
|
@@ -22,9 +22,9 @@ user-facing side (what a bundle owns, profiles, needs) is in [memory.md](../memo
|
|
|
22
22
|
|
|
23
23
|
| Piece | Role |
|
|
24
24
|
|---|---|
|
|
25
|
-
| `BundleCommand` (`lib/samagotchi/bundle_command.rb`) | `chi bundle install/upgrade/uninstall/status/diff/list/build/trash/new/test/check
|
|
25
|
+
| `BundleCommand` (`lib/samagotchi/bundle_command.rb`) | `chi bundle install/upgrade/uninstall/status/diff/list/build/trash/new/test/check/try` (`install --dev`: the Installer's `dev:`, links and `Provenance::DEV_SHA`); runs the Installer, prints its summary, runs the conflict agent step; refuses a source already installed under another name (`installed_under_another_name?`: a git URL bundle recorded under its repo's name before the manifest named it) |
|
|
26
26
|
| `BundleCommand::Check` (bundle_command/check.rb) | `chi bundle check [PATH] [--fix] [--set K=V]`: a bundle being written, checked as an install takes it (docs/cli.md lists the codes); parses the manifest from `Shas.parseable` (a placeholder sha is a `sha` finding, not a YAML error), reads the user's installed bundles only for `clash`, and installs and loads through `Testing::Install` in a `Testing::Sandbox` with `force: true`, the log's echoes swallowed |
|
|
27
|
-
| `BundleCommand::Try` (`bundle_command/try/`) | `chi bundle try PATH -p TEXT`: filtered-config `Sandbox` installs the tried bundle and shipped `guardrails` / `cost-cap` in a separate child, then `Run` starts one non-interactive chi process from its realpath work dir; `TraceReader` reads that session's analytics, cards and session JSON by id (never `Session.list`), joining timing and tool args for text or experimental JSON output. `Sandbox#child_env` is a Hash, never an ENV mutation; it keeps ordinary user model settings and Keychain access while dropping the spawner's session/guardrail/worker markers. |
|
|
27
|
+
| `BundleCommand::Try` (`bundle_command/try/`) | `chi bundle try PATH -p TEXT`: `Command` (its flags, help and checks) drives the rest and `Render` prints the trace; a filtered-config `Sandbox` (its folders a `SandboxLayout`, the layout `Testing::Sandbox` uses too) installs the tried bundle and shipped `guardrails` / `cost-cap` in a separate child, then `Run` starts one non-interactive chi process from its realpath work dir; `TraceReader` reads that session's analytics, cards (`Bridge::CardStore.saved`, as the web does) and session JSON by id (never `Session.list`), joining timing and tool args for text or experimental JSON output (a turn a `--timeout` interrupt cut keeps its calls like a Ctrl-C, the one cut short answered as interrupted, so its rows come from the saved calls too; `Run::Result#timed_out` becomes `timed_out`). `Sandbox#child_env` is a Hash, never an ENV mutation; it keeps ordinary user model settings and Keychain access while dropping the spawner's session/guardrail/worker markers (`ChildEnv`, the scrub a session's worker spawn uses too). The child runs in its own process group: a `--timeout` sends it INT, then `ProcessGroup.stop` (the groups its processes started too); after any exit the group is signalled only while a straggler is still in it, never a reaped group. `Sandbox#stop_leftovers` (before the trace, and in `cleanup`) stops what left the child's tree, found by the sandbox's own records: a delegate's worker by its session's `owner.lock`, a task by its running `task.json` whose pid still leads its group. |
|
|
28
28
|
| `BundleCommand::Scaffold` (bundle_command/scaffold.rb) | `chi bundle new NAME`: the starting files (block-form `plugin:` entry, `requires_chi` at this chi, a kit test that passes as written); the manifest's shas are filled through `Shas.refresh` |
|
|
29
29
|
| `BundleCommand::TestRun` (bundle_command/test_run.rb) | `chi bundle test`: `RbConfig.ruby -I <chi's lib>` on `test/**/*_test.rb` (Minitest) or `spec/` (RSpec, as `rake bundles:specs`), under `Bundler.with_unbundled_env` when Bundler's runtime is loaded |
|
|
30
30
|
| `Shas` (shas.rb) | a manifest's `sha256:` lines: `.refresh(dir[, text])` rewrites them line by line (the layout kept; a `files:` entry it can't place raises `Shas::Error`), `.stale` → `[Stale(file, old, new)]` (`old` `""` for a placeholder, nil for a plugin / hook / script entry with no sha line), `.parseable` (a placeholder `sha256: sha256:` isn't YAML). Plain Ruby: `rake bundles:sha` / `bundles:check` (`script/release/release_tools.rb`) and `chi bundle check --fix` share it |
|
|
@@ -32,7 +32,7 @@ user-facing side (what a bundle owns, profiles, needs) is in [memory.md](../memo
|
|
|
32
32
|
| `SourceIdentity` (source.rb) | what a source is, for comparing two installs of one name: `shipped:<dir>` (any gem or checkout), `git:<url>` (no `#ref`, trailing `/` or `.git`), `path:<realpath>`; nil when it can't place one |
|
|
33
33
|
| `PreparedSource` (source.rb) | one source normalized once (dir, owned, git commit): `BundleCommand` reads the bundle's name from its `manifest.yml` (the name for every kind of source, a git URL too) and hands it to the Installer (`prepared:`), so a source is cloned or extracted once per command |
|
|
34
34
|
| `MemoryFiles` | which top-level `*.md` files of a bundle are its memories (`.select`: the `files:` list, else all but README/LICENSE and the like), `.unshipped` (listed, not shipped) and `.symlinks` (skipped: a symlink is never a memory) |
|
|
35
|
-
| `Manifest` | parses/validates `manifest.yml` (`name:` is the bundle's name and a directory under `.bundles/`, so a path such as `../x`, `a/b`, `.` or `..` is refused: `name: … is not a bundle name`; YAML that doesn't parse or isn't a mapping is a `ValidationError` too; files, hooks as `BundleHook`, plugin, scripts, needs, requires_chi, includes, context_providers, session_settings: the keys a session's `chi --set` may give, recorded at install in `manifest.json` for `BundleSessionSettings` to check) and writes it (`Manifest.write`, for Builder). It ignores `requires_chi_next`, a shipped bundle's release-time marker (`script/release/release_tools.rb`, docs/releasing.md) |
|
|
35
|
+
| `Manifest` | parses/validates `manifest.yml` (`name:` is the bundle's name and a directory under `.bundles/`, so a path such as `../x`, `a/b`, `.` or `..` is refused: `name: … is not a bundle name`; YAML that doesn't parse or isn't a mapping is a `ValidationError` too; files, hooks as `BundleHook`, plugin, scripts, needs, requires_chi, includes, context_providers, session_settings: the keys a session's `chi --set` may give, a list or a mapping of them to types (`SESSION_SETTING_TYPES`), recorded at install in `manifest.json` (`session_settings`, `session_setting_types`) for `BundleSessionSettings` to check) and writes it (`Manifest.write`, for Builder). It ignores `requires_chi_next`, a shipped bundle's release-time marker (`script/release/release_tools.rb`, docs/releasing.md) |
|
|
36
36
|
| `Installer` | one install/upgrade/dry run of one bundle (below) |
|
|
37
37
|
| `AssetInstaller` | the parts that live in `.bundles/<name>/`: hooks, guardrail rules, plugin, scripts; returns `AssetInstaller::Installed` (the paths the record keeps) |
|
|
38
38
|
| `Merger` | 3-way classification per file: `install`, `fast_forward`, `keep`, `noop`, `conflict` |
|
|
@@ -63,7 +63,7 @@ The checks live in `Hooks::BundleLoader.load` and `Plugin::Loader.load_bundle`;
|
|
|
63
63
|
plugin file: build, `register`, `commit!`), which raise instead of logging: the bundle test kit
|
|
64
64
|
(`Samagotchi::Testing::Bundle`) loads a bundle's source through them, unchecked. `Samagotchi::Testing::Install` is the
|
|
65
65
|
checked path in a sandbox: the Installer, then `ExtensionLoad` (bundle hooks, then `load_plugins`), returning the
|
|
66
|
-
Installer's warnings, this bundle's load failures and what it registered (`Install.registered`, also the parity
|
|
66
|
+
Installer's warnings, this bundle's load failures and what it registered (`Install.registered`, an `Install::Registered` value, also the parity
|
|
67
67
|
spec's shape). A load failure carries `LoadFailures::Failure#location` (the first backtrace frame inside the
|
|
68
68
|
bundle, or a SyntaxError's own line), which `Install` maps from the installed copy (`.bundles/<name>/plugin/`,
|
|
69
69
|
`hooks/`) back to the source dir.
|