Skip to content

πŸ”’ Terminal REPL POC (#774) β€” VIEW_ONLY evidence - #779

Draft
taras wants to merge 73 commits into
agent/issue-755-codex-native-launchfrom
agent/issue-774-black-box-repl-poc
Draft

πŸ”’ Terminal REPL POC (#774) β€” VIEW_ONLY evidence#779
taras wants to merge 73 commits into
agent/issue-755-codex-native-launchfrom
agent/issue-774-black-box-repl-poc

Conversation

@taras

@taras taras commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Result: VIEW_ONLY β€” evidence, not a shipped REPL

Issue #774 asked whether generic terminal-state convergence can make black-box
tmux input delivery reliable enough while provider session files supply
authoritative acceptance and completion. After the deterministic evidence and
architecture review, the decision is VIEW_ONLY:

  • Passive session-file observation is sound and is the reusable outcome.
  • Reliable message dispatch is not established over black-box tmux input and
    stays ACP-owned.

This PR is that evidence. It is a finite, disposable POC under
packages/terminal-tmux/poc/repl/. It exports nothing from the package and
changes no production, architecture, specification, dependency or lockfile.

The exact race

The controller samples the pane and the provider across an acknowledged barrier,
prepares the private file and tmux buffer, then takes one final combined sample
before recording AttemptStarted and pasting. Every observable change up to that
final sample refuses with zero paste.

What cannot be closed is the interval between that final sample and the single
guarded paste
. A provider turn can open there. It is not observable before the
paste, and a tmux-only guard cannot atomically refuse it (the guard can recheck
pane generation, process, liveness and mode, but not the provider's session
file). A delivery admitted in that window is only ever settled uncertain after
the fact, never proved safe before the bytes are sent. Because a safe input point
cannot be guaranteed, tmux panes remain view-only for coordinated work.

Full write-up: packages/terminal-tmux/poc/repl/RESULT.md.

What the evidence proves (all deterministic, Deno/Node/Bun)

  • Strict read-only observation: exact native identity and project from bounded
    header reads, cursor advances only past a complete record, Claude output grouped
    by requestId turn, and refusals for ambiguity, truncation, rotation, identity
    mismatch and unsupported shapes. Never writes a provider file.
  • Convergence folds provider open-turn, cursor, event count and physical size
    (including a partial tail) into both barrier samples and a final post-preparation
    sample, so a turn opening during the barrier or during buffer load refuses with
    zero paste.
  • A Flux store whose actions are the only mutations, with a sequence-numbered
    staged-write log that refuses gaps, duplicates, malformed records and illegal
    transitions, and observation that never duplicates an event across restart.
  • A report schema and overall aggregator whose PASS was reachable only with both
    live provider journeys; the suite records the VIEW_ONLY conclusion in a
    schema-valid overall report.
  • The terminal boundary kept as evidence (a single conditional guard over an
    injectable tmux command seam with a real control-mode activity source), driven
    by fake-seam boundary tests.

Closeout

  • The live-delivery journeys are permanently disabled: the supervisor launches no
    coding agent and spends no model turn under any gate; it returns the VIEW_ONLY
    conclusion. The grid launch documents and the two live proof documents are
    removed. No Claude or Codex model turn was ever spent.
  • A production retained REPL and action store are not authorized by this result.

How to verify

deno task test packages/terminal-tmux/tests/repl-poc.test.ts
deno task check
deno task lint
git diff --check

Scope confirmation

  • Every changed file supports the closeout described above.
  • Unrelated cleanup and formatting changes are excluded.
  • No production, architecture, specification, dependency or lockfile change.
  • The description matches the final diff and test results.

taras and others added 30 commits September 7, 2026 04:35
`<Terminal.Grid>` and `<Terminal>` become reserved core structural syntax.
This Story owns the authored structure alone: the grammar, the placement
rules, and the row-major layout a grid derives. No terminal authority,
provider, pane execution, shell, durability or replay is built here.

The grid's closed props hold one required positive-integer `columns`; a
pane's hold one required non-empty `title`. Titles are display labels and
identify nothing β€” a pane's structural identity is its ordinal among the
grid's direct children, and rows are derived in authored row-major order
with the last row left short.

`structural-rules.ts` decides what the source says, so expansion and
document validation cannot disagree about it: only whitespace and direct
`<Terminal>` panes may appear in a grid, and an empty grid, direct text, a
non-pane element, a control structure that would produce panes, a nested
grid, and a pane written anywhere else are refused. `terminal-grid.ts`
places the panes once `columns` and each `title` are known.

A grid the grammar accepts runs until a terminal provider would be asked
for one. This build installs none, so it refuses there, before any pane
body expands or a default shell starts, and carries the layout it derived
beside the refusal.

Evidence rows TG1-TG4: the new structural suite owns TG1, TG2 and TG4; the
catalog, validation and `xmd syntax` suites own TG3.
Both descriptions lead with what the author can do and show the invocation
to copy. The placement rule, the row-major detail and the title's display
role are the catalog's structured fields and Β§6.21's to state, not a second
reference entry beside the forms.

The frozen catalog entries in `syntax-catalog.test.ts` mirror the two
declarations, so they move with them.
The replaceable seam a terminal grid executes through, before any of the
execution that uses it.

`packages/runtime/terminal.ts` is the contextual provider: `prepare()`
builds the whole composite while it stays hidden, `attach()` shows it once
every pane is ready, and `destroy()` gives the root terminal back. The
request is provider-neutral β€” columns, rows, and the authored panes with
their derived positions β€” and names no terminal, socket, process or window.
Middleware may observe, narrow, refuse, wrap or delegate; presentation
never decides an outcome, so `update()` receives states core has already
settled on.

`packages/core/src/terminal/authority.ts` mints one-use pane claims for one
request's ordinals. A claim admits one interactive operation at a time on
its pane and holds that pane's readiness latch. Two claims do not contend,
which is what lets panes stay interactive together.

`packages/core/src/terminal/pane.ts` is the seam interactive work inside a
pane reaches for, so it runs as that pane's owner instead of competing for
the root foreground lease. Absence means "not in a pane".

Evidence: `packages/runtime/tests/terminal-provider.test.ts`, 11 rows.
`runTerminalGrid()` owns the lifecycle the reader sees: it takes the run's
one foreground-terminal lease, flushes root output, prepares the composite
while it stays hidden, starts every pane concurrently, and attaches only
once every pane has reported a spawn through its claim.

Ordering is the contract. The lease and the composite are both scope-owned,
so success, failure and cancellation all release the terminal and destroy
exactly the composite that was prepared β€” there is no path that skips
teardown. A pane that settles without ever reporting a spawn fails startup
rather than being presented as a running pane. Before the barrier a pane
failure fails the whole grid closed; after it, the failure is that pane's
status and its siblings keep running. Close cancels a live pane as `closed`,
which is not a failed pane, and the grid fails with the first failed pane in
authored order.

`display()` and an `onUpdate` hook complete the provider surface: a pane's
rendered text goes to that pane, and a suite reacts to a state the grid
decided rather than waiting and hoping.

Evidence: `packages/core/tests/terminal-grid.test.ts` (15 rows) and
`packages/runtime/tests/terminal-provider.test.ts` (11 rows). The readiness
barrier row was verified by removing the barrier: it fails without it.
`<Terminal.Grid>` now executes. Each authored pane becomes a concurrent
child of the grid: a self-closing pane runs the host's default shell
through its claim, and a paired pane expands its own content in a scope of
its own.

A pane inherits the bindings, providers, configuration and working
directory visible where the grid was written, and keeps everything it
creates afterwards. Its `<Break>` has no loop to exit, its `<Return>` has
no enclosing value body to claim, and its checked failures settle the pane
rather than reaching the root or a sibling. A pane's rendered text is
displayed in that pane; the grid itself renders `""`, so the root output
holds what surrounds the grid and no pane display at all.

#729's five execution-dependent rows move here, where they assert the
layout against the request the provider actually receives rather than
reading it off a refusal's cause β€” the structural suite keeps the grammar,
placement and pure-layout rows it owns. Moving them was authorized rather
than assumed.

Evidence: 22 rows in `packages/core/tests/terminal-grid.test.ts` and 11 in
`packages/runtime/tests/terminal-provider.test.ts`; the whole Deno core
(350) and runtime (14) suites pass.
`paneWork()` never read the grid element it was handed. Its caller has it,
and a pane's own diagnostics are positioned at the pane.
…#730)

Restores the authority boundary on the `AgentProviders` handshake, and puts
each pane on its own durable child coroutine.

**The boundary.** `TerminalGrids` is routing and only routing: `open()`
answers `unknown` and core throws the answer away, so middleware may
observe, narrow, refuse, wrap or delegate but can never authorize. The
capability that takes the leases, mints pane claims and settles a grid is a
non-contextual authority delivered straight to the registered provider
through a one-use install handshake. Core mints one identity-bearing request
per expansion; presenting a copy, a rebuilt lookalike, a changed request, an
already-presented one, or one from a superseded installation generation
authorizes nothing, and a handler that answers without presenting settles
nothing.

**Durable children.** Each pane is a durable child of the grid, allocated in
authored order, so a pane's identity follows its ordinal rather than the
order the runtime scheduled it in. The layout is recorded in the parent
coroutine before the lease and before any provider is contacted.

**Ordering.** A pane that settles before attach keeps the status it settled
to instead of being overwritten with `running`, and simultaneous startup
failures are selected by authored ordinal rather than by whichever rejected
first. Each pane also expands under a counter of its own, so two concurrent
panes cannot take block identities that depend on which ran first.

`durableSpawn` could not be used: the task it returns is spawned inside the
ephemeral effect's own scope, which closes as the effect resolves, so
awaiting it throws `halted`. It has no call sites or tests upstream.
`durableAll` is the exercised primitive and is what the panes and the grid
child use.

Evidence: 30 rows in `packages/core/tests/terminal-grid.test.ts` and 10 in
`packages/runtime/tests/terminal-provider.test.ts`; core 349, runtime 15.
`durableSpawn` returned a task spawned inside the `ephemeral` effect's own
scope, and that scope closed as the effect resolved β€” so every
`yield* task` threw `halted`. It had no call sites and no tests. It now
starts the child in the routine's own scope, so the task outlives the call
and can be awaited or halted by whoever asked for it.

A retained `Close(cancelled)` meant one thing to the code and two things in
practice. Under `durableRace` and `durableAll` it is a race loser or a
fail-fast sibling, and the same combinator cancels it again β€” those keep
DEC-024 exactly. Under `durableSpawn` nobody cancels it a second time, so
suspending hung the resumed run forever. `runDurableChild` now takes an
explicit `CancelledChildPolicy`, fixed at each combinator's call site and
never chosen by a caller. Resuming uses a new internal
`ReplayIndex.reopen()`, which forgets one coroutine's retained Close while
keeping its yields β€” so the child continues its own history rather than
restarting, and the divergence guard stops reading the remaining effects as
a coroutine continuing past its own close. Neither it nor `disableReplay` is
exported.

DEC-039 records the policy and marks DEC-024's invariant as superseded in
part: it assumed every cancelled child belongs to race or all.

The grid uses the repaired primitive: the whole grid is one durable child,
each pane is its own durable child allocated in authored ordinal order, and
each pane task is observed outside its child β€” so a replayed pane's retained
outcome publishes its status and satisfies the readiness barrier without
entering a body, a shell, or a launcher.

Evidence: 9 rows in `packages/durable-streams/tests/durable-spawn.test.ts`
(lifetime, completed replay, interrupted resume, retained-history
continuation, and both combinators keeping their own policy); 30 rows in
`packages/core/tests/terminal-grid.test.ts`. durable-streams 32, core 349,
runtime 15.
Two decisions exposed while implementing #730, and no implementation.

**DEC-040 β€” a cancelled child records why.** DEC-039's `"resume"` fired on
every retained `Close(cancelled)` under an incomplete parent, which revives
work a caller deliberately halted: the record of a deliberate `task.halt()`
and the record of an interrupted run are the same event. The cancelled close
now carries `cancellation: "caller"` or `"unwound"`, written by whichever
path cancelled the child, and `"resume"` continues only `"unwound"`. A
deliberate stop suspends, which is DEC-024's reproduction argument applied
to a caller instead of a combinator; a legacy record with no reason reads as
`"caller"`, because refusing to revive is the safe direction. The reason is
retained evidence, not authority: nothing outside `runDurableChild` reads
it, and no caller chooses a policy.

Terminal grids need nothing wider. A grid halts its pane tasks at close, so
those retain `"caller"` β€” and the grid child completes, so a resumed run
short-circuits the region and never reaches them. The case that must resume,
an interrupted run, unwinds and retains `"unwound"`.

**TG17 narrows to the resolved layout.** A continuation executes the root the
journal retained; the supplied source is not read, compared or refused
(proved in #722). A grid's authored structure β€” pane count, order, form β€” is
therefore fixed for the life of a journal and cannot differ between runs, so
comparing it compares a value with itself, which is why the refusal never
fired. What a fixed retained document still resolves differently is
`columns` and each `title`, through prop-borne values, since props are not
restored. Those refuse before the lease and before provider contact.

Authored-structure change is a root-definition compatibility question, not a
grid one. Root-definition authority is preserved rather than overridden by a
pre-replay comparison against the current file, and the versioned root
boundary that would refuse a changed source stays open work.
**DEC-040.** A cancelled Close now records why: `cancellation: "caller"`
when the owner halts the task `durableSpawn` returned, `"unwound"` for
anything involuntary. `durableSpawn` resumes only `"unwound"`; a deliberate
stop suspends until the caller's deterministic control flow halts it again,
and a record with no reason reads as `"caller"` so nothing legacy is
revived. `durableAll` and `durableRace` keep DEC-024 whatever the reason
says.

The halt is intercepted without changing the public `Task` surface: the
returned task carries every member the real one defines, copied with its
prototype, and only `halt` is replaced. A proxy cannot do this β€” a task's
members are read-only and non-configurable, so a `get` trap is required to
hand back exactly what the target holds.

The reason had to survive three boundaries that were dropping it: the
protocol parser, the observable copy, and β€” the one that actually mattered β€”
`detachResult`, which froze every cancellation down to `{ status }`.

**TG15.** The harness's `attached` and `pastGrid` signals are now separate,
and a run that expects its grid to complete waits for the sibling *after*
the grid before halting the root at `<Hold />`. That is what leaves a
completed grid child under an incomplete root, which is the only state in
which a completed region can be observed replaying at all. Both a successful
grid and a contained failed one replay their exact retained result with no
provider, pane content, shell or launcher work, and each row asserts the
grid child genuinely recorded a terminal close. No timeouts.

**TG17.** Prop-borne `columns` and `title` change independently against one
fixed retained document β€” the only things a fixed retained root can still
resolve differently β€” and each refuses with zero provider observation. For
supplied-file changes to pane count, order and form, the continuation opens
the retained structure rather than the file's, asserted request-for-request.
The retained record carries every authored pane's ordinal, title, form and
derived position.

`readLayout()` parses totally: the layout object and every pane field, with
missing, extra, mistyped, out-of-position and self-inconsistent records all
refused rather than half-read.

Evidence: durable-spawn 14 rows, terminal-grid 36 rows, structural 13,
provider 10. Packages: durable-streams 33, core 349, runtime 15,
workflow 172.
#730)

**The harness cannot pass a hung replay any more.** `runInterrupted()` had a
1500ms timer racing its signals, so a replay that hung returned a
DocumentRun that looked finished; it also slept a fixed 5ms to let records
land. Both are gone. It now waits only on events the run produced:
`attached`, `pastGrid`, and a new `panesSettled` for the rows that read pane
records β€” a pane's status is published only after its durable child returned,
so counting settled panes is also counting durable pane closes. A replay
that hangs now reaches none of them and hangs the row.

**TG15's failed case is a real contained failure.** A pane that fails before
attachment fails the whole region, so the old document could not both fail
and continue. The failing pane is now a shell that starts, waits for
attachment, and only then exits badly β€” contained as that pane's status,
with the grid settling as failed and the document carrying on. Both runs
capture the printed errors, and the row asserts the replayed run produced
the same ones, reached `PAST_THE_GRID`, and did no provider, pane, shell or
launcher work.

**DEC-040 gets boundary tests where the evidence actually travels.**
`parse.test.ts` round-trips both reasons to the same bytes, keeps a legacy
absence absent, and refuses an unrecognised reason at `$.result.cancellation`.
`retained.test.ts` proves retention and `consumable()` carry both reasons,
leave a legacy absence absent, drop an unrecognised one to the safe default,
and that the reason reaches the replay index. The DEC-040 rows in
`durable-spawn.test.ts` no longer coordinate by delay: a child says when it
is running, and the caller says when it has halted.

**Malformed retained layouts** are covered by replaying a real journal with
only its layout entry replaced β€” a missing member, an extra one, a mistyped
one, a pane out of position, and a record that disagrees with itself. Each
refuses with zero provider observation.

The `durableSpawn` doc comment no longer says every retained cancellation is
an interrupted run.
The DEC-040 block still slept where it meant to synchronise β€” my previous
replacements silently failed to match after the file was reformatted, so
none of them landed. The block is rewritten rather than patched.

Every row now waits on something the run reported. A shared `living()` child
resolves a `started` signal and then suspends, so each row halts or unwinds a
child that is provably live rather than one a delay happened to reach. The
caller resolves `halted` after performing its deliberate halt, so a run is
interrupted only once both facts β€” the deliberate stop and the interruption β€”
are in the journal.

Non-revival is established by control flow rather than by waiting: the
resumed run reaches its own `task.halt()` and says so, and a revived child
would have recorded its mark before the caller could get there. The
legacy-absence row signals once the child has been asked for and the request
returned. No new timeout, and `sleep` stays imported because the lifetime
rows above still use it deliberately.

`retained.test.ts` drops the cast and the row it supported: rejecting an
unrecognised reason is the parser's, proved there, and retention proves only
that `"caller"`, `"unwound"` and a legacy absence survive.
…730)

**`Symbol.asyncDispose` bypassed the deliberate-stop evidence.** The task
`durableSpawn` returns copied it from the original unchanged, so
`await using` β€” or an explicit `task[Symbol.asyncDispose]()` β€” recorded
`cancellation: "unwound"` and the next run revived the child. `halt()` and
the async dispose are the same decision spelled two ways, and both are now
observed. Awaiting a task is not a stop and is left exactly as it was. A
regression disposes a live task, asserts the retained reason is `"caller"`,
resumes the journal, and proves the body is not entered again.

**Reader close no longer halts panes.** It asks them to stop: a pane races
its work against a close signal, settles as `closed`, and records that
outcome as its own. Nothing on the ordinary close path is a caller-cancelled
child any more, so a resumed run restores a pane the reader closed rather
than finding a cancelled child it must either re-enter or wait on forever.
Statuses are published before anything is awaited, so a pane with slow
finalizers cannot delay the outcome the grid already knows.

**Β§6.21 now agrees with architecture.md and TG17.** Partial replay compares
the resolved layout β€” columns and titles. Pane count, order and form come
from the retained root and cannot diverge within a continuation, so a
changed supplied file is ignored in favour of the retained structure;
refusing a changed authored structure is a root-definition boundary this
specification does not yet define.

DEC-040 is unchanged and nothing deliberately stopped is revived.
Implements the amendment at 1833870 without revising it.

**The live handshake.** `composite.closed()` settling now only *proposes* the
boundary. The grid's durable child publishes that proposal and waits; the
owner awaiting the child acknowledges it; and only then does the grid seal
admission and ask its panes to close. The handshake is one live rendezvous β€”
no provider identity, nothing journaled.

**Committing an outcome before the scope finishes unwinding.** A durable
child can now declare its terminal value, and `runDurableChild` records that
value if the child never reaches a normal ending. That is the piece the
contract needs: the grid commits its retained record as the boundary is
crossed, and each pane live at that moment commits `closed`, so a
cancellation arriving while pane and provider finalizers are still running
records what close decided rather than a cancellation. Committing is live
state; it reaches the journal only as the ordinary `Close`. A child that
returns or throws normally overrides it, and a child that never committed
still records the cancellation it actually reached β€” DEC-040 untouched.

Cancellation stays deferred because Effection completes a child's teardown β€”
pane finalizers, provider destroy, terminal restoration, lease release, the
`Close` append and the task's settlement β€” before the halt reaches the owner.

**Pane work stays inside its ordinal-derived durable child.** Reader close
asks the pane to close; it never halts the pane's durable task. The pane
commits `closed`, stops its live nested work through its own scope, and
settles only once that work and its finalizers have settled.

No durable closing marker was added, and completed replay is unchanged.
Implements architecture 1833870 at the owner boundary. The grid's durable
child now runs in a scope of its own β€” a child of the owner's, so it inherits
every context the document runs under, and its own so that tearing the owner
down does not reach it first. A finalizer registered after that scope exists
runs before it is destroyed, and that is the cancellation-deferred await: once
the owner has acknowledged the provider's close proposal, the grid and its panes
finish teardown and append their ordinary completed Close records, and only then
does the cancellation carry on to the parent.

Removes the exported CommitOutcome/durableSpawn(commit) API. Cancellation is
never turned into success inside runDurableChild; durableSpawnIn only says where
a child lives, and grants nothing a caller does not already have.

TG19 proves the ordering with signals alone: a live pane arms a blocking
finalizer, the reader leaves, the finalizer is entered and held, cancellation
begins, the finalizer is released, and the run ends with the composite destroyed,
a completed grid Close retained, the live pane retained as closed β€” and the
sibling after the grid never reached. The continuation then replays past it with
no provider, no pane body and no finalizer re-entered.

TG6 isolates paired-pane sequencing on its own: the reader leaves only once the
pane's second component has run.
The production lifecycle is unchanged. TG19 now reads counters and journal
records rather than a log's shape.

The controlled composite keeps live resource counters β€” composites prepared,
composites attached, shells started β€” each raised when it takes something and
lowered when it gives it back, however it left. TG19 reads them once while a
pane finalizer is blocked, so it knows they went up, and again when the
cancellation has completed, so it knows they came back down.

The harness now says when a blocked finalizer *leaves*, not only when it is
entered: a finalizer that was entered and then cancelled reaches the first hook
and never the second. And after every interrupted run it takes the foreground
lease and gives it back twice β€” the first proves the grid returned it, the
second proves the harness did.

TG19 adds: one grid Close(ok) retaining close: "reader"; two pane Closes, both
completed, with no cancellation recorded at either level; the finalizer entered
and left exactly once; destroy:0 exactly once. The first-attempt claim that no
following sibling ran and the replay tripwires are unchanged.

Every one of these was broken on purpose and re-run: dropping any of the three
counter releases, the deferral, the finalizer-exit hook, or double-logging
destroy fails TG19, and a second holder of the foreground lease is refused.
A `<Session.Launch>` written at the root takes the run's one foreground
terminal, so native UIs are sequential. Inside a `<Terminal>` that would defeat
the point of a grid, where every pane is interactive at the same time.

So core installs a native launcher in each paired pane's scope, closed over that
pane's claim. `<Session.Launch>` finds it by being written there: it is handed
no pane, ordinal, token or mode, and its request, result and retained phases are
the ones a root launch would have. What changes is which terminal answers
`reserve` and `flush` β€” the pane's, through its claim, so two panes do not
contend and one pane admits one live launch at a time. A pane also flushes what
it has rendered before the UI draws over it, which is the root rule in the one
place a pane's text goes.

Readiness now has a boundary a launch can report. `NativeLauncherHandler.launch`
takes the runtime's child-start event as a parameter β€” not a request member, not
a context, not a result β€” and the foreground launcher reports it from the
child's own `spawn` event, before it waits for the exit. The pane launcher
listens and trips its claim's latch there and nowhere else: preparation, the
reservation, the flush and an allocated PID are not a start, and a child that
never ran never reports one. `nativeLaunch()` is unchanged for adapters, which
hear nothing about the start.

Terminal ownership and Agent-session ownership stay independent. Nothing pane-
derived enters the coordinator key, the launch request, the retained record or a
diagnostic, and two panes naming one logical session still contend through the
existing non-waiting coordinator.

No tmux, no new Agent advertisement, and root launch behavior is unchanged.

Evidence: SP1–SP5 in the core launch suite (pane lease, concurrency, readiness,
a failure before the start, one-live-launch-per-pane), FL8–FL9 in the runtime
launcher (the start event, and a child that never starts), and Tier GN over the
checked-in journey `TerminalGridNativeLaunch.test.md` through the whole TestAgent
stack. Removing the pane launcher fails SP1–SP4 and GN1–GN4; never reporting
readiness fails SP1, SP2, SP3 and SP5.
No production change. The pane-scoped launcher, the runtime spawn callback, the
authority boundaries and root-launch behavior are exactly as reviewed.

The checked-in journey is now the 2Γ—2 grid TG5 asks for: three native Agent
sessions and the host's default shell. All four children report their start
before the composite attaches, each waits for its siblings while holding its own
pane, and the row reads back the authored row-major positions and forms.

Four rows added, all driven by signals this run produced:

- GN7: after attachment one native UI exits nonzero while its sibling is live.
  Only that pane fails; the sibling is observed alive on the far side of the
  failure and stops only when the reader leaves; the grid ends on the pane that
  failed, and the close's cancellation is not a second failure.
- GN8: the reader leaves with both launches live. Both are cancelled where they
  stood, neither pane fails, the composite comes down β€” and a root launch after
  the grid, naming a session a pane held, proves both leases came back. Which
  refusal it gets is the proof: not "already holds this run's terminal", not
  "another owner is using session", but the #517 recovery tombstone a cancelled
  native UI leaves behind.
- GN9: a pane admits its next user only once the last one is wholly done, with
  the launch and the prompt that follows it going through the real coordinator.
- GN10: a grid interrupted with a live pane launch, resumed on the same journal.
  It rebuilds the composite, starts the native child on the identity the first
  attempt retained, prepares nothing, and the retained record comes back
  unchanged β€” identity, route, binding and phase alike.

SP5 now proves the pane stays held through both halves: refused while the child
is live, refused again once the child has gone but the lease around it is still
unwinding, admitted only after both. A launch that merely returned showed only
the first.

Two harness repairs. Pane states are read as a set of panes rather than a count
of messages β€” a pane still live when the reader leaves is told twice, once from
the outcome close decided and once from its own settlement, and that is display
rather than a second settlement. And a generated variant is written to a
directory of its own with copies of the scenarios it names, so a killed run
leaves nothing in the repository.
The harness's interrupted branch built a `Result` as an object literal and cast
it. Effection has a constructor for exactly that, so it uses it: no cast, and
the type is the constructor's rather than an assertion's.

Behavior and evidence are unchanged.
An orderly cancellation that finished proves everything a normal return proves,
and must say so. It did not: `ownership.quiesced()` was a statement after
`authority.perform()`, and cancellation unwinds past every statement after the
operation it cancels. A reader closing a terminal grid therefore left every
session its panes had launched carrying a recovery tombstone, and the next owner
was told to recover a session nothing was using.

The launch now runs in a scope the ownership body owns, and the acknowledgement
is that scope's cleanup β€” reached on every path there is, cancellation included.
It brings the launch down deliberately and reads the outcome of doing so, so the
two facts it needs are facts rather than inferences: the native child and its
cleanup settled, and this provider holds no handle for the session. A teardown
that could not prove the child stopped throws out of `destroy()` and is not
quiescence β€” and is still a failure, so it propagates rather than passing
quietly.

Nothing grid-specific reaches the provider. Reader close is the ordinary launch
cancellation path, and this is the ordinary launch cancellation path's rule.

The conservative cases keep their tombstone: a detach that failed or a session
prepared and never handed over leaves a handle, and a child or provider cleanup
that failed leaves the acknowledgement unmade. Cancellation, a released lease, a
PID and elapsed time still prove nothing on their own.

CX1 asserted the behavior this replaces β€” that a cancelled launch stays owned β€”
so it now asserts the accepted one. CX2 is new and holds the other half: a
cleanup that could not finish withholds quiescence, and the record stays active.
GN8 is rebuilt as directed: two pane children held on unresolved operations,
signals from each child's own teardown, teardown proven to finish after both,
and a root launch afterwards on one of the same logical sessions that acquires
ownership and starts β€” receiving neither session-busy nor
session-recovery-required, and reclaiming the root foreground lease as it goes.

Broken on purpose and re-run: acknowledging only on a normal return fails CX1
and GN8; acknowledging without proving the cleanup settled fails CX2, and only
CX2.
A terminal grid may not report a pane settled, admit the next launch into it, or
let the document continue while something a launch started can still act. A PID,
a delivered signal, an attach client going away and an elapsed timeout each
establish none of that.

`packages/runtime/terminal-processes.ts` is what does: the process table,
terminal holders, signal delivery and reachability, behind one host seam whose
own default refuses every question. Refusing is the point β€” "nobody is there"
and "I cannot see" are the two answers a quiescence proof must never confuse, so
a host that installs no observer stops the document rather than reporting a pane
quiet it never looked at. The POSIX handler answers with `ps` and `lsof`; the
`lsof` sweep is the expensive half and grows with the process count, which is
why it is behind the seam rather than inlined.

Two shapes carry the rule. `paneOccupants()` takes the snapshot β€” the child, its
descendants, its process group β€” and must be taken *before* the first signal,
because a killed child's children reparent to init and a later reading names
fewer processes than the launch actually started. `establishQuiescence()` asks
about every one of them and about the terminal, and reports everything still
true rather than the first thing it found.

Nothing here decides policy. It reports; the pane worker finishing a launch and
the provider tearing a grid down decide what the report means.

Tier TP proves the difference between establishing and assuming: a host with no
observer refuses, the POSIX reader finds this process in the real table, a
snapshot read after a kill names nobody, and a pane whose child is gone is still
not free while a descendant runs or anything else holds the terminal.
`select-layout tiled` picks its own column count from the window's dimensions,
so the same four panes are 2Γ—2 in one terminal and 4Γ—1 in another. An authored
`columns` has to be told to tmux rather than asked of it.

`packages/cli/src/terminal/layout.ts` writes the description tmux prints in
`#{window_layout}` and accepts back: a checksum, then a tree of cells sized
row-major from the pane count and the authored column count. A final row with
fewer panes than columns spans the row, because tmux has no empty cells and the
author wrote panes rather than a rectangle.

One thing the string cannot do is place a particular pane β€” tmux fills the
leaves in window-list order and ignores the pane ids they name β€” so authored
order is imposed afterwards by swaps. `swapsInto()` says which, produces none
for an order that is already right, and refuses a window that does not hold a
pane the author wrote instead of putting some other pane there.

Tier TX checks the geometry at four terminal sizes, that the cells tile exactly
with one separator between them, that the checksum tracks the tree, and all
three swap cases.
A pane's initial process is a worker that owns the pane's terminal for the
pane's whole life, and everything it does is asked of it over a socket only this
invocation can reach.

**The channel.** One directory per grid, mode 0700, directly under `$TMPDIR`
because a Unix socket path is capped at 104 bytes and a directory named after a
repository path spends most of that first. Inside it, one socket and one
mode-0600 token per pane, both written before any pane exists, so a worker that
starts finds its socket listening rather than racing it. Admission is the whole
boundary: a connection is admitted when its first frame is a `hello` naming this
pane's ordinal and carrying this pane's token, and a connection that says
anything else, says it late, names another ordinal, or arrives after that pane is
admitted is closed without being answered. The token is single-use because the
worker removes the file as it reads it.

**What crosses it.** The exact argv vector, working directory and environment.
tmux has a command parser, and a command parser is a place where an argument can
become two arguments, or a quote, or a `;`. tmux is told a directory and an
ordinal, and that is all its parser ever sees.

**The worker.** `xmd terminal-worker <ordinal> <dir>` β€” reusing this executable
rather than shipping a second script, which is what makes it work in the
compiled distribution. It is in no command table, so it is in no help output and
no catalog, and naming it grants nothing: without a pane's single-use token
nobody answers. It is dispatched at the entrypoint, before `main()`, and runs
under `run()`, because `main()` binds SIGINT to its own shutdown and would exit
130 on the first `^C` typed into the pane β€” the keystroke the foreground child is
supposed to receive. It ignores SIGINT, SIGQUIT and SIGTSTP itself so the child,
which gets default dispositions across `exec`, is the one interrupted.

**Readiness and settlement, kept apart.** Readiness is the runtime's `spawn`
event and nothing earlier; a missing executable delivers `error` instead of it,
never after it. Settlement is the escalation and sweep that follow β€” a child that
exited on its own may have left descendants in its group or an orphan holding the
terminal, and the pane is not free until neither is true. `exited` is reported
only after that, so the next launch is refused while a sweep that would reach it
is still running.

One hazard the evidence found: the settlement sweeps the process group it is in,
and a worker that was not a session leader would be sweeping whatever started it.
In a pane tmux makes it one β€” but a settlement one signal away from killing the
run that started the grid is not something to leave to the topology being what it
should be, so the sweep now never reaches an ancestor of the worker.

Tier TW proves it with a real worker process over a real socket and no tmux at
all: the modes, the removal, the handshake, three ways of failing it, awkward
argv crossing intact, a child that never starts, one-live-child exclusivity,
display written and never read, and shutdown's final sweep.
One invocation-private server per grid, on its own socket, started with
`-f /dev/null` so a reader's `.tmux.conf` cannot redecide an authored layout. A
pane per authored ordinal, each running that pane's worker β€” tmux's parser sees
an ordinal and a directory and never a launch's argv. Nothing is visible until
`attach()`, which core calls only after every pane has reported a start.

Three clients, kept apart because they answer different questions. The visible
one is the reader's. The control one attaches `-f no-output`, so pane bytes
never travel through this process, and what it reports is how reader detach,
server stop and control loss are told apart β€” an attach client's exit code
cannot tell them apart, being 0 after `detach-client`, 0 after `kill-session`
and 1 after `kill-server`. The workers are not clients at all; they are the
panes.

Teardown is registered before the first command, so a composite that fails
half-built still takes its server down. A detach is *asked for* before anything
is signalled, because a client that leaves restores the terminal and one that is
killed cannot. `stop()` establishes the server pid is unreachable and the server
refuses its session β€” never the socket file's absence, which outlives it.

`probeTmux()` answers the prerequisites before a server exists: a terminal to
divide, and a tmux new enough to divide it as an authored layout needs.

Tier TG runs against a fake server that reproduces the behaviours this code
exists to work around β€” a split inserts its pane into the window list after the
one it split, and a layout string's leaves are filled in window-list order with
the ids in them ignored. What is not faked is the composite: the same layout
string, the same swap decisions, and real control-mode lines from a fixture
process through the same splitter and classifier.

Both halves of the ordering claim were broken on purpose: removing the swaps
fails TG2, and a fake that honours the leaf ids fails TG2 as well β€” so the row
is passing because the composite imposes the order, not because the two happened
to coincide.

Stated plainly, and not claimed here: a fixture client inherits a pipe, so it
cannot restore a terminal it never had. That a real `tmux attach` gives the
reader's terminal back when asked to detach is #726's evidence on real tmux.
**The visible client is not a pane child.** A pane child is settled by sweeping
its process group and its terminal, because a pane's terminal belongs to the
grid. The reader's terminal belongs to the run: everything holding it is XMD,
whatever started XMD, and the rest of XMD's foreground group. A settlement of
that shape aimed at the attach client is a settlement aimed at the document.
`attach-client.ts` owns exactly one process instead β€” asked to detach first,
through tmux, and only then insisted on by pid, with no group, no descendants
and no terminal sweep anywhere in it.

**A successful `kill-server` is not proof.** Teardown now succeeds only once the
recorded server pid is unreachable and the server refuses its own session, and
throws a provider-neutral `TerminalTeardownFailed` when either is still unproved
at the bound. The rule is in the resource finalizer too, so a preparation that
failed halfway is held to it as well.

**Nothing private in a diagnostic.** `TmuxCommandFailed` carries the step's name
and nothing else β€” not the arguments, which hold the socket path, session name,
pane and client identifiers and the worker's private directory, and not stderr,
which tmux writes paths into. A provider's topology stays private on the paths
taken when something goes wrong, which are the paths a diagnostic is read on.

**Closures before removal.** The private directory is removed only after every
accepted socket and every listening server has actually closed β€” counted from
their own `close` events rather than from having been asked.

Three regressions, each broken on purpose and re-run:

- TG11 gives the process table company β€” XMD, its parent, two more in the same
  group, and four holders of the reader's terminal β€” and proves the escalation
  reaches the client's pid alone. Settling it like a pane child fails it.
- TG10 plants markers in the socket, session, pane and client identifiers, the
  worker directory, the arguments and stderr, and proves none reaches the
  surfaced error. Restoring raw arguments fails it.
- TG12 counts real closures at the moment of removal. Not awaiting them fails it.

Also conformed to the repository's rules: `@effectionx/fs` for stat, rm,
readTextFile and writeTextFile, with `node:fs/promises` kept only for `chmod`
and `appendFile`, both adapted through `until`; the client fixture is an
Effection operation; and the newly introduced `as const` assertions are gone in
favour of typed values.
`end()` sent SIGKILL and then discarded what the wait after it established, so
a client still holding the reader's terminal was reported as torn down. The
shared `stop()` resolved successfully on top of that, and the document carried
on.

It now establishes the client is gone, and raises a provider-neutral teardown
failure when it is not β€” so `stop()` rejects and the document stops instead.
`leftWithin()` also looks once more at the boundary itself rather than falling
back on the cached exit event: a client that left during the final interval is
gone, and reporting it as still there would be reporting a stale reading.

The boundary is unchanged and still narrow: detach is asked for through tmux
first, and every signal after that names the exact client pid. Nothing inspects
or signals its process group, its descendants, or the holders of the reader's
terminal β€” on this terminal, each of those is the run itself. The refusal
carries none of the socket, session, client name, argv, environment, terminal
or host message.

TG13 models a client that survives the ask, SIGTERM and SIGKILL: teardown
refuses, the signals delivered are exactly SIGTERM and SIGKILL to the client's
pid, three same-group bystanders and three holders of the reader's terminal are
untouched, and no planted marker reaches the refusal. TG11's successful
escalation is unchanged.

Reinstating the discarded result fails TG13 and leaves TG11 green, which is the
discrimination the two rows are for.
`provider.ts` is where #730's provider-neutral request meets tmux: it prepares
the private channels, the hidden server and the panes, resolves each pane's
worker command before a server exists, and hands core a composite it drives
through its own lifecycle. Nothing tmux-shaped crosses in either direction.

The reader leaving and the host's terminal going away settle the same
`closed()`. That is deliberate: a hangup is not a second teardown path to keep
honest separately, it is the ordinary structured close every other stop uses.
The SIGHUP listener is a resource, so it is removed with the run rather than
answering for a terminal the next one is using.

`host.ts` states which hosts present grids. The Deno entrypoint and the compiled
binary supply `foregroundTerminalGrid()`; every other caller gets
`unsupportedTerminalGrid`, which still opens the installation so a grid is
validated and refused by core rather than being silently absent. Node and Bun
therefore catalog and validate the same grids and open none β€” threaded through
`AgentStack` beside the machine-session assembly, which is the same shape this
repository already uses for "Deno supplies the live one, Node and Bun supply the
one that installs nothing".

architecture.md's terminal-grid inventory row said "implementation unbuilt",
which four layers had made untrue. It now says what each Story built, that the
controlled provider remains the authority for core lifecycle semantics, that
this Story's evidence uses a fake tmux with real tmux behaviour remaining
#726's, and that Node and Bun install no operational provider.

Checkpoint 3's evidence is not in this commit: the Node/Bun refusal row, the
SIGHUP-through-host-installation row, and the CLI regressions are still to come.
taras and others added 15 commits September 7, 2026 04:42
A route's `ExecutableBuildBindingV1` was a release lock: every continuation
held the live observation against the retained one, so upgrading Claude
stranded every session the previous build had opened. It is now immutable
audit evidence β€” written once at first publication, never rewritten, and never
compared with a live build.

What authorizes acting on an existing session is what the installed executable
independently proves: the route's stable adapter protocol, the capability the
work needs, the shape its own probe recognizes, and this host. Which build
accepted the identity first is history, and history is not a capability.

Removes the four live-vs-retained comparisons (attachment, native resume,
concurrent winner, replay) and the `buildDrift` helper they shared. The one
remaining comparison stays: a route and its prepared journal are two durable
accounts of a single observation, so a difference between them is still a
replay that cannot say which session it is resuming.

Tier XR takes a 2.1.261 session to a 2.1.263 build across native resume, ACP
attachment, prepared-only replay, detached replay, a concurrently published
winner, and a build reporting no release β€” each asserting the exact retained
identity, unchanged audit evidence, no allocation or republication, and the
live executable's own path. XR7 and XR8 hold the fail-closed boundary that a
release change is not a protocol change.
A V2 construction route fixes the stable protocol its identity was published
under through its provider, agent and launcher contract. Continuation read that
protocol off whatever adapter happened to be registered under the launcher
instead, so an adapter declaring another protocol β€” beside a host policy that
had proved exactly that protocol, for that capability, in that shape, on this
machine β€” could adopt a session it never constructed. ACP attachment also never
held the route's retained launcher to the live adapter at all.

The pin is compiled in and reachable through no host-supplied dependency, and it
is asked before the executable is observed: which conversation this is was never
a question about a build. Native resume, ACP attachment, concurrent-winner
adoption and both incomplete replay phases now require the live adapter's
launcher and protocol to be the ones the retained contract names, and the
independent admission of the current executable's capability, shape and host
follows it. A refusal observes nothing, allocates nothing, launches, ensures,
prompts and constructs nothing, and leaves the route and the journal exactly as
their first publication left them.

Executable bindings stay what they became: immutable audit evidence, and a
cross-check between two durable accounts of one observation β€” never a live
same-build gate. The source contracts that still described them as one are
reconciled.

Evidence: XR9 takes an injected adapter speaking another protocol, with the host
policy admitting that protocol, through native continuation, ACP attachment,
both replay phases and concurrent-winner adoption, and asserts the executable is
never even observed. XR10 refuses an attachment whose retained launcher is not
the live adapter's. Planting each seam's absence back one at a time fails only
that seam's case, and reading the pin off the adapter fails both cases and
nothing else.
An empty route read settles nothing: a concurrent publication is only
revealed by publishing. Until now a run whose read found no route fell
straight through to observing the executable, allocating an identity and
writing a candidate, and asked whether it could account for the protocol
only after meeting the winner β€” having already acted on behalf of a
session it was never entitled to construct.

The pin is now asked twice from one reading. Prospectively, before any
observation or allocation, against the live adapter's launcher; and
again of the record that actually won, whose route may differ from the
candidate's. The legacy V1 path is untouched.

Reconcile the remaining same-build continuation wording across the
touched source set. A retained binding is immutable audit evidence and
is only ever held to the prepared journal derived from the same
observation, never to a build installed later.
`packages/terminal` is created by this stack's extraction commit, which was
authored while the repository pinned `@effectionx/node` 0.2.4. Main has since
moved to 0.2.5 (#748). A file a commit creates conflicts with nothing, so the
rebase carried the stale pin forward beside a root that now asks for 0.2.5, and
`deno task setup` refused: the frozen lockfile carries no `npm:@effectionx/node@0.2.4`
specifier for a workspace member to resolve through.

The pin now matches the one every other package and the root already use. The
0.2.4 entries that remain in `deno.lock` are `@effectionx/process@0.8.1`'s own
transitive dependency, and they are present on main unchanged. No other
dependency version moves.
Three call sites this stack added kept compiling against surfaces main has
since changed. Each pair of edits touched different lines, so the rebase merged
both sides without a conflict and the mismatch only surfaced under `deno check`.

`xmd plan` used to settle a whole agent stack through a local three-argument
helper. Main replaced that with the authorship-only resolver, which takes two
arguments because the command starts no program, so the terminal-grid installer
this stack threaded through no longer has a parameter to occupy. `xmd run` is
unchanged and still installs it: the installer reaches `installRunAgentStack`
through `resolveAgentStack`, which is the one path with a terminal to give away.

The syntax suite's two terminal-grid rows still named `syntaxCatalog` and the
local `parseCatalog`, which main renamed to `syntaxSymbols` and `parseSymbols`.
Only the names move; both rows assert the same origins, authored forms,
descriptions and reserved-name exclusions they always did.

The launcher suite imported `reap` from the provider-neutral contract after this
stack split the module in two. `reap` ends a POSIX child, so it comes from
`posix-launcher.ts` beside `installForegroundLauncher`, and the contract import
keeps only what a consumer that never reaches a process needs.
The rebase reintroduced fourteen listeners the ownership rule refuses: a
raw `once()` in the POSIX launcher's spawned callback, subscriptions whose
`ensure()` was yielded after them β€” itself a suspension an owner can be
halted in β€” removals hidden behind a helper the pairing cannot be read
from, a self-removing socket handler a cancelled scope never reaches, and
three process signals registered under a computed name.

Every one of them now names its receiver, event and handler on both sides,
with the removal established before the subscription or held by a `finally`
around it. The launcher keeps its inline exit/error race, its complete
reap and a spawned callback that fires exactly once: the handler comes off
when the start arrives and again on every way out, so a cancelled or failed
launch publishes no readiness at all. `usePaneChannels` remembers each
accepted socket with the handler it carries and detaches every one of them
at teardown, including a socket that closed on its own.

FL10 reads the child's own listener counts after delivery, after a startup
failure and after cancellation, then replays `spawn` on it: restore the raw
`once()` and the row fails. TW14 already held the tmux half, and fails if
the new channel detach is removed.

Terminal-grid TG3 expects the catalog version main's Syntax v2 migration
publishes.

Co-Authored-By: Claude <noreply@anthropic.com>
Include the extracted terminal package in SYN47's explicit published payload closure and assert its lifecycle subpath resolves to the staged file. Production manifests already declare the dependency; preserve canonical workspace imports and the no-reexport package boundary.
Normalize Linux ps's no-terminal spelling to the existing process-interface marker without changing real device names or holder proofs. Declare the extracted terminal packages only in the private root test host, keeping provider dependencies out of the neutral domain. Freeze both boundaries with regressions; regenerate the two pnpm workspace links through setup.
Run the native-grid TestAgent harness through a focused subprocess entry that installs the compiler and invokes the real worker. Keep ACPX, controller transport, scenarios, journal, lifecycle, public CLI dispatch coverage, and the 20-second document deadlines intact. Require the GN2 journey to complete successfully as well as checking retained identity.
Disposable POC under packages/terminal-tmux/poc/repl/: a Flux-style action
store, strict read-only Claude/Codex session-file observers, a generic
terminal-convergence algorithm, literal tmux paste delivery, and a
schema-validated report. Deterministic RP1-RP18 evidence runs against fake
panes and synthetic session files in packages/terminal-tmux/tests/repl-poc.test.ts.
The live journey is doubly gated per provider and unexercised until authorized.

No production src, package export, CLI grammar, terminal/core/ACP API,
Workflow, architecture, specification, dependency or lockfile changed.

@github-actions github-actions Bot 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 20 redundant comments. Inline suggestions to remove them below.

if (type === "result") {
return classifyResult(record);
}
// Summaries, system notices and anything else bear on nothing here.

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
// Summaries, system notices and anything else bear on nothing here.

provider: "codex",
identityFromName() {
// Codex names its rollout files by timestamp, not by identity, so the
// identity is only ever read from the `session_meta` record inside.

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
// identity is only ever read from the `session_meta` record inside.

return { outcome: "refused", refusal: open.refusal };
}

// A pane whose generation moved is a replacement, never silently adopted.

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
// A pane whose generation moved is a replacement, never silently adopted.

}

yield* store.dispatch({ type: "TerminalObserved", key, readiness: "ready" });
// The durable intent, before any byte reaches the terminal.

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
// The durable intent, before any byte reaches the terminal.

// The cursor was established against a particular file identity. Re-locating
// finds the current file, but a change of identity since the cursor was set
// is a rotation β€” so the remembered key, not the freshly located one, is
// what `read` enforces.

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 `read` enforces.

yield* store.dispatch({ type: "MessageQueued", key, id: messageId, text, marker });

// Attempt until admitted, then observe until the exact user event and its
// completion appear, or a deadline diagnoses a hang and marks it uncertain.

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
// completion appear, or a deadline diagnoses a hang and marks it uncertain.

const decoder = new TextDecoder();
const tail = decoder.decode(bytes.subarray(cursor));
const segments = tail.split("\n");
// The last segment has no terminating newline: it is the partial tail.

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
// The last segment has no terminating newline: it is the partial tail.

case "UserAccepted":
// Acceptance resolves an attempt in flight and also an attempt a restart
// left uncertain: a later exact user event under the intended identity is
// allowed to settle that uncertainty.

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
// allowed to settle that uncertainty.

trap();
}
// A barrier is an acknowledged round-trip; the yield models that wait
// without changing any structural fact by itself.

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
// without changing any structural fact by itself.

// deno-lint-ignore require-yield
*guardedPaste(guard: PaneSnapshot, delivery: PasteRequest): Operation<GuardOutcome> {
// The recheck and the paste happen with no suspension between them: the
// current state is read and compared, and a matching guard pastes at once.

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
// current state is read and compared, and a matching guard pastes at once.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

PR #779: πŸ§ͺ Prove black-box terminal REPL messaging (#774)

19 files, +5718 / -0

Scope

πŸ”΄ PR has 5718 lines changed. Split into focused PRs.

🟑 5718 lines changed. PRs under 400 receive more thorough review.

Structural

βœ… No structural bloat detected.

Slop

  • packages/terminal-tmux/poc/repl/live-worker.ts:221 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:236 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:344 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:391 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:398 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:468 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:494 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:246 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:299 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:634 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:639 (removed)
  • packages/terminal-tmux/poc/repl/live-worker.ts:658 (removed)
  • packages/terminal-tmux/poc/repl/claude-observer.ts:48 β€” // Summaries, system notices and anything else bear on nothing here.
  • packages/terminal-tmux/poc/repl/codex-observer.ts:26 β€” // identity is only ever read from the session_meta record inside.
  • packages/terminal-tmux/poc/repl/controller.ts:133 β€” // being written is visible before it parses as a complete event.
  • packages/terminal-tmux/poc/repl/controller.ts:266 β€” // Nothing was recorded and nothing sent: reset the head to queued.
  • packages/terminal-tmux/poc/repl/controller.ts:373 β€” // record after an interruption produces no duplicate action or event.
  • packages/terminal-tmux/poc/repl/controller.ts:379 β€” // whose text differs is someone else's turn and settles nothing.
  • packages/terminal-tmux/poc/repl/controller.ts:403 β€” // they are part of the same provider turn.
  • packages/terminal-tmux/poc/repl/delivery.ts:90 β€” // acquiring and registering cannot leave the file or the buffer behind.
  • packages/terminal-tmux/poc/repl/live-worker.ts:67 β€” // %N is stable for one pane and changes when a pane is replaced.
  • packages/terminal-tmux/poc/repl/live-worker.ts:84 β€” // An acknowledged round-trip that changes nothing by itself.
  • packages/terminal-tmux/poc/repl/live-worker.ts:120 β€” // have reached the pane, so the outcome is uncertain rather than pasted.
  • packages/terminal-tmux/poc/repl/observer.ts:160 β€” // shared root is scoped by the header's own project.
  • packages/terminal-tmux/poc/repl/observer.ts:206 β€” // never parsed half-read.
  • packages/terminal-tmux/poc/repl/observer.ts:225 β€” // does not declare an identity header, so it is not a match here.
  • packages/terminal-tmux/poc/repl/observer.ts:233 β€” // metadata refuses rather than being accepted.
  • packages/terminal-tmux/poc/repl/observer.ts:276 β€” // The last segment has no terminating newline: it is the partial tail.
  • packages/terminal-tmux/poc/repl/observer.ts:380 β€” // Closed unconditionally after the read, never inside a finally that yields.
  • packages/terminal-tmux/poc/repl/state.ts:241 β€” // allowed to settle that uncertainty.
  • packages/terminal-tmux/poc/repl/store.ts:147 β€” // read off it: a change to either the schema stops compiling here.
  • packages/terminal-tmux/poc/repl/store.ts:224 β€” // PaneUnavailable and ObserverRefused only need the role to exist.
  • packages/terminal-tmux/tests/fixtures/repl-poc/fake-terminal.ts:149 β€” // current state is read and compared, and a matching guard pastes at once.
  • packages/terminal-tmux/tests/fixtures/repl-poc/fake-terminal.ts:166 β€” // The buffer pasted but the submit key could not be proved sent.

Static Analysis

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

no-unsafe-type-assertion (3): packages/terminal-tmux/poc/repl/report.ts, packages/terminal-tmux/poc/repl/controller.ts, packages/terminal-tmux/poc/repl/live-supervisor.ts
consistent-return (2): packages/terminal-tmux/poc/repl/state.ts, packages/terminal-tmux/poc/repl/observer.ts
no-shadow (1): packages/terminal-tmux/poc/repl/observer.ts
no-underscore-dangle (1): packages/terminal-tmux/poc/repl/store.ts
no-base-to-string (1): packages/terminal-tmux/poc/repl/observer.ts
no-floating-promises (1): packages/terminal-tmux/poc/repl/live-worker.ts

Correctness

No extraneous code patterns detected.

Addresses the Architect's REQUEST CHANGES on f94f501:

- Convergence now samples the provider session file on both sides of the
  acknowledged barrier and the controller re-samples before the guard, so a turn
  opening mid-barrier refuses instead of pasting (new barrier-race regression).
- The live tmux probe reads real pane generation, process, terminal, mode and
  output/client-activity, and honors the paste/submit command outcomes
  (declined/uncertain), never reporting pasted after a failed command.
- Per-provider live grid documents so authorizing one provider cannot launch the
  other; supervisor gate isolation proven offline.
- Observer locates by exact identity AND project from header records only, groups
  Claude output/completion by turn identity, and a missing Claude completion
  yields PROVIDER_EXCLUDED.
- Delivery registers cleanup before acquiring the message file and tmux buffer,
  and removes the buffer; the live child registers teardown before spawn and
  proves close after SIGKILL. RP18 covers a declined partial acquisition.
- The store parses every persisted action's full shape with zod and refuses
  filename/sequence disagreement, gaps, duplicates and unknown types.
- report.schema.json gates PASS per mode on full evidence (head+base SHAs,
  version, hashed identities, acceptance/completion, spent turn, delivery); added
  negative schema rows.

Deterministic RP1-RP18 plus the new discriminating rows pass under Deno, Node and
Bun. No production, specification, or lockfile change.

@github-actions github-actions Bot 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 1 redundant comment. Inline suggestions to remove them below.


const key = identity.id;
// A provider-specific document launches exactly one pane, so the target is
// always pane 0 of the grid.

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
// always pane 0 of the grid.

Second Architect round on PR #779. Six blockers:

1. Provider state (open turn, cursor, event count, physical size incl. a
   partial tail) is sampled across the barrier and again after the buffer is
   prepared, so a turn opening during buffer load or the barrier refuses with
   zero paste.
2. The live tmux boundary is one server-side if-shell recheck-and-paste over an
   injectable command seam with real control-mode activity generations; its
   outcome is acknowledged (pasted/declined/uncertain), never always-pasted.
   Boundary tests drive the live probe through a fake seam.
3. Observer locates by exact identity and project from bounded header reads,
   fails closed on missing project metadata, requires Claude's requestId turn
   identity, and derives PROVIDER_EXCLUDED from an explicit capability, not a
   deadline.
4. Delivery prepares before the final sample and cleans file and buffer on
   success, decline, uncertain and cancellation; the live child proves close
   after SIGKILL; the isolated root is created synchronously and cleanup is read
   only after teardown; provider state is cleaned via the provider's own op.
5. The store validates legal transitions on replay and dispatch, and observation
   deduplicates an event a restart already recorded.
6. An overall aggregator gates PASS on RP1-RP18 plus both live journeys, safe
   counters, no re-execution and verified cleanup; the schema rejects an unsafe,
   uncleaned or re-executed PASS; live reports carry a real head SHA and count
   the materialization turn; the documents invoke the full checked-in validator.

No production, specification, dependency or lockfile path changed.

@github-actions github-actions Bot 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 16 redundant comments. Inline suggestions to remove them below.

recheck.outcome === "unreadable"
? recheck.reason
: "provider-or-pane-changed-before-guard";
// Nothing was recorded and nothing sent: reset the head to queued.

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
// Nothing was recorded and nothing sent: reset the head to queued.

}
// An event a restart already recorded is not dispatched again: the durable
// event carries its own file-and-byte-range key, so re-reading the same
// record after an interruption produces no duplicate action or event.

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
// record after an interruption produces no duplicate action or event.

return;
}
// Assistant output and completion belong to the accepted message only when
// they are part of the same provider turn.

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
// they are part of the same provider turn.

