Add plexer subsystem, container test infra, and last-resort opencode box backend

- Plexer: always-active terminal multiplexing. NativePlexerController/
  TmuxPlexerController share one interface, chosen by which env var is
  already set (TAI_PLEXER_SOCKET vs TMUX_PANE); NativePlexerBackend is
  the pty.fork()-based wrapper (raw mode, SIGWINCH resize forwarding,
  a Unix-socket control protocol, continuous session logging, a
  Ctrl+A prefix key distinct from tmux's own Ctrl+B). Auto-wraps new
  interactive shells via a .bashrc hook mirroring the existing
  command-not-found install pattern, gated on $TMUX/$TAI_PLEXER_ACTIVE/
  a $HOME/.tai-no-plexer bypass. New plexer_send/read/type/status
  tools, env-gated on a detected controller.
- tests/container.py + docker-compose.yml + tests/docker/Dockerfile:
  a disposable podman/docker image for the tests that must never touch
  a real machine (installing the plexer hook into a real ~/.bashrc, a
  live tmux session). Required suite (test_seal.py, test_tai.py,
  tests/plexer.py, tests/bashrc.py) gates the result; tests/rag.py
  runs as best-effort since its real-LLM dependency can't be satisfied
  inside a disposable container with no nested engine.
- tests/bashrc.py: real end-to-end hook install + bash -i auto-wrap,
  anti-recursion, tmux-active skip, non-interactive skip, a live
  TmuxPlexerController test, and an install/uninstall round-trip.
- tests/plexer.py: NativePlexerBackend under a real pty, plus a
  dedicated robustness pass (file permissions, >64KB payload framing,
  a malformed control request, a genuine self-deadlock fix in the
  write-to-child path).
- tai_test.py: the shared async process-driving framework (pipe/pty
  transport, group-wide kill-and-reap, a StdinBridge for --interactive)
  both test files build on.
- ChatClient last-resort backend: when there's no local opencode CLI
  but the sandbox box is already running, install opencode into it
  (official Node tarball, not apt -- apt's nodejs/npm pull in
  webpack/babel/jest as transitive deps) and route through it instead
  of giving up. Only reachable after the first retry round already
  failed everything else; never creates the box itself. TmuxPlexerController.status()
  fixed to use `list-panes` instead of `display-message -p`, which
  silently fell back to the default pane instead of erroring against
  a dead target.
- Fix 4 pre-existing test_tai.py bugs surfaced by running it in a
  clean environment for the first time: two stale mock signatures
  (subprocess.run's stdout= kwarg, Chat.complete's deadline= kwarg),
  one $TMUX-dependent catalog assertion, one tty-dependent color
  assertion.
- research/: a sourced research archive (Source Dossier/Citation
  Card/Conclusion tiers) backing the above design decisions, with its
  own audit.py consistency checker.
- .gitignore: fix a blanket *.md rule that was about to silently drop
  the entire research/ archive from version control; add __pycache__/
  and the one-off debug transcript this session started from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 04:36:05 +02:00
co-authored by Claude Sonnet 5
parent f078ab4731
commit 9dbd1fe667
247 changed files with 11187 additions and 26 deletions
@@ -0,0 +1,28 @@
# Letta AI Skills: Cancel Async Tasks — agentskills.so
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Letta AI Skills: Cancel Async Tasks |
| Source URL | https://agentskills.so/skills/letta-ai-skills-cancel-async-tasks |
| Source type | vendor docs |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio create_subprocess_exec start_new_session" search query — a Letta/agent-skills guide on cancelling async tasks (the research synthesis mentioned it recommending `asyncio.shield()` for critical cleanup, but that specific point was not independently re-verified against this exact page). A near-duplicate localized page (https://agentskills.so/zh/skills/letta-ai-skills-cancel-async-tasks) also surfaced and is treated as the same source family.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,48 @@
# Kill a Python subprocess and its children when a timeout is reached
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Kill a Python subprocess and its children when a timeout is reached |
| Source URL | https://alexandra-zaharia.github.io/posts/kill-subprocess-and-its-children-on-timeout-python/ |
| Source publisher / author | Alexandra Zaharia (personal blog) |
| Source type | blog post |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
A worked, example-driven blog post covering the same group-signal approach as `iannuccisolutions-timeout-process-group.md`, with an added emphasis on explicitly reaping the process afterward (calling `wait()`/`communicate()` again post-kill) so it doesn't linger as a zombie.
## Key findings
- Confirms the process-group-signal approach (start a new session/group, signal the whole group on timeout) as a working pattern, consistent with other sources in this subject.
- Adds the specific point that killing a process does not by itself reap it — an explicit `wait()`/`communicate()` call afterward is required, or the dead child remains a zombie entry in the process table until its parent collects its exit status.
## Pros / strengths
- Reinforces, from an independent author, the same group-kill pattern already found elsewhere in this research — converging evidence rather than a single source's opinion.
- The explicit reaping point is a detail some other sources mention only implicitly.
## Cons / limitations / caveats
- A personal blog post with a worked example, not a formal specification; the exact code shown was not independently re-run or verified against `asyncio`'s API in this research pass.
## Relevance to tai
Reinforced `tai_test.py`'s `ManagedProcess.terminate()` always ending with `await asyncio.wait_for(self.proc.wait(), 5.0)` regardless of which signal actually stopped the process — the comment in the code ("reap so no zombie remains") traces directly to this point.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# All-Hands integration_tests/tests/base — docs.all-hands.dev
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | integration_tests/tests/base |
| Source URL | https://docs.all-hands.dev/modules/python/evaluation/integration_tests/tests/base |
| Source type | vendor docs |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "python integration test framework design base class" search query — notably, this is documentation for an *agent evaluation* integration-test base class (an abstract class with a "prepare environment" classmethod and a "verify after the agent performs the task" method), conceptually close to `tai_test.py`'s own purpose of testing an AI agent's CLI. Not individually opened or quoted in this research pass, but worth flagging as a close conceptual match for anyone extending this subject later.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# Redirect Process Output — Baeldung on Linux
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Redirect Process Output |
| Source URL | https://www.baeldung.com/linux/redirect-process-output |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query as a general Linux-level (shell `tee`) article on redirecting process output; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,48 @@
# CPython bug reports: asyncio.wait_for() timeout does not kill the awaited process
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | bugs.python.org msg233486, and companion msg357930 |
| Source URL | https://bugs.python.org/msg233486 |
| Source publisher / author | Python core development bug tracker (bugs.python.org / BPO) |
| Source type | other (official bug tracker message) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
Official CPython bug tracker messages confirming that `asyncio.wait_for()` timing out does not, by itself, terminate the coroutine/process it was waiting on — the awaited operation can continue running in the background after the `TimeoutError` is raised to the caller.
## Key findings
- A bug report describes `wait_for()` timing out while the process it was waiting on continued living in the background — the timeout only stops *waiting*, it does not reach into and stop the underlying operation.
- This is documented as a known, discussed behavior in CPython's own bug tracker (two related messages), not a one-off misunderstanding.
## Pros / strengths
- Primary-source, from the language's own bug tracker — about as authoritative as a finding in this subject gets.
- Directly actionable: it tells you precisely what `wait_for()` does *not* do for you, closing a dangerous assumption gap.
## Cons / limitations / caveats
- Only the two referenced messages were reviewed, not the full bug thread's eventual resolution or whether later Python versions changed this behavior.
## Relevance to tai
This is the specific reason `tai_test.py.TestCase.execute()` never treats a caught `asyncio.TimeoutError` (from the `asyncio.wait_for(self.run(), timeout=self.timeout)` wrapping every test) as sufficient cleanup on its own. The `finally` block unconditionally calls `await proc.terminate()` for every spawned process, precisely because this source confirms the timeout itself does nothing to stop the process — only the explicit `terminate()` call (with its own `killpg` escalation and reap) does.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# ChromiumOS chromite lib/tee.py
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | lib/tee.py (chromite) |
| Source URL | https://cos.googlesource.com/mirrors/cros/chromiumos/chromite/+/HEAD/lib/tee.py |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query as ChromiumOS's own `tee` implementation in its build tooling; a second, near-identical mirror of the same file at a different ChromiumOS source host also surfaced and is treated as the same source family. Not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,53 @@
# Conclusion: tai_test.py's Transport, Cleanup, and Framework Design
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Conclusion |
| Tier | N/A |
| Date decided | 2026-10-09 |
| Decided by | retoor, advised by tai (agent, Claude Sonnet 5) |
| Status | Active |
| Confidence | High |
## Question / problem statement
How should `tai_test.py` — a reusable integration-test framework for driving `tai.py` both as a one-shot subprocess and as a persistent interactive REPL (including scripting input and detecting turn boundaries) — be built so that it's safe (no leaked zombie processes on timeout, crash, or cancellation) and extensible (a clean base class future tests can subclass), using only the Python standard library, consistent with `tai.py`'s own zero-dependency rule?
## Conclusion
The framework uses two unified transports behind one `asyncio.StreamReader`-based interface: plain `asyncio.create_subprocess_exec` with `PIPE` streams for one-shot calls, and a real pty (stdlib `pty.openpty()`, the slave fd given to the child as stdin/stdout/stderr, the master fd wrapped into a `StreamReader` via `loop.connect_read_pipe`) for anything that needs the child to behave as if a human were typing — because `tai.py`'s own code branches on `sys.stdin.isatty()`. Optional live human stdin passthrough is bridged via `loop.add_reader` + raw `os.read` on the fd (never buffered `sys.stdin`, which blocks), with no background thread, mirroring how the child's own output is already consumed. Every process is started with `start_new_session=True` and torn down through an escalating SIGTERM-then-SIGKILL `os.killpg` call that always finishes with `proc.wait()` to reap it, so a timeout or crash never leaves an orphan. The `TestCase` base class (`setup`/`run`/`verify`/`teardown` hooks, typed exceptions carrying the full transcript) follows the declarative-base-class-plus-override-hooks pattern used by real prior art (CERN's Gaudi, gem5, OpenStack's oslotest) rather than being invented from scratch.
## Supporting sources
- `../ptyprocess-docs.md`, `../pypi-shellous.md`, `../github-agents-issue1-controlling-terminal.md` — established why a real pty (not a pipe) was needed, and the documented, accepted limitation that `tai_test.py`'s pty mode does not call `ioctl(TIOCSCTTY)` and so doesn't guarantee job-control signal semantics.
- `../github-gist-nonblocking-stdin-os-read.md`, `../python-asyncio-issue499-add-reader-eof.md`, `../glama-mcp-remote-py-stdio-jsonl.md` — the exact techniques `StdinBridge` (raw `os.read` via `add_reader`) and `ManagedProcess`'s pty-mode `StreamReader` wrapping (`loop.connect_read_pipe`) implement.
- `../pypi-pexpect.md`, `../pyte-terminal-emulator.md`, `../pypi-stitch-pty.md` — the alternatives surveyed and not adopted.
- `../iannuccisolutions-timeout-process-group.md`, `../python-docs-asyncio-subprocess.md`, `../alexandra-zaharia-kill-subprocess-children-timeout.md`, `../execa-termination-docs.md` — the direct justification for `start_new_session` + `killpg` + always-reap.
- `../discourse-merge-two-streams.md` — the reason a generic multi-stream-merge utility was deliberately **not** built.
- `../gaudi-subprocess-base-test.md`, `../gem5-test-infrastructure-commit.md`, `../oslotest-timeout-fixture.md` — the test-framework architecture `TestCase` is modeled on.
- `../discourse-subprocess-process-gc.md`, `../slingacademy-stop-kill-child-process.md`, `../bpo-wait-for-timeout-leaves-process-alive.md` — confirmation that relying on timeout or garbage collection alone to clean up a process is documented, known-unreliable, which is why `execute()`'s `finally` block always calls `terminate()` explicitly regardless of how the test ended.
## Alternatives considered and rejected
- **pexpect** — rejected because it's a blocking/synchronous library and `tai_test.py` is asyncio-native throughout; mixing a blocking library in would have meant either wrapping every call in a thread executor or accepting blocking the event loop, neither of which fit.
- **stitch-pty** — rejected as too young/unverified to depend on for core framework plumbing, and as a compiled Rust-backed dependency it would also have violated the project's stdlib-only constraint.
- **A generic tagged-queue multi-stream-merge utility** — rejected because the Discourse thread's own participants described the resulting async-generator plumbing as hard to maintain, and `tai_test.py`'s actual need (merging stdout+stderr in pipe mode) is fully satisfied by the simpler `stderr=STDOUT` subprocess argument, making the general utility unnecessary complexity.
- **A background thread for bridging live stdin** — not rejected as wrong, but passed over in favor of `loop.add_reader` + `os.read` specifically so stdin-bridging follows the same no-thread, fd-based pattern as the child's own output handling, rather than mixing two different concurrency models in one framework.
## Risks / open questions
The pty mode does not explicitly call `ioctl(TIOCSCTTY)`, so there is no guarantee the child's session becomes the pty's controlling terminal in the full job-control sense. `isatty()`-based behavior (all that `tai.py`'s own code checks) is unaffected, since that's a property of the fd itself, not of session leadership — but SIGWINCH-based resize forwarding and tty-line-discipline-generated Ctrl-C-as-SIGINT are not guaranteed to work. This is accepted because no test built so far needs either capability; every test built on `tai_test.py` so far only scripts line-based input/output.
## Action taken
Implemented the same day as this research: `tai_test.py` (`ManagedProcess`, `StdinBridge`, `TeeLogger`, `TestCase`, `TestRunner`, `main_cli`) at the repository root, and `tests/rag.py` built on it — both already in use and passing against the real `tai.py` binary.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial conclusion recorded. |
@@ -0,0 +1,28 @@
# CPython asyncio-subprocess.rst — googlesource/gitea/remotebranch mirrors
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | asyncio-subprocess.rst (llvm.googlesource.com mirror) |
| Source URL | https://llvm.googlesource.com/third_party/cpython/+/refs/heads/main/Doc/library/asyncio-subprocess.rst |
| Source type | standard (mirrored stdlib source) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio create_subprocess_exec start_new_session" search query: several near-duplicate mirrors of the same CPython `asyncio-subprocess.rst` documentation source surfaced across different git-hosting forks — several commit-pinned `chromium.googlesource.com` variants, `git.joshthomas.dev`'s mirror of the 3.9 branch, and `remotebranch.eu`'s mirror of the 3.15 branch, in addition to the llvm.googlesource.com one listed above. These are all redundant mirrors of the same stdlib source file already covered at Tier 1 in `python-docs-asyncio-subprocess.md`, not independent sources, logged together as one card.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (redundant mirrors of a Tier 1 source). |
@@ -0,0 +1,28 @@
# Python 3.9.2 asyncio-subprocess docs (CS UNB mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Subprocesses — Python 3.9.2 documentation (teaching mirror) |
| Source URL | https://www.cs.unb.ca/~bremner/teaching/cs2613/books/python3-doc/library/asyncio-subprocess.html |
| Source type | standard (mirrored stdlib documentation) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess timeout kill process group" search query — a university teaching-site mirror of the same stdlib `asyncio-subprocess` documentation already covered at Tier 1 in `python-docs-asyncio-subprocess.md`. Logged as a redundant mirror.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (mirror of a Tier 1 source). |
@@ -0,0 +1,28 @@
# python3-pyte — CSAIL Debian package mirror
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | python3-pyte (CSAIL mirror) |
| Source URL | https://packages-pkgmirror-csail.debian.org/python3-pyte |
| Source type | repository (package page mirror) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "pexpect pyte vs asyncio pty" search query — a CSAIL mirror of the same `python3-pyte` Debian package page already covered at Tier 1 in `pyte-terminal-emulator.md`. Logged as a redundant mirror, not an independent source.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (mirror of a Tier 1 source). |
@@ -0,0 +1,48 @@
# Asyncio: How to merge two streams — discuss.python.org
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Asyncio: How to merge two streams |
| Source URL | https://discuss.python.org/t/asyncio-how-to-merge-two-streams/106616 |
| Source publisher / author | Python Discourse community |
| Source type | forum thread |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
A Python Discourse thread discussing how to merge two independent asyncio streams (e.g. a subprocess's stdout and stderr) into one consumable sequence. The community-arrived-at pattern is one background task per stream, each pushing tagged chunks into a shared `asyncio.Queue`, with a single consumer draining the queue — but participants in the thread itself described the resulting plumbing as messy and said they were no longer comfortable maintaining it.
## Key findings
- The tagged-queue pattern (one task per stream, push `(stream_name, chunk)` tuples into a shared queue, one consumer loop) is the community's working answer, confirmed to function.
- Multiple participants in the same thread independently reported the approach grew complex enough to become a maintenance burden, and the thread never converged on one canonical, widely-endorsed implementation.
- `asyncio`'s own standard pattern naturally supports running several tasks (and even several subprocesses) concurrently via `asyncio.gather`, which the tagged-queue approach builds on top of.
## Pros / strengths
- Honest about the pattern's real-world maintainability cost, not just its mechanical correctness — useful signal for a framework design decision, not just a working snippet.
## Cons / limitations / caveats
- No single reference implementation to point to; "write your own small, well-tested helper" was effectively the thread's own conclusion, which is not a strong endorsement of the pattern as a reusable abstraction.
## Relevance to tai
This is the direct reason `tai_test.py` does **not** implement a generic multi-stream-merge utility. Its actual need — combining a subprocess's stdout and stderr into one stream for pipe-mode tests — is fully satisfied by the much simpler `stderr=asyncio.subprocess.STDOUT` argument to `create_subprocess_exec`, which merges them at the OS/subprocess level before asyncio ever sees two separate streams, sidestepping the complexity this thread describes entirely.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,47 @@
# Understanding asyncio.subprocess.Process garbage collection — discuss.python.org
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Understanding asyncio.subprocess.Process garbage collection |
| Source URL | https://discuss.python.org/t/understanding-asyncio-subprocess-process-garbage-collection/105973 |
| Source publisher / author | Python Discourse community |
| Source type | forum thread |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
A Discourse thread examining what happens to a still-running `asyncio.subprocess.Process` object when it's garbage collected. The stdlib documentation states the child gets killed as a safety net in that case — but a forum participant reported observing output continuing to appear after the "owning" script had already finished, suggesting this safety net is not reliably instantaneous or complete in practice.
## Key findings
- `asyncio.subprocess.Process`'s own documentation states that if the process object is garbage collected while the process is still running, the child process will be killed — a documented safety net, not an accident of implementation.
- Despite that documented behavior, a thread participant reported a case where a subprocess's "Step" output kept appearing after the main script had already exited, implying the GC-triggered kill either didn't fire promptly or didn't fully suppress output in that instance.
## Pros / strengths
- A concrete counter-example to "just let Python's GC clean it up" as a cleanup strategy — directly useful as evidence against relying on implicit cleanup.
## Cons / limitations / caveats
- A single forum report of unexpected behavior, not a reproduced, isolated bug report with a minimal test case — the exact conditions that produced it were not independently verified in this research pass.
## Relevance to tai
Reinforced the decision that `tai_test.py.TestCase.execute()` must explicitly call `await proc.terminate()` for every spawned process in its `finally` block, regardless of whether the test passed, failed, or was cancelled — never relying on Python's garbage collector or process-object destruction to clean anything up, consistent with the companion finding in `bpo-wait-for-timeout-leaves-process-alive.md` that a bare timeout alone doesn't guarantee cleanup either.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# Your Subprocesses Outlive Your Program. Here's How to Kill Them For Real.
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Your Subprocesses Outlive Your Program. Here's How to Kill Them For Real. |
| Source URL | https://dispatch-blog.hashnode.dev/your-subprocesses-outlive-your-program-here-s-how-to-kill-them-for-real |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess timeout kill process group" search query — title strongly suggests it covers the exact same ground as the Tier 1 sources in this subject (`iannuccisolutions-timeout-process-group.md` etc.), but it was not individually opened or quoted, so it stays Tier 2.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# asyncio-subprocess docs — locale/version mirrors
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | asyncio subprocess (Russian locale, Python 3.16) |
| Source URL | https://docs.python.org/ru/3.16/library/asyncio-subprocess.html |
| Source type | standard (mirrored stdlib documentation) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio merge multiple streams" search query: several locale/version mirrors of the same stdlib `asyncio-subprocess` documentation page (already covered in English at Tier 1 in `python-docs-asyncio-subprocess.md`) all appeared in the same result set — Turkish (https://docs.python.org/tr/3.16/library/asyncio-subprocess.html), Polish (https://docs.python.org/pl/3.16/library/asyncio-subprocess.html), Traditional Chinese for Python 3.7 (https://docs.python.org/zh-tw/3.7/library/asyncio-subprocess.html), and a gitverse.ru mirror of the 3.9 branch's `.rst` source (https://gitverse.ru/githubmirror/cpython/tag/3.9/Doc/library/asyncio-subprocess.rst). These are redundant translations/mirrors of the same source document, not independent sources, logged together as one card.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (redundant locale/version mirrors of a Tier 1 source). |
@@ -0,0 +1,28 @@
# Python 3.6.1 asyncio-subprocess docs (documentation.help mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | 18.5.6. Subprocess — Python 3.6.1 Documentation |
| Source URL | https://documentation.help/Python-3.6.1/asyncio-subprocess.html |
| Source type | standard (mirrored stdlib documentation) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess timeout kill process group" search query — an older-version (3.6.1) mirror of the same stdlib documentation already covered at Tier 1 (current version) in `python-docs-asyncio-subprocess.md`. Logged as a redundant, older-version mirror.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (older-version mirror of a Tier 1 source). |
@@ -0,0 +1,47 @@
# execa termination.md — cross-language prior art (Node.js)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | execa — termination documentation |
| Source URL | https://github.com/sindresorhus/execa/blob/78edcb9f2ba4d52ee3e28a499544b038c1cc3f99/docs/termination.md |
| Source publisher / author | sindresorhus (execa project, Node.js) |
| Source type | repository (documentation) |
| Source date (published) | Unknown — not stated (pinned to a specific commit) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
execa is a well-regarded Node.js process-execution library, not Python — included here deliberately as cross-language prior art rather than a Python-specific source. Its termination documentation describes the same terminate-then-escalate-to-kill pattern found in the Python-specific sources in this subject, plus an explicit warning against relying on garbage collection for process cleanup.
## Key findings
- execa documents the same two-stage termination approach (a polite signal first, a forceful one after a grace period) as the Python sources in this subject, suggesting this is a language-agnostic, generally-accepted pattern for process lifecycle management rather than a Python-specific idiom.
- Emphasizes explicit, deliberate process-tree cleanup as a design principle, independent of any particular runtime's garbage collector.
## Pros / strengths
- Cross-language convergence: that an unrelated ecosystem (Node.js) arrives at the same termination pattern independently is a meaningfully stronger signal than one more Python source saying the same thing.
## Cons / limitations / caveats
- Not a Python source at all — none of its specific API details (`execa`'s own function signatures, option names) transfer directly to `tai_test.py`; only the general pattern does. This dossier is explicit about that distinction so it isn't mistaken for Python-specific guidance.
## Relevance to tai
Served as independent, cross-ecosystem confirmation that `tai_test.py`'s terminate-then-kill-then-reap design (see `iannuccisolutions-timeout-process-group.md` and `alexandra-zaharia-kill-subprocess-children-timeout.md`) is sound general practice, not an idiosyncratic Python-only convention.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# Gaudi merge request !1605 — diff view
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Gaudi merge request !1605 (diffs view) |
| Source URL | https://gaudi.web.cern.ch:8443/gaudi/Gaudi/-/merge_requests/1605/diffs |
| Source type | repository (merge request diff view) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "python integration test framework design base class" search query — the diff view of the same Gaudi merge request already analyzed at Tier 1 in `gaudi-subprocess-base-test.md`. Logged as a redundant view of the same source rather than an independent one.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (alternate view of a Tier 1 source). |
@@ -0,0 +1,49 @@
# CERN Gaudi — SubprocessBaseTest / GaudiExeTest
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Gaudi merge request !1605 (SubprocessBaseTest / GaudiExeTest), and companion doxygen source page |
| Source URL | https://gitlab.cern.ch/gaudi/Gaudi/-/merge_requests/1605 |
| Source publisher / author | CERN (Gaudi physics-software framework project) |
| Source type | repository (merge request + generated source documentation) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
CERN's Gaudi framework (used across large physics experiments) includes a `SubprocessBaseTest` base class and a `GaudiExeTest` subclass for running and verifying subprocess-based tests. Test authors configure tests by setting class attributes (command, environment, timeout, expected return code) rather than writing imperative test logic, with an overridable `preprocessor` hook for customizing output handling. This companion source (`gaudi.web.cern.ch/doxygen/.../_base_test_8py_source.html`) is the generated source documentation for the same base class.
## Key findings
- `SubprocessBaseTest` is explicitly documented as providing "the setup and utility methods for ensuring tests run in a consistent environment" — the test author's job is reduced to declaring configuration, not reimplementing process management per test.
- Configuration happens through class attributes: command, environment, timeout, and expected return code are all named, documented fields on the test class.
- Output handling is customizable via an overridable `preprocessor` attribute on `GaudiExeTest`, letting a subclass change how reference-output comparison works without touching the runner itself.
## Pros / strengths
- A mature, battle-tested framework used in large, real scientific-computing codebases — strong evidence this "declarative base class + override hooks" shape scales to many tests written by many different authors over time, which is exactly the goal stated for `tai_test.py` ("a structure that scales and will be allowed [to be] followed").
## Cons / limitations / caveats
- Domain-specific to physics-software testing (comparing program output against reference files); not every detail (e.g. reference-file resolution relative to the test's own location) was adopted, since `tai_test.py`'s tests assert on vault state and search output rather than diffing against golden files.
- Reviewed via its merge request description and generated source documentation, not a full read of the implementation's actual Python source.
## Relevance to tai
The direct architectural model for `tai_test.py.TestCase`: class-level attributes (`name`, `timeout`, `mode`) instead of constructor boilerplate, and override hooks (`setup`/`run`/`verify`/`teardown`) instead of one monolithic test method — mirroring Gaudi's class-attribute configuration plus its overridable `preprocessor` hook.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,50 @@
# gem5 test infrastructure — Timer-based timeout + typed exceptions
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | tests: Add test infrastructure as a Python module (commit), and companion helpers.py |
| Source URL | https://git.sanchayanmaity.net/sanchayanmaity/gem5/commit/f385adc8af744e7f6b78ff9adafd9c3ecf65ce68 |
| Source publisher / author | gem5 project (computer architecture simulator) |
| Source type | repository (commit + source file) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
A commit adding Python test infrastructure to the gem5 simulator project, together with its companion `tests/testing/helpers.py`. The helper uses a `threading.Timer` to enforce a timeout on a running subprocess: the timer's callback terminates the child process, then the surrounding code raises a dedicated exception carrying both the captured exit status and the process's output.
## Key findings
- Timeout enforcement pattern: `Timer(timeout, on_timeout_handler)` started before `communicate()`-ing with the process, cancelled afterward if the process finished in time.
- On timeout, the handler terminates the child and a dedicated exception type is raised carrying the captured status and output together — the caller doesn't have to separately go fetch output after catching a generic timeout error.
- gem5's newer test framework (noted in the same research pass) further splits each test into a "run" stage and a "verify" stage, where verify units are automatically skipped if their corresponding run stage didn't execute.
## Pros / strengths
- The "exception carries the captured output" design is directly actionable and immediately reusable — it solves the exact problem of "how do you debug a timed-out test" by attaching the evidence to the failure itself rather than requiring separate log inspection.
- The run/verify stage split is a clean idea for keeping a test's "did it crash" question separate from "did it produce the right result."
## Cons / limitations / caveats
- Uses `threading.Timer`, a synchronous/thread-based mechanism, not `asyncio.wait_for` — `tai_test.py` adapted the *idea* (typed exception carrying output) to an asyncio-native mechanism (`asyncio.wait_for` plus try/except) rather than reusing gem5's thread-based implementation directly.
- Reviewed via commit metadata and the helper file's described behavior, not gem5's full test suite or its actual usage patterns in practice.
## Relevance to tai
The direct model for `tai_test.py`'s exception hierarchy: `TestTimeout`, `TestCrashed`, and `TestAssertionFailed` all subclass `TaiTestError`, which carries a `transcript` list — so a failing test's exception *is* the debugging evidence, exactly matching gem5's "exception carries status and output" pattern, just built on `asyncio.wait_for`/try-except instead of `threading.Timer`.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# BunkerWeb tests/Test.py
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Test.py (BunkerWeb) |
| Source URL | https://gitea.com/palmcoasty/bunkerweb/src/branch/master/tests/Test.py |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "python integration test framework design base class" search query as another real project's custom `Test` base class; not individually opened or quoted — the Tier 1 architectural models used for this subject's conclusion are Gaudi, gem5, and oslotest instead.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# distributedcloud/dccommon/subprocess_cleanup.py — StarlingX distcloud
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | subprocess_cleanup.py (StarlingX distcloud) |
| Source URL | https://gitea13.opendev.org:3081/starlingx/distcloud/src/commit/6435d6c3572562ff2e8e6b23075b4086d42ac8ae/distributedcloud/dccommon/subprocess_cleanup.py |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio create_subprocess_exec start_new_session" search query as a real project's dedicated subprocess-cleanup module; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,49 @@
# sync: interactive launches strip the harness of its controlling terminal (start_new_session=True) · Issue #1 · anntnzrb/agents
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | sync: interactive launches strip the harness of its controlling terminal (start_new_session=True) |
| Source URL | https://github.com/anntnzrb/agents/issues/1 |
| Source publisher / author | GitHub user anntnzrb |
| Source type | repository (issue) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
A GitHub issue documenting a real, encountered bug: a test/automation harness launched an interactive child process with `start_new_session=True`, which left the harness without a controlling terminal and broke window-resize and Ctrl-C handling for the child. The issue includes a small probe script for diagnosing whether a child process actually has a controlling terminal.
## Key findings
- `start_new_session=True` makes the child a new session leader; if that child is then given a pty as stdin/stdout/stderr, whether the pty becomes its *controlling* terminal (in the POSIX job-control sense) is not automatic — the issue describes a concrete case where it did not, breaking SIGWINCH-based resize and tty-line-discipline Ctrl-C-as-SIGINT.
- The reporter's diagnostic approach: a probe script that checks `os.isatty(0)`, `os.tcgetpgrp(0)`, and whether `/dev/tty` can be opened, to confirm a child process actually has a controlling terminal rather than just an open pty fd.
## Pros / strengths
- A real, lived-through failure report rather than theoretical speculation — exactly the kind of pitfall that's easy to get subtly wrong when combining `start_new_session=True` (needed for safe process-group-wide `killpg` cleanup) with pty-based spawning (needed for `isatty()`-correct interactive behavior).
- The diagnostic probe script is a concrete, reusable technique for anyone needing to verify controlling-terminal status.
## Cons / limitations / caveats
- A single GitHub issue report, not a kernel/POSIX specification — the exact conditions under which a session leader's first terminal open becomes its controlling terminal can depend on how the fd was obtained (opened directly by the child vs. inherited already-open from the parent), and this issue doesn't fully disambiguate that for the inherited-fd case `tai_test.py` actually uses.
- No resolution/fix is confirmed as verified in this research pass (the issue's eventual outcome was not reviewed).
## Relevance to tai
Directly shaped `tai_test.py`'s documented, accepted limitation: its pty mode does not explicitly call `ioctl(TIOCSCTTY)`, so controlling-terminal/job-control semantics (SIGWINCH forwarding, Ctrl-C-as-SIGINT from the tty line discipline) are not guaranteed. This was judged acceptable because `tai.py`'s own code only checks `sys.stdin.isatty()` (a property of the fd itself, unaffected by session/controlling-terminal status) and `tai_test.py`'s tests only script line-based input/output, never relying on resize or terminal-generated signals.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,50 @@
# Non blocking file reader for python asyncio · GitHub Gist
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Non blocking file reader for python asyncio |
| Source URL | https://gist.github.com/nilp0inter/5f400b6afb6df7c0b553 |
| Source publisher / author | GitHub user nilp0inter |
| Source type | repository (gist) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
A GitHub gist demonstrating the correct way to read stdin (or any fd) without blocking the asyncio event loop: use `loop.add_reader(fd, callback)` where the callback does a raw `os.read(fd, n)`, never a call through Python's buffered `sys.stdin` object, which blocks regardless of `add_reader`.
## Key findings
- Calling `sys.stdin.read()` inside an `add_reader` callback still blocks, because Python's buffered stdin object waits for more data internally — `add_reader` only tells the event loop the fd is readable, it does not change how a buffered read call behaves.
- The correct pattern is `os.read(fileno, n)` on the raw file descriptor inside the callback.
- On EOF (an empty read), the callback should call `loop.remove_reader(fileno)` to stop watching the fd.
## Pros / strengths
- Identifies a genuine, easy-to-make mistake (buffered read inside a non-blocking callback) with a precise fix, backed by a reported working example.
- The pattern is minimal and requires no third-party dependency — pure stdlib (`os`, `asyncio`).
## Cons / limitations / caveats
- A gist, not an authoritative reference — no discussion of edge cases like partial reads mid-line or how this interacts with terminal raw/cooked mode.
- Only covers the read side; doesn't address writing back to the child or coordinating with a separate output stream.
## Relevance to tai
This is the exact pattern `tai_test.py`'s `StdinBridge._on_readable()` implements: `os.read(self._fd, 4096)` inside an `add_reader` callback, buffering until a newline, and calling `self.stop()` (which internally calls `loop.remove_reader`) on an empty read. Using `sys.stdin.read()` instead would have silently reintroduced blocking behavior that defeats the entire point of the non-blocking design.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# h0lley/nonBlockingStdin — GitHub
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | nonBlockingStdin |
| Source URL | https://github.com/h0lley/nonBlockingStdin |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query as a small repository dedicated to exactly this problem; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,49 @@
# mcp-remote-py stdio_jsonl.py — stdin bridged via loop.connect_read_pipe
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | mcp_remote_py/stdio_jsonl.py |
| Source URL | https://glama.ai/mcp/servers/@elphinsnow/mcp-remote-py/blob/8ea9ac8b79f7ad844362b51b53f354834c9f5828/mcp_remote_py/stdio_jsonl.py |
| Source publisher / author | GitHub user elphinsnow (mcp-remote-py project), via glama.ai mirror |
| Source type | repository |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
Source code from an MCP (Model Context Protocol) server implementation showing a Unix-only, thread-free pattern for bridging real stdin into asyncio: build an `asyncio.StreamReader` plus a `StreamReaderProtocol`, then call `loop.connect_read_pipe(lambda: protocol, sys.stdin.buffer)`. On Windows, the same codebase falls back to a blocking-readline-in-a-thread approach instead.
## Key findings
- `loop.connect_read_pipe(protocol_factory, pipe)` is the mechanism that turns an arbitrary readable file object into a proper `asyncio.StreamReader`-backed stream, integrated with the event loop's own backpressure and scheduling — not just a raw `add_reader` callback.
- This technique is explicitly Unix-only in the source reviewed; the same project uses a different (thread-based) strategy on Windows, acknowledging the platform split rather than pretending one approach covers both.
## Pros / strengths
- A concrete, working, real-world example of integrating stdin into asyncio with no background thread — directly reusable technique, not just a theoretical description.
- The explicit Windows fallback shows the author understood and documented the technique's platform limits rather than hiding them.
## Cons / limitations / caveats
- The dossier reflects a synthesis of this one file's approach, not a full read of the surrounding MCP server codebase or its test coverage.
- Applies the technique to real stdin; `tai_test.py` adapts the same mechanism to a different fd (a pty master), which was not something this source itself demonstrated — the adaptation is `tai_test.py`'s own design step, not something directly sourced here.
## Relevance to tai
This is the direct technical basis for `ManagedProcess.start()`'s pty-mode branch: rather than manually polling a pty master fd with raw `add_reader` callbacks and hand-rolled buffering, it wraps the master fd in an `asyncio.StreamReader` via `loop.connect_read_pipe(lambda: protocol, self._master_file)`, giving `wait_for_pattern()`/`drain_until_eof()` a uniform, native `StreamReader.read()` interface identical to the one `asyncio.subprocess.PIPE` streams already provide in pipe mode. This is what let `tai_test.py` expose one interface for both transports instead of two different ones.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# test_local_interrupt_cleanup.py — hermes-agent
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | test_local_interrupt_cleanup.py |
| Source URL | https://download.hermesedi.duckdns.org/.hermes/hermes-agent/tests/tools/test_local_interrupt_cleanup.py |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio create_subprocess_exec start_new_session" search query — a test file from another agent project specifically about interrupt/cleanup behavior; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# OpenBB CLI test_session.py
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | test_session.py (OpenBB CLI tests) |
| Source URL | https://huggingface.co/spaces/CatPtain/OpenBB/blob/main/cli/tests/test_session.py |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess pty interactive CLI testing" search query as an example of CLI session testing code in a real project; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# rag-code-assistant: asyncio-subprocess.html (doc mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | asyncio-subprocess.html |
| Source URL | https://huggingface.co/spaces/parthtamu/rag-code-assistant/blob/main/docs/asyncio-subprocess.html |
| Source type | repository (mirrored documentation) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess pty interactive CLI testing" search query as another mirror/bundling of the stdlib asyncio-subprocess documentation (inside an unrelated RAG-assistant project's doc bundle); not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,47 @@
# Your timeout killed the process, not the process group — Iannucci Solutions
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Your timeout killed the process, not the process group |
| Source URL | https://www.iannuccisolutions.com/blog/timeout-does-not-kill-child-processes.html |
| Source publisher / author | Iannucci Solutions (blog) |
| Source type | blog post |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
A blog post documenting a real incident: a script's timeout mechanism killed only the top-level process it had launched, while a worker process that script had itself spawned kept running in the background. The root cause and fix described are directly applicable to any subprocess-timeout design.
## Key findings
- `proc.kill()`/`proc.terminate()` signal only the direct child process — if that child spawns further children (a shell command, a subagent, a worker), killing the direct child does not propagate to them.
- The documented fix: start the child in its own session/process group (e.g. `start_new_session=True` in Python), which makes the child's PID equal to its process group ID (PGID), then signal the whole group (`os.killpg(pgid, sig)`) rather than just the one process.
## Pros / strengths
- A concrete, lived incident report, not a theoretical warning — the exact failure mode (worker alive after the "parent" was killed) is precisely the risk a test framework's timeout handling needs to guard against, since `tai.py` itself can spawn subagents and shell commands.
## Cons / limitations / caveats
- Describes the synchronous `subprocess` module's behavior; the asyncio adaptation (confirming `start_new_session=True` is forwarded correctly by `asyncio.create_subprocess_exec`) was treated as a reasonable extrapolation, not independently verified against asyncio's own documentation in this specific source.
## Relevance to tai
This is the direct, primary justification for `tai_test.py`'s `ManagedProcess` always spawning with `start_new_session=True` and `terminate()` using `os.killpg(self.proc.pid, sig)` rather than `self.proc.terminate()`. Without this, a timed-out test driving `tai.py` through a shell command or a forked subagent could leave that spawned work running indefinitely in the background after the test framework believed it had cleaned up.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# IPython utils/io.py (mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | IPython utils/io.py |
| Source URL | https://mitra.stanford.edu/kundaje/marinovg/oak/various/programs/IPython/utils/io.py |
| Source type | repository (mirrored source) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query as IPython's own I/O utility source code, on the same mirror site that surfaced CPython test files for other queries in this subject; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# Streaming subprocess stdin and stdout with asyncio in Python
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Streaming subprocess stdin and stdout with asyncio in Python |
| Source URL | https://kevinmccarthy.org/2016/07/25/streaming-subprocess-stdin-and-stdout-with-asyncio-in-python/ |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess pty interactive CLI testing" search query as a blog post on the same general topic (streaming subprocess I/O with asyncio); not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# pykiso test_coordinator/test_case.py — kiso-testing docs
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | pykiso.test_coordinator.test_case |
| Source URL | https://kiso-testing.readthedocs.io/en/latest/_modules/pykiso/test_coordinator/test_case.html |
| Source type | vendor docs |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "python integration test framework design base class" search query as another embedded/hardware-testing framework's base test case module; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# How can I run an external command asynchronously from Python? — Stack Overflow (Kiwix mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | How can I run an external command asynchronously from Python? |
| Source URL | https://dev.library.kiwix.org/content/stackoverflow_en_nopic_2021-08/questions/636561/how-can-i-run-an-external-command-asynchronously-from-python |
| Source type | forum thread (Stack Overflow, offline Kiwix mirror) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio merge multiple streams" search query as a general Stack Overflow question on running async external commands from Python, archived via a Kiwix offline mirror; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# Python 3: fight for nonblocking pipe — Medium
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Python 3: fight for nonblocking pipe |
| Source URL | https://medium.com/@denismakogon/python-3-fight-for-nonblocking-pipe-68f92429d18e |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query as a blog post describing difficulty getting non-blocking pipe reads working in Python 3; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# MicroPython PR #4859
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | MicroPython pull request #4859 |
| Source URL | https://github.com/micropython/micropython/pull/4859 |
| Source type | repository (pull request) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query. Off-target/tangential: MicroPython is a different, resource-constrained Python implementation for microcontrollers, not CPython's `asyncio` — logged honestly as a near-miss rather than silently dropped.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (off-target: different Python implementation/runtime). |
@@ -0,0 +1,47 @@
# OpenStack oslotest — timeout moved from base class into a fixture
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | oslotest/timeout.py, and companion base.py commit |
| Source URL | https://gitea10.opendev.org:3081/x/deb-oslotest/src/branch/master/oslotest/timeout.py |
| Source publisher / author | OpenStack project (oslotest) |
| Source type | repository |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
OpenStack's `oslotest` originally read a timeout value from an `OS_TEST_TIMEOUT` environment variable directly inside its base test class, then later moved that behavior into a separate, opt-in fixture (`fixtures.Timeout`) rather than something every test inherits unconditionally.
## Key findings
- The commit message for the change explicitly frames it as replacing inline base-class code that checked `OS_TEST_TIMEOUT` with a dedicated fixture, so tests opt into timeout behavior rather than being forced to inherit it.
- This is a structural decision about *where* configuration lives (base class vs. opt-in fixture), not a new timeout algorithm.
## Pros / strengths
- A real example of a mature test framework deliberately refactoring away from "everything lives in the base class" toward more granular, optional composition — relevant precedent for deciding how configurable a shared test base class should be.
## Cons / limitations / caveats
- Only the timeout-related commit and file were reviewed; the broader `oslotest` fixture ecosystem and how widely this opt-in pattern is used elsewhere in the project were not examined.
## Relevance to tai
Informed the decision to make `timeout` a per-`TestCase` class attribute (with a sensible default, overridable per test and via the `--timeout` CLI flag in `main_cli`) rather than a single hardcoded constant baked into the framework's execution loop — closer in spirit to oslotest's "configurable, not force-inherited" approach than to a one-size-fits-all timeout.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# Non-blocking reading from stdin in Python — blog.pelicandd.com
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Non-blocking reading from stdin in Python |
| Source URL | https://blog.pelicandd.com/article/191 |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query as a blog post directly on this topic; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,50 @@
# Ptyprocess — Ptyprocess 0.7 documentation
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Ptyprocess — Ptyprocess 0.7 documentation |
| Source URL | https://ptyprocess.readthedocs.io/en/latest/ |
| Source publisher / author | ptyprocess maintainers (readthedocs project) |
| Source type | vendor docs |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
Ptyprocess is a Python library for spawning a child process attached to a real pseudo-terminal (pty) rather than plain pipes. Its documentation describes the behavioral guarantees a real pty gives you that a pipe cannot: unified stdio, window-size tracking with SIGWINCH delivery, and line-discipline signal generation (Ctrl-C becomes SIGINT).
## Key findings
- When a program runs under ptyprocess, all three of its standard streams (stdin/stdout/stderr) are connected to a single pty, not three separate pipes.
- Ptyprocess tracks the terminal's window size and notifies the child process with a SIGWINCH signal when it changes.
- In cooked (line-buffered) terminal mode, the kernel's tty line discipline turns a Ctrl-C keystroke (byte 0x03) into a SIGINT delivered to the foreground process group — this only happens because the device is a real pty, not a pipe.
## Pros / strengths
- Documents, in one place, exactly which "feels like a real terminal" behaviors a pty provides that a pipe does not — this is the core justification for why `tai_test.py` needed pty support at all, since `tai.py`'s own code branches on `sys.stdin.isatty()`.
- The SIGWINCH and line-discipline-SIGINT points are concrete, testable claims, not vague marketing.
## Cons / limitations / caveats
- The dossier is based on a web-search synthesis of the documentation, not a full read of the library's source or a hands-on trial of ptyprocess itself — `tai_test.py` does not depend on ptyprocess as a library, only on the general pty behavior it documents (which was reimplemented directly against the stdlib `pty` module).
- Says nothing about asyncio integration; ptyprocess itself is a synchronous/blocking library.
## Relevance to tai
Directly informed the decision to give `tai_test.py`'s pty mode a real pseudo-terminal rather than a pipe, since `tai.py` branches on `isatty()` for approval prompts and readline behavior. The SIGWINCH/Ctrl-C-SIGINT behaviors this source describes are explicitly **not** relied on by `tai_test.py` — see the companion dossier on the `start_new_session`/controlling-terminal pitfall (`github-agents-issue1-controlling-terminal.md`) and the framework's own documented limitation in its module docstring.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# py-toolbox io module docs
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | pytb.io module |
| Source URL | https://py-toolbox.readthedocs.io/en/latest/modules/io.html |
| Source type | vendor docs |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query as a general-purpose I/O utility module's documentation; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# asyncio-pipe — PyPI
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | asyncio-pipe |
| Source URL | https://pypi.org/project/asyncio-pipe/ |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query as a small package apparently dedicated to pipe/stdin integration with asyncio; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# ckill 1.0.1 — PyPI
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | ckill 1.0.1 |
| Source URL | https://pypi.org/project/ckill/1.0.1/ |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess timeout kill process group" search query as a small package apparently dedicated to child-process killing; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# pexpect — version-pinned PyPI mirrors
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | pexpect 4.0.1 (and other version-pinned mirrors) |
| Source URL | https://pypi.org/project/pexpect/4.0.1/ |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "pexpect pyte vs asyncio pty" search query: several version-pinned PyPI pages for pexpect all appeared in the same result set — 4.0.1 (listed as the primary URL above), 4.0 (https://pypi.org/project/pexpect/4.0/), 3.2 (https://pypi.python.org/pypi/pexpect/3.2), 4.1.0 (https://pypi.org/project/pexpect/4.1.0), and a CERN package-mirror listing of 4.9.0 (https://simple-repository.app.cern.ch/project/pexpect). These are redundant version/mirror listings of the same project already covered at Tier 1 in `pypi-pexpect.md`, not independent sources, so they are logged together as one card rather than five separate ones.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (redundant version mirrors of a Tier 1 source). |
@@ -0,0 +1,50 @@
# pexpect — PyPI project page
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | pexpect |
| Source URL | https://pypi.org/project/pexpect |
| Source publisher / author | pexpect maintainers (PyPI) |
| Source type | repository |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
pexpect is a well-established pure-Python library for spawning child applications, controlling them, and responding to expected patterns in their output. Its own documentation explicitly lists automated software testing as a use case. It is built on the stdlib `pty` module (Unix-only for its main features) and is a blocking/synchronous library.
## Key findings
- pexpect's documented purpose statement explicitly includes "automated software testing" alongside general automation.
- Its core features require the stdlib `pty` module, which limits full functionality to Unix-like systems (some features — waiting for patterns from file descriptors or subprocesses — are also available on Windows, per the project's own docs).
- It is blocking for async I/O (a third-party comparison table makes this claim, though the research flagged that table as coming from a competing library's vendor, i.e. a potentially biased source — still consistent with pexpect's own synchronous API design).
## Pros / strengths
- The most mature, widely-used library in this space, with a long track record specifically for testing interactive terminal programs.
- `expect()`-style pattern matching against output is exactly the turn-boundary-detection problem `tai_test.py` needed to solve for REPL-mode testing.
## Cons / limitations / caveats
- Synchronous/blocking — adopting it inside `tai_test.py` would have meant either running it in a thread executor on every call or accepting that it blocks the event loop, neither of which fit an asyncio-native framework cleanly.
- Not independently trialed in this research pass; findings are based on its documented feature list and use-case statement, not hands-on testing of its API against `tai.py`.
## Relevance to tai
Surveyed and explicitly not adopted. `tai_test.py`'s `ManagedProcess.wait_for_pattern()` is functionally pexpect's `expect()` reimplemented natively in asyncio (regex matching against an accumulating, ANSI-stripped buffer read from an `asyncio.StreamReader`), chosen specifically so the whole framework stays asyncio-native rather than mixing in a blocking dependency.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# shellous 0.4.0 — older version listing
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | shellous 0.4.0 |
| Source URL | https://pypi.org/project/shellous/0.4.0 |
| Source type | repository |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query — an older version listing of `shellous`, already covered at Tier 1 in `pypi-shellous.md`. Logged as a redundant version mirror, not an independent source.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (version mirror of a Tier 1 source). |
@@ -0,0 +1,50 @@
# shellous — Async Processes and Pipelines
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | shellous: Async Processes and Pipelines |
| Source URL | https://pypi.org/project/shellous |
| Source publisher / author | shellous project (PyPI) |
| Source type | repository |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
shellous is an asyncio-native subprocess library. Its feature list includes running a program attached to a pseudo-terminal (pty, Unix-only) and `send()`/`expect()` methods for interactively driving a subprocess by pattern-matching its output — functionally the closest existing library to what `tai_test.py` ended up building itself.
## Key findings
- shellous explicitly supports pty-backed subprocess execution, and documents that this is Unix-only.
- It offers `send()`/`expect()` primitives, i.e. "write input, then wait for a pattern in the output" — the same shape as `tai_test.py`'s `ManagedProcess.send_line()` + `wait_for_pattern()`.
- It is built on asyncio, unlike pexpect (see `pypi-pexpect.md`), which is synchronous.
## Pros / strengths
- Proof that the exact combination `tai_test.py` needed — asyncio-native, pty-capable, expect-style interaction — is a real, previously-solved design space, not a one-off idea.
- Having `send()`/`expect()` as named primitives validates `tai_test.py`'s own `send_line()`/`wait_for_pattern()` naming and shape.
## Cons / limitations / caveats
- Only the top-level feature list was reviewed, not the actual implementation or its API stability/maturity; no version number, changelog, or usage examples were independently inspected.
- `tai_test.py` did not adopt shellous as a dependency — it is pure stdlib (`pty`, `asyncio`) by design, consistent with the `tai.py` project's own zero-dependency rule, so this source served as validation of the approach rather than as a library to use.
## Relevance to tai
Confirmed that building a pty-capable, asyncio-native, expect-style subprocess controller is a well-trodden pattern, which gave confidence in `tai_test.py`'s own from-scratch implementation (`ManagedProcess`) rather than needing to invent an unprecedented design.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,48 @@
# stitch-pty — PyPI project page
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | stitch-pty |
| Source URL | https://pypi.org/project/stitch-pty/ |
| Source publisher / author | stitch-pty project (PyPI) |
| Source type | repository |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Low |
| Status | Active |
## Summary
stitch-pty is described as a cross-platform, async pseudo-terminal library for Python with integrated terminal emulation, implemented in Rust with Python bindings. The research flagged it as a young project it could not independently verify further.
## Key findings
- Claims cross-platform support (notably including Windows, unlike the stdlib `pty` module), unlike every other option surveyed in this subject.
- Implemented in Rust with bindings — a compiled-extension dependency, not a pure-Python one.
## Pros / strengths
- If its claims hold up, it would solve the Windows-support gap that every stdlib-`pty`-based approach (including `tai_test.py`'s own) has.
## Cons / limitations / caveats
- Explicitly flagged during research as young and unverified — no maturity signal (adoption, release history, test coverage) was found or checked.
- A compiled Rust extension is a real dependency, directly in tension with `tai.py`'s own zero-dependency, pure-stdlib project constraint that `tai_test.py` was built to respect.
## Relevance to tai
Surveyed and explicitly not adopted, for two independent reasons: insufficient verified maturity, and a hard conflict with the project's stdlib-only constraint. `tai_test.py` built its pty support directly on the stdlib `pty` module instead, accepting the resulting Windows limitation (POSIX-only, `pty` module is not available on Windows) as the simpler, dependency-free tradeoff.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,48 @@
# python3-pyte — Debian package page
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | python3-pyte |
| Source URL | https://packages.debian.org/python3-pyte |
| Source publisher / author | pyte maintainers, packaged for Debian |
| Source type | repository (package page) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
pyte is a simple VT100/VTxxx-compatible terminal emulator written in Python 3. Unlike pexpect or shellous, it does not spawn or control processes itself — it is fed raw bytes (that some other program wrote to a terminal) and maintains a rendered screen buffer you can then assert against (cursor position, character colors, line contents).
## Key findings
- pyte's job is purely to interpret terminal control sequences and produce a rendered screen state, not to manage any process lifecycle.
- This makes it suited to checking what a TUI actually *displays* (cursor position, colors, screen layout) rather than just what raw bytes a program emitted.
## Pros / strengths
- Solves a different, complementary problem to `tai_test.py`'s own scope: verifying rendered screen state, not just text content.
## Cons / limitations / caveats
- Adds no value for `tai_test.py`'s actual tests, which assert on text content (e.g. does a reply contain `mem:<id>`), never on cursor position or rendered color — using pyte would have been complexity with no corresponding benefit for the tests actually written.
- Findings are based on general knowledge of what pyte does plus the package page's description, not a hands-on trial.
## Relevance to tai
Considered and explicitly not needed. If a future integration test ever needs to assert on *rendered* terminal output (e.g. verifying a specific color or cursor position in `tai.py`'s colored REPL prompt, rather than just its text), pyte is the documented tool to reach for — but no current test in `tests/` requires this, so it was not adopted.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,48 @@
# `loop.add_reader` and `<<EOF` · Issue #499 · python/asyncio
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | `loop.add_reader` and `<<EOF` · Issue #499 |
| Source URL | https://github.com/python/asyncio/issues/499 |
| Source publisher / author | python/asyncio project (GitHub) |
| Source type | repository (issue) |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
An upstream issue in the (now-archived, pre-stdlib-merge) `python/asyncio` project discussing edge cases of `loop.add_reader` combined with shell here-document (`<<EOF`) input and EOF handling on stdin.
## Key findings
- `add_reader`/stdin EOF handling is treated as a known rough edge in asyncio's own issue tracker, not a fully solved, documented-everywhere corner of the API.
- The issue's existence itself is evidence that different stdin-feeding mechanisms (interactive shell vs. here-doc vs. piped file) can surface different edge-case behavior with `add_reader`.
## Pros / strengths
- Primary-source confirmation (from the asyncio project itself) that this is a genuinely tricky area, not something `tai_test.py`'s own testing was wrong to be cautious about.
## Cons / limitations / caveats
- This dossier is based on a web-search synthesis of the issue's existence and general topic, not a full read of the issue thread's technical discussion or resolution — the specific EOF-handling edge case it describes was not independently verified against `tai_test.py`'s own `StdinBridge` implementation.
- The issue predates asyncio's merge into the stdlib and may reference an older API surface.
## Relevance to tai
Reinforced the decision to keep `StdinBridge.start()` defensive: it returns `False` (no-op) when `sys.stdin.isatty()` is false, rather than attempting `add_reader` against every possible stdin configuration (pipes, here-docs, redirected files) and hoping EOF behaves consistently. `tai_test.py` only ever activates the live-stdin bridge when a real interactive terminal is confirmed present.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,48 @@
# Subprocesses — Python 3 documentation (asyncio.subprocess)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Subprocesses — Python 3.14 documentation |
| Source URL | https://docs.python.org/3/library/asyncio-subprocess.html |
| Source publisher / author | Python Software Foundation |
| Source type | standard (official stdlib documentation) |
| Source date (published) | Unknown — not stated (continuously updated) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | High |
| Status | Active |
## Summary
The canonical stdlib reference for `asyncio.subprocess`, confirming the exact semantics of `terminate()`/`kill()`, how `Process.stdout`/`Process.stderr` expose `StreamReader` instances when `PIPE` is used, and the deadlock risk of reading those streams manually instead of via `communicate()`.
## Key findings
- On POSIX, `Process.terminate()` sends SIGTERM to the child; `Process.kill()` sends SIGKILL. On Windows, `terminate()` calls the Win32 `TerminateProcess` API; there is no separate POSIX-signal distinction.
- When `stdout=PIPE` or `stderr=PIPE` is passed, `Process.stdout`/`Process.stderr` are `asyncio.StreamReader` instances — the same abstraction `tai_test.py` wraps a pty master fd into via `loop.connect_read_pipe`, for a uniform interface across both transports.
- The docs warn that reading those streams manually (rather than via `communicate()`) can deadlock, because the child can block on a full pipe buffer if its output isn't drained quickly enough while it's also waiting on something else.
## Pros / strengths
- Authoritative, primary-source documentation — the single most load-bearing source in this entire subject for API semantics.
## Cons / limitations / caveats
- Documents stdlib behavior as of whatever version was fetched; does not itself discuss process-group-wide termination (`killpg`) or the controlling-terminal/pty nuances this subject's other sources cover — those required separate sources.
## Relevance to tai
The definitive basis for `tai_test.py`'s choice of `terminate()` (SIGTERM) before escalating to `kill()` (SIGKILL) in `ManagedProcess.terminate()`'s escalation loop, and for exposing `Process.stdout`/`Process.stderr` as the pipe-mode reader rather than hand-rolling a reader over raw fds in that transport. The deadlock warning is why `ManagedProcess.drain_until_eof()` continuously reads in a loop until EOF rather than attempting a single large read.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# Interacting with sys.stdin/sys.stdout/sys.stderr — python-tulip Google Group
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Interacting with sys.stdin/sys.stdout/sys.stderr |
| Source URL | https://groups.google.com/g/python-tulip/c/6tRswyRhwE4 |
| Source type | forum thread |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query — "python-tulip" was asyncio's pre-stdlib-merge codename, so this is an early-era discussion of the exact same stdin-integration problem; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# CPython 3.7.3 Lib/test/test_asyncio/test_subprocess.py (mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Lib/test/test_asyncio/test_subprocess.py (CPython 3.7.3) |
| Source URL | https://mitra.stanford.edu/kundaje/marinovg/oak/programs/Python-3.7.3/Lib/test/test_asyncio/test_subprocess.py |
| Source type | repository (mirrored stdlib source) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess pty interactive CLI testing" search query, same mirror site as `python373-test-pty-source.md`, this time CPython's own `asyncio.subprocess` test suite; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# CPython 3.7.3 Lib/test/test_pty.py (mirror)
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Lib/test/test_pty.py (CPython 3.7.3) |
| Source URL | https://mitra.stanford.edu/kundaje/marinovg/oak/programs/Python-3.7.3/Lib/test/test_pty.py |
| Source type | repository (mirrored stdlib source) |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess pty interactive CLI testing" search query as a mirror of CPython's own stdlib `pty` module test suite; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# PEXPECT - Automation of the terminal — pythonlib.ru
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | PEXPECT - Automation of the terminal |
| Source URL | https://pythonlib.ru/en/library-theme75 |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "pexpect pyte vs asyncio pty" search query as an introductory article on pexpect; the primary pexpect source used for Tier 1 is `pypi-pexpect.md`, so this secondary article was not individually opened or quoted.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# python-asyncio-advanced — Python Sheets
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | python-asyncio-advanced |
| Source URL | https://pythonsheets.com/_sources/notes/asyncio/python-asyncio-advanced.rst |
| Source type | vendor docs |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio merge multiple streams" search query as a general advanced-asyncio cheat-sheet; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# Waiting in Python: wait(), communicate(), poll(), and the Deadlock Between Them
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Waiting in Python: wait(), communicate(), poll(), and the Deadlock Between Them |
| Source URL | https://www.runxbuild.com/blog/wait-command-python/ |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess timeout kill process group" search query as a blog post specifically about the deadlock risks between `subprocess`'s `wait()`/`communicate()`/`poll()` methods — a topic adjacent to, but not individually opened/quoted for, the Tier 1 findings on that risk in `python-docs-asyncio-subprocess.md`.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# How to read stdin without stopping a loop? — Rust Users Forum
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | How to read stdin without stopping a loop? |
| Source URL | https://users.rust-lang.org/t/how-to-read-stdin-without-stopping-a-loop/29372 |
| Source type | forum thread |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio non-blocking stdin read" search query. Off-target/tangential: this is a Rust forum discussing Rust's own stdin-handling APIs, not Python or asyncio — logged honestly as a near-miss rather than silently dropped, per this archive's quality bar of not pretending every search result was on-topic.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed (off-target: wrong language). |
@@ -0,0 +1,28 @@
# Running Python code in a subprocess with a time limit — Simon Willison's TIL
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Running Python code in a subprocess with a time limit |
| Source URL | https://til.simonwillison.net/python/subprocess-time-limit |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio subprocess timeout kill process group" search query as a "today I learned" note on enforcing a subprocess time limit; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,46 @@
# Python asyncio: How to stop/kill a child process — Sling Academy
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Dossier |
| Tier | 1 |
| Source title | Python asyncio: How to stop/kill a child process |
| Source URL | https://www.slingacademy.com/article/python-asyncio-how-to-stop-kill-a-child-process/ |
| Source publisher / author | Sling Academy |
| Source type | blog post |
| Source date (published) | Unknown — not stated |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | web search synthesis |
| Confidence | Medium |
| Status | Active |
## Summary
A practical how-to article confirming the terminate()-then-kill() escalation pattern specifically for `asyncio` subprocesses (as opposed to the synchronous `subprocess` module, which several other sources in this subject focus on).
## Key findings
- Confirms the same two-stage (SIGTERM, then SIGKILL after a grace period) escalation pattern applies directly to `asyncio.subprocess.Process`, not just the synchronous `subprocess.Popen`.
## Pros / strengths
- Asyncio-specific confirmation, closing a small gap left by sources that primarily discuss the synchronous `subprocess` module.
## Cons / limitations / caveats
- A how-to-style article without deep discussion of process groups or zombie-reaping; those details came from other sources in this subject (`iannuccisolutions-timeout-process-group.md`, `alexandra-zaharia-kill-subprocess-children-timeout.md`).
## Relevance to tai
One more confirming data point that `ManagedProcess.terminate()`'s SIGTERM-then-SIGKILL escalation is correct for the asyncio subprocess API specifically, not just a synchronous-API pattern being assumed to transfer.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Initial dossier created. |
@@ -0,0 +1,28 @@
# tessl.io — sh I/O handling docs
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | io-handling.md (pypi-sh package docs) |
| Source URL | https://tessl.io/registry/tessl/pypi-sh/2.2.0/files/docs/io-handling.md |
| Source type | vendor docs |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query as documentation for the `sh` package's I/O handling; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |
@@ -0,0 +1,28 @@
# Redirect output of process to a file and streams — TutorialsPoint
## Metadata
| Field | Value |
|---|---|
| Institute | Molodetz Institute — Research & Retrieval Division |
| Subject | async-pty-process-testing |
| Document type | Source Citation Card |
| Tier | 2 |
| Source title | Redirect output of process to a file and streams |
| Source URL | https://www.tutorialspoint.com/article/redirect-output-of-process-to-a-file-and-streams |
| Source type | blog post |
| Date researched | 2026-10-09 |
| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor |
| Research method | Search aggregator result listing (not independently opened or quoted) |
| Confidence | Not independently verified |
| Status | Active |
## Why it's here
Surfaced by the "asyncio tee process output to file and stdout" search query as a general article on redirecting process output; not individually opened or quoted during this research pass.
## Changelog
| Date | Change |
|---|---|
| 2026-10-09 | Logged as consulted-but-not-analyzed. |