🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726) - #727

Open
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers
Open

🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)#727
taras wants to merge 4 commits into
mainfrom
spike/tmux-pane-workers

Conversation

@taras

@tarastaras commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Why

#717 (<Terminal.Grid>) needs a process topology that gives reliable readiness, pane output, sequential native launches and complete teardown. #726 asks for an executable proof of the persistent pane-worker topology and a report so the Planner can adopt it or the Architect can narrow the lifecycle contract. This PR is that proof and report; it decides #717's next step and ships nothing in the product binary.

What changes

Before: the topology was a hypothesis. tmux's pane_pid cannot say whether a native process started, and a native command as the pane command leaves no endpoint for document text or a second launch.

After: deno task proof:tmux-pane-workers runs one 2×2 workspace (and the smaller topologies each check needs) end to end and writes evidence.json + summary.md. plans/tmux-pane-workers-proof.md records the run at b0fcee9 — 8/8 checks, 115 claims — the measured lifecycle for 2/4/8 panes, what teardown can and cannot prove, the smallest interfaces the evidence supports, and the recommended decision: adopt the pane-worker topology, with the teardown claim narrowed to process group, ancestry at teardown start, and terminal holders.

How it works

proof.ts → outer private tmux (a terminal for the visible attach) → proof.ts --inner
→ per check: private 0700 dir → pane sockets + 0600 tokens → hidden tmux server,
explicit row-major layout, worker.ts as each pane's initial process
→ workers connect over IPC → launch/display/cancel/shutdown
→ interactive-process resource in the worker (spawn event = ready; exit = settled;
SIGINT → SIGKILL → snapshot sweep; terminal-holder sweep at shutdown)
→ control client (-C, no-output) classifies detach / control loss / server stop
→ evidence.json, summary.md

Review guide

Start with:plans/tmux-pane-workers-proof.md — the decision, the acceptance table, and What teardown can and cannot prove.

Then review:

  1. scripts/proofs/tmux-pane-workers/interactive-process.ts — the product-boundary resource derived from packages/runtime/launcher.ts: readiness vs settlement, escalation, the pre-signal snapshot.
  2. worker.ts and ipc.ts — what a pane worker is, what crosses the socket, admission by token, exited after the sweep, the shutdown terminal sweep; run() not main().
  3. provider.ts and layout.ts — hidden server, explicit layout + swap-pane ordering, control-mode classification, detach-before-signal on the visible client, stop() proof.
  4. checks.ts — one check per acceptance item; evidence.ts, workspace.ts, proof.ts are harness.

Look carefully at:

  • the orphan rows in the report's teardown table: the one honest negative, and where each sweep has to live (the worker at shutdown, not the provider).
  • plans/tmux-pane-workers-evidence.json is the raw run with IPC tokens redacted; its pane ids, ttys, pids and socket paths describe one run and nothing durable.

What must stay true

  • Readiness is the child-process spawn event and nothing earlier; startup-failed never follows ready — enforced by useInteractiveProcess, checked by readiness-boundary.
  • Argv, cwd and env never pass through tmux's command parser — enforced by the IPC protocol; checked by the journey's byte-for-byte argv claim.
  • A pane is free for the next child only after its sweep — enforced by the worker sending exited after stop(); checked by the journey's sequential-reuse claim.
  • Every acquired resource has one scope and one finalizer — the ownership diagram in the report is the code's structure; checked by cancellation-points at five phases.

How to verify it

  • deno task proof:tmux-pane-workers (≈5 min, unattended; needs tmux 3.6a-class and Deno) proves every claim in the report and fails if any pid, server, socket directory or terminal setting survives teardown.
  • deno task proof:tmux-pane-workers -- --only negative-children proves descendant coverage and fails if the pre-signal snapshot or the worker's terminal sweep is removed (both were removed on purpose during development and did fail).
  • -- --only layout-geometry fails if swap-pane ordering is skipped (tmux ignores leaf ids in layout strings).
  • -- --only readiness-boundary fails if ready is sent before the spawn event.
  • -- --only cancellation-points fails if stop() skips kill-server.
  • -- --attach shows the journey on your own terminal and waits for you to detach.
  • deno task lint and deno task check are green; the proof lives under scripts/proofs/, outside test discovery, so no CI suite runs it.

Scope

Included

  • The proof, its task, the report and the redacted evidence of one run.

Intentionally unchanged

  • architecture.md, the specs and packages/runtime/launcher.ts: the decision is recommended here for the Architect to record; nothing in the product moves.

