samagotchi 0.33.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/README.md +4 -1
  4. data/docs/context.md +5 -0
  5. data/docs/guardrails.md +31 -1
  6. data/docs/hooks.md +1 -1
  7. data/docs/memory.md +17 -1
  8. data/docs/plugins.md +77 -2
  9. data/docs/releasing.md +1 -1
  10. data/docs/sessions.md +9 -3
  11. data/lib/samagotchi/bundles/coordinator/manifest.yml +14 -0
  12. data/lib/samagotchi/bundles/coordinator/plugin.rb +114 -0
  13. data/lib/samagotchi/bundles/coordinator/skill_coordinator.md +60 -0
  14. data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
  15. data/lib/samagotchi/bundles/mcp/manifest.yml +3 -3
  16. data/lib/samagotchi/bundles/mcp/plugin.rb +10 -11
  17. data/lib/samagotchi/bundles/system/delegated.md +1 -1
  18. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  19. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  20. data/lib/samagotchi/bundles/system/self_map.md +4 -2
  21. data/lib/samagotchi/children_status.rb +157 -0
  22. data/lib/samagotchi/cut_policy.rb +58 -0
  23. data/lib/samagotchi/engine.rb +16 -103
  24. data/lib/samagotchi/engine_peers.rb +30 -0
  25. data/lib/samagotchi/guardrail_wiring.rb +27 -2
  26. data/lib/samagotchi/guardrails/approvals.rb +7 -0
  27. data/lib/samagotchi/guardrails/child_boundary.rb +103 -0
  28. data/lib/samagotchi/guardrails/context.rb +32 -0
  29. data/lib/samagotchi/guardrails/shared_git.rb +124 -0
  30. data/lib/samagotchi/guardrails.rb +1 -0
  31. data/lib/samagotchi/kernel_loop.rb +9 -22
  32. data/lib/samagotchi/llm/chat_loop.rb +11 -22
  33. data/lib/samagotchi/memory_bundle/builder.rb +14 -1
  34. data/lib/samagotchi/memory_bundle/installer.rb +6 -2
  35. data/lib/samagotchi/memory_bundle/manifest.rb +26 -5
  36. data/lib/samagotchi/memory_bundle/provenance.rb +2 -1
  37. data/lib/samagotchi/memory_paths.rb +10 -0
  38. data/lib/samagotchi/plugin/api.rb +2 -2
  39. data/lib/samagotchi/plugin/sessions.rb +45 -1
  40. data/lib/samagotchi/process_group.rb +2 -5
  41. data/lib/samagotchi/steer_cut.rb +125 -0
  42. data/lib/samagotchi/system_prompt.rb +20 -8
  43. data/lib/samagotchi/tool_declarations.rb +9 -5
  44. data/lib/samagotchi/tools/builtins.rb +2 -2
  45. data/lib/samagotchi/tools/delegate.rb +103 -14
  46. data/lib/samagotchi/tools/delegate_wait.rb +55 -13
  47. data/lib/samagotchi/turn_note.rb +9 -1
  48. data/lib/samagotchi/version.rb +1 -1
  49. data/lib/samagotchi/worker.rb +7 -2
  50. metadata +10 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca8acdcb161f8d093ab1c860913dd3a1e826083bcfff0165039847e38a26c5ad
4
- data.tar.gz: 5c286ada590d84b793e95d9f2dec051132462469aace12c0faf1da0f1c4e3fdf
3
+ metadata.gz: c6f812c6128ae3ffaf91d9cc04716a77d351fb57793a7d9a4863e675f1f2337f
4
+ data.tar.gz: 25e7600fe6dc335b7d28320795a03ea9e8527185f6a0dd918787777458402f39
5
5
  SHA512:
