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
//! **Running one invocation** (yog's `docs/REMOTE.md` §5.2, §5.3; DESIGN §3.4,
//! §3.5): the far end of the routing leg, where a tool actually happens.
//!
//! **It is a local tool contract, unchanged**: the `tool_use.input` JSON on
//! stdin, bytes on stdout, the exit code the verdict. So a foot's tool
//! executable is the same kind of program the engine's own pool tools are, and
//! the capture that comes back is the same three facts. `command` is an argv,
//! spawned directly — no shell, and no interpolation of the invocation's input
//! into it, because a shell would make the declared `input_schema` advisory and
//! turn an operator's config file into a command-injection surface for anything
//! a model can type.
//!
//! **Every outcome is a capture.** A tool that ran, a tool that overran, a name
//! this box does not carry and a command that could not be spawned at all are
//! four exit codes and four sentences, never four kinds of failure — because an
//! invocation that earned no answer would be the hang the whole leg exists to
//! exclude, and the model at the far end reads a refusal exactly as it reads a
//! tool that failed, which is what it is.
//!
//! **Bytes become text here, once.** A capture ends as a model's tool result
//! and a model's message is text, so the transcode happens at the one place
//! bytes stop being bytes and nothing downstream carries an encoding case. A
//! tool whose output is not UTF-8 loses exactly the bytes no `String` can name.
//!
//! **Two deadlines, measuring different things** (REMOTE §5.3). This box's own
//! bound terminates the child and answers with a sentence; the asking side's
//! longer patience stands behind it for the case where this whole process went
//! away. Neither is a knob: an engine that has not answered is down, and a tool
//! that has not answered is working.
//!
//! **Containment honesty** (DESIGN §3.5): what thrall enforces locally is the
//! operator's config — which names may run at all, with which argv and in which
//! directory — plus this deadline. It is not a sandbox and does not pretend to
//! be one. The child runs as this process's user, with this process's
//! environment less the scrub the spawn boundary performs. **The deadline
//! reaches what the child started** (bl-a78e): the child leads a process group
//! of its own and the cascade signals that group, so a tool that backgrounds a
//! helper and then hangs does not leave the helper behind. What it does not
//! reach is a descendant that left the group under its own hand — `setsid` and
//! `setpgid` are the child's to call, and a process outside the group is
//! outside the signal. Whoever administers the box is still the party that can
//! contain a foot, and the design must not imply otherwise.
//!
//! **The deadline bounds the capture and not merely the child** (bl-6c14).
//! That distinction is the whole of `child` and `pipes` below: a pipe outlives
//! the process it was given to, so a helper the tool backgrounded used to hold
//! this end reading after the tool had exited — an invocation that earned no
//! answer, which is the one thing this file's second paragraph says cannot
//! happen.
//!
//! **A capture is bounded in size as well as in time** (bl-6028). The wire
//! carries one frame and will not carry an unbounded one, so an output past
//! [`CAPTURE_LIMIT`] is truncated here, where the bytes are, and the capture
//! says what was dropped — the same in-band shape a deadline earns. A foot that
//! let the framing refuse the completion instead lost the invocation and the
//! channel, for a fact it was holding in its own hand.
//!
//! **This file is the dispatch.** Which entry, whose directory, and the three
//! facts that come back; running the child is `child`, and reading it without
//! blocking is `pipes`.
use Duration;
use crate;
use crate;
/// One child, from the fork to the capture.
/// The child's three pipes, pumped without blocking.
/// **This box's own bound on one tool.** It is not a knob: a tool that has not
/// answered is working, and the asking side's longer patience is what covers
/// the case where this whole process went away.
pub const DEADLINE: Duration = from_mins;
/// **What one stream of a capture may grow to** (bl-6028), and the only thing
/// about a tool's output this box enforces.
///
/// A capture ends as one JSON frame, and the wire will not carry a frame past
/// [`MAX_FRAME`](crate::channel::frame::MAX_FRAME) — so a foot that read a
/// tool's output without a bound had two failures waiting: an allocation as
/// large as whatever the tool cared to print, and then a completion the framing
/// refuses, which every layer above it reads as a dead channel. Neither is a
/// thing to answer at the wire. **The size is a local fact and it is knowable
/// here**, at the one place holding the bytes, so it is decided here and the
/// capture says what it dropped.
///
/// **It is derived from the frame and never typed beside it.** Two streams, and
/// JSON can spend up to six bytes on one input byte (a control character
/// becomes `\u00XX`), so a capture at this bound encodes to at most twelve
/// sixteenths of a frame whatever a tool prints — the rest is the gesture's
/// envelope and the margin. A megabyte of tool output is already far past what
/// any model reads; the bound is generous in the only units that matter.
pub const CAPTURE_LIMIT: usize = crateMAX_FRAME / 16;
/// The verdict a child that outran its deadline earns — the shell's own
/// convention for `timeout`, so an operator reading a transcript recognizes it.
pub const TIMED_OUT: i32 = 124;
/// The verdict a name this box cannot run earns — the shell's own convention
/// for "command not found", and REMOTE §5's *"a client refuses a tool it no
/// longer carries"* answered at the end that actually knows.
pub const NO_SUCH_TOOL: i32 = 127;
/// What a child that a signal ended, and that this foot did not signal, earns.
///
/// The shell's convention is `128 + N`, and `N` is not readable without a
/// platform extension this crate does not take — so this says *a signal ended
/// it* and does not pretend to say which. It is the honest half of a
/// convention rather than a guess at the other half.
pub const SIGNALLED: i32 = 128;
/// **The hand-off the loop takes** (`run::Handoff`): run one invocation against
/// this box's set, under this box's own deadline.
/// [`handoff`] with the deadline named, so the suite can watch the cascade
/// without waiting two minutes for it.
/// **The worktree lane's gate on this box** (REMOTE §5.4, bl-36f7): an
/// invocation carrying a working directory runs there only when the
/// operator's own document says this entry may (`"subject_cwd": true`) —
/// a box must opt in to executing at a path a caller names, because the
/// directory is one of the three things thrall enforces (DESIGN §3.5) and
/// it stays the operator's unless the operator says otherwise. Every
/// refusal names the remedy in the operator's terms, and all are the
/// same in-band three facts every other refusal here is.
///
/// **A relative directory is refused before it is looked for** (bl-3c93). It
/// would resolve against whatever directory this process was started in, which
/// is the hazard the operator's own `cwd` is refused for at the read
/// (`config::one`) and the one `paths::root` refuses for the data root — and a
/// relative path that happened to exist there would run the tool somewhere
/// nobody named, silently, which is worse than a refusal.
/// The three facts, transcoded once.
/// A refusal this box makes about itself, in the same three facts — so the far
/// end reads it exactly as it reads a tool that failed, which is what it is.
///
/// `pub(crate)` because the bridge ([`mcp`](crate::mcp)) makes the same kind of
/// refusal about the same kind of event, one register down: it runs AS a tool
/// and its failures are the tool's. Two spellings of "this box says no, in the
/// three facts" would drift, and the sentence's `thrall:` prefix is the thing
/// that must not.
pub