mcp-yieldshell 0.6.0__tar.gz → 1.1.0__tar.gz

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 (27) hide show
  1. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/PKG-INFO +122 -50
  2. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/README.md +120 -48
  3. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/pyproject.toml +3 -3
  4. mcp_yieldshell-1.1.0/src/mcp_yieldshell/execution/__init__.py +1 -0
  5. {mcp_yieldshell-0.6.0/src/mcp_yieldshell/process → mcp_yieldshell-1.1.0/src/mcp_yieldshell/execution}/managed.py +4 -4
  6. {mcp_yieldshell-0.6.0/src/mcp_yieldshell/process → mcp_yieldshell-1.1.0/src/mcp_yieldshell/execution}/manager.py +174 -161
  7. {mcp_yieldshell-0.6.0/src/mcp_yieldshell/process → mcp_yieldshell-1.1.0/src/mcp_yieldshell/execution}/ring_buffer.py +1 -1
  8. {mcp_yieldshell-0.6.0/src/mcp_yieldshell/process → mcp_yieldshell-1.1.0/src/mcp_yieldshell/execution}/spawn.py +1 -1
  9. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/server.py +50 -32
  10. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/types.py +7 -7
  11. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_integration.py +440 -304
  12. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_lifecycle_hardening.py +216 -214
  13. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_protocol.py +54 -13
  14. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_ring_buffer.py +1 -1
  15. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_side_effects.py +102 -100
  16. mcp_yieldshell-0.6.0/src/mcp_yieldshell/process/__init__.py +0 -1
  17. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/.gitignore +0 -0
  18. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/__init__.py +0 -0
  19. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/__main__.py +0 -0
  20. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/config.py +0 -0
  21. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/policy.py +0 -0
  22. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/security.py +0 -0
  23. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/src/mcp_yieldshell/side_effects.py +0 -0
  24. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/__init__.py +0 -0
  25. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_config.py +0 -0
  26. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_release.py +0 -0
  27. {mcp_yieldshell-0.6.0 → mcp_yieldshell-1.1.0}/tests/test_security.py +0 -0
@@ -1,31 +1,37 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-yieldshell
3
- Version: 0.6.0
4
- Summary: A drop-in shell MCP that auto-yields long-running commands into managed background processes.
3
+ Version: 1.1.0
4
+ Summary: A drop-in shell MCP that auto-yields long-running commands into managed background executions.
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: mcp<2,>=1.9.0
7
7
  Description-Content-Type: text/markdown
8
8
 
9
9
  # YieldShell MCP
10
10
 
11
- A drop-in shell MCP server that auto-yields long-running commands into managed background processes.
11
+ A drop-in shell MCP server that auto-yields long-running commands into managed
12
+ background executions.
12
13
 
13
14
  ## Why Auto-Yielding?
14
15
 
15
16
  Most shell tools present a frustrating choice: either block the LLM agent until the command finishes, or force the agent to decide upfront that a command should run in the background.
16
17
 
17
- **YieldShell MCP** solves this by keeping normal foreground semantics for fast commands, then automatically promoting long-running commands into managed background processes after a delay (`yield_ms`, default: 30 seconds).
18
+ **YieldShell MCP** solves this by keeping normal foreground semantics for fast
19
+ commands, then automatically promoting long-running commands into managed
20
+ background executions after a delay (`yield_ms`, default: 30 seconds).
18
21
 
19
22
  ```mermaid
20
23
  graph TD
21
- A[exec_command] --> B["Wait for yield_ms (default: 30s)"]
22
- B --> C{Is process still running?}
23
- C -->|Yes| D["backgrounded<br>Returns process_id"]
24
+ A[execute] --> B["Wait for yield_ms (default: 30s)"]
25
+ B --> C{Is execution still running?}
26
+ C -->|Yes| D["backgrounded<br>Returns execution_id"]
24
27
  C -->|No| E["completed<br>Returns full output"]
25
28
  ```
26
29
 
27
30
  - **Fast Commands** (e.g., `echo hello`, `ls`): Complete instantly, returning the output immediately.
28
- - **Long-Running Commands** (e.g., `npm run dev`, `docker build`, `sleep 60`): Automatically yield control back to the agent with a `process_id` and a snapshot of initial output, letting the agent decide when to `read`, `wait`, or `stop` the process.
31
+ - **Long-Running Commands** (e.g., `npm run dev`, `docker build`, `sleep 60`):
32
+ Automatically yield control back to the agent with an `execution_id` and a
33
+ snapshot of initial output, letting the agent decide when to `read`, `wait`,
34
+ or `stop` the execution.
29
35
 