6
- metadata.gz: 394a942740165c9ea457b3a3043a070ef9338a897c6eb41c387b1101e8426c8e21c938757868d87493216324d84441165a82601b796801be90feef5c8e6c7091
7
- data.tar.gz: 29d24ccdb0bd41d027ccdaf4b8c6e0a191d292b636fc3af1e27e994f30b1331192f59d3bf6aa5d858e78ec5d84d32d8760f2df03c8dace2feaad1ac87783c1c6
6
+ metadata.gz: 322e9f5a5b553f7ed1f2955307b8d5bd8c901a355ac3ffcdff6b3fcb5013b28011c7c330d9496486338c77d2c0f58e2854393d4cb40ec33d00ed2516a38bd066
7
+ data.tar.gz: f6048763f53d9b865858842acaf60587d5e57a365aaba759c3c85fcf69532227c4b201fe0f878da3e48191dd43856637dad29537203a3dfd7159756caac6f778
data/CHANGELOG.md CHANGED
@@ -8,6 +8,51 @@ 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
+
11
56
  ## [0.33.0] - 2026-10-06
12
57
 
13
58
  ### Changed
@@ -1663,7 +1708,8 @@ and long-lived sessions.
1663
1708
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
1664
1709
  hotkey panel that send selected text or the clipboard to your sessions.
1665
1710
 
1666
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.33.0...HEAD
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
1667
1713
  [0.33.0]: https://github.com/dm1try/samagotchi/compare/v0.32.0...v0.33.0
1668
1714
  [0.32.0]: https://github.com/dm1try/samagotchi/compare/v0.31.0...v0.32.0
1669
1715
  [0.31.0]: https://github.com/dm1try/samagotchi/compare/v0.30.0...v0.31.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
@@ -154,6 +154,11 @@ update note, whose last line becomes:
154
154
  Every UI labels the turn `context <name> changed`, and the web's
155
155
  notifications count it as a finished turn however short.
156
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
+
157
162
  ## GitHub PRs: the github-pr bundle
158
163
 
159
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
@@ -420,7 +420,7 @@ stripped of surrounding whitespace, except file text, an edit's
420
420
  | `list_sessions` | — | — | `cwd` |
421
421
  | `send_note` | `text` | — | `session` |
422
422
  | `context_read` | `name` | — | `offset`, `limit` |
423
- | `delegate` | `task` | — | `model`, `session`, `wait`, `timeout` |
423
+ | `delegate` | `task` | — | `model`, `session`, `cwd`, `wait`, `timeout` |
424
424
  | `delegate_result` | — | — | `session`, `timeout` |
425
425
  | `ask_user_question` | `question` | — | `question`, `options`, `header`, `multi_select`, `allow_freeform` |
