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
278
279
280
//! `lernie stop <repo> <branch> [--stop-children]` — SIGTERM per ARCH §2.9.
//!
//! **Default: stop the one agent.** A bare `lernie stop` signals the
//! process group of the single executor driving `<branch>` — its
//! 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). The kernel pgid is scoped to
//! that **one** executor's own subprocesses: those are its limbs, not
//! agents. A still-running child on a descended branch is a *separate*
//! agent with its own pgid (each executor takes its own pgid at
//! startup, root and child alike, §2.9) and is **not** touched — it
//! outlives the parent and later deposits its result into the stopped
//! parent's inbox, which revives the parent (§2.11).
//!
//! **`--stop-children`: walk the id namespace.** The agent→agent cascade
//! is opt-in. 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). The flag enumerates that prefix and
//! folds each descendant executor's pgid into the one SIGTERM sweep.
//!
//! 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 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 — `lernie stop` is a
/// fire-and-forget operation, not a transactional one.
/// Stop the harness driving `branch`; optionally its subagent subtree.
///
/// 1. Validate `agents/<branch>` exists in `<workspace>/repo.git`.
/// 2. Collect the inbox directories to signal (§2.11 lock homes):
/// `inbox/<branch>/` always, plus every `inbox/<branch>-*/`
/// descendant (hyphenated descent, §2.3) **iff** `stop_children` —
/// the opt-in agent→agent cascade. Default touches only the one
/// agent; a live child keeps running and revives the parent on its
/// later deposit (§2.9, §2.11).
/// 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. `lernie 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 `lernie 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). `stop_children` is the `--stop-children`
/// flag (§2.9): `false` stops the one agent, `true` walks the id
/// namespace. 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: `lernie prompt` (root) and `lernie 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 a bare `lernie stop` on a parent cannot cross the agent boundary
/// into a running child — that cascade is now the opt-in CLI-level id
/// namespace walk of `--stop-children`, not a kernel-group side effect.
/// 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, **iff** `stop_children`, 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. Default (`stop_children == false`) returns only the one
/// agent's inbox, leaving live children untouched (§2.9). 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(())`.