30
36
  ---
31
37
 
@@ -135,12 +141,19 @@ Add to your OpenCode MCP settings:
135
141
  }
136
142
  ```
137
143
 
144
+ These client command and environment snippets are unchanged in 1.0; the
145
+ breaking changes affect tool calls and response fields, not server launch
146
+ configuration.
147
+
138
148
  ---
139
149
 
140
150
  ## Tool Reference
141
151
 
142
- ### `exec`
143
- Execute a shell command. If the command runs longer than `yield_ms`, it yields a `process_id` and runs in the background.
152
+ ### `execute`
153
+ Execute a shell command. If it runs longer than `yield_ms`, the execution moves
154
+ to the background. Addressable responses include an opaque `execution_id`;
155
+ clients must pass it through unchanged to lifecycle tools. `process_id` is the
156
+ numeric OS PID of the initial shell and is not a lifecycle handle.
144
157
 
145
158
  * **Parameters**:
146
159
  * `command` (string, **required**): The command string to execute in the shell.
@@ -152,7 +165,7 @@ Execute a shell command. If the command runs longer than `yield_ms`, it yields a
152
165
  * `shell` (string, optional): Shell executable used to run the command. Defaults to the platform shell. Explicit shells are checked by the same allow/deny command policy.
153
166
  * `stdin` (string, optional): Initial text input written to standard input immediately after spawning.
154
167
  * `close_stdin` (boolean, default: `true`): Close standard input after the initial input is written. Set to `false` when follow-up `write` calls are expected.
155
- * `name` (string, optional): A human-readable label/name to identify this process.
168
+ * `name` (string, optional): A human-readable label for this execution.
156
169
  * `yield_ms` (integer, optional): Milliseconds to wait before yielding execution to background. Both omitted and explicit values are clamped to the lesser of `YIELDSHELL_MAX_YIELD_MS` and the transport-safe 55,000ms ceiling. Defaults to `YIELDSHELL_DEFAULT_YIELD_MS` (30,000ms).
157
170
  * `timeout_ms` (integer, optional): Total execution runtime limit in milliseconds. Process is terminated if it runs longer than this. Defaults to `YIELDSHELL_DEFAULT_TIMEOUT_MS` (3,600,000ms). Pass `0` explicitly for unlimited execution.
158
171
  * `max_output_bytes` (integer, optional): Maximum bytes returned in total across both response streams. Subject to the `YIELDSHELL_MAX_OUTPUT_BYTES` cap. This bounds the response only — retention is controlled separately by `YIELDSHELL_MAX_BUFFER_BYTES`, so output withheld here stays readable via `read`.
@@ -175,37 +188,52 @@ Execute a shell command. If the command runs longer than `yield_ms`, it yields a
175
188
  * Inline code execution: prefer writing the content to a reviewable workspace file (for example `scripts/migrate.sql` or `tools/build.sh`) and run it in a small, inspectable step. Declaring `side_effects=["RUNS_INLINE_CODE"]` is rejected under the default policy; operators can clear that default with `MCP_YIELDSHELL_BLOCKED_SIDE_EFFECTS=,`.
176
189
 
177
190
  * **Output Statuses**:
178
- * Every status returns the cursor fields described under [Byte Cursors & Incremental Reads](#byte-cursors--incremental-reads): `start_seq`, `next_seq`, `latest_seq`, `capped`, and `evicted`. The inline snapshot advances the process's server-side read cursor, so a following `read` or `wait` continues after it rather than repeating it.
179
- * `completed`: Process finished within `yield_ms`. Returns exit code, stdout, and stderr. If output was withheld and terminal-process retention is enabled, it also returns `process_id` so the remainder can be inspected with `read`.
180
- * `backgrounded`: Process auto-yielded. Returns `process_id`, `pid`, a snapshot of initial stdout/stderr, `duration_ms`, the cursor fields, and a `message` string describing that the process is running in the background.
181
- * `timed_out`: Process exceeded `timeout_ms` and was terminated.
182
- * `stopped`: Process was explicitly terminated.
183
- * `failed_to_start`: Command could not be spawned (e.g., bad directory or policy violation).
191
+ * Every status returns the cursor fields described under [Byte Cursors & Incremental Reads](#byte-cursors--incremental-reads): `start_seq`, `next_seq`, `latest_seq`, `capped`, and `evicted`. The inline snapshot advances the execution's server-side read cursor, so a following `read` or `wait` continues after it rather than repeating it.
192
+ * Every retained result (`completed`, `backgrounded`, `timed_out`, `stopped`, or `failed`) returns `execution_id` and, when available, the initial shell's numeric `process_id`.
193
+ * `completed`: Execution finished within `yield_ms`. Returns exit code, stdout, and stderr; retained output remains addressable with `execution_id`.
194
+ * `backgrounded`: Execution auto-yielded. Returns both identifiers, a snapshot of initial stdout/stderr, `duration_ms`, cursor fields, and a lifecycle-tool hint.
195
+ * `timed_out`: Execution exceeded `timeout_ms` and its process group was terminated.
196
+ * `stopped`: Execution was explicitly terminated.
197
+ * `failed_to_start`: The command could not create a managed execution (for example, because of a bad directory or policy violation). This outcome is not addressable and may omit both identifiers.
184
198
  * `failed`: An internal execution error occurred.
185
- * If initial `stdin` delivery fails, responses for the retained process include
199
+ * If initial `stdin` delivery fails, responses for the retained execution include
186
200
  `stdin_error` with the transport error. Initial delivery runs as tracked
187
201
  background work so pipe backpressure does not delay auto-yielding.
188
202
 
203
+ Example background response (cursor/output fields abbreviated):
204
+
205
+ ```json
206
+ {
207
+ "status": "backgrounded",
208
+ "execution_id": "a97e81c9034f",
209
+ "process_id": 42137,
210
+ "stdout": "",
211
+ "stderr": "",
212
+ "next_seq": 1,
213
+ "message": "Execution is running in the background. Use read, wait, or stop with execution_id."
214
+ }
215
+ ```
216
+
189
217
  ### `read`
190
- Read stdout and/or stderr output from a running or completed background process.
218
+ Read stdout and/or stderr from a running or completed execution.
191
219
 
192
220
  * **Parameters**:
193
- * `process_id` (string, **required**): Unique identifier of the process.
221
+ * `execution_id` (string, **required**): Opaque execution handle returned by `execute`; pass it through unchanged.
194
222
  * `since_seq` (integer, optional): Byte-position cursor returned as `next_seq` by the previous read. Enables lossless incremental log polling. When omitted, the read resumes from the server-side cursor instead (see [Byte Cursors & Incremental Reads](#byte-cursors--incremental-reads)); pass `since_seq=1` to re-read from the beginning.
195
223
  * `max_output_bytes` (integer, optional): Clamps total output returned across the selected streams. Defaults to the server cap.
196
224
  * `streams` (string, default: `"both"`): The streams to read. Options: `"both"`, `"stdout"`, or `"stderr"`.
197
225
  * `tail_lines` (integer, optional): Return only the newest N lines instead of reading forward. Useful for monitoring a noisy build or test run without paging through it. Mutually exclusive with `since_seq`; must be positive. If the requested tail exceeds `max_output_bytes`, the newest bytes are kept rather than the oldest.
198
226
 
199
227
  * **Returns**:
200
- * `process_id`, `status`, `exit_code`, `signal`, and `stdin_error` when initial input delivery failed. `stdout` and `stderr` text are included based on the `streams` filter — `"both"` includes both, `"stdout"` includes only `stdout`, and `"stderr"` includes only `stderr`.
228
+ * `execution_id`, `status`, `exit_code`, `signal`, and `stdin_error` when initial input delivery failed. `stdout` and `stderr` text are included based on the `streams` filter — `"both"` includes both, `"stdout"` includes only `stdout`, and `"stderr"` includes only `stderr`.
201
229
  * Cursor fields: `start_seq` (position of the first byte returned), `next_seq` (cursor to use in the next `since_seq` read), and `latest_seq` (how far output has advanced overall — when it exceeds `next_seq`, more is waiting).
202
230
  * Withheld-output flags: `capped` means more output is available and reading again continues from `next_seq`; `evicted` means output fell out of the ring buffer before it was read and cannot be recovered. When either is set, a `hint` field names the concrete follow-up call.
203
231
 
204
232
  ### `write`
205
- Write text input to the standard input (`stdin`) of a running process.
233
+ Write text input to the standard input (`stdin`) of a running execution.
206
234
 
207
235
  * **Parameters**:
208
- * `process_id` (string, **required**): Unique identifier of the process.
236
+ * `execution_id` (string, **required**): Opaque execution handle returned by `execute`; pass it through unchanged.
209
237
  * `input` (string, **required**): Text input to write.
210
238
  * `newline` (boolean, default: `false`): If `true`, appends `\n` to the input.
211
239
  * `close_stdin` (boolean, default: `false`): Close standard input after this write, delivering EOF to the process.
@@ -213,11 +241,11 @@ Write text input to the standard input (`stdin`) of a running process.
213
241
  request ceiling and return `ok: false` with an error.
214
242
 
215
243
  ### `wait`
216
- Block execution until the process exits or the wait timeout expires. This allows the LLM to pause and await completion without spawning a new execution loop.
244
+ Block until an execution exits or the wait timeout expires.
217
245
 
218
246
  * **Parameters**:
219
- * `process_id` (string, **required**): Unique identifier of the process.
220
- * `timeout_ms` (integer, default: `55000`): Maximum time to wait. Never terminates the process.
247
+ * `execution_id` (string, **required**): Opaque execution handle returned by `execute`; pass it through unchanged.
248
+ * `timeout_ms` (integer, default: `55000`): Maximum time to wait. Never stops the execution.
221
249
  * `max_output_bytes` (integer, optional): Maximum total output bytes to return across stdout and stderr.
222
250
  * `since_seq` (integer, optional): Byte-position cursor from a previous response. When omitted, `wait` resumes from the server-side cursor, so polling in a loop returns each byte exactly once without any cursor bookkeeping.
223
251
  * `tail_lines` (integer, optional): Return only the newest N lines. Mutually exclusive with `since_seq`.
@@ -227,7 +255,7 @@ Block execution until the process exits or the wait timeout expires. This allows
227
255
  * `waited_ms`: time actually spent waiting.
228
256
  * `max_wait_ms`: the effective budget after capping, so a clamped request is visible rather than silent.
229
257
 
230
- * **Important**: If the wait timeout expires, `wait` returns the current status but **does not kill** the process. It continues running in the background.
258
+ * **Important**: If the wait timeout expires, `wait` returns the current status but **does not stop** the execution. It continues running in the background.
231
259
  * The effective wait duration is capped at 55 seconds to stay well under typical MCP request timeouts, even if a larger `timeout_ms` is requested. The response reports that cap in `max_wait_ms`.
232
260
  * `wait` treats the managed process group disappearing as completion. If the tracked shell exits while descendants in its process group remain alive, the record continues to report `running`. For normal process-group completion, stdout/stderr are drained before the response is returned. Final drain waits are bounded so inherited pipes cannot block a request indefinitely.
233
261
 
@@ -237,45 +265,63 @@ Three separate timers control different things. Only one of them ever terminates
237
265
 
238
266
  | Parameter | Meaning | Terminates the process? | Default | Effective cap |
239
267
  | :--- | :--- | :--- | :--- | :--- |
240
- | `exec(yield_ms)` | How long `exec` stays inline before handing back a `process_id` and letting the command continue in the background. | No | 30,000ms | Lesser of `YIELDSHELL_MAX_YIELD_MS` and 55,000ms |
241
- | `exec(timeout_ms)` | Total execution limit. On expiry the process group is sent `SIGTERM`, then `SIGKILL`, and the status becomes `timed_out`. | **Yes** | 3,600,000ms | None (`0` means unlimited) |
268
+ | `execute(yield_ms)` | How long `execute` stays inline before handing back an `execution_id` and letting the command continue in the background. | No | 30,000ms | Lesser of `YIELDSHELL_MAX_YIELD_MS` and 55,000ms |
269
+ | `execute(timeout_ms)` | Total execution limit. On expiry the process group is sent `SIGTERM`, then `SIGKILL`, and the status becomes `timed_out`. | **Yes** | 3,600,000ms | None (`0` means unlimited) |
242
270
  | `wait(timeout_ms)` | Maximum time a single `wait` call blocks. On expiry it returns `wait_result: "deadline_reached"` and the process keeps running. | No | 55,000ms | 55,000ms |
243
271
 
244
272
  If `wait` returns with `status: "running"`, check `wait_result`: `"deadline_reached"` means the wait budget expired while the tracked shell or one of its descendants was still alive. The process keeps running and can be polled again.
245
273
 
246
274
  ### `stop`
247
- Gracefully terminate or force kill a running process.
275
+ Gracefully terminate or force kill a running execution.
248
276
 
249
277
  * **Parameters**:
250
- * `process_id` (string, **required**): Unique identifier of the process.
278
+ * `execution_id` (string, **required**): Opaque execution handle returned by `execute`; pass it through unchanged.
251
279
  * `signal` (string, default: `"SIGTERM"`): OS signal to send (e.g. `SIGTERM`, `SIGKILL`, `SIGINT`). Invalid names are rejected without stopping the process. Valid names are ignored on Windows.
252
280
  * `force_after_ms` (integer, default: `10000`): Grace period before escalating to force kill (`SIGKILL`). It is clamped below the transport-safe 55-second request ceiling so force-kill observation, final output drain, and subprocess reaping still fit within the request.
253
281
 
254
282
  ### `ps`
255
- List all managed processes.
283
+ List managed executions, including retained terminal records.
256
284
 
257
- Terminal records are retained temporarily for inspection. Before each otherwise valid `exec` spawn, records older than `YIELDSHELL_PROCESS_RETENTION_MS` are removed and the remaining terminal set is reduced to `YIELDSHELL_MAX_RETAINED_PROCESSES`, oldest first. Running records are never automatically removed. If the shell exits but its process group still has live descendants, status remains `running` until descendants stop. Reaped IDs disappear from `ps` and become unknown to `read`, `wait`, `write`, and `stop`.
285
+ Terminal records are retained temporarily for inspection. Before each otherwise valid `execute` spawn, records older than `YIELDSHELL_PROCESS_RETENTION_MS` are removed and the remaining terminal set is reduced to `YIELDSHELL_MAX_RETAINED_PROCESSES`, oldest first. Running records are never automatically removed. If the shell exits but its process group still has live descendants, status remains `running` until descendants stop. Reaped IDs disappear from `ps` and become unknown to `read`, `wait`, `write`, and `stop`.
258
286
 
259
287
  * **Parameters**:
260
- * `include_completed` (boolean, default: `true`): If `false`, finished/stopped processes are excluded from the output.
288
+ * `include_completed` (boolean, default: `true`): If `false`, terminal executions are excluded.
261
289
  * `limit` (integer, default: `50`): Maximum number of entries.
262
- * **Returns**: `processes` — a list of process summary objects, each containing: `process_id`, `pid`, `name`, `command`, `cwd`, `status`, `exit_code`, `signal`, `started_at`, `ended_at`, `duration_ms`, `stdout_bytes`, `stderr_bytes`, and `stdin_error` (`null` unless initial input delivery failed).
290
+ * **Returns**: `executions` — a list of execution summaries. Each contains opaque `execution_id`, numeric OS `process_id` (or `null` if unavailable), `name`, `command`, `cwd`, `status`, `exit_code`, `signal`, `started_at`, `ended_at`, `duration_ms`, `stdout_bytes`, `stderr_bytes`, and `stdin_error`.
263
291
  * Activity fields help distinguish a genuine hang from quiet work:
264
292
  * `last_output_at`: wall-clock time output last arrived on either stream, or `null` if none has.
265
293
  * `idle_ms`: milliseconds since that moment, or `null` if no output has arrived. A large and growing `idle_ms` on a `running` process is the signal that it may be stuck.
266
- * `latest_seq`: the process-wide cursor position just past the newest captured byte. Comparing it to a `next_seq` you hold shows how much output is waiting.
294
+ * `latest_seq`: the execution-wide cursor position just past the newest captured byte. Comparing it to a `next_seq` you hold shows how much output is waiting.
295
+
296
+ ```json
297
+ {
298
+ "executions": [
299
+ {
300
+ "execution_id": "a97e81c9034f",
301
+ "process_id": 42137,
302
+ "status": "running",
303
+ "idle_ms": 125.4,
304
+ "latest_seq": 2049
305
+ }
306
+ ]
307
+ }
308
+ ```
267
309
 
268
310
  ### Error Responses
269
311
 
270
- All tools that accept a `process_id` parameter return a structured error dict when the ID is unknown, e.g. `{"process_id": "proc_abc123", "error": "Unknown process_id: proc_abc123"}`. Tools that accept `process_id` always include it in the response alongside the error.
312
+ All tools that accept an `execution_id` return a structured error when the
313
+ opaque handle is unknown, for example:
314
+ `{"execution_id": "a97e81c9034f", "error": "Unknown execution_id: a97e81c9034f"}`.
315
+ The response echoes `execution_id`; a numeric `process_id` is not accepted as
316
+ a substitute.
271
317
 
272
318
  ### `cleanup`
273
- Prune completed, stopped, timed-out, and failed process records to free memory.
319
+ Prune completed, stopped, timed-out, and failed execution records.
274
320
 
275
321
  * **Parameters**:
276
- * `completed_older_than_ms` (non-negative integer, default: `3600000`): Prunes completed processes older than this threshold (1 hour default).
277
- * `stopped_older_than_ms` (non-negative integer, default: `3600000`): Prunes stopped, timed-out, or failed processes older than this threshold (1 hour default).
278
- * **Returns**: `removed` — the count of process records that were pruned. Negative thresholds are rejected without removing records and include an `error` message.
322
+ * `completed_older_than_ms` (non-negative integer, default: `3600000`): Prunes completed executions older than this threshold (1 hour default).
323
+ * `stopped_older_than_ms` (non-negative integer, default: `3600000`): Prunes stopped, timed-out, or failed executions older than this threshold (1 hour default).
324
+ * **Returns**: `removed` — the count of execution records pruned. Negative thresholds are rejected without removing records and include an `error` message.
279
325
 
280
326
  ---
281
327
 
@@ -284,8 +330,8 @@ Prune completed, stopped, timed-out, and failed process records to free memory.
284
330
  To avoid sending duplicate data over the MCP protocol (which can consume context window space), the server implements a byte-position polling protocol:
285
331
 
286
332
  1. Every stdout/stderr byte receives a unique position in a cursor shared by both streams. Positions start at 1 and only ever increase.
287
- 2. `read`, `wait`, and `exec` all return `next_seq`, the position immediately after the range covered by the response. A response cap can therefore end safely inside a drain chunk.
288
- 3. **You do not have to track cursors.** A `read` or `wait` with no `since_seq` and no `tail_lines` resumes from where the last such call stopped, then advances a server-side cursor held per process. Polling in a loop therefore returns each byte exactly once. `exec`'s inline snapshot advances the same cursor, so a following `wait` continues after it.
333
+ 2. `read`, `wait`, and `execute` all return `next_seq`, the position immediately after the range covered by the response. A response cap can therefore end safely inside a drain chunk.
334
+ 3. **You do not have to track cursors.** A `read` or `wait` with no `since_seq` and no `tail_lines` resumes from where the last such call stopped, then advances a server-side cursor held per execution. Polling in a loop therefore returns each byte exactly once. `execute`'s inline snapshot advances the same cursor, so a following `wait` continues after it.
289
335
  4. To drive the cursor yourself instead, pass the previous `next_seq` back as `since_seq` and keep the same `streams` selection.
290
336
  5. **A cursorless read consumes.** Once output has been delivered, a later cursorless read will not return it again. To inspect output that was already delivered, pass `since_seq=1` to re-read from the beginning, or use `tail_lines`.
291
337
  6. Requests that pass `since_seq`, pass `tail_lines`, or narrow `streams` are treated as **out-of-band**: they neither consult nor advance the server-side cursor. This means a peek at the tail or at stderr cannot silently skip output the polling stream has not seen yet.
@@ -297,11 +343,11 @@ To avoid sending duplicate data over the MCP protocol (which can consume context
297
343
 
298
344
  Cursor boundaries preserve valid UTF-8 characters. If a single character is larger than a very small requested page, that page may exceed `max_output_bytes` by at most three bytes so the cursor can make progress without corrupting the character.
299
345
 
300
- Positions are never reused and never reset. Eviction does not rewind them: it advances the oldest retained position while `next_seq` keeps climbing, so a cursor is safe to hold indefinitely. A cursor pointing at data that has since been evicted is not an error — the read returns data from the earliest retained position onward and sets `evicted` to `true`, and `start_seq` shows where the returned range actually begins. Positions are scoped to one process; a cursor from one `process_id` is meaningless for another.
346
+ Positions are never reused and never reset. Eviction does not rewind them: it advances the oldest retained position while `next_seq` keeps climbing, so a cursor is safe to hold indefinitely. A cursor pointing at data that has since been evicted is not an error — the read returns data from the earliest retained position onward and sets `evicted` to `true`, and `start_seq` shows where the returned range actually begins. Positions are scoped to one execution; a cursor from one `execution_id` is meaningless for another.
301
347
 
302
348
  Incremental cursors are scoped to the selected `streams` value. Keep the same stream selection while advancing a cursor; switching from `stdout`-only or `stderr`-only polling to another selection can intentionally skip bytes from the previously unselected stream.
303
349
 
304
- When a process exits normally, `exec`/`wait` responses include output drained through stdout/stderr EOF. If a descendant keeps inherited stdout/stderr open after the tracked process exits, the server stops waiting on those inherited pipes to avoid indefinite blocking; output written only by that descendant after the tracked process exits is not part of the managed process result.
350
+ When the initial shell exits normally, `execute`/`wait` responses include output drained through stdout/stderr EOF. If a descendant keeps inherited stdout/stderr open, the server stops waiting on those inherited pipes to avoid indefinite blocking; output written only by that descendant after the shell exits is not part of the managed execution result.
305
351
 
306
352
  ---
307
353
 
@@ -314,12 +360,12 @@ Configure the server by setting these environment variables prior to launch:
314
360
  | `YIELDSHELL_DEFAULT_CWD` | Current directory | The fallback working directory for commands. |
315
361
  | `YIELDSHELL_ALLOWED_CWDS` | *(none)* | A list of allowed directory paths separated by `os.pathsep` (e.g., `:` on UNIX, `;` on Windows). If set, all command execution paths must resolve inside one of these roots. |
316
362
  | `YIELDSHELL_MAX_OUTPUT_BYTES` | `20000` | The default and maximum bytes any single response may return across stdout and stderr. This is a response cap only; it does not limit what is retained. Nonpositive or nonnumeric values use the default. |
317
- | `YIELDSHELL_MAX_BUFFER_BYTES` | `262144` | Capacity of each per-process stdout/stderr ring buffer. Retention is deliberately larger than one response so `since_seq` cursors and `tail_lines` still resolve after a gap between polls. Raise it for very chatty commands, at a cost of roughly this value × 2 streams × live processes in memory. Nonpositive or nonnumeric values use the default. |
363
+ | `YIELDSHELL_MAX_BUFFER_BYTES` | `262144` | Capacity of each per-execution stdout/stderr ring buffer. Retention is deliberately larger than one response so `since_seq` cursors and `tail_lines` still resolve after a gap between polls. Raise it for very chatty commands, at a cost of roughly this value × 2 streams × live executions in memory. Nonpositive or nonnumeric values use the default. |
318
364
  | `YIELDSHELL_MAX_PROCESSES` | `50` | Maximum concurrent live managed process groups, including descendants that outlive a completed shell. Spawning a new command when this limit is reached returns `failed_to_start`. Nonpositive or nonnumeric values use the default. |
319
365
  | `YIELDSHELL_DEFAULT_YIELD_MS` | `30000` | Fallback delay before auto-yielding. Effective yields are also capped at 55,000ms. |
320
366
  | `YIELDSHELL_MAX_YIELD_MS` | `300000` | Configured maximum for `yield_ms`; the effective maximum is the lesser of this value and 55,000ms. |
321
367
  | `YIELDSHELL_DEFAULT_TIMEOUT_MS` | `3600000` | Default hard runtime limit (1 hour). An explicit tool argument of `0` means no limit. |
322
- | `YIELDSHELL_PROCESS_RETENTION_MS` | `3600000` | Age after which terminal process records are reaped before a valid spawn. Zero requests immediate age-based reaping. Negative or nonnumeric values use the default. |
368
+ | `YIELDSHELL_PROCESS_RETENTION_MS` | `3600000` | Age after which terminal execution records are reaped before a valid spawn. Zero requests immediate age-based reaping. Negative or nonnumeric values use the default. |
323
369
  | `YIELDSHELL_MAX_RETAINED_PROCESSES` | `100` | Maximum retained terminal records after age reaping; oldest records are removed first. Zero retains no prior terminal records. Negative or nonnumeric values use the default. Running records are excluded. |
324
370
  | `YIELDSHELL_DENY_COMMAND_REGEX` | *(none)* | A regular expression pattern. Commands matching this pattern are blocked before starting. Invalid patterns cause startup to fail with a configuration error naming this variable. |
325
371
  | `YIELDSHELL_ALLOW_COMMAND_REGEX` | *(none)* | A regular expression pattern. If set, only commands matching this pattern are permitted. Invalid patterns cause startup to fail with a configuration error naming this variable. |
@@ -331,7 +377,7 @@ Configure the server by setting these environment variables prior to launch:
331
377
  ## Security Notes
332
378
 
333
379
  * **Arbitrary Code Execution**: This server executes shell commands on the host system. Always run the server inside a container, sandbox, or isolated development VM.
334
- * **Side-Effect Declarations**: Every `exec` call must declare its plausible side-effect categories via `side_effects`. By default, `KILLS_AGENT_PROCESS`, `MODIFIES_OS_SETTINGS`, `MODIFIES_OS_USER_SETTINGS`, `MODIFIES_PROTECTED_FILES`, and `RUNS_INLINE_CODE` are blocked. Operators can adjust the blocklist via `MCP_YIELDSHELL_BLOCKED_SIDE_EFFECTS` (including cleared to `,` to disable every default). This is an explicit risk signal — it is not a complete sandbox, and LLM under-declaration remains possible.
380
+ * **Side-Effect Declarations**: Every `execute` call must declare its plausible side-effect categories via `side_effects`. By default, `KILLS_AGENT_PROCESS`, `MODIFIES_OS_SETTINGS`, `MODIFIES_OS_USER_SETTINGS`, `MODIFIES_PROTECTED_FILES`, and `RUNS_INLINE_CODE` are blocked. Operators can adjust the blocklist via `MCP_YIELDSHELL_BLOCKED_SIDE_EFFECTS` (including cleared to `,` to disable every default). This is an explicit risk signal — it is not a complete sandbox, and LLM under-declaration remains possible.
335
381
  * **Inline Code Execution**: The `RUNS_INLINE_CODE` default discourages agents from executing code supplied inline to an interpreter or shell (e.g. `python -c`, `node -e`, `ruby -e`, `perl -e`, shell heredocs piped into interpreters, or `curl ... | sh`). The safer pattern is to write the content to a reviewable workspace file and execute it in a small, inspectable step with explicit matching `side_effects`. Operators can override the default to permit the category.
336
382
  * **OS User Settings Damage**: `MODIFIES_OS_USER_SETTINGS` covers commands that change user-level configuration such as shell rc files, XDG config directories, dotfiles, or per-user application preferences. This is distinct from `MODIFIES_OS_SETTINGS`, which covers broader OS-level configuration such as systemd units, kernel parameters, `/etc` files, and package manager system config. Blocked by default; operators can override.
337
383
  * **Agent Process Termination**: `KILLS_AGENT_PROCESS` covers commands that may terminate the MCP client, agent, or related process running the agent workflow (e.g., `kill` commands targeting the agent PID, or commands that cause the agent to exit). This is distinct from `STOPS_OR_RESTARTS_SERVICES`, which covers OS-level services. Blocked by default; operators can override.
@@ -341,9 +387,35 @@ Configure the server by setting these environment variables prior to launch:
341
387
 
342
388
  ## Lifecycle and Compatibility
343
389
 
344
- YieldShell gives graceful termination 10 seconds before force-killing, waits up to 5 seconds for a POSIX process group to disappear, and allows up to 3 seconds for final stream draining. On stdio server shutdown, all live managed processes are terminated concurrently using the same bounded graceful/forced policy. Already-terminal records are not changed by shutdown.
390
+ YieldShell gives graceful termination 10 seconds before force-killing, waits up
391
+ to 5 seconds for a POSIX process group to disappear, and allows up to 3 seconds
392
+ for final stream draining. On stdio server shutdown, all live managed
393
+ executions are terminated concurrently using the same bounded graceful/forced
394
+ policy. Already-terminal records are not changed by shutdown.
395
+
396
+ `execution_id` is an opaque, server-generated string. Clients must pass it
397
+ through unchanged rather than parsing or pattern-matching it. Its current
398
+ shape is an implementation detail, not a client contract.
399
+
400
+ Current lifecycle defaults are a 30-second auto-yield, one-hour runtime timeout, 55-second effective request-wait and stop-grace ceilings, 10-second default stop grace, automatic terminal-record retention, opt-in streaming output redaction, and shutdown containment. Standard input closes after `execute` by default; callers that need an interactive session must set `close_stdin=false`. Use explicit shorter timing values where lower latency is preferred, and pass `timeout_ms=0` to retain unlimited runtime behavior.
401
+
402
+ ### Migration (0.x → 1.0)
403
+
404
+ Version 1.0.0 is a clean breaking release. There are no compatibility aliases
405
+ or dual-emitted legacy fields.
406
+
407
+ | 0.x API | 1.0 API |
408
+ | :--- | :--- |
409
+ | Tool `exec` | Tool `execute` |
410
+ | Managed handle `process_id` (string) | Managed handle `execution_id` (opaque string) |
411
+ | OS field `pid` | OS field `process_id` (integer) |
412
+ | `ps` response key `processes` | `ps` response key `executions` |
413
+ | Assumed ID pattern `^proc_[0-9a-f]{12}$` | No supported pattern; treat `execution_id` as opaque |
345
414
 
346
- Current lifecycle defaults are a 30-second auto-yield, one-hour runtime timeout, 55-second effective request-wait and stop-grace ceilings, 10-second default stop grace, automatic terminal-record retention, opt-in streaming output redaction, and shutdown containment. Standard input closes after `exec` by default; callers that need an interactive session must set `close_stdin=false`. Use explicit shorter timing values where lower latency is preferred, and pass `timeout_ms=0` to retain unlimited runtime behavior.
415
+ The most important migration trap is that `process_id` still exists but now
416
+ means the numeric OS PID. Store `execution_id` from `execute` and pass it
417
+ unchanged to `read`, `write`, `wait`, and `stop`. Calls that send the OS
418
+ `process_id` as the handle fail with `Unknown execution_id`.
347
419
 
348
420
  ---
349
421