From 736b909de8a52216a2ff88ff9edade86181d19a1 Mon Sep 17 00:00:00 2001 From: Taras Mankovski <74687+taras@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:41:35 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Define=20interactive=20terminal?= =?UTF-8?q?=20grid=20architecture=20(#717)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- architecture.md | 277 +++++++++++++++++- specs/executable-mdx-spec.md | 330 +++++++++++++++++++++- specs/native-agent-session-launch-spec.md | 204 +++++++++++-- 3 files changed, 777 insertions(+), 34 deletions(-) diff --git a/architecture.md b/architecture.md index b3d26989..97b9ab06 100644 --- a/architecture.md +++ b/architecture.md @@ -108,8 +108,11 @@ Existing documents and code get aligned to this section retroactively. | session materialization | the transition that makes a placement's chosen route and its backend history resumable. ACP-first materialization happens only when the backend reports that it accepted the session's first turn; client-native materialization is the native launch's existing retained construction. Nothing else promotes a placement — not a returning ensure, a first output, a terminal result, a checkpoint token, an error code or a diagnostic | | established session | a placement whose immutable construction route and durable provider or native identity both already exist, and which is therefore validated eagerly: reattached, compared against its retained history, and refused when either is missing or names another conversation | | instruction layer | the provider-native session, system or developer instructions a launch installs before the native UI accepts its first user turn. It is not a user message, and it is not conversation history | -| foreground-terminal lease | the one exclusive claim on the run's terminal a native launch holds. A host with no terminal refuses it, and two launches cannot hold it at once even when they name different sessions | -| native launcher | the host-owned seam that reserves that lease, flushes what the document has produced, spawns one native UI with the terminal inherited, and reports its terminal status and nothing else. It is not `exec`, whose children are piped, captured and journaled | +| foreground-terminal lease | the one exclusive claim on a document execution's foreground experience. A root native launch holds it for one inherited terminal; a terminal grid holds it for one composite presentation. A host with no terminal refuses it, and no second root launch or grid can hold it concurrently | +| terminal grid | one provider-neutral foreground region whose direct terminal panes begin concurrently, remain independently interactive, and settle under one scope after complete provider and pane teardown | +| terminal pane | one authored position in a terminal grid, identified structurally by its grid and ordinal and presented by its authored title. It owns one interactive terminal at a time; a paired pane expands its own document flow and a self-closing pane runs the host's default shell | +| pane-terminal lease | the exclusive claim one live interactive operation holds on one terminal pane. Claims in different panes do not contend; two claims in one pane do. It is minted and validated by the host's terminal authority and grants no authority over an Agent session | +| native launcher | the host-owned seam that reserves the foreground terminal or the current pane terminal, flushes what that terminal has pending, starts one native UI there, and reports its terminal status and nothing else. It is not `exec`, whose children are piped, captured and journaled | | launch request | the frozen, one-use value public launch middleware routes. It carries the facts of one launch and `with()`, and nothing that can settle one. Identity is object identity: a rebuilt look-alike describes the same ask and authorizes none of it | | provider authority | what core delivers to the provider factory it installs, as an argument that factory closes over. It validates the routed request, runs each absent phase once, cross-checks and retains what comes back, and derives the result. There is no reader for one, no context holding one, and no request member carrying one | | session coordinator | the host-built capability that answers who owns one logical agent session right now, across processes. It is passed directly into the provider that needs it and is deliberately not contextual: a decision document middleware could replace is not one. Acquisition never waits | @@ -2757,6 +2760,273 @@ Forwarding, completion, cancellation and teardown belong to the executable block's own Effection scope. Cancelling the block stops the child and the tasks forwarding its output before the block settles. +## Interactive terminal grids + +An executable document can replace its one foreground terminal with one +provider-neutral grid of independently interactive terminal panes: + +```md + + + + + Implement the accepted plan. + + + + + + + Review the implementation against the plan. + + + + + +``` + +`Terminal` names the interactive endpoint the document requires. It does not +name the presentation technology: a tmux integration, another terminal +multiplexer, and a host-native composite UI are providers for the same +contract. A component that elicits values through a terminal UI is a different +abstraction, just as `` is one presentation for ``; it does not +change what an interactive process requires here. + +The grid and its panes are core-owned structural syntax. `` is +paired, requires a positive integer `columns`, and contains at least one direct +`` child. Whitespace may separate those children, but ordinary text, +dynamic control structures, and every other direct element are invalid. A +pane requires a non-empty `title`; titles are display labels and need not be +unique. Its ordinal among the direct children is its structural identity. +Rows are derived in row-major order from the pane count and columns. A paired +pane expands ordinary document flow; a self-closing pane runs the host's +default shell. A nested grid and a `` outside a grid are invalid. +Neither form accepts a provider, executable, shell, layout identifier, or +`as`, and neither renders or returns document content. + +Core ownership is necessary here. An ordinary function component receives its +content as one rendered string, after the effects in that content have already +run; it cannot inspect direct authored children or begin them concurrently. +Core instead validates the complete static grid before any provider contact, +then creates one durable child operation per pane in authored order. Each pane +gets an isolated binding and evaluation scope. It inherits the bindings, +contextual providers, working directory, and configuration visible at the grid +site, while bindings and contextual changes made in one pane remain there and +are discarded when it settles. A pane cannot observe a sibling's writes, and +`` or `` inside a pane cannot transfer control to a construct +outside that pane. Each pane also owns its checked-failure ledger. A checked +failure settles that pane without poisoning the root or a sibling; core alone +observes the pane outcome and applies the grid's settlement rule after close. + +### Terminal authority + +One grid holds the execution's foreground-terminal lease for its whole visible +lifetime. A root `` and a grid therefore contend for the same +host resource; neither can begin while the other holds it. After taking the +lease, core flushes root document output before provider preparation. Output +displayed by a pane is routed to that pane's terminal and is not copied into the +root document output or a capture around the grid. Nested effects keep their own +ordinary result and retention contracts; terminal bytes from a native Agent UI +or default shell are never captured or journaled. The grid renders nothing, and +root document output resumes only after the provider has restored the root +terminal. + +The host owns one non-contextual terminal authority, built and delivered +directly to the installed provider. It validates the exact grid request and +provider installation generation, mints one-use claims for the authored pane +ordinals, and is the only capability that can take or release the root and pane +terminal leases. No context value, prop, binding, provider result, retained +record, diagnostic, or structurally similar request carries that authority. + +The stable contextual terminal API is request routing only. Middleware may +observe, narrow, refuse, wrap, or delegate a one-use request. A handler's +return value is ignored, and answering without delegation authorizes and +settles nothing. Core supplies the one request for the exact expansion; the +provider factory closes over the direct authority and must present that same +request to act. This preserves provider composition without letting a document +or replacement context mint terminal ownership. + +A pane claim grants one interactive terminal at that ordinal, not an Agent +session. Core installs a pane-scoped native launcher that closes over the claim. +`` in that pane consequently reserves, flushes, and launches on +the pane terminal instead of competing for the root lease. Launches in +different panes may run concurrently; two interactive launches in one pane +cannot. Sequential launches in one paired pane remain ordinary composition. +The session coordinator is unchanged and independently authoritative, so two +panes attempting to own the same logical Agent session still contend and one +is refused. The provider starts a self-closing pane's host-configured default +shell under the same kind of pane claim. + +Each claim also closes over one host-owned readiness latch. The pane-scoped +native launcher acknowledges it from the runtime's successful child-spawn event +and before it waits for exit; failed preparation, reservation, or spawn never +acknowledges it. Allocation of a PID and the child's first output are not this +event. The self-closing shell path acknowledges the same boundary. The latch is +not a request member, contextual value, provider return, public event, or +process handle, and acknowledging it twice has no effect. A root launch has no +grid readiness latch. This is how the grid observes successful interactive +start without changing `Session.Launch`'s result or exposing a child process. + +A provider whose pane endpoint is owned by a persistent process routes child +creation through that process. The launch's exact argv vector, working +directory, and environment cross a provider-private authenticated channel; +they never pass through the presentation provider's command language. The pane +owner creates the child with all three standard streams inherited from the pane +terminal, reports the runtime spawn event, and remains only the lifecycle and +display owner. It writes provider display messages to the terminal but never +reads terminal input, so interactive input belongs to the foreground child. +It admits one live launch at a time and releases the pane only after that +launch's observable terminal ownership has been swept. Sequential launches use +the same pane owner and endpoint rather than replacing the pane. + +Provider-specific commands, socket paths, session names, window identifiers, +pane identifiers, attach keys, and process topology remain private inside the +provider closure. They appear in no authored surface, durable identity, result, +or diagnostic. Provider-neutral diagnostics identify a grid expansion and pane +ordinal or title only. A provider may show sanitized pane status, but core owns +the operation result; presentation never decides whether a pane or grid +succeeded. Core sends the provider only the closed presentation states +`starting`, `running`, `succeeded`, `failed`, and `closed`: readiness moves a +pane to `running`, pane settlement supplies `succeeded` or `failed`, and a live +pane cancelled solely by reader close becomes `closed` rather than failed. + +### Atomic presentation and settlement + +The grid runs as one structured scope: + +1. Core validates the whole structural layout, takes the foreground-terminal + lease, and flushes root output. +2. The provider checks its live prerequisites and prepares the entire hidden + composite: every pane endpoint, its supervision, and the default shell where + requested. No grid is attached yet. +3. Core starts the pane child operations concurrently, using deterministic + durable child identities derived from the grid expansion and authored + ordinal. A paired pane begins its document flow and a self-closing pane + begins its shell. +4. A pane is ready only when its interactive child emits the runtime's + successful spawn event. Reserving an endpoint, allocating a process + identifier, or receiving output is not readiness. A child that starts and + exits immediately can be both ready and settled. +5. Only after every pane reaches readiness does the provider attach the one + composite presentation. Any preparation or pane-start failure before this + barrier cancels every pane, awaits complete teardown, discards the hidden + composite, and fails without exposing a partial grid. Agent preparation or + retained route work that occurred before a failed native spawn remains + durable; atomicity covers terminal presentation and lifecycle, not rollback + of earlier provider effects. +6. Once attached, each pane settles independently and keeps its final status + visible while siblings continue. The composite remains present after all + panes settle until the reader closes or leaves it. +7. Closing begins an ordered teardown: prevent new pane launches, cancel live + pane scopes, await every child and finalizer, detach and destroy the exact + provider composite, restore the root terminal, and only then release the + foreground lease and settle the grid. The document never continues while an + observable pane child or provider-owned process can still act through the + grid. + +Parent cancellation follows the same teardown from preparation, readiness, or +the active grid and remains cancellation. A provider or host failure cancels +the whole grid and is the grid's canonical failure. An ordinary pane failure +after attachment is contained as that pane's status and does not cancel its +siblings. When the reader closes the grid, core fails it with the first failed +pane in authored order; cancellation initiated by grid teardown is not a pane +failure. If no pane failed, the grid succeeds and document execution continues. +Where several startup failures are observed together, authored pane order also +selects the reported one. Existing fatal infrastructure and cleanup precedence +still applies, and teardown is attempted for every acquired resource regardless +of the selected result. + +There is no implicit grid timeout. An enclosing run deadline or parent +cancellation bounds it. Before sending the first cancellation signal for a +launch, the provider snapshots the child descendants and every other member of +the pane process group that the host can observe. It establishes that the +child, those descendants, and those group members stopped. Before reporting +that launch settled and admitting a sequential launch, the pane owner also +establishes that no other process holds the pane terminal. Grid teardown repeats +that terminal-holder sweep while the pane owner still keeps the terminal +observable, then establishes that every pane owner, attachment, control client, +and multiplexer server stopped and that the provider's private paths were +removed. A PID, elapsed timeout, disappearance of one attach client, or a +successful signal delivery establishes none of those facts. A failed proof is +a teardown failure and the document does not continue. + +There is one deliberate boundary to that claim. On macOS, a descendant that +creates a new session, closes every descriptor for the pane terminal, and +outlives the parent that connected it to the launch is no longer discoverable +from process ancestry, process group, or terminal holders. The provider does +not claim to find or terminate such a detached daemon. It does prove that +nothing remains in the pane's process group, nothing remains descended from a +child that was alive when cancellation began, and nothing holds the pane +terminal. A launch that needs stronger descendant accountability must retain an +observable ownership mechanism of its own instead of severing all three links. + +### Durability and replay + +A terminal grid is a core-owned structured durable region. Its layout identity +contains the columns and the ordered pane forms and titles, never a provider or +live terminal identifier. Each pane is a deterministic durable child coroutine, +so effects in paired content retain and replay under the same rules they use +outside a grid. A self-closing shell is a terminal child effect that retains +only provider-neutral start and exit status; its executable, argv, environment, +terminal bytes, and conversation history are live-only. + +The completed grid record retains the provider-neutral layout, close kind, and +ordered pane outcomes after the normal secret gate. Completed replay claims the +whole region and returns its retained outcome without installing or contacting +a terminal provider, starting a shell, expanding pane content, acquiring an +Agent session, or launching a native UI. The structured durable boundary owns +that short circuit; a public replay context does not. + +Partial replay first compares the exact authored layout and refuses divergence +before provider work. It rebuilds a fresh provider composite: completed pane +children are restored as settled statuses without re-running their effects, +while incomplete children replay or start their remaining work. An incomplete +`` preserves the prepared/detached identity rules of its own +contract; placing it in a pane neither allocates a replacement session nor +weakens session ownership. An incomplete self-closing shell starts the current +authorized default shell and makes no claim to resume its prior terminal +history. Provider identifiers are recreated live and are never reconciled with +a journal. + +The first production provider uses tmux where the Deno or compiled host has a +foreground terminal and the required tmux capability. It prepares one private +tmux server per grid and starts one persistent pane worker as each pane's +initial process and session leader. Each worker connects over its pane's Unix +socket in an invocation-private mode-0700 directory, authenticates once with a +mode-0600 token that is then removed, and receives only provider lifecycle, +display, and exact child-launch messages. The directory path stays short enough +for the host's Unix-socket limit. A worker runs under Effection's `run()`, not +`main()`: `main()` consumes `SIGINT`, while the worker must remain alive as +terminal job-control signals reach its foreground child. Loss of the root +host's terminal is translated from `SIGHUP` into ordinary grid cancellation so +the hidden server cannot outlive its supervising run. + +The provider creates the panes, installs the exact explicit row-major layout, +and then swaps pane positions into authored order; tmux layout strings do not +honor the pane IDs written in their leaves. One no-output control-mode client +observes server and client events independently of the inherited-stdio visible +attach client. This lets reader detach, control-channel loss, and server stop +remain three different events. The visible client attaches only after the +readiness barrier and is detached before cancellation signals are sent so it +can restore the root terminal. Teardown quiesces every worker, performs the +per-pane terminal-holder proof, closes the private channels, stops the server, +establishes both server-process disappearance and session refusal, and removes +the private directory. + +The proof enumerates terminal holders with `lsof -t`. That establishes the +required boundary but is not the provider contract: on the measured macOS host +it costs about 0.4 seconds per call, and the median exit-to-`exited` handoff grew +from 616 milliseconds at two panes to 1.5 seconds at eight. Terminal-holder +enumeration therefore belongs behind the host process adapter so a cheaper +primitive can replace it without changing readiness, pane reuse, or teardown +semantics. Optimization may remove that latency; it may not admit the next +child before the same quiescence fact is established. + +The same authored syntax remains provider-neutral: a host without that +provider, including Node and Bun until they install one, refuses before pane +start. Tests install a controlled non-tmux provider to prove that core grammar, +authority, lifecycle, settlement, and replay do not depend on tmux behavior. + ## Contextual run configuration Nothing has a timeout by default. Three contextual values bound three different @@ -3802,7 +4072,8 @@ Status is measured against main. | testing harness (``) | runs another document as a real root under a production host profile, authorized by canonical `` alone: declarations installed before the root import, child output displayed progressively and collected only when asked, journal retention selected independently of observation, and the outcome published by the invocation's own terminal through a request public middleware composes around but cannot answer | built on the #454 stack for `host="run"`; the workflow profile and `` are unbuilt, and a host that offers no workflow profile refuses them | | nested run-profile Agent and elicitation declarations | lets one `` declare one child-scoped `` scenario set and one non-delegating `` matcher set; only frozen test data crosses the harness request, the trusted host constructs both providers inside the isolated child, siblings share no session or provider state, ordinary component shadowing remains in force, and the child journal retains only the selected Prompt and Elicit components' ordinary results | built on the #641 stack | | `Config` run deadline / exec default / Fetch default / verbosity | three independently owned contextual timeouts, absent unless configured, each read by exactly one consumer, and contextual verbosity — a boolean that is false unless configured, seeded by the command line and overridable for a lexical subtree, bounding nothing and owning no authority | built on this stack | -| native session launch (`` / `launchAgentSession()`) | prepares one durable coding-agent session from the rendered body of `` and hands the provider's native UI the terminal for that exact session, then continues the document after it exits. The body renders completely first and only what it rendered crosses as the instruction layer; the launch performs no model turn; the run's one foreground-terminal lease is taken before an agent is resolved, so a host with no terminal refuses without probing for an installed CLI. A session is constructed once, by one of two mechanisms, and its create-once construction route says which. Where the provider returns the identity, the ACPX provider creates the session, installs the layer at creation, releases ACP ownership before the spawn, and marks its handle stale so a later `` reattaches. Where the adapter names its own sessions, it allocates the identity inside ownership before any process exists, the native process creates the session under that name from a private mode-0600 instruction file, and ACP creates nothing — the instruction text reaches neither argv nor environment, and the file is removed on success, failure and cancellation alike while ownership is still held. Neither route converts into the other, and which one governs is chosen by the first operation that consumes the placement rather than by the `` that made it: a fresh `` publishes no route and establishes nothing, so a `` nested inside one constructs the session it placed, while a first subscribed `` publishes ACP-first before it ensures and keeps that account even if the turn that follows is never accepted. An established route is validated eagerly by a later ``, and a launch meeting a published ACP-first route refuses before an identity exists. A `` or `` meeting a bound client-allocated route attaches under the route's exact identity; a legacy unbound route or an unavailable attachment capability refuses before a turn and creates no substitute conversation. Phases are retained as `agent_session_launch` records under one expansion identity — `prepared` before ownership is released, then `detached`, then `exited` — so a completed replay launches nothing, a replay holding only `prepared` proves the handoff never began and may still create under the retained identity, and one holding `detached` resumes and never falls back. The public route carries an opaque one-use launch request and answers nothing; authority to run and retain a phase is delivered to the installed provider directly, so neither a returned completion nor a rebuilt request authors a launch. Every operation that can act on an advertised session takes exclusive ownership under one natural key first, through a coordinator the host built and passed in; contention refuses instead of queueing, and an owner that never proved it stopped leaves a recovery tombstone. A host that cannot say who owns a session refuses every advertised operation, and one that cannot say how a session was constructed additionally refuses an agent that names its own — before any provider effect. Every private setup or child-creation failure is normalized to `process-creation-failed` with fixed provider-owned text, carrying no path, argv, environment or host message. No launch path discards persistent provider state. A client-allocated session is bound to one executable build: the build is observed inside ownership before an identity is allocated, the binding is published with the V2 route and retained beside the prepared record, the native child runs the exact observed path in place of the launcher name, and every later create, resume, attachment and incomplete replay reobserves and compares before a process, an ensure or a turn. A `` or `` meeting a bound client-native route attaches to it: it reobserves the build, requires any retained provider arrangement to assert that same conversation, calls ensure with the route identity as `resumeSessionId`, and requires the provider to report that identity before a turn — refusing on missing capability, build drift, missing history or a differing assertion without creating a substitute conversation. ACP runtimes are partitioned by resolved agent command and binding, each handle is closed by the partition that created it, and a bound partition is torn down when its last handle closes. A legacy V1 client-native route keeps exactly the released native-only behavior and never attaches | built on the #517 stack, extended by the #519 and #561 stacks; Deno and the compiled binary assemble the host — coordinator, route store and executable observer — and Node and Bun keep the same advertised names while assembling none of it, so every advertised operation refuses before provider work; `claude` is advertised for native launch after passing the client-allocated gate at Claude Code 2.1.241 on macOS arm64 (#520) and separately for client-native attachment after passing the native-to-ACP marker gate (#561), and Codex remains unadvertised because nothing has run its provider-returned claims against an installed Codex; `Agent.AddDir` is unbuilt | +| terminal grid (`` / ``) | replaces the root foreground terminal with one provider-neutral composite whose statically declared direct panes begin concurrently, stay independently interactive, preserve their final statuses until the reader closes the composite, and tear down completely before document execution continues. A paired pane expands isolated document flow; a self-closing pane runs the host's default shell. The grid owns one foreground-terminal lease, each pane owns a separate pane-terminal lease, and a pane-scoped native launcher lets `` use that pane without weakening the independent Agent session coordinator. Core validates the complete row-major layout before provider contact, attaches only after every pane is ready, contains post-attach pane failures until close, and records the ordered provider-neutral outcomes. Completed replay contacts no terminal or Agent provider; partial replay rebuilds a fresh composite, restores completed panes as statuses, and continues incomplete pane effects under their existing durable identities. Provider commands, sockets, process topology and layout identifiers remain live-only inside the provider closure | defined for #717; #726 proves the persistent tmux pane-worker topology and its observable teardown boundary on macOS; first production provider is tmux in the Deno and compiled foreground hosts; controlled non-tmux provider proves the core contract; implementation unbuilt | +| native session launch (`` / `launchAgentSession()`) | prepares one durable coding-agent session from the rendered body of `` and hands the provider's native UI the terminal for that exact session, then continues the document after it exits. The body renders completely first and only what it rendered crosses as the instruction layer; the launch performs no model turn; at the root it takes the run's foreground-terminal lease before an agent is resolved, while a launch inside `` takes that pane's lease through its pane-scoped native launcher. A host with no applicable terminal refuses without probing for an installed CLI. A session is constructed once, by one of two mechanisms, and its create-once construction route says which. Where the provider returns the identity, the ACPX provider creates the session, installs the layer at creation, releases ACP ownership before the spawn, and marks its handle stale so a later `` reattaches. Where the adapter names its own sessions, it allocates the identity inside ownership before any process exists, the native process creates the session under that name from a private mode-0600 instruction file, and ACP creates nothing — the instruction text reaches neither argv nor environment, and the file is removed on success, failure and cancellation alike while ownership is still held. Neither route converts into the other, and which one governs is chosen by the first operation that consumes the placement rather than by the `` that made it: a fresh `` publishes no route and establishes nothing, so a `` nested inside one constructs the session it placed, while a first subscribed `` publishes ACP-first before it ensures and keeps that account even if the turn that follows is never accepted. An established route is validated eagerly by a later ``, and a launch meeting a published ACP-first route refuses before an identity exists. A `` or `` meeting a bound client-allocated route attaches under the route's exact identity; a legacy unbound route or an unavailable attachment capability refuses before a turn and creates no substitute conversation. Phases are retained as `agent_session_launch` records under one expansion identity — `prepared` before ownership is released, then `detached`, then `exited` — so a completed replay launches nothing, a replay holding only `prepared` proves the handoff never began and may still create under the retained identity, and one holding `detached` resumes and never falls back. The public route carries an opaque one-use launch request and answers nothing; authority to run and retain a phase is delivered to the installed provider directly, so neither a returned completion nor a rebuilt request authors a launch. Every operation that can act on an advertised session takes exclusive ownership under one natural key first, through a coordinator the host built and passed in; contention refuses instead of queueing, and an owner that never proved it stopped leaves a recovery tombstone. A host that cannot say who owns a session refuses every advertised operation, and one that cannot say how a session was constructed additionally refuses an agent that names its own — before any provider effect. Every private setup or child-creation failure is normalized to `process-creation-failed` with fixed provider-owned text, carrying no path, argv, environment or host message. No launch path discards persistent provider state. A client-allocated session is bound to one executable build: the build is observed inside ownership before an identity is allocated, the binding is published with the V2 route and retained beside the prepared record, the native child runs the exact observed path in place of the launcher name, and every later create, resume, attachment and incomplete replay reobserves and compares before a process, an ensure or a turn. A `` or `` meeting a bound client-native route attaches to it: it reobserves the build, requires any retained provider arrangement to assert that same conversation, calls ensure with the route identity as `resumeSessionId`, and requires the provider to report that identity before a turn — refusing on missing capability, build drift, missing history or a differing assertion without creating a substitute conversation. ACP runtimes are partitioned by resolved agent command and binding, each handle is closed by the partition that created it, and a bound partition is torn down when its last handle closes. A legacy V1 client-native route keeps exactly the released native-only behavior and never attaches | built on the #517 stack, extended by the #519 and #561 stacks; Deno and the compiled binary assemble the host — coordinator, route store and executable observer — and Node and Bun keep the same advertised names while assembling none of it, so every advertised operation refuses before provider work; `claude` is advertised for native launch after passing the client-allocated gate at Claude Code 2.1.241 on macOS arm64 (#520) and separately for client-native attachment after passing the native-to-ACP marker gate (#561), and Codex remains unadvertised because nothing has run its provider-returned claims against an installed Codex; `Agent.AddDir` is unbuilt | | `` | performs one XMD-mediated HTTP read through contextual `API.Fetch`, admitting the whole request before transport, and retains the normalized request and the detached response as one `fetch` durable observation; capture decides whether a status is data or a failure, and the trusted host's destination ceiling sits below the component | built on the #456 stack; a generated fragment may name the pinned identity only for a request the trusted host stated exactly, on the #369 stack | | `API.Files` | routes every document filesystem operation to the installed provider, with no host default and structural failure data. Its mandatory semantic operations include `ensureDirectory`, which recursively creates or adopts one directory and returns Unit; separately loaded copies compose through the stable Api name | built on the #227 stack; directory ensure added by #643 | | `` | removes one file the document names, inside the contextual working directory. An ordinary overridable core default with a closed schema of one required non-empty `path`, **self-closing only** — a paired spelling never enters its body, because the component declares its one form and canonical invocation-form dispatch enters that body only for the form the scan recorded, before `Env.cwd` is read and before the provider is reached. Neither the composable `Component.hasContent()` chain nor a method on whatever object a caller handed over takes part. It renders the empty string, declares no `returns` and hands back no receipt, so an ordinary `as` captures that empty string; absence is the same success, so deleting a path twice succeeds twice. One regular file or one final symbolic link goes — the link rather than its target, inside or outside — and every directory is refused, an empty one included. Empty, absolute, lexically escaping and parent-link-escaping paths are refused before any removal, and a printed error names only the path the document wrote. One semantic `API.Files.deleteFile` call and no filesystem access of its own; under a workflow run it is one `workspace_file` effect retaining `{ kind: "deleted" }`. The standard Deno workflow profile admits it to generated XMD as the exact self-closing identity `@executablemd/core#File.Delete`, third in the write table, where it performs that same ordinary effect and contributes no evaluator result | built on the #567 stack | diff --git a/specs/executable-mdx-spec.md b/specs/executable-mdx-spec.md index a6b2435d..f032f538 100644 --- a/specs/executable-mdx-spec.md +++ b/specs/executable-mdx-spec.md @@ -2652,11 +2652,11 @@ A component name is resolved in tiers, and the first tier that answers wins: 1. **structural syntax** — ``, ``, ``, ``, ``, ``/``, ``/``, ``/``, - ``, ``/``. These are the language's own - constructs. They are reserved: a registration cannot claim one, and a - repository file named after one never stands in for it. A structural name - written where its construct gives it no meaning is a printed error, not a - missing component. + ``, ``/``, and + ``/``. These are the language's own constructs. + They are reserved: a registration cannot claim one, and a repository file + named after one never stands in for it. A structural name written where its + construct gives it no meaning is a printed error, not a missing component. 2. **a host claiming the name** — a reserved registration protecting a language or security invariant, or a *declared Markdown component*: exact first-party Markdown a trusted host handed this execution. Both claim the name rather @@ -8497,6 +8497,297 @@ Its skipped body is absence, not a retained decision. Replay of a completed root is unchanged, and a live or partial expansion reads the value that applies to that execution. +### 6.21 Opening concurrent terminal panes: `` and `` + +Use a terminal grid when several interactive tools must remain available at the +same time in one foreground view: + +```md + + + + + Implement the accepted plan. + + + + + + + Review the implementation. + + + + + + + Run the focused verification and repair failures. + + + + + +``` + +The example opens a two-column, two-row foreground grid. The first three panes +expand their document content concurrently and the fourth runs the host's +default shell. A `` keeps the provider's native Agent UI; the +grid does not proxy prompts or replace it with an XMD chat surface. Each pane +can finish while the others keep running, and its final status stays visible +until the reader closes the grid. + +`Terminal` names an interactive terminal endpoint, not tmux. The document asks +for panes and their authored layout; the host chooses the presentation provider. +There is no provider, multiplexer, executable, shell, socket, session, window, +pane-ID, attach-key, or teardown prop. A terminal-native input component may be +a presentation for `` in its own right; it does not change this process +terminal contract. + +#### Forms and props + +`` has exactly one paired form: + +```md + + + + + +``` + +Its closed props schema contains one required `columns` value, which must +resolve to a positive integer. The grid requires at least one pane. Columns do +not have to divide the pane count; rows are derived by placing direct panes in +authored row-major order and leaving unused positions at the end of the last +row. + +`` has two forms and one required prop: + +```md +... + +``` + +`title` must resolve to a non-empty string. It is a display label rather than +identity, so duplicate titles are allowed. A paired pane expands its content as +ordinary sequential document flow. A self-closing pane starts the default +interactive shell configured by the host. The shell choice is live host policy, +not document data. + +Neither element accepts `as`. Neither renders content into the surrounding +document or returns a value. Text that a paired pane renders is displayed in +that pane; it is not concatenated into the root's rendered result. Effects +inside paired content still keep their own ordinary return and durability +contracts. In particular, an executable block retains the process result its +execution profile normally retains, while bytes from a native Agent UI or +default shell are never captured or journaled by the grid. + +#### Structural placement + +Both names are reserved core structural syntax rather than registered or +repository-overridable components. A function component receives rendered +content after its effects have happened and therefore cannot define this +concurrent direct-child boundary. + +Only direct `` children may appear in a grid. Whitespace between panes +is allowed; ordinary Markdown text and every other direct element are refused. +A control structure such as `` or `` cannot dynamically produce the +direct panes. Put control flow inside a paired pane instead. `` outside +a grid, a nested ``, a self-closing grid, paired content on the +self-closing pane form, and a grid with no pane are invalid. + +Syntax validation checks the two names, closed props, authored forms, placement, +and direct-child structure without opening a terminal, looking for tmux, +choosing a shell, resolving an Agent, or installing a provider. Runtime resolves +prop expressions and validates the complete concrete layout before the terminal +provider is contacted. The syntax catalog includes both structural entries and +their two distinct forms even on a host that has no operational grid provider; +catalog inspection performs no availability probe. + +#### Pane scope and output + +Each pane begins as a deterministic child of the grid, in direct-child order. +The children are started concurrently. Scheduling order does not change their +identity or the order in which simultaneous failures are reported. + +A paired pane inherits the bindings, contextual providers, configuration, +working directory, and repository selection visible where the grid is written. +It then gets its own binding and evaluation overlay. A binding or contextual +change made by one pane is visible to later work in that pane and not to any +sibling; all such changes are discarded when the pane settles. Pane content +cannot `` a loop outside the pane or `` from an enclosing +component. A component invoked inside the pane still sees the pane's inherited +and local scope through ordinary content projection. Each pane has its own +checked-failure ledger as well: a checked failure settles that pane and cannot +poison the root or a sibling. Core reads those isolated outcomes when it applies +the grid's close rule; no provider or ordinary component can grant that +containment. + +Before the grid opens, root output is flushed. Display produced by pane content +is routed to that pane and is not copied into the root document output or a +capture around the grid. The grid itself renders `""`. Only after the provider +has torn down the composite and restored the root terminal can a following +sibling render to the root again. + +#### Readiness and the visible lifetime + +Opening a grid is atomic from the reader's perspective: + +1. Core validates the whole layout and acquires the root foreground-terminal + lease. Another root native launch or terminal grid cannot hold it at the same + time. +2. The provider validates its live prerequisites and prepares every terminal + endpoint in a hidden composite. It presents nothing yet. +3. All authored pane children begin concurrently. A self-closing pane starts its + shell. A paired pane expands until it starts its first interactive child, + normally ``. +4. A pane reaches readiness only when that interactive child emits the + runtime's child-spawn event. A paired pane that settles without starting one + fails startup. Merely allocating an endpoint or process identifier, or + receiving the child's first output, is not readiness; an interactive child + that starts and immediately exits is both ready and settled. +5. The provider attaches the complete composite only after every pane is ready. + +Successful child start is acknowledged through a private one-use latch held by +the pane terminal claim. The pane-scoped launcher acknowledges from the +runtime's spawn event and before waiting for exit; a startup error never +acknowledges. The self-closing shell does the same. The latch is absent for a +root launch and appears in no prop, binding, contextual API, public request, +provider return, process result, or durable record. + +When a persistent process owns a pane endpoint, the launcher sends the exact +argv vector, working directory, and environment over the provider's private +authenticated channel to that pane owner. The presentation provider's command +parser never sees those values. The pane owner creates the child with +stdin/stdout/stderr inherited from the pane terminal, forwards the runtime +spawn event to the readiness latch, and never reads terminal input itself. +Provider display text is written to the pane without becoming child input. + +A provider preparation failure, or a pane failure before every pane is ready, +cancels all pane scopes, awaits their finalizers, discards the hidden composite, +restores the root terminal, and fails without showing a partial grid. Effects +that finished before an interactive start failed keep their ordinary durable +records. Grid atomicity is not a transaction that rolls back Agent preparation, +files, commands, or other completed work. + +After attachment, a pane's normal exit or failure changes that pane's visible +status and does not cancel its siblings. Paired content may continue with later +sequential work after one interactive child exits, including another launch on +the same pane. The composite remains visible when all panes have settled. The +reader closes or leaves it to finish the grid. + +The provider receives presentation updates only as `starting`, `running`, +`succeeded`, `failed`, or `closed`. Readiness selects `running`; final pane flow +selects success or failure; a live pane cancelled only because the reader +closed the grid becomes `closed`. These states display core's result and never +author it. + +Close first prevents new pane launches, then cancels live pane scopes, awaits +every child and provider finalizer, destroys the exact composite, restores the +root terminal, and releases the foreground lease. Only then does the element +settle and a later document sibling begin. There is no implicit timeout; parent +cancellation and an enclosing execution deadline use the same complete teardown. + +#### Native launch ownership inside a pane + +Each pane receives a pane-scoped native launcher. A `` there +reserves and flushes that pane terminal rather than the root foreground lease. +Different pane terminals do not contend, so their native UIs may run +concurrently. Two live interactive launches in one pane contend; sequential +launches in it are allowed. + +A launch releases the pane only after its child and every observable process +related to that launch have stopped and no other process holds the pane +terminal. The release precedes the pane's terminal exit status and admission of +a sequential launch. A failure to establish that quiescence fails teardown and +keeps the pane unavailable. + +Terminal ownership does not grant Agent-session ownership. The native session +coordinator keeps its natural provider, agent, and logical-session key. If two +panes launch the same logical Agent session, one owns it and the other is +refused as busy exactly as it would be outside a grid. Grid placement does not +change Agent resolution, construction route, retained identity, instruction +layer, attachment, or cancellation semantics. + +#### Failure result + +Before attachment, simultaneous pane-start failures are selected in authored +pane order after every sibling has been cancelled and torn down. After +attachment, ordinary pane failures are contained as statuses so siblings can +continue. When the reader closes the grid, the first failed pane in authored +order fails the element; cancellation caused solely by closing the grid is not +counted as a failed pane. With no failed pane, close succeeds and the document +continues. + +A provider or host failure cancels the composite and is the grid failure. +Parent cancellation remains cancellation rather than becoming a pane failure. +All acquired resources are finalized even when an earlier failure already +decides the result, and the existing fatal-infrastructure and cleanup precedence +still applies. Before the first cancellation signal, the provider snapshots +every observable descendant of a live child and every member of its pane +process group. It establishes that those processes stopped, and while the pane +owner still makes its terminal observable it establishes that no other process +holds the terminal. It also establishes that its pane owners, attachment, +control client, and multiplexer server stopped. Disappearance of the attach +client, one process ID, a successful signal delivery, or a bounded amount of +time is insufficient proof. + +On a host that exposes only process ancestry, process groups, and open terminal +descriptors, this proof has an exact limit: a descendant that creates a new +session, closes the pane terminal, and outlives its parent has severed every +observable link. The provider makes no claim to discover or terminate that +detached daemon. It does prove that no process remains in a pane process group, +no process remains descended from a child alive when teardown began, and no +process holds a pane terminal. A provider that cannot establish those bounded +facts fails teardown. + +Diagnostics identify the source grid and a provider-neutral pane ordinal or +authored title. They contain no provider command, socket, server, session, +window, pane identifier, executable path, argv, environment, or terminal bytes. + +#### Durability and replay + +The grid is one structured durable region. Its identity includes the resolved +column count and ordered pane forms and titles. Each direct pane receives a +deterministic child-coroutine identity derived from the grid expansion and its +ordinal, never from its title, scheduling order, or provider layout. + +The completed region retains its provider-neutral layout, how it closed, and +the ordered pane outcomes after the ordinary secret gate. Completed replay +claims that whole region and restores its result without contacting a terminal +provider, creating a composite, starting a shell, expanding pane content, +resolving an Agent, taking session ownership, or launching a native UI. + +Partial replay compares the complete resolved layout first and refuses a +changed column count, title, form, count, or order before provider work. It then +builds a new live composite. Completed pane children appear as already-settled +statuses and perform no effects; incomplete children continue from their own +durable records. An incomplete `` keeps the exact +`prepared`/`detached` replay and logical-session identity rules defined by the +native launch specification. An incomplete self-closing pane starts the current +authorized default shell and does not claim continuity of shell process or +terminal history. + +No provider-specific layout identity is retained or reconciled. A tmux-backed +continuation, for example, creates fresh live tmux identifiers even if the prior +attempt used tmux too. The first production provider is installed for the Deno +and compiled foreground hosts when their terminal and tmux prerequisites are +available. It owns one private tmux server per grid and one persistent worker as +each pane's initial process. The workers authenticate once over per-pane Unix +sockets in a short mode-0700 private directory with mode-0600 tokens, run under +Effection's `run()` so the worker does not consume foreground `SIGINT`, and +accept only lifecycle, display, and exact launch messages. The provider uses an +explicit tmux layout and swaps panes into authored row-major order rather than +trusting layout-leaf pane identifiers. A no-output control-mode client observes +reader detach, control loss, and server stop independently from the visible +inherited-stdio attach client. It attaches visibly only after readiness, +detaches that client before sending cancellation signals, and translates root +host `SIGHUP` into structured grid cancellation. + +Node and Bun accept and validate the same syntax but install no provider and +therefore refuse before pane start. A controlled provider that is not tmux +exercises the same core contract in tests. + ## 7. Entry point @@ -10438,6 +10729,33 @@ Each row names the derivation it kills. | AF24 | A Session pins the exact value it was issued | A fresh `` calls `session()` once and hands the same object — by identity, not by key — to every `` nested inside it. A provider decides whether a session may be acted on by that identity, so a rebuilt look-alike is a value nobody issued | | AF25 | A fresh Session performs no provider effect | A self-closing `` places one and renders nothing: no prompt is started, and nothing about the placement appears in the document where the element stood | +### Tier TG — Terminal grids (§6.21) + +Core lifecycle rows use a controlled provider that is not tmux. Production +adapter rows use fake tmux processes and exact invocation-private handles; no +test derives a core result from a provider identifier. + +| # | Test | Verify | +|---|------|--------| +| TG1 | Frozen grammar | `Terminal.Grid` accepts only paired form with a positive integer `columns`; `Terminal` accepts paired and self-closing forms with a non-empty `title`; both reject unknown props and `as` | +| TG2 | Structural placement | An empty grid, direct text or non-pane element, a dynamically produced direct pane, a nested grid, and a pane outside a grid are refused before a provider call or body effect; whitespace between direct panes is inert | +| TG3 | Catalog and validation are inert | Both reserved entries and exact forms appear under structural syntax on every runtime; syntax and document validation contact no terminal provider, tmux, shell, Agent registry, or session coordinator | +| TG4 | Row-major layout | One through five authored panes under two and three columns produce the exact derived positions, keep duplicate titles, and derive identity from ordinal rather than title or scheduling | +| TG5 | Representative 2×2 journey | Three controlled native Agent sessions and one controlled default shell all start before the grid attaches, remain concurrently interactive, and use the authored row-major positions | +| TG6 | Isolated pane scopes | Every pane inherits the grid site's values, cwd, repository selection and providers; one pane's new bindings and contextual changes reach later work in that pane only, and its `Break` or `Return` cannot escape the pane | +| TG7 | Pane output | Rendered pane text reaches only that pane and the grid renders `""`; a surrounding capture gets no pane display; nested executable effects retain their ordinary results; native UI and shell bytes enter no capture, process journal or transcript | +| TG8 | Readiness barrier | Endpoint allocation, PID allocation, preparation, route publication, detach and first output are not ready; the runtime child-spawn event is. A paired pane that settles without one fails startup, and a child that spawns then exits immediately is ready and settled | +| TG9 | Atomic startup failure | Each provider-preparation position and each authored pane start can fail; no composite attaches, all started siblings and finalizers settle, completed earlier effects remain durable, the root terminal is restored, and simultaneous pane failures report the first authored ordinal | +| TG10 | Independent settlement | After attach, one pane can exit successfully or fail while siblings remain live and usable; its status stays visible. Closing a grid with failed panes reports the first failed authored ordinal, while teardown cancellation itself does not create a pane failure | +| TG11 | Terminal versus session ownership | Distinct pane leases permit concurrent native launches, one pane refuses overlapping launches, and a sequential launch is admitted only after the previous child, its observable descendants and group members, and every other holder of that pane terminal are gone; two panes naming one logical Agent session still contend through the unchanged non-waiting coordinator | +| TG12 | Reader close | Close prevents a later launch, cancels every live pane scope, awaits each child, shell and provider finalizer, destroys the exact composite, restores the root terminal, releases the foreground lease, and only then starts the following document sibling | +| TG13 | Cancellation and provider failure | Parent cancellation during prepare, readiness and active presentation follows complete teardown and remains cancellation; an active provider failure cancels every pane and fails the grid; cleanup is attempted for all resources under existing failure precedence | +| TG14 | Bounded teardown proof | Before cancellation signals, the provider snapshots the live child's observable descendants and pane process-group members; before pane reuse and again before its worker exits it proves those processes and all other terminal holders gone. Grid teardown also proves every worker, attachment, control client and server gone and removes private paths. An attach exit, one PID, signal delivery or timeout is not proof. A descendant that already started a new session, closed the pane terminal and lost its parent is recorded as outside the host's observable boundary rather than falsely claimed stopped | +| TG15 | Completed replay | A completed successful or failed grid restores its exact result while contacting no terminal provider, shell, Agent provider, coordinator, pane content or native launcher | +| TG16 | Partial replay | Exact layout rebuilds a fresh provider composite; completed pane children appear settled without effects, incomplete paired children follow their durable records, incomplete native launches preserve prepared/detached session identity, and an incomplete shell starts current host policy without terminal-history continuity | +| TG17 | Replay divergence and retained shape | A changed column count, pane count, order, form or title refuses before provider work; retained layout, close kind and pane outcomes contain no provider command, socket, process, session, window or pane identifier, path, argv, environment or terminal bytes | +| TG18 | Provider neutrality | The controlled non-tmux provider passes TG1–TG17; the tmux adapter prepares one hidden invocation-private server with authenticated persistent pane workers, transmits exact child creation outside tmux parsing, applies explicit row-major layout, distinguishes visible detach from control loss and server stop, attaches only after runtime spawn readiness, and satisfies TG14 without leaking provider identifiers; Node and Bun validate the same document and refuse before pane start with no provider installed | + ### Tier CR — Component registration and resolution | # | Test | Verify | @@ -10476,7 +10794,7 @@ so the include-boundary rows are the same on every host. Defined in §5.3. | # | Test | Verify | |---|------|--------| | SY1/SY2 | Versioned shape | `version` is 1, the categories are the fixed tuple, and one structural, one registered and one repository entry appear together | -| SY3/SY4 | Structural vocabulary | The declarations are exactly the reserved names, each with authored forms and a description; `Let`, `Content`, `Else`, `Break`, `Answers` and `Answer` carry the frozen forms, and `as` applies to `Let` and `Each` alone | +| SY3/SY4 | Structural vocabulary | The declarations are exactly the reserved names, each with authored forms and a description; `Let`, `Content`, `Else`, `Break`, `Answers`, `Answer`, `Terminal.Grid` and `Terminal` carry the frozen forms, and `as` applies to `Let` and `Each` alone | | SY5 | Structural stays structural | A repository file named after a construct never moves it out of the structural category | | SY6/SY7 | Repository mapping | Direct `.md`/`.ts`, direct `index`, nested dotted and nested index paths describe names; a lowercase segment, an empty stem, a dotted stem and a dotted directory describe none, and the inversion is held to the single-segment grammar directly | | SY7c | Pruning | A lower-case, hidden or dotted directory is never read — at the top level or deeper — while the direct, nested and index candidates beside it stay discoverable; every skipped directory throws if it is read, and the recorded reads name only the ones a name reaches | diff --git a/specs/native-agent-session-launch-spec.md b/specs/native-agent-session-launch-spec.md index e7e07677..74c42270 100644 --- a/specs/native-agent-session-launch-spec.md +++ b/specs/native-agent-session-launch-spec.md @@ -439,10 +439,12 @@ Given `xmd AGENTS.md#Implementor`: 3. Target projection excludes sibling roles. 4. XMD expands the target and renders `Session.Launch` content completely. 5. File reads, captures, parsing, and deterministic evaluation finish or fail. -6. `Session.Launch` takes the run's one foreground-terminal lease. A host with - no terminal refuses here — before an agent is resolved, so learning that - this invocation cannot launch anything costs no availability probe. -7. The host flushes what the document has produced, so the native UI does not +6. `Session.Launch` takes its applicable terminal lease. At the document root + this is the run's foreground-terminal lease; inside `` it is that + pane's lease through the pane-scoped native launcher. A host with no + applicable terminal refuses here — before an agent is resolved, so learning + that this invocation cannot launch anything costs no availability probe. +7. The host flushes what that terminal has pending, so the native UI does not open over half-written output. 8. The provider resolves the logical Agent and Session against the contextual cwd, and takes exclusive ownership of that session. @@ -502,25 +504,26 @@ owner to release — what has to be settled first is which conversation this is: From there both rejoin: -13. The provider spawns the native UI as a foreground child with the terminal - inherited — resuming the native session ID for a provider-returned adapter, - and for a client-allocated one creating it under the allocated identity from - the private file, or resuming it by the same name when the route already - named it. +13. The provider spawns the native UI as an interactive child with the selected + root or pane terminal inherited — resuming the native session ID for a + provider-returned adapter, and for a client-allocated one creating it under + the allocated identity from the private file, or resuming it by the same + name when the route already named it. 14. `Session.Launch` suspends while the child runs. 15. The child handles prompts, tools, permission dialogs, rendering, and native transcript persistence directly. 16. When the child exits, XMD records its terminal outcome, removes the private file and its directory while ownership is still held, and releases the - terminal lease. The route and the retained phases stay: they are what the - next invocation resumes from. + selected terminal lease. The route and the retained phases stay: they are + what the next invocation resumes from. 17. Later Agent work depends on the route again. On an `acp-first` session it lazily reattaches through ACP to the same provider session. On a bound `client-native` route advertised for attachment, `` and `` follow *ACP attachment on a bound route* below and join the same provider conversation. A legacy unbound route or an unavailable attachment capability refuses before a turn and creates no substitute conversation. -18. Document execution continues after `Session.Launch`. +18. Execution continues after `Session.Launch`: in root document flow outside a + grid, or in the sequential flow of its paired pane inside one. An enclosing `` or `` resolves by its own contract, before anything inside it runs, so a document that wraps a launch has already reached @@ -711,6 +714,88 @@ refuses an advertised agent that names its own sessions, on the same terms and before any provider effect; an agent whose provider returns the identity is unaffected, because it constructs nothing a route governs. +### Terminal-grid composition + +Terminal ownership and Agent-session ownership remain independent when a launch +is written inside ``: + +```text +grid foreground lease + ├─ pane 0 lease ─ native launch for logical session A + ├─ pane 1 lease ─ native launch for logical session B + └─ pane 2 lease ─ default shell + +session coordinator + ├─ natural key for logical session A + └─ natural key for logical session B +``` + +The grid owns the root foreground-terminal lease. The terminal authority mints +one private one-use claim per authored pane ordinal, and core installs a native +launcher in each pane scope that closes over that claim. `Session.Launch` uses +the launcher already in scope; it receives no pane prop, token, identifier, or +mode. The launcher validates the claim through the host's direct terminal +authority and reserves that pane for the launch. A claim from another grid, +provider installation generation, pane ordinal, or completed invocation +authorizes nothing. + +Different pane claims do not contend, so native launches in different panes can +hold their terminals concurrently. One pane remains exclusive: a second launch +cannot begin while the first is live there, and sequential launches work after +the first releases it. Release requires the child, its observable descendants +and process-group members, and every other holder of that pane terminal to be +gone; the pane remains busy if the launcher cannot establish those facts. A +root launch and a terminal grid contend for the root foreground lease, so +neither can overlap the other. + +None of that changes the coordinator key or acquisition. Two panes naming the +same provider, agent, and logical session still ask for one natural-key owner; +one succeeds and the other receives `session-busy` without waiting. Two distinct +sessions may be owned concurrently. A terminal claim grants no permission to +ensure, detach, create, resume, prompt, or attach to an Agent session, and a +session lease grants no terminal. + +The pane-scoped launcher keeps the same launch request and provider authority +division as the root launcher. Public middleware can route or refuse a request +but cannot settle it, replace the pane, or mint a launch. Provider-specific grid +or pane identities never enter the `AgentLaunchRequest`, terminal result, +`agent_session_launch` record, construction route, ownership key, diagnostic, +or private instruction file. + +The grid's readiness barrier observes the launch only at the existing successful +interactive-child start boundary. Session preparation, route publication, +private-file creation, and detach do not make a pane ready. If spawn fails, the +launch keeps the durable phases its contract already completed, fails the pane's +startup, and participates in the grid's atomic hidden teardown. The grid does +not roll those phases back. A child that successfully starts and exits before +the other panes become ready has nevertheless crossed readiness and retains its +ordinary exit outcome. + +The pane claim carries a private one-use readiness latch. The native launcher +acknowledges it from the runtime's child-spawn event and before waiting for +exit; allocating a PID or observing output is not readiness, and a startup error +never acknowledges. A root launch carries no such latch. It is not added to +`AgentLaunchRequest`, `AgentLaunchResult`, the public Agent Api, a retained +launch phase, or a process handle, so readiness composition changes neither the +launch's authored nor durable contract. + +Under the tmux provider the pane-scoped launcher sends exact argv, cwd, and +environment values over a private authenticated socket to the persistent pane +worker. The worker, not a tmux command line, creates the native child with all +three standard streams inherited from the pane terminal. It forwards the spawn +event, writes pane display without reading input, and refuses a concurrent +launch. It uses Effection's `run()` rather than `main()` so Effection does not +convert terminal `SIGINT` into worker exit 130 while the foreground child is +handling job control. + +After the grid is visible, a nonzero native exit fails its pane flow but does +not cancel sibling panes. Core keeps that failure as the pane's status and +selects the first failed pane in authored order when the reader closes the grid. +Grid-initiated close cancels a still-live launch through the ordinary launch +cancellation path, awaits session quiescence and native-child teardown, and does +not reclassify that cancellation as an independent pane failure. Parent +cancellation remains parent cancellation for the entire grid. + ## Construction route Ownership and construction answer different questions, and this contract keeps @@ -822,14 +907,17 @@ key, the route key and provider work. It is never resolved a second time inside ownership, because a registry free to answer differently would name a different session than the one this operation prepared. -V1 also holds one foreground-terminal lease for the root CLI execution. Two -native launches cannot concurrently own the same terminal, even when they name -different sessions. Sequential launches are ordinary document composition. - -Cancellation interrupts the native foreground process, establishes that it can -no longer execute or hold the terminal, restores terminal state, and runs every -provider finalizer. A process that ignores the initial interruption is -terminated according to the host process adapter's bounded shutdown policy. +At the root, V1 holds the foreground-terminal lease for the CLI execution. In a +terminal grid, the grid holds that root lease and a launch holds only its current +pane lease. Two launches cannot concurrently own the same root or pane terminal, +even when they name different sessions. Launches on distinct panes may run +concurrently, and sequential launches on one terminal are ordinary composition. + +Cancellation interrupts the native interactive process, establishes that it can +no longer execute or hold its root or pane terminal, restores that terminal's +state, and runs every provider finalizer. A process that ignores the initial +interruption is terminated according to the host process adapter's bounded +shutdown policy. The adapter attempts to collect the native exit status, but a runtime-retained defunct PID or a lost exit event is not live process ownership. After a fatal signal was accepted, or the process was already absent, bounded settlement may @@ -953,6 +1041,14 @@ reuses the recorded native session, and continues at the first incomplete phase. was interrupted reattaches the native UI to that same session; it never creates a replacement or reconstructs state from a transcript. +When the launch is a pane child, completed replay of the enclosing completed +grid claims the whole structured region before this operation is reached, so it +also contacts nothing. Partial grid replay restores a completed launch as a +settled pane status. An incomplete launch is reached under a newly created live +pane terminal and follows the same phase rules above; neither the new provider +layout nor the pane ordinal changes its retained launch or logical-session +identity. + Those are operation/runtime replay semantics: they define how an execution behaves when an embedder, a test, or a future retained execution host supplies the launch's durable history again. They do not create a public continuation @@ -1037,6 +1133,25 @@ hosts can install a controlled launcher that needs no terminal; a host that installs none — `xmd test`, document inspection, an embedder — refuses every launch, which is what keeps help and inspection free of any of this. +The Deno source host and compiled binary install the first terminal-grid +provider for an ordinary foreground run when a TTY and the required tmux +capability are available. The provider prepares one invocation-private tmux +server and one persistent initial worker per pane. Its per-pane sockets live in +a short mode-0700 directory and admit one connection through a mode-0600 token +that is removed after authentication. It keeps tmux commands, socket paths, +tokens, session, window, pane, process, and control-client identifiers private. +It derives an explicit layout and swaps panes into authored row-major order, +because tmux does not honor pane IDs embedded in layout leaves. Its visible +inherited-stdio client and no-output control client remain distinct, and loss of +the root terminal becomes structured cancellation. A missing prerequisite +refuses the grid before pane start. + +Node and Bun validate and catalog the same `` and `` +syntax but install no grid provider. Installing a grid provider advertises no +new Agent, launch adapter, session-construction mechanism, or attachment +capability; each `` still passes the existing independent +advertisement gates. + `` installs a controlled launcher for its own body, because a scripted agent's native UI does not exist and the terminal a host would hand it belongs to whoever is running the tests. That is what lets an authored @@ -1069,6 +1184,20 @@ launcher records the request, claims a known provider-native session ID, waits on a test-controlled operation, and exits with a selected status. It never starts Claude, Codex, or a model. +Terminal-grid tests additionally install a controlled provider that is not +tmux. It exposes readiness, independent pane settlement, reader close, provider +failure, parent cancellation, and teardown completion as test-controlled +operations while using the same core terminal authority and pane-scoped native +launchers. Separate tmux integration evidence exercises the production adapter; +core semantics are not inferred from tmux identifiers or process behavior. The +tmux evidence covers exact argv over private IPC, the runtime spawn boundary, +display that cannot become child input, real terminal job control, explicit +layout, atomic attach, independent close signals, cancellation phases, and the +bounded descendant, process-group, and terminal-holder teardown proof. It also +exercises pane reuse after terminal-holder quiescence; a process that has +already started a new session, closed the terminal, and lost its parent is +recorded as outside the observable host boundary. + Focused tests prove: 1. help discovers roles and performs no preparation or launch; @@ -1119,7 +1248,12 @@ Focused tests prove: what it answers with before acknowledging quiescence, and a close that failed releases nothing and withholds quiescence; and 23. a canonical version parse accepts exactly one matching line, and refuses - zero or several without repeating the output. + zero or several without repeating the output; and +24. launches on distinct pane terminals run concurrently while launches in one + pane remain exclusive, the same logical Agent session still contends across + panes, pane readiness occurs only after successful native-child start, grid + close awaits launch cancellation and session quiescence, and completed and + partial grid replay preserve the launch's existing identity rules. The authored half of this is one executable Markdown document, `packages/test-agent/src/NativeSessionLaunch.test.md`, run whole. It authors the @@ -1198,7 +1332,9 @@ in a released unbound form and a bound one; the host-owned executable observer and the build binding it produces; ACP attachment to a bound client-native session under its exact retained identity, through runtime partitions keyed by agent command and build; -an inherited-terminal foreground child with cancellation and bounded reaping; +an inherited root- or pane-terminal interactive child with cancellation and +bounded reaping; composition with the terminal grid's independent pane leases +without changing session ownership or durable launch identity; and the controlled TestAgent fixture that proves all of it without starting a model. @@ -1236,9 +1372,10 @@ Additional provider adapters land independently against the same core contract. An adapter that cannot prove instruction injection before the first user turn stays unsupported rather than weakening `Session.Launch` semantics. -Native UI event mirroring, XMD-rendered interactive chat, simultaneous terminal -sessions, automatic nested `AGENTS.md` discovery, bootstrap model turns, and -workflow role scheduling are outside this contract. +Native UI event mirroring, XMD-rendered interactive chat, simultaneous root +foreground sessions outside a terminal grid, automatic nested `AGENTS.md` +discovery, bootstrap model turns, and workflow role scheduling are outside this +contract. ## Structural checklist @@ -1294,6 +1431,23 @@ Implementation review checks these frozen invariants: moment its handle exists; a cancellation observes and settles an ensure it already started before quiescence; quiescence is answered from that account; and a close that failed releases nothing and acknowledges none. +24. A terminal grid holds the root foreground lease while each launch holds only + its current pane lease; distinct panes do not contend for terminal ownership, + and one pane remains exclusive until observable processes and terminal + holders from the prior launch are gone. +25. Pane terminal ownership never replaces or weakens natural-key Agent-session + ownership, so two panes naming one session still contend without waiting. +26. A pane is ready only at the runtime child-spawn event; preparation, PID + allocation, route publication, detach, private-file creation and first + output are not readiness, and a failed spawn rolls none of them back. +27. Grid cancellation reaches every live launch, awaits its child teardown and + session quiescence, and exposes no provider-specific layout identity in an + authored, durable, result, or diagnostic surface. +28. The tmux provider creates native children only through authenticated + persistent pane workers, preserves byte-exact argv outside tmux parsing, + keeps worker display out of child input, distinguishes reader detach from + control loss and server stop, and proves the bounded process and terminal + teardown before pane reuse and grid settlement. Item 12 is the 2026-08-20 architecture amendment. ACPX fixes `systemPrompt` at session creation, while native turns are not authoritative in its cached