samagotchi 0.16.0 → 0.18.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 +149 -1
- data/README.md +2 -0
- data/docs/architecture.md +38 -2
- data/docs/cli.md +37 -9
- data/docs/configuration.md +8 -6
- data/docs/guardrails.md +51 -8
- data/docs/hooks.md +4 -1
- data/docs/internals/background-tasks.md +1 -0
- data/docs/memory.md +41 -0
- data/docs/plugins.md +5 -2
- data/docs/sessions.md +8 -6
- data/docs/sub-agent.md +43 -4
- data/docs/testing.md +47 -0
- data/lib/samagotchi/answer_command.rb +23 -2
- data/lib/samagotchi/answer_tail.rb +52 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +20 -5
- data/lib/samagotchi/bridge.rb +86 -14
- data/lib/samagotchi/bridge_client.rb +18 -3
- data/lib/samagotchi/bundle_command.rb +121 -4
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +18 -3
- data/lib/samagotchi/bundles/guardrails/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +5 -5
- data/lib/samagotchi/bundles/system/self_map.md +1 -0
- data/lib/samagotchi/commands/registry.rb +28 -0
- data/lib/samagotchi/config.rb +7 -1
- data/lib/samagotchi/continue_offer.rb +177 -0
- data/lib/samagotchi/engine.rb +73 -19
- data/lib/samagotchi/guardrails/outside.rb +46 -0
- data/lib/samagotchi/guardrails/parent_approvals.rb +2 -0
- data/lib/samagotchi/guardrails/parent_continue.rb +43 -0
- data/lib/samagotchi/guardrails/rules.rb +17 -5
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +267 -0
- data/lib/samagotchi/guardrails/targets.rb +30 -13
- data/lib/samagotchi/guardrails.rb +2 -0
- data/lib/samagotchi/iteration_limit.rb +38 -0
- data/lib/samagotchi/kernel_loop.rb +2 -1
- data/lib/samagotchi/llm/backend.rb +2 -1
- data/lib/samagotchi/llm/chat_loop.rb +2 -1
- data/lib/samagotchi/llm/native_backend.rb +2 -1
- data/lib/samagotchi/memory_bundle/builder.rb +22 -0
- data/lib/samagotchi/memory_bundle/index_updater.rb +17 -6
- data/lib/samagotchi/memory_bundle/installer.rb +60 -21
- data/lib/samagotchi/memory_bundle/profile.rb +9 -4
- data/lib/samagotchi/memory_bundle/provenance.rb +14 -0
- data/lib/samagotchi/memory_bundle/shipped_update.rb +5 -3
- data/lib/samagotchi/memory_bundle/system_bundle.rb +5 -3
- data/lib/samagotchi/memory_bundle/trash.rb +136 -0
- data/lib/samagotchi/memory_bundle/uninstaller.rb +34 -5
- data/lib/samagotchi/parent_report.rb +39 -9
- data/lib/samagotchi/plugin/context.rb +14 -2
- data/lib/samagotchi/question_desk.rb +205 -46
- data/lib/samagotchi/reply_wait.rb +10 -4
- data/lib/samagotchi/self_report.rb +49 -3
- data/lib/samagotchi/send_command.rb +46 -6
- data/lib/samagotchi/session.rb +3 -2
- data/lib/samagotchi/session_commands.rb +3 -2
- data/lib/samagotchi/session_manager.rb +4 -1
- data/lib/samagotchi/session_metrics.rb +137 -48
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +46 -5
- data/lib/samagotchi/terminal_ui/attached_loop.rb +39 -6
- data/lib/samagotchi/terminal_ui/event_renderer.rb +2 -1
- data/lib/samagotchi/terminal_ui/question_prompt.rb +34 -2
- data/lib/samagotchi/terminal_ui.rb +35 -2
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tools/builtins.rb +11 -3
- data/lib/samagotchi/tools/delegate_relay.rb +4 -0
- data/lib/samagotchi/tools/delegate_wait.rb +14 -5
- data/lib/samagotchi/tools/edit.rb +6 -1
- data/lib/samagotchi/tools/execute.rb +5 -4
- data/lib/samagotchi/tools/peers.rb +3 -2
- data/lib/samagotchi/tools/read.rb +2 -1
- data/lib/samagotchi/tools/task_runtime.rb +1 -1
- data/lib/samagotchi/turn_flow.rb +8 -7
- data/lib/samagotchi/turn_note.rb +7 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +189 -43
- data/lib/samagotchi/web/public/app.js +157 -51
- data/lib/samagotchi/web/public/data.js +12 -13
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/notify.js +12 -5
- data/lib/samagotchi/web/public/question_card.js +28 -1
- data/lib/samagotchi/web/public/question_cards.js +74 -3
- data/lib/samagotchi/web/public/sessions_list.js +1 -0
- data/lib/samagotchi/web/public/stage_view.js +13 -1
- data/lib/samagotchi/web/public/strip.js +11 -0
- data/lib/samagotchi/web/public/timing.js +67 -12
- data/lib/samagotchi/web/public/turn_events.js +74 -3
- data/lib/samagotchi/web/public/update_notice.js +15 -0
- data/lib/samagotchi/worker.rb +58 -60
- data/lib/samagotchi/worker_sidecar.rb +7 -1
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c7c0dfd8a859bb4808e10128560904cac788bc6bed8ec0b9d799425c6eee2fb
|
|
4
|
+
data.tar.gz: 4263cf99ea0998c74d7ae0f4e37d3bdbed8acc0140400082eed696da58e14847
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16b8b6cda0d263cb6540e052d207483396a5fd4588e3e4ddd776e02bcc107fb63ef73e19835b20b44e5c95e850d30b018bf985d898c8a2b07e6ae7a7a2984034
|
|
7
|
+
data.tar.gz: ade20718b92e00ee24274d5b0cdce3ff102ae88487cb00af244168134f1f0c0d3259cb1e7f19ade71bf8fe442cba1a764bd6550c5d1911ef7cce558fabac044f
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,152 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.18.0] - 2026-10-03
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- The guardrails bundle (0.4.0) asks before git that changes another checkout: `cd ../main && git commit`,
|
|
16
|
+
`git -C ../main add .`, an `execute` with `cwd:` there (commit, add, reset, checkout, switch, rebase, merge, push,
|
|
17
|
+
stash, rm, mv, cherry-pick, revert, pull, restore, am). Read-only git and tests there still run unasked. Its card
|
|
18
|
+
offers once, this session, or "rule in this repo" (stored, so later sessions there don't ask); switch it off with
|
|
19
|
+
`guardrails.disable: [git-outside-repo]`. Run `chi update` (or `chi bundle upgrade guardrails`) to get it.
|
|
20
|
+
- Web: a tab left open across a `chi web` upgrade says so: when its event stream reconnects to a newer chi, a toast
|
|
21
|
+
`chi was updated to X` offers a Reload button (it never reloads by itself; ✕ dismisses it).
|
|
22
|
+
- `chi bundle trash` lists the bundle trash (moved files from uninstalls/upgrades) oldest first, with file count,
|
|
23
|
+
size, and age; `--empty` deletes all folders, `--older-than DAYS` keeps only recent ones, `--dry-run` previews.
|
|
24
|
+
- A turn that runs out of iterations now waits as a question (kind `continue`, the step-limit question):
|
|
25
|
+
`chi send --wait` exits 3 with it, `chi answer --option Continue|Stop` answers it, and the web badge and bell show
|
|
26
|
+
it. The web draws it as a card with Continue, Stop and a reason for Stop; `chi --attach` reads the continue words
|
|
27
|
+
at its prompt (Enter continues). A parent agent may Continue unless `turn.parent_continue: false` (config.yml only);
|
|
28
|
+
a delegate's goes back to the parent model.
|
|
29
|
+
- `turn.max_iterations` (default 100, env `SAMAGOTCHI_TURN_MAX_ITERATIONS`) sets the per-turn step limit;
|
|
30
|
+
`--no-interrupt` turns get the larger of 1000 and it.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `write-outside-repo` and any `path: outside_repo` rule measure from the session's repo (not a plugin call's own
|
|
35
|
+
`cwd:`), with symlinks resolved: `/tmp/x` and `/private/tmp/x` are the same
|
|
36
|
+
folder, and a link in the repo that points elsewhere counts as outside. Writes into tmp folders (`$TMPDIR`, `/tmp`)
|
|
37
|
+
no longer ask, unless the session itself lives there. Hook events' `targets` gain `git_dirs`.
|
|
38
|
+
- A one-word `/word` that no command answers (`/modle`) is no longer sent to the model as a prompt: every UI
|
|
39
|
+
(the REPL, the attached TUI, the web composer) prints `Unknown command /modle. Did you mean /model? /help lists
|
|
40
|
+
the commands.` instead, with the "Did you mean" part only when a command name is close. Lines that are prompts
|
|
41
|
+
(`/foo bar`, `/usr/bin/env is missing`) still go to the model.
|
|
42
|
+
- `chi send --wait` reports `limit` instead of `no_answer` when a turn ran out of iterations and nobody can answer
|
|
43
|
+
(an older worker), with how to continue it.
|
|
44
|
+
- Stop at the step limit (`/continue no`) keeps the turn's work, with a note for the model, instead of erasing the
|
|
45
|
+
prompt and everything the turn did; `!rollback` still erases it. Reminder turns follow `turn.max_iterations`.
|
|
46
|
+
- The web page's end-of-turn and card re-reads fetch only what they show: the session's state, the last answer,
|
|
47
|
+
that turn's timings and the cards, from a new light worker read (`GET /session/:id/tail`) instead of the
|
|
48
|
+
whole conversation and timing history, so they stay the same size however long the session runs.
|
|
49
|
+
- `execute` gives a command 120 s by default (was 30 s) before it is killed; `execute.timeout_sec` /
|
|
50
|
+
`SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` still set it.
|
|
51
|
+
- An `execute` command killed at its time limit says how to run a long one: a second line
|
|
52
|
+
`(killed at the limit; for a long command use task_create, then task_wait)` follows `Error: command timed out after Ns`.
|
|
53
|
+
- `read` with only `end_line` reads from line 1 instead of failing with "start_line must be provided". A range
|
|
54
|
+
`edit` with only `end_line` still refuses (it would overwrite the top of the file) and now says what to pass:
|
|
55
|
+
`pass start_line too (1-based, the first line to replace)`.
|
|
56
|
+
- In a worktree or subfolder, the system prompt's "Project root" line now says it is only where shared project
|
|
57
|
+
memories come from, and to read, edit, run and commit in the current working directory; a model took the root for
|
|
58
|
+
its workspace and committed to the main checkout.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- Web: a session waiting on a plugin card (check-in's Nudge / Keep going / Stop) shows its "needs you" badge as a
|
|
63
|
+
pill on its session card again; it was drawn as a large empty circle over the card.
|
|
64
|
+
- Web: with a prompt queued behind a turn, the next turn's live timing line no longer freezes or takes the
|
|
65
|
+
previous turn's number when the previous turn's re-read lands late.
|
|
66
|
+
- `chi sessions list` shows the first message of a session that has no turn yet, such as one started with
|
|
67
|
+
`chi send --new -m "/model x"` or from the web start page; its row was blank.
|
|
68
|
+
- Web: a terminal-only command (`/stats`, `/exit`, `/recap` …) typed as the start page's first message is answered on
|
|
69
|
+
the start page; it no longer starts a session just to show that reply.
|
|
70
|
+
|
|
71
|
+
Update with `chi update`: the guardrails bundle moves to 0.4.0 (the `git-outside-repo` rule; it needs chi 0.18.0).
|
|
72
|
+
|
|
73
|
+
## [0.17.0] - 2026-10-03
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- The system prompt names the model the session runs on: `Model: this session runs on box:gemma-small (host box,
|
|
78
|
+
box.test:8080; model key gemma-small).`, plus a line to answer "which model are you" from it, not from training
|
|
79
|
+
(a fine-tune often knows only its base model's name), and that model-only guidance goes in
|
|
80
|
+
`memory_write current_model_only: true` overlays. A local llama.cpp host whose `/props` names another model adds
|
|
81
|
+
`; the server says it serves <name>`. The line changes only with `/model`.
|
|
82
|
+
- `execute` and `task_create` export `SAMAGOTCHI_SESSION_MODEL`, the session's model ref (it follows `/model`; the
|
|
83
|
+
model's `env` can't set it).
|
|
84
|
+
- Plugins: `ctx.model` (the session's model ref, right after `/model` too) and `ctx.model_key` (its memory overlay
|
|
85
|
+
key).
|
|
86
|
+
- `chi self` has a `model key` row: the memory overlay key of the model it reports.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- `chi self` run by a session's `execute` reports **that session's** model, and its host, loop, profile, thinking and
|
|
91
|
+
served model: `model splash:… (this session abcd1234; default main:…)`. Before, it showed the config default
|
|
92
|
+
(or the `--model` the worker started with), wrong after `/model`, for web-picker and `chi send --new --model`
|
|
93
|
+
sessions and delegate children. Elsewhere the row reads `main:… (default)`. `chi self --model` is still the default.
|
|
94
|
+
- The identity memory and the preloaded memories (`--memory`, config `memories:`) get their model overlays
|
|
95
|
+
(`<name>.<key>.md`) in the system prompt, as `memory_read` gives them.
|
|
96
|
+
- A session's live state (what the web and an attached terminal read from its worker) no longer carries every turn's
|
|
97
|
+
and tool call's timing record, only the newest turn's: reads stay the same size however long the session runs. The
|
|
98
|
+
full history stays in the session's `analytics.json`.
|
|
99
|
+
- A memory a bundle installed says so in its `index.md` line, and so in the prompt's memory index:
|
|
100
|
+
`- **memory_guide** · system · 2026-10-03 · 5120 · from samagotchi-system — …`. `memory_write` and `write`/`edit`
|
|
101
|
+
keep the tag; a same-name memory the install skipped gets none. Bundles installed earlier get it on their next
|
|
102
|
+
install or upgrade (the system bundle: with the next chi version).
|
|
103
|
+
- `chi bundle uninstall` (and an upgrade dropping a file the new version no longer ships) moves the bundle's memory
|
|
104
|
+
files to `<memories>/.bundles/.trash/<bundle>-<time>/` instead of deleting them, and says where:
|
|
105
|
+
`Moved to the trash: notes.md (…/.trash/notes-bundle-20261003-120000)`. Nothing empties the trash; delete it by hand.
|
|
106
|
+
- `chi bundle build` leaves out the memories an installed bundle owns (the system bundle's `identity.md`, a shipped
|
|
107
|
+
bundle's memory), one line each: `Left out identity.md: installed by bundle samagotchi-system (name it to include
|
|
108
|
+
it)`; naming a file in `FILES...` includes it. It now prints the builder's warnings.
|
|
109
|
+
- An `index.md` line a bundle install, upgrade or uninstall couldn't write or remove is now a warning
|
|
110
|
+
(`index.md: line for notes not updated (…)`) instead of passing silently; `chi bundle uninstall` of a profile
|
|
111
|
+
prints its bundles' warnings too.
|
|
112
|
+
- Web: a composer line is a command only when it names one of the session's commands (or is `!…`); an unknown
|
|
113
|
+
`/word` (a path like `/usr/bin/env …`, a typo) goes to the model as a prompt, as in a terminal, instead of being
|
|
114
|
+
refused with `not a session command`.
|
|
115
|
+
- Web: a window under 700 px tall hides the latest-sessions strip by itself (the `▾ N sessions` pill shows it again
|
|
116
|
+
until the window is tall), so a question or approval card in the turn's stage has room for its choices and
|
|
117
|
+
buttons. Taller windows and phones are unchanged.
|
|
118
|
+
|
|
119
|
+
### Fixed
|
|
120
|
+
|
|
121
|
+
- `chi self`'s `served model` row for a chat host (`api: openai`), or a server without `/props`, says
|
|
122
|
+
`reported per turn (the server has no /props)`; it said `unknown (no answer from the server's /props)`, which a
|
|
123
|
+
model took to mean the server never said what it served.
|
|
124
|
+
- `chi bundle uninstall` no longer deletes a memory the user had before the install. A bundle now records (and
|
|
125
|
+
so upgrades and removes) only the files it wrote; a same-name file that was already there is reported
|
|
126
|
+
`Skipped` and stays the user's, on install, re-install and upgrade. Before, it was recorded with the user's
|
|
127
|
+
content as its base, so uninstall removed it without `--force`: a third-party bundle shipping `notes.md`, or
|
|
128
|
+
`chi bundle build` then installing the result on the same machine. An upgrade no longer reports such a file as
|
|
129
|
+
a conflict (or `chi update --dry-run` as kept). Bundles installed before this fix may still list such a file;
|
|
130
|
+
uninstall now moves it to the trash (below) rather than deleting it.
|
|
131
|
+
- A memory file two installed bundles list (one an older install adopted, like `identity.md` under both
|
|
132
|
+
`samagotchi-system` and a `chi bundle build` of your memories) stays when one of them is uninstalled, or drops it
|
|
133
|
+
in an upgrade: `Kept identity.md: bundle samagotchi-system has it too`. It goes with the last one.
|
|
134
|
+
- Web: after a reload, a turn with an empty retry mid-turn shows each tool row's own duration; the rows took their
|
|
135
|
+
records by iteration, so every call after the retry showed the wrong one, or none.
|
|
136
|
+
- Attached TUI: `/detach` typed at a question's `?` prompt detaches (the question stays open), as Ctrl-D does; it
|
|
137
|
+
was read as an answer and refused as an unknown option.
|
|
138
|
+
- Attached TUI: joining a turn while a tool call runs (a long `delegate`, a slow command), its row's duration counts
|
|
139
|
+
from the call's start, not from the join.
|
|
140
|
+
- Web: in a short window a question or approval card that opens in the turn's stage is scrolled into view (its
|
|
141
|
+
choices and buttons); it sat below the prompt and headline, out of sight.
|
|
142
|
+
- A session command sent as a message runs as the command, as typed in a terminal: `chi send -m "/model x" ID`,
|
|
143
|
+
`chi send --new -m "/model x"`, `chi -p "/model x"` (attached and `--non-interactive`) and the web start page's
|
|
144
|
+
first message went to the model as a prompt. `chi send` says `sent as a session command`; with `--wait` there is
|
|
145
|
+
no reply to wait for (exit 0, `--format json` status `command`). An unknown `/word` still goes to the model.
|
|
146
|
+
- macOS 26: a dead worker's leftover `bridge.json` no longer counts as a live worker. Ruby's `Socket.tcp` with a
|
|
147
|
+
connect timeout returns the socket of a refused connect there, so the check said live: clients tried its port
|
|
148
|
+
first, `chi update` listed the dead worker as running, and an approval relay kept watching a parent that was gone.
|
|
149
|
+
- REPL with piped input: when the input ends at a question or approval, no bare `? ` line is left above its
|
|
150
|
+
`(denied)` / `(cancelled)` summary.
|
|
151
|
+
- Web: an approval from a delegate its parent isn't waiting on directly rings once (the parent's relayed card), not
|
|
152
|
+
twice: a child's approval now waits 3.5 s (was 1.5 s) for the relay before it notifies on its own. Only delegated
|
|
153
|
+
children's approvals wait; one nobody relays rings that much later.
|
|
154
|
+
|
|
155
|
+
Update with `chi update` (no bundle changes; the system bundle upgrades itself with chi).
|
|
156
|
+
|
|
11
157
|
## [0.16.0] - 2026-10-03
|
|
12
158
|
|
|
13
159
|
### Added
|
|
@@ -856,7 +1002,9 @@ and long-lived sessions.
|
|
|
856
1002
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
857
1003
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
858
1004
|
|
|
859
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
1005
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.18.0...HEAD
|
|
1006
|
+
[0.18.0]: https://github.com/dm1try/samagotchi/compare/v0.17.0...v0.18.0
|
|
1007
|
+
[0.17.0]: https://github.com/dm1try/samagotchi/compare/v0.16.0...v0.17.0
|
|
860
1008
|
[0.16.0]: https://github.com/dm1try/samagotchi/compare/v0.15.0...v0.16.0
|
|
861
1009
|
[0.15.0]: https://github.com/dm1try/samagotchi/compare/v0.14.0...v0.15.0
|
|
862
1010
|
[0.14.0]: https://github.com/dm1try/samagotchi/compare/v0.13.0...v0.14.0
|
data/README.md
CHANGED
|
@@ -201,6 +201,8 @@ npm test # web frontend specs
|
|
|
201
201
|
npm run e2e # web UI happy paths in Chromium, fake model (once: npx playwright install chromium)
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
Integration specs against a live model server, and what the suite isolates: [docs/testing.md](docs/testing.md).
|
|
205
|
+
|
|
204
206
|
## License
|
|
205
207
|
|
|
206
208
|
MIT, see [LICENSE](LICENSE).
|
data/docs/architecture.md
CHANGED
|
@@ -177,12 +177,19 @@ rescues sink errors). A new UI (web, API) supplies its own `on_event` and
|
|
|
177
177
|
renders whatever it needs from the stream + final `Result`. The public Engine API:
|
|
178
178
|
|
|
179
179
|
```ruby
|
|
180
|
-
engine.run_turn(session, prompt, on_event: nil, max_iterations:
|
|
180
|
+
engine.run_turn(session, prompt, on_event: nil, max_iterations: nil, cancel_controller: nil) # nil: turn.max_iterations
|
|
181
181
|
engine.run(session: nil, prompt: "...", on_event: nil) # create/resume session + run
|
|
182
182
|
engine.system_prompt # fully built system prompt string
|
|
183
183
|
engine.session # current session (Engine owns create/resume)
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
+
The system prompt (`SystemPrompt#build`) is the base prompt, then rg guidance,
|
|
187
|
+
AGENT.md, the working directory, **the model** (`Model: this session runs on
|
|
188
|
+
<ref> (host …; model key …)` and a line telling the model to answer "which model
|
|
189
|
+
are you" from it, not from training), the session id and log, the memory indexes,
|
|
190
|
+
the identity and the preloaded memories. It is built once per loop and rebuilt only
|
|
191
|
+
by a model switch (or changed tools), so the model line costs no KV churn.
|
|
192
|
+
|
|
186
193
|
#### Subscribing to the live stream (and the bridge)
|
|
187
194
|
|
|
188
195
|
For an **always-on** consumer (an external SSE client, a second UI), use
|
|
@@ -249,9 +256,36 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
249
256
|
- `GET /session/:id/state` — `session_state_snapshot` (JSON).
|
|
250
257
|
- `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.
|
|
251
258
|
- `GET /session/:id/snapshot` — the snapshot frame's content as one request (the web server renders
|
|
252
|
-
the messages itself, then streams from its `event_seq`).
|
|
259
|
+
the messages itself, then streams from its `event_seq`). Read by the web's full show
|
|
260
|
+
(`GET /api/sessions/:id`, `?parts=1`) and the plugin's `list_sessions`; every message is copied.
|
|
261
|
+
- `GET /session/:id/tail` — the page's light re-read (`Bridge#tail_frame`): `session_state_snapshot`,
|
|
262
|
+
`answer` (the last message a UI shows as an answer, `AnswerTail`: the one message, copied alone),
|
|
263
|
+
`cards` (`CardStore#list`), `event_seq`, `event_id`, all taken in one event-log hold; no message
|
|
264
|
+
list, so it costs the same however long the session is. `?turn_id=` answers that turn's answer
|
|
265
|
+
(an unknown or missing id: the newest); the only route that reads its query.
|
|
253
266
|
- `OPTIONS *` — CORS preflight (`Access-Control-Allow-Origin: *`).
|
|
254
267
|
|
|
268
|
+
The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` frames
|
|
269
|
+
(`SessionMetrics#snapshot`) carry the session's totals and only the recent timing records: the
|
|
270
|
+
newest turn the worker finished and any it hasn't saved yet (at most 20 turns), with their tool
|
|
271
|
+
calls and the running turn's finished ones. The full history is the session's `analytics.json`,
|
|
272
|
+
which `SessionMetrics#persist` rewrites after each turn; the web server merges the two by id.
|
|
273
|
+
|
|
274
|
+
The page's frequent reads of `GET /api/sessions/:id` stay light too:
|
|
275
|
+
- `?tail=1&recent=1` (each turn's end, a cancel, an answer display): `{tail, session: {id, status,
|
|
276
|
+
used_memory_names}, messages: [the answer], markdown_warning, timing}`, from the worker's `/tail`
|
|
277
|
+
alone (no session file parsed, no `analytics.json`); `timing` carries the worker's recent records
|
|
278
|
+
and `turn_count` (finished turns). The page merges the records by id (`timing.js mergeTiming`) and
|
|
279
|
+
finishes the ended turn's own line by its `turn_id` (sent as `&turn_id=`, which also picks that
|
|
280
|
+
turn's answer). A plain `?tail=1` (a tab opened before this) gets the whole timing.
|
|
281
|
+
- `?timing=1`: the whole timing (`analytics.json` merged with the worker's `/state`) and
|
|
282
|
+
`turn_count`, when the page's merge came up short.
|
|
283
|
+
- `?cards=1`: the cards, from `/tail`; the session file is only stat'ed for the 404.
|
|
284
|
+
|
|
285
|
+
A worker without `/tail` (an older chi; `404 not_found`) is read through `/snapshot` in the same
|
|
286
|
+
shape; any other failure (no worker, a 500, a timeout) reads the disk, trimmed to the newest
|
|
287
|
+
turn's records with `recent=1`.
|
|
288
|
+
|
|
255
289
|
Every route goes through `Bridge#dispatch`: an id other than the bridge's own session is
|
|
256
290
|
`404 unknown_session`, and a handler that raises answers `500 bridge_error` (logged as
|
|
257
291
|
`handler_failed`) rather than dropping the connection.
|
|
@@ -267,6 +301,8 @@ never pulls from the page: `chi web` runs one `Samagotchi::Web::SessionHub` (a t
|
|
|
267
301
|
the server, no daemon) that keeps an in-memory projection of the session list and pushes
|
|
268
302
|
changes to every open tab over `GET /api/events` (SSE: a `snapshot` frame on every connect,
|
|
269
303
|
then `session` for an upsert and `session_gone` for a removal, `: ping` while idle, no replay).
|
|
304
|
+
The snapshot names chi's version: a tab served by another one (its `<body data-version>`) offers a
|
|
305
|
+
reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade.
|
|
270
306
|
Files stay the source of truth and workers don't know the hub. Its watcher is a 1 s tick that
|
|
271
307
|
stats the sessions dir (every session writer goes tmp + rename, which bumps the dir's mtime) and
|
|
272
308
|
each `<id>/` folder (recap.json, bridge.json, the stopped and archived markers), re-parsing only the files whose mtime or size
|
data/docs/cli.md
CHANGED
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
- `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`). An approval can be denied, not allowed, unless `guardrails.parent_approvals: once` (a convention, not a security boundary; see [Guardrails](guardrails.md#approvals-from-a-parent-agent))
|
|
22
22
|
- `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text or images to a session (live, stopped or new) as a message (⏎, a turn runs) or a context note (⌘⏎) (see [Desktop helper](desktop.md))
|
|
23
23
|
- `chi models [--format text|json] [--timeout S] [TEXT]` — list the models every configured host offers, as the names `--model` takes (see [Listing the models](#listing-the-models))
|
|
24
|
-
- `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles
|
|
24
|
+
- `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
|
|
25
25
|
- `chi update [--dry-run] [--no-gem] [--no-bundles] [--no-desktop]` — update an installed chi: the gem, the system bundle, the shipped bundles you installed and the desktop helper, in one table (see [Updating](#updating))
|
|
26
|
-
- `chi bundle install|upgrade|uninstall|status|diff|list|build` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
|
|
26
|
+
- `chi bundle install|upgrade|uninstall|status|diff|list|build|trash` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
|
|
27
27
|
|
|
28
28
|
### First setup
|
|
29
29
|
|
|
@@ -283,7 +283,12 @@ In an attached terminal:
|
|
|
283
283
|
- The prompt stays open while a turn runs; see [Typing during a turn](#typing-during-a-turn).
|
|
284
284
|
- `/model`, `/models`, `/guardrails`, `/continue`, `!rollback` and `!commands` run in the
|
|
285
285
|
worker, and every UI sees their output; `/stats` and `/recap` work too. The
|
|
286
|
-
Web UI's composer takes the same commands
|
|
286
|
+
Web UI's composer takes the same commands, and so do `-p "/model x"` and
|
|
287
|
+
`chi send -m "/model x"`. A `/word` the session doesn't know (a path, a
|
|
288
|
+
typo) goes to the model as a prompt; a one-word `/word` that no command
|
|
289
|
+
answers (`/modle`) is not sent: the UI prints `Unknown command /modle. Did
|
|
290
|
+
you mean /model? /help lists the commands.` (the "Did you mean" part only
|
|
291
|
+
when a name is close).
|
|
287
292
|
- `!commands` and the model's tools run in the session's directory (where it
|
|
288
293
|
was started), whichever terminal you attach from.
|
|
289
294
|
- `-p` sends its prompt once attached, `--model` switches the worker's model
|
|
@@ -395,7 +400,9 @@ REPL alike:
|
|
|
395
400
|
- `/stats` and `/recap` answer at once, and in an attached terminal so do `/help`
|
|
396
401
|
and a plugin's anytime command (`/btw`). Other commands (`!cmd`, `/model`, `/models`,
|
|
397
402
|
`!rollback`, `/continue`, `/guardrails`) say `busy: wait for the turn to end`
|
|
398
|
-
and go back into the prompt, so Enter runs them once the turn ends.
|
|
403
|
+
and go back into the prompt, so Enter runs them once the turn ends. A
|
|
404
|
+
one-word `/word` no command answers (`/modle`) is not steering text either:
|
|
405
|
+
the hint prints and the line is dropped.
|
|
399
406
|
- A question (`ask_user_question`, a guardrails approval) turns the prompt into
|
|
400
407
|
a yellow `? ` and lists its choices under it, fitted to the terminal; only a
|
|
401
408
|
line submitted there answers it (a number, `1,3`, a label, `y`/`n` for an
|
|
@@ -801,8 +808,29 @@ Behavior notes:
|
|
|
801
808
|
|
|
802
809
|
## Iteration Limit Behavior
|
|
803
810
|
|
|
804
|
-
-
|
|
805
|
-
|
|
806
|
-
-
|
|
807
|
-
-
|
|
808
|
-
-
|
|
811
|
+
- A turn's step limit (model ↔ tool rounds) is `turn.max_iterations` in config.yml (env
|
|
812
|
+
`SAMAGOTCHI_TURN_MAX_ITERATIONS`, default `100`, an integer of 1 or more). `--no-interrupt` and
|
|
813
|
+
`--non-interactive` turns get the larger of 1000 and it. Reminder turns follow it too.
|
|
814
|
+
- Tool side effects that already ran before the limit are not rolled back.
|
|
815
|
+
- `Samagotchi::KernelLoop#run` returns a resumable result object with the visible output plus the accumulated
|
|
816
|
+
conversation. If the limit is reached while tool calls are still pending, the result is marked resumable so callers
|
|
817
|
+
can continue from the saved conversation instead of restarting from scratch. The session's `last_turn` records it
|
|
818
|
+
(`"exhausted": true, "limit": N`).
|
|
819
|
+
- In a session's worker (plain `chi`, the web, `chi send`) the offer waits as a question between turns, the
|
|
820
|
+
**step-limit question** (kind `continue`, header "Step limit", options Continue / Stop): the session reads
|
|
821
|
+
`waiting` in the lists, the web shows a card with Continue and Stop and a reason box (a reason goes with Stop), the
|
|
822
|
+
session card's badge reads `out of steps` and the bell rings ("hit its step limit"). `chi send --wait` exits 3 with
|
|
823
|
+
it and `chi answer --option Continue|Stop` answers it (see [chi as a sub-agent](sub-agent.md)). It can't be
|
|
824
|
+
dismissed. A new message instead (or a due reminder) drops it and the partial turn stays; a typed `/continue …`
|
|
825
|
+
answers it too. If the worker exits (a stop, a crash), the offer goes with it.
|
|
826
|
+
- In the attached terminal (`chi --attach`) the question's prompt reads the continue words: Enter alone, `yes`, `y`
|
|
827
|
+
or `/continue` continue; `no` stops; `no, <reason>` stops and tells the model why; `1` and `2` pick the options.
|
|
828
|
+
- In the plain REPL (`--no-shared`) the CLI asks at the `? ` prompt, with the choices listed under it: `yes` (Enter
|
|
829
|
+
alone, or `/continue`) resumes, `no` stops, and `no, <explanation>` stops while keeping the reason in conversation
|
|
830
|
+
context. Once answered, one line stays: `? The turn ran out of iterations. Continue it? → no, too slow`.
|
|
831
|
+
- Stop keeps the interrupted turn's work in the conversation, as a Ctrl-C or a new prompt does, with a note for the
|
|
832
|
+
model that it wasn't continued (or your reason); `!rollback` still erases it.
|
|
833
|
+
- `--non-interactive` (`chi -p`) has nobody to ask: the turn ends there. A `chi send --wait` on a session nobody can
|
|
834
|
+
answer (an older worker) reports `limit` (exit 1) with how to continue it: `chi send ID -m '/continue yes'`.
|
|
835
|
+
- The check-in bundle's card closes as the turn ends, before the step-limit question opens, so the two never stand
|
|
836
|
+
together; with check-in's default `after: 50` and the limit of 100 a long turn usually shows a check-in card first.
|
data/docs/configuration.md
CHANGED
|
@@ -279,14 +279,14 @@ spec. It requires the model to call `execute` and return the current UTC date:
|
|
|
279
279
|
|
|
280
280
|
```shell
|
|
281
281
|
SAMAGOTCHI_INTEGRATION=1 \
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
bundle exec rspec spec/integration/chat_loop_spec.rb
|
|
282
|
+
SAMAGOTCHI_INTEGRATION_HOST=192.0.2.10 SAMAGOTCHI_INTEGRATION_PORT=8000 \
|
|
283
|
+
SAMAGOTCHI_INTEGRATION_MODEL=incoai/Qwen3.8-27B-Splash \
|
|
284
|
+
bundle exec rspec spec/integration/chat_loop_spec.rb
|
|
285
285
|
```
|
|
286
286
|
|
|
287
287
|
The same test works against a llama.cpp OpenAI-compatible server by changing
|
|
288
288
|
the host, port, and model values. The test is skipped unless
|
|
289
|
-
`SAMAGOTCHI_INTEGRATION=1` is set.
|
|
289
|
+
`SAMAGOTCHI_INTEGRATION=1` is set; see [Testing](testing.md).
|
|
290
290
|
|
|
291
291
|
oMLX's known tool-call limitation (a stream filter that strips markup) only
|
|
292
292
|
affects its `/v1/chat/completions` endpoint, not the `/v1/completions` endpoint
|
|
@@ -869,6 +869,8 @@ described in their own sections.
|
|
|
869
869
|
| `retry.base_delay` | `0.5` | yes | |
|
|
870
870
|
| `retry.max_delay` | `8.0` | yes | |
|
|
871
871
|
| `retry.empty_answer` | `1` | | Times a turn asks again after an empty answer (at most 3, `0` = off). See "Llama Network Retry Behavior". |
|
|
872
|
+
| `turn.max_iterations` | `100` | | A turn's step limit: model ↔ tool rounds before it stops and asks to continue (an integer ≥ 1). `--no-interrupt` turns get the larger of 1000 and this. See [CLI: Iteration Limit Behavior](cli.md#iteration-limit-behavior). |
|
|
873
|
+
| `turn.parent_continue` | `true` | | Whether a parent agent (`chi answer`) may answer Continue to a session's step-limit question; `false`: Stop only. config.yml only: no environment variable. See [Sub-agent](sub-agent.md) and [Guardrails](guardrails.md#approvals-from-a-parent-agent). |
|
|
872
874
|
| `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
|
|
873
875
|
| `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
|
|
874
876
|
| `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
|
|
@@ -882,14 +884,14 @@ described in their own sections.
|
|
|
882
884
|
| `execute.truncate_at_bytes` | `65536` | yes | The same for `execute` output. |
|
|
883
885
|
| `execute.preview_bytes` | `12288` | yes | |
|
|
884
886
|
| `execute.telemetry_threshold_pct` | `80` | yes | |
|
|
885
|
-
| `execute.timeout_sec` | `
|
|
887
|
+
| `execute.timeout_sec` | `120` | yes | Seconds one `execute` command may run before it is stopped. |
|
|
886
888
|
| `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
|
|
887
889
|
| `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token (`chi web --new-token` replaces it). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
|
|
888
890
|
| `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
|
|
889
891
|
| `web.view` | `stage` | yes | How `chi web` draws a turn: `stage` (the running turn pinned above the composer) or `turn` (one block per turn in the history). See [CLI](cli.md#web-views). |
|
|
890
892
|
| `web.annotate_presets` | `Agreed\|Could you please elaborate?` | yes | Quick replies next to Annotate in `chi web`, `\|`-separated (a YAML list works too); `""` in the file or on the CLI leaves only Annotate (an empty env value means the default). See [CLI](cli.md#web-annotate-presets). |
|
|
891
893
|
| `history.file` | state dir | | Prompt history path, shared by the TUI and the web composer. |
|
|
892
|
-
| `no_interrupt` | `false` | `--no-interrupt` | Raise the
|
|
894
|
+
| `no_interrupt` | `false` | `--no-interrupt` | Raise the step limit of a turn to 1000 (or `turn.max_iterations`, when larger); a top-level key. |
|
|
893
895
|
| `no_default_input` | `false` | `--no-default-input` | Don't pre-fill `default.input`; a top-level key. |
|
|
894
896
|
| `skip_agent_md` | `false` | | Don't load AGENT.md; a top-level key (see below). |
|
|
895
897
|
|
data/docs/guardrails.md
CHANGED
|
@@ -118,6 +118,12 @@ directory, the rule) stay with the web and the terminal. An approval that
|
|
|
118
118
|
doesn't offer "once" can't be allowed from a parent, and neither can one whose
|
|
119
119
|
offered scopes are missing or don't fit its options: then only Deny goes.
|
|
120
120
|
|
|
121
|
+
A child that has to work in another folder (`write-outside-repo`,
|
|
122
|
+
`git-outside-repo`) is denied this way too. Start it in that folder instead
|
|
123
|
+
(`chi send --new --dir PATH`), or have your user allow "rule … in this repo"
|
|
124
|
+
once on the web (it is stored for later sessions in that repo), or switch the
|
|
125
|
+
rules off: `guardrails: {disable: [write-outside-repo, git-outside-repo]}`.
|
|
126
|
+
|
|
121
127
|
Whatever the setting, a parent can't allow a call on chi's own files: a
|
|
122
128
|
`write`, `edit` or `execute` on the config dir (config.yml, installed bundles'
|
|
123
129
|
rules), the hooks dir or the approval store, by its rule (`chi-config`,
|
|
@@ -126,6 +132,11 @@ config.yml rewrite would allow everything from then on, so those stay with the
|
|
|
126
132
|
user: `chi answer` and the worker refuse the allow with `only the user can
|
|
127
133
|
allow it`.
|
|
128
134
|
|
|
135
|
+
A turn's step-limit question (kind `continue`) isn't an approval: Continue
|
|
136
|
+
grants no permission, so a parent may answer it either way by default.
|
|
137
|
+
`turn.parent_continue: false` (config.yml only, like `parent_approvals`)
|
|
138
|
+
makes parents stop-only. See [chi as a sub-agent](sub-agent.md#the-step-limit).
|
|
139
|
+
|
|
129
140
|
The guardrails settings (`enabled`, `small_models`, `parent_approvals`) are
|
|
130
141
|
read from config.yml only: they have no environment variable, and a worker
|
|
131
142
|
chi starts unsets every `SAMAGOTCHI_GUARDRAILS_*` it would inherit. What a
|
|
@@ -182,9 +193,16 @@ guardrails:
|
|
|
182
193
|
tool: [write, edit]
|
|
183
194
|
path: outside_repo # or a glob: "**/.git/hooks/**", "/etc/**", "config/*.yml"
|
|
184
195
|
verdict: ask
|
|
185
|
-
reason: writes outside
|
|
196
|
+
reason: writes outside this session's repo
|
|
197
|
+
- id: git-outside-repo
|
|
198
|
+
tool: shell
|
|
199
|
+
git: outside_repo # a shell call runs commit/add/reset/… in another checkout
|
|
200
|
+
verdict: ask
|
|
201
|
+
scopes: [once, session, rule]
|
|
186
202
|
```
|
|
187
203
|
|
|
204
|
+
A rule gives at least one of `tool`, `command`, `path` and `git`.
|
|
205
|
+
|
|
188
206
|
A tool name may be a glob, so one rule covers a plugin's tools (an MCP server's,
|
|
189
207
|
say): `tool: "mcp_*"` or `tool: ["mcp_{git,gh}_*", web_fetch]` (`*`, `?`, `[…]` and
|
|
190
208
|
`{a,b}`, matched with `File.fnmatch`). `/guardrails` lists the glob as given.
|
|
@@ -203,11 +221,30 @@ All the fields a rule gives must match. Absolute and `**/` globs match the
|
|
|
203
221
|
resolved path; other globs match the path relative to the repo root. Paths
|
|
204
222
|
resolve the way the tools resolve them (against the cwd; `~` expanded).
|
|
205
223
|
|
|
206
|
-
`outside_repo`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
`outside_repo` is measured from the session's repo: the git work tree its
|
|
225
|
+
folder is in (a linked worktree is its own), or the folder itself outside a
|
|
226
|
+
repo. Not the call's own `cwd:`: an `execute` with `cwd:` in another checkout
|
|
227
|
+
is outside. Symlinks are resolved on both sides (`/tmp` and `/private/tmp` are
|
|
228
|
+
one folder; a link in the repo that points elsewhere is outside). Never outside:
|
|
229
|
+
|
|
230
|
+
- a memory's file: a `*.md` right in the project or system memories folder (not
|
|
231
|
+
`index.md`, not a hidden file). `write`/`edit` there is what `memory_write`
|
|
232
|
+
does unasked, and the model is told to `edit` a memory for a small change.
|
|
233
|
+
The memories folder's `index.md`, `.bundles/`, and the rest of chi's config
|
|
234
|
+
folder still count as outside, as does chi's state folder (sessions,
|
|
235
|
+
approvals);
|
|
236
|
+
- a tmp folder (`$TMPDIR`, `/tmp`, `/var/tmp`), unless the session's repo is
|
|
237
|
+
itself in that tmp folder: a sandbox there still gets asked about its siblings.
|
|
238
|
+
|
|
239
|
+
`git: outside_repo` (shell tools only) reads the command for git subcommands
|
|
240
|
+
that change a checkout: `commit`, `add`, `reset`, `checkout`, `switch`,
|
|
241
|
+
`rebase`, `merge`, `push`, `stash` (not `stash list`/`show`), `rm`, `mv`,
|
|
242
|
+
`cherry-pick`, `revert`, `pull`, `restore`, `am`. It follows `cd`,
|
|
243
|
+
`pushd`/`popd`, `( … )`, `git -C`, `--git-dir`/`--work-tree`, `GIT_DIR`/`GIT_WORK_TREE`
|
|
244
|
+
and the call's `cwd:`, and matches when one of them runs outside the session's
|
|
245
|
+
repo (the same `outside_repo` as above). `cd /other && git status`, `git -C
|
|
246
|
+
/other log` and `cd /other && bundle exec rspec` don't match. A folder the text
|
|
247
|
+
doesn't tell (`cd $REPO`, `cd "$(…)"`, `cd -`) doesn't either; see Limits.
|
|
211
248
|
|
|
212
249
|
To switch off single rules (a bundle's, say) without editing its files, list
|
|
213
250
|
them under `disable:`. A plain id switches off every rule with that id; `bundle:id`
|
|
@@ -268,7 +305,10 @@ installs a default rule set plus a short memory telling the model not to
|
|
|
268
305
|
route around a deny. It asks before `git push`, `reset --hard`, `clean -f`,
|
|
269
306
|
`branch -D`, `rebase`, `filter-branch`/`filter-repo`; `rm -rf` on `/`, `~`,
|
|
270
307
|
`$HOME` or `..` paths; `curl … | sh` and `base64 -d … | sh`; writes outside the
|
|
271
|
-
repo
|
|
308
|
+
session's repo (`write-outside-repo`) and git that changes another checkout
|
|
309
|
+
(`git-outside-repo`: `cd ../main && git commit`, `git -C ../main add .`; it
|
|
310
|
+
offers once, this session and "rule in this repo", the last stored for later
|
|
311
|
+
sessions in that repo); shell commands that name chi's config, hooks or guardrails or
|
|
272
312
|
`.git/hooks`; and answering chi's questions around `chi answer`: `chi --attach`
|
|
273
313
|
or `chi -p` with stdin from a pipe, a here-string or a file
|
|
274
314
|
(`chi-answer-piped`), and `curl`/`wget` to a session's `/answer` route
|
|
@@ -354,6 +394,9 @@ it as `~` or `$HOME`, so the model rarely has to spell it.
|
|
|
354
394
|
|
|
355
395
|
Text matching on shell commands stops accidents, not a model set on getting
|
|
356
396
|
around it: `sh -c`, base64, a script written earlier, `git -C` variants and
|
|
357
|
-
aliases can get past a regex.
|
|
397
|
+
aliases can get past a regex. `outside_repo` covers the file tools and git
|
|
398
|
+
only: `sed -i`, `cp` or `cat > file` into another folder from the shell aren't
|
|
399
|
+
asked about, and neither is git run through `sh -c`, a script, an alias, `make`
|
|
400
|
+
or a folder in a variable. `!cmd` lines typed by you are not checked. The only
|
|
358
401
|
real defence against an adversarial model is isolation (a sandbox, a git
|
|
359
402
|
identity without push rights).
|
data/docs/hooks.md
CHANGED
|
@@ -341,7 +341,10 @@ the voter's own (a guard that wants the model to retry a corrected call:
|
|
|
341
341
|
`event[:context]` is `{cwd:, repo_root:, branch:, session_id:, interface:, origin:}`
|
|
342
342
|
(`interface` is `:repl`, `:worker` or `:non_interactive`). `event[:targets]` is
|
|
343
343
|
what the call acts on, resolved as the tools resolve it:
|
|
344
|
-
`{command:, paths:, cwd:, repo_root:, outside_repo:}`.
|
|
344
|
+
`{command:, paths:, cwd:, repo_root:, outside_repo:, git_dirs:}`. `outside_repo`
|
|
345
|
+
is measured from the session's repo, not the call's `cwd:`; `git_dirs` lists where
|
|
346
|
+
a shell call runs git that changes a checkout (`"unknown"` for a folder the
|
|
347
|
+
text doesn't tell; `[]` for other tools).
|
|
345
348
|
|
|
346
349
|
The older flag still works: `event[:blocked] = true` with an optional
|
|
347
350
|
`event[:block_reason]`. It is folded into the verdict after each hook (so it
|
|
@@ -22,5 +22,6 @@ Behavior:
|
|
|
22
22
|
- `task_get` returns metadata and `output_path`; use `read` for output contents.
|
|
23
23
|
- `task_wait` accepts `timeout`, `tail_lines` (maximum 100), and `done_pattern` (a regular expression string).
|
|
24
24
|
- `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.
|
|
25
|
+
- `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) and `SAMAGOTCHI_SESSION_MODEL` (the resolved ref the session runs on, read at each call so it follows `/model`; unset when unknown). `chi self` reads the latter for its `model` row.
|
|
25
26
|
|
|
26
27
|
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/memory.md
CHANGED
|
@@ -23,6 +23,8 @@ Each memory entry may have a companion file named `<name>.<model-key>.md` in the
|
|
|
23
23
|
|
|
24
24
|
- **Key derivation**: The harness normalizes the model name sent to the server, without its host prefix (`box:qwen3.6-35b-a3b` keys as `qwen3.6-35b-a3b`; an alias keys as its target, and the alias's own key is read when the target has no overlay) (lowercase, replace non-alphanumeric with `-`, squeeze dashes) to derive the file key. For example, `qwen3.6-35b-a3b` → `qwen3-6-35b-a3b`.
|
|
25
25
|
- **Saving overlays**: Pass `current_model_only: true` to `memory_write` (the harness resolves the model key automatically). This writes the content as `<name>.<model-key>.md` and skips index maintenance.
|
|
26
|
+
- **Which key is mine**: the system prompt's `Model:` line names the session's model key, and `chi self` (via `execute`) has a `model key` row; both follow `/model`.
|
|
27
|
+
- **In the system prompt**: the identity memory and the preloaded memories (`--memory`, config `memories:`) get their overlays too, as `memory_read` gives them; after `/model` the rebuilt prompt carries the new model's.
|
|
26
28
|
- **Dormancy**: Overlays are only active under the matching model key; other models see the base entry only.
|
|
27
29
|
- **Invariant**: The base entry is the contract. Overlays only add model-specific guidance and never contradict the base protocol.
|
|
28
30
|
|
|
@@ -80,6 +82,45 @@ skills`, [docs/plugins.md](plugins.md#the-skills-bundle)) adds `/skill save`,
|
|
|
80
82
|
one-line diff after each update, and nudges a model that skips a failing
|
|
81
83
|
step instead of fixing the skill.
|
|
82
84
|
|
|
85
|
+
## What a bundle owns
|
|
86
|
+
|
|
87
|
+
A bundle owns the memory files it wrote, nothing else. `chi bundle install`
|
|
88
|
+
copies each of its `*.md` files into the scope dir; a file of the same name
|
|
89
|
+
that is already there is left as it is and reported `Skipped` (`--force`
|
|
90
|
+
overwrites it, and then the bundle owns it). A skipped file stays yours: the
|
|
91
|
+
bundle doesn't record it, so `chi bundle upgrade` never merges into it and
|
|
92
|
+
`chi bundle uninstall` never removes it. That holds for a file the user wrote,
|
|
93
|
+
for `chi bundle build` then installing the result on the same machine, and
|
|
94
|
+
for a file another bundle installed.
|
|
95
|
+
|
|
96
|
+
- **The index says where a memory came from.** The `index.md` line of a file
|
|
97
|
+
a bundle installed ends in `· from <bundle>`, before the description:
|
|
98
|
+
`- **memory_guide** · system · 2026-10-03 · 5120 · from samagotchi-system —
|
|
99
|
+
…`. `memory_write`, `write` and `edit` keep it. A bundle installed by chi
|
|
100
|
+
0.16 or earlier gets it on its next install or upgrade.
|
|
101
|
+
- **Uninstall moves, it doesn't delete.** The bundle's memory files go to
|
|
102
|
+
`$XDG_CONFIG_HOME/samagotchi/memories/.bundles/.trash/<bundle>-<YYYYmmdd-HHMMSS>/`,
|
|
103
|
+
and the output names them and the dir:
|
|
104
|
+
`Moved to the trash: notes.md (…/.trash/team-notes-20261003-120000)`. So
|
|
105
|
+
does an upgrade's removal of a file the new version no longer ships. A
|
|
106
|
+
file you edited still needs `--force`. `chi bundle trash` lists the trash
|
|
107
|
+
(oldest first, with file count, size, and age); `--empty` deletes all
|
|
108
|
+
folders, `--older-than DAYS` keeps only recent ones, and `--dry-run`
|
|
109
|
+
previews. Hooks, rules and the plugin live in the bundle's own dir and
|
|
110
|
+
are deleted with it.
|
|
111
|
+
- **A file two bundles list stays** until the last of them is uninstalled
|
|
112
|
+
(`Kept identity.md: bundle samagotchi-system has it too`); its index line
|
|
113
|
+
then names the bundle that still has it.
|
|
114
|
+
- **Installs by chi 0.16 or earlier** recorded skipped files too. Such a bundle may
|
|
115
|
+
still list a file that was yours; uninstalling it moves that file to the
|
|
116
|
+
trash rather than deleting it, so check the `Moved to the trash` line and
|
|
117
|
+
move a file back if it was yours (and its line in `index.md` comes back
|
|
118
|
+
with the next `memory_write`, `write` or `edit` of it).
|
|
119
|
+
- **`chi bundle build`** packs your memories, not other bundles': a memory an
|
|
120
|
+
installed bundle owns is left out with a line (`Left out identity.md:
|
|
121
|
+
installed by bundle samagotchi-system (name it to include it)`); naming it
|
|
122
|
+
in `FILES...` includes it.
|
|
123
|
+
|
|
83
124
|
## Bundle profiles: core and dev
|
|
84
125
|
|
|
85
126
|
Every bundle chi ships (the system bundle aside) belongs to one of two
|
data/docs/plugins.md
CHANGED
|
@@ -229,8 +229,9 @@ chi's own. Path and command rules need to know what a call acts on, and that
|
|
|
229
229
|
is what `targets:` says:
|
|
230
230
|
|
|
231
231
|
- `paths:`: files the call reads or writes, absolute or relative to `cwd:`
|
|
232
|
-
(else the session's directory). `path:` globs, `outside_repo`
|
|
233
|
-
|
|
232
|
+
(else the session's directory). `path:` globs, `outside_repo` (measured
|
|
233
|
+
from the session's repo, whatever the call's `cwd:`) and the protected
|
|
234
|
+
paths (chi's config, …) match them.
|
|
234
235
|
- `command:`: a shell command the call runs; `command:` rules match it.
|
|
235
236
|
- `cwd:`: where it runs, for the repo root and relative paths.
|
|
236
237
|
|
|
@@ -478,6 +479,8 @@ full label, `plugin.rb (bundle my-bundle)`.
|
|
|
478
479
|
| `ctx.session_id` | the session's id (nil before there is one) |
|
|
479
480
|
| `ctx.cwd` | the session's working directory |
|
|
480
481
|
| `ctx.repo_root` | the git checkout holding `cwd`, or nil |
|
|
482
|
+
| `ctx.model` | the model the session runs on now: its resolved ref (`host:id`), right after `/model` too |
|
|
483
|
+
| `ctx.model_key` | that model's memory overlay key (`<name>.<key>.md`, what `memory_write current_model_only` writes) |
|
|
481
484
|
| `ctx.settings` | the bundle's settings, frozen |
|
|
482
485
|
| `ctx.data_dir` | `$XDG_STATE_HOME/samagotchi/plugins/<bundle>/`, created on first use. A session's own state goes in `sessions/<id>.json` or `sessions/<id>/` there: chi removes it when the session is deleted, discarded or pruned |
|
|
483
486
|
| `ctx.log` | `ctx.log.info(:event, key: value)`: debug-log records tagged `plugins`, with `bundle=<bundle>` |
|