1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
//! `litany stop <repo> <branch>` — SIGTERM per ARCH §2.9.
//!
//! **The cascade is the stop** (§2.9, bl-3114). One `litany stop`
//! signals the process group of the executor driving `<branch>` **and
//! of every descendant executor** — each one's provider adapter (`bz`)
//! and cooperating tool subprocesses die with it (§2.9 steps 1-2),
//! in-flight HTTP dropped — with a 5-second flush deadline before
//! SIGKILL (the same cascade §4.4 pins for adapters and §3.3 for tools,
//! applied to the harness). A kernel pgid is scoped to **one**
//! executor's own subprocesses, which are its limbs and not agents; the
//! reach across the agent boundary is the enumerated id-namespace walk
//! below, never a kernel-group side effect.
//!
//! The walk was opt-in until bl-3114 measured what that default cost: a
//! child's `final-response` return **revives its dispatcher** (§2.11
//! pin 2), so a bare stop on a conversation with live children reported
//! `ok` and the conversation kept spending. A stop that holds nothing it
//! names is worse than a refusal.
//!
//! **The id namespace is the walk.** Descent is encoded in the
//! hyphenated agent id (§2.3), so the children (and all deeper
//! descendants) of `<branch>` are exactly the inbox directories prefixed
//! `<branch>-` (single source of truth — the flat id namespace *is* the
//! tree, so one prefix scan covers every depth; no separate recursion).
//! [`collect_inbox_dirs`] enumerates that prefix and every descendant
//! executor's pgid folds into the one SIGTERM sweep. `--stop-children`
//! survives as an accepted, redundant spelling of it (`crate::cmd`).
//!
//! No on-disk cancel marker is written: per §2.9 the on-disk
//! signature of a stopped branch is the latest step's `response.json`
//! closed (`IN_CLOSE_WRITE`, §3.5) without a terminal brazen `end`
//! event. The kernel produces that signature for free when the
//! harness terminates without flushing — same way crashes and
//! external kills are indistinguishable on disk per §2.9.
//!
//! Pid discovery derives from `/proc/<pid>/fd/*` symlink targets
//! against the agent's **inbox directory** — the executor lock's
//! `flock` home (§2.11), held for the whole step loop. The target is
//! `<workspace>/inbox/<branch>/` (plus each sibling `inbox/<branch>-*/`
//! under `--stop-children`). No sidecar pid file: the open lock fd is
//! the *is-anyone-driving* signal the §2.11 lock probe and §3.5
//! classification already read — and, unlike the `response.json`
//! model-call fd, it is open across tool execution and between-step
//! gaps too, so a stop lands whenever an executor is alive (§2.9).
//!
//! **A discovered pgid is vetted twice before anything is signalled**
//! (§2.9). [`discover`] refuses a pgid that is not its holder's own pid
//! — a settled executor is a group leader, and a non-leader reading is
//! the group the executor *inherited from its spawner*. [`vet_targets`]
//! then refuses any pgid this process itself belongs to. The two are
//! one hazard seen from both ends: `kill(-pgid, SIGTERM)` against an
//! unsettled reading fells the operator's shell job in production, and
//! did fell the coverage runner under `make check`.
use crateINBOX_DIR;
use cratenotice;
use crate;
use io;
use ;
use Duration;
use Error;
pub use ;
pub use ;
pub use ;
/// SIGTERM-to-SIGKILL grace pinned by ARCH §2.9 (mirrors §4.4 / §3.3).
/// Tests pass a sub-second deadline; production uses this constant.
pub const STOP_DEADLINE: Duration = from_secs;
/// Polling cadence while waiting for SIGTERM'd processes to exit.
/// Small enough that user stop feels instant, large enough that an
/// idle wait costs nothing measurable.
const POLL_INTERVAL: Duration = from_millis;
/// Every way [`run`] can fail. Idempotent paths (no lock holder found,
/// already-stopped) are `Ok(())`, not errors — `litany stop` is a
/// fire-and-forget operation, not a transactional one.
/// Stop the harness driving `branch` **and its whole subagent subtree**.
///
/// 1. Validate `agents/<branch>` exists in `<workspace>/repo.git`.
/// 2. Collect the inbox directories to signal (§2.11 lock homes):
/// `inbox/<branch>/` and every `inbox/<branch>-*/` descendant
/// (hyphenated descent, §2.3). There is no narrower reach: leaving a
/// live child running leaves the conversation spending, because the
/// child's return revives its dispatcher (§2.9, §2.11 pin 2).
/// 3. Resolve each lock holder's pgid via the supplied [`PgidFinder`].
/// 4. SIGTERM the unique pgid set, wait `deadline`, SIGKILL leftovers.
///
/// Idempotent: a stopped branch (no lock holder found) returns `Ok(())`.
// Four of the arguments are injected trait objects (inspector, finder,
// signaler, git) — a test seam, not a data clump; bundling them buys
// nothing and obscures the stub wiring the tests depend on.
/// This process's own process group. `litany stop` never makes itself a
/// group leader, so this is whatever launched it: an operator's shell
/// job, or the test runner under `make check`.
// SAFETY: `getpgrp` takes no arguments, reads only the caller's own
// kernel state, and cannot fail.
/// Belt-and-braces last stop before the cascade: refuse to signal a
/// group the stop process itself belongs to (ARCH §2.9).
///
/// Discovery already refuses a pgid that is not its holder's own pid,
/// so reaching here with `own` in the set means that invariant was
/// somehow satisfied by a group we are standing in — impossible for a
/// detached executor, and catastrophic if signalled: `kill(-own, ...)`
/// reaches the invoking shell's job (production) or the coverage
/// runner (`make check`), which is exactly the observed failure this
/// guard closes off. Refuse the whole sweep rather than filter: a stop
/// that resolved a bogus target has not established what it *would*
/// have hit, and a half-performed kill is worse than none.
/// CLI entry point for `litany stop` (ARCH §3.4 — kept in the lib so
/// the bin file stays under the 300-line code cap and the wiring
/// itself is unit-testable). It takes no reach argument: every stop
/// walks the id namespace (§2.9). Production builds use the default
/// deps; tests exercise [`run`] directly with stubs.
/// Promote the calling process to a process-group leader so the
/// §2.9 cascade (`kill(-pgid, SIGTERM)`) reaches this executor's own
/// provider adapter and tool subprocesses without escaping into the
/// invoking shell or UI's process group — and, symmetrically, without
/// reaching *out* to a sibling or parent executor. Called at the top
/// of **every** driver: `litany prompt` (root) and `litany dispatch`
/// (child re-entry) alike. The old no-setpgid-for-child-harnesses rule
/// is retired (§2.9): a child executor takes its own pgid like a root,
/// so no kernel group ever crosses the agent boundary. A stop still
/// reaches every descendant — by the enumerated id-namespace walk
/// ([`collect_inbox_dirs`]), which signals one vetted pgid per executor
/// rather than whatever group a process happened to inherit.
/// Inner core for [`become_pgid_leader`]: parameterized on the
/// `setpgid` syscall so a unit test can exercise both branches
/// without mutating the test runner's pgid.
/// The inbox directory `inbox/<branch>/` — the home of the agent's own
/// executor lock (§2.11) — plus every `inbox/<branch>-*/` descendant
/// (hyphenated descent per §2.3). The branch name itself is the agent
/// id; descended subagent conversations have ids that prefix-match the
/// parent's (`<conv>-<sub>`), and the single `<branch>-` prefix scan
/// matches every depth of the subtree — the flat id namespace already
/// encodes the tree, so no recursion is needed. The walk is
/// unconditional (§2.9, bl-3114): a stop that left a live child running
/// left the conversation spending. Absent `inbox/` (an agent spawned but
/// whose executor has not yet opened a lock) yields an empty set — a
/// stop with nothing to signal, idempotently `Ok(())`.