samagotchi 0.8.0 → 0.9.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 +54 -1
- data/docs/configuration.md +1 -0
- data/docs/guardrails.md +48 -2
- data/docs/memory.md +5 -3
- data/docs/plugins.md +13 -5
- data/docs/releasing.md +1 -1
- data/lib/samagotchi/bundles/guardrails/guardrails/small-models.yml +27 -0
- data/lib/samagotchi/bundles/guardrails/manifest.yml +4 -2
- data/lib/samagotchi/bundles/skills/manifest.yml +3 -3
- data/lib/samagotchi/bundles/skills/plugin.rb +61 -11
- data/lib/samagotchi/bundles/system/identity.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +5 -4
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/engine.rb +13 -0
- data/lib/samagotchi/guardrail_wiring.rb +4 -1
- data/lib/samagotchi/guardrails/gate.rb +7 -4
- data/lib/samagotchi/guardrails/model_size.rb +87 -0
- data/lib/samagotchi/guardrails/rules.rb +35 -3
- data/lib/samagotchi/guardrails/targets.rb +29 -5
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/log_subscriber.rb +5 -1
- data/lib/samagotchi/memory_bundle/index_sync.rb +73 -0
- data/lib/samagotchi/memory_bundle/installer.rb +24 -3
- data/lib/samagotchi/session_commands.rb +6 -1
- data/lib/samagotchi/session_manager.rb +2 -2
- data/lib/samagotchi/session_metrics.rb +2 -1
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +2 -1
- data/lib/samagotchi/terminal_ui/event_renderer.rb +3 -1
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +20 -3
- data/lib/samagotchi/tools/memory.rb +1 -2
- data/lib/samagotchi/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9290117f26040b725e12c5cd18bf2a8858a573f34e2323e5a56517c030f95d7f
|
|
4
|
+
data.tar.gz: 7e5d1e2e8a285424b907172038f6bab1045b1388129241cc1a32cb26132437ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d70bd257f5b82a2057077e2142adf5601f4bc652a0e820c695b5ecff305156c0270815374054e41d6403ecb0ee4579fd75fda84f670094e99a21f7cf73e34b5c
|
|
7
|
+
data.tar.gz: 961269d190e236ecbe3a3ce0dae6d18d86adf93f5c16aa8246dd91e10a43b73152c8595a4e6377491aaf86e74d8558b225893364dc6dd226634d6b94a7484504
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,57 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.9.0] - 2026-10-01
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- A small change to a memory or skill can go through `edit` on its file instead of rewriting it all with
|
|
16
|
+
`memory_write`; the prompt, the memory guide and the nudge below say so. `write`/`edit` on a memory's file
|
|
17
|
+
refresh its line in `index.md` (date, size; the description is kept), and the default guardrails no longer ask
|
|
18
|
+
before them as "writes outside the repository".
|
|
19
|
+
- The system prompt no longer tells the model to keep `index.md` updated (`memory_write` does it); its result
|
|
20
|
+
says "Index line refreshed automatically." instead of naming the index file.
|
|
21
|
+
- `skills` bundle 0.1.1 (`chi update`; needs chi 0.9.0): a skill changed some other way (an `execute` running `sed`) counts as
|
|
22
|
+
updated: you get the usual "skill X updated" line, `/skill diff` has the old text, and the nudge and the turn-end
|
|
23
|
+
"wasn't updated" line no longer fire for it. The nudge asks to `edit` the changed step rather than rewrite the
|
|
24
|
+
whole skill.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `chi bundle install` of a bundle that's already installed no longer says "Skipped X (already exists; use --force
|
|
29
|
+
to overwrite)" for a file that's the same as the bundle's; it skips it quietly and says once that
|
|
30
|
+
`chi bundle upgrade NAME` updates the bundle and keeps local edits. A file skipped for a local edit no longer
|
|
31
|
+
also reports a checksum mismatch.
|
|
32
|
+
- `chi bundle install` and `upgrade` no longer list a bundle's unchanged guardrail rule files as "Installed"; they
|
|
33
|
+
are skipped as already up to date like its memory files.
|
|
34
|
+
- A memory file you edited, then `chi bundle install` of its bundle again (skipped for the edit), no longer gets
|
|
35
|
+
overwritten by the next `chi bundle upgrade`: the re-install kept your edit as the bundle's base, so the upgrade
|
|
36
|
+
took it as unedited.
|
|
37
|
+
- `chi sessions list --sort/--order` is no longer ignored with `--live`, `--cwd` or `--format`.
|
|
38
|
+
- A tool's time in the web's turn timing and the debug log's `tool_call_completed ms:` no longer counts the wait for
|
|
39
|
+
a guardrail approval; the log line shows the wait as `waited_ms:`.
|
|
40
|
+
|
|
41
|
+
Update with `chi update` (bundles: `skills` 0.1.1).
|
|
42
|
+
|
|
43
|
+
## [0.8.1] - 2026-10-01
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Guardrail rules can name the models they apply to (`models: small`, or globs on the model name). The guardrails
|
|
48
|
+
bundle (0.2.0, `chi update`; needs chi 0.8.1) adds two asks only small models get: `git checkout -- <file>` /
|
|
49
|
+
`git restore` (discards uncommitted changes) and `git stash drop`/`clear`. Small is `guardrails.small_models`:
|
|
50
|
+
`auto` (the default: 32B or less by the size in the model's name, an MoE's active size counting), a list of
|
|
51
|
+
globs, or `[]` for none. A chi older than 0.8.1 that shares the bundles dir (a checkout next to the gem, or a
|
|
52
|
+
worker still running after a gem update) doesn't know `models:` and denies every tool call until it is
|
|
53
|
+
restarted on the new chi.
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- A tool row's time in the REPL and the attached TUI no longer counts the wait for a guardrail approval
|
|
58
|
+
(`blocked (2m 54s)` for a call denied at once).
|
|
59
|
+
- docs/guardrails.md: rules are read again on the next tool call after config.yml or a bundle's rules change, with
|
|
60
|
+
no restart.
|
|
61
|
+
|
|
11
62
|
## [0.8.0] - 2026-10-01
|
|
12
63
|
|
|
13
64
|
### Added
|
|
@@ -462,7 +513,9 @@ and long-lived sessions.
|
|
|
462
513
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
463
514
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
464
515
|
|
|
465
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
516
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.9.0...HEAD
|
|
517
|
+
[0.9.0]: https://github.com/dm1try/samagotchi/compare/v0.8.1...v0.9.0
|
|
518
|
+
[0.8.1]: https://github.com/dm1try/samagotchi/compare/v0.8.0...v0.8.1
|
|
466
519
|
[0.8.0]: https://github.com/dm1try/samagotchi/compare/v0.7.0...v0.8.0
|
|
467
520
|
[0.7.0]: https://github.com/dm1try/samagotchi/compare/v0.6.0...v0.7.0
|
|
468
521
|
[0.6.0]: https://github.com/dm1try/samagotchi/compare/v0.5.1...v0.6.0
|
data/docs/configuration.md
CHANGED
|
@@ -753,6 +753,7 @@ described in their own sections.
|
|
|
753
753
|
| `image.max_bytes` | `3750000` | | See "Images". |
|
|
754
754
|
| `image.max_per_request` | `20` | | See "Images". |
|
|
755
755
|
| `guardrails.enabled` | `true` | | See [Guardrails](guardrails.md). |
|
|
756
|
+
| `guardrails.small_models` | `auto` | | Which models get the `models: small` rules: `auto` (32B or less by the size in the name, an MoE's active size), a list of globs, or `[]`. See [Guardrails](guardrails.md#rules-for-some-models). |
|
|
756
757
|
| `log.file` | state dir | yes | See "Debug Log File". |
|
|
757
758
|
| `log.disable` | `false` | yes | No file logging. |
|
|
758
759
|
| `log.level` | `info` | yes | `debug`, `info`, `warn`, `error`. |
|
data/docs/guardrails.md
CHANGED
|
@@ -126,6 +126,12 @@ All the fields a rule gives must match. Absolute and `**/` globs match the
|
|
|
126
126
|
resolved path; other globs match the path relative to the repo root. Paths
|
|
127
127
|
resolve the way the tools resolve them (against the cwd; `~` expanded).
|
|
128
128
|
|
|
129
|
+
`outside_repo` leaves out a memory's file: a `*.md` right in the project or
|
|
130
|
+
system memories folder (not `index.md`, not a hidden file). `write`/`edit`
|
|
131
|
+
there is what `memory_write` does unasked, and the model is told to `edit` a
|
|
132
|
+
memory for a small change. The memories folder's `index.md`, `.bundles/`, and
|
|
133
|
+
the rest of chi's config folder still count as outside.
|
|
134
|
+
|
|
129
135
|
To switch off single rules (a bundle's, say) without editing its files, list
|
|
130
136
|
them under `disable:`. A plain id switches off every rule with that id; `bundle:id`
|
|
131
137
|
only that bundle's:
|
|
@@ -138,8 +144,40 @@ guardrails:
|
|
|
138
144
|
`/guardrails` marks them `disabled (guardrails.disable)` and names entries that
|
|
139
145
|
match no rule. `disable:` only removes rules; hooks and the core checks still vote.
|
|
140
146
|
|
|
141
|
-
Rules
|
|
142
|
-
|
|
147
|
+
### Rules for some models
|
|
148
|
+
|
|
149
|
+
`models:` makes a rule vote only for some models: `small`, or a glob on the
|
|
150
|
+
model's name (`Qwen3.6-*`, matched on the bare name without the host prefix and
|
|
151
|
+
on the model key `qwen3-6-27b`, case-insensitively), or a list of them. Without
|
|
152
|
+
`models:` a rule is for every model. With no model name set, a `models:` rule
|
|
153
|
+
doesn't vote.
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
guardrails:
|
|
157
|
+
small_models: auto # the default; or a list of globs, or [] for none
|
|
158
|
+
rules:
|
|
159
|
+
- id: no-force-push-small
|
|
160
|
+
tool: shell
|
|
161
|
+
command: '\bgit\s+push\b.*--force'
|
|
162
|
+
models: small # or "gemma-*", or [small, "deepseek-*"]
|
|
163
|
+
verdict: deny
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Which models are small is `guardrails.small_models`. Nothing reports a model's
|
|
167
|
+
size, so `auto` reads it from the name: `Qwen3.6-27B` is 27B, `gemma-4-E4B-it`
|
|
168
|
+
4B, `Mixtral-8x7B` 56B, and for an MoE the active size counts
|
|
169
|
+
(`Ornith-1.5-35B-A3B` is 3B, `Qwen3-235B-A22B` 22B). 32B or less is small. A name
|
|
170
|
+
without a size (`deepseek-v4.1-flash`) is not small. A list of globs names the
|
|
171
|
+
small models yourself (`auto` may be one of them), and `[]` means no model is
|
|
172
|
+
small. It is read on every check, so `/model` and config edits apply at once.
|
|
173
|
+
|
|
174
|
+
`/guardrails` names the model and whether it is small (`model: Qwen3.6-27B —
|
|
175
|
+
small (auto, 27B)`), shows each rule's `models`, and marks a rule that doesn't
|
|
176
|
+
vote for the current model `off for this model`.
|
|
177
|
+
|
|
178
|
+
Rules are read again on the next tool call after `config.yml` or an installed
|
|
179
|
+
bundle's rules change, so a long-running worker follows edits without a
|
|
180
|
+
restart. A rule that doesn't parse (an unknown key, a bad regex, no
|
|
143
181
|
verdict, a `disable:` that isn't a list of ids) makes chi **deny every tool call** and say why, rather than run
|
|
144
182
|
without it.
|
|
145
183
|
|
|
@@ -157,6 +195,14 @@ repo; and shell commands that name chi's config, hooks or guardrails or
|
|
|
157
195
|
`.git/hooks`. It denies writes into `.git/hooks`. The rules are in
|
|
158
196
|
`lib/samagotchi/bundles/guardrails/guardrails/rules.yml`.
|
|
159
197
|
|
|
198
|
+
Small models (`models: small`, see above) get two more asks, in
|
|
199
|
+
`guardrails/small-models.yml`: `git checkout -- <path>`, `git checkout <rev> --
|
|
200
|
+
<path>`, `git checkout .` and `git restore <path>` (not `--staged` alone), which
|
|
201
|
+
discard uncommitted changes, and `git stash drop`/`clear`. They offer only
|
|
202
|
+
"once" and "this session", so an approval doesn't silence them for good. To get
|
|
203
|
+
them on every model, set `small_models` to `"*"`; to drop them, `[]` (or
|
|
204
|
+
`disable:` the ids).
|
|
205
|
+
|
|
160
206
|
A bundle ships rules as `guardrails/*.yml` (the same `rules:` shape). Install
|
|
161
207
|
records each file's sha256; a file changed afterwards, missing, or not parsing
|
|
162
208
|
denies every call until the bundle is reinstalled.
|
data/docs/memory.md
CHANGED
|
@@ -12,7 +12,8 @@ Tool behavior:
|
|
|
12
12
|
- `memory_read`: `scope` is optional.
|
|
13
13
|
- If `scope` is provided (`project` or `system`), only that scope is read.
|
|
14
14
|
- If `scope` is omitted, read falls back from project to system.
|
|
15
|
-
- `memory_write`: `scope` is required (`project` or `system`). The entry name is passed via the `name` parameter (not `path` — the file tools use `path`). On success, the return value includes the full file path
|
|
15
|
+
- `memory_write`: `scope` is required (`project` or `system`). The entry name is passed via the `name` parameter (not `path` — the file tools use `path`). On success, the return value includes the full file path. For a small change to an existing memory (a step, a line) the model is told to `edit` that file rather than rewrite it all with `memory_write`.
|
|
16
|
+
- `write`/`edit` that change a `*.md` right in a scope dir refresh its managed `index.md` line (date, bytes; the description is kept), as `memory_write` does. A memory created with `write` gets a line with no description; any other `.md` written there gets a line too. Model overlays (`<name>.<key>.md` next to `<name>.md`), files in another project's folder and files changed by `execute` are left out.
|
|
16
17
|
|
|
17
18
|
## Model-Specific Memory Overlays
|
|
18
19
|
|
|
@@ -51,8 +52,9 @@ chi is told about skills by the system bundle (`identity.md`, every turn, and
|
|
|
51
52
|
looks unexpected on the way (a failing check, a warning the skill doesn't
|
|
52
53
|
mention), chi stops, says what it saw and asks how to go on.
|
|
53
54
|
- **Updating.** When a step turned out different (a renamed script, an extra
|
|
54
|
-
step), chi fixes the skill in the same turn: those steps changed
|
|
55
|
-
|
|
55
|
+
step), chi fixes the skill in the same turn: those steps changed (with `edit`
|
|
56
|
+
on its file, so a small fix doesn't resend the whole skill), the rest kept, a
|
|
57
|
+
dated Changelog line added. No confirmation.
|
|
56
58
|
|
|
57
59
|
A skill is plain Markdown, no frontmatter:
|
|
58
60
|
|
data/docs/plugins.md
CHANGED
|
@@ -940,16 +940,24 @@ skills> skill release updated (+2 −1): 1. Run `scripts/verify.sh`; stop if it
|
|
|
940
940
|
The line is the file on disk changing, whatever the tool answered; a denied
|
|
941
941
|
write shows nothing.
|
|
942
942
|
|
|
943
|
+
A skill read in the turn is also watched for changes made another way (an
|
|
944
|
+
`execute` running `sed`, a script): after each other tool call, and at the
|
|
945
|
+
turn's end, its file is compared with the content last seen (at the read, or
|
|
946
|
+
after the last write). A change shows the same line, keeps the content last
|
|
947
|
+
seen as a version (so `/skill diff` has it) and counts as the skill updated.
|
|
948
|
+
A plain read keeps no version.
|
|
949
|
+
|
|
943
950
|
**The nudge** (`nudge: true`). Some models, finding a skill's step broken,
|
|
944
951
|
skip it and go on without fixing the skill. In a turn that read a skill
|
|
945
952
|
(`memory_read` of a `skill_*` name, or `read` of its file), the first failing
|
|
946
953
|
tool call after it (an `execute` that exited non-zero, a tool error) steers
|
|
947
954
|
the model once: *"A step of skill release failed. Find out why before skipping
|
|
948
|
-
it; if the skill is out of date, fix it now:
|
|
949
|
-
its
|
|
950
|
-
|
|
951
|
-
skill
|
|
952
|
-
failed, the skill wasn't updated`. A failure
|
|
955
|
+
it; if the skill is out of date, fix it now: edit the step that changed in
|
|
956
|
+
its file (or memory_write the whole skill) and add a Changelog line."* No
|
|
957
|
+
steer once a skill read this turn has changed, by any tool. If the turn ends
|
|
958
|
+
with a failed step and no skill read changed, one line says so: `skill
|
|
959
|
+
release was followed, a step failed, the skill wasn't updated`. A failure
|
|
960
|
+
unrelated to the skill (a test
|
|
953
961
|
meant to fail) can set it off too: once per turn, and only after a skill was
|
|
954
962
|
read.
|
|
955
963
|
|
data/docs/releasing.md
CHANGED
|
@@ -49,7 +49,7 @@ as the GitHub release body.
|
|
|
49
49
|
| Task | What it does |
|
|
50
50
|
| --- | --- |
|
|
51
51
|
| `rake bundles:sha` | Recomputes the sha256 lines of every shipped bundle manifest (`files:`, `hooks:`, `plugin:`). |
|
|
52
|
-
| `rake bundles:check` | Sha lines match; a bundle changed since the last `v*` tag has a higher version (skipped, with a note, before the first tag). |
|
|
52
|
+
| `rake bundles:check` | Sha lines match; a bundle changed since the last `v*` tag has a higher version (skipped, with a note, before the first tag). Warns (doesn't fail) when such a bundle's `requires_chi` still admits the tagged chi: raise it if the change uses anything new. |
|
|
53
53
|
| `rake release:draft_changelog` | A draft Unreleased section from the commit subjects since the last tag, grouped. Stdout only. |
|
|
54
54
|
| `rake release:bump[X.Y.Z]` | VERSION, the system manifest, Gemfile.lock, CHANGELOG. No commit. |
|
|
55
55
|
| `rake release:check` | Clean tracked tree, VERSION == system bundle, a CHANGELOG section, `bundles:check`, rspec + npm test, `gem build`, then a clean install into a temp GEM_HOME that runs `chi --version`, `chi self` and `chi bundle list` with a temp HOME. Needs the network (gem dependencies). |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Guardrail rules only small models get (models: small): asks that a strong
|
|
2
|
+
# model would find noise. Which models are small is guardrails.small_models:
|
|
3
|
+
# auto (the default) is 32B or less by the size in the model's name, an
|
|
4
|
+
# MoE's active size counting (35B-A3B is small); a list of globs; or [] for
|
|
5
|
+
# none. `/guardrails` says whether the current model is small.
|
|
6
|
+
#
|
|
7
|
+
# These asks offer only once and session: an approval for the repo or the
|
|
8
|
+
# whole rule would silence them for good, on any model.
|
|
9
|
+
# Same fields and git prefix as rules.yml.
|
|
10
|
+
rules:
|
|
11
|
+
# ── losing uncommitted work ────────────────────────────────────────────
|
|
12
|
+
# git checkout -- <path>, git checkout <rev> -- <path>, git checkout .,
|
|
13
|
+
# and git restore of the worktree (not git restore --staged alone).
|
|
14
|
+
- id: git-discard-worktree
|
|
15
|
+
tool: shell
|
|
16
|
+
command: '\bgit(?:\s+(?:-C\s+\S+|-c\s+\S+|--?[A-Za-z][\w-]*(?:=\S+)?))*\s+(?:checkout\b[^;&|\n]*\s(?:--\s+\S|\.(?:\s|$|/))|restore\b(?![^;&|\n]*\s--staged\b)(?=[^;&|\n]*\s[^-\s])|restore\b(?=[^;&|\n]*\s(?:--worktree|-W)\b))'
|
|
17
|
+
models: small
|
|
18
|
+
verdict: ask
|
|
19
|
+
reason: git checkout -- / git restore discards uncommitted changes
|
|
20
|
+
scopes: [once, session]
|
|
21
|
+
- id: git-stash-drop
|
|
22
|
+
tool: shell
|
|
23
|
+
command: '\bgit(?:\s+(?:-C\s+\S+|-c\s+\S+|--?[A-Za-z][\w-]*(?:=\S+)?))*\s+stash\s+(drop|clear)\b'
|
|
24
|
+
models: small
|
|
25
|
+
verdict: ask
|
|
26
|
+
reason: git stash drop/clear deletes stashed work
|
|
27
|
+
scopes: [once, session]
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: guardrails
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.2.0
|
|
4
4
|
scope: system
|
|
5
|
-
description: Optional default guardrail rules (git push/rewrites, rm -rf wide, curl | sh, writes outside the repo, git hooks) and a note for the model
|
|
5
|
+
description: Optional default guardrail rules (git push/rewrites, rm -rf wide, curl | sh, writes outside the repo, git hooks), asks only small models get (git checkout --/restore, git stash drop), and a note for the model
|
|
6
6
|
trust_level: reviewed
|
|
7
|
+
# models: in small-models.yml (guardrails.small_models)
|
|
8
|
+
requires_chi: ">= 0.8.1"
|
|
7
9
|
files:
|
|
8
10
|
guardrails.md: sha256:04207fd1738aeeeef3b7a8053fc57597565bddd7862f01acaba784419cbd909b
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skills
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.1
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Skills (skill_<name> memories, the steps of a task done together): /skill save, list, show and diff; older versions kept with a short diff line on every update; a nudge when a followed skill's step fails"
|
|
6
6
|
trust_level: reviewed
|
|
7
7
|
plugin:
|
|
8
8
|
file: plugin.rb
|
|
9
|
-
sha256: sha256:
|
|
10
|
-
requires_chi: ">= 0.
|
|
9
|
+
sha256: sha256:ecf76de5d0f4d07a0e87b96cdc13b0bbc22369f8eea843bfd22c6fade469dfca
|
|
10
|
+
requires_chi: ">= 0.9.0"
|
|
@@ -13,13 +13,20 @@
|
|
|
13
13
|
# shows a line: "skill release updated (+2 −1): …" or "skill release saved
|
|
14
14
|
# (project, 14 lines)".
|
|
15
15
|
#
|
|
16
|
+
# A skill read in this turn is also watched for changes made some other way
|
|
17
|
+
# (an execute running sed, a script): after each other tool call, and at the
|
|
18
|
+
# turn's end, its file is compared with the content last seen (at the read,
|
|
19
|
+
# or after the last write above); a change keeps that content as a version,
|
|
20
|
+
# shows the same line and counts as the skill updated.
|
|
21
|
+
#
|
|
16
22
|
# The nudge (nudge: true), for models that skip a failing step instead of
|
|
17
23
|
# fixing the skill: in a turn that read a skill (memory_read of a skill_*
|
|
18
24
|
# name, or a read of its file), the first failing tool call after it (an
|
|
19
25
|
# execute with "exit: N", N ≠ 0, or with no exit line an Error: line near
|
|
20
26
|
# the top; any tool's "[tool] Error: …") steers the model once to find out
|
|
21
|
-
# why and fix the skill
|
|
22
|
-
# the
|
|
27
|
+
# why and fix the skill, unless a skill read was changed already (by any
|
|
28
|
+
# tool, see above). At the turn's end, a failed step with no change to a
|
|
29
|
+
# skill read gets a notice line.
|
|
23
30
|
#
|
|
24
31
|
# Settings (config.yml, bundles: skills:):
|
|
25
32
|
# history_keep: 20 older versions kept per skill
|
|
@@ -31,8 +38,7 @@ class Plugin
|
|
|
31
38
|
WRITE_TOOLS = %w[memory_write write edit].freeze
|
|
32
39
|
NOTICE_WIDTH = 60 # the changed line in an update's notice
|
|
33
40
|
NUDGE = "A step of skill %s failed. Find out why before skipping it; if the skill is out of date, fix it now: " \
|
|
34
|
-
"
|
|
35
|
-
"line added."
|
|
41
|
+
"edit the step that changed in its file (or memory_write the whole skill) and add a Changelog line."
|
|
36
42
|
NOT_FAILURES = %w[memory_read memory_write].freeze
|
|
37
43
|
USAGE = "usage: /skill save [name] [--system] | list | show <name> | diff <name> [N]"
|
|
38
44
|
|
|
@@ -79,16 +85,38 @@ class Plugin
|
|
|
79
85
|
def after_tool_call(event, ctx)
|
|
80
86
|
stash = @stash
|
|
81
87
|
@stash = nil
|
|
88
|
+
stash = nil unless stash && stash[:tool] == event[:tool].to_s
|
|
89
|
+
changed_elsewhere(ctx) unless stash
|
|
82
90
|
step_failed(event) if @nudge && !@read.empty?
|
|
83
|
-
return unless stash
|
|
91
|
+
return unless stash
|
|
84
92
|
|
|
85
93
|
now = File.file?(stash[:path]) ? File.read(stash[:path]) : nil
|
|
86
94
|
return if now.nil? || now == stash[:old]
|
|
87
95
|
|
|
88
|
-
@written
|
|
96
|
+
@written |= [stash[:name]]
|
|
97
|
+
@seen[stash[:name]] = { path: stash[:path], scope: stash[:scope], content: now }
|
|
89
98
|
ctx.notify(change_notice(stash, now))
|
|
90
99
|
end
|
|
91
100
|
|
|
101
|
+
# A skill read this turn whose file differs from the content last seen was
|
|
102
|
+
# changed by something other than the write tools (an execute's sed, say):
|
|
103
|
+
# keep what was there as a version, show the change, count the skill as
|
|
104
|
+
# updated (any change counts, the user's own edit too). A file gone since
|
|
105
|
+
# is left alone.
|
|
106
|
+
def changed_elsewhere(ctx)
|
|
107
|
+
@seen.each do |name, seen|
|
|
108
|
+
now = File.file?(seen[:path]) ? File.read(seen[:path]) : nil
|
|
109
|
+
next if now.nil? || now == seen[:content]
|
|
110
|
+
|
|
111
|
+
keep_version(ctx, seen[:scope], name, seen[:content]) if seen[:content]
|
|
112
|
+
ctx.notify(change_notice({ name: name, scope: seen[:scope], old: seen[:content] }, now))
|
|
113
|
+
seen[:content] = now
|
|
114
|
+
@written |= [name]
|
|
115
|
+
end
|
|
116
|
+
rescue SystemCallError => e
|
|
117
|
+
ctx.log.warn(:watch_failed, error: e.class.name, msg: e.message)
|
|
118
|
+
end
|
|
119
|
+
|
|
92
120
|
def change_notice(stash, now)
|
|
93
121
|
name = stash[:name]
|
|
94
122
|
return "skill #{name} saved (#{stash[:scope]}, #{now.lines.size} lines)" unless stash[:old]
|
|
@@ -105,21 +133,42 @@ class Plugin
|
|
|
105
133
|
|
|
106
134
|
def reset_turn
|
|
107
135
|
@read = [] # skills read this turn, in order
|
|
136
|
+
@seen = {} # of those, name => {path:, scope:, content:} last seen
|
|
108
137
|
@written = [] # skills changed this turn
|
|
109
138
|
@failed = false # a step failed after a skill was read
|
|
110
139
|
@nudged = false
|
|
111
140
|
end
|
|
112
141
|
|
|
113
142
|
def note_read(tool, event)
|
|
114
|
-
|
|
143
|
+
found = case tool
|
|
115
144
|
when "memory_read"
|
|
145
|
+
scope = event.dig(:call, :scope).to_s.strip
|
|
146
|
+
scope = nil unless memory_dirs.key?(scope)
|
|
147
|
+
# A skill not written yet is watched where memory_write would
|
|
148
|
+
# put it by default: the read scope, else project.
|
|
116
149
|
event.dig(:call, :content).to_s.split(",").map(&:strip).select { |entry| entry.start_with?("skill_") }
|
|
117
|
-
.filter_map
|
|
150
|
+
.filter_map do |entry|
|
|
151
|
+
name = skill_name(entry) or next
|
|
152
|
+
unwritten = File.join(memory_dirs[scope || "project"], "skill_#{name}.md")
|
|
153
|
+
[name, skill_path(name, scope: scope) || unwritten]
|
|
154
|
+
end
|
|
118
155
|
when "read"
|
|
119
|
-
Array(event.dig(:targets, :paths)).filter_map { |path| skill_at(path)
|
|
156
|
+
Array(event.dig(:targets, :paths)).filter_map { |path| (at = skill_at(path)) && [at.last, path] }
|
|
120
157
|
else []
|
|
121
158
|
end
|
|
122
|
-
|
|
159
|
+
found.each { |name, path| watch(name, path) }
|
|
160
|
+
@read |= found.map(&:first)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Remember a read skill's content (nil: not written yet) the first time in
|
|
164
|
+
# a turn: a later read mustn't hide a change made in between.
|
|
165
|
+
def watch(name, path)
|
|
166
|
+
return if @seen.key?(name) || path.nil?
|
|
167
|
+
|
|
168
|
+
content = File.file?(path) ? File.read(path) : nil
|
|
169
|
+
@seen[name] = { path: File.expand_path(path), scope: skill_at(path)&.first, content: content }
|
|
170
|
+
rescue SystemCallError
|
|
171
|
+
nil
|
|
123
172
|
end
|
|
124
173
|
|
|
125
174
|
def step_failed(event)
|
|
@@ -127,7 +176,7 @@ class Plugin
|
|
|
127
176
|
return if NOT_FAILURES.include?(tool) || !failure?(tool, event[:output].to_s)
|
|
128
177
|
|
|
129
178
|
@failed = true
|
|
130
|
-
return if @nudged
|
|
179
|
+
return if @nudged || @read.any? { |name| @written.include?(name) }
|
|
131
180
|
|
|
132
181
|
@nudged = !!event[:steer]&.call(format(NUDGE, @read.join(", ")))
|
|
133
182
|
end
|
|
@@ -146,6 +195,7 @@ class Plugin
|
|
|
146
195
|
end
|
|
147
196
|
|
|
148
197
|
def after_turn(ctx)
|
|
198
|
+
changed_elsewhere(ctx)
|
|
149
199
|
return unless @nudge && @failed
|
|
150
200
|
|
|
151
201
|
missed = @read - @written
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
- **Skills**: a skill is a memory named `skill_<name>` holding the steps of a repeatable task we did together (sections Steps, Gotchas, Changelog; `memory_write description:` says when to use it).
|
|
9
9
|
When the user asks to keep how we did something ("let's memorize this", "save this as a skill", `/skill save`), write it right away with `memory_write` (project scope; system only when the user asks or it isn't about this project), then show it briefly.
|
|
10
10
|
Before a task that a `skill_*` in the memory index matches, read it and follow it.
|
|
11
|
-
When a step turned out different (a renamed command, an extra step, a gotcha), update the skill in the same turn:
|
|
11
|
+
When a step turned out different (a renamed command, an extra step, a gotcha), update the skill in the same turn: `edit` those steps in its file (memory_write only to rewrite it all), keep the rest as it was, add a dated Changelog line.
|
|
12
12
|
When a skill's step fails or its file/command is missing, find out why (look around, read nearby READMEs) before skipping it; a step that says stop means stop and ask.
|
|
13
13
|
While following a skill, when anything looks unexpected (a check fails, output differs from what a step says, a warning the skill doesn't mention): stop, don't improvise a fix, tell the user what you saw and ask how to go on. Read each command's whole output, including warnings, before the next step.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: samagotchi-system
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.9.0
|
|
4
4
|
scope: system
|
|
5
5
|
description: Default system memories — identity, self map, config modification protocol, memory guide and the delegated-session rules
|
|
6
6
|
files:
|
|
7
|
-
identity.md: sha256:
|
|
7
|
+
identity.md: sha256:29c2f7df9f6c5acbc5ec17000a4490741e55e426af15ca0d81c8706edd9bd027
|
|
8
8
|
self_map.md: sha256:93c788fadaf98adeb8946b4a357d2372bd6ebb439042d7cef8dda8fb44187315
|
|
9
9
|
config_modification_protocol.md: sha256:24e95727488daa80ac3008e020d8be098c7a1882153b7a61aba35450f72b86b7
|
|
10
|
-
memory_guide.md: sha256:
|
|
10
|
+
memory_guide.md: sha256:81b911b9a015f0924c48162b3dc4b6376309e743765074419a99fab46e480858
|
|
11
11
|
delegated.md: sha256:0a71d5dfa7127df2d51cb5f0e7214240ed88777e36559430a8724c796d7ae561
|
|
@@ -6,7 +6,7 @@ This memory teaches you (the agent) how to use Samagotchi memories — persisten
|
|
|
6
6
|
|
|
7
7
|
- Plain Markdown files (`*.md`) stored outside the repo so they persist.
|
|
8
8
|
- Loaded at startup into your system prompt as **Project memories** and **System memories** indexes (blank-name `memory_read`).
|
|
9
|
-
- You manage them via tools, not shell file ops.
|
|
9
|
+
- You manage them via tools (`memory_write`, or `edit` on a memory's file for a small change), not shell file ops. `index.md` is auto-maintained on every write — do not edit it manually.
|
|
10
10
|
|
|
11
11
|
## Scopes — where files live
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ This memory teaches you (the agent) how to use Samagotchi memories — persisten
|
|
|
16
16
|
| `project` | `<system dir>/projects/<basename>_<hash>/` (`MemoryPaths.project_dir`: `basename(root)` + 8-char `MD5(root)`, root = `MemoryPaths.project_root`) | Repo-specific conventions, workflow, stack decisions |
|
|
17
17
|
|
|
18
18
|
- The project root is the git repository: its common git dir, which every linked worktree shares. All worktrees and subdirectories of one repository share one project folder; the system prompt shows the resolved root and folder. Outside a repository the root is the working directory. A separate clone is a different project.
|
|
19
|
-
- `index.md` lives in each scope dir and contains auto-managed lines like `- **name** · scope · date · bytes — description`. Your free-form sections in `index.md` are preserved but managed lines are owned by `memory_write
|
|
19
|
+
- `index.md` lives in each scope dir and contains auto-managed lines like `- **name** · scope · date · bytes — description`. Your free-form sections in `index.md` are preserved but managed lines are owned by `memory_write` (and refreshed when `write`/`edit` change a memory's file).
|
|
20
20
|
|
|
21
21
|
## Tools you have
|
|
22
22
|
|
|
@@ -34,7 +34,8 @@ This memory teaches you (the agent) how to use Samagotchi memories — persisten
|
|
|
34
34
|
- `name` is the entry name **without** `.md` (the tool adds it). Use `name`, **not** `path` — `path` belongs to the file tools and is ignored here. `name: "index"` is a verbatim write to `index.md` (no auto-index update) — rarely needed.
|
|
35
35
|
- `scope` is **required** — never omit. Prefer `project` for repo conventions, `system` for user preferences.
|
|
36
36
|
- `description` is appended to the managed `index.md` line (`— description`). Replaces previous description if given; otherwise preserves existing one.
|
|
37
|
-
-
|
|
37
|
+
- For a small change to an existing memory (a step, a line), `edit` its file instead of rewriting it all: the path is in `memory_write`'s result and the scope dirs are in the prompt. Its index line is refreshed either way.
|
|
38
|
+
- On success returns `Memory 'name' saved to <scope> scope (N bytes). File written: <path> Index line refreshed automatically.` — confirm `bytes` and `scope`; the index needs nothing from you.
|
|
38
39
|
|
|
39
40
|
**When to write:**
|
|
40
41
|
- After learning a durable preference (e.g. commit style, test command, coding guideline) that the user confirmed or you observed repeatedly — ask before overwriting existing entries where appropriate.
|
|
@@ -67,7 +68,7 @@ Shape (plain Markdown, no frontmatter):
|
|
|
67
68
|
- **Scope**: `project` by default; `system` when the user asks, or when the skill is clearly not about this project.
|
|
68
69
|
- **Saving**: on a request to keep how something was done ("let's memorize this", "save this as a skill", `/skill save`), write it at once, then show it briefly. On an ambiguous one ("I like how we did that") you may ask whether to save it.
|
|
69
70
|
- **Following**: before a task a `skill_*` index line matches, `memory_read` it and follow its steps. A step that fails or names a missing file or command: find out why (look around, read nearby READMEs) before skipping it; a step that says stop means stop and ask. When anything looks unexpected (a check fails, output differs from what a step says, a warning the skill doesn't mention): stop, don't improvise a fix, tell the user what you saw and ask. Read each command's whole output, including warnings, before the next step.
|
|
70
|
-
- **Updating**: when a step turned out different,
|
|
71
|
+
- **Updating**: when a step turned out different, update the skill in the same turn: `edit` those steps in its file (`<scope dir>/skill_<name>.md`), or rewrite it with `memory_write`; keep the rest as it was, add a dated Changelog line. No confirmation needed.
|
|
71
72
|
- **The `skills` bundle** (`chi bundle install skills`, optional) adds `/skill save [name] [--system]`, `/skill list`, `/skill show <name>`, `/skill diff <name> [N]`; it keeps older versions and shows a short diff line after each update.
|
|
72
73
|
|
|
73
74
|
## Memory Bundles — shareable packs
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -103,6 +103,9 @@ module Samagotchi
|
|
|
103
103
|
Entry.new(key: "image.max_per_request", yaml_path: %w[image max_per_request], type: :integer, default: 20, expose: %i[env config]),
|
|
104
104
|
|
|
105
105
|
Entry.new(key: "guardrails.enabled", yaml_path: %w[guardrails enabled], type: :bool, default: true, expose: %i[env config]),
|
|
106
|
+
# Which models get the rules with `models: small` (Guardrails::ModelSize): auto (32B or less by the name's size, an MoE's
|
|
107
|
+
# active size counting), globs on the bare model name or its key, "|"-separated (a YAML list works too), or "" ([]) for none.
|
|
108
|
+
Entry.new(key: "guardrails.small_models", yaml_path: %w[guardrails small_models], type: :string, default: "auto", expose: %i[env config]),
|
|
106
109
|
|
|
107
110
|
Entry.new(key: "log.file", yaml_path: %w[log file], type: :string, default: nil, expose: %i[env config cli]),
|
|
108
111
|
Entry.new(key: "log.disable", yaml_path: %w[log disable], type: :bool, default: false, expose: %i[env config cli]),
|
data/lib/samagotchi/engine.rb
CHANGED
|
@@ -126,6 +126,7 @@ module Samagotchi
|
|
|
126
126
|
tools: -> { @tools },
|
|
127
127
|
session: -> { @session },
|
|
128
128
|
model_key: -> { @model_key },
|
|
129
|
+
model_name: -> { guardrail_model_name },
|
|
129
130
|
cancelled: -> { !!active_cancel_controller&.cancelled? },
|
|
130
131
|
ask: ->(fields) { open_question(fields) }
|
|
131
132
|
)
|
|
@@ -428,6 +429,11 @@ module Samagotchi
|
|
|
428
429
|
attr_reader :metrics
|
|
429
430
|
attr_reader :default_model_name, :effective_model_name
|
|
430
431
|
|
|
432
|
+
# The effective model's key (ModelOverlay.key_for its bare name): memory
|
|
433
|
+
# overlays, guardrail rules' models:.
|
|
434
|
+
# @return [String, nil]
|
|
435
|
+
attr_reader :model_key
|
|
436
|
+
|
|
431
437
|
# The prompt profile for the effective model (see #profile_resolution).
|
|
432
438
|
# @return [ModelProfile]
|
|
433
439
|
def profile = profile_resolution.profile
|
|
@@ -452,6 +458,13 @@ module Samagotchi
|
|
|
452
458
|
@host_registry.bare_name(full_ref)
|
|
453
459
|
end
|
|
454
460
|
|
|
461
|
+
# The effective model's bare name for the guardrails' models: rules;
|
|
462
|
+
# nil without a model name.
|
|
463
|
+
def guardrail_model_name
|
|
464
|
+
name = @effective_model_name && bare_model_name(@effective_model_name)
|
|
465
|
+
name.to_s.strip.empty? ? nil : name
|
|
466
|
+
end
|
|
467
|
+
|
|
455
468
|
# Point the kernel (and a chat backend) at the effective model's host
|
|
456
469
|
# (after /model, --model, resume).
|
|
457
470
|
def sync_kernel_client!
|
|
@@ -29,14 +29,16 @@ module Samagotchi
|
|
|
29
29
|
# @param tools [#call] → Tools::Registry
|
|
30
30
|
# @param session [#call] → Session, nil
|
|
31
31
|
# @param model_key [#call] → String, the effective model's overlay key
|
|
32
|
+
# @param model_name [#call] → String, nil: the effective model's bare name
|
|
32
33
|
# @param cancelled [#call] → Boolean, whether the running turn is cancelled
|
|
33
34
|
# @param ask [#call] (fields) → the answer; the Engine's question flow
|
|
34
|
-
def initialize(scratch:, hooks:, tools:, session:, model_key:, cancelled:, ask:)
|
|
35
|
+
def initialize(scratch:, hooks:, tools:, session:, model_key:, cancelled:, ask:, model_name: -> {})
|
|
35
36
|
@scratch = scratch
|
|
36
37
|
@hooks_lookup = hooks
|
|
37
38
|
@tools_lookup = tools
|
|
38
39
|
@session_lookup = session
|
|
39
40
|
@model_key_lookup = model_key
|
|
41
|
+
@model_name_lookup = model_name
|
|
40
42
|
@cancelled_lookup = cancelled
|
|
41
43
|
@ask = ask
|
|
42
44
|
@failures = Guardrails::LoadFailures.new
|
|
@@ -78,6 +80,7 @@ module Samagotchi
|
|
|
78
80
|
-> { @hooks_lookup.call },
|
|
79
81
|
context_lookup: -> { context },
|
|
80
82
|
model_key_lookup: -> { @model_key_lookup.call },
|
|
83
|
+
model_name_lookup: -> { @model_name_lookup.call },
|
|
81
84
|
approver: ->(verdict) { request_approval(verdict) },
|
|
82
85
|
approvals_lookup: -> { @approvals },
|
|
83
86
|
checks_lookup: -> { checks },
|
|
@@ -21,7 +21,8 @@ module Samagotchi
|
|
|
21
21
|
# the kernel is built.
|
|
22
22
|
# @param context_lookup [#call] returns the Context for this call
|
|
23
23
|
# (the Engine's: session, interface, origin, per-turn git cache)
|
|
24
|
-
# @param model_key_lookup [#call] the model key (memory overlays)
|
|
24
|
+
# @param model_key_lookup [#call] the model key (memory overlays, rules' models:)
|
|
25
|
+
# @param model_name_lookup [#call] the bare model name (rules' models:)
|
|
25
26
|
# @param approver [#call, nil] settles an ask (Engine#request_approval);
|
|
26
27
|
# without one an ask is denied
|
|
27
28
|
# @param approvals_lookup [#call] returns the Approvals store (or nil)
|
|
@@ -31,12 +32,13 @@ module Samagotchi
|
|
|
31
32
|
# hook's stop_turn): the rest of a batch is denied without a vote
|
|
32
33
|
# @param tools_lookup [#call] returns the Tools::Registry (or nil): a
|
|
33
34
|
# plugin tool's targets: say what its call acts on
|
|
34
|
-
def initialize(hooks_lookup, context_lookup: -> { Context.new }, model_key_lookup: -> {},
|
|
35
|
-
approvals_lookup: -> {}, checks_lookup: -> { [] }, cancelled_lookup: -> { false },
|
|
35
|
+
def initialize(hooks_lookup, context_lookup: -> { Context.new }, model_key_lookup: -> {}, model_name_lookup: -> {},
|
|
36
|
+
approver: nil, approvals_lookup: -> {}, checks_lookup: -> { [] }, cancelled_lookup: -> { false },
|
|
36
37
|
tools_lookup: -> {})
|
|
37
38
|
@hooks_lookup = hooks_lookup
|
|
38
39
|
@context_lookup = context_lookup
|
|
39
40
|
@model_key_lookup = model_key_lookup
|
|
41
|
+
@model_name_lookup = model_name_lookup
|
|
40
42
|
@approver = approver
|
|
41
43
|
@approvals_lookup = approvals_lookup
|
|
42
44
|
@checks_lookup = checks_lookup
|
|
@@ -113,7 +115,8 @@ module Samagotchi
|
|
|
113
115
|
end
|
|
114
116
|
|
|
115
117
|
def targets_for(call, context)
|
|
116
|
-
Targets.for(call, context, model_key: @model_key_lookup.call,
|
|
118
|
+
Targets.for(call, context, model_key: @model_key_lookup.call, model_name: @model_name_lookup.call,
|
|
119
|
+
registry: @tools_lookup.call)
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
# Registry#fire_each rescues a raising hook itself.
|