return { outcome: "not-ready", reason: `provider-${second.reason}` };
}
if (second.sample.provider.openTurn) {
// A turn that opened during the barrier β€” the barrier race.

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
// A turn that opened during the barrier β€” the barrier race.

const path = join(request.dir, `${request.id}.msg`);
const buffer = bufferName(request.id);
// Cleanup registered before either resource exists, so a halt between
// acquiring and registering cannot leave the file or the buffer behind.

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
// acquiring and registering cannot leave the file or the buffer behind.

const parsed = parser.classify(record);
if (parsed.kind !== "identity") {
// No identity record before the first relevant/other record: this file
// does not declare an identity header, so it is not a match here.

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
// does not declare an identity header, so it is not a match here.

const decoder = new TextDecoder();
const tail = decoder.decode(bytes.subarray(cursor));
const segments = tail.split("\n");
// The last segment has no terminating newline: it is the partial tail.

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
// The last segment has no terminating newline: it is the partial tail.

case "UserAccepted":
// Acceptance resolves an attempt in flight and also an attempt a restart
// left uncertain: a later exact user event under the intended identity is
// allowed to settle that uncertainty.

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
// allowed to settle that uncertainty.

}
default:
// TerminalObserved, ProviderBusy/Idle, AssistantObserved, ObserverAdvanced,
// PaneUnavailable and ObserverRefused only need the role to exist.

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
// PaneUnavailable and ObserverRefused only need the role to exist.

return { outcome: "declined", reason: "tmux-command-failed" };
}
if (failure === "uncertain") {
// The buffer pasted but the submit key could not be proved sent.

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
// The buffer pasted but the submit key could not be proved sent.

Result-only closeout, no production/architecture/specification change.

- RESULT.md records the VIEW_ONLY verdict and the exact race: a provider turn can
  open between the final combined sample and the single guarded paste, a window
  that is not observable before the paste and cannot be atomically refused, so
  reliable dispatch is not established and tmux delivery stays view-only while
  reliable REPL interaction remains ACP-owned.
- The live-delivery journey is permanently disabled: the supervisor launches no
  agent and spends no turn under any gate, returning the VIEW_ONLY conclusion;
  live-worker keeps only the terminal-boundary evidence. The grid launch
  documents and the two live proof documents are removed.
- The deterministic suite records the VIEW_ONLY conclusion in a schema-valid
  overall report and proves the journey is disabled; it passes under Deno, Node
  and Bun.
- README updated with the conclusion.
@taras taras changed the title πŸ§ͺ Prove black-box terminal REPL messaging (#774) πŸ”’ Terminal REPL POC (#774) β€” VIEW_ONLY evidence Sep 8, 2026

@github-actions github-actions Bot 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 22 redundant comments. Inline suggestions to remove them below.

if (type === "result") {
return supportsCompletion ? classifyResult(record) : { kind: "ignore" };
}
// Summaries, system notices and anything else bear on nothing here.

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
// Summaries, system notices and anything else bear on nothing here.

supportsCompletion: true,
identityFromName() {
// Codex names its rollout files by timestamp, not by identity, so the
// identity is only ever read from the `session_meta` record inside.

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
// identity is only ever read from the `session_meta` record inside.

return { outcome: "refused", refusal: readOut.refusal };
}
// The physical size, including a partial tail no cursor covers, so a record
// being written is visible before it parses as a complete event.

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
// being written is visible before it parses as a complete event.

recheck.outcome === "unreadable"
? recheck.reason
: "provider-or-pane-changed-before-guard";
// Nothing was recorded and nothing sent: reset the head to queued.

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
// Nothing was recorded and nothing sent: reset the head to queued.

}
// An event a restart already recorded is not dispatched again: the durable
// event carries its own file-and-byte-range key, so re-reading the same
// record after an interruption produces no duplicate action or event.

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
// record after an interruption produces no duplicate action or event.

case "UserAccepted":
// Acceptance resolves an attempt in flight and also an attempt a restart
// left uncertain: a later exact user event under the intended identity is
// allowed to settle that uncertainty.

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
// allowed to settle that uncertainty.

});