426
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 read other sessions: see [Other sessions](#other-sessions-ctxsessions) |
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) # => {id:, title:, status:, parent_id:, running:, messages:}
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
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:`
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 (`wait: false` returns at once, so several children can run side by side, and the reply comes back by itself as a *delegate report*, below; `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.
185
- - `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.
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.
@@ -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"
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The coordinator bundle (docs/plugins.md, The coordinator bundle): chi as a
4
+ # coordinator of parallel work. Its skill_coordinator memory holds the steps
5
+ # (split the work, one worktree and a wait: false delegate per task, check
6
+ # each report, ask before each merge); this plugin adds two commands.
7
+ #
8
+ # /children [all] shows this session's children as a card, newest first:
9
+ # state, branch, the last reply and whether it was reported (ChildrenStatus
10
+ # through ctx.sessions.children), with a Stop button per running or waiting
11
+ # child and Refresh. `all` adds archived children. The card has one id, so
12
+ # Refresh and Stop replace it. It is read on demand: no thread, no polling.
13
+ #
14
+ # /children stop <id> stops one of this session's own children
15
+ # (ctx.sessions.stop, by session id) and shows the card again.
16
+ #
17
+ # /coordinate <goal> asks the model, in this session, to follow the skill
18
+ # for the goal. A REPL session (--no-shared) takes no messages: the request
19
+ # is shown for the user to send.
20
+ #
21
+ # A child is asked before it changes anything outside its worktree by chi
22
+ # itself (the core child-boundary guardrail), in every mode: nothing to set up.
23
+ class Plugin
24
+ CARD_ID = "children"
25
+ # A card takes 6 actions: Refresh and up to 5 Stops.
26
+ MAX_STOPS = 5
27
+ STOPPABLE = %w[running waiting].freeze
28
+ TEXT_CHARS = 80
29
+ USAGE = "usage: /children [all] | /children stop <id>"
30
+ COORDINATE_USAGE = "usage: /coordinate <goal> — chi splits it into tasks, one child session and worktree each"
31
+
32
+ def initialize(_settings = {}); end
33
+
34
+ def register(chi)
35
+ chi.command "/children", "this session's child sessions: state, branch, last reply; all, stop <id>",
36
+ anytime: true do |args, ctx|
37
+ children_command(args.to_s.strip, ctx)
38
+ end
39
+ chi.command "/coordinate", "run work in parallel: chi splits <goal> into tasks, each in a child session and worktree",
40
+ anytime: true do |args, ctx|
41
+ coordinate(args.to_s.strip, ctx)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def children_command(args, ctx)
48
+ verb, rest = args.split(/\s+/, 2)
49
+ case verb
50
+ when nil then show(ctx, all: false)
51
+ when "all" then rest.to_s.empty? ? show(ctx, all: true) : USAGE
52
+ when "stop" then rest.to_s.strip.empty? ? USAGE : stop(rest.strip, ctx)
53
+ else USAGE
54
+ end
55
+ end
56
+
57
+ # The card, made or replaced; nil, so the command adds no text of its own.
58
+ def show(ctx, all:, note: nil)
59
+ children = ctx.sessions.children(all: all)
60
+ title = "children of #{ctx.session_id.to_s[0, 8]}"
61
+ title += " (#{children.size})" unless children.empty?
62
+ body = children.empty? ? "no children" : children.map { |child| line(child) }.join("\n")
63
+ body = "#{note}\n\n#{body}" if note
64
+ refresh = all ? "/children all" : "/children"
65
+ stops = children.select { |child| STOPPABLE.include?(child[:state]) }.first(MAX_STOPS)
66
+ .map { |child| { label: "Stop #{child[:short_id]}", command: "/children stop #{child[:short_id]}" } }
67
+ ctx.card(id: CARD_ID, title: title, body: body, actions: [{ label: "Refresh", command: refresh }, *stops])
68
+ nil
69
+ end
70
+
71
+ # - `ab12cd34` · running · fix/flaky · "fix the flaky spec…"
72
+ # - `9a8b7c6d` · done · feat/x · reported · "All 12 specs pass…"
73
+ # - `77aa66bb` · waiting (approval) · open it: chi --attach 77aa66bb
74
+ def line(child)
75
+ parts = ["`#{child[:short_id]}`", child[:waiting] ? "waiting (#{child[:waiting]})" : child[:state]]
76
+ parts << child[:branch] if child[:branch]
77
+ parts << "fork" unless child[:delegate]
78
+ parts << "archived" if child[:archived]
79
+ if child[:waiting]
80
+ parts << "open it: chi --attach #{child[:short_id]}"
81
+ elsif child[:last_reply]
82
+ parts << (child[:reported] ? "reported" : "not reported yet")
83
+ parts << quote(child[:last_reply])
84
+ elsif !child[:title].to_s.strip.empty?
85
+ parts << quote(child[:title])
86
+ end
87
+ "- #{parts.join(" · ")}"
88
+ end
89
+
90
+ def stop(id, ctx)
91
+ stopped = ctx.sessions.stop(id)
92
+ show(ctx, all: false, note: "stopped #{stopped[0, 8]}")
93
+ rescue Samagotchi::Plugin::Sessions::Error => e
94
+ "/children stop: #{e.message}"
95
+ end
96
+
97
+ def coordinate(goal, ctx)
98
+ return COORDINATE_USAGE if goal.empty?
99
+
100
+ request = "Read the skill_coordinator memory and follow it for this goal:\n\n#{goal}"
101
+ begin
102
+ ctx.sessions.send(ctx.session_id, request)
103
+ rescue Samagotchi::Plugin::Sessions::Error => e
104
+ return "/coordinate: #{e.message}. Send this yourself:\n\n#{request}"
105
+ end
106
+ "asked chi to coordinate it; a running turn gets it at its next step"
107
+ end
108
+
109
+ def quote(text)
110
+ line = text.to_s.gsub(/\s+/, " ").strip
111
+ line = "#{line[0, TEXT_CHARS - 1]}…" if line.length > TEXT_CHARS
112
+ "\"#{line}\""
113
+ end
114
+ end
@@ -0,0 +1,60 @@
1
+ # Skill: coordinator
2
+
3
+ Use when the user hands over work with several independent parts, or asks to "run it in parallel",
4
+ "delegate", "coordinate". The user decides; you do the legwork and keep them in the loop briefly.
5
+
6
+ ## Steps
7
+ 1. Find the repository's default branch, <default> below: `git symbolic-ref --short
8
+ refs/remotes/origin/HEAD` (drop the `origin/`), else `main` or `master`, whichever exists; ask the
9
+ user if unsure. Split the work into independent tasks (at most session.max_children at once,
10
+ default 4). Tell the user the split in a few lines and go on unless they object.
11
+ 2. For each task: from your own folder, `git worktree add ../<repo>-<task> -b <type>/<task>` with
12
+ execute; then `delegate` with `wait: false`, `cwd:` that worktree's absolute path, and a
13
+ self-contained task. The task starts with "Work only in <absolute worktree path>: every cd, read,
14
+ write, test and commit goes there, never the project root" and says what to report (the result,
15
+ evidence: commands and what they printed, what isn't done) and "commit on the branch, don't merge,
16
+ don't push".
17
+ 3. Tell the user what started (one line per child: its id, branch and task) and end your turn or keep
18
+ talking. Don't call delegate_result: chi brings each child's reply as a delegate report, also when
19
+ you are idle.
20
+ 4. On each report, check it before telling the user: `git -C <worktree> log --oneline <default>..` and
21
+ `git diff --stat <default>...<branch>`; run the tests the child names (execute with cwd: the worktree).
22
+ Say the result in one or two lines: done and verified / done with doubts / failed.
23
+ 5. Then one of: a follow-up (`delegate session: <id>`, narrower, `wait: false`; then end your turn:
24
+ its report comes by itself, so don't wait for it with delegate_result or task_wait), stop it
25
+ (`chi sessions stop <session id>` with execute; task_stop is for task ids, not children; the user
26
+ can use /children), or tell the user it is ready to merge.
27
+ 6. Merging is the user's call: ask (ask_user_question) per branch, "Merge <branch> into <default>?".
28
+ Before merging, in your own folder: `git branch --show-current` must print <default> (if it
29
+ doesn't, stop and ask the user; don't switch branches yourself); then check <default>:
30
+ `git log --oneline -3 <default>` and `git merge-base --is-ancestor <branch> <default>`; if the branch is already in, say so and don't merge
31
+ again. Merge with `git merge --ff-only <branch>` yourself, in your own folder. If that fails, ask the
32
+ child to rebase its branch on <default> (a follow-up that says "rebase only, don't merge") or ask the
33
+ user; don't rebase it from here (the branch is checked out in the child's worktree). Children
34
+ never merge. Never push unless asked.
35
+ 7. Clean up only a branch that was merged or that the user said to drop: stop its child
36
+ (`chi sessions stop <session id>`), then `git worktree remove ../<repo>-<task>`, then
37
+ `git branch -d <branch>` (in that order: a branch checked out in a worktree can't be deleted). If
38
+ `-d` refuses, say why to the user; never `-D` unless they say so. Remove only worktrees and
39
+ branches you created in this session. Every other branch stays as it is, its child, worktree and
40
+ branch too (the user said "keep it", or didn't say): leave them and tell the user they remain.
41
+ 8. When all are done, report: what merged, what didn't and why, what you cleaned up and what remains,
42
+ follow-ups found but not fixed. Report only what the commands' output showed: a step you planned
43
+ but didn't run, or whose output you didn't see, is not done; say so.
44
+
45
+ ## Gotchas
46
+ - Children are asked before changing anything outside their worktree (your checkout, a sibling's):
47
+ the user approves it on the child's card. Still check where commits landed (step 4).
48
+ - Never kill processes by a pid from a file or an old note; stop children with `chi sessions stop
49
+ <session id>` (execute) and background tasks with task_stop (task ids only: it doesn't stop a child).
50
+ - A child's tool approvals go to the user on the child's card; tell the user when a report says a
51
+ child waits for one.
52
+ - A report is the child's claim: verify it (step 4) before telling the user it is done, and check
53
+ where its commits landed: `git log --oneline <default>..<branch>` has them and `git status` in your own
54
+ folder is clean. A child that worked in your folder instead of its worktree: tell the user.
55
+ - Don't send a follow-up to a child that is still running; wait for its report.
56
+ - Don't start more children than the user asked for.
57
+
58
+ ## Changelog
59
+ - 2026-10-06 created (coordinator bundle 0.1.0)
60
+ - 2026-10-06 clean up only merged or dropped branches; report only what commands showed (0.1.2)
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: dev
3
- version: 0.2.0
3
+ version: 0.3.0
4
4
  scope: system
5
5
  description: "Developer tools"
6
6
  trust_level: reviewed
7
- requires_chi: ">= 0.32.0"
7
+ requires_chi: ">= 0.34.0"
8
8
  includes:
9
9
  - known-names
10
10
  - mcp
@@ -12,3 +12,4 @@ includes:
12
12
  - skills
13
13
  - source-links
14
14
  - github-pr
15
+ - coordinator
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: mcp
3
- version: 0.4.4
3
+ version: 0.4.5
4
4
  scope: system
5
5
  description: "Tools from MCP servers (stdio): each server in config.yml bundles: mcp: servers: runs for the session, its tools are mcp_<server>_<tool>; /mcp lists them"
6
6
  trust_level: reviewed
7
7
  files: {}
8
8
  plugin:
9
9
  file: plugin.rb
10
- sha256: sha256:05543038e2950a5391655184f57a1028133eda8ca05f9e46e5cd2eddc3e9e608
11
- requires_chi: ">= 0.8.0"
10
+ sha256: sha256:1ebedc86cd21c918766c794015b6bd16c397c0f7e2734d939d8310d446b8a9a1
11
+ requires_chi: ">= 0.33.0"
@@ -31,6 +31,7 @@
31
31
  require "digest"
32
32
  require "json"
33
33
  require "open3"
34
+ require "samagotchi/process_group"
34
35
  require "time"
35
36
  require "shellwords"
36
37
  require "tmpdir"
@@ -59,6 +60,9 @@ class Plugin
59
60
  class Dead < Error; end
60
61
 
61
62
  POLL_SECONDS = 0.1
63
+ # How long close waits for the server after closing its stdin, after
64
+ # TERM, and after KILL.
65
+ STOP_GRACE_SECONDS = 1
62
66
 
63
67
  attr_reader :pid
64
68
 
@@ -136,14 +140,15 @@ class Plugin
136
140
  end
137
141
 
138
142
  # End the process: stdin closed first (most servers leave then), then
139
- # TERM and KILL to its process group.
143
+ # TERM and KILL to its process group (chi's ProcessGroup), a second's
144
+ # grace each.
140
145
  def close
141
146
  @mutex.synchronize { @closing = true }
142
147
  [@stdin].each { |io| io.close unless io.closed? }
143
- unless @wait.join(1)
144
- signal("TERM")
145
- signal("KILL") unless @wait.join(1)
146
- @wait.join(1)
148
+ unless @wait.join(STOP_GRACE_SECONDS)
149
+ Samagotchi::ProcessGroup.stop(@pid, grace: STOP_GRACE_SECONDS, poll: STOP_GRACE_SECONDS,
150
+ stopped: -> { !@wait.alive? }, wait: ->(seconds) { @wait.join(seconds) })
151
+ @wait.join(STOP_GRACE_SECONDS)
147
152
  end
148
153
  [@stdout, @stderr].each { |io| io.close unless io.closed? }
149
154
  [@reader, @err_reader].each { |t| t.join(1) }
@@ -234,12 +239,6 @@ class Plugin
234
239
  @on_exit&.call(reason) unless closing
235
240
  end
236
241
 
237
- def signal(name)
238
- Process.kill(name, -@pid)
239
- rescue Errno::ESRCH, Errno::EPERM
240
- nil
241
- end
242
-
243
242
  def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
244
243
  end
245
244