Risks and limitations

  • Platform-specific by design: measured on macOS arm64 with tmux 3.6a; ps -o tpgid, lsof -t and the pty-revoke behaviour are the macOS facts the report names.
  • A descendant that left the session, closed the pane terminal and outlived its parent cannot be proven stopped by any parent process; the report says so and proposes the narrowed contract.
  • Cancellation between new-session forking the server and the server listening is a window the proof records rather than tests.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

An executable proof of the pane-worker topology #717 needs: one persistent
worker per tmux pane owning the pane's terminal, driven over an
invocation-private Unix socket, starting interactive children that inherit
the terminal through an Effection process resource derived from the native
launcher. The proof runs the reader-visible journey, the readiness boundary,
explicit row-major layout, atomic attach, the three close signals, negative
children, cancellation at every phase, and 2/4/8-pane timings, and records
what teardown can and cannot prove.
deno task proof:tmux-pane-workers
Evidence from b0fcee9: 8/8 checks, 115 claims, 2/4/8-pane timings over 20
runs each. Recommends adopting the persistent pane-worker topology for #717
with the teardown claim narrowed to process group, ancestry at teardown start
and terminal holders — a descendant that left the session, closed the pane's
terminal and outlived its parent is outside what a parent can prove.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 7 redundant comments. Inline suggestions to remove them below.

rows === Math.ceil(shape.panes / shape.columns),
rows,
);
// What `select-layout tiled` would have done with the same panes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// What `select-layout tiled` would have done with the same panes.

workspace.links.every((link) => link.connected()),
);

// 3. The server stops underneath everything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 3. The server stops underneath everything.


await main(function* () {
const options = parseOptions(process.argv.slice(2));
// tmux and ps output is collected, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// tmux and ps output is collected, never echoed.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// A child that exited on its own may have left descendants in the
// pane's process group. They are swept before `exited` is reported,
// because `exited` is what makes the pane free for the next child —
// and a sweep running beside a new child would reach that child too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// and a sweep running beside a new child would reach that child too.

limitMs: number = DEFAULT_LIMIT_MS,
): Operation<T> {
// Subscribe before scanning, so an event between the scan and the wait
// is not lost.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// is not lost.

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown

PR #727: 🔬 Prove persistent tmux pane workers for <Terminal.Grid> (#726)

14 files, +6544 / -0

Scope

🔴 PR has 6544 lines changed. Split into focused PRs.

🟡 6544 lines changed. PRs under 400 receive more thorough review.

🟡 PR mixes config and source changes.

🟡 New abstraction files: scripts/proofs/tmux-pane-workers/provider.ts. Verify 3+ consumers.

Structural

Oxlint structural signals:

  • no-unnecessary-type-arguments ×2: scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
  • no-redundant-type-constituents ×1: scripts/proofs/tmux-pane-workers/interactive-process.ts

Slop

  • scripts/proofs/tmux-pane-workers/proof.ts:238// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/proof.ts:241// Collected by the caller, never echoed.
  • scripts/proofs/tmux-pane-workers/interactive-process.ts:206// Already released.

Static Analysis

Oxlint: 10 diagnostics across 6 files (4 rules)
Density: 0.002 violations/added-line

no-floating-promises (5): scripts/proofs/tmux-pane-workers/provider.ts, scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/worker.ts (+1)
no-unnecessary-type-arguments (2): scripts/proofs/tmux-pane-workers/ipc.ts, scripts/proofs/tmux-pane-workers/workspace.ts
no-unnecessary-template-expression (2): scripts/proofs/tmux-pane-workers/checks.ts
no-redundant-type-constituents (1): scripts/proofs/tmux-pane-workers/interactive-process.ts

Correctness

No extraneous code patterns detected.

A worker's settlement is now the escalation followed by the terminal-holder
sweep, on natural exit as well as cancel and shutdown, and `exited` is
reported only after it. A new sequential-handoff regression starts a child
whose descendant setsid()s, keeps the pane terminal and outlives its parent,
and proves a launch sent during settlement is refused, the descendant is
named and stopped before `exited`, and only the worker and the second child
hold the terminal after admission. Measurements gain the handoff and
relaunch latencies the sweep introduces. The setsid() + closed-terminal
orphan remains the recorded unprovable boundary.
Evidence from 650510b: 9/9 checks, 127 claims. Adds the handoff and
relaunch columns to the measurements and the sequential-handoff regression
to the teardown account; the setsid() + closed-terminal orphan stays the
recorded unprovable boundary.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 redundant comments. Inline suggestions to remove them below.

// tmux and ps output is collected, never echoed.
yield* Stdio.around({
*stdout() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

// Collected by the caller, never echoed.
},
*stderr() {
// Collected by the caller, never echoed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Collected by the caller, never echoed.

try {
child.unref();
} catch {
// Already released.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// Already released.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@taras