// The schema is held to the declared action union rather than the union being
// read off it: a change to either the schema stops compiling here.

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
// read off it: a change to either the schema stops compiling here.

}
default:
// TerminalObserved, ProviderBusy/Idle, AssistantObserved, ObserverAdvanced,
// PaneUnavailable and ObserverRefused only need the role to exist.

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
// PaneUnavailable and ObserverRefused only need the role to exist.

// deno-lint-ignore require-yield
*guardedPaste(guard: PaneSnapshot, delivery: PasteRequest): Operation<GuardOutcome> {
// The recheck and the paste happen with no suspension between them: the
// current state is read and compared, and a matching guard pastes at once.

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
// current state is read and compared, and a matching guard pastes at once.

return { outcome: "declined", reason: "tmux-command-failed" };
}
if (failure === "uncertain") {
// The buffer pasted but the submit key could not be proved sent.

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
// The buffer pasted but the submit key could not be proved sent.

@taras

taras commented Sep 10, 2026

Copy link
Copy Markdown
Owner Author

Follow-up graph reconciled

The Product Owner interview is complete. This PR's accepted VIEW_ONLY result and disposable evidence contract are unchanged.

The production follow-ups are now:

#799 presentation POC β†’ #800 shared Markdown presentation
                                  β”œβ†’ #782 read-only Tail
                                  β””β†’ #783 linear ACP REPL

#782 and #783 share normalized entries and presentation, but not transport, live state, or lifecycle. Tail rebuilds a disposable view from provider files; the REPL receives updates from the ACP work it owns. #801 separately gates the PTY and terminal-emulator boundary required by the future first-party Grid provider in #784.

Nothing in this reconciliation authorizes tmux delivery, provider-file interaction, production promotion of this POC, or real-Agent reruns.

@taras
taras force-pushed the agent/issue-755-codex-native-launch branch 3 times, most recently from cc7f3e2 to ba9047f Compare September 11, 2026 01:29
Sign up for free to 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