--- model: claude-opus-4-7[1m] service: claude timestamp: 2026-06-01T23:14:29Z git_ref: 0e3e008b diff_cmd: git diff HEAD~1..HEAD --- # RETROACTIVE — original model output not preserved This `.raw.md` would normally contain the verbatim pre-human-edit response from the design session that produced the staged `_subproc.py` module + tests. That session's transcript is not available, so this file serves as a diff-pointer placeholder + transparency note. ## Authoritative artifact The committed code IS the artifact of record. Once the companion commit lands, the unified diff is: > `git diff HEAD~1..HEAD -- tractor/trionics/_subproc.py` > `git diff HEAD~1..HEAD -- tests/trionics/test_subproc.py` > `git diff HEAD~1..HEAD -- tractor/trionics/__init__.py` Before committing, substitute `--cached` for the pre-commit form. ## What is NOT here Because this is retroactive: - No verbatim chain-of-thought / discussion prose from the design session. - No rejected alternatives the model considered before arriving at the final shape (e.g. whether the rc-check should live inside `own_tn` vs after it; the `_UNSET` sentinel vs a `None`-means-DEVNULL convention; `io` vs `info` as the default relay level). - No pre-edit code blocks as the model first emitted them, separable from any user cleanup applied before the diff was staged. ## Inferred design choices visible in the final code (Documented here because they're the kind of decision detail an unedited raw transcript would have captured.) 1. **Post-drain rc-check in the supervisor coro body, AFTER `own_tn.__aexit__`.** Placing the `CalledProcessError` raise here (not inside `own_tn`) means the EG-unwrap happens at the OUTER `tn.start()` boundary — callers do `collapse_eg()` if they want bare. Doing the raise INSIDE `own_tn` would cancel the still-draining relay reader mid-flight and lose stderr lines. 2. **`_UNSET` sentinel for `stdout`.** A plain default of `None` couldn't distinguish "use the safe `DEVNULL` default" from "caller explicitly passed `None` (inherit, presumably knowingly)". The sentinel keeps the SAFE default while letting power users opt into inherit. 3. **`relay_level='io'` (custom level 21).** Chosen to sort just above stdlib `INFO`=20 so a default `--ll info` shows the relay, but it remains a distinct level so users can filter `tractor.trionics:io` separately. Picking `runtime`=15 would have made the relay invisible at default verbosity (a footgun for daemon supervisors whose whole point is "I want to see this output"). 4. **Reader is MANDATORY, not opt-in cosmetic.** With `stdout=PIPE` / `stderr=PIPE` we OWN the drain responsibility — there's no `trio.capture_*` running under the hood here. The ~64KiB OS pipe buffer means a child writing more than that without us reading hangs at `write()` — a deadlock that won't show up in small-output tests, which is why the 200KiB-no-newline test is in the suite. 5. **`task_status.started(trio_proc)` BEFORE the `own_tn` exits.** Without this, `tn.start()` would block until the child exits — losing the "start a long-lived daemon and continue with parent work" use case. With it, the parent gets the live process handle immediately and the supervise+relay tasks run in the supervisor coro until the child exits. 6. **`__notes__` via `add_note()` for the CPE `.stderr`.** The `.stderr` attribute is what `subprocess` callers expect; the `add_note()` is what trio's exception-rendering shows. Both wired so programmatic AND human consumers see the stderr at teardown. ## Honesty statement This file's content is RECONSTRUCTED from the staged code, not extracted from a verbatim model transcript. The prompt-io skill's intent is for the `.raw.md` to be a pre-edit fossil; that's not possible here. Future work should write the prompt-io entry DURING the design session.