samagotchi 0.32.0 → 0.34.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 +75 -1
- data/README.md +4 -1
- data/docs/context.md +8 -1
- data/docs/guardrails.md +31 -1
- data/docs/hooks.md +7 -4
- data/docs/memory.md +17 -1
- data/docs/plugins.md +79 -3
- data/docs/releasing.md +3 -2
- data/docs/sessions.md +9 -3
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +1 -15
- data/lib/samagotchi/bridge.rb +8 -3
- data/lib/samagotchi/bundles/coordinator/manifest.yml +14 -0
- data/lib/samagotchi/bundles/coordinator/plugin.rb +114 -0
- data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +60 -0
- data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
- data/lib/samagotchi/bundles/mcp/manifest.yml +3 -3
- data/lib/samagotchi/bundles/mcp/plugin.rb +10 -11
- data/lib/samagotchi/bundles/system/delegated.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/bundles/system/self_map.md +4 -2
- data/lib/samagotchi/child_reports.rb +31 -10
- data/lib/samagotchi/children_status.rb +157 -0
- data/lib/samagotchi/client_id.rb +52 -0
- data/lib/samagotchi/context_absorber.rb +2 -1
- data/lib/samagotchi/context_fetch.rb +14 -45
- data/lib/samagotchi/context_sources.rb +20 -10
- data/lib/samagotchi/cut_policy.rb +58 -0
- data/lib/samagotchi/engine.rb +24 -108
- data/lib/samagotchi/engine_peers.rb +30 -0
- data/lib/samagotchi/guardrail_wiring.rb +27 -2
- data/lib/samagotchi/guardrails/approvals.rb +7 -0
- data/lib/samagotchi/guardrails/child_boundary.rb +103 -0
- data/lib/samagotchi/guardrails/context.rb +32 -0
- data/lib/samagotchi/guardrails/parent_approvals.rb +0 -2
- data/lib/samagotchi/guardrails/shared_git.rb +124 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/kernel_loop.rb +9 -22
- data/lib/samagotchi/llm/chat_loop.rb +11 -22
- data/lib/samagotchi/memory_bundle/builder.rb +35 -1
- data/lib/samagotchi/memory_bundle/installer.rb +6 -2
- data/lib/samagotchi/memory_bundle/manifest.rb +28 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +2 -1
- data/lib/samagotchi/memory_paths.rb +10 -0
- data/lib/samagotchi/plugin/api.rb +2 -2
- data/lib/samagotchi/plugin/sessions.rb +47 -2
- data/lib/samagotchi/process_group.rb +157 -0
- data/lib/samagotchi/prompt_warmup.rb +20 -1
- data/lib/samagotchi/question_desk.rb +2 -1
- data/lib/samagotchi/relay_verifier.rb +2 -4
- data/lib/samagotchi/reply_wait.rb +9 -4
- data/lib/samagotchi/send_command.rb +2 -1
- data/lib/samagotchi/session_manager.rb +2 -4
- data/lib/samagotchi/steer.rb +27 -19
- data/lib/samagotchi/steer_cut.rb +125 -0
- data/lib/samagotchi/system_prompt.rb +20 -8
- data/lib/samagotchi/terminal_ui/attached_loop.rb +5 -4
- data/lib/samagotchi/terminal_ui/formatting.rb +21 -12
- data/lib/samagotchi/terminal_ui.rb +2 -1
- data/lib/samagotchi/tool_declarations.rb +9 -5
- data/lib/samagotchi/tools/builtins.rb +2 -2
- data/lib/samagotchi/tools/delegate.rb +105 -16
- data/lib/samagotchi/tools/delegate_wait.rb +55 -13
- data/lib/samagotchi/tools/execute.rb +10 -81
- data/lib/samagotchi/tools/task_runtime.rb +25 -38
- data/lib/samagotchi/turn_note.rb +9 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +2 -1
- data/lib/samagotchi/web/public/app.js +19 -20
- data/lib/samagotchi/web/public/data.js +3 -0
- data/lib/samagotchi/web/public/question_card.js +3 -4
- data/lib/samagotchi/web/public/shown_texts.js +13 -0
- data/lib/samagotchi/web/public/stable_html.js +18 -0
- data/lib/samagotchi/web/public/turn_events.js +9 -2
- data/lib/samagotchi/web/public/turn_view.js +1 -1
- data/lib/samagotchi/worker.rb +40 -13
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6f812c6128ae3ffaf91d9cc04716a77d351fb57793a7d9a4863e675f1f2337f
|
|
4
|
+
data.tar.gz: 25e7600fe6dc335b7d28320795a03ea9e8527185f6a0dd918787777458402f39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 322e9f5a5b553f7ed1f2955307b8d5bd8c901a355ac3ffcdff6b3fcb5013b28011c7c330d9496486338c77d2c0f58e2854393d4cb40ec33d00ed2516a38bd066
|
|
7
|
+
data.tar.gz: f6048763f53d9b865858842acaf60587d5e57a365aaba759c3c85fcf69532227c4b201fe0f878da3e48191dd43856637dad29537203a3dfd7159756caac6f778
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,78 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.34.0] - 2026-10-06
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `delegate cwd:` starts a child in another folder of the session's repository: a worktree the model made with
|
|
16
|
+
`git worktree add`, or a subfolder. Another repository is refused.
|
|
17
|
+
- Plugins: `ctx.sessions.children` lists the session's children (state, branch, last reply and whether the parent
|
|
18
|
+
was given it), and `ctx.sessions.stop(id)` stops one of its own children.
|
|
19
|
+
- Bundles: a `files:` entry in `manifest.yml` may carry a `description:`, which the install writes into the
|
|
20
|
+
memory's `index.md` line.
|
|
21
|
+
- The `coordinator` bundle: chi coordinates parallel work. `/coordinate <goal>` (or asking for it) has chi split
|
|
22
|
+
the work into tasks, each in a child session in its own git worktree, check each report and ask you before each
|
|
23
|
+
merge. `/children` shows the session's children: state, branch, last reply and whether it was reported, with a
|
|
24
|
+
Stop button each.
|
|
25
|
+
- Guardrails: a delegate child asks before it changes anything outside its folder (its worktree): a write there,
|
|
26
|
+
git that changes the main checkout or a sibling worktree, a command run from or naming one of them. It's built in,
|
|
27
|
+
in every mode and without the guardrails bundle, and answered once or for the child's session.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- The `delegate` and `delegate_result` tools tell the model to start a child with `wait: false` unless its turn
|
|
32
|
+
can't go on without the reply, then end its turn or keep talking with you: the reply comes back by itself as a
|
|
33
|
+
delegate report. `delegate_result` is only for a reply the turn needs now.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- A wake turn (a context source's change, a delegate's report) that fails no longer tells the model the user's
|
|
38
|
+
message went unanswered: it says the wake wasn't answered, and that no other wake turn starts until you write.
|
|
39
|
+
For a delegate's report it adds that the report comes again with your next message.
|
|
40
|
+
- A delegate wait that timed out or was canceled says the child's reply still comes as a delegate report, instead
|
|
41
|
+
of steering the model into waiting again with `delegate_result`; so does a question's result.
|
|
42
|
+
- `delegate` with `wait: false` in a session that gets no delegate reports (a `--no-shared` REPL, `-p`) says
|
|
43
|
+
`delegate_result` waits for the reply, instead of promising a report that never comes.
|
|
44
|
+
- In a linked git worktree the system prompt no longer names the repository's main checkout ("Project root"), which
|
|
45
|
+
led a model to work and commit there: it names this checkout's top and says to work only in it. Project memories
|
|
46
|
+
and session lists still follow the repository.
|
|
47
|
+
- `delegate` refused for too many running children tells a session that gets delegate reports to end its turn (a
|
|
48
|
+
report frees a slot), instead of to wait with `delegate_result`.
|
|
49
|
+
- At a repository's root the system prompt names the checkout the project's commands run in, and calls the project
|
|
50
|
+
memories folder your notes about the project, not its files: a model no longer goes looking for the repository
|
|
51
|
+
in the memories folder.
|
|
52
|
+
|
|
53
|
+
Update with `chi update`: it installs the new `coordinator` bundle for the `dev` profile (dev 0.3.0) and updates mcp
|
|
54
|
+
(0.4.5: stops an MCP server through chi's own process-group handling).
|
|
55
|
+
|
|
56
|
+
## [0.33.0] - 2026-10-06
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- Input from a client chi doesn't know (a script calling the web API with its own id, say) is no longer taken
|
|
61
|
+
for the user's: the model reads it as automatic input, not your message, and it never cuts in on a thinking
|
|
62
|
+
model. The TUI and the web label a prompt by its sender: "chi send", "chi answer", "plugin", or "automatic";
|
|
63
|
+
"user" is left for your own lines.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- `task_stop` no longer signals a process group chi didn't start: a missing or reused pid in a task record could
|
|
68
|
+
stop chi or other processes.
|
|
69
|
+
- A delegate child that crashes still wakes its idle parent.
|
|
70
|
+
- `chi send <id> --wait` to a stopped session with attached context prints the answer instead of "no answer".
|
|
71
|
+
- `chi bundle build` keeps a bundle's `scripts:` and `context_providers:`, so a rebuilt bundle still attaches its URLs.
|
|
72
|
+
- A context source's wake is no longer lost when a later update that doesn't ask to wake comes in before the
|
|
73
|
+
session reads it.
|
|
74
|
+
- A failed wake turn leaves a plain note in the conversation; a reload no longer draws an empty turn for it.
|
|
75
|
+
- Web: the info bar no longer swallows a click on "copy chi --attach" or Restart while a turn runs, and its
|
|
76
|
+
tooltips stay up.
|
|
77
|
+
- Web: an image the server refuses shows why; a 409 that isn't "open in a chi REPL" no longer switches the page to
|
|
78
|
+
the REPL notice.
|
|
79
|
+
- Web: an answer equal to an earlier turn's ("Done." twice) is shown.
|
|
80
|
+
|
|
81
|
+
Update with `chi update`.
|
|
82
|
+
|
|
11
83
|
## [0.32.0] - 2026-10-06
|
|
12
84
|
|
|
13
85
|
### Added
|
|
@@ -1636,7 +1708,9 @@ and long-lived sessions.
|
|
|
1636
1708
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
1637
1709
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
1638
1710
|
|
|
1639
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
1711
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.34.0...HEAD
|
|
1712
|
+
[0.34.0]: https://github.com/dm1try/samagotchi/compare/v0.33.0...v0.34.0
|
|
1713
|
+
[0.33.0]: https://github.com/dm1try/samagotchi/compare/v0.32.0...v0.33.0
|
|
1640
1714
|
[0.32.0]: https://github.com/dm1try/samagotchi/compare/v0.31.0...v0.32.0
|
|
1641
1715
|
[0.31.0]: https://github.com/dm1try/samagotchi/compare/v0.30.0...v0.31.0
|
|
1642
1716
|
[0.30.0]: https://github.com/dm1try/samagotchi/compare/v0.29.0...v0.30.0
|
data/README.md
CHANGED
|
@@ -145,7 +145,10 @@ never takes orders from. The web and the attached terminal show it as a dim
|
|
|
145
145
|
a note reaches (`id<TAB>description`). Agents can do the same: `list_sessions`
|
|
146
146
|
finds another session, `send_note` tells it something, and `delegate` hands a
|
|
147
147
|
task to a child session that runs in parallel and reports back only its final
|
|
148
|
-
reply (a normal session: `chi --attach <id>` steers it).
|
|
148
|
+
reply (a normal session: `chi --attach <id>` steers it). With the `coordinator`
|
|
149
|
+
bundle (in `dev`), `/coordinate <goal>` has chi split the work into tasks, each
|
|
150
|
+
in a child session in its own git worktree, check each report and ask you
|
|
151
|
+
before each merge; `/children` shows the children and their state.
|
|
149
152
|
|
|
150
153
|
`chi send` is the other half: the text goes in as your message, the same as
|
|
151
154
|
typing it in the attached terminal or the web composer, and a turn runs. Piped
|
data/docs/context.md
CHANGED
|
@@ -135,7 +135,9 @@ attaches a URL; the web never adds a command.
|
|
|
135
135
|
A change can start a turn by itself, so the agent tells you about it while
|
|
136
136
|
you're away from the terminal. All of these must hold:
|
|
137
137
|
|
|
138
|
-
- the source asked (`"wake": true`; a plain-text source never does)
|
|
138
|
+
- the source asked (`"wake": true`; a plain-text source never does), in
|
|
139
|
+
any change since the session last heard of it: a later change that doesn't
|
|
140
|
+
ask doesn't take the wake back;
|
|
139
141
|
- `context.wake` is on (default `true`);
|
|
140
142
|
- the session's worker is up and idle: no turn running, nothing queued, no
|
|
141
143
|
question or continue offer waiting, not just started;
|
|
@@ -152,6 +154,11 @@ update note, whose last line becomes:
|
|
|
152
154
|
Every UI labels the turn `context <name> changed`, and the web's
|
|
153
155
|
notifications count it as a finished turn however short.
|
|
154
156
|
|
|
157
|
+
A wake turn that fails goes back to before it and keeps the update as a
|
|
158
|
+
plain note; the model reads `[SYSTEM: the previous turn failed before any
|
|
159
|
+
answer: … The wake turn for the change in attached context <name> was not
|
|
160
|
+
answered; chi starts no other wake turn until the user writes.]`.
|
|
161
|
+
|
|
155
162
|
## GitHub PRs: the github-pr bundle
|
|
156
163
|
|
|
157
164
|
```sh
|
data/docs/guardrails.md
CHANGED
|
@@ -11,7 +11,8 @@ are offered.
|
|
|
11
11
|
Who votes, in order:
|
|
12
12
|
|
|
13
13
|
1. `before_tool_call` hooks (Ruby; see [Hooks](hooks.md#guardrails-from-a-hook)).
|
|
14
|
-
2. Core checks: a required guardrail that failed to load, then protected paths
|
|
14
|
+
2. Core checks: a required guardrail that failed to load, then protected paths, then (a delegate child only)
|
|
15
|
+
[the child boundary](#a-delegate-childs-boundary).
|
|
15
16
|
3. YAML rules: `config.yml`'s `guardrails:` section, then installed bundles' rule files (by bundle name).
|
|
16
17
|
|
|
17
18
|
The UI shows the tool line first, then the approval under it.
|
|
@@ -189,6 +190,35 @@ Built in, for `write`, `edit` and `memory_write` (symlinks resolved):
|
|
|
189
190
|
`execute` can still reach them; the guardrails bundle asks about shell
|
|
190
191
|
commands that name them.
|
|
191
192
|
|
|
193
|
+
## A delegate child's boundary
|
|
194
|
+
|
|
195
|
+
Built in, for [delegate](sessions.md#delegating) children only, in every
|
|
196
|
+
mode (also with `guardrails.enabled: false`) and without the guardrails
|
|
197
|
+
bundle: a child asks before it changes anything outside its folder, the work
|
|
198
|
+
tree top of the folder it was started in (a worktree from `delegate cwd:`,
|
|
199
|
+
else the parent's checkout). Rule `child-boundary`, source `core`; it asks when
|
|
200
|
+
|
|
201
|
+
- `write` or `edit` names a path outside it (tmp dirs and memory files don't count);
|
|
202
|
+
- `execute` or `task_create` runs mutating git in another dir (`git -C`, `cd`,
|
|
203
|
+
`GIT_DIR`: what `git: outside_repo` matches), or a command that isn't
|
|
204
|
+
read-only starts outside it (its `cwd:`) or names a path in the repository's
|
|
205
|
+
other checkouts (the main one and sibling worktrees, read from
|
|
206
|
+
`<common git dir>/worktrees/*/gitdir`) or in its common git dir;
|
|
207
|
+
- `execute` or `task_create` runs git that changes what every checkout shares,
|
|
208
|
+
even from its own worktree: a `git config` write (not `--get`, `--list`,
|
|
209
|
+
`--worktree`), `update-ref`, `worktree add|remove|move|prune`, `stash
|
|
210
|
+
drop|clear|pop`, creating or deleting a tag, and deleting, forcing or
|
|
211
|
+
overwriting a branch other than its own.
|
|
212
|
+
|
|
213
|
+
Read-only commands (`git -C ../app log`, `cat ../app/x`) and paths elsewhere
|
|
214
|
+
(`/usr/bin`, `~/.gem`) never ask. The answer is once or for the child's
|
|
215
|
+
session only: a repo or rule answer would hold in every worktree, for every
|
|
216
|
+
child, so a stored repo or rule approval doesn't let it through either. Its
|
|
217
|
+
ask goes before the rules, so with the guardrails bundle in strict mode the
|
|
218
|
+
card still names `child-boundary`. The parent (never a delegate), forks and
|
|
219
|
+
ordinary sessions don't get it. Like the rules it reads command text: a
|
|
220
|
+
`sh -c` body, a script or `$(…)` isn't looked into.
|
|
221
|
+
|
|
192
222
|
## Rules in config.yml
|
|
193
223
|
|
|
194
224
|
```yaml
|
data/docs/hooks.md
CHANGED
|
@@ -122,9 +122,12 @@ Its `source` is the hook's bundle (a config or turn hook's label otherwise).
|
|
|
122
122
|
Lines typed into a running turn are saved as `{role: "user", kind: "input",
|
|
123
123
|
content: "…"}`: part of that turn, not a turn of their own. They carry a
|
|
124
124
|
`source` when they are not the user's own: `chi_send` (`chi send -m`),
|
|
125
|
-
`parent_agent` (a delegate's follow-up)
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
`parent_agent` (a delegate's follow-up), `plugin_send` (`ctx.sessions.send`),
|
|
126
|
+
or `automatic:<client id>` for a client id chi doesn't know (an unknown sender
|
|
127
|
+
is never taken for the user). The model reads them with the same kind of
|
|
128
|
+
header (`[Steer from the user, …]`, `[Steer sent with chi send, …]`, and so
|
|
129
|
+
on; `[Automatic input from <client id>, sent mid-task; not your user's
|
|
130
|
+
message. …]` for an unknown one).
|
|
128
131
|
|
|
129
132
|
Timing: a notice from `:after_turn` or `:session_end` shows after the turn's
|
|
130
133
|
end line. A question from `:before_tool_call` shows **before** the tool
|
|
@@ -417,7 +420,7 @@ stripped of surrounding whitespace, except file text, an edit's
|
|
|
417
420
|
| `list_sessions` | — | — | `cwd` |
|
|
418
421
|
| `send_note` | `text` | — | `session` |
|
|
419
422
|
| `context_read` | `name` | — | `offset`, `limit` |
|
|
420
|
-
| `delegate` | `task` | — | `model`, `session`, `wait`, `timeout` |
|
|
423
|
+
| `delegate` | `task` | — | `model`, `session`, `cwd`, `wait`, `timeout` |
|
|
421
424
|
| `delegate_result` | — | — | `session`, `timeout` |
|
|
422
425
|
| `ask_user_question` | `question` | — | `question`, `options`, `header`, `multi_select`, `allow_freeform` |
|
|
423
426
|
|
data/docs/memory.md
CHANGED
|
@@ -102,6 +102,22 @@ for a file another bundle installed.
|
|
|
102
102
|
`- **memory_guide** · system · 2026-10-03 · 5120 · from samagotchi-system —
|
|
103
103
|
…`. `memory_write`, `write` and `edit` keep it. A bundle installed by chi
|
|
104
104
|
0.16 or earlier gets it on its next install or upgrade.
|
|
105
|
+
- **A bundle can give a memory its index description.** A `files:` entry
|
|
106
|
+
may be a mapping instead of a sha line:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
files:
|
|
110
|
+
identity.md: sha256:…
|
|
111
|
+
skill_coordinator.md:
|
|
112
|
+
sha256: sha256:…
|
|
113
|
+
description: "Coordinate parallel work: …"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The install writes that description into the memory's `index.md` line
|
|
117
|
+
(after `— `), so the model can tell what a shipped skill is for from the
|
|
118
|
+
index alone. The bundle's record keeps it, `chi bundle build` writes the
|
|
119
|
+
mapping back, and `rake bundles:sha` refreshes the nested sha. A file
|
|
120
|
+
with a plain sha line keeps the description its line already has.
|
|
105
121
|
- **Uninstall moves, it doesn't delete.** The bundle's memory files go to
|
|
106
122
|
`$XDG_CONFIG_HOME/samagotchi/memories/.bundles/.trash/<bundle>-<YYYYmmdd-HHMMSS>/`,
|
|
107
123
|
and the output names them and the dir:
|
|
@@ -133,7 +149,7 @@ profiles, meta bundles that install a set of them:
|
|
|
133
149
|
| profile | bundles |
|
|
134
150
|
|---|---|
|
|
135
151
|
| `core` | loop-guard, check-in, guardrails: the recommended safety set |
|
|
136
|
-
| `dev` | known-names, mcp, btw, skills, source-links |
|
|
152
|
+
| `dev` | known-names, mcp, btw, skills, source-links, github-pr, coordinator |
|
|
137
153
|
|
|
138
154
|
`chi bootstrap` installs `core` and, on a terminal, offers `dev`. An existing
|
|
139
155
|
install gets neither by itself: `chi bundle install core` (or `dev`).
|
data/docs/plugins.md
CHANGED
|
@@ -496,7 +496,7 @@ full label, `plugin.rb (bundle my-bundle)`.
|
|
|
496
496
|
| `ctx.stop_turn(reason)` | stop the running turn after a warn notice with the reason; true when it stopped one now. In a `:before_tool_call` block it also denies that call |
|
|
497
497
|
| `ctx.stop_generation(reason)` | cut the generation that is streaming: the turn goes on and the model is asked again ([hooks.md](hooks.md#watching-the-stream)). Shows nothing: post your own notice. True when it cut one now |
|
|
498
498
|
| `ctx.ask_model(messages:, prompt:, …)` | a side answer from the session's model: see [Side answers](#side-answers-ctxask_model) |
|
|
499
|
-
| `ctx.sessions` | fork, send to and
|
|
499
|
+
| `ctx.sessions` | fork, send to, read, list and stop other sessions: see [Other sessions](#other-sessions-ctxsessions) |
|
|
500
500
|
| `ctx.context` | attach outside text to this session and list it: see [Attached context](#attached-context-ctxcontext) |
|
|
501
501
|
|
|
502
502
|
The Engine itself is never handed to a plugin.
|
|
@@ -608,7 +608,9 @@ llama.cpp serves on a native host too.
|
|
|
608
608
|
id = ctx.sessions.fork(messages: ctx.messages + [{ role: "user", content: q }, { role: "model", content: a }],
|
|
609
609
|
title: "btw: #{q}")
|
|
610
610
|
ctx.sessions.send(id, "go on from here")
|
|
611
|
-
ctx.sessions.read(id)
|
|
611
|
+
ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, messages:}
|
|
612
|
+
ctx.sessions.children # => [{id:, short_id:, state:, branch:, last_reply:, reported:, ...}]
|
|
613
|
+
ctx.sessions.stop(id) # one of this session's own children
|
|
612
614
|
```
|
|
613
615
|
|
|
614
616
|
- `fork(messages:, title: nil, prompt: nil)` starts a child session in its
|
|
@@ -629,6 +631,23 @@ ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, message
|
|
|
629
631
|
- `read(id)` gives a session now: from its worker when one runs (with a
|
|
630
632
|
running turn so far, `running: true`), else as saved. `messages` has no
|
|
631
633
|
system prompt.
|
|
634
|
+
- `children(all: false)` lists this session's children, newest first, as
|
|
635
|
+
frozen Hashes: `{id:, short_id:, title:, state:, waiting:, live:,
|
|
636
|
+
delegate:, cwd:, branch:, last_reply:, last_reply_at:, reported:,
|
|
637
|
+
updated_at:, archived:}`.
|
|
638
|
+
- `state` is `waiting` (a question or approval is open; `waiting` says
|
|
639
|
+
which), `running`, `failed` (the last turn failed or the worker
|
|
640
|
+
crashed), `stopped`, `done` (the last turn ended with a reply) or `idle`.
|
|
641
|
+
- `delegate` is true for a `delegate` child, false for a fork. Archived
|
|
642
|
+
children are listed only with `all: true`.
|
|
643
|
+
- `branch` is the branch checked out in the child's folder (read from its
|
|
644
|
+
`HEAD`, a short sha when detached; nil outside git). `last_reply` is the
|
|
645
|
+
first line of its newest reply, and `reported` says whether this session
|
|
646
|
+
was already given that reply (a delegate report or a wait).
|
|
647
|
+
- It reads every session's file: call it on demand, not in a loop.
|
|
648
|
+
- `stop(id)` stops one of this session's own children, as
|
|
649
|
+
`chi sessions stop` does, and waits up to 2 s for its worker to let go. It
|
|
650
|
+
returns the child's id. Any other session raises.
|
|
632
651
|
- Each raises `Samagotchi::Plugin::Sessions::Error` with the reason.
|
|
633
652
|
|
|
634
653
|
## Attached context: `ctx.context`
|
|
@@ -1120,6 +1139,62 @@ and a summary of what changed in counts, authors and states only; it wakes
|
|
|
1120
1139
|
the session for a review requesting changes, checks turning red, or the PR
|
|
1121
1140
|
merged or closed. See [Attached context](context.md#github-prs-the-github-pr-bundle).
|
|
1122
1141
|
|
|
1142
|
+
## The coordinator bundle
|
|
1143
|
+
|
|
1144
|
+
```sh
|
|
1145
|
+
chi bundle install coordinator # in the dev profile
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
chi as a coordinator of parallel work, the way you would run several agents
|
|
1149
|
+
yourself: split the work, one git worktree and child session per task, keep
|
|
1150
|
+
talking while they run, check each report, and merge only with your OK. It
|
|
1151
|
+
works in a session with a worker (plain `chi`, the web). A `--no-shared` REPL
|
|
1152
|
+
or `-p` gets no delegate reports, so the children's replies wouldn't come back
|
|
1153
|
+
by themselves there.
|
|
1154
|
+
|
|
1155
|
+
- **The skill.** `skill_coordinator` (system scope; its index line says what
|
|
1156
|
+
it is for, from the manifest's `description:`) holds the steps. The model
|
|
1157
|
+
creates each worktree with `execute` (`git worktree add ../<repo>-<task> -b
|
|
1158
|
+
<branch>`), starts a child there with `delegate wait: false, cwd:`, tells
|
|
1159
|
+
you what started and ends its turn. When a child's report comes, it checks
|
|
1160
|
+
the branch (`git log`, `git diff --stat`, the tests the child names) before
|
|
1161
|
+
telling you. It asks before each merge (`ask_user_question`), checks that its
|
|
1162
|
+
own folder is on the default branch and that branch's log first (another
|
|
1163
|
+
agent may have merged the branch already), merges `--ff-only` and never
|
|
1164
|
+
pushes unless you ask. After a merge it stops the
|
|
1165
|
+
child and removes only the worktrees and branches it made.
|
|
1166
|
+
- **`/coordinate <goal>`** sends this session a turn asking the model to
|
|
1167
|
+
follow the skill for the goal (in a `--no-shared` REPL it shows the text to
|
|
1168
|
+
send yourself). Asking "do this in parallel" works too: the index line leads
|
|
1169
|
+
the model to the skill.
|
|
1170
|
+
- **`/children [all]`** shows the session's children as a card, newest first,
|
|
1171
|
+
one line each: `` `ab12cd34` · running · fix/flaky · "fix the flaky spec" ``,
|
|
1172
|
+
`` `9a8b7c6d` · done · feat/x · reported · "All 12 specs pass" `` (`not
|
|
1173
|
+
reported yet` when the parent wasn't given that reply), `` `77aa66bb` ·
|
|
1174
|
+
waiting (approval) · open it: chi --attach 77aa66bb ``. A fork is marked
|
|
1175
|
+
`fork`; `all` adds archived children. It runs during a turn too. The card
|
|
1176
|
+
has Refresh and a `Stop <id>` per running or waiting child (up to 5);
|
|
1177
|
+
`/children stop <id>` stops one of this session's own children (by session
|
|
1178
|
+
id, as `chi sessions stop` does) and shows the card again.
|
|
1179
|
+
- **Children stay in their worktree.** A child asks before it changes
|
|
1180
|
+
anything outside its worktree (your checkout, a sibling's), in every
|
|
1181
|
+
guardrails mode and without the guardrails bundle ([a delegate child's
|
|
1182
|
+
boundary](guardrails.md#a-delegate-childs-boundary)); you answer on its
|
|
1183
|
+
card, or on the parent's while it waits.
|
|
1184
|
+
- **Merges.** The parent's merge in its own checkout is a plain `git
|
|
1185
|
+
merge`; to be asked before every merge, add a rule ([Rules in
|
|
1186
|
+
config.yml](guardrails.md#rules-in-configyml)):
|
|
1187
|
+
|
|
1188
|
+
```yaml
|
|
1189
|
+
guardrails:
|
|
1190
|
+
rules:
|
|
1191
|
+
- id: git-merge
|
|
1192
|
+
tool: shell
|
|
1193
|
+
command: '\bgit\s+merge\b'
|
|
1194
|
+
verdict: ask
|
|
1195
|
+
reason: merging a child's branch
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1123
1198
|
## Shutdown
|
|
1124
1199
|
|
|
1125
1200
|
When the REPL exits, or a session's worker exits (an idle exit, `/exit`, a
|
|
@@ -1181,7 +1256,8 @@ gem.
|
|
|
1181
1256
|
- `chi bundle diff <name> [plugin.rb]`: shows the installed base and the
|
|
1182
1257
|
file on disk.
|
|
1183
1258
|
- `chi bundle build --name <name>`: puts the installed plugin (and
|
|
1184
|
-
`requires_chi`) into the built
|
|
1259
|
+
`requires_chi`, its `scripts/` and `context_providers:`) into the built
|
|
1260
|
+
bundle.
|
|
1185
1261
|
- `chi bundle uninstall <name>`: removes the plugin with the bundle.
|
|
1186
1262
|
|
|
1187
1263
|
## Not yet
|
data/docs/releasing.md
CHANGED
|
@@ -13,7 +13,7 @@ can run the whole release; the user approves the notes before the tag and the
|
|
|
13
13
|
and `rake release:check` enforce it). The system bundle upgrades itself when
|
|
14
14
|
chi starts.
|
|
15
15
|
- **Every other shipped bundle** (btw, check-in, guardrails, known-names,
|
|
16
|
-
loop-guard, mcp, skills, source-links, github-pr, and the profiles core and dev) has its own semver in its
|
|
16
|
+
loop-guard, mcp, skills, source-links, github-pr, coordinator, and the profiles core and dev) has its own semver in its
|
|
17
17
|
`manifest.yml` and, when it needs a newer chi, a `requires_chi:` line. They never upgrade by themselves: users run
|
|
18
18
|
`chi update` (all of them, with the gem) or `chi bundle upgrade NAME`. So:
|
|
19
19
|
- a change to a bundle's files bumps that bundle's `version:`
|
|
@@ -49,7 +49,8 @@ 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). Warns (doesn't fail) when such a bundle's `requires_chi` still admits the tagged chi: raise it if the change uses anything new. |
|
|
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. The release workflow and `release:check` run it in full. |
|
|
53
|
+
| `rake 'bundles:check[ci]'` | The CI workflow's (every push and PR): the same, except the version check leaves out the system bundle, which is changed and unbumped between releases (`release:bump` moves its version with VERSION). A task argument, not `CI=1`: GitHub sets `CI` in the release workflow too. |
|
|
53
54
|
| `rake release:draft_changelog` | A draft Unreleased section from the commit subjects since the last tag, grouped. Stdout only. |
|
|
54
55
|
| `rake release:bump[X.Y.Z]` | VERSION, the system manifest, Gemfile.lock, CHANGELOG. No commit. |
|
|
55
56
|
| `rake release:check` | Clean tracked tree, VERSION == system bundle, a CHANGELOG section, `bundles:check`, `rake lint` (RuboCop), 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). |
|
data/docs/sessions.md
CHANGED
|
@@ -120,6 +120,9 @@ system message marked `kind: turn_note` (the UIs don't show it; they showed the
|
|
|
120
120
|
before any answer: network error after 2 attempts (host main: Errno::ECONNREFUSED). The message went back to
|
|
121
121
|
the user, who may send it again.]` in the REPL and attached mode, where the prompt is given back;
|
|
122
122
|
`… The user's last message was not answered.]` after `-p`, where the prompt stays in the session;
|
|
123
|
+
`… The wake turn for the change in attached context pr-7 was not answered; chi starts no other wake turn until
|
|
124
|
+
the user writes.]` after a wake turn chi started (a context source's change, a delegate's report), not the user
|
|
125
|
+
(for a delegate's report it adds `chi keeps the report and brings it again with the user's next message.`);
|
|
123
126
|
- cancelled (Ctrl-C, the web's stop): `[SYSTEM: the previous turn was cancelled (ctrl-c) after 12s; the reply
|
|
124
127
|
above ends where it was cut off.]` (an answer or only narration had streamed), or `…; no answer had been shown.]` when only thinking had streamed;
|
|
125
128
|
- ended with nothing visible: `[SYSTEM: the previous turn ended with no visible answer (thinking only, or
|
|
@@ -181,8 +184,10 @@ chi send --wait 3fa2 # sends nothing: waits fo
|
|
|
181
184
|
|
|
182
185
|
The `delegate` tool hands a task to a **child session**: an ordinary chi session in a worker of its own, started in the parent's `working_directory` with the task, verbatim, as its first user message, on the parent's model unless the call names one (`model:` takes a name or an alias, resolved as `--model` is; an unknown host comes back as the tool's `Error:` line; an id the host's saved model list doesn't have starts the child with a `Warning: host 'h' doesn't list model 'x' (did you mean: …?)` line in the result). Children run in parallel and only their **final reply** comes back to the parent: the child's newest `output/<timestamp>.txt` file, which the worker writes at the end of each turn that produced visible text, cut head-and-tail like an `execute` result. Nothing of the child's trace enters the parent's context. Nothing is hidden: a child shows in `chi sessions list` (`↳ <parent>`), the web (a `↳` chip on its card, `delegated by` in its info bar, listed right after its parent in the all-sessions view) and the `list_sessions` tool (`child`; the parent shows as `parent` in the child's own list); the user can `chi --attach <child id>` and steer it mid-run, and `chi send` and `send_note` reach it like any session.
|
|
183
186
|
|
|
184
|
-
- `delegate(task, model:, session:, wait:, timeout:)`: starts a child and, by default, waits for its reply
|
|
185
|
-
- `
|
|
187
|
+
- `delegate(task, model:, session:, cwd:, wait:, timeout:)`: starts a child and, by default, waits for its reply. `wait: false` is the usual way for work that takes a while, several children side by side, or to keep talking with the user: it returns at once, the parent ends its turn or works on, and the reply comes back by itself as a *delegate report* (below), in a turn of its own if the parent is idle. The tool descriptions tell the model so, and to wait (`wait: true`, `delegate_result`) only when its turn can't go on without the reply. `timeout`, default 600 s, leaves a slow child running; 0 checks once without waiting. With `session:` (a child's id or prefix) the task goes to that child as a follow-up instead, delivered like `chi send` (client id `delegate:<parent short id>`, shown as `delegate>` in the child's UIs); only this session's children take one.
|
|
188
|
+
- `cwd:` starts the child in another folder of the parent's repository: a linked worktree (`git worktree add ../<repo>-<task> -b <branch>`, which the model runs with `execute` first, under guardrails; chi runs no git here) or a subfolder; a relative path is taken from the parent's folder. The folder must exist, have the parent's project root (the same common git dir, `MemoryPaths.project_root`) and be in a work tree: a git dir (`.git`, `.git/objects`, a bare layout's `.bare`) or a bare layout's container is refused (`git rev-parse --is-inside-work-tree`, read-only, decides). Outside git only a subfolder of the parent's folder is allowed. Otherwise the tool returns `Error: cwd must be a folder of this session's repository …` or `… is not in a work tree …`. A follow-up (`session:`) refuses `cwd:`: the child keeps its folder. The child's guardrails measure from its own worktree, and project memories, `list_sessions` and the web's project are the same as the parent's.
|
|
189
|
+
- `delegate_result(session:, timeout:)`: waits for a child's next reply: the one named, or the newest running child (`timeout` as for `delegate`: 0 checks once and returns at once). The reply comes back once; a later call waits for the next one. After `wait: false` it isn't needed: the report brings the reply.
|
|
190
|
+
- A wait that timed out or was canceled still gets its delegate report: neither moves the parent's cursor, so the child's reply comes as a report when its turn ends. The result says so (`chi brings its reply here by itself as a delegate report …`; with `queue`, that it joins the next step or turn) and that `delegate_result` waits again only if the turn needs the reply now. A parent that gets no reports (`session.delegate_reports: off`, a `--no-shared` REPL, `--non-interactive`) is told `delegate_result <id> waits again` instead, and `wait: false`'s result says `delegate_result waits for its reply`.
|
|
186
191
|
- What a wait returns, `session: <id>` then `status:` and the text, with the status words `chi send --wait --format json` uses: `answered` with the reply after a `---` line; `question` as soon as the child opens a question (the model's own, or a hook's), with the question's text, options and the `chi answer` command for it (nothing is answered for it: `chi answer`, attach or use the web, then wait again); `failed`, `canceled` or `no_answer` when the child's turn ended with nothing to say (its session shows what happened); `error` (its worker crashed) or `stopped` (`chi sessions stop`) at once; `worker_gone` when the child has had no worker for 15 seconds (it crashed without saying so, or idle-exited): `delegate` with `session:` starts it again; `running` on timeout, or when the parent's own turn is canceled (the child keeps running; the next turn can wait again). A reply saved before the child's worker idle-exited is returned at once.
|
|
187
192
|
- **A child's step-limit question goes back to the parent model**, not to its user: `delegate_result` returns `status: question` with the `chi answer` commands for Continue and Stop, and says the model may instead send a narrower follow-up (`delegate session:`, which drops the question) or report back. A parent's `chi answer` follows `turn.parent_continue` (see [chi as a sub-agent](sub-agent.md#the-step-limit)).
|
|
188
193
|
- **A child's approvals go to the parent's user** (the approval relay). While the parent waits (`delegate`, `delegate_result`), a guardrail approval the child opens reopens as the parent session's own approval: the same card in the parent's web page and attached terminal, with the same facts and diff, a line naming the delegate and its task (`delegate ab12 · fix the flaky spec…`, linked to its session) and the header `Approve delegate ab12's tool call?`. The options are the child's, by index; "for the session" says it is the delegate's session (the session scope keys on the child; repo and rule approvals are one shared store). The user answers once there; the parent's model never sees the approval, only one line per relayed approval in the tool's result, before the reply: `approval relayed to your user: execute: git push origin main → allowed once` (or `denied ("why")`, `dismissed (denied)`, `answered on the child`, `not delivered (the child's worker is gone)`, `refused by the child …`, `still open (your turn was stopped)`). The time the card is open doesn't count against `timeout`. While the parent waits on one child, its other running children's approvals are relayed too, oldest first, one card at a time (`+1 more delegate waiting`); each outcome comes in that child's next result.
|
|
@@ -194,9 +199,10 @@ The `delegate` tool hands a task to a **child session**: an ordinary chi session
|
|
|
194
199
|
- Parent mid-turn: the report joins the turn at its next iteration boundary (a "delegate report" bubble in the web, `delegate report> ab12cd34 answered: …` in the attached terminal).
|
|
195
200
|
- Parent idle: it runs a turn for the report (a *wake turn*, `last_turn.origin: "delegate_report"`) within the worker's 5 s tick, or wakes a worker for it when the parent's has idle-exited. Several reports that wait together are one turn and one message. A stopped, archived or scratch parent, or one open in a `--no-shared` REPL, is not woken: the rings wait on disk and join the next turn it runs (the user's message first). A worker just started (`chi send` to a stopped parent) runs no wake turn in its first 2 s, so the message it was started for goes first.
|
|
196
201
|
- The parent's cursor per child (the newest reply it was given and the child as it was then) is in its `delegates.json`, shared with `delegate_result` and `wait: true`: a reply the model already had is never reported again, also after the parent's worker restarts. A ring is deleted only when the turn that took its report is kept; a turn that fails leaves it for the next one (and the report isn't handed back to the composer).
|
|
197
|
-
- Limits: `session.max_wakes` (default `10`) wake turns in a row with no human input; past it the reports wait for the next message and the UIs show `N delegate reports waiting; they join your next message`. A wake turn that fails pauses wakes until the next human input. A continue offer waiting in the parent takes the reports into its continue turn instead.
|
|
202
|
+
- Limits: `session.max_wakes` (default `10`) wake turns in a row with no human input; past it the reports wait for the next message and the UIs show `N delegate reports waiting; they join your next message`. A wake turn that fails pauses wakes until the next human input; its reports' rings stay, so they join that input's turn, and the failure note tells the model so. A continue offer waiting in the parent takes the reports into its continue turn instead.
|
|
198
203
|
- `session.delegate_reports`: `wake` (default), `queue` (no wake turns: reports join the parent's next turn) or `off` (no rings; only `delegate_result` asks, as before). Env `SAMAGOTCHI_SESSION_DELEGATE_REPORTS`, `SAMAGOTCHI_SESSION_MAX_WAKES`.
|
|
199
204
|
- The web's bell rings at a wake turn's end however short (nobody started it, so nobody watched it). The text a model writes about a report mid-turn, before its next tool call, stays visible after the report's bubble once the turn ends.
|
|
200
205
|
- Every child starts with the shipped `delegated` system memory preloaded (`preloaded_memory_names: ["system/delegated"]`; `--mute delegated` would hide it): put everything in the final reply, don't delegate further, don't write memories or change config, stay in the folder. Its system prompt also says `Delegated by session <parent id>`. The parent's own `--mute` list does not carry over.
|
|
206
|
+
- **A child asks before it changes anything outside its folder** (the work tree top of the folder it started in): a write or edit there, mutating git in another checkout, a command that isn't read-only run from outside it or naming the main checkout or a sibling worktree. It's a core guardrail, on in every mode and without the guardrails bundle, and answered once or for the child's session ([Guardrails](guardrails.md#a-delegate-childs-boundary)); the approval relays to the parent's user as any other.
|
|
201
207
|
- Limits: a child cannot `delegate` (depth 1; the tool refuses), and one session may have at most `session.max_children` (default `4`, env `SAMAGOTCHI_SESSION_MAX_CHILDREN`) children running at a time, counted from the session files (a finished or idle-exited child does not count; one whose worker is still starting does, for 15 s). A refused call creates no session. Nothing stops children with the parent: `chi sessions stop ID` does.
|
|
202
208
|
- The link is the session's `parent_id`, written before the worker starts, so a respawn keeps it (and `delegate: true`, which a fork's `parent_id` doesn't come with; a child saved before that field is known by its `system/delegated` memory); `GET /api/sessions` and `/api/sessions/:id` carry it, and `SessionManager.children_of` lists a parent's children.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "client_id"
|
|
3
4
|
require_relative "session"
|
|
4
5
|
require_relative "session_manager"
|
|
5
6
|
require_relative "bridge_client"
|
|
@@ -189,7 +190,7 @@ module Samagotchi
|
|
|
189
190
|
client.dismiss_question(id: qid)
|
|
190
191
|
else
|
|
191
192
|
client.answer(id: qid, selected: selected, freeform: options[:text],
|
|
192
|
-
client_id:
|
|
193
|
+
client_id: ClientId::CLI_ANSWER)
|
|
193
194
|
end
|
|
194
195
|
break unless reply.status == 408
|
|
195
196
|
end
|
|
@@ -17,12 +17,6 @@ module Samagotchi
|
|
|
17
17
|
# file from memory (and bumps updated_at) at every turn.
|
|
18
18
|
module ArchiveStore
|
|
19
19
|
FILE = "archived"
|
|
20
|
-
# Input a human typed: a web tab, a chi TUI, `chi send`, and nil (a
|
|
21
|
-
# worker's initial prompt; a web client may send none). Delegates,
|
|
22
|
-
# plugins and reminders are not (an allowlist, so a new automatic
|
|
23
|
-
# origin stays out).
|
|
24
|
-
USER_CLIENT_PREFIXES = %w[web: tui:].freeze
|
|
25
|
-
USER_CLIENT_IDS = ["cli:send"].freeze
|
|
26
20
|
|
|
27
21
|
# @return [Hash, nil] the marker (string keys), nil when none or unreadable
|
|
28
22
|
def self.read(session_dir)
|
|
@@ -57,15 +51,7 @@ module Samagotchi
|
|
|
57
51
|
write(session_id, { "unarchived_at" => Time.now.iso8601(3) }, state_dir: state_dir)
|
|
58
52
|
end
|
|
59
53
|
|
|
60
|
-
#
|
|
61
|
-
def self.user_input?(client_id)
|
|
62
|
-
return true if client_id.nil?
|
|
63
|
-
|
|
64
|
-
id = client_id.to_s
|
|
65
|
-
USER_CLIENT_IDS.include?(id) || USER_CLIENT_PREFIXES.any? { |prefix| id.start_with?(prefix) }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# A human's input came into the session: it is back in the lists. Its
|
|
54
|
+
# A human's input (ClientId.human?) came into the session: it is back in the lists. Its
|
|
69
55
|
# children stay archived. Never raises.
|
|
70
56
|
# @return [Boolean] whether it was archived
|
|
71
57
|
def self.user_input(session_id, state_dir:)
|
data/lib/samagotchi/bridge.rb
CHANGED
|
@@ -55,6 +55,8 @@ module Samagotchi
|
|
|
55
55
|
CANCEL_REASONS = %w[manual user ctrl_c].freeze
|
|
56
56
|
# How long #stop waits for requests it is answering (not open streams).
|
|
57
57
|
REQUEST_GRACE_SECONDS = 1.0
|
|
58
|
+
# How long #stop waits for each killed RelayWatcher thread to end.
|
|
59
|
+
RELAY_WATCHER_JOIN_SECONDS = 1.0
|
|
58
60
|
# The largest request body read (images travel as refs, never bytes).
|
|
59
61
|
MAX_BODY_BYTES = 1_000_000
|
|
60
62
|
# A request's deadline (see #handle_post_turn) that isn't epoch seconds.
|
|
@@ -198,7 +200,9 @@ module Samagotchi
|
|
|
198
200
|
await_answers(REQUEST_GRACE_SECONDS)
|
|
199
201
|
@connection_threads.each { |t| t.kill rescue nil }
|
|
200
202
|
@connection_threads.clear
|
|
201
|
-
|
|
203
|
+
# Its relay watchers (RelayWatcher) end with it.
|
|
204
|
+
watchers = @mutex.synchronize { (@relay_watchers || []).tap { @relay_watchers = [] } }
|
|
205
|
+
watchers.each { |thread| thread.kill.join(RELAY_WATCHER_JOIN_SECONDS) }
|
|
202
206
|
@accept_thread&.join(2)
|
|
203
207
|
remove_sidecar
|
|
204
208
|
nil
|
|
@@ -839,8 +843,9 @@ module Samagotchi
|
|
|
839
843
|
return [{}, 500, { error: "enqueue_failed", detail: "could not write turn input" }] unless enqueued
|
|
840
844
|
|
|
841
845
|
# Queued (outside the event lock): a running generation that has only
|
|
842
|
-
# been thinking for long is cut for it, unless a plugin
|
|
843
|
-
# image input never merges mid-turn, so
|
|
846
|
+
# been thinking for long is cut for it, unless a plugin or a client
|
|
847
|
+
# chi doesn't know sent it. An image input never merges mid-turn, so
|
|
848
|
+
# it never cuts.
|
|
844
849
|
@engine.cut_for_steer(Steer.source_for_client(client_id), epoch: epoch) if images.empty?
|
|
845
850
|
[{}, 202, { status: "accepted", enqueued_id: enqueued_id, session_id: @session_id }]
|
|
846
851
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coordinator
|
|
3
|
+
version: 0.1.2
|
|
4
|
+
scope: system
|
|
5
|
+
description: "Coordinate parallel work: the coordinator skill (split the work, one worktree and child session per task, check each report, ask before each merge), /coordinate <goal> and /children"
|
|
6
|
+
trust_level: reviewed
|
|
7
|
+
files:
|
|
8
|
+
skill_coordinator.md:
|
|
9
|
+
sha256: sha256:b21067c3f152c8dfd1b1f70001c6654ad1911616543a36b625727fb13d4ce3f6
|
|
10
|
+
description: "Coordinate parallel work: split it, delegate each task wait:false in its own worktree (cwd:), check each report, ask the user before each merge"
|
|
11
|
+
plugin:
|
|
12
|
+
file: plugin.rb
|
|
13
|
+
sha256: sha256:5173faeb6311984ad4f2a40b9052dcc577a90790fc5c5a8409b95d4f114e66a7
|
|
14
|
+
requires_chi: ">= 0.34.0"
|