{"vulnerability": "CVE-2021-30481", "sightings": [{"uuid": "35bb5e90-28fb-4bcd-94a2-98eea936e4b9", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "published-proof-of-concept", "source": "Telegram/XEi2T4_l_Bz3t1f9DHQqkd2gD4aijlbyHseTlVU6iTgmWso", "content": "", "creation_timestamp": "2021-10-18T09:09:38.000000Z"}, {"uuid": "5ccddd7f-2c57-4852-ae46-be695dcf129d", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "published-proof-of-concept", "source": "https://t.me/thebugbountyhunter/5323", "content": "CVE-2021-30481: Source engine remote code execution via game invites https://secret.club/2021/04/20/source-engine-rce-invite.html", "creation_timestamp": "2021-04-20T19:03:29.000000Z"}, {"uuid": "ab80aa87-2d28-4554-b0dd-8b59bcde29f5", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "published-proof-of-concept", "source": "https://t.me/CyberSecurityTechnologies/3180", "content": "#Threat_Research\n1. CVE-2021-30481:\nSource engine RCE via game invites\nhttps://secret.club/2021/04/20/source-engine-rce-invite.html\n2. Analysis of a use-after-free Vulnerability in Adobe Acrobat Reader DC\nhttps://blog.exodusintel.com/2021/04/20/analysis-of-a-use-after-free-vulnerability-in-adobe-acrobat-reader-dc", "creation_timestamp": "2024-05-08T02:13:50.000000Z"}, {"uuid": "e31df379-ee3e-47c1-baf2-7849372e6fa7", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "published-proof-of-concept", "source": "https://t.me/reverseame/1112", "content": "CVE-2021-30481: Source engine remote code execution via game invites https://secret.club/2021/04/20/source-engine-rce-invite.html", "creation_timestamp": "2026-08-03T11:06:34.726215Z"}, {"uuid": "2f24327a-4e49-4339-9014-4e5bb7695d9b", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "seen", "source": "https://gist.github.com/YoraiLevi/acaa0b584389cecae0036db4624f0210", "content": "# The Terminal Middle Layer: A Map of the Space\n\n## 0. What this document is, and how to read it\n\n### 0.1 The problem\n\nAn **ADE** (Agentic Development Environment \u2014 a desktop app that spawns coding-agent CLIs, one per git worktree, each in its own terminal pane) owns the PTY today. When the ADE dies, restarts, or gets replaced, every agent session dies with it. ADEs churn fast: this census records six that died or closed in the last eight months.\n\nThe thing being shopped for is a layer that sits *under* the shell and *above* the agent, owned by neither:\n\n```\n   ADE layer         ORCA today, a different ADE tomorrow \u2014 deliberately swappable\n      |  spawns via an overridable launch command\n   MIDDLE LAYER      &lt;-- the subject of this document\n      |  owns the PTY; exposes an ADE-independent interface\n   agent CLI         claude, codex, opencode, copilot \u2026\n```\n\nORCA (stablyai/orca, Electron + node-pty) is the current ADE. Its per-agent launch command is overridable \u2014 `agentCmdOverrides` `[SRC src/shared/types.ts, consumed at src/shared/tui-agent-launch-command.ts:30]` \u2014 so the hook point for inserting a middle layer exists and is verified.\n\n**The four hard constraints**, applied as pass/fail throughout:\n\n| # | Constraint | What fails it |\n|---|---|---|\n| 1 | **Native Windows 11, no WSL** | Anything POSIX-only, WSL-required, or Linux-container-only |\n| 2 | **Open source** | Commercial/closed tools are reported as context, clearly labelled, never as candidates |\n| 3 | **Cross-platform is a plus, not a requirement** | Windows-only is a demerit, not a disqualifier (the user also runs Linux) |\n| 4 | **Headless-first** | Anything that needs a human attached, or a GUI, to be useful |\n\nPlus one topology requirement that is not a constraint but kills candidates anyway: **nestability**. The middle layer runs *inside* the ADE's PTY, so prefix-key collisions, resize propagation, alt-screen handling, mouse passthrough and double VT parsing all matter.\n\nResearch date: 2026-08-02. Star counts and last-activity dates churn weekly \u2014 treat every number as a 2026-08-02 snapshot.\n\n### 0.2 Table of contents\n\n| Section | What is in it |\n|---|---|\n| [1. Verdict](#1-verdict) | The three findings, in one page |\n| [2. The Taxonomy](#2-the-taxonomy) | Ten axes for placing any tool, present or future, plus a placement matrix and a worked example |\n| [3. The Full Inventory](#3-the-full-inventory) | Every candidate found, viable and refuted \u2014 3.1 native-Windows daemons, 3.2 POSIX ancestors, 3.3 PTY-as-a-service, 3.4 ADEs, 3.5 containers, 3.6 protocols, 3.7 PTY libraries, 3.8 non-multiplexer channels |\n| [4. Middle-Layer Fitness](#4-middle-layer-fitness) | Candidates scored on the five things that matter. **4.1 is the build spec for Windows** and **4.2 is the nesting hazard**; if you read two subsections, read those |\n| [5. The Interface Options Map](#5-the-interface-options-map) | Seven interface shapes; **5.1 is the verb set a middle layer must expose** |\n| [6. Negative Results and Dead Ends](#6-negative-results-and-dead-ends) | ConPTY facts that mislead, node-pty defects live in ORCA today, why late-attach is refuted |\n| [7. What Was Refuted](#7-what-was-refuted) | 50 claims killed during research \u2014 do not re-import any of them |\n| [8. Open Questions](#8-open-questions) | 24 questions with a next step, a cost, and what the answer changes |\n| [9. Coverage Gaps](#9-coverage-gaps) | What is *not* verified, ordered by how much damage a wrong assumption does |\n| [10. How to narrow this yourself](#10-how-to-narrow-this-yourself) | A decision scaffold keyed on your answers, not mine |\n| [11. If you decide to build it](#11-if-you-decide-to-build-it) | The four places in this document that together are most of a build spec |\n\n### 0.3 Glossary\n\nTerminal-emulator vocabulary used throughout, defined once.\n\n| Term | Meaning |\n|---|---|\n| **ADE** | Agentic Development Environment \u2014 a desktop app that spawns and supervises coding-agent CLIs, typically one per git worktree. ORCA, Zed, kandev, Pane, herdr are ADEs |\n| **PTY / pseudo-terminal** | A kernel object pair that lets one process pretend to be a terminal for another. On Unix it is `/dev/ptmx`; on Windows it is ConPTY |\n| **ConPTY** | Windows' pseudo-console API (Win10 1809+). `CreatePseudoConsole` returns an **HPCON** handle; the OS spawns a headless `conhost.exe` behind it that translates between VT byte streams and the classic Windows console API |\n| **HPCON** | The opaque handle identifying one ConPTY instance. Whoever holds it is the only party that can resize it |\n| **alt-screen** | The secondary screen buffer (`CSI ?1049h`) that full-screen TUIs switch into so they can restore the scrollback on exit. Nesting two programs that both use it produces visible corruption if either mishandles the switch |\n| **DA1** | \"Device Attributes, primary\" \u2014 the `CSI c` query a terminal is expected to answer with a capability report. ConPTY sends one at startup and blocks for up to 3 s waiting for a reply |\n| **CPR / `ESC[6n`** | \"Cursor Position Report\" \u2014 a query asking the terminal where the cursor is. ConPTY emits one at startup when `INHERIT_CURSOR` is set, and will not service the child's console connection until it is answered |\n| **XTWINOPS `CSI 18 t`** | An xterm query meaning \"how many rows and columns are you?\", answered with `CSI 8 ; rows ; cols t`. The only in-band way to learn a size when there is no SIGWINCH |\n| **OSC 133 / OSC 9;4 / OSC 777** | Operating System Command escapes carrying semantic metadata rather than glyphs: 133 marks prompt/command/output boundaries, 9;4 sets Windows taskbar progress, 777 is a notification channel |\n| **win32-input-mode** | An in-band negotiated mode (`CSI ?9001h`) in which a ConPTY-hosted child receives full Win32 `INPUT_RECORD` key fidelity encoded as VT, instead of lossy VT key encoding. Negotiated at runtime, **not** a creation flag \u2014 see 6.1 |\n| **integrity level** | The Windows mandatory-access-control tier of a process (Low / Medium / High / System). A lower-integrity process cannot read a higher-integrity console |\n| **session 0** | The Windows terminal-services session reserved for services. Interactive logons get session 1+. Console objects do not cross the boundary |\n| **`-CC` control mode** | tmux's line-based machine protocol: you send tmux commands, it replies with `%begin`/`%end`/`%output`/`%layout-change` lines. Lets an outer program own rendering while tmux owns sessions. Fully described in 3.6 |\n| **double parse** | Two VT parsers in series, each rendering and re-emitting: multiplexer parses the agent's bytes into a grid, re-emits ANSI, ADE parses that again. Costs fidelity at every hop. Section 2.2 shows it is avoidable |\n| **L0-L3** | Persistence levels defined in 2.9: 0 = dies with the connection, 1 = survives client disconnect, 2 = survives the ADE, 3 = survives a reboot |\n\n### 0.4 Evidence tiers\n\nEvery claim carries its evidence tier, separated from judgement. **An untagged cell is not verified** \u2014 it is inherited from a row note, a neighbouring citation, or (in the tables that predate this convention) simply unverified. Do not read absence of a tag as a stronger claim than the weakest tag present.\n\n| Tag | Meaning |\n|---|---|\n| `[SRC]` | Verified by reading source code or a config/manifest file |\n| `[ART]` | Verified by release-artifact list, CI config, or package manifest |\n| `[DOC]` | Verified against a project's **own** primary documentation |\n| `[2ND]` | Secondary source only \u2014 blog, aggregator, third-party writeup, field report |\n| `[EMP]` | Verified empirically on this machine (native Windows 11, build 26200) |\n| `[INF]` | Inference from verified facts \u2014 reasoning, not observation |\n| `[UNV]` | Asserted somewhere, not verified in any pass |\n| **Judgement:** | Reading of the evidence, not the evidence. Always labelled |\n\n### 0.5 Verdict vocabulary\n\nThe Verdict column in every section-3 table uses exactly these words. Each has a test.\n\n| Verdict | Test it passed / failed |\n|---|---|\n| **VIABLE** | Meets all four hard constraints, is installable or vendorable today, and every load-bearing claim was verified at source |\n| **VIABLE-WITH-CAVEAT** | As above, but one named property is degraded or unproven. The caveat is stated inline \u2014 read it, it is always the deciding factor |\n| **EXTRACTABLE** | Works and is verified, but is not shipped as a standalone product. You vendor the package or run the host binary |\n| **UNVALIDATED** | Design is sound and source-verified, but nobody outside the author has run it on Windows. Zero external issues, zero external CI, or zero users |\n| **REFUTED** | Fails a hard constraint. The failing constraint is always named |\n| **CONTEXT** | Not a candidate. Read it for a design idea, a precedent, or a warning |\n| **DEAD** | Archived, sunset, or abandoned by its own maintainers |\n| **NEGATIVE SPACE** | A thing that does not exist. Recorded so it is not searched for again |\n\nNote the previous editions of this document used ~18 different verdict strings; `UNVERIFIED` and `REFUTED (no external validation)` have both been normalised to **UNVALIDATED**, and `VIABLE (extract, not install)` / `VIABLE (vendor the package)` to **EXTRACTABLE**. One status per candidate, stated once in 3.x, propagated to 1 and 4.\n\n### 0.6 Roster \u2014 the names, before they arrive cold\n\nEvery tool named in section 1 or the taxonomy, with one clause and where it is covered. Read this once and section 2 becomes readable without jumping.\n\n| Name | One clause | Covered in |\n|---|---|---|\n| **psmux** | Rust tmux-workalike, Windows-only, loopback TCP + tmux `-CC` control mode, 3.1k\u2605 | 3.1, 4, 4.2 |\n| **Zellij** | Rust multiplexer, 34.6k\u2605, Windows port shipped 0.44.0, named pipes, WASM plugins | 3.1, 4 |\n| **rmux** | Rust tmux-verb-compatible daemon, 2.5k\u2605, named pipes scoped by SID + integrity level | 3.1, 4 |\n| **herdr** | Rust multiplexer/ADE hybrid, 23.5k\u2605, Windows on the preview channel only | 3.1, 4 |\n| **oly** (slaveOftime/open-relay) | Rust session daemon, 89\u2605, named-pipe IPC + HTTP/WS | 3.1, 4 |\n| **qscreen** | Rust single-pane session daemon, 6\u2605, structured frame protocol + raw-byte attach | 3.1, 4 |\n| **quil** | Go session daemon, 11\u2605, MCP server with 18 tools, ConPTY but AF_UNIX IPC | 3.1, 4 |\n| **`wezterm-mux-server`** | The headless multiplexer inside WezTerm (28k\u2605); 15 CLI verbs, versioned binary codec | 3.1, 4 |\n| **OpenCode `/pty`** | An undocumented HTTP+WebSocket PTY API inside OpenCode (192k\u2605) | 3.3, 3.6, 4 |\n| **`ao pty-host`** | A hidden detached-ConPTY subcommand inside Agent Orchestrator (8.7k\u2605) | 3.1, 3.4, 4 |\n| **upterm** | Go session-sharing host over SSH, real ConPTY, but no detach/reattach | 3.1, 4 |\n| **boo** (coder/boo) | Zig screen-replacement on libghostty-vt with `peek --json`/`wait --idle`; POSIX-only | 3.2 |\n| **ht** (andyk/ht) | Rust \"wrap any binary with a terminal interface\"; the shape to copy, POSIX-only | 3.3 |\n| **ripple** (yotsuda) | C# MCP + ConPTY session tool; cited only for its `VtLiteState` and a post-mortem doc | 2.2, 7.1 |\n| **tuios**, **dtach**, **abduco/dvtm**, **reptyr** | POSIX-only ancestors and refuted candidates, kept for their design lessons | 3.2 |\n| **ORCA** | The ADE in use. Electron + node-pty. `agentCmdOverrides` is the hook point | 3.4 |\n\n---\n\n## 1. Verdict\n\n**The middle layer already exists \u2014 ten times over \u2014 and not one of them is packaged as the thing you want.**\n\nSource-level verification found **ten** projects that meet the structural predicate: *they run natively on Windows over ConPTY, they survive the client that spawned them dying, and they expose a non-GUI interface a third party can drive.* Seven are standalone daemons:\n\n| Project | Transport | Status (see 0.5) |\n|---|---|---|\n| **psmux** | loopback TCP + tmux `-CC` control mode | VIABLE-WITH-CAVEAT \u2014 Windows-only, 8 months old |\n| **Zellij** | named pipes via the `interprocess` crate, Windows port shipped in 0.44.0 | VIABLE |\n| **rmux** | named pipes, namespaced per SID + integrity level | VIABLE |\n| **herdr** | named pipes, 23.5k stars | VIABLE-WITH-CAVEAT \u2014 Windows on the preview channel only |\n| **oly/open-relay** | named pipes + HTTP/WS | VIABLE-WITH-CAVEAT \u2014 unauthenticated pipe, no ACL |\n| **qscreen** | named pipes, raw-byte attach mode | UNVALIDATED \u2014 6 stars, zero CI, zero external users |\n| **quil** | **AF_UNIX on all platforms**, not named pipes \u2014 the \"Named Pipes on Windows\" claim in its own ADR-2 was never implemented | UNVALIDATED \u2014 zero Windows CI |\n\nThree more exist *inside* larger products and are **EXTRACTABLE** rather than installable: **`wezterm-mux-server`** (headless by design, versioned binary codec, 15 CLI verbs, first-class attach/detach), **OpenCode's `/pty` HTTP+WebSocket API** (replay cursor, ticket auth, shipped in the Windows binary, documented nowhere on its own website), and **Agent Orchestrator's `ao pty-host`** (detached ConPTY host over loopback TCP with scrollback replay and largest-client resize arbitration).\n\n**Note on the predicate.** An earlier edition of this document framed the set as \"non-POSIX-IPC\". That framing was wrong and is retired: **AF_UNIX is a real Windows transport since Windows 10 1803**, so a `UnixListener` in source proves nothing about platform support either way \u2014 see 2.8. What separates these ten from everything else is ConPTY + survival + a non-GUI interface, nothing about the socket family. quil is in the set *and* uses AF_UNIX; both are true.\n\n**Judgement:** there is no leader. Each candidate forfeits exactly one of {maturity, Windows-nativeness, standalone packaging, contract stability}. psmux is Windows-only and 8 months old. Zellij is the most mature but its Windows input path keys off `TERM`/`WT_SESSION`, which an ADE PTY may not set \u2014 it picks the *native-console* path, which is implemented and conservative, so this is a config check rather than a breakage (see Q3). WezTerm's mux server is the strongest engineering but **its newest tagged release is 2024-02-03, two and a half years old** \u2014 the current Windows path is the rolling `nightly` tag. OpenCode's `/pty` is undocumented and its wire is UTF-8 text, not raw bytes. `ao pty-host` is a `Hidden: true` subcommand inside an 8.7k-star ADE.\n\n**The second finding reframes the question.** The middle layer may not need to own a PTY at all. `claude -p --input-format stream-json --output-format stream-json` is a full-duplex NDJSON channel over ordinary pipes, identical on Windows and Linux; Claude Code hooks are a *write* path (`permissionDecision` + `updatedInput` answers interactive prompts headlessly, and `type: \"http\"` hooks let a long-lived process you own be the endpoint \u2014 for the 13 events that support `http`; SessionStart and Setup do not); OpenTelemetry emits 34 named `claude_code.*` identifiers including `claude_code.tool.blocked_on_user`. PTY ownership buys exactly two things those cannot: exact on-screen state, and a human typing into a live session.\n\n**What was foreclosed.** Anthropic closed both network-attach requests as `not_planned` (#24365, #6686) \u2014 do not bet on Claude Code growing this natively. Non-WSL container paths narrow to two open-source options (Podman `hyperv`, minikube `--driver=hyperv`). Late-attach via `AttachConsole` is a read-only escape hatch at best. No terminal-control standard exists \u2014 MCP has zero terminal SEPs among 41 Final ones.\n\n---\n\n## 2. The Taxonomy\n\nThis is the durable part. Place any future tool on these ten axes and you will know what it is without re-surveying. Each axis carries a **How to determine it** column \u2014 the concrete check that places a tool you have never seen, so the axes work on tools that do not exist yet.\n\nAxes 2, 8, 9 and 10 replaced earlier formulations that turned out to be wrong. What was wrong with each, and the evidence that killed it, is recorded in 7.1.\n\nTwo navigation aids follow the axes: **2.11** places every live candidate on all ten axes in one matrix, and **2.12** walks a tool that is *not* in the census through the ten checks end to end.\n\n### 2.1 Session/window coupling\n\n| Pole | Meaning | How to determine it | Instances |\n|---|---|---|---|\n| Monolithic | One binary owns session persistence AND pane/window layout | One executable, and killing it loses both the layout and the sessions | screen, tmux, Zellij, psmux, rmux, herdr, boo |\n| Split | Persistence and layout are separate, swappable programs | The layout program appears as an *argument* to the session program, and can be replaced with any other command | abduco + dvtm |\n| Session-only | No layout concept at all | No `split`, `new-window` or `select-pane` verb exists anywhere in the CLI | dtach, qscreen (single pane by design), upterm |\n\n**Judgement:** the split pole (abduco/dvtm) is the closest existing precedent for what you are building \u2014 a session layer that deliberately does not own rendering. `[DOC brain-dump.org/blog/abduco-dvtm-a-lightweight-alternative-to-tmux-and-screen/]` abduco's author states it directly: \"these are two distinct features: window and session management shouldn't be intermingled\". Both halves are dormant (abduco last commit 2020-04-30, dvtm 2021-03-06) and POSIX-only, so the idea travels but the code does not.\n\n### 2.2 Terminal-state ownership \u2014 three poles, not two\n\nThe original formulation (\"rendering multiplexer vs transparent byte-passer\") was refuted. There is a third pole and most modern candidates sit in it.\n\n| Pole | Behaviour | How to determine it | Instances |\n|---|---|---|---|\n| Opaque relay | No screen model; forwards bytes verbatim; cannot redraw on reattach | No VT/emulator crate in the dependency manifest at all | dtach `[DOC dtach README]` \u2014 \"dtach does not have a terminal emulation layer, and passes the raw output stream of the program to the attached terminals\" |\n| Full VT owner | Parses every escape into a grid, re-emits rendered ANSI | The live-path payload type is a **string** or a rendered-cell struct, not a byte slice | screen, tmux, Zellij, psmux TUI mode, herdr, ripple's `VtLiteState` |\n| **Parallel model, raw live path** | Maintains a VT model for snapshot/resize/scrollback bookkeeping, but ships **raw child bytes** on the live path | The live-path payload type carries `bytes` / `Vec` / `[]byte`, *and* a separate VT model exists for snapshots | qscreen `AttachMode::Bytes` `[SRC crates/qscreen-protocol/src/lib.rs:56-61]`; oly `ServerMessage::Data { data: Vec }` `[SRC src/http/ws.rs:36-46]`; quil `PaneOutputPayload { Data []byte }` `[SRC internal/ipc/protocol.go:204-208]`; psmux `-CC` `%output` (pane output ring, octal-escaped) `[SRC src/server/mod.rs:1222-1227]` |\n\n**Why this matters for nesting.** The third pole means the ADE's own xterm.js can be the *only* VT parser in the chain while the daemon still replays scrollback on reattach. The \"unavoidable double parse\" framing is false \u2014 it is avoidable today, with shipped interfaces, in three of the four small candidates.\n\n**Apply the same test to OpenCode `/pty` and it lands in the second pole, not the third.** `[SRC packages/core/src/pty/protocol.ts]` its outbound frames are described as \"raw UTF-8 terminal chunks\", but the payload type is a JS `string`: `chunks(data: string)` slices at `REPLAY_CHUNK = 64 * 1024` using `String.slice`, i.e. **UTF-16 code units**, so a surrogate pair straddling a replay-chunk boundary is split into lone surrogates; and `decodeInput` uses `new TextDecoder(\"utf-8\", { fatal: true })` inside a `try { \u2026 } catch { return undefined }`, so **invalid UTF-8 on the input path is silently dropped** \u2014 the file's own comment says so. This is the same failure class as psmux's `String::from_utf8_lossy` (4.2, third-order), and it is called out here for the same reason: applied consistently, the criterion demotes a candidate this document otherwise praises.\n\n### 2.3 Client/server namespace model\n\nOrdered worst to best. Note the poles are about *who can collide with you*, not about the transport.\n\n| Pole | Mechanism | How to determine it | Instances |\n|---|---|---|---|\n| Machine-global default | The default name contains no user, SID or project component, so two users on one host collide | Read the socket/pipe name construction: is there any per-user or per-project component in the **default** path? | oly \u2014 default `open-relay.oly.sock`, no user component `[SRC src/config.rs:123-126]` |\n| Single fixed instance (per-user, no per-project knob) | One instance per user by construction, no way to run two side by side | The name embeds a user component but no `-L`-style label, and no env var overrides it | qscreen (`\\\\.\\pipe\\qscreen-`) `[SRC crates/qscreen-shared/src/lib.rs:6-11]` |\n| Socket-name-as-namespace | The socket/pipe name IS the namespace, selectable per invocation | A `-L`/`-S` flag or a name env var exists | tmux `-L`/`-S` `[DOC tmux(1)]`, psmux `-L`/`-S`, rmux `-L`, **oly via `OLY_SOCKET_NAME`** `[SRC src/config.rs:123-126]` |\n| Env-var home split | A whole state directory per instance | An env var relocates the entire state root, not just the socket | quil `QUIL_HOME`, Claude Code `CLAUDE_CONFIG_DIR` |\n| Identity-scoped | Namespace derived from user SID + integrity level, not from a string the caller picks | The name is *computed* from `GetTokenInformation`-class data, so it cannot be spoofed by argument | rmux `[SRC crates/rmux-ipc/src/endpoint.rs]`: `\\\\.\\pipe\\{prefix}-{sid}-il-{integrity}-{label}` |\n\n**Correction, applied to a judgement this document previously got wrong.** oly's socket name is **not hardcoded**: `[SRC src/config.rs:123-126]` reads `std::env::var(\"OLY_SOCKET_NAME\").ok().and_then(normalize_optional_string).unwrap_or_else(|| \"open-relay.oly.sock\".to_string())` \u2014 an env-var-selectable name with a machine-global *default*. It therefore occupies the socket-name-as-namespace pole, same as tmux. Earlier editions placed it in a \"None \u2014 no isolation knob\" pole and called it the worst design found; the cited lines refute that. What survives, and is the real defect, is separate: the **default** name has no user component, and the pipe is created with **no ACL and no `FILE_FLAG_FIRST_PIPE_INSTANCE`** `[SRC src/ipc.rs, 296 lines, zero ACL/SID/security-descriptor keywords]`, so a hostile local process can pre-create the pipe and harvest client connections. Namespacing and access control are different problems and oly fails the second one, not the first.\n\n**Judgement:** identity-scoped is strictly the best design found and only rmux does it, because it is the only pole where the namespace cannot be entered by a process that merely guesses a string. Everything below it depends on the pipe's ACL for actual security, and only rmux and qscreen set one.\n\n### 2.4 Command-language uniformity\n\n| Pole | How to determine it | Instances |\n|---|---|---|\n| One grammar usable from shell, config file, and keybinding | The same verb string works as `tool verb`, as a config-file line, and as a keybinding target | tmux `[DOC tmux(1) \"COMMAND PARSING AND EXECUTION\"]`, psmux, rmux (90+ tmux-compatible verbs) |\n| Separate keybinding table bolted onto a distinct config syntax | The config file's grammar and the CLI's grammar do not overlap | screen |\n| Discoverability-first on-screen hinting | The primary interface is a rendered hint bar, and CLI verbs are secondary | Zellij `[DOC README: \"must not sacrifice simplicity for power\"]` |\n\n**Why this axis matters for a headless middle layer:** the first pole is the only one where a script, a hook and a human all speak the same language, so nothing has to be re-learned when the human leaves the loop.\n\n### 2.5 Extensibility mechanism\n\n| Pole | How to determine it | Instances |\n|---|---|---|\n| Shell hooks / pipes | Extension points take a shell command string | tmux hooks, `pipe-pane`, screen backtick |\n| External wire protocol for driving the multiplexer | A documented framing exists that a non-child process can speak | tmux `-CC` control mode, psmux `-CC`, qscreen `ScreenFrame`, WezTerm mux codec |\n| Sandboxed compiled plugin runtime | Plugins ship as compiled artifacts against a schema'd host API | Zellij WASM plugins (protobuf-schema'd since 0.38.0) `[DOC zellij.dev/news/session-manager-protobuffs/]` |\n| Embedded scripting runtime | An interpreter is linked in and the config file is a program | WezTerm (Lua config + event API) |\n| Typed SDK | A published client library exists in at least one language, with types | rmux (Rust/Python/TypeScript: `session`/`pane`/`snapshot`/`wait_for_text`), herdr (third-party TS + Python clients) |\n\n### 2.6 Agent-state derivation strategy\n\n| Pole | Mechanism | Instances | Failure mode |\n|---|---|---|---|\n| Vendor-emitted structured events | The agent CLI itself emits typed events | Claude Code OTel (34 `claude_code.*` identifiers) + JSONL transcripts + `stream-json`; Codex `--json` rollout events; OpenCode `/global/event` SSE | Vendor-specific; config surface changes |\n| Native typed state | The multiplexer owns a VT parser and exposes typed peek/wait/snapshot | boo `wait --idle`/`peek --json`, rmux `snapshot()`, quil `MsgScreenshotPaneResp{Text,CursorX,CursorY}`, andyk/ht | Only as good as the emulator |\n| External heuristic scraping | Regex/OSC-title matching over another program's rendered screen | tmux-agent-status, tmuxai, kiro_cli poller | **Documented convergent bug** (see status caveat below): whole-buffer matches go stale when a TUI redraws in place `[DOC awslabs/cli-agent-orchestrator#182 \u2014 \"Kiro CLI 2.0 TUI redraws the screen in-place\u2026 retains 'Kiro is working' from earlier rendering alongside the new idle prompt\"; impact: \"Handoff delegations never complete\"]`. Independently mitigated the same way by two projects: anchor to bottom-N lines/OSC title, never whole-screen, plus NOT-gates for stale artifacts |\n\n**How to determine which pole a tool is in:** grep for a VT/emulator dependency and for a regex over screen text. Structured-event consumers have neither; native-typed-state tools have the emulator; scrapers have the regex and no emulator.\n\n**Citation status, disclosed per this document's own method rule 1.** `awslabs/cli-agent-orchestrator#182` is **closed as `completed` on 2026-04-20** and its title begins `fix(kiro_cli):` \u2014 the bug was fixed. It remains valid evidence that *the failure mode is real and was hit in production by a shipped project*; it is not evidence that any current tool is broken today. **Also disambiguate:** `awslabs/cli-agent-orchestrator` (the AWS project, cited here) and `Untrivial-ai/agent-orchestrator` (called \"Agent Orchestrator\" everywhere else in this document, and the home of `ao pty-host`) are **different, unrelated projects**.\n\n**Judgement, with the headless-first constraint applied:** scraping is disqualified as an *architecture*, on the grounds that a stale-buffer misread has nobody to notice it when no human is in the loop \u2014 not on the grounds that any specific instance is currently broken. Vendor-emitted structured events are the correct default channel and they cost nothing \u2014 they do not touch the PTY, so they survive ADE swaps for free.\n\n**On \"34 events\".** There are exactly 34 distinct `claude_code.*` identifiers in the monitoring documentation, but they are not all events: the set mixes **metrics** (`cost.usage`, `token.usage`, `session.count`, `lines_of_code.count`), **events**, and **spans** (`tool.execution`, `hook`). The load-bearing one for headless use, `claude_code.tool.blocked_on_user`, is confirmed present. \"34 named `claude_code.*` identifiers\" is the accurate phrasing and is used from here on.\n\n### 2.7 Substrate stance\n\n| Pole | How to determine it | Instances |\n|---|---|---|\n| Wrap tmux, keep it as substrate | `tmux` appears as a hard prerequisite in install docs, or as a spawned binary in source | tmuxai, tmux-agent-status, tmux-message-bus, claude-squad, uzi, Agent Orchestrator (on Darwin/Linux) |\n| Replace tmux with a new PTY-owning binary | A PTY library appears in the dependency manifest and no tmux prerequisite exists | psmux, quil, oly, qscreen, zellij, rmux, herdr, boo |\n| No multiplexer at all \u2014 no PTY | No PTY library anywhere; the agent is spawned with ordinary pipes | Claude Code `-p --input-format stream-json`; Codex `app-server`; OpenHands EventStream; GitHub Actions runner |\n\n**The criterion that decides the pole, stated independently of any one writeup.** A session layer is necessary exactly when the *agent host* does not own process lifecycle end to end. If the agent tool spawns, supervises and outlives its own processes, a multiplexer underneath is dead weight; if the host dies and takes its children with it, the multiplexer is the only thing that makes sessions survivable.\n\nThat criterion is not this document's invention \u2014 it is the same line drawn by an independent practitioner writeup. `[2ND guancyxx.cn/en/blog/tmux-skills-ai-agents]` Galen Guan: \"For Claude Code and OpenClaw users working through SSH-disconnected sessions, tmux is essential infrastructure. For Hermes users, it's an unnecessary abstraction layer\" \u2014 where **Hermes** is an agent tool whose own terminal tool owns process lifecycle end to end, and **OpenClaw** is an agent CLI that, like Claude Code, does not. (Tagged `[2ND]`, not `[DOC]`: this is a third-party blog writing about somebody else's tools, not a project's own documentation. It corroborates the criterion; it does not establish it.)\n\n**Judgement:** ORCA spawns agent CLIs through its own in-process node-pty, so sessions die with ORCA. It is on the \"essential infrastructure\" side of the line by construction. The wrapper is not NIH here.\n\n### 2.8 Transport binding \u2014 four poles, not two\n\nThe original binary formulation (\"POSIX-only vs native-Windows\") misclassifies candidates. **AF_UNIX is a real Windows transport since Windows 10 1803**, so a `UnixListener` in source is no longer proof of a POSIX-only daemon.\n\n| Pole | Test | Instances |\n|---|---|---|\n| `cfg(unix)`-gated UDS / `forkpty` | Source has `std::os::unix::net`, `std.posix`, `forkpty`, or `nix` with term features | dtach, abduco, boo `[SRC std.posix in 11 files]`, ht `[SRC src/pty.rs forkpty + nix]`, tuios, gotty (creack/pty `start_windows.go` literally `return nil, ErrUnsupported`) |\n| Cross-platform AF_UNIX shim | UDS routed through a shim crate | WezTerm `[SRC wezterm-uds/src/lib.rs: #[cfg(windows)] use uds_windows::UnixStream]`, Codex `[SRC codex-rs/uds/src/lib.rs:1 \"Cross-platform async Unix domain socket helpers\", line 162 #[cfg(windows)] \u2192 uds_windows::UnixListener::bind]` |\n| Windows named pipes | `\\\\.\\pipe\\`, `CreateNamedPipeW`, `interprocess::GenericNamespaced`, `tokio::net::windows::named_pipe` | oly, qscreen, zellij, rmux, herdr, node-pty (conin/conout) |\n| TCP loopback | `TcpListener::bind(\"127.0.0.1:0\")` | psmux (control + cross-session), `ao pty-host`, Warp `local_control` (`http://127.0.0.1:PORT/v1/control`), pywinpty's internal bridge |\n| Naked AF_UNIX on all platforms | `net.Listen(\"unix\", \u2026)` unconditional | quil `[SRC internal/ipc/server.go:311]` \u2014 works on Windows 10 17063+, but `os.Chmod(path, 0600)` is a **no-op on Windows** and there is no `SO_PEERCRED`, so the socket's only protection is the parent directory ACL |\n\n**The two AF_UNIX poles differ in hygiene, not in portability.** Both work on Windows 10 17063+. The difference is that a shim crate documents the platform intent and centralises the fallbacks, whereas a naked `net.Listen(\"unix\", \u2026)` leaves the caller believing POSIX socket semantics still apply. **Neither pole gets kernel-enforced permissions on Windows**: `SO_PEERCRED` does not exist, and `chmod`/`os.Chmod` on an AF_UNIX path is a **no-op**. Whichever pole you are in, an AF_UNIX socket on Windows is protected only by the ACL on its parent directory. A properly-ACL'd named pipe is strictly stronger; a loopback TCP port is strictly weaker (see 5.1's closing note).\n\n**Cross-cutting fact worth remembering:** the `uds_windows` Rust crate and the `interprocess` crate are the two de-facto answers. WezTerm and Codex independently landed on `uds_windows`; Zellij, herdr and oly independently landed on `interprocess`. `[DOC wezterm docs/multiplexing.md: \"Unix domains are supported on all systems, even Windows\"]`\n\n### 2.9 Persistence scope\n\n**How to determine the level:** kill the spawning process tree, then ask two questions \u2014 is the child still running, and can a *new* client re-attach to it? Both yes = L2. Child alive but no re-attach path = the tool is a supervisor, not a session host. Neither = L0.\n\n| Level | Meaning | Instances |\n|---|---|---|\n| 0 \u2014 none | New process per connection, killed on disconnect | ttyd `[SRC src/protocol.c:377-383 \u2014 LWS_CALLBACK_CLOSED unconditionally kills, per-connection `pss-&gt;process`]`, wetty `[SRC src/server/spawn.ts .on('disconnect', () =&gt; term.kill())]`, terminado `UniqueTermManager` |\n| 1 \u2014 survives client disconnect | Session lives as long as the host process | upterm, sshx, terminado `NamedTermManager`, ACP `session/load` (conversation state, not process) |\n| **1+ \u2014 L1 plus cursor-resumable replay** | Survives client disconnect *and* a reconnecting client can resume from an absolute output cursor rather than replaying from zero. Still dies with the host process, so it is **not** L2 | OpenCode `/pty` `[SRC packages/core/src/pty/protocol.ts]` \u2014 the only instance found |\n| 2 \u2014 survives the ADE | Detached daemon outlives whoever spawned it | psmux, quil, oly, qscreen, zellij `[SRC zellij-client/src/lib.rs:463-480 CREATE_NO_WINDOW \\| CREATE_NEW_PROCESS_GROUP]`, rmux, `ao pty-host` `[SRC conpty/spawn_windows.go \u2014 CREATE_NEW_PROCESS_GROUP + DETACHED_PROCESS \"so the host survives daemon exit\"]`, `wezterm-mux-server --daemonize` |\n| 3 \u2014 survives host reboot | State restored across a full restart | cmux (macOS only) `[DOC FAQ: \"the state survives a full computer restart\u2026 Agent sessions like Claude Code, Codex, and OpenCode come back too\"]` \u2014 the only level-3 claim found anywhere |\n\n### 2.10 Ownership timing \u2014 a settled question, recorded so it is not re-opened\n\nThis is not an axis: there is nothing to place, because one pole is refuted outright. It is kept in the taxonomy's numbering because every re-derivation of this research has re-opened it.\n\n| Pole | Mechanism | Verdict |\n|---|---|---|\n| Own-from-birth | The wrapper spawns the child and holds the PTY handle | The only viable pole. Every working implementation found does this |\n| Late-attach | Reach into a console the wrapper did not spawn (`AttachConsole(pid)`) | Read-only escape hatch at best \u2014 the full argument is in 6.4 |\n\n**Correction that removes the last piece of prior art for late-attach:** node-pty was thought to prove late-attach works. It does not. `[SRC src/windowsPtyAgent.ts:149]` `this._innerPid = connect.pid;` \u2014 the PID it passes to `AttachConsole` is the process **it spawned itself**. node-pty re-enters its own child's console from a disposable fork. It is another own-from-birth system. **Nothing found anywhere late-attaches to a foreign console and drives it**, except one hobby project (section 6.4).\n\n### 2.11 The placement matrix \u2014 every live candidate on all ten axes\n\nRows are the candidates that survived to section 4. Read a column to compare a design decision across the field; read a row to characterise one tool. Cells are pole names, abbreviated; `\u2014` means the axis does not apply to that candidate's shape.\n\n| Candidate | 2.1 coupling | 2.2 state ownership | 2.3 namespace | 2.4 command lang | 2.5 extensibility | 2.6 agent state | 2.7 substrate | 2.8 transport | 2.9 persist | 2.10 timing |\n|---|---|---|---|---|---|---|---|---|---|---|\n| **psmux** | Monolithic | Full VT (TUI) / raw (`-CC`) | Socket-name (`-L`/`-S`) | One grammar | Wire protocol (`-CC`) | Native typed | Replace | TCP loopback | L2 | Own-from-birth |\n| **Zellij** | Monolithic | Full VT owner | Socket-name | Hint-first | WASM plugins | Native typed | Replace | Named pipes | L2 | Own-from-birth |\n| **rmux** | Monolithic | Full VT owner | **Identity-scoped** | One grammar | Typed SDK | Native typed | Replace | Named pipes | L2 | Own-from-birth |\n| **herdr** | Monolithic | Full VT owner | Socket-name | One grammar | Typed SDK (3rd-party) | Native typed | Replace | Named pipes | L2 (beta) | Own-from-birth |\n| **oly** | Monolithic | **Parallel/raw** | Socket-name (`OLY_SOCKET_NAME`), machine-global default | One grammar | HTTP/WS | Native typed | Replace | Named pipes | L2 | Own-from-birth |\n| **qscreen** | Session-only | **Parallel/raw** | Single fixed (per-user) | One grammar | Wire protocol | Native typed | Replace | Named pipes | L2 | Own-from-birth |\n| **quil** | Monolithic | **Parallel/raw** | Env-var home (`QUIL_HOME`) | One grammar | MCP (18 tools) | Native typed | Replace | **Naked AF_UNIX** | L2 | Own-from-birth |\n| **`wezterm-mux-server`** | Monolithic | Full VT owner | Socket-name (domains) | One grammar | Lua + wire codec | Native typed | Replace | AF_UNIX shim | L2 | Own-from-birth |\n| **OpenCode `/pty`** | Session-only | Full VT (UTF-8 string wire) | \u2014 (HTTP, per-server) | \u2014 | HTTP/WS | Vendor events | No multiplexer | HTTP/WS | **L1+** | Own-from-birth |\n| **`ao pty-host`** | Session-only | **Parallel/raw** | Registry file per session | \u2014 | Binary wire protocol | Vendor events | Replace | TCP loopback | L2 | Own-from-birth |\n| **upterm** | Session-only | Opaque relay (SSH) | \u2014 (SSH keys) | One grammar | \u2014 | \u2014 | Replace | SSH | L1 | Own-from-birth |\n| **No-PTY architecture** | \u2014 | \u2014 (no VT at all) | Env-var home (`CLAUDE_CONFIG_DIR`) | CLI + NDJSON | Hooks (`http`/`mcp_tool`) | **Vendor events** | No multiplexer | pipes / HTTP | you own it | \u2014 |\n\n### 2.12 Worked example \u2014 placing a tool that is not in the census\n\nTo show the procedure is mechanical, here is `mobydeck/atch` \u2014 refuted in 3.2 on one line, walked through all ten axes from scratch.\n\n1. **Coupling** \u2014 no `split`/`new-window`/`select-pane` verb in the CLI \u2192 **session-only**.\n2. **State ownership** \u2014 no VT or emulator crate in the manifest; it forwards the child's stream \u2192 **opaque relay**.\n3. **Namespace** \u2014 socket path is taken from an argument \u2192 **socket-name-as-namespace**.\n4. **Command language** \u2014 one small verb set, no config-file grammar \u2192 **one grammar**, trivially.\n5. **Extensibility** \u2014 none published \u2192 no pole.\n6. **Agent state** \u2014 nothing typed exposed \u2192 no pole; a consumer would have to scrape.\n7. **Substrate** \u2014 owns its own PTY, no tmux prerequisite \u2192 **replace**.\n8. **Transport** \u2014 POSIX sockets, `cfg(unix)`-shaped, no Windows path \u2192 **`cfg(unix)`-gated UDS**. **This is the disqualifying cell.**\n9. **Persistence** \u2014 detached daemon, re-attachable \u2192 **L2**.\n10. **Timing** \u2014 spawns its own child \u2192 **own-from-birth**.\n\n**Verdict: REFUTED**, on constraint 1, decided entirely by axis 8. Two useful properties of this procedure show up here: the verdict falls out of a single cell rather than a holistic impression, and the *reason* is recorded in a form the next reader can re-check in one grep. Every REFUTED row in section 3 was reached this way.\n\n---\n\n## 3. The Full Inventory\n\n### 3.1 Session daemons and multiplexers \u2014 native-Windows candidates\n\n**Reading the `Last activity` column:** every value is the repo's `pushed_at` unless the cell says otherwise. Where a project's default branch and its `pushed_at` disagree (side branches), both are given.\n\n**Reading the `Linux/macOS` column** (constraint 3): this is the \"uniform Windows+Linux behaviour is a plus\" check, and it changes the reading of three rows at a glance.\n\n| Name | Runtime | License | Stars | Last activity | Native Win | Linux/macOS | Interface | Persist | Nesting | Verdict | Evidence |\n|---|---|---|---|---|---|---|---|---|---|---|---|\n| **psmux/psmux** | Rust daemon; a PowerShell installer/test suite is the larger byte count, so \"mostly PowerShell\" language stats are misleading \u2014 the product is the Rust binary | MIT | 3,140 | 2026-08-02 | Yes `[SRC crates/portable-pty-psmux]` | **No \u2014 Windows-only.** `[ART psmux/psmux .github/workflows/ci.yml; release v3.3.7 ships six Windows-only assets]` CI builds only `x86_64/i686/aarch64-pc-windows-msvc`; the `ubuntu-latest`/`macos-latest` job is `posix-helper-tests`, running two shell scripts, **not a build** | tmux CLI verbs + `-C`/`-CC` control mode | L2 | Own guard + `PSMUX_ALLOW_NESTING=1` override; pipe mode negotiates size via `CSI 18 t` | **VIABLE-WITH-CAVEAT** (Windows-only; unauthenticated cross-session port) | `[SRC src/main.rs:232-241, 4031-4032, 4194; src/control.rs; docs/control-mode.md (432 lines)]` |\n| **zellij-org/zellij** | Rust | MIT | 34,647 | 2026-08-01 | Yes `[SRC zellij-server/src/os_input_output_windows.rs \u2014 CreatePseudoConsole/ResizePseudoConsole/HPCON; zellij-utils/src/ipc.rs:8 interprocess::local_socket]` | **Yes \u2014 original target platforms** | CLI + WASM plugins | L2 | `nested_session_handling` = ask/fullscreen/descend/never `[SRC zellij-utils/assets/config/default.kdl:268-275]` | **VIABLE** | `[ART v0.44.3 ships zellij-x86_64-pc-windows-msvc.zip + .msi]` |\n| **Helvesec/rmux** | Rust (12 crates, 21 MB) | dual-licensed; the pair is not stated in repo metadata (GitHub reports NOASSERTION) \u2014 **read `LICENSE-*` at the tag you would vendor before relying on it** | 2,533 | 2026-07-26 | Yes `[SRC crates/rmux-ipc/src/stream_windows.rs \u2014 NamedPipeServer + ImpersonateNamedPipeClient + server_identity_windows.rs]` | **Yes** | 90+ tmux-compatible verbs + Rust/Python/TS SDKs + Ratatui widget + E2E-encrypted web share | L2 | Not tested | **VIABLE** | `[ART rmux-0.9.1-windows-x86_64.zip; winget/scoop/choco]` |\n| **herdrdev/herdr** | Rust | Apache-2.0 | 23,466 | 2026-08-01 | **Beta only** `[DOC website/src/content/docs/windows-beta.mdx]` | **Yes \u2014 stable `v0.7.5` ships linux + macos assets and no Windows asset** | CLI + HTTP API (`src/api/server.rs`) + third-party TS/Python SDKs | L2 (beta \u2014 \"Local persistent sessions \\| beta\" is listed under **Supported**) | \"Nested launch override \\| beta\" (Supported table) | **VIABLE-WITH-CAVEAT** (preview channel only on Windows) | `[ART Windows ships preview-channel only: `preview-2026-07-29` \u2192 herdr-windows-x86_64.zip; stable v0.7.5 has no Windows asset]` |\n| **slaveOftime/open-relay (oly)** | Rust | **MIT** `[SRC gh api repos/slaveOftime/open-relay/license \u2192 spdx_id MIT; LICENSE at repo root]` | 89 | `main` HEAD 2026-07-01; `pushed_at` 2026-07-30 (four side branches) | Yes (`interprocess` `GenericNamespaced`) | **Yes** | Named-pipe IPC + HTTP/WS + push notifications | L2 | **No nesting evidence** \u2014 see row note; what exists is a windows-latest PTY test matrix over **shells**, not agents | **VIABLE-WITH-CAVEAT** (unauthenticated, no pipe ACL) | `[SRC tests/e2e_daemon.rs, e2e_pty.rs; .github/workflows/ci.yml matrix incl. windows-latest]` |\n| **dualface/qscreen** | Rust | MIT | 6 | 2026-07-21 `[SRC gh api pushed_at; last default-branch commit \"docs(readme): add feature boundaries and quick start\"]` | Yes `[SRC crates/qscreen-daemon/Cargo.toml: \"Windows\u5e73\u53f0\u4ec5\u9700tokio\u7684named_pipe\u5b9e\u73b0\"]` | Unverified \u2014 no CI on any platform | `qscn` CLI + `ScreenFrame`/`ScreenRun` structured protocol + `AttachMode::Bytes` | L2 | Untested | **UNVALIDATED** | Zero issues, zero PRs, zero CI ever `[ART contents/.github \u2192 404]` |\n| **artyomsv/quil** | Go | **MIT** `[SRC gh api repos/artyomsv/quil/license \u2192 spdx_id MIT; LICENSE at repo root]` | 11 | 2026-08-01 | Yes (ConPTY) but **AF_UNIX IPC, not named pipes** | **Yes** \u2014 and CI runs **only** there | MCP (18 tools) + `quild` CLI + TUI | L2 | Mouse-mode forwarding for nested alt-screen apps `[SRC internal/daemon/mousemode.go]` | **UNVALIDATED** | `[SRC internal/ipc/server.go:311 net.Listen(\"unix\"); go.mod has no go-winio; ipc/ has no _windows.go split]` |\n| **`wezterm-mux-server`** (wezterm/wezterm \u2014 note `wez/wezterm` now 301-redirects) | Rust | MIT | 28,101 | 2026-07-31 | Yes | **Yes** | 15 CLI verbs (`list`, `spawn`, `split-pane`, `send-text`, `get-text`, `kill-pane`, `proxy`\u2026) + versioned binary codec + tmux `-CC` client | L2 | Attach/detach first-class `[DOC docs/config/lua/MuxDomain/attach.md]` | **EXTRACTABLE** | `[SRC ci/deploy.sh:115-124 copies wezterm-mux-server.exe into the Windows zip + Inno installer; RPM spec: \"Multiplexer server (headless)\"; codec/src/lib.rs varint framing \"so client and server can more gracefully manage unknown enum variants\"]` |\n| **`ao pty-host`** (Untrivial-ai/agent-orchestrator) | Go | Apache-2.0 | 8,742 | 2026-08-02 | Yes | **Yes** (the surrounding ADE is cross-platform; the `pty-host` package itself is `spawn_windows.go`-gated) | 8-message binary protocol `[type][BE32 len][payload]` over loopback TCP | L2 | `applyLargestLocked` sizes the shared PTY to the largest attached client | **EXTRACTABLE** | `[SRC backend/internal/adapters/runtime/conpty/{spawn_windows.go,host_main.go,proto.go,host.go,ptyregistry/registry.go}]` |\n| **owenthereal/upterm** | Go | Apache-2.0 | 1,270 | 2026-07-25 | Yes `[SRC host/internal/pty_windows.go \u2014 charmbracelet/x/conpty + Job Objects]` | **Host yes; relay `uptermd` is Linux-only** | SSH (public-key auth: `--authorized-keys`, `--github-user`, `--gitlab-user`\u2026) | L1 only | `[SRC host/host_windows.go]` deliberately ignores `os.Interrupt` \"to prevent upterm from dying when SSH clients send Ctrl+C to child processes via ConPTY\" | **REFUTED** (fails the survive-the-ADE requirement: L1 only, no detach/reattach) | `[ART v0.24.0 ships upterm_windows_{386,amd64,arm64}; relay `uptermd` is Linux-only]` |\n\n**Row notes.**\n\n- **psmux** \u2014 `[SRC src/server/mod.rs:839]` control listener is `TcpListener::bind((\"127.0.0.1\",0))` with an application-layer `AUTH ` handshake `[SRC src/server/connection.rs:304-315]`. Two weaknesses: the key is generated from `std::collections::hash_map::RandomState` over a nanosecond timestamp + PID, truncated to 64 bits `[SRC src/server/mod.rs:852-860]` \u2014 explicitly non-cryptographic per Rust's own docs \u2014 and the key file gets no explicit ACL, with the source comment conceding \"user-only visibility on Windows comes from the profile directory ACLs\" `[SRC src/server/mod.rs:864]`. Separately, `src/cross_session_server.rs` opens a second, **completely unauthenticated** channel, and it is worse than \"input injection\": `:98` binds `TcpListener::bind(\"127.0.0.1:0\")`, `:127` accepts the **first connection with no handshake at all**, `:131-132` pushes that socket into `crate::types::PIPE_WRITERS` as a **tee writer** \u2014 so the connecting process receives a copy of every byte the pane's ConPTY emits \u2014 and `:149` writes everything it reads back into `pty_writer.write_all()`. That is **full-duplex: output exfiltration plus input injection**, unauthenticated, on a loopback port any local process can scan, gated only on the user performing a cross-session pane move. Windows-only: CI builds only `*-pc-windows-msvc`. **On the other side of the ledger**, psmux's CI runs `cargo audit --deny warnings` against both the workspace and the test-monitor lockfile \u2014 a supply-chain signal no other small candidate matches. **Self-contradicting nesting guard:** the check at `:954` and `:4056` reads `PSMUX_ALLOW_NESTING`, but the error text printed at `:958`/`:4060` says *\"psmux: sessions should be nested with care, unset PSMUX_SESSION to force\"* \u2014 a scripted wrapper that follows the message will not clear the guard.\n\n- **rmux** \u2014 the best-designed transport in the census and the only identity-scoped namespace (2.3). Anti-squatting is **not** unique to qscreen: rmux also uses `FILE_FLAG_FIRST_PIPE_INSTANCE` `[SRC crates/rmux-pty/src/backend/windows/io.rs]`, *and* validates the peer with `ImpersonateNamedPipeClient` `[SRC crates/rmux-ipc/src/stream_windows.rs]`, *and* derives the pipe name from SID + integrity level so a name cannot be guessed into. Its exposure is not technical: **936 of ~1000 commits are from a single author** `[UNV \u2014 commit-attribution count carried from an earlier pass, not re-verified with `gh api`]`, and the dual licence pair is unstated in metadata. Nesting behaviour was never tested.\n\n- **`wezterm-mux-server`** \u2014 headless by design and the only candidate whose wire format was *explicitly* built for version-skewed client/server pairs, which is precisely the ADE-churn insulation being shopped for. The contract-stability caveat is sharper than \"ships from nightlies\": **the newest tagged release is `20240203-110809-5046fc22`, published 2024-02-03 \u2014 two and a half years old** `[SRC gh api repos/wezterm/wezterm/releases]`. The rolling `nightly` tag (which ships `WezTerm-windows-nightly.zip`) is the only current Windows path, so \"pin a version\" and \"run on Windows\" are in direct tension. Also note the repo moved: `wez/wezterm` 301-redirects to `wezterm/wezterm`.\n\n- **upterm** \u2014 reclassified **REFUTED** in this edition. It has genuinely good properties (real ConPTY via `charmbracelet/x/conpty`, Job Objects, the only public-key auth model in the census, Windows release artifacts) and its `os.Interrupt` handling is a real nested-ConPTY lesson worth stealing. But it is **L1 only**: the session lives exactly as long as the `upterm host` process. It fails the one requirement that defines this document's subject \u2014 surviving the process that spawned it \u2014 so it cannot be a middle layer, whatever else it is good at.\n- **quil** \u2014 `[SRC PR #51]` documents a dated production incident (2026-06-10) where a second `quild` against the same `QUIL_HOME` unlinked the live daemon's socket and overwrote its PID file, \"bricking the original for new clients\" \u2014 in exactly the instance-isolation mechanism previously reported as a strength. **The mechanism is one line:** `[SRC internal/ipc/server.go:309]` `os.Remove(s.path) // Clean up stale socket`, executed **unconditionally** at the top of `Start()`, before `net.Listen`. There is no liveness check on the existing socket, so a second daemon deletes a live one's endpoint by design. Same audit fixed a VT-emulator-disposal race leaking a goroutine plus a 10,000-line scrollback grid per closed pane, an unbounded PTY-coalescer debounce buffer, and a Windows `WaitExit` handle leak per destroyed pane. `[ART .github/workflows/*.yml]` CI is **ubuntu-latest only** \u2014 25+ Windows-specific source files, zero Windows CI, zero external issues ever filed.\n- **oly** \u2014 two things, one corrected and one new.\n  - **Correction: oly has no nesting evidence, and earlier editions of this document said it had the best in the field.** That claim was wrong. `[SRC tests/e2e_pty.rs, 895 lines]` contains **zero** occurrences of `copilot` or `opencode`; it spawns **shells** \u2014 `cmd.exe`, `bash --noprofile --norc`, `pwsh` \u2014 on windows-latest. The `tests/output-copilot.log` file is a 17,718-byte **recorded ANSI dump** consumed by `[SRC src/session/logs.rs]`, the log-rendering unit tests \u2014 not by the PTY e2e tests, and not by anything that attaches to a live agent. What oly actually has, which is still more than any other small candidate, is **a real windows-latest PTY test matrix**. It tests shells under ConPTY on Windows; it does not test nesting and it does not test agent CLIs.\n  - **New finding from the same fixtures, and it cuts against constraint 3:** oly ships **`tests/output-copilot.expected.windows` alongside `tests/output-copilot.expected`** \u2014 a Windows-specific expected output for the *same* input log. That is direct evidence that oly's VT/log-rendering layer produces **different output on Windows than on Unix**. For a document whose constraint 3 values uniform Windows+Linux behaviour, that is a more interesting fact than the live-agent-tests claim it was mistaken for.\n  - Security, unchanged and still disqualifying-adjacent: `[DOC milestones/M4_security_hardening.md, status \"\u23f3 Not started\" since 2026-03-12]` lists \"Windows: enforce named-pipe ACL (current-user SID only)\" as unimplemented. `[SRC src/ipc.rs, 296 lines]` contains no ACL/SID/security-descriptor keyword, and `FILE_FLAG_FIRST_PIPE_INSTANCE`, `security_descriptor` and `SECURITY_ATTRIBUTES` are all **zero hits** in the repo. `[DOC learn.microsoft.com/windows/win32/api/winbase/nf-winbase-createnamedpipea]` a NULL security descriptor grants \"read access to members of the Everyone group and the anonymous account\" \u2014 for a pipe carrying agent terminal I/O, that is an API-key disclosure path. Its later security audit `[SRC PR #83]` fixed the *web* layer (3 High: command injection in notification hooks, permissive CORS, no rate limiting on node-join; 14 Medium; 4 Low) and Unix socket modes, but no Windows pipe-ACL item appears in the findings table.\n- **qscreen** \u2014 does two things oly does not: `.first_pipe_instance(true)` on the initial listener (anti-squatting) `[SRC crates/qscreen-daemon/src/lib.rs:162]` and per-user pipe namespacing. **It is not unique in this** \u2014 rmux does both as well (see the rmux row note); the contrast is with oly specifically, which does neither. Its VT crate `crates/vt100-psmux` is a `cargo vendor` of the published `vt100-psmux` 0.16.2 (Jesse Luehrs' vt100-rust, patched, republished by `marlocarlo`) \u2014 the same crate psmux vendors, independently, not a fork of psmux's directory. Its `portable-pty` is 0.8, a full minor behind oly's 0.9 and psmux's patched 0.9.6, in a crate whose Windows backend changes between minors.\n- **zellij** \u2014 `[SRC zellij-client/src/os_input_output_windows.rs:32]` chooses between a VT-byte input path and a native-console `INPUT_RECORD` path via `use_vt_path() { env::var(\"TERM\").is_ok() || env::var(\"WT_SESSION\").is_ok() }`. Inside ORCA's node-pty ConPTY neither is necessarily set. **Judgement, downgraded from \"hazard\" to \"config check\":** the `else` branch is the **native-console `INPUT_RECORD` path**, which is implemented, is the conservative choice under a real console, and ConPTY *does* give the child a real console. So the honest statement is \"Zellij picks the native-console path, untested in this topology, and one env var (`TERM=xterm-256color`) forces the other\". Calling it the *wrong* path was unevidenced. **What is genuinely unknown is the observable symptom** if the native-console path misbehaves under a nested ConPTY \u2014 dead keyboard, garbled input, or silently fine. Q3 has no pass/fail criterion until someone runs it; that limitation is recorded in section 9. Known Windows rough edges from maintainer `divens`' tracking issue #4745: no sixel, unreliable OSC 7 / live-cwd under PowerShell, focus-report sequences not reaching subpanes, scroll-mode stickiness. Two citation-path corrections from the audit: `use_vt_path()` is at `os_input_output_windows.rs:32-33`, not `:91-99`; and `default.kdl` is at `zellij-utils/assets/config/default.kdl` \u2014 the bare `assets/config/default.kdl` path cited in earlier editions **404s**. There is also a second, 100 ms poller at `:38`/`:52` alongside the 50 ms resize poll documented in 4.1.\n- **herdr** \u2014 **the Windows-beta page must be read as two tables, and earlier editions read only one.** Its **Supported** table lists \"Local persistent sessions | beta\", \"Native panes through ConPTY | beta\", \"**Windows Terminal / PowerShell app attach | beta**\", \"Pane screen history | beta\" and \"Nested launch override | beta\". Its **Not supported** table lists \"Direct terminal attach | unsupported\", \"Live server handoff | unsupported\", \"Unix file-descriptor handoff | unsupported\", \"Unix foreground process groups | unsupported\", \"Prefix input-source switching | unsupported\" `[DOC website/src/content/docs/windows-beta.mdx]`. **Judgement:** read together, \"Direct terminal attach\" sits in a cluster of Unix-specific mechanisms (fd handoff, live server handoff, foreground process groups) and is most likely the fd-passing attach path, not the ADE-spawn path \u2014 which is separately listed as *supported in beta*. That makes Q2 much less likely to be a showstopper than earlier editions claimed, and it is why Q2 has been demoted from \"highest-value single verification in this whole map\". It still needs confirming, because the inference is this document's, not herdr's.\n- **`ao pty-host`** \u2014 `[SRC conpty/host.go]` \"replays scrollback to new clients, fans output to all connected clients\"; \"On PTY exit it broadcasts a status update but stays alive (keep-alive, mirroring tmux behavior)\". `[SRC conpty/ptyregistry/registry.go]` persists `~/.ao/windows-pty-hosts.json` with `{sessionId, ptyHostPid, pipePath}` so orphans are findable after metadata loss. `[SRC host_main.go]` carries its own caveat: \"loopback bind only; any local process on this host can connect to the assigned port. A per-session random token handshake is the upgrade path.\" Apache-2.0 makes vendoring legally clean, unlike the AGPL candidates.\n\n**Install and prove-it commands.** Section 8 proposes hands-on tests; these are the exact commands to get to a running daemon and to prove the one property that matters \u2014 that a session survives its parent. Run each from a normal PowerShell prompt. `[UNV \u2014 assembled from each project's own install docs and verb list; not executed on this machine in any pass]`\n\n```powershell\n# psmux        \u2014 winget install psmux.psmux    (or: scoop install psmux)\npsmux new-session -d -s probe ; psmux ls          # detached session, then list it\n# Zellij       \u2014 winget install zellij-org.zellij   (or the v0.44.3 msvc .msi)\nzellij --session probe options --help ; zellij list-sessions\n# rmux         \u2014 winget install Helvesec.rmux   (or: scoop install rmux / choco install rmux)\nrmux new -d -s probe ; rmux ls\n# herdr        \u2014 download preview-2026-07-29 herdr-windows-x86_64.zip  (NOT stable v0.7.5 \u2014 no Windows asset)\nherdr list\n# wezterm-mux  \u2014 download WezTerm-windows-nightly.zip  (no current tagged release; see row note)\nwezterm-mux-server --daemonize ; wezterm cli list\n# OpenCode     \u2014 winget install anomalyco.opencode\nopencode serve --port 4096                        # then: POST /pty, GET /pty/:id/connect (WS)\n\n# The one command that proves detachment, for any of the above:\n#   1. start a session,  2. kill the parent terminal window outright,\n#   3. open a new terminal and run the tool's `ls`/`list` verb.\n#   If the session is still listed and re-attachable, it is L2. If not, it is L1.\n```\n\n### 3.2 Session daemons \u2014 POSIX-only ancestors and refuted candidates\n\n| Name | Runtime | License | Stars | Last activity | Native Win | Persist | Verdict | Evidence |\n|---|---|---|---|---|---|---|---|---|\n| GNU screen | C | GPL | \u2014 | last release 4.9.1 (2023) | No | L2 | CONTEXT (ancestor) | `[SRC github.com/Orc/screen README]` \u2014 Laumann's original beta-test README, preserved as an artifact: \"Since 'screen' uses pseudo-ttys, the select system call, and UNIX-domain sockets, it will not run under a system that does not include these features of 4.2 and 4.3 BSD UNIX.\" Also: full VT100/ANSI-X3.64 emulation and the `^A` prefix are in the original. **Provenance correction:** the file carries **no date**, and the distribution channel it names is **`mod.sources`**, not `net.sources`. Earlier editions dated it \"1987 net.sources\"; neither element is supported by the artifact. |\n| tmux | C | ISC | \u2014 | 3.7b (2026-07-01) | No | L2 | CONTEXT (ancestor) | `[2ND undeadly.org/cgi?action=article;sid=20090712190402]` Marriott: screen had \"poor documentation, a strange configuration file and an unintuitive command-line interface\u2026 And that isn't mentioning the code\". `[SRC cmd-attach-session.c:74-76]` nested-session refusal string. (Tagged `[2ND]`: an interview on a third-party news site, not tmux's own documentation.) |\n| dtach | C | public-domain-ish | 732 | 2025-06-20 | No | L2 | REFUTED (POSIX) | `[SRC README]` \"assumes that the host system uses POSIX termios, and has a working forkpty function available\" |\n| abduco + dvtm | C | ISC | 969 / 956 | 2020-04-30 / 2021-03-06 | No | L2 / \u2014 | REFUTED (POSIX + dormant) | `[DOC brain-dump.org/blog/abduco-dvtm-a-lightweight-alternative-to-tmux-and-screen/]` \u2014 note this is the article URL; the bare `brain-dump.org/blog` index cited in earlier editions does **not** contain the quote |\n| coder/boo | Zig | MIT | 748 | 2026 (bare year \u2014 `pushed_at` not pulled this pass) `[UNV]` | No | L2 | REFUTED (POSIX) | `[DOC coder/boo README]` \"your terminal &lt;-(raw tty)-&gt; boo client &lt;-(unix socket)-&gt; session daemon\"; `[SRC std.posix in 11 files, AF_UNIX 0 hits]`. Notable for swapping screen's ancient emulator for libghostty-vt and shipping `peek --json`/`wait --idle` as first-class. |\n| Roost | Swift+AppKit / Rust+gtk4 | unstated | \u2014 | 2026 (bare year \u2014 `pushed_at` not pulled this pass) `[UNV]` | No | L1 | REFUTED | `[DOC charliek.github.io/roost]` \"Not a Windows app \u2014 macOS and Linux only.\" |\n| Gaurav-Gosain/tuios | Go | \u2014 | \u2014 | \u2014 | No | \u2014 | REFUTED | Unix-socket-only daemon, zero Windows build tags, procfs dependency |\n| fukuyori/wtmux | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | No | REFUTED | Detach/attach documented as \"planned\" |\n| weill-labs/amux | \u2014 | \u2014 | \u2014 | \u2014 | darwin+linux releases only | \u2014 | REFUTED | Release artifacts |\n| prompt-toolkit/pymux | Python | \u2014 | \u2014 | 2023 | \u2014 | \u2014 | REFUTED | Unmaintained |\n| chyyran/conmux | \u2014 | \u2014 | \u2014 | 2019 | \u2014 | No | REFUTED | Abandoned, no session support |\n| mobydeck/atch, yazgoo/diss, gHexaByte/retach, whisperity/monomux | \u2014 | \u2014 | \u2014 | \u2014 | No | \u2014 | REFUTED | POSIX sockets |\n| densharik/kip | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | REFUTED | In-process TUI, not a daemon |\n| smtg-ai/claude-squad | Go | AGPL-3.0 | 8,222 | 2026-07-30 | **No** | \u2014 | REFUTED | `[SRC go.mod]` `creack/pty v1.1.24` and **no ConPTY library of any kind**, despite active maintenance; hard tmux prerequisite; open issue #275 |\n| devflowinc/uzi | Go | MIT | 580 | 2025-06-04 | Unknown | \u2014 | UNVALIDATED (dormant) | Hard tmux prerequisite; zero Windows issues ever filed \u2014 neglect, not confirmed non-support |\n| nelhage/reptyr | C | MIT | 6,309 | **2025-11-20** `[SRC gh api pushed_at]` \u2014 8+ months dormant, not \"active\" as earlier editions said | No | \u2014 | CONTEXT | The POSIX solution to the structurally identical problem (grab I/O of a process you didn't spawn) via `ptrace`. **No Windows analogue exists.** 10+ years, and no attempted port was located `[UNV \u2014 absence of a port is an absence-of-evidence finding from repo and issue searches, not an exhaustive proof]` \u2014 read as evidence the platform makes this hard, not that the niche is unfilled. |\n\n### 3.3 PTY-as-a-service \u2014 terminal over HTTP/WS/SSH\n\n| Name | Runtime | License | Stars | Last activity | Native Win | Interface | Persist | Verdict | Evidence |\n|---|---|---|---|---|---|---|---|---|---|\n| **OpenCode `/pty` API** | TS/Bun | MIT | 192,203 | 2026-08-02 | **Yes** | `GET/POST /pty`, `GET\\|PUT\\|DELETE /pty/:id`, `POST /pty/:id/connect-token`, `GET /pty/:id/connect` (WS) | L1+ replay (see 2.9) | **VIABLE-WITH-CAVEAT** (undocumented contract; UTF-8 string wire, not raw bytes) | `[SRC packages/opencode/src/server/routes/instance/httpapi/groups/pty.ts; packages/core/src/pty/protocol.ts \u2014 \"raw UTF-8 terminal chunks\u2026 one control frame \u2014 a 0x00 byte followed by UTF-8 JSON \u2014 carries the absolute output cursor after replay so clients can resume later\"; pty.node.ts: `process.platform === \"win32\" ? { useConptyDll: true } : {}`]`. **Documented nowhere on opencode.ai** \u2014 the published server doc has zero `/pty` occurrences. **Byte-fidelity caveat, from the same 38-line file:** `chunks(data: string)` slices at 64 KiB in UTF-16 code units (surrogate-splitting hazard), and `decodeInput` drops invalid UTF-8 input silently via `new TextDecoder(\"utf-8\", { fatal: true })` inside a bare `catch`. See 2.2. |\n| ttyd (tsl0922) | C | MIT | 12,113 | code 2026-03-20 | **Source yes, shipped binary broken** | HTTP + raw WS | **L0** | REFUTED as shipped | `[SRC src/protocol.c:154,353,377-383]` a **new process per WebSocket**, unconditionally killed on close. `[DOC issue #1501]` the only Windows release (`ttyd.win32.exe`, MinGW, 1.7.7, 2024-03-30) fails to spawn any child on Windows 11 build 26200 \u2014 **your exact build**. MSVC fix merged 2026-03-19; no release cut since. WinGet and Scoop both pin the broken 1.7.7. |\n| gotty (yudai / sorenisanerd) | Go | MIT | 19,531 / 2,523 | 2024-08-01 / 2026-08-01 | **No** | HTTP + WS | L0 | REFUTED | `[SRC creack/pty start_windows.go: `return nil, ErrUnsupported`]`. The active fork's release matrix ships freebsd/netbsd/openbsd/solaris and deliberately omits windows. |\n| wetty (butlerx) | Node | MIT | 5,362 | 2026-07-31 | **No** | HTTP + socket.io | L0 | REFUTED | `[SRC src/server/spawn.ts]` every PTY, including `--command`, is `pty.spawn('/usr/bin/env', cmd, xterm)`; `src/server/spawn/env.ts` parses GNU coreutils version strings; `src/server/command.ts` gates local mode on `process.getuid?.() === 0`, undefined on Windows |\n| terminado / jupyter_server_terminals | Python | BSD-3 | 373 / 20 | 2025-08-02 (docs only; last code 2024-04-30) / 2026-01-14 | Yes (pywinpty) | REST + WS `[\"stdout\",text]` etc. | L1 (`NamedTermManager`), L0 (`UniqueTermManager`) | CONTEXT (design template) | `[SRC terminado/management.py:23-34]` \u2014 a nested `try/except ImportError` chain, **not** a platform conditional; if both imports fail it binds `PtyProcessUnicode = object` and fails later with an obscure AttributeError. `[SRC pywinpty winpty/ptyprocess.py]` bridges non-selectable ConPTY output by binding a **per-PTY ephemeral 127.0.0.1 TCP listener** pumped from a daemon thread \u2014 one loopback port and one polling thread per terminal, an `accept()` race any local process can win, output round-tripped through `str`, and a literal `'0011Ignore'` sentinel injected on empty reads |\n| ekzhang/sshx | Rust | MIT | 7,567 | **2025-06-19 (13.5 mo)** | Yes (zhiburt `conpty`, **not** portable-pty) | gRPC client\u2194relay + custom canvas UI | L1 | REFUTED for this use | `[SRC crates/sshx/src/terminal/windows.rs]` author's own comment: \"I can't get `powershell.exe` to work with ConPTY, since it returns error 8009001d\" \u2014 probe list is Git Bash then cmd.exe. No arbitrary-command arg (only `--shell`), no detach, `[DOC ekzhang/sshx README]` \"Self-hosted deployments are not supported at the moment\", `[ART gh api repos/ekzhang/sshx/releases \u2014 every entry has an empty `assets` array]` every release has **zero assets** |\n| tmate | C (tmux fork) | BSD | 6,092 | 2026-07-29 | No | SSH | L2 | REFUTED (tmux fork \u2014 inherits tmux's POSIX dependency) | Project is alive. `[UNV]` The \"shutting down\" claim is attributed to a downstream project's README rather than tmate itself; that attribution was not re-verified this pass \u2014 treat \"a downstream README, not tmate\" as unconfirmed, and the `pushed_at` date as the actual evidence of liveness. Free relay has an open reliability issue (#322, stale since 2025-11) |\n| Coder `agent/reconnectingpty` | Go | AGPL-3.0 (+ proprietary `enterprise/`) | 14,004 | 2026-08-02 | Yes | Coordinator mesh \u2192 WS/SSH | L1, self-terminating | PRIOR-ART ONLY | `[SRC agent/reconnectingpty/reconnectingpty.go]` two backends; `screen.go` gated `runtime.GOOS == \"linux\"` (\"Screen seems flaky on Darwin\"); `buffered.go` is a **64 KiB** ring dumped raw `[SRC buffered.go:52, 222-223]`, self-labelled \"(buggy)\". Closes itself after `Options.Timeout` (default 5 min); 30 s initial attach window. Called from exactly one non-test place \u2014 needs a coderd control plane + Postgres. |\n| **VS Code `ptyHost`** (microsoft/vscode) | TS | MIT | \u2014 | active | Yes | VS Code internal IPC | L1 across window reloads | PRIOR-ART, strongest | `[SRC src/vs/platform/terminal/node/ptyService.ts]` `PersistentTerminalProcess` with `detachFromProcess(id, forcePersist)` (:403), `shouldPersistTerminal` (:416), orphan-question protocol, `serializeTerminalState(ids)` (:230). Replay is **not** a raw byte dump: it runs `@xterm/headless` (:33,39,1033) + `@xterm/addon-serialize` (:1133) to serialize real terminal state, and `terminalRecorder.ts` records **resize events too**, `MaxRecorderDataSize = 10 MB`. Contrast Coder: 10 MB state-aware + resize replay vs 64 KiB raw `conn.Write(prevBuf)`. Open-source server packaging (openvscode-server) is Linux-only. |\n| andyk/ht | Rust | Apache-2.0 | 902 | 2025-07-25 | **No** | HTTP + WS + NDJSON stdio | \u2014 | REFUTED (POSIX) | Description is nearly your brief verbatim: \"wrap any binary with a terminal interface for easy programmatic access\". Embeds `avt` so callers query the **rendered screen**, not the byte stream. `[SRC src/pty.rs]` `forkpty` + `nix` + `tokio::io::unix::AsyncFd`. **The shape to copy, not the tool to use.** |\n| PowerShell/Win32-OpenSSH | C | BSD-style | 8,223 | 2025-10-27 | **Yes** | SSH | L0 | CONTEXT | First-party, ConPTY-backed, installable as a Windows optional feature. Zero session persistence \u2014 a transport, not a middle layer. `[DOC issue #2291, filed by MSFT member SteveL-MSFT, open]`: \"On Linux, you can use something like tmux to keep a long lived session, but the equivalent doesn't exist on Windows.\" |\n| wavetermdev/waveterm | Go+TS | Apache-2.0 | 21,892 | 2026-07-31 | Yes | GUI terminal | \u2014 | CONTEXT | Gets ConPTY out of a Go stack via `replace github.com/creack/pty =&gt; github.com/photostorm/pty` \u2014 a **15-star fork last pushed 2024-04-14**. The practical escape hatch for Go projects blocked by creack/pty's stub, and a named supply-chain risk. |\n| code-server, Gitpod, Eclipse Che, Butterfly, shellinabox, tty-share, webssh, Guacamole, Teleport | various | various | \u2014 | \u2014 | **No** (or wrong category) | \u2014 | \u2014 | REFUTED / CONTEXT | code-server `[DOC docs/install.md]` \"We currently do not publish Windows releases\"; Gitpod/Che are Linux-container-only; Butterfly `[SRC setup.py classifiers]` `POSIX :: Linux`, last commit 2018; shellinabox last commit 2019; tty-share `[SRC go.mod]` creack/pty 1.1.11; webssh and Guacamole are **relays to an existing endpoint**, never own a local PTY; Teleport `[DOC goteleport.com/docs \u2014 installation/supported platforms]` \"Teleport server does not run on Windows yet\" |\n| Eclipse Theia | TS | EPL-2.0 | 21,617 | 2026-08-01 | Partial | HTTP/WS or Electron IPC | attach-by-id exists | UNVALIDATED (server-mode packaging) | `[SRC packages/terminal/src/node/shell-process.ts:90-91]` `if (isWindows) { return 'cmd.exe'; }`; `[SRC base-terminal-server.ts:49]` `async attach(id: number)` with a backend terminal registry |\n\n### 3.4 ADEs and agent hosts\n\n**Why an ADE census is in a document about the layer *underneath* ADEs.** Two reasons, both load-bearing. First, it is the evidence base for the churn argument that motivates the whole project: six of the entries below died, sunset or closed-sourced within eight months, which is what makes ADE-independence worth paying for. Second, the middle layer can only be inserted where an ADE lets you override the launch command \u2014 so this table is the proof that such hooks exist in the wild, and the sample of what shape they take.\n\n**The decisive filter: genuine free-form command override.** Among the **native-Windows** candidates, only four document a real \"point me at any executable\" hook a third party can use without forking source: ORCA, kandev, Pane and Zed (ACP-shaped). Three further candidates document an override at least as good but are **refuted on platform, not on override** \u2014 cmux (`cmux.json`, `cmux hooks setup --agent`; macOS only), superset (the best-documented override of any candidate; \"Windows/Linux untested\"), and sculptor (\"run and manage *any* terminal-based agents\"; Mac/Linux downloads only). Counting all seven, override hooks are not rare; **native-Windows** override hooks are.\n\n| Name | Runtime | License | Stars | Last activity | Native Win | Override | Own control surface | Verdict |\n|---|---|---|---|---|---|---|---|---|\n| **ORCA** (stablyai) | Electron/TS, node-pty | MIT | 35,379 | 2026-08-02 | Yes | **Yes** \u2014 `agentCmdOverrides` `[SRC src/shared/types.ts; consumed src/shared/tui-agent-launch-command.ts:30]` | CLI verbs + MCP registry | Your ADE |\n| **kandev** (kdlbs) | Go + web | AGPL-3.0 | 528 | 2026-08-02 (v0.83.0) | Yes `[SRC apps/backend/go.mod: UserExistsError/conpty v0.1.4 + build-tag-split PTY files + cmd/winjob/]` | **Yes** \u2014 Settings \u2192 Agents \u2192 Add TUI Agent (parsed with `strings.Fields`) `[DOC docs/public/agents-and-profiles.md]` | **Extensive**: `ws://localhost:38429/ws` `[DOC websocket-api.md]`, four MCP modes incl. **External MCP** `[DOC automation-and-mcp.md]`, `agentctl` HTTP route groups, plugin marketplace, service install | VIABLE |\n| **Pane** (dcouple) | Electron + Node/Python | AGPL-3.0 + attribution | 348 | 2026-07-31 | Yes (`@lydell/node-pty` prebuilds) `[DOC docs/BUILDING_ON_WINDOWS.md]` | **Yes** \u2014 `--tool-command ` `[DOC docs/RUNPANE_CLI_CONTRACT.md]` | `runpane` CLI (all `--json`) + machine-readable `contracts/runpane/contract.json` + first-party Python client + remote daemon | VIABLE |\n| **Zed** | Rust/GPUI | GPL-3.0 core + Apache-2.0 crates | 87,888 | v1.13.1 2026-07-29 | Yes | **Yes, ACP-shaped** `[UNV \u2014 not re-verified in any pass; see section 9]` \u2014 `agent_servers` in settings.json; spawned process must speak ACP JSON-RPC, not a raw PTY | ACP | VIABLE (different shape) |\n| **Agent Orchestrator** (Untrivial-ai) | Go + Electron | Apache-2.0 | 8,742 | 2026-08-02 | Yes | **No** \u2014 30 compiled Go packages under `backend/internal/adapters/agent/` | **`ao` CLI over a loopback HTTP REST API** `[DOC docs/cli/README.md]`: `ao spawn` \u2192 `POST /api/v1/sessions`, `ao send`, `ao session ls\\|get\\|kill\\|restore`, `ao agent ls`, `ao status --json`, `ao doctor --json`, `ao completion powershell`; `AO_SESSION_ID`/`AO_PROJECT_ID` let a nested agent resolve itself | **VIABLE \u2014 contains the middle layer** |\n| emdash (generalaction) | Electron | Apache-2.0 | 5,320 | 2026-08-02 | Yes (.msi + .exe) | No \u2014 fixed 35-provider enum `[DOC agents/integrations/providers.md]` | None found | CONTEXT |\n| paseo (getpaseo) | Electron + Node daemon + Expo | AGPL-3.0 | 11,862 | v0.2.5 2026-07-30 | Yes | No \u2014 fixed enum `[SRC packages/protocol/src/provider-manifest.ts]` | WebSocket API + MCP server in `packages/server` | CONTEXT |\n| Nimbalyst | Electron | MIT | 1,373 | 2026-08-01 | Yes | **No** \u2014 exactly 4 agents; its extension system is for **file-type editors**, not agents `[DOC Nimbalyst README]` | Desktop + mobile | CONTEXT |\n| **Vibe Kanban** (BloopAI) | Rust + npx | Apache-2.0 | 27,622 | **SUNSET 2026-04-24** | \u2014 | No | MCP + HTTP + relay tunnel | **DEAD** \u2014 `[SRC gh api repos/BloopAI/vibe-kanban/commits]` second-to-last commit is literally \"Add README sunsetting banner\"; README `\n`: \"Vibe Kanban is sunsetting.\" 534 open issues, repo left read-write |\n| Crystal (stravu) | \u2014 | MIT | 3,107 | **DEPRECATED Feb 2026** | \u2014 | \u2014 | \u2014 | **DEAD** \u2014 produced **two** descendants, not one: Nimbalyst (named successor) and **Pane** `[SRC dcouple/Pane docs/CRYSTAL_ARCHITECTURE.md, a renamed Crystal doc]`; Nimbalyst still ships `crystal-run.ps1` |\n| Terragon | \u2014 | Apache-2.0 | 253 | **SHUT DOWN Jan/Feb 2026** | \u2014 | \u2014 | \u2014 | **DEAD** \u2014 OSS snapshot only |\n| Roo Code (RooCodeInc) | TS | Apache-2.0 | 24,361 | **ARCHIVED**, last push 2026-05-15 | Yes | \u2014 | VS Code ext | **DEAD**, no stated reason |\n| cmux (manaflow-ai) | Swift+AppKit | **GPL-3.0-or-later** (GitHub reports NOASSERTION only because of a dual-offer preamble) | 25,483 | 2026-08-02 | **No \u2014 macOS only** | Yes (`cmux.json`, `cmux hooks setup --agent`) | CLI hooks | REFUTED \u2014 but holds the only **level-3 persistence** claim in the census |\n| superset-sh/superset | Bun | NOASSERTION | 12,733 | 2026-08-02 | **No** (\"Windows/Linux untested\") | **Best documented override of any candidate** \u2014 Settings \u2192 Agents, \"add any terminal agent\" | \u2014 | REFUTED (platform) |\n| imbue-ai/sculptor | desktop | **MIT** | 212 | 2026-08-01 | **No** (Mac/Linux/Linux-ARM64 downloads only) | \"run and manage _any_ terminal-based agents\" | \u2014 | REFUTED (platform) |\n| coder/mux | Electron | AGPL-3.0 | 1,950 | 2026-08-02 | Yes (v0.28.1 ships `.exe` despite README saying otherwise) | N/A \u2014 has its **own** agent loop, does not spawn external CLIs | VS Code ext | REFUTED (wrong architecture) |\n| OpenChamber | Electron/web | MIT | 7,122 | 2026-08-02 | Yes | No \u2014 bundles the matching OpenCode CLI | `--lan`/`--host`, SSH remote | CONTEXT |\n| automagik-dev/genie; iishyfishyy/operator-oss | TS | MIT / Apache-2.0 | 327 / 170 | 2026-07-30 / 2026-08-01 | Unknown | Unknown | Unknown | UNVALIDATED |\n| Long tail (topic sweep) | mixed | mixed | 0-350 each | mostly 2026-07/08 | Unknown | \u2014 | \u2014 | UNVALIDATED \u2014 wibus-wee/cradle-app (50\u2605), kaanozhan/Frame (319\u2605), soromi (32\u2605), uxnan (24\u2605), Termote (28\u2605), workstream-labs/workstreams (76\u2605, stale 2026-04-22), Zeus-Deus/codemux, construct-worlds/construct, ~24 more &lt;30\u2605 |\n| GitKraken Kepler; JetBrains Air; Warp; Factory/Droid; Devin; Google Jules; Cursor; Conductor; AIDEN | closed | proprietary | \u2014 | \u2014 | Yes (except Conductor: Mac-only) | No documented arbitrary-CLI override found in any `[UNV \u2014 closed-source; nothing here was verified at source in any pass]` | Warp: Oz CLI + HTTP API + Python/TS SDKs; Air: MCP + provider accounts `[UNV]` | **CONTEXT (commercial)** |\n| Cline, Kilo Code, opencode, Amp, Codebuff | \u2014 | Apache-2.0 / MIT / MIT / proprietary / Apache-2.0 | 65k / 26.7k / **192k** / \u2014 / 8.2k | all 2026-08 (month granularity \u2014 exact `pushed_at` not pulled) `[UNV]` | \u2014 | \u2014 | \u2014 | **CONTEXT \u2014 these are the agents, i.e. the thing a middle layer wraps** `[UNV \u2014 the agent-vs-host classification was not re-verified; see section 9]`. Note `sst/opencode` now redirects to `anomalyco/opencode`; `opencode-ai/opencode` is a **different, archived** project (13,604\u2605, last push 2025-09-18) \u2014 do not conflate |\n| microsoft/intelligent-terminal | \u2014 | MIT | 1,639 | 2026-08-02 | Yes (Win 10 19041+) | Auto-detects Copilot/Claude/Codex/Gemini/OpenCode | ACP over stdio | CONTEXT \u2014 a Windows Terminal fork with an agent pane; itself an ADE-layer competitor |\n| batrachianai/toad | Python/Textual | AGPL-3.0 + `commercial-license` topic | 3,350 | 2026-05-26 | **No** | ACP client | \u2014 | REFUTED \u2014 `[DOC batrachianai/toad README:175-177]` \"Toad runs on Linux and macOS. Native Windows support is currently lacking\u2026 but Toad will run quite well with WSL\"; roadmap `- [ ] Windows native support` unchecked |\n\n**Agent-churn casualty count: 6.** Crystal (Feb 2026), Terragon (Jan/Feb 2026), Roo-Code (archived, 2026-05-15), Vibe Kanban (sunset 2026-04-24, 27.6k stars \u2014 the largest), DevPod (abandoned, see 3.5), Daytona (closed-sourced June 2026). **Method note:** \"stale\" and \"sunset\" were conflated once in this research. Any candidate whose last push is &gt;60 days old needs a specific check for a shutdown notice, not just a date.\n\n### 3.5 Containers, sandboxes, isolation\n\n**Read this first \u2014 it is why the twenty rows below do not change the answer.** Containerising an agent session **relocates** the PTY-ownership problem; it does not eliminate it. Every viable container path still needs something *inside* the boundary to own the session \u2014 tmux-in-pod, or sbx's proprietary attach layer \u2014 and adds one more VT-parsing and resize boundary on top of ORCA's own PTY. **Containers are an optional outer isolation shell around the middle layer, not a replacement for it.** The census is here because the WSL question had to be settled precisely rather than waved at, and because two open-source non-WSL paths do survive.\n\n| Name | Category | License | Native Win without WSL | Persist+reattach | Verdict | Evidence |\n|---|---|---|---|---|---|---|\n| Windows Containers (process isolation) | OS feature | N/A | Yes | \u2014 | **REFUTED** | `[DOC learn.microsoft.com]` Windows kernel only; cannot run Linux binaries |\n| Windows Containers (Hyper-V isolation) | OS feature | N/A | Yes | \u2014 | **REFUTED** | Real VM boundary, but the guest is still Windows. Page \"Applies to\" lists Server 2016-2025 only, no Windows 11 client SKU `[UNV \u2014 the \"Applies to\" field was quoted from the docs page; no URL was recorded and it was not re-retrieved]` |\n| Docker Desktop, Hyper-V backend | container runtime | **proprietary, paid above 250 employees / $10M** | Yes (all-users install, Pro/Ent/Edu, `com.docker.service`) | \u2014 | **REFUTED on constraint 2** | `[DOC docs.docker.com/desktop/setup/install/windows-install/]` \"Docker Desktop's functionality remains consistent on both WSL and Hyper-V, without a preference for either architecture\" \u2014 the widely-repeated \"Hyper-V is deprecated\" blog claim is false. Same page: `docker-users` membership \"is equivalent to granting administrative privileges on the host\" |\n| **Podman, `hyperv` machine provider** | container runtime | **Apache-2.0** | **Yes** | \u2014 | **VIABLE (one of two)** | `[DOC containers/podman docs/tutorials/podman-for-windows.md]` \"Because Podman uses WSLv2 or Hyper-V recent features, you need **Windows 11 or later**\"; Hyper-V Administrators group required (mitigable once via `podman system hyperv-prep`); \"**WSL and Hyper-V machines cannot run simultaneously**\". Scriptable: `podman machine init --provider hyperv`, `CONTAINERS_MACHINE_PROVIDER=hyperv`, `[machine] provider = \"hyperv\"` |\n| **minikube `--driver=hyperv`** | k8s-for-one | Apache-2.0 | **Yes** | \u2014 | **VIABLE (two of two)** | `[DOC minikube.sigs.k8s.io/docs/drivers/ (page footer: \"Last modified September 25, 2025\")]` Windows drivers list \"Hyper-V \u2014 VM (**preferred**)\" \u2014 first-class, not a fallback. **Scope correction:** the same list marks \"Docker \u2014 VM + Container (**preferred**)\" as well. Both carry the label; quoting only Hyper-V overstates the point. What survives is that Hyper-V is not a fallback |\n| Docker Sandboxes (`sbx`) | agent microVM | **closed** (`docker/sbx-releases` is binaries-only) | **Yes**, Windows Hypervisor Platform, **no edition gate**, \"Docker Desktop is not required\" `[DOC docs.docker.com/ai/sandboxes/get-started/]` | **Yes** \u2014 `sbx create --name X claude .` then `sbx run --name X` \"re-attaches from anywhere\"; \"Sandboxes persist after the agent exits\"; active-session concept; SSH; documented CI/headless path `[DOC docs.docker.com/ai/sandboxes/usage/]` | **REFUTED on constraint 2 only** | Two pages, cited separately above \u2014 the prerequisites live on `get-started/`, the session semantics on `usage/`. **Judgement: architecturally the closest existing thing to what you want.** Requires `sbx login` (Docker OAuth, browser). No scrollback-replay claim. |\n| Rancher Desktop | k8s-for-one | Apache-2.0 | **No** | \u2014 | REFUTED | `[DOC docs.rancherdesktop.io \u2014 installation prerequisites]` \"Rancher Desktop requires Windows Subsystem for Linux on Windows\" \u2014 unconditional |\n| k3s | k8s distro | \u2014 | **No** | \u2014 | REFUTED | `[DOC docs.k3s.io/installation/requirements]` Linux-only by its own requirements page \u2014 no Windows server node support |\n| kind | k8s-for-one | Apache-2.0 | Inherits backend | \u2014 | VIABLE-by-inheritance | Backend-agnostic auto-detect. **Note:** Docker Desktop's \"bundled Kubernetes\" **is** kind (or kubeadm) \u2014 they are not two candidates `[DOC docs.docker.com/desktop/features/kubernetes/]` |\n| dagger/container-use | agent MCP | Apache-2.0 | **Partial and broken** | \u2014 | **REFUTED (maturity + Windows)** | `[DOC PR #252, open since 2025-07-24]` \"#242 added Windows compilation but **no installation method**\". `[SRC cmd/container-use/terminal_windows.go]` discards caller args (`cmd.Args = append([]string{\"dagger\",\"run\"}, os.Args...)`) \u2014 open fix PR #348. Last release 2025-08-19; only 2026 commits are two docs edits. Registers as `container-use stdio` \u2014 a stdio MCP server, which cannot own persistent sessions |\n| loft-sh/DevPod | devcontainer client | OSS | Docs only show WSL | \u2014 | **REFUTED (abandoned)** | Last release v0.7.0-**alpha**.34 (2025-06-23); `[DOC issue #1946]` \"their attention is focused on vcluster\u2026 they do not have plans at this moment to allocate resources to maintaining this project\" |\n| daytonaio/daytona | agent sandboxes | **none \u2014 license removed** | \u2014 | \u2014 | **REFUTED (closed-sourced)** | Repo root now contains only `README.md` and `assets`; `GET /license` \u2192 404. README: \"As of June 2026, Daytona's core development has moved to a private codebase.\" 72k stars on an empty repo |\n| **anthropic-experimental/sandbox-runtime (`srt`)** | OS sandbox | **Apache-2.0** | **Yes, alpha** \u2014 dedicated `srt-sandbox` local user + Windows Filtering Platform egress fence keyed to that SID | N/A | **CONTEXT \u2014 and architecturally hostile** | **The cross-track finding:** `[DOC README]` the sandboxed process is launched via `CreateProcessWithLogonW` under a **different local user account**, \"not as the calling user\". Two consequences: (a) `[INF]` a console owned by a foreign user SID is not `AttachConsole`-able by your process; (b) `[DOC README, no inference needed]` \"Per-user tool installs are not reachable\u2026 tools installed under your profile (nvm/fnm-managed Node, per-user winget/Scoop packages, `pip install --user`, `%LOCALAPPDATA%\\Programs\\\u2026`) resolve on the inherited PATH but cannot be opened by the sandbox account.\" **Claude Code's default Windows install lands exactly there.** 4,824 stars, pushed 2026-07-24 |\n| Claude Code built-in Bash sandbox | agent sandbox | \u2014 | **No** | \u2014 | REFUTED | `[DOC code.claude.com/docs/en/sandbox-environments]` verbatim: \"This option does not support native Windows. On Windows hosts, use WSL2 or one of the container or VM approaches below.\" |\n| Windows Sandbox | OS feature | N/A | Yes | **No** \u2014 resets on close, no CLI attach model | REFUTED | Fails headless-first and persistence |\n| LCOW (Linux Containers on Windows) | historical | \u2014 | \u2014 | \u2014 | **DEAD** | `[DOC docs.docker.com/engine/deprecated/]` deprecated v20.10, removed v23.0: \"the feature never reached completeness\u2026 Developers who want to run Linux workloads on a Windows host are encouraged to use Docker Desktop with WSL2 instead\" |\n| `docker attach` / `exec` | attach semantics | \u2014 | \u2014 | L1-ish | CONTEXT | `attach` shows only PID-1's stream, no scrollback replay; `CTRL-p CTRL-q` detach requires `-i -t`. `[DOC docs.docker.com/reference/cli/docker/container/attach/]` **`--detach-keys` is remappable**, per-container or globally \u2014 the prefix collision is configurable away, exactly like tmux's prefix. This is the only *documented* prefix-remapping mechanism found in the whole census; see 4.3. moby/moby#2838 and #2855 (both 2013) still cited |\n| `kubectl attach` / `exec` | attach semantics | \u2014 | \u2014 | L0 | CONTEXT | Fresh process every time. Community's own fix is **tmux inside the pod** \u2014 `predatorray/kubectl-tmux-exec`, which is 100% Shell+Makefile `[UNV \u2014 language breakdown taken from repo metadata, not re-pulled this pass]` and needs bash+tmux on the **client**, so it cannot run on native Windows |\n| OpenHands runtime | agent fleet | \u2014 | **No** | \u2014 | REFUTED | `[DOC docs.all-hands.dev \u2014 installation/local-setup]` \"OpenHands only supports Windows via WSL\u2026 Native Windows is not officially supported\" (supersedes an older non-WSL guide) |\n\n**The container/WSL tension, resolved precisely.** It is narrower than \"containers on Windows = WSL only\", but the surviving set is small. Hyper-V-the-hypervisor and WSL2-the-compatibility-layer are different code paths over the same primitives; the non-WSL path exists but is consistently the less-traveled option: all-users installs, admin at install time, and a Windows Pro/Enterprise/Education edition gate (Home cannot run Hyper-V). After applying constraint 2, **exactly two open-source non-WSL container paths survive on native Windows: Podman with `--provider hyperv`, and minikube `--driver=hyperv`.** Everything else is closed (Docker Desktop, sbx, Daytona), hard-WSL (Rancher Desktop, k3s, Claude Code's sandbox, OpenHands), dead (DevPod, container-use), or not a Windows product (Sculptor).\n\n**And the thesis at the top of this section survives the whole census intact:** the two viable paths still need something inside the boundary to own the session, so they are an outer shell around the middle layer, never a substitute for it.\n\n### 3.6 Protocols and wire formats\n\n**Two kinds of row, in one table, distinguished by the `Kind` column.** **Contract** rows are things you could *adopt* \u2014 a wire format with an implementation you can speak to today. **Precedent** rows are things you could *learn from* \u2014 designs from other ecosystems that solved a structurally identical problem, with no code you would ship. Do not read a `CONTEXT` verdict on a Precedent row as a weaker version of a `VIABLE` verdict on a Contract row; they are not on the same axis.\n\n| Name | Kind | Shape | License | Adoption | Native Win | Persist/detach | Verdict | Evidence |\n|---|---|---|---|---|---|---|---|---|\n| **tmux control mode (`-C`/`-CC`)** | **Contract** | Line-based text: commands in, `%begin`/`%end`/`%output`/`%window-add`/`%layout-change` out | ISC | **\u22655 implementers, not 1** | **Yes, via psmux and WezTerm** | Inherits tmux/psmux | **CONTEXT \u2192 strong reusable contract** | `[DOC tmux.1 CONTROL MODE]`. Implementers: iTerm2; **WezTerm** `[SRC wezterm-escape-parser/src/tmux_cc/tmux.pest, mux/src/tmux.rs, tmux_pty.rs:90-91 `#[cfg(windows)] fn as_raw_handle`; changelog records a **Windows-specific `tmux -CC` bug being fixed**]`; **psmux** `[SRC src/main.rs:4194, src/control.rs 554 lines, docs/control-mode.md 432 lines, 3 PowerShell tests]`; Tomiyou/ivyterm (48\u2605); paulrobello/par-term (`par-term-tmux` crate) |\n| **WezTerm mux codec** | **Contract** | Versioned binary, varint length/ident/serial | MIT | 28k-star project | **Yes** | L2 | **VIABLE** | `[SRC codec/src/lib.rs]` \"length, ident and serial number are encoded using a variable length integer encoding\u2026 client and server can more gracefully manage unknown enum variants\" \u2014 **explicitly designed for version-skewed client/server pairs, i.e. the ADE-churn insulation you are shopping for** |\n| **OpenCode PTY wire protocol** | **Contract** | Raw UTF-8 chunks + one `0x00`+JSON control frame carrying an absolute output cursor | MIT | 192k stars | Yes | replay by cursor | **VIABLE** | `[SRC packages/core/src/pty/protocol.ts; pty.ts:87 `attach: (id, input) =&gt; Effect`, lines 53-67 cursor/replay]` |\n| **`ao pty-host` protocol** | **Contract** | `[1-byte type][BE32 len][payload]`, 8 messages | Apache-2.0 | inside 8.7k ADE | Yes | L2 | EXTRACTABLE (vendor) | `[SRC conpty/proto.go]` MsgTerminalData 0x01 \u2026 MsgKillReq 0x08 |\n| **qscreen `ScreenFrame`/`ScreenRun`** | **Contract** | Structured runs with FRAME_FLAG_* + FrameColor + FrameMouseMode | MIT | 6 stars | Yes | L2 | CONTEXT | Same \"parse once, let the last hop render\" idea as tmux `-CC`, arrived at independently |\n| **ACP (Agent Client Protocol)** | **Contract** | JSON-RPC 2.0, LSP-modeled | Apache-2.0 | 3,840\u2605; Zed, JetBrains, Copilot CLI, Gemini CLI, Claude Code, Neovim, Toad | **No Windows CI** \u2014 every job in `.github/workflows/ci.yml` is `ubuntu-latest` | `session/load` (replays history), `session/resume`, `session/list`, `session/fork`, `session/close` \u2014 **conversation-state continuity, not live-process attach** | CONTEXT | `[DOC docs/protocol/v1/draft/transports.mdx]` \u2014 has a Transports chapter naming **Streamable HTTP** as transport 2 (draft) and stating \"The protocol is transport-agnostic\u2026 can be implemented over any communication channel that supports bidirectional message exchange\", formally sanctioning custom transports. **So ACP over a Windows named pipe is spec-conformant.** v2 terminal work in flight (`schema/src/v2/terminal.rs`, `docs/rfds/v2/terminal-output.mdx`) |\n| **Codex `app-server`** | **Contract** | \"JSON-RPC lite\" with Thread/Turn/Item primitives | \u2014 | OpenAI's flagship CLI | **Transport yes, daemon no** | Threads persist | **VIABLE with a caveat** | `[SRC codex-rs/uds/src/lib.rs]` is cross-platform (uds_windows on Windows); `[SRC app-server-transport/src/transport/mod.rs:27-32]` registers `remote_control`, `stdio`, `unix_socket`, `websocket` **unconditionally**. `--listen ws://IP:PORT` is a standing listener the client does **not** own \u2014 but `[DOC app-server/README.md]` labels websocket \"**experimental / unsupported**. Do not rely on it for production workloads\", and `[DOC codex-rs/app-server-daemon/README.md]` \"The current daemon implementation is **Unix-only**\u2026 does not yet support Windows lifecycle management.\" Ships `/readyz`, `/healthz`, backpressure returning `-32001`, `generate-ts`/`generate-json-schema` for a version-pinned schema. `[ART rust-v0.147.0-alpha.4]` ships `codex-app-server-x86_64-pc-windows-msvc.exe` |\n| **Warp `crates/local_control`** | **Contract** | Versioned action catalog over authenticated loopback HTTP `/v1/control` + Unix-socket credential broker | AGPL-3.0 (client) | 63,870\u2605 | Mixed \u2014 real `[target.'cfg(windows)'.dependencies]` block, but the auth design leans on a **kernel-reported peer-UID check**, POSIX-only | \u2014 | CONTEXT \u2014 **the closest open-source analogue to tmux control mode found** | `[SRC warpdotdev/warp, branch master, crates/local_control/src/catalog.rs:4 `PROTOCOL_VERSION: u32 = 1`]` **84** entries carry `status: Implemented` (earlier editions said \"~60\"): `pane.list/split/resize/focus/navigate/close`, `session.list/activate`, `input.insert/replace`, window/tab/surface/file. **No output-read, no send-keys-to-PTY, no attach** \u2014 that negative is confirmed by reading the whole catalog, and it is why this is not a PTY-attach protocol. But \"cloud orchestration only\" was false |\n| **Jupyter terminado REST+WS** | **Contract** | `GET/POST /api/terminals`, WS `[\"stdout\",text]`/`[\"stdin\",text]`/`[\"set_size\",r,c]` | BSD-3 | Jupyter deployments | Yes | L1 (`NamedTermManager`), with `cull_inactive_timeout` | CONTEXT \u2014 a design template, not an interop standard | See 3.3 row notes |\n| **Jupyter kernel protocol** | **Precedent** | 5 ZeroMQ sockets, `execute_request`/`reply` | BSD-3 | the notebook ecosystem | Yes | Yes | CONTEXT | Different subsystem from Terminals. `bash_kernel` (729\u2605) proves it generalizes past language interpreters \u2014 but is **POSIX-only** `[SRC kernel.py:116,139 `signal.SIGPIPE`; :123 `pexpect.spawn`]` and stale since 2025-01-05 |\n| **MCP for terminals** | **Contract (absent)** | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | **NEGATIVE SPACE** | Zero terminal/PTY SEPs among 41 Final ones `[DOC modelcontextprotocol.io/seps]`; dozens of independently-named `mcp-terminal-server` repos with no shared schema. **Scope correction:** earlier editions attached the Codex-harness quote to \"OpenAI tried MCP for terminals and abandoned it\". The source does not say that \u2014 it never mentions terminals or PTYs. What it does say `[DOC openai.com/index/unlocking-the-codex-harness]` is that \"maintaining MCP semantics in a way that made sense for VS Code proved difficult\", i.e. OpenAI abandoned MCP as its **VS Code integration** protocol. That is weaker evidence, and it is only suggestive of the terminal case |\n| **DAP** | **Precedent** | Seq-numbered request/response/event JSON, transport-agnostic | \u2014 | universal | Yes | \u2014 | CONTEXT (design precedent only) | No primary source found of DAP driving a general shell |\n| **xterm.js attach-addon convention** | **Contract** | Raw bytes over WebSocket | \u2014 | ttyd, code-server, VS Code | Yes | \u2014 | **CONTEXT** | `[UNV]` The superlative used in earlier editions \u2014 \"arguably the most widely deployed de-facto terminal wire convention in existence\" \u2014 is **unsourced and untested**, and this document separately concedes (section 9) that the convention went unexamined. It is retained as a lead, demoted to an explicit `[UNV]` claim, and it is the single largest known gap in this map's protocol coverage |\n| **asciicast / ALiS** | **Contract** | 4 negotiated WS sub-protocols (`v1.alis` binary with 5-byte magic `ALiS\\x01` = `[0x41,0x4C,0x69,0x53,0x01]` + LEB128, `v2.asciicast`, `v3.asciicast`, `raw`) | \u2014 | asciinema ecosystem | Producer is trivially portable | relay holds full VT state for late joiners `[DOC docs.asciinema.org/manual/server/streaming/]` \u2014 \"The server maintains comprehensive state for each active stream by running the whole stream through asciinema's own virtual terminal emulator\" | CONTEXT \u2014 **the CLI is refuted on Windows; the ecosystem is not** | `[DOC docs.asciinema.org/manual/server/streaming/]` for the magic bytes, LEB128 framing, all four sub-protocols and the late-joiner state claim. **Judgement:** a native-Windows producer is small, and it gets an existing OSS relay with N-consumer fan-out and late-joiner screen sync for free. The \"~50 lines\" figure in earlier editions was an unsupported estimate and is withdrawn |\n| **nREPL** | **Precedent** | bencode over TCP, every message has an `op`, `describe` for capability discovery, `clone` for sessions | EPL-1.0 | Clojure ecosystem; 900\u2605, pushed 2026-07-18 | Yes | Yes | CONTEXT \u2014 **best design reference in the whole map** | `[DOC spec.nrepl.org]` \"solves the 'narrow waist' problem\u2026 shares a lot with the Language Server Protocol, though it **predates LSP by several years**\u2026 Writing an nREPL server\u2026 can be done in a couple hundred lines.\" Self-describing, session-oriented, no PTY |\n| **SLIME/swank** | **Precedent** | s-expressions over TCP | permissive | 2,067\u2605, 2026-07-30 | Yes | Yes | CONTEXT | The cleanest one-sentence statement of your goal: the frontend is disposable, the real long-running state lives behind a socket |\n| **Distributed Erlang** **Precedent** | (`epmd`, hidden nodes, `-remsh`) | BEAM distribution | Apache-2.0 | foundational | Yes | Yes | CONTEXT \u2014 **and a citable warning** | `[DOC erlang.org/doc/system/distributed.html Security section]` cookie authentication is **not cryptographically secure** and communication is cleartext by default. Do not homegrow a shared-secret scheme that is weaker than one whose own docs disclaim it. (`-remsh` is documented in the `erl` man page, not this one) |\n| **TN3270E** **Precedent** | (RFC 2355) | Session-state protocol: host sends a BIND image, client ACKs | \u2014 | enterprise mainframe | **Yes** \u2014 `wc3270`/`ws3270`/`s3270`, Chocolatey | partial | CONTEXT \u2014 **under-rated** | `[DOC x3270 Scripting wiki]` the x3270 family ships a **headless scriptable emulator with a three-transport control plane**: `-script` (stdio), `-scriptport` (TCP, **not** marked POSIX-only), `-socket` (**POSIX only**), `-httpd` (**REST over HTTP**). Plus `X3270COOKIEFILE` security cookies (new in 4.4). `[SRC GitHub code search over the x3270 source tree]` 179 `httpd` hits, 69 `scriptport`, **zero `CreateNamedPipe`** \u2014 on Windows the control plane is TCP loopback + HTTP REST |\n| **Source RCON** | **Precedent** | TCP auth packet then command/response | open spec | game servers | Yes | No | CONTEXT | A reference for a tiny \"one command, one reply\" framing. **Correction: the CVE attribution was wrong.** Earlier editions cited \"Documented RCE (CVE-2021-30481)\" as an RCON protocol vulnerability. `[SRC NVD, CVE-2021-30481]` that CVE is *\"Valve Steam before 2021-04-17, when a Source engine game is installed, allows remote authenticated users to execute arbitrary code because of a buffer overflow that occurs for a Steam **invite** after one click\"* \u2014 a Steam invite-handling overflow, **not an RCON protocol defect**. The adjacent claim, that RCON is **cleartext by spec**, is correct and is the one that matters for a design reference. Note: developer.valvesoftware.com sits behind an anti-scraper proof-of-work wall \u2014 the spec itself was **not re-verifiable** this pass |\n| **DOOR32.SYS** **Precedent** | (BBS) | Hands the child an **already-open Winsock socket handle number** | \u2014 | retro | Yes | No | **CONTEXT \u2014 a 20-year-old precedent for \"pass a handle, don't emulate a device\"** | Replaced the DOS-era FOSSIL/INT14h fake-serial-port convention. **Directly load-bearing warning:** `[DOC ENiGMA\u00bd issue #175]` \"socket descriptors cannot be shared\" in Node.js; its own docs' workaround is `bivrost!`, **a Rust sidecar between the Node host and the handle**. ORCA is Electron/Node \u2014 the BBS world hit this exact wall and solved it with the Rust-daemon shape psmux/oly/qscreen already occupy |\n| **Evennia Portal/Server split** | **Precedent** | Two co-located processes over a Twisted AMP TCP socket | BSD-3 | 2,084\u2605, 2026-07-25 | Yes `[DOC evennia.com/docs \u2014 Setup/Installation, Windows section]` | **Yes** | **CONTEXT \u2014 the single best transferable framing found** | `[DOC evennia.com/docs Portal-And-Server]` \"you can fully reload the Server and have players still connected to the game. One [sic \u2014 upstream typo for \"Once\"] the server comes back up, it will re-connect to the Portal and re-sync all players as if nothing happened.\" (Earlier editions silently corrected the typo inside what was presented as a verbatim quotation; the `[sic]` restores it.) **Portal owns every protocol and every persistent connection; Server owns all logic and can crash/reload freely.** That is your ADE-swap requirement, named and shipped, in a text-game engine |\n| **Superlogical** | **Precedent** | unpublished | **presumptively closed commercial** | waitlist only | Unknown \u2014 the cited post contains **no Windows content at all**. (Earlier editions put \"Ghostty has no official Windows GUI\" in this cell; true or not, it is not supported by the source cited here and is not about Superlogical) | unknown | **REFUTED on constraint 2** | `[DOC mitchellh.com/writing/superlogical, 2026-07-29, full text]` \"I've started a **new company**: Superlogical\"; \"Sign up for the newsletter for beta access\"; \"we're hiring\"; \"I have no intention of ever commercializing Ghostty\u2026 Ghostty itself remains a non-profit.\" **Only the consumed components are MIT.** The post contains **zero** architecture content \u2014 no state-sync frames, no protocol description; any such claim traces to secondary journalism |\n\n### 3.7 PTY libraries and ConPTY plumbing\n\n| Library | Language | Windows backend | Notes |\n|---|---|---|---|\n| microsoft/node-pty | Node/C++ | ConPTY | **What ORCA uses.** `[SRC src/win/conpty.cc:289]` the **only** flag site is verbatim `inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0`. `[SRC src/win/conpty.h:26-27]` defines exactly two extra symbols, `PSEUDOCONSOLE_RESIZE_QUIRK (2u)` and `PSEUDOCONSOLE_PASSTHROUGH_MODE (8u)` \u2014 **`PSEUDOCONSOLE_WIN32_INPUT_MODE` is not defined in that header at all**, contrary to earlier editions. **The \"ORCA is disadvantaged on flags\" conclusion is deleted \u2014 see the note below the table; it does not follow, because current ConPTY does not read those bits.** A separate, real hazard: node-pty's bundled prototype has **drifted from the current ConPTY ABI** \u2014 it declares `ConptyClearPseudoConsole(HPCON hPC)` while `microsoft/terminal src/inc/conpty-static.h:44` declares `ConptyClearPseudoConsole(HPCON hPC, BOOL keepCursorRow)`. A one-argument call against a two-argument export is an ABI hazard on the `useConptyDll` path. Drift runs both ways: node-pty also declares `ConptyClosePseudoConsoleTimeout`, which the current public header does not export |\n| wezterm `portable-pty` | Rust | ConPTY | Passes `INHERIT_CURSOR \\| RESIZE_QUIRK \\| WIN32_INPUT_MODE` (and has for years \u2014 the Feb-2024 commits `454ec0a7`/`9d834586` in `wezterm/wezterm` were a one-day regression and its fix, not the introduction) `[UNV \u2014 the two SHAs were not re-resolved this pass; treat the commit identifiers as unconfirmed and the \"has for years\" characterisation as the load-bearing part]`. **Two of those three bits are no-ops on current ConPTY** (see the note below). Loads `conpty.dll` by **bare relative name** \u2014 hijackable by a host terminal's bundled copy |\n| `portable-pty-psmux` 0.9.6 | Rust | ConPTY | Vendored in-tree at `crates/portable-pty-psmux`. **Deliberately drops INHERIT_CURSOR** \u2014 the reasoning, with the source comment, is in **4.1 item 2** (earlier editions pointed at 6.1, which does not discuss `INHERIT_CURSOR`) |\n| Helvesec/rmux `rmux-pty` | Rust | ConPTY | `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE`, Job Objects with `CREATE_BREAKAWAY_FROM_JOB`, `FILE_FLAG_FIRST_PIPE_INSTANCE` `[SRC crates/rmux-pty/src/backend/windows/io.rs]`, and an explicit refusal path with the verbatim error string \"breakaway process creation denied; refusing to run unguarded ConPTY child\" `[UNV \u2014 string quoted from an earlier pass; the exact file and line were not recorded and were not recovered this pass]` |\n| charmbracelet/x/conpty | Go | ConPTY | Used by upterm; the `xpty` family |\n| aymanbagabas/go-pty v0.2.3 | Go | ConPTY | Used by Agent Orchestrator |\n| UserExistsError/conpty v0.1.4 | Go | ConPTY | Used by kandev |\n| **creack/pty** | Go | **none** | `[SRC start_windows.go]` literally `return nil, ErrUnsupported`. Its README never mentions Windows. **Scope correction \u2014 the single-signal version of this test is wrong.** Earlier editions called it \"the single most reliable Go-project disqualifier in this research\". It is not, because a Go project can depend on `creack/pty` *and* be ConPTY-capable through a second library: **quil**, in this document's own inventory, has `creack/pty` in its `go.mod` **and** `charmbracelet/x/conpty v0.2.0`, and works on Windows. The correct test is the **compound** one this document already applies in the claude-squad row: `creack/pty` present **and no ConPTY library of any kind** `[SRC smtg-ai/claude-squad go.mod: creack/pty v1.1.24, zero conpty imports]`. gotty, tty-share and claude-squad fail the compound test; quil passes it |\n| photostorm/pty | Go | ConPTY | The fork waveterm uses to escape creack/pty. **15 stars, last push 2024-04-14** \u2014 a real supply-chain risk |\n| pywinpty | Python/Rust | ConPTY + winpty fallback | **Windows-only** \u2014 not cross-platform, so it is disqualified on constraint 3 as a component even though it works. `[2ND \u2014 the maintainer's 2025 Jupyter Discourse post; named in prose in an earlier pass with no URL recorded, and not re-retrieved]` &gt;30M downloads, \"maintained by a single person\", features \"delayed\u2026 due to this lack of resources\". **Its architecturally interesting part is documented once, in the terminado row of 3.3, not here** \u2014 the per-PTY ephemeral 127.0.0.1 TCP listener, the `accept()` race, the `str` round-trip and the `'0011Ignore'` sentinel. Earlier editions carried that material in two places |\n| @lydell/node-pty | Node | ConPTY | Windows prebuilds; used by Pane and OpenCode's Node path |\n| bun-pty | Bun FFI | portable-pty (WezTerm) | Used by OpenCode's Bun path; has a `x86_64-pc-windows-gnu` publish job |\n| zhiburt `conpty` 0.7.0 | Rust | ConPTY | Used by sshx |\n| **rprichard/winpty** | C++ | ScreenBuffer scraping | 1,380\u2605, MIT, last push 2024-02-19. **Pre-ConPTY**: `ReadConsoleOutputW` + `WriteConsoleInputW` wrapped into a reusable read+write loop **exposed over named pipes**. Shipped for a decade in Git for Windows, MSYS2, Cygwin, pywinpty, node-pty's winpty backend. VS Code's own workaround for the AttachConsole crash is `terminal.integrated.windowsEnableConpty: false`, which falls back to it. Dormant and superseded \u2014 **its existence-and-supersession is the cleanest single argument for ConPTY-based designs** |\n| `Microsoft.Windows.Console.ConPTY` NuGet | redistributable | \u2014 | Bundled by quil (`scripts/fetch-conpty.sh`, SHA256-pinned), node-pty (`third_party/conpty`, 1.25.260303002), WezTerm, ghostel. `[DOC DHowett (MEMBER), microsoft/terminal#7019 comment dated 2026-06-10 \u2014 issue state: **closed as `not_planned` since 2023-09-29**]` **Framing correction:** earlier editions rendered this as \"Microsoft endorses this\". In context it is a **refusal to backport**: \"I'm sorry. When we backport\u2026 The architectural shift in ConPTY as of 1.22 is too broad for us to adequately contain. Use the NuGet package; it updates way faster than Windows.\" The practical instruction is the same \u2014 bundle the NuGet package \u2014 but the reason is that fixes will *not* reach the in-box ConPTY, which is a stronger argument for bundling and a weaker one about Microsoft's blessing |\n| `uds_windows` (Rust) | \u2014 | AF_UNIX | WezTerm + Codex independently |\n| `interprocess` (Rust) | \u2014 | named pipes via `GenericNamespaced` | Zellij + herdr + oly independently |\n| VT parsers | \u2014 | \u2014 | `vt100-psmux` 0.16.2 (a screen model over Alacritty's `vte` 0.15) \u2014 psmux and qscreen vendor it independently; `vt100` upstream \u2014 oly; `charmbracelet/x/vt` \u2014 quil; `libghostty-vt` (Zig) \u2014 boo, Roost, ghostel; **`asciinema/avt`** (Apache-2.0, 229\u2605) \u2014 `[SRC avt Cargo.toml]` its only dependencies are `rgb` and `unicode-width`, with no libc and no platform-conditional code; `[INF]` **it should therefore compile on native Windows \u2014 this is an inference from the dependency list, not a build anybody has run**, and it is recorded as such in section 9 |\n\n**The flags note, and the ranking claim it deletes.** Earlier editions of this document concluded that because node-pty passes only `INHERIT_CURSOR` while portable-pty passes `INHERIT_CURSOR | RESIZE_QUIRK | WIN32_INPUT_MODE`, \"a middle layer on portable-pty/psmux/rmux is strictly better on flags than the ADE hosting it\". **That claim is wrong and is withdrawn.** Current ConPTY does not read those bits at all.\n\n`[SRC microsoft/terminal src/winconpty/winconpty.cpp]` \u2014 the complete set of `dwFlags` consumers in the implementation is three lines:\n\n```cpp\n169: const auto inheritCursor   = (dwFlags &amp; PSEUDOCONSOLE_INHERIT_CURSOR)   ? L\"--inheritcursor \"   : L\"\";\n170: const auto ambiguousIsWide = (dwFlags &amp; PSEUDOCONSOLE_AMBIGUOUS_IS_WIDE) ? L\"--ambiguousIsWide \" : L\"\";\n173: switch (dwFlags &amp; PSEUDOCONSOLE_GLYPH_WIDTH__MASK)\n```\n\n`[SRC code search over microsoft/terminal]` `PSEUDOCONSOLE_RESIZE_QUIRK`, `PSEUDOCONSOLE_WIN32_INPUT_MODE` and `PSEUDOCONSOLE_PASSTHROUGH_MODE` are **zero hits each**. Three consequences, in order of how much they matter:\n\n1. **Passing `0x2 | 0x4` is a no-op.** portable-pty confers no flag advantage over node-pty, and ORCA is not disadvantaged here. Delete the claim wherever it is remembered.\n2. **Passing `0x8` is actively harmful.** `switch (dwFlags &amp; 0x18)` matches `case PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES (0x08)`, so anything still ORing the old `PASSTHROUGH_MODE` value **silently selects grapheme-based width measurement**. 6.1 states this collision correctly; the 3.7 table used to contradict it.\n3. **`win32-input-mode` as a *creation flag* is dead; as a *mode* it is alive.** It is negotiated in band at runtime (`CSI ?9001h`) \u2014 the `#4999` spec cited in 6.1 describes exactly that negotiation. Earlier editions conflated the dead flag with the live mode and derived a \"key-fidelity loss\" for ORCA that does not follow.\n\n**Method rule this produced, added to 7.2 as rule 9:** when you find a flag or a `#define`, check the **consumer**, not the header. Absence from a public header proves only \"undocumented\"; presence in a header proves nothing about whether anything reads it.\n\n### 3.8 Non-multiplexer channels: observation, control, and hosting\n\n| Item | What it is | Native Win | Verdict | Evidence |\n|---|---|---|---|---|\n| **Claude Code hooks** | Lifecycle hooks with **five** types, not three: `command`, `prompt` (an LLM evaluates the decision), `agent` (spawns an agentic verifier), **`http`** (POSTs event JSON to a URL, reads the decision from the response), **`mcp_tool`** (calls a tool on an already-connected MCP server), plus an `allowedHttpHookUrls` policy allowlist | **Yes** | **VIABLE-WITH-CAVEAT \u2014 a write path, not a tap, and `http` is not available at bootstrap** | `[DOC code.claude.com/docs/en/hooks:134]` \"The menu displays all **five** hook types: command, prompt, agent, http, and mcp_tool\" \u2014 earlier editions said three. `PreToolUse` returns `permissionDecision` of allow/deny/ask/defer plus `updatedInput` which **replaces** a tool's arguments. Verbatim: \"AskUserQuestion and ExitPlanMode require user interaction and normally block in non-interactive mode with the `-p` flag. Returning `permissionDecision: \"allow\"` together with `updatedInput` satisfies that requirement: the hook reads the tool's input from stdin, collects the answer through your own UI, and returns it in `updatedInput` so the tool runs without prompting.\" **Two scope limits earlier editions omitted, both load-bearing:** (a) `[DOC hooks:549, repeated at :221]` **\"SessionStart and Setup support command and mcp_tool hooks. They don't support http, prompt, or agent hooks\"** \u2014 so the two bootstrap events a middle layer most wants to hook are exactly the ones where the `http` endpoint trick is unavailable; use `mcp_tool` or `command` there. Thirteen events *do* support all five, including `PreToolUse`, `PermissionRequest`, `Stop`, `SubagentStop` and `UserPromptSubmit`, so the technique is intact where it counts. (b) `[DOC hooks:328]` **\"As of v2.1.199, an MCP tool whose server marks it with `_meta[\"anthropic/requiresUserInteraction\"]` is stricter: a hook can't skip its approval prompt with 'allow', with or without `updatedInput`\"** \u2014 the headless-answering trick has an explicit opt-out that tool authors control. **The middle layer can still BE the HTTP hook endpoint** for the thirteen supporting events \u2014 a long-lived process it owns, sidestepping the stdio-MCP lifetime problem entirely |\n| **Claude Code `terminalSequence`** | Supported escape-emission channel (v2.1.141+) | **Yes, explicitly** | VIABLE, narrow | `[DOC code.claude.com/docs/en/hooks:146,182-192]` \"Hooks run without a controlling terminal, so writing escape sequences directly to /dev/tty fails. Instead, return the escape sequence in the `terminalSequence` field and Claude Code emits it for you through its own terminal write path. This is race-free, works inside tmux and GNU screen, and **works on Windows where there is no /dev/tty**.\" Allowlist: OSC 0/1/2 (titles), OSC 9 (incl. **9;4 taskbar progress**), OSC 99, OSC 777, bare BEL. Restricted \"to sequences that can't move the cursor or alter colors\" \u2014 **so OSC 133 is not available**, but a hook can push out-of-band structured signals into the PTY byte stream that a nested middle layer parses natively |\n| **Claude Code OTel** | 34 named `claude_code.*` identifiers \u2014 a **mix of metrics, events and spans**, not 34 events (see 2.6) | **Yes** (TRACEPARENT inherited by Bash **and PowerShell** subprocesses) | **VIABLE \u2014 best headless observation channel** | Master switch is **`CLAUDE_CODE_ENABLE_TELEMETRY=1`**, not an `OTEL_*` var; traces additionally need `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`; the `claude_code.hook` span needs `ENABLE_BETA_TRACING_DETAILED=1` + `BETA_TRACING_ENDPOINT` + org allowlisting. Content is **redacted by default** \u2014 opt in via `OTEL_LOG_USER_PROMPTS`, `OTEL_LOG_TOOL_DETAILS`, `OTEL_LOG_TOOL_CONTENT`, `OTEL_LOG_ASSISTANT_RESPONSES`, and `OTEL_LOG_RAW_API_BODIES` (with a `=file:` mode writing untruncated bodies to disk with a `body_ref` path). Includes **`claude_code.tool.blocked_on_user`** \u2014 the \"is the agent stuck waiting on a human\" signal, with no PTY. **Caveat:** `[DOC hooks:152]` \"Claude Code removes `OTEL_*` exporter variables from every subprocess it spawns, including hooks\" \u2014 config does not reach grandchildren |\n| **Claude Code JSONL transcripts** | `~/.claude/projects//.jsonl`, one line per event | **Yes** (Windows path documented) | VIABLE | It is a **tree, not a file** `[EMP \u2014 inspected on this machine, native Windows 11 build 26200]`: a sibling `/` directory holds `subagents/agent-.jsonl` plus `agent-.meta.json` sidecars containing `{agentType, description, toolUseId, spawnDepth}`, plus `tool-results/` and `workflows/`. Subagent fan-out topology is readable from the filesystem alone. **Undocumented internals \u2014 version-fragile.** `CLAUDE_CONFIG_DIR` relocates the whole tree (the per-workspace isolation analogue of `QUIL_HOME`); `cleanupPeriodDays` garbage-collects it, so anything treating JSONL as durable needs its own retention |\n| **`claude -p --input-format stream-json --output-format stream-json --verbose`** | Full-duplex NDJSON over ordinary pipes | **Yes** `[DOC code.claude.com/docs/en/cli-reference]` for the flags; **\"identical on Windows and Linux\"** is `[INF]` \u2014 the flags are documented without platform qualification and the transport is ordinary pipes, but **no pass has run this on both platforms and compared output**. This is the second finding's load-bearing cross-platform claim and it rests on an inference; see section 9 and Q23 | **VIABLE \u2014 an alternative architecture** | Companion flags: `--include-hook-events`, `--forward-subagent-text` (with `parent_tool_use_id`), `--include-partial-messages`, `--replay-user-messages` `[DOC cli-reference]`. **Honest scoping:** this is print/headless mode \u2014 it does not attach to an existing interactive TUI. It is a way to build a middle layer that **spawns the agent with no PTY at all** |\n| **anthropics/claude-code#24365 and #6686** | Requests for `claude serve` / ACP-over-network | \u2014 | **FORECLOSED** | Both **closed as `not_planned`**. #24365 auto-closed by a stale-bot 2026-03-14, locked 2026-03-21, 3 total reactions, 3 of 4 comments are bots. #6686: 551 reactions, closed `not_planned` 2026-02-09. **Judgement: Anthropic has declined this twice. Betting on native network-attachable Claude Code sessions is a weak hypothesis \u2014 which strengthens the case for an external middle layer.** |\n| Codex `exec --json` + rollout JSONL | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`, `resume --last` | Yes | VIABLE | **Cross-vendor convergence** with Claude Code on the identical shape is itself evidence this is a real architectural pattern, not one company's idiosyncrasy |\n| OpenCode `opencode serve` | Headless HTTP + OpenAPI 3.1 + `GET /global/event` SSE + `/tui/*` to drive the TUI | Yes | VIABLE | `[DOC opencode.ai/docs/server]` \"When you run opencode it starts a TUI and a server. Where the TUI is the client that talks to the server\u2026 This architecture lets opencode support multiple clients and allows you to interact with opencode programmatically.\" `--port`/`--hostname`/`--cors`/`--mdns`, `OPENCODE_SERVER_PASSWORD` |\n| OSC 133 semantic prompts | Prompt-start/end, output-start, command-finished+exit-code | **Yes** \u2014 including **cmd.exe**, via Clink + oh-my-posh `[DOC ohmyposh.dev/docs/configuration/general]` `shell_integration` \"Works in bash, **cmd (Clink v1.14.25+)**, fish, powershell and zsh\" | CONTEXT | **But Claude Code does not emit it** \u2014 \u22655 duplicate requests since May 2025, all bot-closed. And a nested layer cannot forward it: `[DOC tmux#5237, maintainer nicm]` \"If tmux writes a prompt at 10,10 and you drag the pane so it is now at 15,15, how does tmux tell the terminal it has moved?\u2026 The sequences are not powerful enough\u2026 they are meant for shells, not full screen programs.\" **A nested layer must parse-and-re-expose, not forward.** tmux's own OSC 133 *events* (`pane-command-started` etc.) are in **unreleased master** only \u2014 latest release is 3.7b |\n| Watfaq/PowerSession-rs | asciicast recorder on ConPTY | **Yes** | VIABLE, narrow | **MIT** (verified three ways; the README's \"GitBook Community License\" footer is a theme artifact). 295\u2605, 2026-07-24. **Windows-ONLY** (no `impl_unix`, CI is `windows-latest` only). `server` is a config setter, **not a daemon** \u2014 it hosts nothing. **Defect found at source** `[SRC Watfaq/PowerSession-rs, the ConPTY backend under src/ \u2014 the exact file and line were not recorded when the finding was made and were not recovered this pass; `[UNV]` on the citation, `[SRC]` on the finding]`: it calls `CreatePseudoConsole` once, sized from `GetConsoleScreenBufferInfo` at startup, and **never calls `ResizePseudoConsole` anywhere** (a repo-wide symbol search returns zero hits) \u2014 so a mid-recording resize of the outer ADE pane cannot propagate. Open issue #272: may not honour `ASCIINEMA_SERVER_URL`, and `upload` is not gated behind `auth` \u2014 a recorder captures **everything** that transits the PTY, including secrets |\n| faressoft/terminalizer | recorder | **Yes** (\"Cross platform\", default Windows command `powershell.exe`, `@homebridge/node-pty-prebuilt-multiarch`) | CONTEXT | 16,137\u2605 but last push 2024-08-29 and it pulls in electron ^25 |\n| asciinema / termtosvg | recorders | **No** | REFUTED | `[ART gh api repos/asciinema/asciinema/releases/tags/v3.2.1]` asciinema v3.2.1 release assets are darwin/linux only \u2014 **even in the from-scratch Rust rewrite**. termtosvg is self-declared Unix-only and read-only since June 2020 |\n| PowerShell `Start-Transcript` | flat text log | Yes | CONTEXT | No timing, no ANSI, no replay. Hooks the PowerShell host, not the PTY \u2014 coverage of a native console subprocess's direct writes is host-dependent |\n| reubenlavin08/cc-discord-remote | The **only** working end-to-end late-attach control loop found anywhere | Yes | **Sole existence proof, unvalidated** | MIT, **0 stars, 0 forks, single author, created 2026-05-16, last push 2026-06-08** (dormant). `bot.py` is one 159 KB file. **Cite for its engineering notes, not as a dependency** \u2014 see 6.4 |\n| nvaccess/NVDA `winConsoleHandler.py` | Production late-attach **reader** | Yes | CONTEXT | `AttachConsole(processID)` against a console NVDA never spawned, + `GetConsoleProcessList(2)` liveness, + `ReadConsoleOutputCharacter`, in one of the most widely deployed OSS Windows apps. Actively maintained through Oct 2025. **Reads only \u2014 never writes.** |\n| GitHub Actions runner / Buildkite agent | Long-poll HTTPS job queues, installable as a Windows Service | **Yes** | VIABLE, different shape | Trade-off: no live-attachable PTY \u2014 output is a streamed log, not a standing shell. `actions/runner` is MIT, 6,151\u2605, pushed 2026-07-31 \u2014 which contradicts a claim that it is near end-of-life. `[UNV]` That claim was encountered on an unrecorded third-party blog and the source was never captured; the refutation stands on the `pushed_at` date alone, and the blog should be treated as hearsay rather than cited |\n| LangChain agent-inbox | Queue of pending decisions instead of a live terminal | Yes | REFUTED as an implementation | Requires `from langgraph.types import interrupt` and a running LangGraph deployment. Response discriminator is `'response'`, **not** `'respond'` (config flags use `allow_respond`) `[UNV \u2014 API detail carried from an earlier pass; no file or doc URL was recorded and it was not re-verified]`. **The pattern is transferable; the implementation is not** |\n| RDP / `tscon` / Terminal Services | **The in-box Windows tmux-detach analogue** | Yes | CONTEXT \u2014 and it breaks a synthesis | A disconnected RDP session keeps every process running with a live console; you reattach by reconnecting; `tscon` moves a session between connections. **Gives both persistence and reattach natively, with no third-party code.** Limits: whole-session granularity (no per-worktree agent tabs), `[DOC learn.microsoft.com/windows-server/\u2026/tscon]` \"You can't connect to the console session\", requires Full Control or Connect access, one interactive session on client SKUs |\n| Emacs as supervisor (vterm / eat / term-mode) | \u2014 | **No** | REFUTED at the root | `[DOC akermu/emacs-libvterm README]` \"Vterm is not for you if you are using Windows\". Root cause: `[DOC debbugs.gnu.org bug#71472]` Emacs core has never had a real Windows pty; the ConPTY patch (author Ke Wu, not Eli Zaretskii) was **de-tagged from `patch` on 2025-02-12** and sits dormant at wishlist severity |\n| **dakra/ghostel.el** | Emacs terminal on libghostty-vt with a Zig native module | **Yes, confirmed** | **VIABLE \u2014 the one thing that solved the Windows-PTY-under-an-editor problem** | `[DOC CHANGELOG.md [0.43.0] 2026-07-11]` \"Native Windows support for x86_64 and ARM64 Emacs through a ConPTY backend\"; \"Windows releases now bundle the native module and Microsoft's redistributable ConPTY runtime\u2026 with the system ConPTY as a fallback\". 816\u2605, v0.48.0 (2026-07-29), `src/ConPtyProcess.zig`, every release ships `ghostel-conpty-*.dll` + `ghostel-openconsole-*.exe`. **Architecturally it is your shape: the native module owns ConPTY; the host application is a swappable frontend** |\n| mosh / WinMosh / Eternal Terminal | state-sync shells | **No server on Windows** | REFUTED | `[DOC mosh.org]` \"There is no 'native' mosh executable for Windows available at this time.\" WinMosh is client-only (\"Requires a mosh-server running\u2026 on the remote host\"). Eternal Terminal is WSL-only by its own README |\n| Windows Service / Scheduled Task hosting | persistence without reattach | Yes | VIABLE, with an unraised showstopper | A service runs in **session 0** as SYSTEM while ORCA and the human run in session 1+. **(a)** `[INF]` Console objects are per-session and `AttachConsole` attaches by PID, so the mechanism should not be able to cross the session-0 boundary \u2014 **this is an inference from two documented facts, not an observed failure**, and it is recorded as such in section 9 alongside the structurally identical foreign-SID inference. **(b)** `[DOC]` Agent CLIs authenticate from the **user profile** (`~/.codex/auth.json`, `~/.claude/`), which a SYSTEM service does not have. A Scheduled Task avoids (b) only if it runs as the user rather than as SYSTEM |\n\n---\n\n## 4. Middle-Layer Fitness\n\nJudged on the things that actually matter for a wrapper an ADE spawns: does it survive the ADE dying; can it be driven with no human attached; **do its verbs work with no console at all**; does it behave inside a nested PTY; is its interface a stable contract; can you install it.\n\n**About the \"Verbs with no console\" column.** This is the headless-first constraint stated as a testable property, and it is the one column where the honest answer is almost always *untested*. `zellij action`, `tmux send-keys`, `wezterm cli` and the rest are normally invoked *from inside a terminal*; whether they still function when a Windows Service, a Scheduled Task, or a Claude Code hook invokes them with no console attached and no TTY on stdin/stdout is a different question, and nobody in this research ran it. It is cheap to test (Q23) and **disqualifying if it fails**, because a middle layer you can only drive from a terminal is not headless-first \u2014 it is just a multiplexer.\n\n| Candidate | Survives ADE death | Headless verbs | Verbs with no console | Nesting behaviour | Contract stability | Installable standalone | Overall |\n|---|---|---|---|---|---|---|---|\n| **psmux** | Yes | Full (tmux verb set + `-CC`) | **Untested** | Explicit guard + `PSMUX_ALLOW_NESTING=1` (note the error message names the wrong variable); pipe mode negotiates size in band | tmux-compatible = the most-cloned contract in existence | Yes (Windows only) | **Strongest interface, weakest platform breadth.** Also carries the unauthenticated full-duplex cross-session port (3.1 row note) and a `cargo audit` CI gate no peer matches |\n| **Zellij** | Yes | CLI + WASM plugins | **Untested** | Configurable (`ask`/`fullscreen`/`descend`/`never`) \u2014 the only one with a policy knob | Mature, versioned, protobuf plugin API | Yes (msvc zip + MSI, and Linux/macOS) | **Most mature, and the best-verified Windows claim in this map.** The `TERM`/`WT_SESSION` gate is a **config check, not a hazard** \u2014 it selects the native-console path, which is implemented |\n| **rmux** | Yes | 90+ verbs + typed Rust/Python/TS SDKs | **Untested** | Untested | Single-author (~936 of ~1000 commits `[UNV]`); dual licence pair unstated | Yes | **Best-designed transport \u2014 the only identity-scoped namespace, plus pipe-instance and impersonation checks; thinnest bus factor** |\n| **`wezterm-mux-server`** | Yes (`--daemonize`, handled explicitly on Windows) | 15 CLI verbs, headless by design | **Untested** | Untested nested | **Versioned varint codec explicitly built for skewed client/server pairs** | Ships in the Windows zip and installer \u2014 but **no stable release since 2024-02-03**; the rolling `nightly` tag is the only current Windows path | **Best engineering; you cannot pin a version and be on Windows at the same time** |\n| **OpenCode `/pty`** | While `opencode serve` runs (**L1+, not L2**) | REST + WS + replay cursor + ticket auth | N/A (network client \u2014 the transport is the console-independence) | N/A (network client) | **Undocumented** \u2014 could change without notice | Yes (`opencode-windows-x64.zip`) | **Best-shaped API for resumable attach; zero contract guarantee, and its wire is UTF-8 text with a fatal decoder and 64 KiB UTF-16 chunking, so it is not byte-exact** |\n| **`ao pty-host`** | **Yes, explicitly** (`DETACHED_PROCESS`, survives daemon exit) | GetOutput(lines), Status, Kill, SendMessage | N/A (network client) | `applyLargestLocked` resize arbitration | `Hidden: true` cobra subcommand \u2014 an internal contract | No \u2014 vendor the package or run the AO binary (Apache-2.0 makes that clean) | **Closest architectural match; not a product** |\n| **herdr** | Yes | CLI + HTTP API + SDKs | **Untested** | \"Nested launch override \\| beta\" (listed **Supported**) | 23k stars, active | **Preview channel only on Windows** | **Better placed than earlier editions said:** \"Local persistent sessions\" and \"Windows Terminal / PowerShell app attach\" are both listed **Supported (beta)**; \"Direct terminal attach \\| unsupported\" sits among Unix-specific mechanisms. Confirm the reading (Q2), but it is no longer the map's biggest open risk |\n| **oly** | Yes | HTTP/WS + named-pipe IPC | **Untested** | **No nesting evidence** \u2014 the copilot/opencode \"live agent tests\" claim was wrong (see 3.1 row note). What exists is a windows-latest PTY matrix over shells | Small, single-author, `main` stalled 1 month; MIT | Yes | **Demoted.** Retains a real windows-latest PTY test matrix and MIT licensing. Disqualifying-adjacent: unauthenticated pipe with no ACL. And its `output-copilot.expected.windows` fixture is direct evidence of **platform-divergent rendering**, against constraint 3 |\n| **quil** | Yes | **MCP (18 tools)** \u2014 the richest agent-facing surface | **Untested** | Mouse-mode forwarding for nested alt-screen apps | Single-author, ~1 release/day, zero external issues ever; MIT | Yes | **Best agent interface; zero Windows CI, AF_UNIX with a no-op chmod, and an unconditional `os.Remove` on the socket path that bricks a live daemon** |\n| **qscreen** | Yes | `qscn` verbs + `AttachMode::Bytes` | **Untested** | Untested | 6 stars, zero CI, zero external engagement ever; MIT | Yes | **UNVALIDATED on evidence, not refuted on design.** Anti-squatting + per-user namespacing are genuinely right; nobody has ever run it but the author |\n| **upterm** | **No \u2014 L1 only** | SSH + public-key auth | N/A (SSH is the transport) | Ignores `os.Interrupt` deliberately for ConPTY nesting | Apache-2.0, active | Yes (scoop) | **REFUTED. Best auth model in the census; no detach/reattach \u2014 that is the disqualifier, and it is a hard one** |\n| **No-PTY architecture** (`claude -p` stream-json + HTTP hooks + OTel) | N/A \u2014 you own the process | **Near-complete**: bidirectional NDJSON, permission decisions, `updatedInput`, 34 typed identifiers | **Yes by construction** \u2014 ordinary pipes, no console needed anywhere. The only row that passes this column outright | No PTY, no nesting problem at all | Vendor-controlled, but publicly documented | N/A | **Best headless story by a wide margin.** Two caveats now named: `http` hooks are unavailable on SessionStart/Setup, and `requiresUserInteraction` tools can refuse the headless-answer trick. Cannot show a human a live screen |\n\n### 4.1 The four things a middle layer must get right on Windows\n\n**1. Answer ConPTY's startup handshake, or eat multi-second stalls.** *DA1 is the `CSI c` device-attributes query \u2014 \"what kind of terminal are you?\" \u2014 which a terminal is expected to answer.*\n\n**Citation status first, because this document's own method rule 1 requires it and earlier editions did not disclose it.** All the quotes below are verbatim and were read in full, but they live in comments on **`microsoft/terminal#7019`, which is closed as `not_planned` since 2023-09-29**, was created 2020-07-22, and is titled *\"conpty exhibits pathological performance on scrolling region redraw (repaints entire screen)\"*. Post-closure comments from maintainers are legitimate evidence; the state is disclosed so nobody mistakes this for an open, tracked commitment.\n\n`[DOC lhecker (MSFT), microsoft/terminal#7019 comment 2026-06-10]` \"it sends you a DA1 request, which you're expected to respond to, because you're using a PTY, not just setting up any random pipes. **It waits 3s for a response.** It's not multiple queries with 1s waits each.\" `[DOC DHowett (MEMBER), same issue]` \"If you have the ability to answer them, _answer them._ If you do not\u2026 that would mean you are not a terminal emulator.\"\n\nA measured data point in the same thread took startup from **2121 ms \u2192 142 ms** by answering instantly. **Attribution correction:** that measurement is a **third-party application developer's** (commenter `Eyalm321`), not a Microsoft benchmark \u2014 earlier editions placed it among Microsoft attributions where readers would reasonably assume otherwise.\n\n**oly and psmux both answer the handshake** (`extract_query_responses_no_client`; `tests-rs/test_cpr_responder.rs`) `[UNV \u2014 the oly symbol was recorded without a file path in an earlier pass and was not relocated this pass]`. **Judgement: this is the single most load-bearing design requirement in the whole map** \u2014 get it wrong and every spawn costs seconds, or hangs.\n\n**2. Do not set `PSUEDOCONSOLE_INHERIT_CURSOR` unless you answer `ESC[6n` fast.** *`ESC[6n` is CPR, the cursor-position report request; the reply is `CSI row ; col R`.* `[SRC psmux crates/portable-pty-psmux/src/win/psuedocon.rs:125-133]`, with a dedicated regression test: \"With it, conhost emits an `ESC[6n` cursor-position request at startup and will not service a child's console connection until the host answers it. So if that reply is sent later than the child's connect attempt, the child blocks in `ConsoleCreateConnectionObject` during process initialization (a single thread, before any user code runs) until the reply arrives: a temporary stall if it is merely late, **indefinite if it never comes**.\" WezTerm sets it; node-pty sets it when `inheritCursor` is passed.\n\n**3. Load a bundled `conpty.dll` by absolute path, or not at all.** `[SRC psmux psuedocon.rs:45-51]` refuses sideloading entirely: \"terminal emulators like WezTerm bundle their own conpty.dll + OpenConsole.exe, and the DLL search order can pick those up when psmux runs inside such a terminal. Using a foreign conpty.dll causes blank panes and broken I/O.\" **Resolution:** the hazard is real but specific to *how* you load. WezTerm uses a bare relative name (`Path::new(\"conpty.dll\")`) \u2014 hijackable. quil (`filepath.Join(filepath.Dir(exe), \"conpty.dll\")`) and node-pty (`PathCombineW(currentDir, L\"conpty\\\\conpty.dll\")`) use absolute paths and are safe. Note node-pty resolves a *different* export name on the bundled path (`ConptyCreatePseudoConsole` vs kernel32's `CreatePseudoConsole`), and `ClearPseudoConsole` is bundled-only.\n\n**4. Resize does not propagate by itself.** There is no Windows analogue of SIGWINCH. `ResizePseudoConsole(HPCON, COORD)` is an explicit call by whoever owns the handle, and a child console app only learns of it by reading `WINDOW_BUFFER_SIZE_EVENT` off its console input buffer \u2014 which a byte-stream-only consumer never sees. Two shipping solutions exist: **psmux's pipe mode** asks the outer terminal in band via XTWINOPS `CSI 18 t` (*\"how big are you?\"*) and waits 500 ms for `CSI 8 ; rows ; cols t`, falling back to 120x30 `[SRC src/ssh_input.rs:2016-2039, src/main.rs:4112-4130]`; **Zellij** polls `crossterm::terminal::size()` at 50 ms on the VT-reader path `[SRC zellij-client/src/os_input_output_windows.rs:95 doc-comment, :176 sleep]` \u2014 and separately runs a 100 ms poller at `:38`/`:52`. **Open risk:** whether Electron/xterm.js answers `CSI 18 t` by default is unknown \u2014 xterm.js gates `windowOptions` behind opt-in flags. If it does not, psmux's pipe mode silently degrades to a hardcoded 120x30. See also the node-pty ConPTY ABI drift noted in 3.7, which bears on this item: a stale one-argument `ConptyClearPseudoConsole` prototype against a two-argument export is exactly the kind of mismatch that surfaces as a resize or clear that silently does nothing.\n\n### 4.2 The nesting hazard that matters most\n\n`[SRC psmux src/main.rs:4188-4193]`, maintainer's own words \u2014 **quoted with its antecedent restored, which earlier editions dropped**: \"**When running over SSH with a ConPTY console**, Windows ConPTY silently consumes DCS escape sequences (including the `\\x1bP1000p` that iTerm2 uses to detect tmux control mode) and also interleaves its own cursor positioning sequences into the output, **corrupting the line-based protocol**.\" The remedy the source gives is likewise SSH-specific: \"the SSH client must disable PTY allocation so that stdin/stdout are raw pipes: `ssh -T user@host tmux -CC`.\"\n\n**Judgement, and it is an inference, not the source's claim.** The mechanism \u2014 ConPTY eating DCS and injecting cursor sequences \u2014 plausibly generalises to any ConPTY between a `-CC` server and its client, including a node-pty one. But **the source does not say that**, and earlier editions presented the generalised version as a `[SRC]` quotation. So: `[INF]` if ORCA spawns the middle layer on a node-pty ConPTY, `-CC` control mode is likely degraded, and it wants raw pipes. This is empirically testable in an afternoon (Q1) and it determines whether the strongest interface in the map is usable in your actual topology.\n\n**And the remedy may not be reachable from ORCA, which is the question Q1 does not ask.** ORCA spawns through node-pty, i.e. **always a ConPTY**. The `-CC` fix is \"give the child raw pipes\". So before asking whether corruption happens, ask whether the cure is available at all: **can `agentCmdOverrides` produce a raw-pipe child?** If it cannot, then the corruption question is moot and the strongest interface in this map is unavailable in this topology regardless of Q1's outcome. That is filed as **Q1a**, and it should be answered first because it is cheaper and it can make Q1 unnecessary.\n\nSecond-order: `[DOC psmux docs/control-mode.md:391]` \"ConPTY may normalize line endings and process certain cursor movement sequences internally. `%output` data may look slightly different from what a Unix tmux session would produce\" \u2014 a documented Windows/Linux behavioural divergence against the cross-platform-uniformity goal.\n\nThird: `[SRC psmux src/server/mod.rs:1225]` does `String::from_utf8_lossy(&amp;bytes)` on each ring drain before escaping. A multi-byte character split across two drains becomes U+FFFD **permanently**. Agent CLIs emit heavy Unicode \u2014 the `\u2733` in Claude Code's own window title already broke quil's emulator. Note this is a bytes-to-string bug, not a double-parse bug \u2014 **and the same class of bug is present in OpenCode's `/pty` wire** (2.2), so it is a property of string-typed terminal transports generally, not a psmux defect.\n\n### 4.3 Prefix keys in a nested pane \u2014 the least-researched nesting concern\n\nFive nesting concerns matter (see 0.1). Four are covered: double parse in 2.2, resize in 4.1 item 4, control-mode corruption in 4.2, and alt-screen/mouse glancingly via quil's `mousemode.go`. The fifth \u2014 **whether a multiplexer's prefix key eats the ADE's keybindings, and whether it can be rebound or disabled headlessly** \u2014 is the thinnest-evidenced part of this map, and it is a daily-annoyance-grade problem rather than a correctness one.\n\n| Candidate | Has a prefix? | Rebindable? | Disable-able for headless use? |\n|---|---|---|---|\n| psmux | Yes, tmux-style `C-b` | `[INF]` yes \u2014 tmux-compatible config implies `set prefix`, **not verified** | `[UNV]` |\n| Zellij | Yes, `Ctrl` modes rather than a single prefix | `[INF]` yes \u2014 keybindings are a first-class KDL config section | `[UNV]` |\n| rmux | Yes, tmux-compatible | `[INF]` yes, same reasoning as psmux | `[UNV]` |\n| herdr | Yes | `[UNV]` \u2014 but note its Windows-beta page lists \"**Prefix input-source switching \\| unsupported**\" `[DOC windows-beta.mdx]`, which is the only primary-source statement about prefixes on Windows found anywhere in this research | `[UNV]` |\n| qscreen, oly, quil, `ao pty-host`, OpenCode `/pty` | `[UNV]` \u2014 single-pane or network-attached designs may have no prefix at all | `[UNV]` | `[UNV]` |\n| `docker attach` (as a design reference) | Yes, `CTRL-p CTRL-q` | **Yes, documented** \u2014 `--detach-keys`, per-container or global `[DOC docs.docker.com/reference/cli/docker/container/attach/]` | Yes, by remapping to an unused chord |\n\n**Judgement:** the only *documented* remapping mechanism in the entire census belongs to docker, not to any multiplexer candidate \u2014 every multiplexer's rebindability above is an inference from \"it has a config file\". For a headless-first middle layer the right target is stronger than rebinding anyway: **no prefix at all on the headless path**, with the prefix existing only when a human attaches. None of the candidates is known to offer that. This is recorded as a genuine gap rather than resolved, and it is the substance of Q24.\n\n---\n\n## 5. The Interface Options Map\n\nThe brief was to map interface shapes rather than pick one. **Seven exist.** They are not mutually exclusive \u2014 the best-shaped candidates offer two or three.\n\n| Shape | What it buys | What it costs | Who offers it | Windows |\n|---|---|---|---|---|\n| **CLI verbs** | Trivially scriptable from anything; no client library; survives being called by a shell, a hook, an agent, or a human; composes with `--json` | Process-spawn latency per call; no push/streaming; state must be re-fetched | tmux, psmux, rmux (90+), `wezterm cli` (15), `ao` (thin client over HTTP), Pane `runpane` (all `--json`), qscreen `qscn`, herdr, Zellij | All yes |\n| **tmux control mode (`-CC`)** | A real, decade-hardened, **multi-implementer** contract for \"parse once, let the last hop render\"; `%output` carries raw pane bytes so no double parse | Line-based text protocol is fragile to interleaving; **ConPTY corrupts it when nested** | tmux, psmux, WezTerm, ivyterm, par-term | psmux and WezTerm yes |\n| **Binary wire protocol** | Version-skew tolerance (WezTerm's varint framing is explicitly designed for it); low overhead; exact byte fidelity | You must write and maintain a client; no ecosystem | WezTerm codec, `ao pty-host` proto.go, qscreen ScreenFrame, asciinema ALiS | All yes |\n| **HTTP + WebSocket** | Every language has a client; auth/TLS/proxying are solved; SSE or WS gives push; OpenAPI gives a generated client | Port management, bind-address and auth become your problem; a stray `0.0.0.0` is a real leak | **OpenCode `/pty`** (REST + WS + replay cursor + ticket auth), oly, kandev `ws://localhost:38429/ws`, terminado REST+WS, Warp `local_control` `/v1/control`, x3270 `-httpd`, AO `/api/v1/*` | All yes |\n| **MCP** | The agent can drive it *itself*, with no glue; discoverable tools | **A stdio MCP server cannot own persistent sessions \u2014 the client spawns it.** Only a server the middle layer runs independently works. No terminal-tool naming convention exists anywhere | quil (18 tools), kandev (**External MCP** mode explicitly \"for a client outside a task\"), paseo daemon, vibe-kanban (dead), ripple | Yes |\n| **JSON-RPC over stdio/pipe** | The agent-CLI ecosystem is converging here; transport-agnostic by spec | Subprocess-bound lifetime unless you add a network transport | ACP (spec sanctions custom transports \u2014 **ACP over a named pipe is conformant**), Codex `app-server` (stdio/uds/ws/remote_control) | ACP: no Windows CI. Codex: transport yes, daemon lifecycle no |\n| **Structured event stream (no control plane)** | Zero PTY; survives ADE swaps for free; vendor-maintained | Read-only for observation; write path is a separate mechanism | Claude Code OTel (34 events) + JSONL tree; Codex rollout JSONL; OpenCode `/global/event` SSE | Yes |\n\n### 5.1 What an ADE-agnostic middle layer would have to expose\n\n**Judgement, derived from the union of every candidate's verb set** (`runtimeselect.Runtime` in AO, `wezterm cli`, tmux/psmux/rmux verbs, OpenCode `/pty`, quil's MCP tools):\n\n| Verb | Why it is non-negotiable |\n|---|---|\n| `spawn(cmd, cwd, env, cols, rows) \u2192 session_id` | The ADE hook point. ORCA's `agentCmdOverrides` is exactly this |\n| `list() \u2192 [{id, alive, pid, exit_code, cwd, title}]` | Discovery without state in the ADE |\n| `attach(id, {cursor}) \u2192 {replay, cursor, stream}` | The one primitive nothing else substitutes for. **Must carry a resumable cursor** \u2014 OpenCode's `protocol.ts` is the model **for the cursor semantics only**; copy its absolute-output-cursor control frame, not its wire type. Its payloads are JS strings, so it splits surrogate pairs at 64 KiB and drops invalid UTF-8 input (2.2). Carry `bytes`, not `string` |\n| `detach(id)` | Distinct from kill; the thing dtach/abduco exist for |\n| `send(id, bytes)` and `send_keys(id, keys)` | Raw bytes for fidelity, named keys for scripts |\n| `get_output(id, lines)` / `snapshot(id) \u2192 {text, cursor_x, cursor_y}` | Headless read without attaching. quil's `MsgScreenshotPaneResp` is the shape |\n| `resize(id, cols, rows)` | Must be explicit on Windows. Multi-client needs an arbitration rule \u2014 AO's `applyLargestLocked` is the only one found |\n| `status(id) \u2192 {alive, pid, exit_code, blocked_on_user?}` | Headless liveness. The `blocked_on_user` bit is what OTel gives you free |\n| `kill(id, signal)` | With a process-tree story: Job Objects on Windows (upterm, rmux, kandev's `cmd/winjob/`), not bare `TerminateProcess` |\n| `wait(id, {idle\\|text\\|exit}, timeout)` | The primitive that makes it scriptable. boo, rmux and ht all shipped this independently |\n\n**Two properties beyond the verb set, both learned the hard way:**\n\n- **Namespacing must be identity-scoped, not merely selectable.** rmux's `\\\\.\\pipe\\{prefix}-{sid}-il-{integrity}-{label}` is the only correct implementation found, because the name is *computed from the token* rather than picked from a string. oly is the counterexample, but for a sharper reason than earlier editions gave: it *does* have a name knob (`OLY_SOCKET_NAME`), and a knob is not access control \u2014 its default is machine-global and its pipe carries no ACL and no `FILE_FLAG_FIRST_PIPE_INSTANCE`. **Namespacing prevents accidents; ACLs prevent attacks. You need both, and only rmux has both.**\n- **A local transport is not an auth boundary.** psmux's auth is per-*user*, not per-*process*: ORCA, every agent CLI, every MCP server and every npm postinstall script run as the same Windows user and can all read the key file. That is the same trust model as tmux's 0700 socket \u2014 a category property, not a psmux scandal. But note the asymmetry: a Unix socket's mode is kernel-enforced at connect time, whereas **a TCP loopback port is reachable by any local process, including other users and low-integrity ones**, with only a 64-bit non-CSPRNG secret in a default-ACL file in the way. A properly-ACL'd named pipe with `FILE_FLAG_FIRST_PIPE_INSTANCE` is strictly stronger.\n\n### 5.2 The reframe worth taking seriously\n\n`[DOC opencode.ai/docs/server]` \"When you run opencode it starts a TUI and a server. Where the TUI is the client that talks to the server.\" `[DOC evennia.com/docs]` Portal owns every connection; Server owns all logic and can be reloaded with clients still attached. `[DOC spec.nrepl.org]` the frontend is disposable; the state lives behind a socket.\n\nThree unrelated ecosystems \u2014 a coding agent, a MUD engine, and a Lisp REPL \u2014 independently arrived at the same shape. **Judgement:** that convergence is the strongest architectural signal in this entire map, stronger than any individual tool finding. The name for what you are building is *Portal*.\n\n---\n\n## 6. Negative Results and Dead Ends\n\n### 6.1 ConPTY facts that will mislead you if you re-derive them\n\n- **`PSEUDOCONSOLE_PASSTHROUGH_MODE = 0x8` is a stale dead define, and passing it is actively harmful.** It is defined-and-unused in microsoft/node-pty (`src/win/conpty.h:27`) and marked `#[allow(dead_code)]` in wezterm's portable-pty. `[SRC microsoft/terminal src/inc/conpty-static.h]` the current public flag list is `INHERIT_CURSOR (0x1)`, `GLYPH_WIDTH__MASK 0x18` / `GLYPH_WIDTH_GRAPHEMES 0x08` / `GLYPH_WIDTH_WCSWIDTH 0x10` / `GLYPH_WIDTH_CONSOLE 0x18`, `AMBIGUOUS_IS_WIDE (0x20)`. **Bit 0x08 now means grapheme width measurement**, so anything still ORing the old passthrough value silently opts into grapheme-based text measurement. A global code search finds the stale symbol in 148 files, including psmux, Sora-bluesky/winsmux, aws/amazon-q-developer-cli, and vibetunnel \u2014 it is propagating through the exact candidate pool.\n  - **Reasoning correction, because the argument earlier editions gave for this was invalid.** The claim was justified by \"it is absent from the public header\". That inference does not work: `0x2` (`RESIZE_QUIRK`) and `0x4` (`WIN32_INPUT_MODE`) are **equally absent** from that header, and this document treated *those* as live features elsewhere. Absence from a public header proves only \"undocumented\". The decisive evidence is the **consumer**, not the header: `[SRC src/winconpty/winconpty.cpp:169-186]` reads exactly three things out of `dwFlags` \u2014 `INHERIT_CURSOR`, `AMBIGUOUS_IS_WIDE`, and the `GLYPH_WIDTH__MASK` switch \u2014 and `RESIZE_QUIRK`, `WIN32_INPUT_MODE` and `PASSTHROUGH_MODE` are **zero hits across the whole repository**. That is what makes 0x2 and 0x4 no-ops and 0x8 a mis-selection. Full argument and its consequences for candidate ranking: end of 3.7.\n- **The good news is the inverse of what you would expect.** `[SRC microsoft/terminal PR #17510, merged and closed 2024-08-01T20:38:11Z]` \"A minor ConPTY refactoring: Goodbye VtEngine Edition\" \u2014 \"any VT output that an application generates will now be given to the terminal **unmodified**.\" Microsoft shipped the passthrough *outcome as the default*, and issue #1173 closed as `completed` at `2024-08-01T20:38:13Z` \u2014 **two seconds later**, not \"seven minutes\" as earlier editions said. (The seven-minute figure is real but belongs to a different issue, #5468: created 17:14:15, closed 17:21:46 \u2014 see 6.4. It was carried across by mistake.) **Since Aug 2024, ConPTY passes application VT output through unmodified by default.** The \"double VT parsing is unsolved at OS level\" conclusion is wrong.\n- **But passthrough makes one bug worse.** `[DOC lhecker (MSFT), microsoft/terminal#7019 \u2014 issue closed `not_planned` 2023-09-29; comment thread remains the primary source]` passthrough-mode ConPTY \"already injects VT sequences into stdout irrespective of the VT parser state\" \u2014 which is exactly the mechanism behind `[DOC microsoft/terminal#19621, filed by DHowett (MEMBER), open]` \"This will impact tmux control mode, as ConPTY will send `\\x1B` to terminal and promptly terminate the in-flight exchange. All in-band signaling from ConPTY must be suppressed when passing through a DCS.\"\n- **ConPTY nesting is a designed, negotiated scenario, not an accident.** `[DOC doc/specs/#4999 - Improved keyboard handling in Conpty, lines 299-322]` works through `WT \u2192 conpty[1] \u2192 wsl \u2192 conpty[2] \u2192 cmd.exe`: \"Conpty[2] will ask for `win32-input-mode` from conpty[1] when conpty[2] first boots up. As conpty[1] is just a conhost that knows how to handle `win32-input-mode`, it will switch its own VT input handling into `win32-input-mode`.\" It also confirms the double-parse cost is by design: each layer re-encodes INPUT_RECORDs to VT and back.\n- **Still open and unfixable from outside:** `[DOC microsoft/terminal#15976, promoted to a megathread]` ConPTY reflow/resize desync \u2014 \"we're promoting this to a megathread cause this is a Hard problem\". `[DOC doc/specs/#13000 - In-process ConPTY, lhecker]` names it \"**unsolvable**\" in the current architecture: the drift is between **ConPTY's buffer and the hosting terminal's buffer \u2014 two separate processes** \u2014 and a named MUTEX is explicitly rejected on ABBA-deadlock grounds. **A nested middle layer that keeps its own screen model becomes a third independent representation.**\n- `[DOC microsoft/terminal#17384, open since 2024-06-06]` pasting &gt;5 KiB into a slow-reading app deadlocks the whole terminal; Ctrl-C stops working.\n- `[DOC microsoft/terminal#17643]` residual VT-passthrough issues **still open**: `COMMON_LVB_GRID_HORIZONTAL` \u2192 SGR 53 translation dropped, `VtIo::Writer::WriteInfos` not verifying per-character width, `SetConsoleActiveScreenBuffer` destroying grapheme clusters. (Cooked-read reflow, `ScrollConsoleScreenBuffer`, the DA3 truncation and the PSReadLine SGR issue are all **fixed** \u2014 do not carry them forward as hazards.)\n\n### 6.2 node-pty defects live in ORCA's own stack today\n\n| Defect | Status | Consequence |\n|---|---|---|\n| `AttachConsole failed` crash on kill of an already-exited shell | `[SRC PR #886]` **open since 2026-02-06, unmerged**; `[SRC PR #901]` (Node 22 resize-after-exit) **open, unmerged** | Both surfaced as user-visible crash dialogs in shipping Electron products: openai/codex#25415 and microsoft/vscode#201029 |\n| The try/catch guard | `[SRC commit 2b25c761, v1.2.0-beta.11, 2026-02-03]` already merged upstream | Codex and VS Code shipped **stale bundled copies**. The fix is: pin node-pty \u2265 1.2.0-beta.11 |\n| Root cause still open | `[DOC vscode#201029 final comment]` \"kill() unconditionally tries to enumerate console processes for a shell whose exit has already been handled\" \u2014 `resize()` has an `_exitCode` guard, `kill()` does not | Triggered by short-lived diagnostic shells (`cmd.exe /c \"exit /b 0\"`) racing the kill path |\n| Data race in `ptyHandles` | `[SRC gh api repos/microsoft/node-pty/issues/921 \u2192 closed_at 2026-05-13T11:28:17Z]` \u2014 fixed **2026-05-13**, not 05-12 | A process-global vector mutated from per-PTY watcher threads; concurrent `std::remove_if` dereferenced a moved-from `unique_ptr`. **Real hazard at moderate session counts** |\n| Conout worker deadlock | `[SRC PR #943, merged 2026-07-31]` | A **regression of a bug already fixed once** (#763 \u2192 PR #885 \u2192 regression \u2192 #943). Triggered when the Node inspector pauses the conout worker thread |\n| ORCA's own instance | `[DOC stablyai/orca#9586, closed with zero comments and no maintainer reply]` | The \"root cause / fixed in v1.2.0-beta.11\" text is the **reporter's own unreviewed self-diagnosis**, not an established finding |\n\n### 6.3 Non-multiplexer routes: what each one actually gives up\n\n| Route | Persistence | Reattach | Verdict |\n|---|---|---|---|\n| Windows OpenSSH Server | **No** | No | `[DOC Win32-OpenSSH#2291, filed by MSFT member SteveL-MSFT, open]` no tmux equivalent exists. sshd puts children in a Job Object; disconnect tears it down \u2014 **not SIGHUP, so there is no signal to trap**. (Caveat: the Job-Object mechanism is asserted in one maintainer sentence; code searches for `CreateJobObject`/`KILL_ON_JOB_CLOSE` in openssh-portable return zero \u2014 treat as absence-of-evidence, not established.) |\n| Windows Service | Yes | **No** | Plus the session-0 showstopper: `[INF]` `AttachConsole` should not be able to cross it (console objects are per-session, and the call takes a PID) \u2014 **an inference from two documented facts, never observed to fail**; and `[DOC]` a SYSTEM account has no user-profile agent credentials, which is the half that is established |\n| Scheduled Task | Yes | **No** | Same, unless run as the user |\n| Hook + file/JSONL IPC | Structured replay | Not live | `[DOC Claude Code hooks]` resumed sessions **replay saved hook output** rather than re-running it, so timestamps and commit SHAs go stale |\n| Job-queue fleets (Actions runner, Buildkite, OpenHands) | Yes at scale | **No** \u2014 a streamed log, not a shell | Deliberate trade |\n| **RDP / `tscon`** | **Yes** | **Yes** | The counterexample that breaks \"every route gives up one\" \u2014 but whole-session granularity only |\n| **`codex app-server --listen ws://`** | **Yes** | Standing listener the client does not own | The other counterexample \u2014 but experimental, and it owns no PTY |\n\n`[DOC openai/codex#11750, filed by the harness author]` is the cleanest evidence that file-based IPC narrows PTY dependency without eliminating it: \"the only workaround is spawning `codex fork` inside a pseudo-TTY, polling the filesystem for new rollout files to discover the forked session ID, killing the TUI\u2026 This works but is fragile and slow (~6s overhead), requires PTY bindings (e.g. node-pty)\". (Note bearlyai/OpenADE has **no LICENSE file and `license: null`** \u2014 all-rights-reserved by default, usable as evidence, not as a component. And `thread/fork` already exists on app-server, so the PTY fallback exists because `codex exec` lacks a fork verb, not because the capability is intrinsically PTY-bound.)\n\n### 6.4 Late-attach (`AttachConsole`) \u2014 refuted as a foundation\n\n**Microsoft has explicitly told developers not to build on the half you need.** `[DOC learn.microsoft.com/windows/console/writeconsoleinput]` \u2014 all four sentences below are verbatim, but **they come from two different callouts, and earlier editions attributed all four to the \"Important\" banner.** The **Important** banner says the API is \"console platform functionality that is **no longer a part of our ecosystem roadmap**\". A separate **Tip** callout in Remarks says: \"This API is not recommended and does not have a virtual terminal equivalent\u2026 This operation is considered the **wrong-way verb** for this buffer. Applications remoting via cross-platform utilities and transports like SSH **may not work as expected** if using this API.\" The substance is unaffected; the attribution is corrected. `[DOC .../classic-vs-vt]` names the class: input injection and output scraping \"provide a vector to cross security and privilege-levels or domains\". `AttachConsole` itself survives in the still-condoned \"exceptions\" list for process bookkeeping.\n\n`[DOC DHowett-MSFT, microsoft/terminal#5468]` \u2014 created 17:14:15 and closed **`Resolution-By-Design` at 17:21:46, seven minutes later** (this is the issue the \"seven minutes\" figure belongs to; see 6.1) \u2014 \"A lower-integrity process is not allowed to read the console output of a higher-integrity console. **In general, there is no reason to ever use the ReadConsoleOutput API.**\" Note the rule is *directional*: same-user, same-integrity siblings (which ORCA's children are) are unaffected. It bites only for elevated agents, cross-user, or session 0.\n\n**Nobody has built a general late-attach library, and the split explains why.** `[DOC pywinauto#492]`, filed by a pywinauto maintainer in 2018 and still open, contains his own 2022 empirical attempt: `AttachConsole` succeeded, `ReadConsoleOutputCharacter` returned ten spaces, and it did not work at all against cmd.exe or PuTTY. The mature implementations all pick one half: **NVDA** late-attaches but only reads; **winpty** and **wexpect** read *and* write but own the console from birth so they never call `AttachConsole`. **Judgement: every half has a production implementation, and the halves never combine \u2014 the ecosystem has already converged on own-from-birth.**\n\nThe one working full loop, `reubenlavin08/cc-discord-remote` (MIT, 0 stars, dormant since 2026-06-08), documents its constraints as design workarounds rather than solutions, and those notes are the valuable part: a Win32 process can own **only one console**, so it spawns a throwaway `console_helper.py` per operation rather than corrupt its own stdio; `ReadConsoleOutputCharacter` sees **only the visible viewport, never scrollback**; concurrent human typing plus programmatic writes garble the JSONL, so \"one client per session at a time\"; and \"the JSONL format and session-registry layout are undocumented internals\".\n\n**One more structural note:** late-attach is not a *nested* topology at all. It is an out-of-band, sideways-reaching daemon \u2014 a different shape from every other candidate in this map, and worth naming rather than silently conflating.\n\n### 6.5 Standards that do not exist\n\n- No MCP terminal/PTY convention: zero terminal SEPs among 41 Final ones, dozens of independently-named `mcp-terminal-server` repos with no shared schema.\n- No second production consumer of tmux control mode was thought to exist. **That was wrong** \u2014 WezTerm, ivyterm and par-term all implement it, and WezTerm exercises it on Windows. But there is still no *specification* independent of tmux's implementation.\n- No Windows analogue of reptyr, after 10+ years.\n- No compatibility matrix, from Microsoft or anyone, for running a newer bundled `conpty.dll`/`OpenConsole.exe` against an older or locked-down Windows Server Core / LTSC host.\n- No primary Microsoft source pins the build threshold for `WIN32_INPUT_MODE` (spec authored 2020-05-07) or for passthrough (`build &gt;= 22621` is corroborated only by two independent Rust implementations, psmux and rmux, converging on the same magic number).\n- No open-source SCADA/industrial session manager with a persistent-session-plus-attach design was located \u2014 genuinely under-researched, not confirmed absent.\n\n---\n\n## 7. What Was Refuted\n\nEverything here was believed at some point during this research and killed by an adversarial pass. Do not re-import any of it.\n\n### 7.1 Facts that were wrong\n\n**Read the `Consequence` column first if you are scanning.** `verdict changed` means a candidate moved between VIABLE / REFUTED / CONTEXT because of this \u2014 those are the rows that matter for a decision. `framing corrected` means the conclusion held but the reasoning or scope did not. `citation only` means a fact was right and its source was wrong \u2014 method hygiene, not decision-relevant.\n\n| Claim | Reality | Source | Consequence |\n|---|---|---|---|\n| \"quil uses Named Pipes on Windows (per ADR-2)\" | **Never implemented.** `net.Listen(\"unix\", \u2026)` unconditional, no build-tag split in `internal/ipc/`, no `go-winio` in go.mod. The claim exists only in prose in quil's own `docs/architecture.md:20` | `[SRC internal/ipc/server.go:311, client.go:15]` | **verdict changed** |\n| \"psmux uses TCP + named pipes; issue #5 was closed by #13's named-pipe migration\" | **PR #13 was closed without being merged** (`merged: false`). psmux has **no named-pipe IPC at all**. It kept TCP and bolted on an `AUTH` key | `[SRC gh api repos/psmux/psmux/pulls/13 -&gt; merged:false, state:closed]` | **verdict changed** |\n| \"ConPTY passthrough was prototyped and never shipped; double-VT-parsing is unsolved at OS level\" | Issue #1173 is **closed as `completed`**; PR #17510 shipped the outcome as the **default** in Aug 2024 | `[SRC gh api repos/microsoft/terminal/issues/1173 -&gt; closed_at 2024-08-01T20:38:13Z, state_reason completed; pulls/17510 -&gt; merged, closed_at 20:38:11Z]` | **framing corrected** |\n| \"There is an active, heavily-upvoted open request for Claude Code network attach\" | **#24365 is closed `not_planned`, stale-bot-closed, locked, 3 reactions, 3 of 4 comments are bots.** #6686 also closed `not_planned` | `[SRC gh api repos/anthropics/claude-code/issues/24365 -&gt; closed, not_planned, locked:true, 3 reactions, 4 comments; issues/6686 -&gt; closed, not_planned, 551 reactions]` | **framing corrected** |\n| \"tmux control mode has one production implementer (iTerm2)\" | At least four more, one of which (WezTerm) ships nightly Windows binaries and has fixed a Windows-specific `tmux -CC` bug | `[SRC wezterm mux/src/tmux*.rs, docs/changelog.md]` | **verdict changed** |\n| \"darktile#288 shows near-zero control-mode adoption\" | The issue is **closed**, zero reactions, and darktile itself is dead (last push 2023-03-19). It supports no conclusion in either direction | `[SRC gh api repos/liamg/darktile/issues/288 -&gt; closed, 0 reactions; repo pushed_at 2023-03-19]` | **citation only** |\n| \"Vibe Kanban is ~3 months stale\" | **Sunset.** Second-to-last commit is \"Add README sunsetting banner\" | `[SRC gh api commits, README]` | **verdict changed** |\n| \"Agent Orchestrator has no CLI/HTTP surface, just a plugin SDK\" | Full Cobra CLI over a documented loopback HTTP REST API, plus a native ConPTY session-host subsystem, plus Windows npm binary packages | `[DOC docs/cli/README.md; SRC backend/internal/adapters/runtime/conpty/]` | **verdict changed** |\n| \"kandev is a web UI with no third-party API\" | WebSocket API, four MCP modes incl. External MCP, `agentctl` HTTP route groups, plugin marketplace, service install | `[DOC docs/public/*.md]` | **verdict changed** |\n| \"No candidate uses a raw TCP or binary wire protocol as its primary control surface\" | AO uses loopback HTTP REST for the daemon and a custom binary protocol over loopback TCP for the PTY layer. Warp ships `crates/local_control`. psmux binds TCP | `[SRC Untrivial-ai/agent-orchestrator backend/internal/adapters/runtime/conpty/proto.go; warpdotdev/warp crates/local_control/src/catalog.rs; psmux/psmux src/server/mod.rs:839]` | **framing corrected** |\n| \"asciinema/Windows: no artifact\" \u2014 but the whole ecosystem is refuted | The **CLI** is refuted; the **ecosystem** is not. `avt` (Apache-2.0, deps `rgb` + `unicode-width` only) compiles on Windows, and the four ALiS/asciicast WS sub-protocols are fully specified for third-party producers | `[SRC Cargo.toml; DOC docs.asciinema.org]` | **verdict changed** |\n| \"No observation channel provides a write path; injecting input requires PTY ownership\" | Refuted four ways, three of them native-Windows and headless: Claude Code hooks (`permissionDecision` + `updatedInput`), `claude -p --input-format stream-json`, OpenCode's `/tui` endpoint, and `AttachConsole` + `WriteConsoleInput` | `[DOC hooks, cli-reference, opencode server docs]` | **framing corrected** |\n| \"Reconstructing on-screen state requires PTY ownership\" | It requires the byte stream plus a VT emulator. asciinema's relay does exactly this and never touches a PTY: \"The server maintains comprehensive state for each active stream by running the whole stream through asciinema's own virtual terminal emulator\" | `[DOC docs.asciinema.org/manual/server/streaming/]` | **framing corrected** |\n| \"Claude Code's TUI swallows escape codes, so escape-based signalling is impossible from outside\" | Anthropic **deliberately removed** the controlling terminal from hooks in v2.1.139 and shipped a **supported** emission channel (`terminalSequence`) in v2.1.141. The CLI cooperates \u2014 via an allowlist that excludes OSC 133 | `[DOC hooks:146,182-192]` | **framing corrected** |\n| \"No cmd.exe path exists for OSC 133\" | oh-my-posh's `shell_integration` \"Works in bash, **cmd (Clink v1.14.25+)**, fish, powershell and zsh\" | `[DOC ohmyposh.dev/docs/configuration/general -- shell_integration]` | **citation only** |\n| \"tmux OSC 133 events have shipped\" | They are in **unreleased master** (`CHANGES FROM 3.7b TO 3.8`). Latest release is 3.7b | `[SRC gh api CHANGES, releases/latest]` | **citation only** |\n| \"Windows containers, Hyper-V backend of Docker Desktop is viable\" | Docker Desktop is **proprietary and paid above thresholds** \u2014 refuted on constraint 2, exactly like sbx. Applying the constraint to one and not the other was an internal inconsistency | `[DOC docs.docker.com/desktop/setup/install/windows-install/ + Docker Desktop subscription terms]` | **verdict changed** |\n| \"sandbox-runtime and Coder have unconfirmed licenses\" | Apache-2.0 and AGPL-3.0 respectively, one API call away | `[SRC gh api repos/anthropic-experimental/sandbox-runtime/license -&gt; Apache-2.0; repos/coder/coder/license -&gt; AGPL-3.0]` | **citation only** |\n| \"node-pty proves late-attach to a foreign console works\" | It passes `_innerPid` \u2014 the PID **it spawned itself**. Nothing anywhere late-attaches to a stranger's console and drives it | `[SRC src/windowsPtyAgent.ts:149]` | **framing corrected** |\n| \"Nobody wraps AttachConsole+Read/WriteConsole into a reusable library\" | NVDA, winpty and wexpect all do \u2014 each implementing a different *half*. The literal conjunction survives on a technicality; the useful finding is the split | `[SRC nvda source/winConsoleHandler.py; winpty src/agent/*; wexpect console_reader.py]` | **framing corrected** |\n| \"Superlogical distributes the libghostty VT state machine to clients\" | The cited primary source contains **zero** architecture content. That claim traces to secondary journalism. The post's actual decision-relevant fact: **Superlogical is a company shipping a commercial product** | `[DOC mitchellh.com/writing/superlogical, full text]` | **verdict changed** |\n| \"upterm is silent on Windows\" | It has a `### Windows` install section, a closed \"Add Windows Support\" issue, `host/internal/pty_windows.go` with real ConPTY + Job Objects, and Windows release artifacts | `[SRC owenthereal/upterm host/internal/pty_windows.go, host/host_windows.go; ART v0.24.0 upterm_windows_{386,amd64,arm64}]` | **verdict changed** |\n| \"Zellij's Windows support is unknown\" | Shipped in 0.44.0 (~2026-03-23), maintained by `divens`, with MSVC zip + MSI artifacts | `[SRC zellij-server/src/os_input_output_windows.rs, zellij-client/src/os_input_output_windows.rs; ART v0.44.3 zellij-x86_64-pc-windows-msvc.zip + -installer.msi]` | **verdict changed** |\n| \"herdr has no primary repo\" | 23,466 stars, Apache-2.0, `github.com/herdrdev/herdr` \u2014 and its URL was inside an artifact the researcher had already scraped | `[SRC gh api repos/herdrdev/herdr -&gt; 23,466 stars, Apache-2.0]` | **citation only** |\n| \"rmux's Windows claim is README marketing\" | Named-pipe daemon with `ImpersonateNamedPipeClient`, `server_identity_windows.rs` anti-squatting validation, SID+integrity-scoped pipe names, and a real ConPTY backend with Job Objects | `[SRC Helvesec/rmux crates/rmux-ipc/src/stream_windows.rs, server_identity_windows.rs, windows_mutex.rs, crates/rmux-pty/src/backend/windows/io.rs]` | **verdict changed** |\n| \"bash_kernel is viable\" | `signal.SIGPIPE` (nonexistent on Windows) and `pexpect.spawn` (POSIX-only) in `_start_bash()`. And it is stale since 2025-01-05 \u2014 \"updated 23 days ago\" was the `updated_at` metadata field, not `pushed_at` | `[SRC kernel.py:116,123,139]` | **verdict changed** |\n| \"ttyd has partial persistence\" | **Zero.** A new process per WebSocket, unconditionally killed on close | `[SRC src/protocol.c]` | **verdict changed** |\n| \"Coder's reconnectingpty is a viable candidate\" | Prior art only: not deployable standalone, AGPL + a proprietary `enterprise/` tree, and it self-terminates after 5 minutes with nothing attached | `[SRC coder/coder agent/reconnectingpty/{reconnectingpty.go,screen.go,buffered.go}; gh api repos/coder/coder/license -&gt; AGPL-3.0]` | **verdict changed** |\n| \"cmux's license is unknown (NOASSERTION)\" | GPL-3.0-or-later, stated in its LICENSE file; GitHub returns NOASSERTION only because of a dual-offer preamble | `[SRC contents/LICENSE]` | **citation only** |\n| \"PowerSession-rs's license is unclear\" | MIT, verified three ways | `[SRC contents/LICENSE, Cargo.toml, repo metadata]` | **citation only** |\n| \"Nimbalyst's agent extensibility is unresolved\" | Fixed set of exactly four. Its extension system is for **file-type editors**, not agents | `[DOC Nimbalyst README]` | **citation only** |\n| \"Pane is an independent project\" | A **Crystal fork** \u2014 it retains `docs/CRYSTAL_ARCHITECTURE.md` verbatim. Crystal produced two descendants, not one | `[SRC dcouple/Pane docs/CRYSTAL_ARCHITECTURE.md]` | **framing corrected** |\n| \"Daytona is open-source infrastructure\" | Repo root is `README.md` + `assets`, license removed, `GET /license` \u2192 404. \"As of June 2026, Daytona's core development has moved to a private codebase\" | `[SRC gh api repos/daytonaio/daytona -&gt; license null; contents/ = README.md + assets; GET /license -&gt; 404]` | **verdict changed** |\n| \"Sculptor runs agents in Docker containers, license unclear, Windows contradictory\" | MIT; Docker is the **experimental** backend (default is worktrees); download list is Mac/Linux/Linux-ARM64 only \u2014 no Windows build | `[SRC gh api repos/imbue-ai/sculptor/license -&gt; MIT; DOC sculptor README download list]` | **verdict changed** |\n| \"container-use has native Windows support\" | Compilation only, **no installation method** (its own follow-up PR says so, open a year); the \"drop into any agent's terminal\" feature is broken on Windows in every shipped release; last release 2025-08-19 | `[SRC PR #252, #348, #355]` | **verdict changed** |\n| \"DevPod's Kubernetes provider gives a non-WSL path\" | Abandoned. Last release is `v0.7.0-alpha.34` (2025-06-23); maintainers publicly reallocated to vcluster | `[SRC gh api; DOC issue #1946]` | **verdict changed** |\n| \"The non-WSL paths are consistently the less-traveled option\" | minikube's own driver index lists **Hyper-V as \"preferred\"** on Windows, and Docker's docs state there is **no architectural preference** between WSL and Hyper-V | `[DOC minikube.sigs.k8s.io/docs/drivers/ -- both Hyper-V and Docker carry the \"preferred\" label; docs.docker.com/desktop/setup/install/windows-install/]` | **framing corrected** |\n| \"Every non-multiplexer route gives up persistence or reattach\" | RDP/`tscon` gives both natively; `codex app-server --listen ws://` is a standing listener the client does not own | `[DOC learn.microsoft.com/windows-server/administration/windows-commands/tscon; codex-rs/app-server/README.md]` | **framing corrected** |\n| \"Every one of the four small candidates leaks orphaned processes on Windows teardown\" | Supported for psmux, partially for quil, **zero evidence** for oly, and necessarily zero for qscreen (which has no users to file reports) | `[SRC gh api issue searches across psmux/psmux, artyomsv/quil, slaveOftime/open-relay, dualface/qscreen]` | **framing corrected** |\n| \"psmux's star count shows astroturfing\" | The repeated promotional sentence is psmux's **GitHub repository description**, reproduced verbatim by aggregators and link-preview cards \u2014 including on a *fork's* page. Not evidence of a campaign | `[SRC gh api repos/psmux/psmux -&gt; the repeated sentence is the repo `description` field, reproduced verbatim by aggregators and on a fork's page]` | **framing corrected** |\n| \"No independent field report exists for psmux\" | `[2ND laurentkempe.com/2026/03/31/from-3-worktrees-to-n-ai-powered-parallel-development-on-windows/]` \u2014 an independent developer's field report (earlier editions called the author \"a .NET MVP\"; **the string \"MVP\" does not appear on the cited page** and that descriptor is withdrawn), multi-week use across projects, Copilot CLI + worktrees on native Windows: \"I can detach from a session, close my terminal, come back hours later, and reattach to find everything exactly where I left it \u2014 agents still running, output still visible\u2026 **No WSL required. No dependencies.**\" Tagged `[2ND]`: a user's blog, which is the correct tier for a field report and the strongest available evidence for \"somebody other than the author runs this in anger\" | `[2ND laurentkempe.com/2026/03/31/from-3-worktrees-to-n-ai-powered-parallel-development-on-windows/]` | **citation only** |\n| \"ACP defines no network transport and has no detach/reattach primitives\" | Its Transports chapter names Streamable HTTP and formally sanctions custom transports; `session/load`, `session/resume`, `session/list`, `session/fork`, `session/close` all exist. (The *conclusion* holds \u2014 those are conversation-state, not live-process attach \u2014 but the flat assertions were false) | `[DOC docs/protocol/v1/draft/transports.mdx; SRC agent.rs:4681-4700]` | **framing corrected** |\n| \"Warp's Oz API is cloud orchestration only\" | `crates/local_control` is an open-source local control plane with ~60 implemented actions over authenticated loopback HTTP. (The substantive point survives: no output-read, no send-keys, no attach) | `[SRC warpdotdev/warp crates/local_control/src/catalog.rs:4 and the full action catalog -- 84 entries marked `status: Implemented`]` | **framing corrected** |\n| \"microsoft/terminal#5468 is an integrity-level bug\" | Closed `Resolution-By-Design` in 7 minutes. It is a permanent security-model constraint, and directional \u2014 it does not bite same-user same-integrity siblings | `[SRC gh api repos/microsoft/terminal/issues/5468 -&gt; created 17:14:15, closed 17:21:46, label Resolution-By-Design]` | **framing corrected** |\n| \"The double-parse is unavoidable with these candidates\" | qscreen `AttachMode::Bytes`, oly WS `Data`, quil `MsgPaneOutput` and psmux `-CC` `%output` all ship raw bytes today | `[SRC qscreen crates/qscreen-protocol/src/lib.rs:56-61; oly src/http/ws.rs:36-46; quil internal/ipc/protocol.go:204-208; psmux src/server/mod.rs:1222-1227]` | **verdict changed** |\n| \"ripple's HANDOFF_GARBLING.md shows a rendering multiplexer corrupting output\" | It shows the opposite. The broken component was `CommandOutputRenderer`, a naive logical-line model with `MaxCol=100,000` and no grid. The doc states the VT-emulating path \"was OK from the start\" | `[DOC HANDOFF_GARBLING.md:42,103]` | **framing corrected** |\n| \"quil's 0x9C-in-UTF-8 bug proves double-parse corruption\" | Its own techdebt doc classifies it as an **upstream `charmbracelet/x/vt` spec-compliance defect** with a known correct general fix, already worked around, found on **macOS** | `[DOC techdebt/3-3-*.md]` | **framing corrected** |\n| \"TelePharo is a thin but live lead\" | No code activity since 2022-12-23. Abandoned, same class as pymux | `[SRC gh api \u2014 repo metadata for the projects named in this row]` | **verdict changed** |\n| \"Toad's Windows status is undefined\" | \"Toad runs on Linux and macOS. Native Windows support is currently lacking\u2026 but Toad will run quite well with WSL\" \u2014 refuted under the no-WSL constraint | `[DOC README:175-177]` | **verdict changed** |\n| \"Zellij and the libghostty consumers mean no lineage tool is native-Windows\" | Zellij, rmux, herdr and WezTerm all are | `[SRC zellij, Helvesec/rmux, herdrdev/herdr, wezterm/wezterm -- all four verified at source in 3.1]` | **verdict changed** |\n\n### 7.2 Method failures worth institutionalising\n\nThese recurred across angles and each one produced a wrong conclusion:\n\n1. **GitHub artifact status was assumed, not checked.** Six load-bearing citations turned out to be closed, `not_planned`, unmerged, or filed against a dead repo. Any issue or PR cited as evidence needs `{state, state_reason, merged, closed_at, reactions, repo pushed_at}` pulled.\n2. **`updated_at` was read instead of `pushed_at`.** The former ticks on stars and watches. This alone flipped bash_kernel from \"19 months stale\" to \"updated 23 days ago\".\n3. **Vendor docs were trusted over source for feature inventory.** `opencode.ai/docs/server` omits the entire `/pty` API. `docs.warp.dev` omits `local_control`. Both projects are open source; the source was one API call away.\n4. **A README grep was treated as conclusive.** It is the weakest of four available signals. upterm failed the grep on a heading the grep should have caught and passed the source tree, the CI config, the release artifacts and the issue tracker.\n5. **\"Windows release artifact exists\" was treated as proof of a working Windows daemon.** ttyd is the counterexample: real ConPTY in source, and a shipped binary that cannot spawn a child on your exact OS build. Check that the *released* artifact is recent enough to contain the fixes in master.\n6. **A cited URL did not contain the quoted sentence.** This happened at least five times (`terminal#1173`, learn.microsoft Q&amp;A 5789906, the window-station page, `node-pty#886`, abduco's project page). Conclusions mostly survived; citations mostly did not.\n7. **Stopping at the first file that answers the question.** wetty's `login.ts` has the POSIX hardcode \u2014 and so does `spawn.ts`, the actual main path. Grep the whole tree.\n8. **When a source page contains both a \"supported\" and an \"unsupported\" table, quote both.** herdr's Windows-beta page was read for its *Not supported* table and the *Supported* table above it went unread \u2014 which turned \"local persistent sessions and app attach both work in beta\" into \"the exact path you need is the one thing not working\", and made Q2 the map's top open question for two editions. If a page is structured as a capability matrix, the negative half is not the page.\n9. **When you find a flag or a `#define`, check the consumer, not the header.** The whole \"portable-pty is strictly better on flags than node-pty\" conclusion rested on reading two headers and never grepping `microsoft/terminal` for who reads `dwFlags`. Absence from a public header proves only \"undocumented\"; presence in a header proves nothing at all. One code search over the implementation refuted a claim that had propagated into candidate rankings.\n10. **A fixture file is not a test.** `tests/output-copilot.log` in oly's `tests/` directory was read as evidence of live agent-CLI e2e tests. It is a recorded ANSI dump consumed by a *different* module's unit tests. Before citing a test artifact, find the code that opens it.\n11. **A CVE number is a claim like any other and must be fetched.** \"Documented RCE (CVE-2021-30481)\" was carried for an entire research cycle attached to the wrong protocol. One NVD lookup refuted it.\n\n---\n\n## 8. Open Questions\n\nEach with a concrete next step, a cost, and \u2014 new in this edition \u2014 **what the answer changes**. Ordered by decision impact, not by topic. The first four can each eliminate whole branches of the map; the last group is due diligence.\n\n**Tier 1 \u2014 answers that eliminate branches. Do these first.**\n\n| # | Question | Next step | Cost | If yes \u2192 / If no \u2192 |\n|---|---|---|---|---|\n| **1a** | **Can ORCA's `agentCmdOverrides` produce a raw-pipe child at all?** ORCA spawns through node-pty, i.e. always a ConPTY, and the documented `-CC` remedy is \"give the child raw pipes\". Ask whether the cure exists before measuring the disease. | Read ORCA's spawn path from `tui-agent-launch-command.ts` through to the node-pty call; check whether any override shape bypasses the PTY. | 2 hours | **Yes \u2192** Q1 is worth running and `-CC` stays in play. **No \u2192** `-CC` control mode is unreachable in this topology whatever Q1 finds; psmux drops to its plain CLI verbs and the \"strongest interface in the map\" claim does not apply to you |\n| 1 | Does ORCA's node-pty ConPTY actually corrupt `-CC` control mode, as psmux's source warns for the SSH case? | Run `psmux -CC` as an `agentCmdOverrides` target in ORCA and diff the `%output` stream against the same command run from a raw-pipe parent. | One afternoon | **Corrupted \u2192** either apply 1a's remedy or drop `-CC` and use CLI verbs + polling. **Clean \u2192** the SSH-scoping in the source was literal, the hazard does not generalise, and psmux's control mode is the best interface available to you |\n| 9 | **Is the no-PTY architecture sufficient for your actual sessions?** `claude -p --input-format stream-json` + HTTP hooks + OTel covers everything except live screen fidelity and a human typing mid-run. **This is the highest-leverage item in the document** \u2014 a \"no\" moots most of sections 3 to 6. | Instrument one week of real work: count sessions where you actually attached and typed mid-run vs. sessions you only observed. | One week, passive | **Sufficient \u2192** stop evaluating multiplexers; build on pipes and hooks, and the entire PTY-ownership problem disappears. **Not sufficient \u2192** you have a measured number for how often you need attach, which is the input to every remaining trade-off |\n| 3 | Does Zellij's `use_vt_path()` gate hurt inside an ADE PTY? It selects the native-console path when neither `TERM` nor `WT_SESSION` is set. **There is currently no pass/fail criterion** \u2014 nobody knows what the failure would even look like. | Launch Zellij under an ORCA pane, record the observable behaviour (keyboard responsive? input garbled? fine?), then re-test with `TERM=xterm-256color` forced. | 1 hour | **Broken \u2192** one env var in the override command fixes it, and Zellij stays the most mature candidate. **Fine \u2192** the most-cited hazard in three editions of this map was never real, and Zellij is the default choice |\n| 23 | **Do any candidate's verbs work with no console attached?** Every \"headless verbs\" claim in section 4 is untested against a genuinely console-less caller \u2014 a service, a Scheduled Task, or a hook. This is the headless-first constraint stated as a test. | Invoke `zellij list-sessions`, `psmux ls`, `wezterm cli list` and `rmux ls` from a Scheduled Task running with no console, capturing stdout/stderr to a file. | 2 hours | **Work \u2192** the whole section-4 \"headless verbs\" column becomes trustworthy. **Fail \u2192** those candidates are terminal tools, not middle layers, and the no-PTY architecture wins by default |\n\n**Tier 2 \u2014 answers that pick between surviving candidates.**\n\n| # | Question | Next step | Cost | If yes \u2192 / If no \u2192 |\n|---|---|---|---|---|\n| 2 | What does herdr's `\"Direct terminal attach \\| unsupported\"` mean, given the **same page** lists \"Local persistent sessions\" and \"Windows Terminal / PowerShell app attach\" as **Supported (beta)**? Demoted from top priority because the two tables together suggest it is the Unix fd-passing path, not the ADE-spawn path. | Read `src/api/`, `src/ipc.rs` and the Windows-beta doc's surrounding sections; if ambiguous, file one question on the repo. | 2 hours | **Unix-only mechanism \u2192** herdr is a live candidate with a 23k-star maintenance base. **The ADE path \u2192** herdr is out on Windows until the beta lands |\n| 8 | Can `wezterm-mux-server` be driven headlessly end to end on Windows \u2014 `spawn`, `send-text`, `get-text`, `list`, with a client attaching later? | Install the nightly Windows zip, run `wezterm-mux-server --daemonize`, drive it with `wezterm cli` only. | Half a day | **Yes \u2192** you get the only version-skew-tolerant codec in the map, at the price of tracking a nightly. **No (attach needs the GUI) \u2192** the codec remains a design reference only |\n| 4 | Does Electron/xterm.js answer XTWINOPS `CSI 18 t`? | Read xterm.js `windowOptions` defaults; then empirically resize an ORCA pane and check the reported size inside psmux. | 1 hour | **Answers \u2192** psmux's in-band sizing works and resize is solved. **Does not \u2192** psmux silently runs at 120x30 forever, which is a silent-corruption-class bug, not a cosmetic one |\n| 10 | Is `ao pty-host` extractable as a standalone package? Apache-2.0 makes vendoring clean and it is the closest architectural match found. | Try `go build ./backend/internal/adapters/runtime/conpty/...` outside the AO tree and see what it drags in. | 3 hours | **Clean \u2192** you have a working detached-ConPTY host to build on rather than write. **Entangled \u2192** it is a design reference and you write your own |\n| 12 | Do OpenCode's `/pty` endpoints work on native Windows with the shipped binary? | `opencode serve`, then `POST /pty` + WS connect from PowerShell. | 1 hour | **Work \u2192** a REST+WS attach surface exists today with a resumable cursor \u2014 but note it is L1+, not L2, and the wire is UTF-8 text. **Fail \u2192** the protocol is still worth copying |\n| 18 | Do any candidates run correctly nested inside a second ConPTY? **Nobody has tested this, including oly** \u2014 whose \"nested agent tests\" turned out not to exist. | Run each under an ORCA pane with a resize, an alt-screen app, and a mouse-mode app. | One day for all four | Whichever survives is your candidate. This is the single largest untested area in the map |\n| 24 | Can any candidate's prefix key be **disabled entirely** on a headless path, not merely rebound? Only docker documents remapping; every multiplexer's rebindability in 4.3 is an inference. | For each of psmux, Zellij, rmux: find the config key, set it to none, and verify no chord is intercepted. | 3 hours | **Yes \u2192** prefix collision with the ADE stops being a concern. **No \u2192** you inherit a keybinding negotiation with every ADE you ever swap to, which is exactly what this project exists to avoid |\n\n**Tier 3 \u2014 soundness and due diligence before adoption.**\n\n| # | Question | Next step | Cost | If yes \u2192 / If no \u2192 |\n|---|---|---|---|---|\n| 6 | Is psmux's `cross_session_server.rs` path reachable in practice? It accepts the first connection with no handshake, **registers it as a tee writer so it receives all pane output**, and writes everything it reads into the ConPTY \u2014 full-duplex exfiltration plus injection on a loopback port. | Trace every call site of `cross_session_server`, or attempt a PoC connect during a cross-session pane move. | 3 hours | **Reachable \u2192** psmux is unsafe on a shared or multi-user machine until patched; report upstream. **Unreachable \u2192** it is latent, and still a reason to pin and watch |\n| 7 | Does quil's Windows ConPTY stack actually work? 25+ Windows source files, ubuntu-only CI, zero external issues ever filed. | Run its test suite on Windows; add a `windows-latest` job locally and see what breaks. | Half a day | **Works \u2192** you get the richest agent-facing surface in the map (18 MCP tools). **Breaks \u2192** UNVALIDATED becomes REFUTED and the MCP surface is a design reference |\n| 13 | Does oly's M4 named-pipe ACL gap still exist, or was it closed without updating the roadmap doc (untouched since 2026-03-12)? | Diff `src/ipc.rs` across the full commit history for any ACL/SD-related change; or ask the maintainer. | 1 hour | **Closed \u2192** oly's main disqualifier is gone. **Still open \u2192** oly is unusable on any machine with another local account or a low-integrity process |\n| 22 | Do the four small candidates have CVE/GHSA records? Only their own issue trackers were checked. | One NVD/GHSA sweep before adoption sign-off. | 1 hour | Either way this is a gate you pass before shipping, not a discriminator |\n| 5 | Is `AttachConsole` reliable in the exact topology that matters \u2014 a process spawned inside a headless ConPTY created by node-pty inside Electron? | Write a 30-line helper that attaches to a claude.exe spawned by ORCA and calls `GetConsoleProcessList`. | 2 hours | **Works \u2192** you gain a read-only out-of-band inspection path. **Fails \u2192** 6.4's refutation is complete and late-attach is closed permanently |\n| 11 | Does `codex app-server --listen unix://` actually bind on Windows? The `uds` crate is cross-platform and the transport is registered unconditionally, but the daemon lifecycle manager is explicitly Unix-only. | `codex app-server --listen unix://%TEMP%\\test.sock` on Windows 11 and see. | 30 minutes | **Binds \u2192** a second vendor-native no-PTY architecture is available. **Fails \u2192** Codex stays stdio-only on Windows |\n| 14 | Is oly's `main`-branch stall (no commits since 2026-07-01, though four side branches are active) a stable plateau or a departed maintainer? | Watch for 4-6 weeks; check issue response latency. | Passive | Affects only whether oly is worth adopting versus reading |\n| 19 | Should the middle layer's control plane carry ACP over a Windows named pipe? The spec formally permits it and agent CLIs are converging on it \u2014 but ACP has no Windows CI and no live-process attach primitive. | Prototype the transport shim against the reference implementation and see whether it builds and passes on Windows. | Two days | **Builds \u2192** the wrapper becomes protocol-native to the ecosystem it serves. **Fails \u2192** use your own framing and revisit when ACP v2's terminal work lands |\n| 17 | Has microsoft/terminal's in-process ConPTY redesign (`doc/specs/#13000`) shipped, partially shipped, or stalled? | Cross-check against Windows Terminal changelogs and the ConPTY NuGet release notes. | 1 hour | **Shipped \u2192** the reflow/resize desync in 6.1 may be fixable rather than permanent. **Stalled \u2192** plan around it |\n| 15 | Would `kind` or a Hyper-V-backend Podman work end to end on Windows without WSL? Architecturally plausible from both projects' docs; **zero real-world confirmations located**. | One hands-on spike: `podman machine init --provider hyperv` then `kind create cluster`. | Half a day | Only matters if you want container isolation *around* the middle layer; it never replaces it (3.5) |\n| 21 | Does any libghostty consumer have a working Windows build today? ghostel says yes for its own ConPTY module \u2014 is that libghostty on Windows, or ghostel routing around it? | Read `src/ConPtyProcess.zig` and check what it imports. | 1 hour | **libghostty \u2192** a fourth VT-parser option opens up on Windows. **Routing around \u2192** `avt` and `vt100-psmux` remain the only two |\n| 20 | Is there anything transferable in SCADA/industrial session tooling? Genuinely under-researched, not confirmed absent. | One dedicated search round before writing the area off. | 2 hours | Low expected value; recorded so the gap is not mistaken for a finding |\n| 16 | Does the Windows Hypervisor Platform feature work on Windows 11 Home? No Microsoft doc states an edition requirement either way. | Only resolvable empirically or by a Microsoft support statement. | \u2014 | Low priority \u2014 the tool that needs it (`sbx`) is closed-source and already refuted on constraint 2 |\n\n---\n\n## 9. Coverage Gaps\n\nStated plainly so nothing is mistaken for verified, and **ordered by how much damage a wrong assumption does** \u2014 most dangerous first. Inferences lead, because they are the items most likely to be read as facts.\n\n**1. Inference presented as reasoning, never observed.** These are conclusions this document reached from verified premises. Each could be wrong without any of its premises being wrong.\n\n- That a **foreign-SID console is not `AttachConsole`-able** (the `sandbox-runtime` incompatibility, 3.5) \u2014 inferred from documented per-session console semantics, never tested.\n- That the **session-0 boundary blocks `AttachConsole`** (3.8, 6.3) \u2014 same class, same status. Earlier editions stated this as established fact in two places.\n- That the **psmux `-CC` / ConPTY DCS hazard generalises beyond SSH** (4.2). The source scopes it to \"when running over SSH with a ConPTY console\"; the generalisation to a node-pty ConPTY is this document's, and Q1 exists to test it.\n- That **`claude -p --output-format stream-json` behaves identically on Windows and Linux** (3.8) \u2014 inferred from unqualified flag documentation over ordinary pipes. **This is the second finding's load-bearing cross-platform claim and nobody has run it on both platforms.** Q23's neighbour in importance.\n- That **`asciinema/avt` compiles on native Windows** (3.7) \u2014 inferred from a two-crate dependency list with no platform code. Nobody has built it.\n- That **`eat` inherits Emacs core's Windows-pty gap** (3.8).\n- That **Windows Job Objects are what kill sshd's children on disconnect** (6.3) \u2014 asserted in one maintainer sentence, with **zero** code-search corroboration in openssh-portable.\n- That the multiplexer candidates' **prefix keys are rebindable** (4.3) \u2014 inferred from \"they have config files\". Only docker's `--detach-keys` is documented.\n\n**2. Not tested empirically anywhere.** The map is a source-and-docs map. Almost nothing in it has been run.\n\n- **Double-ConPTY nesting** \u2014 by any candidate. Note this now includes oly, whose supposed nested-agent tests were found not to exist (3.1).\n- **Any candidate actually spawned by ORCA.**\n- Whether `AttachConsole` reaches an ORCA-spawned agent.\n- Whether **any candidate's CLI verbs work with no console attached** \u2014 the headless-first constraint has never been tested against a genuinely console-less caller (Q23).\n- What the **observable symptom** would be if Zellij takes the native-console input path inside an ADE PTY (Q3) \u2014 so Q3 currently has no pass/fail criterion.\n- The **install commands in 3.1** were assembled from each project's own docs; none was executed on this machine.\n\n**3. Not re-verified in any pass.** Believed, sourced once or not at all, never re-checked.\n\n- Zed's ACP constraint and Terminal Threads; OpenChamber's OpenCode-only scoping; coder/mux's own-agent-loop architecture.\n- Every closed-source entry (Kepler, Air, Warp/Oz, Factory, Devin, Jules, Cursor, Conductor, AIDEN) \u2014 nothing about these was verified at source, by definition.\n- The agent-vs-host classification for Cline/Kilo/opencode/Amp/Codebuff; automagik-genie and operator-oss; the entire sub-350-star ADE long tail.\n- rmux's \"936 of ~1000 commits\" single-author statistic; the wezterm `454ec0a7`/`9d834586` commit identifiers; the rmux breakaway error string's file and line; PowerSession-rs's missing-`ResizePseudoConsole` file and line; oly's `extract_query_responses_no_client` file; the LangChain `'response'` discriminator; the pywinpty Discourse post's URL; the \"actions/runner is near end-of-life\" blog.\n- Nine citations in 7.1 record a *method* (`gh api`, `README`) rather than a retrievable object. Where the object was recoverable it has been filled in; where it was not, the row now names the repo and file at minimum. **Known limitation:** a handful still resolve only to a repo, not a line.\n\n**4. Never examined at all.**\n\n- The **xterm.js `attach-addon` raw-bytes-over-WebSocket convention**, shared by ttyd, code-server and VS Code. This is the largest single hole in the protocol coverage, and the superlative earlier editions attached to it (\"arguably the most widely deployed\u2026\") is itself unverified \u2014 it is now tagged `[UNV]` in 3.6.\n- `chadbyte/claude-relay` and `myrialabs/ptykit` (\"PTY sessions over WebSocket \u2014 collaborative rooms, resilient client, Node &amp; Bun\", 1 star, pushed 2026-07-30).\n- ACP v2's terminal work; Codex's `stdio-to-uds` crate and its third `remote_control` transport.\n- Whether any candidate other than herdr documents Windows prefix-key behaviour (4.3).\n\n**5. Evidence that could not be retrieved.** Both blocked cases are the **same mechanism** \u2014 an Anubis-style proof-of-work interstitial that returns HTTP 200 with a \"Making sure you're not a bot!\" body, so a naive status check passes them. Earlier editions described one as \"access-gated\" and the other as \"an anti-scraper wall\", which read as two different problems.\n\n- developer.valvesoftware.com \u2014 the Source RCON spec was not independently verifiable.\n- gitlab.freedesktop.org \u2014 the canonical OSC 133 spec; triangulated from three implementer docs instead.\n- The abduco/dvtm COSIN'18 slides did not extract.\n\n**6. Coverage bounds worth stating.** The candidate pool came from GitHub topic and code search plus targeted follow-up. Anything not indexed by GitHub code search \u2014 self-hosted forges, unreleased internal tools, and non-English project descriptions beyond the handful encountered \u2014 is outside this map's reach by construction, not absent from the world.\n\n---\n\n## 10. How to narrow this yourself\n\nThis document deliberately does not pick a winner \u2014 the brief was to map the space, and section 1's verdict is that no candidate dominates. But \"here is the whole space, plus 24 open questions\" is not usable on its own. So: a decision scaffold keyed on **your** answers, not on a recommendation. Answer four questions and most of the map falls away.\n\n**Question A \u2014 Do you ever attach and type into a session mid-run, or do you only observe and steer through the agent's own interfaces?**\n\n- *Only observe* \u2192 **The no-PTY architecture dominates and most of section 3 is moot.** `claude -p --input-format stream-json` over ordinary pipes, plus `http` hooks for the write path, plus OTel for liveness including `blocked_on_user`. No ConPTY, no nesting, no prefix keys, no resize propagation, no double parse \u2014 every hard problem in 4.1 and 4.2 disappears because you never own a PTY. Read 3.8, 5's last row, and stop. Q9 is how you answer this honestly rather than by intuition.\n- *I attach and type* \u2192 continue to B. You need a PTY owner, and the rest of this scaffold applies.\n\n**Question B \u2014 Do you need the same behaviour on Linux, or is Windows the only platform that has to work?**\n\n- *Linux parity matters* \u2192 **psmux is out** (Windows-only; its CI has no Linux build, only two POSIX shell-script jobs). Also note oly ships a **Windows-specific expected-output fixture**, which is direct evidence its rendering diverges by platform. Survivors: **Zellij**, **rmux**, **wezterm-mux-server**, **herdr**.\n- *Windows only* \u2192 psmux comes back, and it has the strongest interface in the map (tmux `-CC`) \u2014 subject to Q1a and Q1.\n\n**Question C \u2014 Can you tolerate a preview or nightly channel, or do you need a pinned stable release?**\n\n- *Need a pinned stable* \u2192 **herdr is out** (Windows ships preview-channel only; stable `v0.7.5` has no Windows asset) and **`wezterm-mux-server` is out** (no tagged release since 2024-02-03; the nightly tag is the only current Windows path). Survivors: **Zellij** (v0.44.3 msvc zip + MSI), **rmux** (winget/scoop/choco).\n- *Nightly is fine* \u2192 all four stay in.\n\n**Question D \u2014 Will you vendor and build source, or do you need something installable?**\n\n- *Installable only* \u2192 **`ao pty-host` is out** (a `Hidden: true` subcommand, not a product) and **OpenCode `/pty` is out** as a middle layer (it is an API inside another product, and only L1+ \u2014 it dies with `opencode serve`).\n- *Willing to vendor Go or Rust* \u2192 **`ao pty-host` opens up** (Apache-2.0, closest architectural match found, detached ConPTY with scrollback replay and resize arbitration) and so does the **WezTerm codec** as a protocol to implement rather than a binary to run.\n\n**Where the branches land, in practice.** *Observe-only* ends at the no-PTY architecture and needs nothing from this map's candidate list. *Attach + Linux parity + stable pin* ends at **Zellij or rmux**, and the tiebreak is maturity versus transport design: Zellij has 34.6k stars, an MSI, and the best-verified Windows claim in this map; rmux has the only identity-scoped namespace and the only three-layer Windows security model, against a single-author bus factor. *Attach + Windows-only* adds **psmux**, whose interface is the best in the map and whose platform breadth is the worst \u2014 decide it with Q1a. *Willing to vendor* adds **`ao pty-host`** as a starting point rather than a dependency.\n\n---\n\n## 11. If you decide to build it\n\nSection 1's verdict is that nothing is packaged as the thing you want. The pieces of a build spec are, however, all present in this document \u2014 scattered, because they were found in different places. Collected here so the loop the verdict opens actually closes.\n\n| Where | What it contributes |\n|---|---|\n| **5.1 \u2014 the verb set** | The ten verbs, derived from the union of every candidate's surface. `spawn`, `list`, `attach` (with a resumable cursor), `detach`, `send`/`send_keys`, `get_output`/`snapshot`, `resize`, `status`, `kill`, `wait`. This is the interface |\n| **4.1 \u2014 the four Windows requirements** | Answer DA1 inside 3 s or eat multi-second stalls; do not set `INHERIT_CURSOR` unless you answer `ESC[6n` fast; load `conpty.dll` by absolute path or not at all; propagate resize explicitly because there is no SIGWINCH. This is what makes it work on the platform |\n| **3.7 \u2014 the PTY libraries, and the flags note under it** | `portable-pty` (Rust) or `charmbracelet/x/conpty` / `aymanbagabas/go-pty` (Go). Bundle the ConPTY NuGet redistributable \u2014 not because Microsoft blesses it but because fixes will not reach the in-box copy. And do not spend effort on creation flags: current ConPTY reads three, and the old passthrough bit now means something else |\n| **2.2's third pole \u2014 the raw live path** | Keep a VT model for snapshots, scrollback and resize bookkeeping, but ship **raw child bytes** on the live path. This is what makes the ADE's xterm.js the only parser in the chain. Carry `bytes`, never `string` \u2014 2.2 and 4.2 both show what string-typed wires cost |\n| **3.6's adoptable contracts** | Do not invent framing. Either speak tmux `-CC` (decade-hardened, \u22655 implementers, and psmux/WezTerm prove it works on Windows), or copy WezTerm's varint codec, which is explicitly designed for version-skewed client/server pairs \u2014 i.e. it is the ADE-churn insulation, in wire form |\n| **2.3 + 5.1's closing note \u2014 the security model** | Identity-scoped namespace (`{prefix}-{sid}-il-{integrity}-{label}`, rmux's shape), a real pipe ACL, and `FILE_FLAG_FIRST_PIPE_INSTANCE`. Not loopback TCP: it is reachable by any local process including other users and low-integrity ones. psmux's cross-session hole is what that costs in practice |\n| **2.9 + 3.1's `ao pty-host` note \u2014 the persistence mechanism** | `CREATE_NEW_PROCESS_GROUP \\| DETACHED_PROCESS` so the host outlives the daemon, plus a registry file mapping `{sessionId, ptyHostPid, pipePath}` so orphans are findable after metadata loss |\n| **5.2 \u2014 the name for the shape** | Portal. A coding agent, a MUD engine and a Lisp REPL independently arrived at \"the frontend is disposable, the long-running state lives behind a socket\". That convergence is the strongest architectural signal in this map |\n\n**And the honest caveat on building it.** Every one of the ten candidates in section 1 got most of this right and still forfeited something. The two things that most often go wrong are not in the verb list: **answering ConPTY's startup handshake** (4.1 item 1, the single most load-bearing requirement here) and **not putting a string type on the wire** (2.2). Both are cheap to get right at the start and expensive to retrofit.\n", "creation_timestamp": "2026-08-02T12:05:16.098922Z"}, {"uuid": "0f340180-3c9c-46ae-b0fd-ad775c55313d", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "seen", "source": "https://t.me/reverseame/1112", "content": "CVE-2021-30481: Source engine remote code execution via game invites https://secret.club/2021/04/20/source-engine-rce-invite.html", "creation_timestamp": "2026-07-29T12:02:58.515078Z"}, {"uuid": "59cbdf25-4be9-4f54-9b88-5518edeae321", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2021-30481", "type": "seen", "source": "https://t.me/reverseame/1112", "content": "CVE-2021-30481: Source engine remote code execution via game invites https://secret.club/2021/04/20/source-engine-rce-invite.html", "creation_timestamp": "2026-07-30T00:02:48.117720Z"}]}