samagotchi 0.91.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 +58 -1
- data/README.md +5 -2
- data/bin/chi +4 -4
- data/docs/architecture.md +16 -12
- data/docs/bundle-authoring.md +2 -1
- data/docs/cli.md +5 -5
- data/docs/configuration.md +3 -1
- data/docs/context.md +5 -3
- data/docs/hooks.md +3 -3
- data/docs/internals/background-tasks.md +2 -2
- data/docs/internals/bundles.md +4 -4
- data/docs/internals/engine.md +21 -8
- 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 +3 -3
- data/lib/samagotchi/bridge.rb +8 -9
- 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 +20 -26
- 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 +1 -1
- data/lib/samagotchi/child_env.rb +50 -0
- data/lib/samagotchi/engine.rb +22 -12
- data/lib/samagotchi/hooks/registry.rb +2 -0
- data/lib/samagotchi/kernel_loop.rb +14 -3
- 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_watch.rb +64 -21
- 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_response.rb +28 -6
- data/lib/samagotchi/tools/task_runtime.rb +2 -2
- 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/toasts.js +17 -9
- data/lib/samagotchi/web/public/unqueue.js +20 -4
- data/lib/samagotchi/worker.rb +77 -185
- data/lib/samagotchi/worker_idle_exit.rb +20 -5
- data/lib/samagotchi/worker_inbound.rb +5 -5
- data/lib/samagotchi/worker_task_wakes.rb +160 -0
- data/lib/samagotchi/worker_wakes.rb +0 -3
- metadata +6 -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,62 @@ 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
|
+
|
|
11
67
|
## [0.91.0] - 2026-10-19
|
|
12
68
|
|
|
13
69
|
### Added
|
|
@@ -3549,7 +3605,8 @@ and long-lived sessions.
|
|
|
3549
3605
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3550
3606
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3551
3607
|
|
|
3552
|
-
[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
|
|
3553
3610
|
[0.91.0]: https://github.com/dm1try/samagotchi/compare/v0.90.0...v0.91.0
|
|
3554
3611
|
[0.90.0]: https://github.com/dm1try/samagotchi/compare/v0.89.0...v0.90.0
|
|
3555
3612
|
[0.89.0]: https://github.com/dm1try/samagotchi/compare/v0.88.0...v0.89.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
364
|
`continue_offered`, `client_connected` (a stream not named by the asker), `reminders`,
|
|
363
|
-
`background_tasks` (with `tasks: [{id, command}]`: tasks a finish wake watches) or `starting`.
|
|
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`, `worker_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. |
|
|
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
|
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.
|
|
@@ -151,8 +151,8 @@ one experimental JSON object to stdout and human trace lines to stderr; its keys
|
|
|
151
151
|
`interactive_blocked`, `used_memories`, `tokens` (`prompt`, `completion`), `cost`, `cost_is_estimate`, `model`,
|
|
152
152
|
`sandbox`, `denied` (1-based tool indexes), `stderr_tail` (last 4,000 characters) and `timed_out`. A run past
|
|
153
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
|
|
155
|
-
|
|
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,
|
|
156
156
|
1 for install/turn failure or timeout, 130 for Ctrl-C, otherwise the child's status.
|
|
157
157
|
|
|
158
158
|
`try` runs the bundle's code and the model's tools with your real HOME, account and keys. Its temporary working
|
|
@@ -280,7 +280,7 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
|
|
|
280
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. |
|
|
281
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). |
|
|
282
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). |
|
|
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`) 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). |
|
|
284
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. |
|
|
285
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. |
|
|
286
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
|
@@ -118,7 +118,9 @@ Besides the settings sections, the file holds maps that are read by their
|
|
|
118
118
|
own subsystems: `hosts:` (below), `models:` (see "Prompt profile" and
|
|
119
119
|
"Images"), `model_aliases:`, `hooks:` (see [Hooks](hooks.md)),
|
|
120
120
|
`guardrails:` (see [Guardrails](guardrails.md)), `bundles:` (a bundle's
|
|
121
|
-
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)),
|
|
122
124
|
`memories:` and `append_system_prompt:` (see "Appended prompt files"). Their entry names (host names, model ids, aliases) are yours to
|
|
123
125
|
choose. The `memories:` list is the persistent baseline for preloaded memory entries —
|
|
124
126
|
the same name shape as `--memory` (bare name or `scope/name`), merged under
|
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
|
|
|
@@ -38,7 +38,7 @@ Behavior:
|
|
|
38
38
|
|
|
39
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
40
|
|
|
41
|
-
- **The watch** (`TaskWatch`, `task_watch.rb`) is the Worker's, 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.
|
|
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
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
43
|
- **The note** is a context note under its own header (`ContextNote::TASK_HEADER`; `TaskFinishNote`), `note_id: task-<id>`, `source: task <id8>`:
|
|
44
44
|
|
|
@@ -56,7 +56,7 @@ A task the model leaves running (it ends its turn: "I'll check when it's finishe
|
|
|
56
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
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
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
|
|
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
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
61
|
|
|
62
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 (
|
|
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.
|
data/docs/internals/engine.md
CHANGED
|
@@ -97,11 +97,14 @@ StandardError in run_turn:
|
|
|
97
97
|
Interrupt (Ctrl-C; the REPL only: a worker traps INT, see the signal row below):
|
|
98
98
|
→ if turn.ended: re-raised only
|
|
99
99
|
→ turn.controller.cancel!(:ctrl_c)
|
|
100
|
-
→ end_turn(turn, "canceled"): ctrl_c_messages keeps turn.messages
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
100
|
+
→ end_turn(turn, "canceled"): ctrl_c_messages keeps the loop's conversation, else turn.messages
|
|
101
|
+
# (it came before the loop), with TurnNote.cancelled(:ctrl_c) at the tail.
|
|
102
|
+
# Both loops attach their conversation to the Interrupt as to a failure
|
|
103
|
+
# (LLM::FailedTurn.attach), so the turn's finished iterations stay, as after a Stop,
|
|
104
|
+
# and the cancel note names the tasks the turn started. One mid-tool is answered:
|
|
105
|
+
# ToolResponse.run_batch gives every call left an interrupted result (the native
|
|
106
|
+
# loop's batch entry joins them after the finished ones), so no call is kept without
|
|
107
|
+
# its result. A half-streamed reply is not kept (a Stop keeps it, [interrupted]).
|
|
105
108
|
→ :turn_canceled {cancellation_reason: :ctrl_c, duration_ms}
|
|
106
109
|
→ re-raised (the caller decides whether to exit)
|
|
107
110
|
|
|
@@ -191,7 +194,8 @@ KernelLoop#run(messages, max_iterations:, on_stream_event:, cancel_controller:,
|
|
|
191
194
|
```
|
|
192
195
|
|
|
193
196
|
`turn.conversation` is the loop's own copy, not the Engine's `turn.messages`. A failure attaches it to the error
|
|
194
|
-
(`LLM::FailedTurn.attach`), so `Engine#failed_messages` can keep it;
|
|
197
|
+
(`LLM::FailedTurn.attach`), so `Engine#failed_messages` can keep it; an Interrupt (Ctrl-C) too, for
|
|
198
|
+
`Engine#ctrl_c_messages` (see Error paths).
|
|
195
199
|
|
|
196
200
|
### ChatLoop (the chat APIs)
|
|
197
201
|
|
|
@@ -468,7 +472,9 @@ TUI. Errors in the sink or an observer are caught; they never break the turn.
|
|
|
468
472
|
|
|
469
473
|
`Engine#announce` puts an event that belongs to no turn (`ANNOUNCEABLE_EVENTS`: `turn_enqueued`, `turn_dequeued`
|
|
470
474
|
(a queued message taken back before it ran, `{enqueued_id, client_id}`: the remover's), `card`,
|
|
471
|
-
`hook_notice`, `guardrail_warning`, `plugin_init_started`, and more) to the observers only.
|
|
475
|
+
`hook_notice`, `guardrail_warning`, `plugin_init_started`, and more) to the observers only. `:side_usage`
|
|
476
|
+
(`{kind, side}`: a side request's usage, the recap's, `/btw`'s or `ask_model`'s, and the session's side sums) goes
|
|
477
|
+
to the observers the same way from `Engine#note_side_usage` (any thread), without being announceable.
|
|
472
478
|
|
|
473
479
|
Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
|
|
474
480
|
|
|
@@ -491,6 +497,8 @@ Turn events (the main ones; `Events` in `events.rb` keeps the shared sets):
|
|
|
491
497
|
| `:tool_dispatch_started` / `:tool_dispatch_completed` | around a batch | `{iteration, call_count}` |
|
|
492
498
|
| `:tool_call_started` | before a call runs | Engine event includes `call` (see ToolRunner); the Bridge's SSE frame omits it |
|
|
493
499
|
| `:tool_call_completed` | after a call ran | see ToolRunner |
|
|
500
|
+
| `:question_requested` / `:question_answered` / `:question_cancelled` | `QuestionDesk`: a question is asked (an approval, `ask_user_question`, a hook's or plugin's `ask_user`, the step-limit Continue), answered, or withdrawn | `{pending_question}` / `{id, answer}` / `{id, reason}` |
|
|
501
|
+
| `:plugin_init_wait` | a turn waits for plugin init tasks that provide tools (`PluginTasks`) | `{tasks: [{bundle, id, label}]}` |
|
|
494
502
|
| `:used_memories_updated` | a memory was read, and after the loop | `{used_memory_names, read_names?}` |
|
|
495
503
|
| `:hook_notice` | a hook's or plugin's notify during a turn | `{hook, text, level}` (announced with `between_turns: true` outside a turn) |
|
|
496
504
|
| `:guardrail_warning` | a guardrails line during a turn | `{message}` |
|
|
@@ -681,7 +689,7 @@ prompt starts with what the server has cached).
|
|
|
681
689
|
and config.yml hooks survive.
|
|
682
690
|
3. **Each loop works on its own copy of the conversation.** The Engine's `turn.messages` is what it sent. After
|
|
683
691
|
a normal end or a Stop it keeps `result.conversation`; after a failure, the copy attached with
|
|
684
|
-
`FailedTurn.attach
|
|
692
|
+
`FailedTurn.attach` (a Ctrl-C's too); `turn.messages` only when it ended before the loop.
|
|
685
693
|
4. **Events are the contract.** UIs read events, not internal state. `on_event` is per turn; observers are
|
|
686
694
|
persistent.
|
|
687
695
|
5. **A stable system prompt for the cache.** `SystemPrompt#build` caches the prompt per Engine, keyed by
|
|
@@ -749,3 +757,8 @@ Paths are under `lib/samagotchi/`.
|
|
|
749
757
|
| `turn_checkpoint.rb` | `TurnCheckpoint`: a worker's running turn saved at the loop's iteration boundaries (`Session#turn_checkpoint`), so a killed worker loses at most `INTERVAL` of it |
|
|
750
758
|
| `context_status.rb` | `ContextStatus`: one turn's context tracking (the status line's value, the `:context_status` event, the model's guidance line) ([context-telemetry.md](context-telemetry.md)) |
|
|
751
759
|
| `session_manager.rb` | Background session processes (workers) and their IPC |
|
|
760
|
+
| `worker_wakes.rb` | `WorkerWakes`: a worker's wake budget for the turns nobody typed (`session.max_wakes`) |
|
|
761
|
+
| `task_watch.rb`, `task_finish_note.rb` | `TaskWatch`: the background tasks a worker's session started that the model hasn't seen finish; `TaskFinishNote`: the note one leaves when it does ([background-tasks.md](background-tasks.md#finish-wake)) |
|
|
762
|
+
| `side_usage.rb` | `SideUsage`: one side request's usage (a recap, `ctx.ask_model`), folded into the session's side sums (`:side_usage`) |
|
|
763
|
+
| `bridge/client_event.rb` | `Bridge::ClientEvent.project`: what an SSE client gets for an engine event ([architecture.md](../architecture.md)) |
|
|
764
|
+
| `guardrails/parent_policy.rb` | `Guardrails::ParentPolicy`: which options of a question a parent agent may pick (`guardrails.parent_answers`) |
|
|
@@ -27,6 +27,14 @@ Telemetry uses existing context estimation settings:
|
|
|
27
27
|
- `SAMAGOTCHI_CONTEXT_WINDOW_TOKENS` (`context.window_tokens`)
|
|
28
28
|
- `SAMAGOTCHI_CONTEXT_CHARS_PER_TOKEN` (`context.chars_per_token`)
|
|
29
29
|
|
|
30
|
+
Line ranges (`read` and `edit`; env only, no config key):
|
|
31
|
+
|
|
32
|
+
- `SAMAGOTCHI_READ_END_OPTIONAL` / `SAMAGOTCHI_EDIT_END_OPTIONAL` (default `true`): a `start_line` without
|
|
33
|
+
`end_line` means "to the end of the file"; `false` makes it an error.
|
|
34
|
+
- `SAMAGOTCHI_READ_ALLOW_OOR_END` / `SAMAGOTCHI_EDIT_ALLOW_OOR_END` (default `true`): an `end_line` past the end is
|
|
35
|
+
clamped to the last line and the result says so; `false` makes it `Error: range out of bounds`. A `start_line`
|
|
36
|
+
past the end is always an error.
|
|
37
|
+
|
|
30
38
|
## Execute Tool Output Guardrails
|
|
31
39
|
|
|
32
40
|
The `execute` tool applies the same guardrail model to command output:
|
|
@@ -64,8 +72,8 @@ built-in name; a tool with none gets `:head`):
|
|
|
64
72
|
everything else the cap leaves, so a larger `max_tool_output_chars` keeps a longer tail. Both are snapped to whole
|
|
65
73
|
lines when that keeps at least half of each. execute's stdout and stderr blocks are cut each on its own (stdout gets
|
|
66
74
|
about two thirds of the room when both are long, a block that needs less gives the rest to the other); the `exit:`
|
|
67
|
-
line is always kept. The whole stays within the cap; a cap
|
|
68
|
-
characters gets the `:head` cut, and so does one whose gap lines leave no room for
|
|
75
|
+
line is always kept. The whole stays within the cap; a cap under three times `ToolOutputCut::MIN_TAIL` (200), so
|
|
76
|
+
under 600 characters, gets the `:head` cut, and so does one whose gap lines leave no room for a 200-char tail (checked with the
|
|
69
77
|
file's planned path before anything is written): nothing is saved then, and the event has no `output_file:`.
|
|
70
78
|
|
|
71
79
|
The gap line says what was left out and where the whole output is:
|
data/docs/memory.md
CHANGED
|
@@ -308,6 +308,12 @@ for a file another bundle installed.
|
|
|
308
308
|
with neither and no terminal it keeps your edits and says so. Kept edits
|
|
309
309
|
exit 2; `chi bundle diff NAME FILE` shows the base, and `--force` takes
|
|
310
310
|
the bundle's version.
|
|
311
|
+
- **A dev install** (`chi bundle install --dev PATH`) links the hooks, rules,
|
|
312
|
+
plugin and scripts to `PATH` but still copies the memories at install, so
|
|
313
|
+
an edit to one of them in `PATH` isn't seen until the next install.
|
|
314
|
+
- **The rest of the manifest** (`plugin:`, `requires_chi`, `session_settings:`,
|
|
315
|
+
`context_providers:`, `requires_chi_next`) is in
|
|
316
|
+
[Plugins](plugins.md) and [internals/bundles.md](internals/bundles.md).
|
|
311
317
|
|
|
312
318
|
## Bundle profiles: core and dev
|
|
313
319
|
|