samagotchi 0.35.0 → 0.37.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 +57 -1
- data/README.md +2 -2
- data/bin/chi +1 -1
- data/docs/broadcast.md +91 -12
- data/docs/configuration.md +8 -1
- data/docs/context.md +37 -0
- data/docs/guardrails.md +26 -3
- data/docs/hooks.md +1 -1
- data/docs/memory.md +3 -0
- data/docs/plugins.md +51 -4
- data/lib/samagotchi/broadcast/triage.rb +267 -0
- data/lib/samagotchi/broadcast/triage_log.rb +165 -0
- data/lib/samagotchi/broadcast/triage_model.rb +56 -0
- data/lib/samagotchi/broadcast_command.rb +273 -69
- data/lib/samagotchi/bundles/coordinator/manifest.yml +7 -6
- data/lib/samagotchi/bundles/coordinator/plugin.rb +72 -4
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +59 -11
- data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
- data/lib/samagotchi/bundles/github-pr/plugin.rb +444 -3
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +11 -0
- data/lib/samagotchi/bundles/guardrails/manifest.yml +5 -4
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +72 -27
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/memory_guide.md +4 -2
- data/lib/samagotchi/cli/command.rb +27 -3
- data/lib/samagotchi/config.rb +9 -0
- data/lib/samagotchi/context_command.rb +0 -17
- data/lib/samagotchi/engine.rb +4 -6
- data/lib/samagotchi/guardrail_wiring.rb +7 -2
- data/lib/samagotchi/guardrails/approval.rb +5 -2
- data/lib/samagotchi/guardrails/approvals.rb +9 -5
- data/lib/samagotchi/guardrails/parent_approvals.rb +6 -0
- data/lib/samagotchi/guardrails/rules.rb +19 -6
- data/lib/samagotchi/guardrails/targets.rb +83 -11
- data/lib/samagotchi/idle_client.rb +48 -28
- data/lib/samagotchi/idle_recap.rb +6 -5
- data/lib/samagotchi/idle_target.rb +95 -0
- data/lib/samagotchi/llm/openai_chat.rb +29 -3
- data/lib/samagotchi/log_line.rb +1 -1
- data/lib/samagotchi/memory_bundle/index_updater.rb +6 -3
- data/lib/samagotchi/note_command.rb +0 -21
- data/lib/samagotchi/recap_setup.rb +38 -52
- data/lib/samagotchi/send_command.rb +0 -22
- data/lib/samagotchi/session_commands.rb +1 -0
- data/lib/samagotchi/tool_activity.rb +14 -3
- data/lib/samagotchi/tool_declarations.rb +8 -4
- data/lib/samagotchi/tools/builtins.rb +1 -1
- data/lib/samagotchi/tools/memory.rb +103 -2
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/question_card.js +4 -3
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 15887ec5fe31be3f32d3e1bfa15b9cf5ca73b03e2101f6416eb2cfad4c2f477a
|
|
4
|
+
data.tar.gz: 2226f5343806c3171babc66d075fc57498af03d313f180458e4f6ee1801c7384
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8986ea963bda07138d6874b9024a031c5c6c000d03bad8281c9c24f57a079234fab5de68efe08305dd38ea4f138bf7ef7b02637e310e69f061182bb17d01d8a3
|
|
7
|
+
data.tar.gz: d9e07e7a5bacd114b8916a538fa2265bbd5b43ed78373fc306766455788b75ef5c1b0eb21d054288887483c93385063293150339abf4e3d02284bc1320cd9509
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,60 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.37.0] - 2026-10-07
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- For plugin authors: a tool's `targets:` may return `acts_as:` (the tool a call stands for, like an MCP tool behind
|
|
16
|
+
a dispatcher), `args:` (the arguments it acts with) and `label:` (the name the approval question shows). A guardrail
|
|
17
|
+
rule's `tool:` then matches either name, and "allow this call" is keyed by both names and those arguments. It can't
|
|
18
|
+
name one of chi's own tools or another bundle's (dropped and logged). See [docs/plugins.md](docs/plugins.md#guardrails).
|
|
19
|
+
- `chi broadcast` asks a small triage model about the sessions no tag matched, instead of skipping them: it reads the
|
|
20
|
+
note and each session's scope card and answers yes or no ("model: yes (p 0.91)" when the host gives logprobs,
|
|
21
|
+
graded by `broadcast.threshold`). The model is `broadcast.triage_model` (with `triage_host_ref` or
|
|
22
|
+
`triage_base_url`), else the recap's, else `default.model`; a ~9B instruct model or larger works well. Up to
|
|
23
|
+
`broadcast.triage_parallel` (4) requests run at once, all within `broadcast.triage_deadline` (20 s): a session not
|
|
24
|
+
judged by then, or whose request failed, gets the note anyway, and the summary line counts it ("1 unchecked: triage
|
|
25
|
+
deadline"). A first line naming one project ("shopfront/checkout") keeps the note to that project's sessions.
|
|
26
|
+
`--dry-run` runs triage too. See [docs/broadcast.md](docs/broadcast.md#triage).
|
|
27
|
+
- `chi broadcast` prints the broadcast's id and logs each recipient's verdict (`~/.local/state/samagotchi/broadcast/log.jsonl`,
|
|
28
|
+
rotated at 2 MiB). `chi broadcast log [--last N] [--format json]` shows the last broadcasts and who got them, and
|
|
29
|
+
`chi broadcast deliver BROADCAST_ID ID...` gives one to sessions it skipped, after all. See
|
|
30
|
+
[docs/broadcast.md](docs/broadcast.md#the-triage-log).
|
|
31
|
+
|
|
32
|
+
Update with `chi update` (mcp 0.5.1).
|
|
33
|
+
|
|
34
|
+
## [0.36.0] - 2026-10-07
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `memory_write` with `name`, `scope` and `description` but no `content` changes only that memory's index line and
|
|
39
|
+
leaves its file as it is (see [docs/memory.md](docs/memory.md)).
|
|
40
|
+
- `memory_write` with `name`, `scope` and `remove: true` removes a memory: its file (and model overlays) move to the
|
|
41
|
+
bundle trash, which `chi bundle trash` lists and empties, and its index line goes; a line left after the file was
|
|
42
|
+
deleted by hand goes too. A memory a bundle installed is refused (`chi bundle uninstall` owns it).
|
|
43
|
+
- The coordinator bundle (0.2.0) keeps a project memory `handoff_<epic>` with what git and the session list can't
|
|
44
|
+
rebuild (the split, your decisions, its verdicts, follow-ups), its status in the index description, and resumes
|
|
45
|
+
from it in a new session after checking git and the session list; `/coordinate resume` picks up an open handoff.
|
|
46
|
+
When the work is done it marks the handoff DONE and asks whether to remove it. See
|
|
47
|
+
[docs/plugins.md](docs/plugins.md#the-coordinator-bundle).
|
|
48
|
+
- The guardrails bundle (0.8.0) asks before every memory removal (`memory-remove`, once at a time). Guardrail rules
|
|
49
|
+
take `memory: remove`.
|
|
50
|
+
- In the web, a file reference in an answer (`lib/foo.rb:28`, `lib/foo.rb:28-34`, in inline code too) links to that
|
|
51
|
+
line of the pull request the session reviews: the PR attached to it or named in your messages (a delegate child's
|
|
52
|
+
task too). Inside a changed hunk it opens the PR's Files changed view at the line, highlighted; elsewhere the file
|
|
53
|
+
at the PR's head. Display only; `bundles: github-pr: line_links: false` turns it off (github-pr 0.2.0; run
|
|
54
|
+
`chi update`). See [docs/context.md](docs/context.md#line-links).
|
|
55
|
+
- `/mcp` shows roughly how many tokens each MCP server's tool definitions take in every request, and the total
|
|
56
|
+
(mcp 0.5.0; run `chi update`). See [docs/plugins.md](docs/plugins.md#the-mcp-bundle).
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- `memory_write` with a blank `description` keeps the stored one instead of wiping it; a multi-line description is
|
|
61
|
+
written on one line (it left orphan lines in `index.md`), and one longer than 200 characters is refused.
|
|
62
|
+
|
|
63
|
+
Update with `chi update` (coordinator 0.2.0 and guardrails 0.8.0, which need chi 0.36.0; github-pr 0.2.0, mcp 0.5.0).
|
|
64
|
+
|
|
11
65
|
## [0.35.0] - 2026-10-06
|
|
12
66
|
|
|
13
67
|
### Added
|
|
@@ -1751,7 +1805,9 @@ and long-lived sessions.
|
|
|
1751
1805
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
1752
1806
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
1753
1807
|
|
|
1754
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
1808
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.37.0...HEAD
|
|
1809
|
+
[0.37.0]: https://github.com/dm1try/samagotchi/compare/v0.36.0...v0.37.0
|
|
1810
|
+
[0.36.0]: https://github.com/dm1try/samagotchi/compare/v0.35.0...v0.36.0
|
|
1755
1811
|
[0.35.0]: https://github.com/dm1try/samagotchi/compare/v0.34.0...v0.35.0
|
|
1756
1812
|
[0.34.0]: https://github.com/dm1try/samagotchi/compare/v0.33.0...v0.34.0
|
|
1757
1813
|
[0.33.0]: https://github.com/dm1try/samagotchi/compare/v0.32.0...v0.33.0
|
data/README.md
CHANGED
|
@@ -120,7 +120,7 @@ chi web --open # web UI: this project's sessions (--s
|
|
|
120
120
|
chi web --web-host lan # the web UI on your phone too: scan the QR code it prints
|
|
121
121
|
chi sessions list # this project's saved sessions (--scope=all: every one)
|
|
122
122
|
pbpaste | chi note --source slack <id> # background context for a session (no turn)
|
|
123
|
-
chi broadcast -m "payments API is down" # the same for every session it concerns (a shared
|
|
123
|
+
chi broadcast -m "payments API is down" # the same for every session it concerns (a shared tag, or a triage model says so)
|
|
124
124
|
chi context add <PR URL> <id> # attached context: chi keeps it fresh, the agent hears when it changes
|
|
125
125
|
pbpaste | chi send -m "same bug?" <id> # a message to a session, the clipboard quoted above it
|
|
126
126
|
chi send --new --wait -m "review feat/x" # a new session you can watch in the web; prints the answer
|
|
@@ -203,7 +203,7 @@ once, for the session, for the repo, or for the whole rule in the repo.
|
|
|
203
203
|
- [Memory](docs/memory.md): scopes and model-specific overlays
|
|
204
204
|
- [Sessions](docs/sessions.md): storage, retention, `chi sessions`
|
|
205
205
|
- [Attached context](docs/context.md): `chi context`, sources, scopes, waking, the github-pr bundle, safety
|
|
206
|
-
- [Broadcast](docs/broadcast.md): `chi broadcast`, who a note reaches, tags, what a session gets
|
|
206
|
+
- [Broadcast](docs/broadcast.md): `chi broadcast`, who a note reaches, tags, triage, what a session gets, the triage log
|
|
207
207
|
- [chi as a sub-agent](docs/sub-agent.md): `chi send --wait --format json`, `chi answer`, instructions for a parent agent
|
|
208
208
|
- [Desktop helper](docs/desktop.md): `chi desktop`, the macOS "Send to chi" Service and hotkey
|
|
209
209
|
- [Guardrails](docs/guardrails.md): allow / ask / deny for tool calls, rules, approvals
|
data/bin/chi
CHANGED
|
@@ -97,7 +97,7 @@ parser = OptionParser.new do |opts|
|
|
|
97
97
|
chi web [--port PORT] [--open] [--scope=all] the web UI
|
|
98
98
|
chi sessions <list|stop|archive|unarchive|delete|prune|clean>
|
|
99
99
|
chi note [--source NAME] [-m TEXT] ID...|--all background context for sessions
|
|
100
|
-
chi broadcast [-m TEXT] [--all] [--dry-run] a note for every session it may concern
|
|
100
|
+
chi broadcast [-m TEXT] [--all] [--dry-run] | log | deliver a note for every session it may concern
|
|
101
101
|
chi context <add|push|ls|show|refresh|rm|mute|unmute> live external text attached to sessions
|
|
102
102
|
chi send [-m TEXT] ID...|--new a message into sessions, as if typed there
|
|
103
103
|
chi answer ID --question QID --option N answer a question a session waits on
|
data/docs/broadcast.md
CHANGED
|
@@ -12,6 +12,8 @@ chi broadcast -m "payments API returns 500 since 14:00 (PAY-123)"
|
|
|
12
12
|
pbpaste | chi broadcast # the note from stdin
|
|
13
13
|
chi broadcast --dry-run -m "…" # who would get it and why; nothing is delivered
|
|
14
14
|
chi broadcast --all -m "deploy freeze until 18:00" # every recipient, tag or not
|
|
15
|
+
chi broadcast log # the last broadcasts and who got them
|
|
16
|
+
chi broadcast deliver b-7f3a1c9e c0ffee12 # one it skipped, after all
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
To tell one session or a few you picked, use `chi note ID...` instead.
|
|
@@ -47,30 +49,69 @@ so `acme/shop#42` doesn't match `acme/other#42`; `PR #42` with no repo matches
|
|
|
47
49
|
pull request 42 of any repo. Words the default ticket pattern would take that
|
|
48
50
|
are no ticket (`UTF-8`, `SHA-256`, `ISO-8601`, `GPT-4`, …) are left out.
|
|
49
51
|
|
|
50
|
-
A session that shares no tag
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
A session that shares no tag goes to triage.
|
|
53
|
+
|
|
54
|
+
## Triage
|
|
55
|
+
|
|
56
|
+
A small model reads the note and each other recipient's scope card (below)
|
|
57
|
+
and answers yes or no: would the agent working there want to know? A yes
|
|
58
|
+
delivers the note, a no skips the session ("model: no"). `--all` skips tags
|
|
59
|
+
and triage and reaches every recipient.
|
|
60
|
+
|
|
61
|
+
- **The model:** `broadcast.triage_model` (with `broadcast.triage_host_ref` or
|
|
62
|
+
`broadcast.triage_base_url`, as the `recap.*` settings take them); with none
|
|
63
|
+
of them set, the [recap's](sessions.md) model, else `default.model`. A ~9B
|
|
64
|
+
dense instruct model or larger works well (qwen3.5-9b missed no session in
|
|
65
|
+
our tests and sent about one extra note per broadcast of 20 sessions);
|
|
66
|
+
4B-class models say yes to nearly everything. A local model keeps the
|
|
67
|
+
cards (your prompts, recaps) on your machine; a remote triage host is your
|
|
68
|
+
explicit choice. Thinking is off, and each answer is a word.
|
|
69
|
+
- **A score, when the host gives one:** with logprobs (OpenAI, OpenRouter
|
|
70
|
+
for some models), the verdict is P(yes) of the answer's first token, shown as
|
|
71
|
+
"model: yes (p 0.83)", and a session needs `broadcast.threshold` (0.5). A
|
|
72
|
+
higher threshold drops real "this blocks you" notes first. A host that
|
|
73
|
+
refuses logprobs (llama.cpp's, some gateways) is asked again without them:
|
|
74
|
+
the plain answer counts as 1 or 0.
|
|
75
|
+
- **A scope line:** when the note's first line names exactly one of the
|
|
76
|
+
recipients' projects as a word (`shopfront/checkout`, `infra / ci`), the
|
|
77
|
+
other projects' sessions, and sessions outside any project, are skipped:
|
|
78
|
+
"scope line names shopfront". Anything fuzzier ("for payments folks") is the
|
|
79
|
+
model's job; it sees the line too. A one-line note counts as its own first
|
|
80
|
+
line: with a project named after a common word (`web`, `docs`), "the web
|
|
81
|
+
composer docs moved" names it and keeps the note there.
|
|
82
|
+
- **Fail open:** at most `broadcast.triage_parallel` (4) requests run at a
|
|
83
|
+
time, all within `broadcast.triage_deadline` (20 s; a local server's first
|
|
84
|
+
request after a pause can take 10 s). A session not judged by then, one
|
|
85
|
+
whose request failed, or one the model answered with neither yes nor no
|
|
86
|
+
gets the note anyway, "unchecked: triage deadline", and the summary line
|
|
87
|
+
counts them: a missed session is worse than an extra note. Without a
|
|
88
|
+
triage model to ask (settings that don't resolve) every such session gets
|
|
89
|
+
it unchecked, and chi says why.
|
|
53
90
|
|
|
54
91
|
## What it prints
|
|
55
92
|
|
|
56
93
|
One line per recipient, the ones that got it first, then a summary:
|
|
57
94
|
|
|
58
95
|
```
|
|
59
|
-
broadcast "payments API returns 500 since 14:00 (PAY-123)"
|
|
96
|
+
broadcast b-7f3a1c9e "payments API returns 500 since 14:00 (PAY-123)"
|
|
60
97
|
3f2a1c9e delivered ticket PAY-123 matches (branch)
|
|
61
98
|
91ab02c4 delivered link notion.so/team/checkout-v2 matches (messages); waits for its next start
|
|
62
|
-
|
|
99
|
+
5e1f0a77 delivered model: yes (p 0.91)
|
|
100
|
+
77aa0b3c delivered unchecked: triage deadline
|
|
101
|
+
c0ffee12 skipped model: no
|
|
63
102
|
d00dad00 skipped open in a chi REPL
|
|
64
|
-
delivered
|
|
103
|
+
delivered 4 · skipped 2 · 1 unchecked: triage deadline
|
|
65
104
|
```
|
|
66
105
|
|
|
67
|
-
|
|
106
|
+
The first line names the broadcast (`b-7f3a1c9e`): `chi broadcast log` and
|
|
107
|
+
`chi broadcast deliver` take it. A session a worker runs adds the note within a few seconds (after a running
|
|
68
108
|
turn); one with no worker gets it at its next start ("waits for its next
|
|
69
109
|
start"). Nothing starts a turn anywhere.
|
|
70
110
|
|
|
71
111
|
`--dry-run` prints the note's tags, each recipient's verdict ("would get it" or
|
|
72
|
-
"skipped") and its scope card: what chi knows about it from local
|
|
73
|
-
what
|
|
112
|
+
"skipped"; triage runs) and its scope card: what chi knows about it from local
|
|
113
|
+
state, and what the triage model reads. A line before them names the triage model
|
|
114
|
+
and the setting it came from.
|
|
74
115
|
|
|
75
116
|
```
|
|
76
117
|
3f2a1c9e would get it ticket PAY-123 matches (branch)
|
|
@@ -83,7 +124,8 @@ what a triage model will read.
|
|
|
83
124
|
```
|
|
84
125
|
|
|
85
126
|
Exit status: 0 when it ran (skipped sessions included), 1 when it was refused,
|
|
86
|
-
no session is active or a delivery failed, 2 for a usage error.
|
|
127
|
+
no session is active or a delivery failed, 2 for a usage error. `chi broadcast
|
|
128
|
+
deliver` exits 0 when every session named has the note now, 1 otherwise.
|
|
87
129
|
|
|
88
130
|
## What a session gets
|
|
89
131
|
|
|
@@ -96,7 +138,8 @@ payments API returns 500 since 14:00 (PAY-123)
|
|
|
96
138
|
[END NOTE]
|
|
97
139
|
```
|
|
98
140
|
|
|
99
|
-
With `--all` the line says "with every active session"
|
|
141
|
+
With `--all` the line says "with every active session"; one that triage
|
|
142
|
+
delivered says "with the sessions it may concern." and no reason. The date is there
|
|
100
143
|
because a session with no worker may read it days later. The system prompt
|
|
101
144
|
tells the model what a note from `broadcast` is: if it affects its current
|
|
102
145
|
work, it says so briefly in its next answer, otherwise it ignores it, and it
|
|
@@ -107,9 +150,37 @@ plain note can't pass for a broadcast.
|
|
|
107
150
|
The note and its line together are capped at 16 KiB like any note; the text
|
|
108
151
|
may take up to 15872 bytes.
|
|
109
152
|
|
|
153
|
+
## The triage log
|
|
154
|
+
|
|
155
|
+
Each broadcast (not a `--dry-run`) leaves one line in
|
|
156
|
+
`$XDG_STATE_HOME/samagotchi/broadcast/log.jsonl` (`~/.local/state/…`): its
|
|
157
|
+
id, time, your text, the note's tags and each recipient's verdict (result,
|
|
158
|
+
reason, P(yes), who decided, the session's tags). Scope cards are not kept:
|
|
159
|
+
they quote your prompts. Past 2 MiB the file becomes `log.jsonl.1`, one old
|
|
160
|
+
file kept. Sessions don't read it: a broadcast stays fire-and-forget.
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
chi broadcast log # the last 5 broadcasts
|
|
164
|
+
chi broadcast log --last 20 --format json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
b-7f3a1c9e 2026-10-06 14:20 "payments API returns 500 since 14:00 (PAY-123)"
|
|
169
|
+
3f2a1c9e delivered ticket PAY-123 matches (branch)
|
|
170
|
+
c0ffee12 skipped model: no
|
|
171
|
+
delivered 1 · skipped 1
|
|
172
|
+
2026-10-06 14:31 delivered anyway: c0ffee12
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
When triage skipped a session that needed the note, `chi broadcast deliver
|
|
176
|
+
BROADCAST_ID ID...` (an id or the start of one, for both) gives it the logged
|
|
177
|
+
text, dated when it was shared and saying you passed it on. A session that
|
|
178
|
+
got it already is left alone. The log keeps it as a correction: labels for
|
|
179
|
+
tuning `broadcast.threshold`, or for a classifier later.
|
|
180
|
+
|
|
110
181
|
## For you, not for an agent
|
|
111
182
|
|
|
112
|
-
`chi broadcast` refuses to run inside a chi session (where
|
|
183
|
+
`chi broadcast` (`log` and `deliver` too) refuses to run inside a chi session (where
|
|
113
184
|
`SAMAGOTCHI_PARENT_SESSION` is set): "chi broadcast is for your user, not an
|
|
114
185
|
agent". With the guardrails bundle, the `chi-broadcast` rule also asks before
|
|
115
186
|
any shell command that runs it, once at a time, and only you may allow it
|
|
@@ -121,9 +192,17 @@ any shell command that runs it, once at a time, and only you may allow it
|
|
|
121
192
|
|---|---|---|
|
|
122
193
|
| `broadcast.active_hours` | `8` | A session with no worker or REPL counts when its last turn ended within this many hours. |
|
|
123
194
|
| `broadcast.ticket_pattern` | `\b[A-Z][A-Z0-9]+-\d+\b` | A Ruby regex for ticket ids; an invalid one warns and the default is used. |
|
|
195
|
+
| `broadcast.triage_model` | recap's, else `default.model` | The triage model, a model ref (`box:qwen3.5-9b`). |
|
|
196
|
+
| `broadcast.triage_host_ref` | none | A `hosts:` name for it. |
|
|
197
|
+
| `broadcast.triage_base_url` | none | An OpenAI API base for it instead. |
|
|
198
|
+
| `broadcast.triage_parallel` | `4` | Triage requests at a time. |
|
|
199
|
+
| `broadcast.triage_deadline` | `20` | Seconds triage may take in all; the rest get the note unchecked. |
|
|
200
|
+
| `broadcast.threshold` | `0.5` | The P(yes) a session needs when the host gives logprobs. |
|
|
124
201
|
|
|
125
202
|
```yaml
|
|
126
203
|
broadcast:
|
|
127
204
|
active_hours: 12
|
|
128
205
|
ticket_pattern: '\b(?:PAY|OPS)-\d+\b'
|
|
206
|
+
triage_model: box:qwen3.5-9b
|
|
207
|
+
triage_deadline: 30
|
|
129
208
|
```
|
data/docs/configuration.md
CHANGED
|
@@ -742,7 +742,8 @@ One record is one line, plus indented payload lines at debug level:
|
|
|
742
742
|
control characters (terminal colours in tool output) are escaped.
|
|
743
743
|
- Tags: `turn` (a session's event trail), `http` (model requests),
|
|
744
744
|
`worker`, `bridge`, `web`, `attached`, `repl`, `idle`, `recap`, `hooks`,
|
|
745
|
-
`plugins`, `guardrails`, `config`, `memory`, `model` (debug dumps), `context` (attached context fetches)
|
|
745
|
+
`plugins`, `guardrails`, `config`, `memory`, `model` (debug dumps), `context` (attached context fetches),
|
|
746
|
+
`broadcast` (chi broadcast's triage).
|
|
746
747
|
- The format is parsed by `Samagotchi::LogLine` (`parse`, `each_record`);
|
|
747
748
|
keep tools that read it on that parser.
|
|
748
749
|
|
|
@@ -884,6 +885,12 @@ described in their own sections.
|
|
|
884
885
|
| `recap.sentences` | `2-4` | yes | Recap length, `N` or `N-M` (1–10). |
|
|
885
886
|
| `broadcast.active_hours` | `8` | | `chi broadcast` reaches a session a worker or a chi REPL runs, or whose last turn ended within this many hours. See [Broadcast](broadcast.md). |
|
|
886
887
|
| `broadcast.ticket_pattern` | `\b[A-Z][A-Z0-9]+-\d+\b` | | A Ruby regex for ticket ids; one in the note and in a session's branch (any case) or prompts delivers the broadcast there. See [Broadcast](broadcast.md). |
|
|
888
|
+
| `broadcast.triage_model` | recap's | | The model that judges a recipient no tag matched (a model ref, as `recap.model`); unset: the recap's model, else `default.model`. A ~9B dense instruct model or larger; 4B-class ones say yes to nearly everything. See [Broadcast](broadcast.md#triage). |
|
|
889
|
+
| `broadcast.triage_host_ref` | none | | A `hosts:` name for the triage model. |
|
|
890
|
+
| `broadcast.triage_base_url` | none | | An OpenAI API base for the triage model instead (`http://h:8081/v1`). |
|
|
891
|
+
| `broadcast.triage_parallel` | `4` | | Triage requests at a time. |
|
|
892
|
+
| `broadcast.triage_deadline` | `20` | | Seconds triage may take in all; a recipient not judged by then gets the note unchecked. |
|
|
893
|
+
| `broadcast.threshold` | `0.5` | | The P(yes) a recipient needs, when the triage host gives logprobs; a plain yes or no is 1 or 0. |
|
|
887
894
|
| `session.shared` | `true` | | Plain `chi` runs its session in a worker and attaches; `--no-shared` per run. |
|
|
888
895
|
| `session.idle_exit_minutes` | `30` | yes | An unused worker exits after this; `0` = never. |
|
|
889
896
|
| `session.keep_empty` | `false` | | Keep sessions nothing happened in. |
|
data/docs/context.md
CHANGED
|
@@ -184,6 +184,43 @@ It needs [`gh`](https://cli.github.com), logged in (`gh auth login`).
|
|
|
184
184
|
- It wakes for a review requesting changes, checks turning red, and the PR
|
|
185
185
|
merged or closed. Comments alone never wake.
|
|
186
186
|
|
|
187
|
+
### Line links
|
|
188
|
+
|
|
189
|
+
In the web, a file reference in an answer links to that line of the pull
|
|
190
|
+
request the session reviews: `lib/foo.rb:28` and `lib/foo.rb:28-34`, also in
|
|
191
|
+
inline code (`` `lib/foo.rb:28` ``). The model writes plain references; the
|
|
192
|
+
links are display only (the model's text stays as it was), and terminals
|
|
193
|
+
show the answer as it is.
|
|
194
|
+
|
|
195
|
+
- **Which PR**: the session's attached PRs (the branch's, `chi context add
|
|
196
|
+
<PR URL>`, "+ URL") and the PR URLs in your messages, newest first (a
|
|
197
|
+
delegate child's task counts: a parent that names the PR there gets links
|
|
198
|
+
in the child's answers). PR URLs in tool output or answers don't count.
|
|
199
|
+
- **Which file**: a file the PR changes, by its path, a renamed file's old
|
|
200
|
+
path, a bare name only one PR file has (`source_links.rb:28`), or an
|
|
201
|
+
absolute path into a checkout (a child's worktree). Any other file isn't
|
|
202
|
+
linked, nor is a path several of the session's PRs change.
|
|
203
|
+
- **Where to**: the line in the PR's **Files changed**, highlighted, when it
|
|
204
|
+
is inside a changed hunk (a removed file: on its old side); else the file
|
|
205
|
+
at the PR's head commit (a removed one: at its base), which has every
|
|
206
|
+
line.
|
|
207
|
+
- Code blocks, markdown links and URLs are left alone.
|
|
208
|
+
- The PR's files are read with `gh api` in the background when a turn
|
|
209
|
+
starts (and when a PR is attached while it runs), at most once a minute,
|
|
210
|
+
and read again only when the PR's head moved; the answer is linked from
|
|
211
|
+
what was read.
|
|
212
|
+
So the first answer of a very short turn may come without links, and
|
|
213
|
+
after a push links may be a few lines off until the next read. Without
|
|
214
|
+
`gh` or its login, nothing is linked.
|
|
215
|
+
|
|
216
|
+
Turn it off:
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
bundles:
|
|
220
|
+
github-pr:
|
|
221
|
+
line_links: false
|
|
222
|
+
```
|
|
223
|
+
|
|
187
224
|
## Safety
|
|
188
225
|
|
|
189
226
|
A command source runs **outside the guardrails gate**, every few minutes,
|
data/docs/guardrails.md
CHANGED
|
@@ -244,7 +244,12 @@ guardrails:
|
|
|
244
244
|
scopes: [once, session, rule]
|
|
245
245
|
```
|
|
246
246
|
|
|
247
|
-
A rule gives at least one of `tool`, `command`, `path`, `git`, `touches` and `
|
|
247
|
+
A rule gives at least one of `tool`, `command`, `path`, `git`, `touches`, `rm` and `memory`.
|
|
248
|
+
|
|
249
|
+
`memory: remove` matches a `memory_write` with `remove: true` (it moves a
|
|
250
|
+
memory into the bundle trash). Nothing else asks about it: a memory's file is
|
|
251
|
+
never outside the repo (below), so without such a rule any session, a delegate
|
|
252
|
+
child included, removes a memory unasked.
|
|
248
253
|
|
|
249
254
|
`rm: outside_tmp` (shell tools) matches a command whose `rm -r -f` reaches
|
|
250
255
|
outside the tmp folders (`$TMPDIR`, `/tmp`, `/var/tmp`): every such `rm` in a
|
|
@@ -287,6 +292,21 @@ A plugin tool whose `targets:` name no command or path (an MCP tool) is asked
|
|
|
287
292
|
about with its arguments (`mcp_x_sum: a=20 b=22`), and an approval of "this
|
|
288
293
|
call" is keyed by them.
|
|
289
294
|
|
|
295
|
+
A plugin tool that runs other tools for the model (a dispatcher such as
|
|
296
|
+
`mcp_call`) can say, in its `targets:`, which tool a call acts as (`acts_as:`)
|
|
297
|
+
and with which arguments (`args:`). A rule's `tool:` then matches the call's
|
|
298
|
+
own name **or** the tool it acts as: `tool: mcp_github_*` fires for an
|
|
299
|
+
`mcp_call` that acts as `mcp_github_merge_pull_request`, and `tool: mcp_call`
|
|
300
|
+
fires whatever it acts as. The question names the tool it acts as (by the
|
|
301
|
+
`label:` its `targets:` gives, if any: display only) and shows its arguments, and an approval of "this call" is keyed by the real name, the
|
|
302
|
+
alias and those arguments (`/guardrails` lists it as
|
|
303
|
+
`mcp_call>mcp_github_merge_pull_request:number=7 …`), so it never covers a
|
|
304
|
+
direct call of that tool, nor a direct call's approval it. A tool can't act as
|
|
305
|
+
one of chi's own tools or another bundle's tool: that `acts_as:` is dropped
|
|
306
|
+
(and logged as `plugin_acts_as_dropped`), and the call is matched by its own
|
|
307
|
+
name only. Hooks and loop-guard's `ignore_tools` still see the call's own
|
|
308
|
+
name.
|
|
309
|
+
|
|
290
310
|
```yaml
|
|
291
311
|
- id: mcp-ask
|
|
292
312
|
tool: "mcp_*" # every MCP tool (the mcp bundle's mcp_<server>_<tool>)
|
|
@@ -408,7 +428,7 @@ rules (`models: small`) are untagged, so they ask a small model in both modes.
|
|
|
408
428
|
leaves out `(strict only)`.
|
|
409
429
|
|
|
410
430
|
A rule in config.yml that uses a key this chi doesn't know yet (`modes:`,
|
|
411
|
-
`touches:`, `rm:`, `skip_read_only:` before 0.20) doesn't parse, and a rule
|
|
431
|
+
`touches:`, `rm:`, `skip_read_only:` before 0.20, `memory:` before 0.36) doesn't parse, and a rule
|
|
412
432
|
that doesn't parse denies every tool call (see Failing closed). Bundles say
|
|
413
433
|
which chi they need (`requires_chi`), so their rules don't hit this.
|
|
414
434
|
|
|
@@ -429,7 +449,10 @@ hooks, approvals, bundles or attached context sources, or a `.git/hooks`
|
|
|
429
449
|
time: a command chi will run every few minutes outside the guardrails; see
|
|
430
450
|
[Attached context](context.md#safety)); `chi broadcast` (`chi-broadcast`,
|
|
431
451
|
once at a time and only by the user: it is the user's command, which also
|
|
432
|
-
refuses to run inside a session; see [Broadcast](broadcast.md));
|
|
452
|
+
refuses to run inside a session; see [Broadcast](broadcast.md)); removing a
|
|
453
|
+
memory with `memory_write remove: true` (`memory-remove`, once at a time:
|
|
454
|
+
a delegate child could otherwise remove its parent's handoff unasked; see
|
|
455
|
+
[Memory](memory.md)); and answering chi's
|
|
433
456
|
questions around `chi answer`: `chi --attach` or `chi -p` with stdin from a
|
|
434
457
|
pipe, a here-string or a file (`chi-answer-piped`), and `curl`/`wget` to a
|
|
435
458
|
session's `/answer` route (`chi-answer-http`), both once or for the session.
|
data/docs/hooks.md
CHANGED
|
@@ -424,7 +424,7 @@ stripped of surrounding whitespace, except file text, an edit's
|
|
|
424
424
|
| `write` | — | `path` | `content` |
|
|
425
425
|
| `edit` | — | `path` | `old_text`, `new_text`, `start_line`, `end_line` |
|
|
426
426
|
| `memory_read` | `name` | — | `scope` |
|
|
427
|
-
| `memory_write` | — | `name` | `content`, `scope`, `description`, `current_model_only` |
|
|
427
|
+
| `memory_write` | — | `name` | `content`, `scope`, `description`, `current_model_only`, `remove` |
|
|
428
428
|
| `task_create` | `command` | — | `cwd`, `env` |
|
|
429
429
|
| `task_get` | `id` or `task_id` | — | — |
|
|
430
430
|
| `task_list` | — | — | — |
|
data/docs/memory.md
CHANGED
|
@@ -15,6 +15,9 @@ Tool behavior:
|
|
|
15
15
|
- If `scope` is provided (`project` or `system`), only that scope is read.
|
|
16
16
|
- If `scope` is omitted, read falls back from project to system.
|
|
17
17
|
- `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`.
|
|
18
|
+
- `memory_write`'s `description` is the managed `index.md` line's text: one line (whitespace and newlines collapse to single spaces), at most 200 characters (longer is refused, since every session's prompt for the repo carries it). A missing or blank one keeps the line's description.
|
|
19
|
+
- `memory_write` with `name`, `scope` and `description` but no `content` changes only that entry's index line (its size and date are refreshed; the date becomes today) and leaves the file as it is: a skill that keeps its status in a memory's description changes it without resending the body. The memory must exist in that scope; the form is refused for `index` and with `current_model_only`.
|
|
20
|
+
- `memory_write` with `name`, `scope` and `remove: true` (no content, description or `current_model_only`) removes a memory: its file and its model overlays (`<name>.<key>.md`, not a dotted memory with an index line of its own) move into one dir of the bundle trash, `memory-<name>-<time>` under `memories/.bundles/.trash/`, which `chi bundle trash` lists and empties, and its index line is dropped. When the file is already gone (deleted by hand), the dangling line is dropped alone. A memory a bundle owns (an installed bundle's record lists it, or its line says `· from <bundle>`) is refused: `chi bundle uninstall` removes it. `index` can't be removed. The guardrails bundle's `memory-remove` rule asks before every removal, once at a time (see [Guardrails](guardrails.md#the-guardrails-bundle)).
|
|
18
21
|
- `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.
|
|
19
22
|
|
|
20
23
|
## Model-Specific Memory Overlays
|
data/docs/plugins.md
CHANGED
|
@@ -162,8 +162,8 @@ in the chat path's `tools:`, after chi's own tools.
|
|
|
162
162
|
it raises, the default is shown. Both are saved with the call's result, so
|
|
163
163
|
a web page reloaded later shows the same row: the web server doesn't run
|
|
164
164
|
plugins.
|
|
165
|
-
- `targets`: `->(args) { { paths: [...], command: "…", cwd: "…" } }`,
|
|
166
|
-
key optional, says what a call acts on, for [guardrails](#guardrails).
|
|
165
|
+
- `targets`: `->(args) { { paths: [...], command: "…", cwd: "…", acts_as: "…", args: {…}, label: "…" } }`,
|
|
166
|
+
each key optional, says what a call acts on, for [guardrails](#guardrails).
|
|
167
167
|
|
|
168
168
|
The block returns the result text. If it starts with `Error:`, it counts as
|
|
169
169
|
a failure. If it raises, the model gets `Error: <message>`. To return images
|
|
@@ -234,6 +234,27 @@ is what `targets:` says:
|
|
|
234
234
|
paths (chi's config, …) match them.
|
|
235
235
|
- `command:`: a shell command the call runs; `command:` rules match it.
|
|
236
236
|
- `cwd:`: where it runs, for the repo root and relative paths.
|
|
237
|
+
- `acts_as:`: the tool a call stands for, when the tool runs other tools
|
|
238
|
+
(the mcp bundle's `mcp_call`). Rules keyed by that tool's name match the
|
|
239
|
+
call too, as well as rules on the tool's own name. It can't be one of
|
|
240
|
+
chi's own tools or another bundle's tool: that is dropped (and logged).
|
|
241
|
+
- `args:`: the arguments it acts with (a Hash), when they aren't the call's
|
|
242
|
+
own (a dispatcher's inner arguments). A call with no `command:` or
|
|
243
|
+
`paths:` is asked about with them, and "allow this call" is keyed by them.
|
|
244
|
+
- `label:`: what the approval question names the call by (`github: x`), in
|
|
245
|
+
place of the tool's `label` (or, with `acts_as:`, that tool's). Display
|
|
246
|
+
only: rules and approvals don't read it. One line of at most 80
|
|
247
|
+
characters, else it is ignored (and logged).
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
targets: ->(args) { { acts_as: "mcp_#{args["server"]}_#{args["tool"]}", args: args["args"],
|
|
251
|
+
label: "#{args["server"]}: #{args["tool"]}" } }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
An approval of an `acts_as:` call keys on the tool's own name as well
|
|
255
|
+
(`mcp_call>mcp_github_x:…`), so it never stands in for an approval of the
|
|
256
|
+
tool it acts as, nor that one for it. Hooks see the call's own name, and so
|
|
257
|
+
does loop-guard's `ignore_tools`.
|
|
237
258
|
|
|
238
259
|
A tool without `targets:` is matched by its name only. A `targets:` that
|
|
239
260
|
raises counts as nothing (it is logged). See [guardrails.md](guardrails.md).
|
|
@@ -836,6 +857,13 @@ bundles:
|
|
|
836
857
|
closed, then TERM and KILL go to the server's process group.
|
|
837
858
|
- **`/mcp`** (anytime) shows a card with the servers, their state (cached
|
|
838
859
|
(not started), running with its pid, failed, stopped) and their tools.
|
|
860
|
+
A cached or running server's line says roughly how many tokens its tool
|
|
861
|
+
definitions take (`~4,232 tokens`), and the last line totals the servers
|
|
862
|
+
whose tools the model has: what every request carries. It is an estimate:
|
|
863
|
+
the definitions' JSON as the chat API gets it (`api: openai`), divided by
|
|
864
|
+
4. The native prompts (Gemma, Qwen) render a flatter schema, so there they
|
|
865
|
+
take less. The log records each server's estimate when it changes
|
|
866
|
+
(`mcp_tools_estimated`).
|
|
839
867
|
- The server's stderr goes to the debug log (`plugins` records, bundle=mcp).
|
|
840
868
|
- **Guardrails.** A rule's `tool:` can be a glob, so one rule covers every
|
|
841
869
|
MCP tool:
|
|
@@ -1137,7 +1165,9 @@ nothing without `gh`, a repository or an open PR), and resolves PR URLs for
|
|
|
1137
1165
|
`chi context add` and the web's "+ URL". Its script prints the PR as text
|
|
1138
1166
|
and a summary of what changed in counts, authors and states only; it wakes
|
|
1139
1167
|
the session for a review requesting changes, checks turning red, or the PR
|
|
1140
|
-
merged or closed.
|
|
1168
|
+
merged or closed. In the web, a `path:line` in an answer links to that line
|
|
1169
|
+
of the session's PR ([Line links](context.md#line-links)). See [Attached
|
|
1170
|
+
context](context.md#github-prs-the-github-pr-bundle).
|
|
1141
1171
|
|
|
1142
1172
|
## The coordinator bundle
|
|
1143
1173
|
|
|
@@ -1163,10 +1193,27 @@ by themselves there.
|
|
|
1163
1193
|
agent may have merged the branch already), merges `--ff-only` and never
|
|
1164
1194
|
pushes unless you ask. After a merge it stops the
|
|
1165
1195
|
child and removes only the worktrees and branches it made.
|
|
1196
|
+
- **A handoff memory.** The conversation is lost when the session ends, so
|
|
1197
|
+
the skill keeps a project memory `handoff_<epic-slug>` with only what git
|
|
1198
|
+
and the session list can't rebuild: the split (branch, worktree, child id
|
|
1199
|
+
per task), your decisions, its verdicts and the follow-ups. Its index
|
|
1200
|
+
description is the status, descriptive only and naming the session that
|
|
1201
|
+
owns it ("OPEN coordinator handoff calc-v2 (session ab12cd34): 2/3 merged",
|
|
1202
|
+
later "DONE: …"): every session in the repo sees that line. The model saves
|
|
1203
|
+
a decision there before acting on it, and changes the status with
|
|
1204
|
+
`memory_write`'s description-only form. When everything is done it marks
|
|
1205
|
+
the handoff DONE and asks whether to remove it (`memory_write remove:
|
|
1206
|
+
true`, which the guardrails bundle asks you to confirm). Children are told
|
|
1207
|
+
not to touch it.
|
|
1208
|
+
- **Resume.** A new coordinator session (or one asked "where were we?")
|
|
1209
|
+
reads the open handoff, checks it against git and the session list (git
|
|
1210
|
+
wins; mismatches are reported) and goes on from the first open step.
|
|
1166
1211
|
- **`/coordinate <goal>`** sends this session a turn asking the model to
|
|
1167
1212
|
follow the skill for the goal (in a `--no-shared` REPL it shows the text to
|
|
1168
1213
|
send yourself). Asking "do this in parallel" works too: the index line leads
|
|
1169
|
-
the model to the skill.
|
|
1214
|
+
the model to the skill. **`/coordinate resume`** asks it to resume this
|
|
1215
|
+
project's open handoff; with several open it shows a card with a Resume
|
|
1216
|
+
action each (`/coordinate resume <name>` picks one), with none it says so.
|
|
1170
1217
|
- **`/children [all]`** shows the session's children as a card, newest first,
|
|
1171
1218
|
one line each: `` `ab12cd34` · running · fix/flaky · "fix the flaky spec" ``,
|
|
1172
1219
|
`` `9a8b7c6d` · done · feat/x · reported · "All 12 specs pass" `` (`not
|