@timurproko/a1 0.2.7-dev.767 → 0.2.7-dev.779

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +30 -0
  2. package/bin/cli.js +4 -0
  3. package/bin/tab-launch-context.d.ts +3 -0
  4. package/bin/tab-launch-context.js +52 -3
  5. package/bin/ui.js +6 -3
  6. package/dist/app/session-shell/session-shell-root.d.ts +2 -1
  7. package/dist/app/session-shell/session-shell-root.js +2 -2
  8. package/dist/app/session-shell/session-shell.js +4 -8
  9. package/dist/app/session-shell/shell-host.d.ts +2 -0
  10. package/dist/app/session-shell/shell-host.js +20 -1
  11. package/dist/cli/dispatch.d.ts +11 -1
  12. package/dist/cli/dispatch.js +79 -5
  13. package/dist/cli/tabs-request.d.ts +7 -0
  14. package/dist/cli/tabs-request.js +1 -0
  15. package/dist/cli/tabs.d.ts +14 -0
  16. package/dist/cli/tabs.js +140 -0
  17. package/dist/composition/owned-ui.d.ts +8 -2
  18. package/dist/composition/owned-ui.js +109 -12
  19. package/dist/features/launch/index.d.ts +1 -1
  20. package/dist/features/launch/intent.d.ts +3 -1
  21. package/dist/features/launch/intent.js +7 -4
  22. package/dist/features/resident-tab/auto-name.d.ts +11 -0
  23. package/dist/features/resident-tab/auto-name.js +42 -0
  24. package/dist/features/resident-tab/index.d.ts +3 -0
  25. package/dist/features/resident-tab/index.js +3 -0
  26. package/dist/features/resident-tab/shortcuts.d.ts +10 -0
  27. package/dist/features/resident-tab/shortcuts.js +35 -0
  28. package/dist/features/resident-tab/status-deriver.d.ts +9 -0
  29. package/dist/features/resident-tab/status-deriver.js +15 -0
  30. package/dist/foundation/launch-context/index.d.ts +4 -0
  31. package/dist/foundation/launch-context/index.js +10 -0
  32. package/dist/foundation/release/bootstrap.d.ts +4 -2
  33. package/dist/foundation/release/bootstrap.js +11 -8
  34. package/dist/foundation/release/cohort-state.d.ts +1 -1
  35. package/dist/foundation/release/cohort-state.js +1 -1
  36. package/dist/foundation/release/release-gc.d.ts +3 -0
  37. package/dist/foundation/release/release-gc.js +44 -8
  38. package/dist/foundation/resident-tabs/bounded-host-command.d.ts +16 -0
  39. package/dist/foundation/resident-tabs/bounded-host-command.js +35 -0
  40. package/dist/foundation/resident-tabs/bridge-client.d.ts +18 -3
  41. package/dist/foundation/resident-tabs/bridge-client.js +87 -7
  42. package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
  43. package/dist/foundation/resident-tabs/doctor.js +166 -0
  44. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +2 -1
  45. package/dist/foundation/resident-tabs/ensure-terminal-host.js +33 -7
  46. package/dist/foundation/resident-tabs/index.d.ts +7 -1
  47. package/dist/foundation/resident-tabs/index.js +4 -1
  48. package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
  49. package/dist/foundation/resident-tabs/maintenance.js +78 -0
  50. package/dist/foundation/resident-tabs/release-holds.d.ts +27 -0
  51. package/dist/foundation/resident-tabs/release-holds.js +77 -0
  52. package/dist/foundation/resident-tabs/routing.d.ts +4 -3
  53. package/dist/foundation/resident-tabs/routing.js +6 -4
  54. package/dist/foundation/resident-tabs/settings.d.ts +7 -2
  55. package/dist/foundation/resident-tabs/settings.js +17 -16
  56. package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +18 -0
  57. package/dist/foundation/resident-tabs/terminal-host-artifact.js +53 -0
  58. package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
  59. package/dist/foundation/resident-tabs/zip-archive.js +56 -0
  60. package/dist/foundation/session-writer-lock/index.d.ts +5 -0
  61. package/dist/foundation/session-writer-lock/index.js +13 -9
  62. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  63. package/dist/integrations/pi/components/shell-footer-status.js +2 -2
  64. package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
  65. package/dist/integrations/pi/components/shell-presenters-info.d.ts +4 -2
  66. package/dist/integrations/pi/components/shell-presenters-info.js +38 -4
  67. package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
  68. package/dist/integrations/pi/components/upstream/components/session-footer.js +7 -4
  69. package/dist/integrations/pi/engine/adapter.d.ts +1 -0
  70. package/dist/integrations/pi/engine/adapter.js +3 -0
  71. package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
  72. package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
  73. package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
  74. package/dist/native/darwin-arm64/manifest.json +2 -2
  75. package/dist/native/darwin-arm64/process-guardian +0 -0
  76. package/dist/native/darwin-arm64/terminal-host +0 -0
  77. package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
  78. package/dist/native/darwin-arm64/terminal-host.manifest.json +44 -0
  79. package/dist/native/darwin-x64/a1_session_lock.node +0 -0
  80. package/dist/native/darwin-x64/manifest.json +20 -0
  81. package/dist/native/darwin-x64/process-guardian +0 -0
  82. package/dist/native/darwin-x64/terminal-host +0 -0
  83. package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10737 -0
  84. package/dist/native/darwin-x64/terminal-host.manifest.json +44 -0
  85. package/dist/native/linux-x64/a1_session_lock.node +0 -0
  86. package/dist/native/linux-x64/manifest.json +2 -2
  87. package/dist/native/linux-x64/process-guardian +0 -0
  88. package/dist/native/linux-x64/terminal-host +0 -0
  89. package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
  90. package/dist/native/linux-x64/terminal-host.manifest.json +44 -0
  91. package/dist/native/win32-x64/a1_session_lock.node +0 -0
  92. package/dist/native/win32-x64/manifest.json +2 -2
  93. package/dist/native/win32-x64/process-guardian.exe +0 -0
  94. package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
  95. package/dist/native/win32-x64/terminal-host.exe +0 -0
  96. package/dist/native/win32-x64/terminal-host.manifest.json +44 -0
  97. package/dist/ui/settings/declarations.d.ts +19 -43
  98. package/dist/ui/settings/declarations.js +19 -35
  99. package/dist/ui/settings/index.d.ts +1 -1
  100. package/dist/ui/settings/index.js +1 -1
  101. package/dist/ui/settings/manager.d.ts +1 -0
  102. package/dist/ui/settings/manager.js +9 -1
  103. package/dist/ui/settings/migrations.js +12 -0
  104. package/dist/ui/settings/resolution.d.ts +0 -2
  105. package/dist/ui/settings/resolution.js +1 -13
  106. package/dist/ui/settings/sections.js +0 -4
  107. package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
  108. package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
  109. package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
  110. package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
  111. package/docs/architecture/internal-naming.md +2 -0
  112. package/docs/architecture/process-guardian-provenance.md +2 -0
  113. package/docs/architecture/resident-tabs-requirements.md +1 -563
  114. package/docs/architecture/resident-tabs.md +166 -94
  115. package/docs/architecture/terminal-host-provenance.md +13 -3
  116. package/docs/architecture/toolchain.md +1 -1
  117. package/docs/ci-release-runbook.md +2 -2
  118. package/docs/features/launch-profiles.md +11 -8
  119. package/docs/manual-resident-tabs-acceptance.md +51 -0
  120. package/docs/openspec-archive-automation.md +15 -3
  121. package/docs/validation.md +9 -1
  122. package/package.json +5 -1
@@ -1,6 +1,6 @@
1
- # Resident tabs — architecture and roadmap
1
+ # Resident tabs — architecture and roadmap
2
2
 
3
- Status: staged implementation. Milestones 1–3 are integrated. Milestone 4's crash restart, durable recovery data, liveness/progress supervision, registry recovery, and bounded diagnostics are implemented on its draft change behind the default-off preview setting; the final synchronized head still requires exact-head three-platform CI evidence before acceptance. Windows physical-survival evidence exists for milestone 2 only. macOS and Linux physical survival, complete tab UX, packaging, maintenance commands, and certification remain deferred to later milestones; CI evidence is not a substitute. Discovery or verification failure safely falls back to the direct path. This document is the living design for multi-agent tabs; each milestone change cites the decisions it implements and updates this file when a decision changes. The full target behavior is in [`resident-tabs-requirements.md`](resident-tabs-requirements.md); each milestone moves its slice of those requirements into its own OpenSpec change and removes it from that file.
3
+ Status: implemented. Milestones 1–6 deliver resident tabs as the bare-`a1` route on Windows x64, Linux x64, macOS arm64, and macOS x64, with `a1 solo` as the direct single-agent route and `a1 tabs` for maintenance. Every release carries a verified terminal host for each of those platforms. Certification is recorded per platform from exact-package evidence (see [Release state and known gaps](#release-state-and-known-gaps)); it is evidence, not a runtime gate. Discovery or verification failure falls back to the direct path with one notice. This document is the living design for multi-agent tabs; each change cites the decisions it implements and updates this file when a decision changes. The one target requirement no milestone has delivered yet is kept in [`resident-tabs-requirements.md`](resident-tabs-requirements.md).
4
4
 
5
5
  ## Context
6
6
 
@@ -14,13 +14,13 @@ The user set three product constraints:
14
14
 
15
15
  A structured, message-based agent protocol cannot meet (2), because an extension's custom UI component is code running inside the agent process. It also forces a second, unrelated system for (3). Every tab is therefore a **terminal session**: a pseudoterminal running the complete A1 owned UI in "tab mode". Native extension components retain the declared text-terminal behavior; inline image protocols are explicitly outside the first version and use the existing text fallback.
16
16
 
17
- The first version is deliberately narrower than the eventual product. It is an opt-in preview on exactly Windows x64, Linux x64, and macOS arm64 (the targets A1 currently validates for the native process-guardian), with `residentTabs=false` by default. It is delivered as six sequenced milestone changes, each merged behind that setting. Restoring tabs after reboot or logout, another architecture or platform, default enablement, automatic resident-cohort upgrades, arbitrary CLI tabs, and split layouts each require a later change and their own evidence.
17
+ The first version is deliberately narrower than the eventual product. It targets exactly Windows x64, Linux x64, and macOS arm64 (the targets A1 currently validates for the native process-guardian), with resident tabs on bare `a1` and argument-free `a1 solo` as the explicit direct-mode opt-out. It is delivered as six sequenced milestone changes. Restoring tabs after reboot or logout, another architecture or platform, automatic resident-cohort upgrades, arbitrary CLI tabs, and split layouts each require a later change and their own evidence.
18
18
 
19
19
  Existing assets:
20
20
 
21
- - **`native/terminal-host`** is a working in-terminal host. It uses `libghostty-vt` pinned at `c5a21edf`, `portable-pty` 0.9.0 (ConPTY on Windows, Unix PTYs elsewhere), crossterm 0.29, and an A1-owned damage-aware frame composer. Its retained model and native encoders came from the fixed 2×2 proof; milestone 2 removed that interactive proof path and moved the model into the resident holder/attach roles. Three-platform certification remains separate from implementation.
21
+ - **`native/terminal-host`** is a working in-terminal host. It uses `libghostty-vt` pinned at `c5a21edf`, `portable-pty` 0.9.0 (ConPTY on Windows, Unix PTYs elsewhere), crossterm 0.29, and an A1-owned damage-aware frame composer. Its retained model and native encoders came from the fixed 2×2 proof; milestone 2 removed that interactive proof path and moved the model into the resident holder/attach roles. Three-platform certification remains separate from implementation.
22
22
  - **The supervisor, process-guardian, and launch guardian** provide the patterns for detached spawn, verified process identity (`--inspect-pid`), an endpoint probe with join-the-winner, and atomic metadata. The process-guardian already has Windows, macOS, and Linux implementations.
23
- - **The 2×2 spike evidence** in [`evidence/terminal-host-spike/`](evidence/terminal-host-spike/) is historical. Its verdicts stay pending and are not evidence for this design.
23
+ - **The 2×2 spike evidence** in [`evidence/terminal-host-spike/`](evidence/terminal-host-spike/) is historical. Its verdicts stay pending and are not evidence for this design.
24
24
 
25
25
  References:
26
26
 
@@ -46,17 +46,18 @@ The read-only review used A1 `develop` at `b42b3c9e`, PR #586 at `8fdb7361`, `E:
46
46
  - v2's `agent/multiagent/daemon.ts` owns all PTYs, performs synchronous spawn/tree-kill work, and has a daemon-wide stall watchdog. `daemon-logic.ts` truncates stalled input to the newest 8192 code units. `protocol.ts`/`child-bridge.ts` also carry rendered frames, creating a second screen source. Retain UX and regression scenarios, not these mechanisms or logic hot-swapping.
47
47
  - v2's `startup-benchmark.md` records warm creation p50 13 ms / p95 41 ms and an outer restart of 6.255 s. Those measure different paths and are not performance promises for A1.
48
48
  - Current herdr's `src/platform/windows.rs`, `src/server/client_transport.rs`, and `src/server/render_stream.rs` are references for detachment, bounded transport, and surface revisions. `src/pty/actor.rs` uses threads, not one process per PTY. Unix-only descriptor handoff and Unix-gated detach/multi-client tests do not certify Windows survival. `src/persist/writer.rs` synchronizes recovery copies, while ordinary session saving in `src/persist/io.rs` still uses unsynchronized write-and-rename.
49
- - A1's existing prompt history is asynchronous and can skip writes; it is not the durable-before-dispatch journal. The native host is a fixed 2×2 proof, not resident infrastructure. Its historical acceptance record still has pending technical/physical verdicts and must not be relabelled as accepted.
49
+ - A1's existing prompt history is asynchronous and can skip writes; it is not the durable-before-dispatch journal. The native host is a fixed 2×2 proof, not resident infrastructure. Its historical acceptance record still has pending technical/physical verdicts and must not be relabelled as accepted.
50
50
  - Before implementation, reconcile current `develop`, including #588's terminal-query/CI ownership and #667's surviving-owner terminal restoration. Update architecture/proof documentation during approved implementation to distinguish this single-pane resident certification from future split certification, preserving historical evidence unchanged.
51
51
  - herdr's Unix paths (socket bind as lock, Unix detach, the macOS per-user bootstrap port fix in #4100) are references for the macOS and Linux roles, not evidence for them.
52
52
 
53
53
  ### Milestone 2 pinned launch contract (2026-10-11)
54
54
 
55
- - The preview flag is `residentTabs` in the flat `a1` object of the global agent `settings.json`, default `false`; pre-UI routing treats absent, unreadable, oversized, and invalid values as off. The former versioned profile document remains a read-only compatibility fallback while that object is absent.
55
+ - Bare `a1` from a terminal always requests the resident tab route; with piped or redirected standard input or output it runs the direct UI without probing the host, because the attach client needs a terminal to size and own. Tab enablement is not persisted or configurable; host discovery or verification failure uses the bounded direct single-agent fallback.
56
+ - Tab settings `tabsMax` and `tabsAutoName` live in the flat `a1` object of the global agent `settings.json`; the former versioned profile document remains a read-only compatibility fallback while that object is absent.
56
57
  - The host artifact resolves to `dist/native/<platform>-<arch>/a1-terminal-host[.exe]`, with hermetic `A1_TERMINAL_HOST_PATH`; discovery may use `A1_TABS_ENDPOINT`.
57
58
  - Tab children consume and erase `A1_TAB_ID`, `A1_TAB_TOKEN`, and `A1_TAB_ENDPOINT` before loading A1, Pi, extensions, or tools.
58
59
  - Client-scoped bridge detach binds `clientId` and `controllerRevision` captured when the command is admitted. The additive generation-1 `attachment` and `bridge.request` fixtures freeze that shape.
59
- - Launch fallback reasons are bounded to unsupported platform, missing artifact, failed probe, incompatible generation, unreadable registry, blocked recovery, unverified launch, and startup timeout. Fallback preserves resident records and presents exactly one direct-session notice.
60
+ - Launch fallback reasons are bounded to unsupported platform, no interactive terminal, missing artifact, failed probe, incompatible generation, unreadable registry, blocked recovery, unverified launch, and startup timeout. Fallback preserves resident records and presents exactly one direct-session notice.
60
61
  - Until milestone 4 adds quarantine and history recovery, an unreadable registry fails closed and is left untouched.
61
62
  - Milestone 2 multiplexes authenticated attach, holder, bridge, and probe roles over the owner endpoint and sends a replaceable full current surface through each client's single-slot render lane. Dedicated holder endpoints and patch deltas remain multi-tab optimizations rather than claims of this one-tab milestone.
62
63
  - The native server, holder, attach, bridge reconnect, child containment, and role integration fixture are implemented. Milestone 2 records complete Windows x64 terminal-close and OpenSSH-loss evidence; macOS arm64 and Linux x64 physical terminal/SSH evidence remains uncertified and is an explicit milestone-6 gate rather than an inferred milestone-2 result.
@@ -68,28 +69,28 @@ The read-only review used A1 `develop` at `b42b3c9e`, PR #586 at `8fdb7361`, `E:
68
69
  - Holders read PTY output continuously, use one bounded input authority, reserve complete pastes before delivery, and emit a typed uncertain-delivery result after an admitted write failure. Server-to-client control lanes are frame/byte bounded; render lanes retain one replaceable full current surface. Holders stream surfaces only while at least one client views their tab.
69
70
  - Server replacement is serialized by `server-start.lock` in the profile runtime directory. Recovery verifies the recorded native owner before termination, waits boundedly for the registry-writer lease, increments the durable epoch, derives holder credentials, and re-admits only exact holder/child identities and incarnations. A durable three-start/sixty-second budget stops automatic replacement; explicit `--restart` clears it.
70
71
  - Registry records now retain holder and child pid/start identities and holder incarnation. Replacement treats unverifiable records as `holder-lost`; it never starts a duplicate writer from uncertain state.
71
- - `tabsMaxConcurrentStarts` is an owned version-15 setting, default 2 and bounded 1–8. The server also enforces the existing ten-tab hard default where no earlier `tabsMax` setting exists, spaces starts by at least 250 ms, and requests graceful tab shutdown before verified forced termination.
72
- - All A1-owned Pi runtimes use one profile-neutral session-writer guard regardless of `residentTabs`. A new target is reserved by canonical location, created as an empty owner-only placeholder, bound to stable file identity without a release gap, and only then exposed to Pi for session content. Runtime replacement acquires and binds the target before publishing it or releasing the prior writer; conflict paths preserve the source and may explicitly fork.
72
+ - `tabsMaxConcurrentStarts` is an owned version-15 setting, default 2 and bounded 1–8. The server also enforces the existing ten-tab hard default where no earlier `tabsMax` setting exists, spaces starts by at least 250 ms, and requests graceful tab shutdown before verified forced termination.
73
+ - All A1-owned Pi runtimes use one profile-neutral session-writer guard regardless of launch mode. A new target is reserved by canonical location, created as an empty owner-only placeholder, bound to stable file identity without a release gap, and only then exposed to Pi for session content. Runtime replacement acquires and binds the target before publishing it or releasing the prior writer; conflict paths preserve the source and may explicitly fork.
73
74
  - The generation-1 holder transport continues to use the authenticated owner endpoint and replaceable full surfaces. Dedicated holder endpoints and patch deltas remain later optimizations and are not milestone-3 claims.
74
75
 
75
76
  ### Milestone 4 crash-recovery contract (2026-10-12)
76
77
 
77
78
  - The server persists restart intent and the three-in-ten-minute budget before instructing the surviving holder to respawn its child. The holder verifies prior-writer release, preserves its PTY ownership boundary, rotates incarnation credentials, and resumes the recorded session with 1/5/30-second backoff. User-requested restart is explicit and does not consume the crash budget.
78
- - Child-event-loop heartbeat silence is counted only while server, bridge, holder, and child supervision remains continuously healthy. Transport loss resets the observation window. Progress stalls are advisory and never trigger automatic termination. `tabsUnresponsiveRestartSeconds` and `tabsStallNoticeSeconds` remain advanced controls hidden unless `residentTabs` is enabled.
79
+ - Child-event-loop heartbeat silence is counted only while server, bridge, holder, and child supervision remains continuously healthy. Transport loss resets the observation window. Progress stalls are advisory and never trigger automatic termination. Milestone 5 retires the `tabsUnresponsiveRestartSeconds` and `tabsStallNoticeSeconds` controls and keeps their 120/300-second behavior internal.
79
80
  - Tab-mode submission is durably journaled before dispatch, correlated with the session entry, synchronized after settlement, and retired idempotently. Recovery reconciles identity and exact text without collapsing duplicate prompts or automatically replaying work. Periodic draft checkpoints expose a degraded state when the one-second healthy-storage envelope cannot be met.
80
81
  - Registry replacement retries transient failures, quarantines unreadable state, recovers only from the newest valid bounded history, and fails closed when no valid generation remains. A changed boot or OS-session identity archives the prior tab set instead of restoring it; journals younger than seven days can be adopted when their sessions are explicitly reopened.
81
82
  - Last-screen and fixture-only stderr files are private bounded recovery artifacts. Structured server, holder, attach, and child crash records use allowlisted fields, rotate without startup truncation, and remain physically separate from recovery content.
82
- - Milestone 4 adds only the minimal lifecycle states, failed-tab banner, and retry/fresh/close handling needed to expose recovery. Rich strip behavior, general shortcuts, mouse interactions, naming, prewarm, idle suspension, and two-client UX remain milestone 5. `a1 tabs doctor`, packaging, physical terminal/SSH-loss acceptance, and complete certification remain milestone 6.
83
+ - Milestone 4 added the minimal lifecycle states, failed-tab banner, and retry/fresh/close handling needed to expose recovery. Milestone 5 adds the rich strip, shortcuts, mouse interactions, naming, prewarm, no-idle-suspension contract, and two-client UX. `a1 tabs doctor`, packaging, physical terminal/SSH-loss acceptance, and complete certification remain milestone 6.
83
84
 
84
85
  ### Relation to the in-process prep refactors (2026-10-09/10)
85
86
 
86
- PRs #729–#736 prepared the owned UI for several sessions in one process. This design runs one A1 process per tab, so it does not depend on the shared helper pools (#733), the engine host's multi-session factory (#734), the terminal-host/presenter split (#735), or the per-agent claim dimension (#736). It does use the lifecycle ordering from #732 (bounded quit, clean disposal), which tab detach and restart rely on, and the unique required session ids from #734. Each tab process must get its own launch runtime id so worktree claims (#736) never collide.
87
+ PRs #729–#736 prepared the owned UI for several sessions in one process. This design runs one A1 process per tab, so it does not depend on the shared helper pools (#733), the engine host's multi-session factory (#734), the terminal-host/presenter split (#735), or the per-agent claim dimension (#736). It does use the lifecycle ordering from #732 (bounded quit, clean disposal), which tab detach and restart rely on, and the unique required session ids from #734. Each tab process must get its own launch runtime id so worktree claims (#736) never collide.
87
88
 
88
89
  ## Goals / Non-Goals
89
90
 
90
91
  **Goals:**
91
92
 
92
- - Several independent A1 agents in opt-in bare `a1` tabs on Windows x64, Linux x64, and macOS arm64, each running the full A1 UI with its extensions. The user can create, switch, rename, reorder, and close them.
93
+ - Several independent A1 agents in bare `a1` tabs on Windows x64, Linux x64, and macOS arm64, each running the full A1 UI with its extensions. The user can create, switch, rename, reorder, and close them, or run `a1 solo` for direct single-agent mode.
93
94
  - Tabs keep running through terminal closure, SSH drops, attach-client crashes, and resident-server crashes. Relaunching from any terminal reattaches with exact screen state. A crashed tab restarts into its session without replaying an interrupted prompt.
94
95
  - One tab's failure never affects another tab, the server, or the client. Package activation retains every immutable release still used by resident processes.
95
96
  - Status comes from structured engine events, not screen text.
@@ -100,7 +101,6 @@ PRs #729–#736 prepared the owned UI for several sessions in one process. This
100
101
 
101
102
  **Non-Goals:**
102
103
 
103
- - Enabling resident tabs by default on any platform.
104
104
  - Restoring tabs after reboot or logout. Resident processes end with the OS session; the sessions stay resumable through the normal session picker (Decision 9).
105
105
  - Platforms beyond the three above, automatic resident server handoff/recycling, arbitrary CLI tabs, split panes/layouts, and v2-style per-folder workspaces or sidebar.
106
106
  - Network or remote attach, and multi-user sharing.
@@ -133,21 +133,21 @@ Alternatives considered:
133
133
  ### 2. One native binary, three roles, plus the A1 child
134
134
 
135
135
  ```text
136
- terminal (detached, per OS user × profile)
137
- ┌──────────────────────────┐ ┌─────────────────────────────────────┐
138
- │ a1 → guardian → │ │ a1-terminal-host server │
139
- │ a1-terminal-host attach │◀───▶│ registry (atomic JSON, fsync) │
140
- │ tab strip + active tab │ pipe│ topology, client fan-out, restore │
141
- │ outer terminal I/O │ └───┬──────────────┬──────────────┬───┘
142
- └──────────────────────────┘ │pipe │pipe │pipe
143
- (launch instance, dies with ▼ ▼ ▼
136
+ terminal (detached, per OS user × profile)
137
+ ┌──────────────────────────┐ ┌─────────────────────────────────────┐
138
+ │ a1 → guardian → │ │ a1-terminal-host server │
139
+ │ a1-terminal-host attach │◀───▶│ registry (atomic JSON, fsync) │
140
+ │ tab strip + active tab │ pipe│ topology, client fan-out, restore │
141
+ │ outer terminal I/O │ └───┬──────────────┬──────────────┬───┘
142
+ └──────────────────────────┘ │pipe │pipe │pipe
143
+ (launch instance, dies with â–¼ â–¼ â–¼
144
144
  the terminal: harmless) holder: tab 1 holder: tab 2 holder: tab N
145
145
  PTY/ConPTY PTY/ConPTY PTY/ConPTY
146
146
  ghostty model ghostty model ghostty model
147
- │ │
148
- node ui.js --tab node ui.js --tab …
147
+ │ │
148
+ node ui.js --tab node ui.js --tab …
149
149
  (full A1 UI + Pi + extensions)
150
- └── tab bridge ──▶ server (status, session, name, detach)
150
+ └── tab bridge ──▶ server (status, session, name, detach)
151
151
  ```
152
152
 
153
153
  - **attach** is the foreground `a1` surface. It runs inside the bare-A1 launch instance and dies with the terminal, which is harmless.
@@ -167,10 +167,10 @@ Alternatives considered:
167
167
 
168
168
  ### 3. Host scope, endpoints, writer authority, and credentials
169
169
 
170
- - There is **one server per OS user × canonical A1 profile root**, independent of the release cohort. Tabs must outlive release changes, and the supervisor is forbidden from retaining runtime processes.
170
+ - There is **one server per OS user × canonical A1 profile root**, independent of the release cohort. Tabs must outlive release changes, and the supervisor is forbidden from retaining runtime processes.
171
171
  - Windows endpoint: `\\.\pipe\a1-tabs-<sha256(canonicalProfileHome)[0:20]>`.
172
172
  - macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same profile hash>.sock` inside an owner-only (`0700`) per-user runtime directory (`$XDG_RUNTIME_DIR` when set, otherwise a private directory under the profile's runtime dir), kept short enough for the platform's socket path limit.
173
- - Generation 1 admits holders over the authenticated owner endpoint. Dedicated `…-h-<tabId>` endpoints remain an optional later optimization.
173
+ - Generation 1 admits holders over the authenticated owner endpoint. Dedicated `…-h-<tabId>` endpoints remain an optional later optimization.
174
174
  - Hermetic override: `A1_TABS_ENDPOINT`, together with the `A1_*_DIR` overrides.
175
175
  - **The endpoint is discovery; the writer lease is authority.** A starting server probes with a real handshake. If a live server answers, it exits as `already-running` and joins the winner. A failed probe does not authorize blind endpoint reclaim: the starter reads the owner marker, verifies pid and native start identity, requests termination, and waits for the owner-only OS-exclusive registry-writer lease. If the owner cannot be verified or the lease cannot be acquired, startup fails closed instead of creating split brain.
176
176
  - The ownership marker `{serverId, pid, startIdentity, bootNonce, generation, build}` is written atomically under the writer lease. Every registry mutation rechecks the held lease and current epoch before commit. A server removes an endpoint or marker only while both still identify it.
@@ -202,19 +202,19 @@ Alternatives considered:
202
202
 
203
203
  The channels carry bounded generation-1 frames: a u32 little-endian length followed by one MessagePack named-map envelope with stable `type`, optional `requestId`, and typed `body` fields. There is a 2 MiB cap per frame, 1 MiB per input-bearing message, bounded nesting and collection lengths, and a 4 s handshake timeout. Length and structural bounds are checked before corresponding allocation or recursion.
204
204
 
205
- - **Handshake:** `hello{role, generation, build, features[], token}` → `welcome{…, limits}`.
205
+ - **Handshake:** `hello{role, generation, build, features[], token}` → `welcome{…, limits}`.
206
206
  - `generation` is the stable compatibility number. Within a generation, named maps change only additively: unknown fields are ignored, new fields are optional with defaults, enums carry an explicit `unknown` fallback, and a missing method disables only that action.
207
207
  - A mismatched generation yields a typed `incompatible-generation` outcome.
208
208
  - A pinned canonical encoder emits exact-byte fixtures. A manifest sorted by fixture name records each SHA-256 and one digest over ordered names and bytes; later additions regenerate it rather than merging digest text by hand.
209
- - **Client ↔ server:**
209
+ - **Client ↔ server:**
210
210
  - **Topology.** `tabs.subscribe` returns a snapshot with a revision, followed by changes. Mutations are `tab.create{kind:"a1", cwd, env, session?}`, `tab.rename`, `tab.reorder{expectedRevision}`, `tab.close`, `tab.retry`, `tab.fresh`, and `tabs.stopAll`. A stale revision is rejected atomically.
211
211
  - **Surface.** `tab.view{tabId, size}` returns a full retained surface (cells, attributes, hyperlinks, cursor, modes), then patches against its revision. `tab.unview` ends it. Only the tab a client is viewing streams surfaces to that client (v2 lesson).
212
212
  - **Controller.** `tab.claimInput{tabId, clientId, expectedControllerRevision}` transfers one controller lease through a fenced input-admission boundary. The sans-IO state moves through active, frozen, draining, awaiting-boundary, then active for the new generation. After every accepted old item enters the shared PTY writer, the holder writes a versioned DCS marker carrying the target generation and a 128-bit nonce through that same queue. When a pseudoterminal reserves raw DCS input syntax, a fixed printable envelope carries the exact marker bytes and the child parser reconstructs them only after earlier queue bytes arrive. Tab-mode input handling consumes it and acknowledges `{generation, nonce}` over the authenticated bridge only after earlier bytes have been admitted by the child parser; a sideband acknowledgement without that PTY ordering is insufficient. Milestone 1 freezes the marker and proves the state machine plus a native fixture; milestone 5 wires the production tab-mode parser and bridge. Requests bind an immutable origin controller generation at command admission, not execution; stale or ambiguous requests are rejected, never relabelled. If the bridge or proven boundary is unavailable, terminal input remains usable with client-scoped child commands disabled; attach-local double-`Ctrl+C` remains available.
213
213
  - **Input.** `tab.input{tabId, controllerRevision, bytes|keyEvent|mouseEvent|paste|focus}` is accepted only from the current controller and encoded by the holder with libghostty-vt against the child's current modes.
214
214
  - **Resize.** `tab.resize{tabId, controllerRevision, cols, rows}`. The PTY size follows the current input controller; read-only viewers cannot resize the child.
215
215
  - **Attention.** Status and attention events arrive for all tabs, even unviewed ones.
216
- - **Server ↔ holder:** `holder.ready{tabId, token, pid, startIdentity, childPid, childStartIdentity, release}`, then surface patches, `holder.exit{code, signal}`, and heartbeats. The server forwards input and resizes.
217
- - **Tab bridge (A1 child ↔ server):**
216
+ - **Server ↔ holder:** `holder.ready{tabId, token, pid, startIdentity, childPid, childStartIdentity, release}`, then surface patches, `holder.exit{code, signal}`, and heartbeats. The server forwards input and resizes.
217
+ - **Tab bridge (A1 child ↔ server):**
218
218
  - The child receives `A1_TAB_ID`, `A1_TAB_TOKEN`, and the endpoint through the fixed startup contract. The child consumes credentials before loading extensions or spawning tools and removes them from inherited environments; holders cannot sanitize grandchildren by themselves. Reconnect credentials remain in child-private memory and are never restored to `process.env`.
219
219
  - The child sends `bridge.status{seq, state}` derived from engine events: `agent_start`, `turn_start`, tool execution, settled idle, `stopReason`, pending extension UI or trust requests, and compaction.
220
220
  - It also sends `bridge.session{file, name}`, `bridge.request{action, clientId, controllerRevision}`, and `bridge.prompt{interrupted?}` metadata. Client-scoped requests are honored only when their controller generation is current. Prompt text and terminal input are never sent through the bridge.
@@ -227,7 +227,7 @@ The channels carry bounded generation-1 frames: a u32 little-endian length follo
227
227
 
228
228
  ### 6. Rendering, input, and outer-terminal fidelity
229
229
 
230
- - **Composition.** The attach client composes row 0 (the strip) and rows `1..N` (the viewed tab's surface). The tab PTY is sized `cols × (rows − 1)`. Composition reuses the proof's damage-aware composer and synchronized-output support, and falls back to a full repaint only when the layout changes.
230
+ - **Composition.** The attach client composes row 0 (the strip) and rows `1..N` (the viewed tab's surface). The tab PTY is sized `cols × (rows − 1)`. Composition reuses the proof's damage-aware composer and synchronized-output support, and falls back to a full repaint only when the layout changes.
231
231
  - **Outer modes.** The client negotiates the outer terminal's modes: raw mode, alternate screen, bracketed paste, focus events, SGR mouse, kitty keyboard protocol where available, and synchronized output.
232
232
  - Child-requested modes are tracked in the holder's model. Input is re-encoded per child mode by libghostty-vt, which the proof already does.
233
233
  - Mouse rows are offset by the strip. Row 0 belongs to the client.
@@ -248,16 +248,15 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
248
248
  | `idle` | settled idle | none |
249
249
  | `working` | agent or turn start, tool execution, compaction | shared progress frames |
250
250
  | `needs-input` | an extension UI, trust, or permission request is pending | yellow `?` (warning) |
251
- | `done-unseen` | the turn settled while no client viewed the tab | `✓` success |
252
- | `error` | the last turn's `stopReason` was error | `✗` error |
253
- | `crashed` / `restarting` / `failed` | holder or child exit | `✗` error plus a banner |
254
- | `suspended` | idle suspension | dim `◌` |
251
+ | `done-unseen` | the turn settled while no client viewed the tab | `✓` success |
252
+ | `error` | the last turn's `stopReason` was error | `✗` error |
253
+ | `crashed` / `restarting` / `failed` | holder or child exit | `✗` error plus a banner |
255
254
 
256
- - `seen` is tracked server-side, so viewing a tab in any client clears `✓` everywhere.
255
+ - `seen` is tracked server-side, so viewing a tab in any client clears `✓` everywhere.
257
256
  - A tab with a broken bridge shows only process-level states.
258
257
  - Future CLI tabs will derive status from process state, and may use BEL or OSC 9/777 from the program as an attention input.
259
- - The glyph set is the same in every client.
260
- - **Icons are the only attention signal (user decision).** There is no bell, sound, terminal notification, or OS notification. The status icon is the signal: spinner = working, `✓` = finished, red `✗` = failed, yellow `?` = needs you.
258
+ - The glyph set is the same in every client. A working spinner uses the resolved A1 accent reported by that specific tab.
259
+ - **Icons are the only attention signal (user decision).** There is no bell, sound, terminal notification, or OS notification. The status icon is the signal: accent spinner = working, `✓` = finished, red `✗` = failed, yellow `?` = needs you.
261
260
 
262
261
  ### 8. Durable state
263
262
 
@@ -278,7 +277,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
278
277
  - restarts: `restarts`, `restartWindowStart`, `lastExit`
279
278
  - `registryRevision`, `epoch`, and `bootId`
280
279
  - **No per-tab credential is persisted in the registry.** The owner-only profile secret is stored separately and derives per-tab/per-incarnation credentials from recorded non-secret identities. The environment sent with `tab.create` is kept in holder memory only; a tab restarted after a crash reuses its holder's environment.
281
- - **Session-writer lease.** All A1-owned session writers (resident, direct/fallback, and Pi-comparison) on the supported platforms use one profile-neutral native lock primitive before opening a session for writing, independent of `residentTabs`. It does not start a host, holder, or bridge for direct modes. The primitive is an OS lock held by the writer process itself (`LockFileEx` on Windows, an open-file-description `fcntl` lock on Linux, `flock` on macOS), so the kernel releases it only when the writer is gone. Node writers take it in-process through a Node-API 9 artifact produced by the terminal-host Cargo package from the same lock implementation; a helper process cannot hold a lock on their behalf. Existing files are opened, identified by stable volume/device plus file identity, locked through that identity, and revalidated so case, junction/symlink, and hard-link aliases converge. A new file first holds a canonical-parent/name reservation, creates/opens without writing session content, acquires and revalidates file-identity authority while the reservation remains held, and only then admits writing; losing that race writes nothing. The writer routes include startup, `--session`, in-runtime switches, fork, import, new session, and the picker rename. The registry and holder coordinate admission, but a holder-only lock is insufficient: native lease custody must remain valid for the actual writer's lifetime, including any interval after holder death. Pin and prove the writer-bound lock/handle design on each platform in milestone 1; do not infer child death from pseudoterminal loss. Replacement requires lease acquisition and verified prior-writer/tree exit; uncertainty blocks it. Session switching acquires the target before relinquishing the old writer lease, and failed switches preserve the original session. Resident selection focuses an existing tab; direct-mode conflicts fail safely or offer an explicit fork, never silently attach or overwrite. Unmodified external Pi, older nonparticipating builds, and arbitrary file writers are outside this cooperative guarantee; document that limitation rather than claiming an OS sandbox.
280
+ - **Session-writer lease.** All A1-owned session writers (resident, solo/fallback, and Pi-comparison) on the supported platforms use one profile-neutral native lock primitive before opening a session for writing, independent of launch mode. It does not start a host, holder, or bridge for direct modes. The primitive is an OS lock held by the writer process itself (`LockFileEx` on Windows, an open-file-description `fcntl` lock on Linux, `flock` on macOS), so the kernel releases it only when the writer is gone. Node writers take it in-process through a Node-API 9 artifact produced by the terminal-host Cargo package from the same lock implementation; a helper process cannot hold a lock on their behalf. Existing files are opened, identified by stable volume/device plus file identity, locked through that identity, and revalidated so case, junction/symlink, and hard-link aliases converge. A new file first holds a canonical-parent/name reservation, creates/opens without writing session content, acquires and revalidates file-identity authority while the reservation remains held, and only then admits writing; losing that race writes nothing. The writer routes include startup, `--session`, in-runtime switches, fork, import, new session, and the picker rename. The registry and holder coordinate admission, but a holder-only lock is insufficient: native lease custody must remain valid for the actual writer's lifetime, including any interval after holder death. Pin and prove the writer-bound lock/handle design on each platform in milestone 1; do not infer child death from pseudoterminal loss. Replacement requires lease acquisition and verified prior-writer/tree exit; uncertainty blocks it. Session switching acquires the target before relinquishing the old writer lease, and failed switches preserve the original session. Resident selection focuses an existing tab; direct-mode conflicts fail safely or offer an explicit fork, never silently attach or overwrite. Unmodified external Pi, older nonparticipating builds, and arbitrary file writers are outside this cooperative guarantee; document that limitation rather than claiming an OS sandbox.
282
281
  - **Prompt journal.** `<dataDir>/tabs/<profile-token>/<tabId>/journal.jsonl` is an owner-only child-owned recovery journal, separate from asynchronous prompt history. Each submission has a stable ID and session/incarnation identity. Admission awaits a successful durable journal commit before dispatch; timeout/failure leaves the prompt in the editor with a visible failure and dispatches nothing. Correlate it with the committed session entry, and retire it only after the corresponding session data is synchronized; a settled turn's durable completion also records its submission ID so recovery never offers a completed prompt as unfinished. Recovery merges journal/session evidence by identity and never automatically resends, including when a crash occurs between dispatch, session append, sync, and journal retirement. Preserve the first-turn journal and reserved session identity if Pi has not created its file yet; distinguish that from an unexpectedly missing existing transcript.
283
282
  - **Draft checkpoint.** Use a periodic dirty checkpoint with a maximum one-second dirty interval while storage is healthy, including uninterrupted typing, rather than inactivity-only debounce. Expose failed/late checkpoints as degraded recovery; never continue claiming the one-second bound during a blocked disk or event-loop stall. Drafts and submitted prompts have different guarantees.
284
283
 
@@ -286,11 +285,11 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
286
285
 
287
286
  | Failure | Detection | Outcome |
288
287
  |---|---|---|
289
- | Tab child exits or crashes | holder sees the child exit | If the exit was not requested: `crashed`. The holder respawns `ui.js --tab --session <file>` with backoff 1 s, 5 s, 30 s. After 3 restarts in 10 min: `failed`, with a banner `✗ <reason> — [r] retry · [f] start fresh · [alt+w] close`. An interrupted prompt (reported via the bridge, or detected as a trailing user entry) is offered back into the editor and never resent. Child stderr, if separately available, is a private content-bearing recovery artifact, never an ordinary diagnostic log. |
288
+ | Tab child exits or crashes | holder sees the child exit | If the exit was not requested: `crashed`. The holder respawns `ui.js --tab --session <file>` with backoff 1 s, 5 s, 30 s. After 3 restarts in 10 min: `failed`, with a banner `✗ <reason> — [r] retry · [f] start fresh · [alt+w] close`. An interrupted prompt (reported via the bridge, or detected as a trailing user entry) is offered back into the editor and never resent. Child stderr, if separately available, is a private content-bearing recovery artifact, never an ordinary diagnostic log. |
290
289
  | Holder crash | pipe EOF plus verified process identity/exit | Pseudoterminal loss is not proof that every descendant exited. Verify the old writer/tree is gone and acquire its writer lease before resuming in a new holder; otherwise keep the tab blocked without duplicate writers. |
291
290
  | Holder hang | holder control heartbeat every 5 s, 3 missed over a healthy supervision path, then one heartbeat of grace for the holder-local watchdog record | Terminate only the verified holder/tree within bounded deadlines, then follow the lease-gated recovery path. |
292
291
  | A1 child hang | missing child event-loop heartbeat over a healthy bridge path | Warn at 30 s; restart at `tabsUnresponsiveRestartSeconds` (default 120), preserving journal/screen. A server/bridge transport outage alone is not proof of a child hang. |
293
- | Server crash | clients and holders see the pipe EOF | Holders keep running. A client or holder starts a replacement, which acquires the released writer lease, increments the epoch, derives expected credentials, and re-admits identity-verified holders. A live but unresponsive recorded owner is terminated only after native identity verification; an unverifiable owner blocks replacement. After 3 starts in 60 s, clients show `✗ tab host stopped — [r] restart · [q] quit`. |
292
+ | Server crash | clients and holders see the pipe EOF | Holders keep running. A client or holder starts a replacement, which acquires the released writer lease, increments the epoch, derives expected credentials, and re-admits identity-verified holders. A live but unresponsive recorded owner is terminated only after native identity verification; an unverifiable owner blocks replacement. After 3 starts in 60 s, clients show `✗ tab host stopped — [r] restart · [q] quit`. |
294
293
  | Attach client crash or terminal close | the server sees the pipe EOF | Nothing happens to tabs. Pending requests stay `needs-input`. |
295
294
  | Reboot or logout | the platform boot identity differs, or no verified holders exist for a new OS session | Tabs are not restored in this version. The server moves the previous tab set to registry history, and bare `a1` starts with one new tab. Every previous session stays resumable through the normal session picker or `a1 --session`. A pending journaled prompt is offered when its session is next opened in a tab, for up to seven days. Restoring tabs after reboot is a follow-up change. |
296
295
  | Missing cwd or session on crash restart | stat or open fails | The tab is `failed` with the reason and path. It is never moved elsewhere, and the file is never overwritten. A reserved first-turn identity with a journal and no previously created transcript follows journal recovery instead. |
@@ -309,10 +308,12 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
309
308
 
310
309
  ### 11. Foreground UX
311
310
 
312
- - **Strip.** Row 0, drawn by the attach client:
313
- - Chips are at most 20 columns, clipped with `…` by grapheme width (fixing v2's UTF-16 bug), with no separators.
314
- - A `…` overflow menu holds tabs that do not fit, and `+` is always reserved.
315
- - Theme roles are resolved by Node at launch from A1 settings and passed to the client, so the colors follow the A1 theme.
311
+ - **Strip.** Row 0, drawn by the attach client, reproduces the approved v2 default appearance:
312
+ - normal chips use RGB background `38,38,38` with foreground `170,170,170`, hover uses `48,48,48`, and the viewed chip uses `55,55,55` with white text, plus v2's palette fallback;
313
+ - the empty state uses the v2 wallpaper treatment and the right edge shows its 12-hour clock;
314
+ - chips are at most 20 columns, clipped with `…` by grapheme width, with no separators and an always-visible `×` close control;
315
+ - a `…` overflow menu holds tabs that do not fit, and `+` is always reserved;
316
+ - these presentation choices are fixed, not settings, while each working spinner uses that tab's reported A1 accent.
316
317
  - **Keys.** These are intercepted by the attach client before input encoding. They are configurable through a keybindings file and conflict-checked against A1's registry at launch.
317
318
 
318
319
  | Key | Action |
@@ -320,62 +321,86 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
320
321
  | `Alt+A` | new tab |
321
322
  | `Alt+W` | close |
322
323
  | `F2` | rename |
323
- | `Alt+1`…`Alt+9`, `Alt+0` | jump to a tab |
324
- | `Alt+.` / `Alt+,` | next / previous, wrapping |
325
- | `Alt+>` / `Alt+<` | move the tab right / left |
324
+ | `Alt+1`…`Alt+9`, `Alt+0` | jump to a tab |
325
+ | unbound (`tabs.next` / `tabs.previous`) | next / previous, wrapping |
326
+ | unbound (`tabs.moveRight` / `tabs.moveLeft`) | move the tab right / left |
326
327
  | `Ctrl+C` `Ctrl+C` | detach (leave `a1`; tabs keep running) |
327
328
 
328
329
  - `Alt+[` and `Alt+]` are avoided because `ESC [` is the CSI introducer in legacy encodings.
329
- - `Alt+←` and `Alt+→` are avoided because A1 uses them for word motion.
330
+ - `Alt+←` and `Alt+→` are avoided because A1 uses them for word motion.
330
331
  - **`Ctrl+C` twice leaves, in every tab (user decision).** The client forwards the first `Ctrl+C` to the tab unchanged, so a single press still clears, copies, or interrupts. It consumes the second press within the existing clear/exit interval as the detach request and does not forward it. The tab therefore never receives a second `Ctrl+C` from the chord, which removes v2's bug where `Ctrl+C+C` killed the agent. The trade-off: a future CLI tab cannot receive two quick `Ctrl+C`s. Pressing them more slowly than the interval still reaches the program.
331
332
  - **Mouse on row 0:**
332
333
  - click activates a tab;
334
+ - the always-visible `×` closes its tab;
333
335
  - right-click opens `Rename`/`Close`;
334
- - dragging reorders, with a `│` drop marker, after 250 ms or once the pointer moves;
336
+ - dragging reorders, with a `│` drop marker, after 250 ms or once the pointer moves;
335
337
  - `+` creates a tab.
336
- - **Commands inside A1 tabs** travel through the bridge:
337
- - `/new-tab [name]`
338
- - `/close`
339
- - `/name <name>` (existing; it also renames the tab)
340
- - `/tabs` (a picker with status and cwd)
341
- - `/quit-all`
338
+ - **Commands inside A1 tabs:** existing `/name <name>` also renames the tab. Milestone 5 adds no tab-management slash commands: `/new-tab`, `/close`, `/tabs`, `/quit-all`, and `/restart-tab` are not exposed.
342
339
  - **Naming:**
343
340
  - Default names are `agent` or `agent N`.
344
341
  - When `tabsAutoName` is on (the default, by user decision), the tab's model proposes a hyphenated lowercase name of at most 16 characters after the first settled turn. It uses a small budget and one retry, with a fallback derived from the first prompt, and it runs inside the child.
345
342
  - A user rename sets `nameSource=user`, and auto-naming never overrides it.
343
+ - An automatic rename uses v2's title transition: 600 ms moving rainbow, typing at 45 ms per character, then a 300 ms settle. Manual renames do not animate.
346
344
  - Rename is inline in the chip: `Enter` commits, and `Esc` or a click elsewhere cancels. The name is trimmed, must not be empty, is at most 64 characters, and has control characters stripped.
347
- - **Close.** When a tab is `working` or `needs-input`, or its bridge reports queued input, the strip asks `Stop "<name>"? Enter stop · Esc cancel` before closing. Closing sends the child a graceful shutdown, waits a bounded time, terminates the tree, and removes the tab. Its session stays resumable. Closing the last tab leaves the strip with `+` and a hint.
345
+ - **Close.** `Alt+W`, the always-visible `×`, and the right-click menu close a tab. When it is `working` or `needs-input`, or its bridge reports queued input, the strip asks `Stop "<name>"? Enter stop · Esc cancel`. Closing sends the child a graceful shutdown, waits a bounded time, terminates the tree, and removes the tab. Its session stays resumable. Closing the last tab leaves the strip with `+` and a hint.
348
346
  - **Detach.**
349
347
  - `Ctrl+C` twice detaches from any tab and is handled by the attach client, so it works even when the bridge is down. Inside an A1 tab, `/quit` and empty-editor `Ctrl+D` send a detach request carrying the immutable command-origin controller identity and revision; stale, ambiguous or unavailable attribution keeps the tab running and directs the user to `Ctrl+C` twice.
350
- - The client restores the terminal. When tabs are still running, it prints `N tabs still running · run a1 to return`; otherwise it prints the resume hint.
351
- - `/quit-all` stops every tab, with confirmation if any is busy.
348
+ - The client restores the terminal. When agents are still running, it prints `1 agent still running run a1 to return.` or `N agents still running run a1 to return.`; otherwise it prints the resume hint.
352
349
  - **Launch and reattach:**
353
350
  - Bare `a1` ensures the server and attaches.
354
351
  - With zero tabs, it creates one A1 tab in the launch cwd.
355
352
  - Otherwise it views the last active tab from its retained surface without byte-history replay. Measure retained first paint and first input separately from bootstrap; status for the remaining tabs arrives without subscribing to their surfaces.
356
353
  - `a1 --session X` opens X in a new tab, or focuses the tab holding it.
357
354
  - A new tab's cwd is the client's launch cwd.
358
- - Prewarm (`tabsPrewarm`, default 1) keeps one hidden standby A1 tab ready after the survival/recovery milestones. Measure A1 warm promotion directly; v2's tens-of-milliseconds result is reference evidence only.
359
- - **Several terminals.** Each client has its own active tab, but each tab has one explicit input controller. A second client may view read-only or claim control through Decision 5's causal input barrier. Only the controller may send input or set PTY size. Delayed slash commands preserve their submission origin; stale or unprovable attribution affects neither client and falls back to attach-local actions.
355
+ - One internal prewarmed standby keeps the startup graph loaded in a hidden A1 process after a trusted tab has reported its session. Before promotion it owns no session or writer lease, has no registry/tab-strip entry, and does not count toward `tabsMax`. Creation promotes it only for an exact cwd, release, Node executable, and environment match; the server durably commits the new tab before exposing it to input. A mismatch discards it and normal creation retargets the next standby from the newly trusted tab. Standby launch or loss never changes visible tab state. It is not a setting. Measure A1 warm promotion directly; v2's tens-of-milliseconds result is reference evidence only.
356
+ - **Several terminals.** Each client has its own active tab, but each tab has one explicit input controller. Focus gain automatically claims the viewed tab through Decision 5's causal input barrier; if focus reporting is unavailable, first input starts the claim and is held until acknowledgement. There is no `Enter` takeover or persistent view-only banner. Only the current controller may send input or set PTY size. Delayed `/quit` and empty-editor `Ctrl+D` preserve their submission origin; stale or unprovable attribution affects neither client and falls back to attach-local double-`Ctrl+C`.
360
357
  - **Background attention.** Only the chip icon changes. There is no bell, sound, or notification, and no setting for one.
361
358
 
362
359
  ### 12. Limits and resources
363
360
 
364
361
  The settings live in the owned settings screen, each with a hard cap.
365
362
 
366
- - `tabsMax` defaults to 10, with a hard cap of 50. `tabsMaxConcurrentStarts` defaults to 2.
367
- - `tabsSuspendIdleAfterMinutes` defaults to 60, by user decision. An idle tab with no pending request, no queued input, and no viewer stops its child after that interval and shows `◌`. Focusing or prompting it resumes the session. Extension in-memory state is lost, which the settings text says.
363
+ - User-facing Tabs settings during resident launches are only `tabsMax` (default 10, range 1–50) and `tabsAutoName` (default on). Tab enablement is not a setting. Direct `a1 solo` and `a1 pi` launches omit the Tabs section entirely.
364
+ - Unresponsive restart and stall-notice timing are internal fixed behavior at 120 and 300 seconds; concurrent starts use internal default 2/hard cap 8 and prewarm uses internal count/hard cap 1.
365
+ - Every open tab keeps its holder and A1 process until explicit close. There is no idle-suspension setting, timer, lifecycle, or glyph.
368
366
  - Scrollback is capped at 10 MiB per holder, and the replay after a server restart is the retained model itself.
369
- - The server exits after 10 minutes with no running tabs and no clients.
367
+ - The server exits after 10 minutes with no attached client and no live user tab; the prewarmed standby does not keep it alive and is stopped first.
370
368
  - Structured diagnostic logs are `terminal-host-server.log` and `terminal-host-holder-<tab>.log`, rotated at 5 MiB with three kept. Use allowlisted fields/reason codes, not arbitrary exception strings, prompt text, terminal content, or credentials.
371
369
  - Raw child stderr cannot be declared sanitized: when available separately from ConPTY's combined terminal stream, store it only under the owner-only per-tab recovery area, at 5 MiB with three generations and a seven-day maximum age, alongside bounded last-screen files. Do not scrape stderr back out of terminal cells. These private artifacts may contain conversation or sensitive content and are excluded from `a1 tabs doctor`; neither their contents nor arbitrary extension errors enter structured crash records.
372
- - `a1 tabs host status` prints the server id, build, generation, detachment mode, tab count, and log paths.
370
+ - `a1 tabs host status` prints the server pid, build, release, generation, epoch, uptime, detachment mode, tab count by status, client count, configured and hard limits, volatile health counters since server start, and log paths.
371
+
372
+ #### Hard bounds (milestone 6 audit)
373
+
374
+ Every queue, buffer, file, and collection the resident roles keep has a declared cap. Each row names where the cap is enforced and the test that pushes past it.
375
+
376
+ | Bound | Cap | Enforced in | Test |
377
+ |---|---|---|---|
378
+ | Protocol frame | 2 MiB, nesting 64, 65,536 collection items | `native/terminal-host/src/protocol.rs`, `src/foundation/resident-tabs/protocol.ts` | `refuses_oversized_header_before_payload`; `refuses a frame header past 2 MiB before buffering its payload`, `refuses nesting past 64 levels`, `refuses a collection past 65,536 items` |
379
+ | Input per message and holder input queue | 1 MiB | `protocol.rs`, `core/holder.rs`, `roles/holder.rs` | `over_capacity_paste_delivers_no_prefix` |
380
+ | Per-client control lane | 1,024 frames and 4 MiB | `roles/server.rs` | `control_lane_is_bounded_by_frames_and_bytes` |
381
+ | Per-client render slot | one pending full surface; a newer one replaces it | `roles/server.rs` `Peer::offer_surface` | `stalled_client_gets_one_full_current_surface` |
382
+ | Concurrent endpoint connections | 128 | `roles/server.rs` `admit_connection` | `connections_beyond_the_cap_are_refused_and_released_slots_are_reused` |
383
+ | Attached viewing clients | 16 | `roles/server.rs` `client_slot_available` | `attached_clients_are_capped` |
384
+ | Open tabs | `tabsMax` (default 10, hard 50), including session-selection starts | `roles/server.rs` `tab.create`, `attach.start` | `ten_concurrent_tab_starts_respect_the_server_limit` |
385
+ | Concurrent holder starts | default 2, hard 8 | `core/server.rs` | `limits_starts_and_drops_stale_completion` |
386
+ | Stopped tab records | 16, oldest dropped first | `roles/server.rs` `prune_stopped_records` | `stopped_records_are_bounded_oldest_first_and_live_tabs_are_kept` |
387
+ | Prior tab-set archives | bounded history | `roles/registry.rs` | `prior_tab_sets_are_bounded` |
388
+ | Abandoned blocking workers | 8 | `shell/workers.rs` | `fails_fast_after_eight_abandoned_workers` |
389
+ | Diagnostic logs | 5 MiB × 3 generations per role | `roles/diagnostics.rs` | `logs_rotate_at_their_cap_and_keep_a_bounded_number_of_generations` |
390
+ | Crash records | 64 KiB each, 50 records, 30 days | `roles/diagnostics.rs`, `shell/watchdog.rs` | `crash_records_are_distinct_bounded_and_allowlisted`, `crash_record_is_bounded_and_owner_only` |
391
+ | Last screen and private stderr sink | 2 MiB; 5 MiB × 3 generations, 7 days | `roles/recovery_artifacts.rs` | `recovery_files_are_plain_bounded_private_and_rotated`, `fixture_stderr_sink_caps_content` |
392
+ | Prompt journal | 8 MiB, 2 MiB per record | `src/foundation/resident-tabs/prompt-journal.ts` | `bounds append size` |
393
+ | Session reconciliation read | 64 MiB | `src/foundation/resident-tabs/recovery.ts` | `refuses to scan a session past the 64 MiB reconciliation cap` |
394
+ | Release-hold report | 32 uncertainty reasons; 1 MiB read | `roles/release_holds.rs`, `release-holds.ts`, `bounded-host-command.ts` | `uncertainty_reasons_stay_within_their_cap`; `fails a release-hold report that pushes past its byte cap without returning a prefix` |
395
+ | Maintenance exchange | 5 s reads, 60 s stops, 4 MiB report | `roles/maintain.rs`, `maintenance.ts`, `bounded-host-command.ts` | `fails a maintenance report that pushes past its byte cap without returning a prefix`, `kills and rejects a command that outlives its deadline` |
396
+ | Attach client | no outbound queue: input is written synchronously in chunks of at most 1 MiB; inbound frames at most 2 MiB; one coalesced wake | `roles/attach.rs` | `refuses_oversized_header_before_payload`, `over_capacity_paste_delivers_no_prefix` |
397
+ | Doctor bundle | 25 MiB, oldest log generations dropped first | `src/foundation/resident-tabs/doctor.ts` | `drops the oldest log generations first to stay within its cap` |
373
398
 
374
399
  ### 13. Packaging and certification
375
400
 
376
- - `a1-terminal-host` is built and packaged for Windows x64, Linux x64, and macOS arm64, extending the impact-selected Windows CI owner introduced by #588 to that exact target matrix, with Zig 0.15.2 for pinned libghostty-vt. It ships in the immutable release with artifact manifest, hash verification, provenance, licenses, and notices.
377
- - Opt-in acceptance requires exact-package hermetic suites on each platform plus manual or isolated-worker evidence per platform for terminal close, SSH/session loss, reattach, input fidelity, extension text UI, failure recovery, rollback, and render smoothness. Evidence is never inferred from one platform to another.
378
- - Default-on support on any platform requires a later change with an authorized isolated-worker 24-hour soak on that platform.
401
+ - `a1-terminal-host` is built and packaged for Windows x64, Linux x64, macOS arm64, and macOS x64 (see the 2026-10-14 decision), extending the impact-selected Windows CI owner introduced by #588 to that exact target matrix, with Zig 0.15.2 for pinned libghostty-vt. It ships in the immutable release with artifact manifest, hash verification, provenance, licenses, and notices.
402
+ - Acceptance requires exact-package hermetic suites on each platform plus manual or isolated-worker evidence per platform for terminal close, SSH/session loss, reattach, input fidelity, extension text UI, failure recovery, rollback, and render smoothness. Evidence is never inferred from one platform to another.
403
+ - An authorized isolated-worker 24-hour soak remains required certification evidence on each platform; a miss blocks certification without silently changing the approved default or weakening fallback.
379
404
 
380
405
  ### 14. Security
381
406
 
@@ -387,7 +412,7 @@ The settings live in the owned settings screen, each with a hard cap.
387
412
 
388
413
  ### 15. Rollback
389
414
 
390
- - `residentTabs: false` restores direct bare-A1 launch. The server is not started, and the registry is preserved. The shared session-writer guard remains active, so fallback cannot write into a live tab's session.
415
+ - `a1 solo` starts the direct single-agent A1 UI without starting the server; the registry is preserved. The shared session-writer guard remains active, so solo or fallback cannot write into a live tab's session.
391
416
  - If the server cannot start, bare A1 falls back to the direct single-agent launch with one notice, instead of failing.
392
417
 
393
418
  ### 16. Reliability engineering
@@ -403,7 +428,7 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
403
428
  5. **Fencing.** The server must hold the OS-exclusive registry-writer lease; each start increments the durable epoch under that lease, each mutation verifies both lease and epoch, and holders and bridges obey only the highest epoch they have seen. A timed-out endpoint alone never authorizes a second writer.
404
429
  6. **Explicit durability per data class.** There is no "best effort". Each class of data has a stated guarantee, and each guarantee has a test.
405
430
  7. **Evidence survives failure.** Logs rotate but are never truncated at startup. Each crash produces its own record.
406
- 8. **Prove it in stages.** Deterministic simulation, crash-point injection, fuzzing, bounded CI chaos on each platform, and exact-package physical evidence per platform gate this opt-in preview. A separately authorized isolated-worker 24-hour soak on a platform gates default-on for that platform; evidence is never inferred across platforms.
431
+ 8. **Prove it in stages.** Deterministic simulation, crash-point injection, fuzzing, bounded CI chaos on each platform, and exact-package physical evidence per platform gate certification. A separately authorized isolated-worker 24-hour soak remains required platform evidence; evidence is never inferred across platforms. Every named crash point exists only in the `fault-injection` test build, and `A1_TABS_FAULT_CRASH_MARK` names the directory where a kill-at-point test reads the breadcrumb a crashed role leaves.
407
432
 
408
433
  **How each v2 failure is closed:**
409
434
 
@@ -411,7 +436,7 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
411
436
  |---|---|---|
412
437
  | A ConPTY spawn blocked forever, the hung daemon kept the pipe, and every window lost its agents | Synchronous `pty.spawn` on the shared loop | The PTY is created only inside that tab's holder, and the server kills that holder after a deadline and retries. Other tabs and the server are unaffected. |
413
438
  | The watchdog SIGKILLed the daemon after a 60 s stall, and every agent died | Agents were children of the daemon | Holders and children survive a server death. The watchdog kills only the stalled process. |
414
- | Synchronous `taskkill` loops blocked the daemon for 30–60 s | Blocking kills on the event loop | Kills run asynchronously and concurrently, with deadlines. The loop keeps answering heartbeats. |
439
+ | Synchronous `taskkill` loops blocked the daemon for 30–60 s | Blocking kills on the event loop | Kills run asynchronously and concurrently, with deadlines. The loop keeps answering heartbeats. |
415
440
  | Blocked ConPTY writes stalled the loop, and keystrokes or pastes were silently dropped ("newest wins") | No flow control | A dedicated writer per holder with a bounded queue. Reserve capacity for a complete paste before delivery; reject an over-capacity paste visibly without delivering a prefix. After admission, preserve byte order and report delivery uncertainty if a child or process fails; never silently truncate or retry accepted bytes. |
416
441
  | Terminal queries (DA/DSR/CPR) were answered by the busy shared emulator, so children stalled | One emulator loop for all agents | Each holder answers its own tab's queries from its own model. |
417
442
  | Repaints arrived in fragments ("ghost frames"), patched with timing heuristics and two screen sources | Emulation reconciled with bridge surfaces by timers | One source of truth, the holder model. Frames are published at synchronized-output boundaries, with a bounded coalescing window. |
@@ -423,7 +448,7 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
423
448
  | A failed persist plus a hot-swap re-read old state from disk and killed live agents as orphans | Rebuilding from disk over newer memory; the persist result was ignored | Memory stays authoritative on write failure: retry with backoff and report degraded health. There is no hot-swap, and processes are never killed because a registry read came back short. |
424
449
  | fsync was probably a no-op on Windows (file opened read-only), and the directory was never synced | Wrong handle access | Files are synced with write access and the directory is synced too. A test asserts the platform flush calls. |
425
450
  | Two processes appended to one session JSONL | Leases lived only in daemon memory | A canonical session-writer lock shared by every A1-owned launch mode, held by the writer process itself so the kernel releases it only when the writer is gone. |
426
- | Boot-identity rounding misclassified a crash as a reboot | `now − uptime` rounded to minutes | Each platform reads an exact native boot identity (Windows boot sequence/`LastBootUpTime`, Linux `/proc/sys/kernel/random/boot_id`, macOS `kern.boottime`) and always verifies process identity by pid plus native start time. |
451
+ | Boot-identity rounding misclassified a crash as a reboot | `now − uptime` rounded to minutes | Each platform reads an exact native boot identity (Windows boot sequence/`LastBootUpTime`, Linux `/proc/sys/kernel/random/boot_id`, macOS `kern.boottime`) and always verifies process identity by pid plus native start time. |
427
452
  | A persist storm: a synchronous pretty-printed JSON write on every status change | Status was persisted with lifecycle | Only lifecycle and identity mutations are persisted. Status is volatile and rebuilt from the bridge. Writes run off-loop. |
428
453
  | Hot-swapping the logic bundle caused generation bugs, lost deferred callbacks, and memory growth; kernel updates never applied | In-process code replacement, needed because restarts killed agents | No logic hot-swap. Crash recovery replaces a server while retaining its compatible cohort. Automatic upgrade handoff and release recycling remain deferred; idle suspension is not a cohort upgrade. |
429
454
  | Crash evidence was deleted: the log was removed at boot, crash logs truncated on respawn, a 256-line queue dropped lines | Log handling | Size-rotated generations that are never truncated at boot. A distinct crash record per incident with a backtrace. `a1 tabs doctor` produces a redacted bundle. |
@@ -447,26 +472,38 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
447
472
  - A streaming reply at the moment of a tab crash: readable from the last-screen snapshot and recovery file, but not resumable as a model turn. That is Pi's semantics.
448
473
  - The only unrecoverable case is a streaming reply when the holder *and* its child die together, for example a machine crash, because it was never persisted.
449
474
 
450
- **Verification program.** It runs incrementally across milestones 1–6 and gates the complete opt-in slice:
475
+ **Verification program.** It runs incrementally across milestones 1–6 and gates the complete resident slice:
451
476
 
452
477
  - Deterministic simulation and property tests of the sans-IO server core over death, loss, reordering, delay, stale epochs, controller transfer, churn, and mutation. Invariants include no lost committed mutation, one registry writer, one live incarnation per tab/session, attributable client-scoped requests, and convergence to running or failed.
453
478
  - Failure points at every persistence and IPC step, with kill-at-each-point crash tests.
454
479
  - `cargo-fuzz` targets for the protocol decoder and surface patch encoder.
455
480
  - Bounded CI chaos on each platform with high-rate fake agents, random process death, resize/attach churn, controller transfer, blocked writes, pseudoterminal creation hang, rename denial, and containment escape attempts.
456
- - Exact-package manual or isolated-worker physical acceptance on each platform. A later default-on proposal must additionally supply an authorized 24-hour soak on that platform with zero lost journaled prompts/committed entries, zero orphans/duplicates, bounded memory/handles, reattach p95 under 300 ms, tab restart under 3 s, and server recovery under 2 s.
481
+ - Exact-package manual or isolated-worker physical acceptance on each platform. Certification must additionally supply an authorized 24-hour soak on that platform with zero lost journaled prompts/committed entries, zero orphans/duplicates, bounded memory/handles, reattach p95 under 300 ms, tab restart under 3 s, and server recovery under 2 s.
482
+
483
+ **Local native iteration.** Zig never runs on a developer workstation, but native tests can, by linking the terminal library a CI lane built:
484
+
485
+ 1. `node scripts/development/fetch-terminal-vt.mjs [--branch <name>]` downloads this platform's `terminal-vt-<platform>-<architecture>` artifact from the branch's newest run that published one (default: the current branch) into `.artifacts/terminal-vt/`. If none exists, dispatch `gh workflow run ci.yml --ref <branch>`; native lanes keep it for 14 days.
486
+ 2. With `TERMINAL_HOST_LOCAL_BUILD=1` and `TERMINAL_HOST_PREBUILT_VT` naming that directory, `build.rs` verifies `terminal-vt.manifest.json` against the vendored `VERSION`, the Ghostty commit, the Cargo and Zig targets, and each file's SHA-256, then links it and skips Zig. A mismatch fails the build in one line and never falls back. Builds with `CI` set ignore the variable.
487
+ 3. Run one test: `cargo test --manifest-path native/terminal-host/Cargo.toml --features resident-fixtures --test role_integration <name>`.
488
+
489
+ Every chaos run prints its seed and records its executed action schedule in its report; `--seed <n>` fixes the seed, and `--replay <report.json>` re-executes a recorded schedule and reports actions it could not apply as divergence. Locally, `--build` is unavailable because it runs the release packaging build, which records the Zig version; build the two binaries through the prebuilt library instead with `cargo build --release --locked --manifest-path native/terminal-host/Cargo.toml --features resident-fixtures --bin terminal-host --bin fake-tab`, then pass `--host` and `--fake-tab` from `native/terminal-host/target/release`.
490
+
491
+ Known limit (2026-10-15): on a Windows desktop whose shells already run inside a job, a server started by a role test cannot leave that job, `verify_detached` rejects it, and tests that expect `started` fail locally while passing in CI; the release retention integration test, which runs whenever debug host binaries exist, fails the same way, and local chaos reports servers that holders did not replace. A separate change addresses this.
492
+
493
+ **Milestone 5 preview performance budgets (p95 on each exact platform build):** cold launch to first input is the direct startup budget plus 800 ms; matching-standby creation to first input is 250 ms; reattach first paint is 300 ms and first accepted input is 400 ms; input-to-process is 16 ms; output-to-present is 33 ms. Each result or miss is recorded independently for Windows x64, macOS arm64, and Linux x64. The platform-native validation lanes run `measure-resident-tabs.mjs` against their built host: its instrumented child reports process-admission timestamps through a real resident holder, while the outer test PTY records presentation, standby promotion, and retained-surface reattach timestamps.
457
494
 
458
495
  ## Risks / Trade-offs
459
496
 
460
- - **[A server crash interrupts the display for everyone]** → Holders keep processes and screens alive. The server holds no VT, Pi, or extension code. It is auto-respawned by any client or holder, and reattach restores exact surfaces.
461
- - **[Terminal fidelity in a composed surface]** (keyboard protocols, mouse, paste, IME, Unicode width, hyperlinks) → Reuse libghostty-vt encoding and the proof's input work, and add fidelity suites on each platform. Claims cover only the certified text-terminal contract; image protocols remain an explicit gap.
462
- - **[Per-tab memory multiplies with holder and A1 processes; historical estimates are not measured A1 bounds]** → `tabsMax`, idle suspension on by default, and throttled rendering while hidden via `bridge.visibility`.
463
- - **[Detachment is platform-specific and fragile]** (Windows jobs and SSH, macOS bootstrap namespaces, Linux logout policies) → Detection and escape live in the native binary per platform. The mode is reported, never assumed, and unverifiable survival falls back instead of claiming persistence.
464
- - **[Three platforms multiply native and certification work]** → Platform code is confined to the thin I/O shell around one shared sans-IO core, and milestone 1 proves every platform primitive before later milestones build on it.
465
- - **[The Zig and libghostty toolchain in CI and release]** → It is pinned already. Extend the #588 Windows owner and provenance gate to a macOS and Linux build matrix.
466
- - **[No image protocol in tabs at first]** → Pi's text fallback applies and the gap is documented. Forwarding is a follow-up.
467
- - **[Stale or duplicate processes after races]** → An OS-exclusive writer lease, current-epoch commits, one start gate, conditional registry revisions, derived credentials, and adoption only after identity verification.
468
- - **[Two clients type into one tab or a delayed slash command detaches the wrong terminal]** → One controller lease, a proven cross-channel input-admission barrier, immutable command-origin generations, and fail-closed ambiguous requests. A sideband acknowledgement alone is not sufficient.
469
- - **[Physical close, logout, and reboot cannot be tested hermetically]** → Owner-tree kills stand in for terminal close. Exact-artifact manual or isolated-worker records are required, and nothing is automated on an active workstation.
497
+ - **[A server crash interrupts the display for everyone]** → Holders keep processes and screens alive. The server holds no VT, Pi, or extension code. It is auto-respawned by any client or holder, and reattach restores exact surfaces.
498
+ - **[Terminal fidelity in a composed surface]** (keyboard protocols, mouse, paste, IME, Unicode width, hyperlinks) → Reuse libghostty-vt encoding and the proof's input work, and add fidelity suites on each platform. Claims cover only the certified text-terminal contract; image protocols remain an explicit gap.
499
+ - **[Per-tab memory multiplies with holder and A1 processes; historical estimates are not measured A1 bounds]** → `tabsMax`, one internal prewarmed standby, and throttled hidden rendering. Every open tab keeps its process until explicit close; there is no idle suspension.
500
+ - **[Detachment is platform-specific and fragile]** (Windows jobs and SSH, macOS bootstrap namespaces, Linux logout policies) → Detection and escape live in the native binary per platform. The mode is reported, never assumed, and unverifiable survival falls back instead of claiming persistence.
501
+ - **[Three platforms multiply native and certification work]** → Platform code is confined to the thin I/O shell around one shared sans-IO core, and milestone 1 proves every platform primitive before later milestones build on it.
502
+ - **[The Zig and libghostty toolchain in CI and release]** → It is pinned already. Extend the #588 Windows owner and provenance gate to a macOS and Linux build matrix.
503
+ - **[No image protocol in tabs at first]** → Pi's text fallback applies and the gap is documented. Forwarding is a follow-up.
504
+ - **[Stale or duplicate processes after races]** → An OS-exclusive writer lease, current-epoch commits, one start gate, conditional registry revisions, derived credentials, and adoption only after identity verification.
505
+ - **[Two clients type into one tab or a delayed slash command detaches the wrong terminal]** → One controller lease, a proven cross-channel input-admission barrier, immutable command-origin generations, and fail-closed ambiguous requests. A sideband acknowledgement alone is not sufficient.
506
+ - **[Physical close, logout, and reboot cannot be tested hermetically]** → Owner-tree kills stand in for terminal close. Exact-artifact manual or isolated-worker records are required, and nothing is automated on an active workstation.
470
507
 
471
508
  ## Milestone 1 implementation record
472
509
 
@@ -478,7 +515,7 @@ Generation 1 is the bounded little-endian named-map MessagePack contract in this
478
515
 
479
516
  ## Roadmap
480
517
 
481
- Each milestone is its own OpenSpec change and pull request, merged to `develop` behind `residentTabs: false`, so nothing user-visible changes until the preview is certified. A milestone starts only after the previous one merges, moves its requirements from [`resident-tabs-requirements.md`](resident-tabs-requirements.md) into its own spec delta, and covers Windows x64, Linux x64, and macOS arm64 together unless its change says otherwise.
518
+ Each milestone is its own OpenSpec change and pull request. Milestones 1–4 merged behind `residentTabs: false`; milestone 5 removes the toggle, makes tabs the bare-`a1` route, and adds `a1 solo` as the direct path. A milestone starts only after the previous one merges, moves its requirements from [`resident-tabs-requirements.md`](resident-tabs-requirements.md) into its own spec delta, and covers Windows x64, Linux x64, and macOS arm64 together unless its change says otherwise.
482
519
 
483
520
  | # | Change | Deliverable and exit evidence |
484
521
  |---|---|---|
@@ -486,17 +523,39 @@ Each milestone is its own OpenSpec change and pull request, merged to `develop`
486
523
  | 2 | `resident-tabs-persistent-tab` (PR #746) | The three native roles with one full A1 text UI, minimal authenticated registry and bridge, bounded I/O, explicit opt-in and direct fallback. Terminal and client closure, detached output, retained reattach, input, and surviving-owner terminal restoration. |
487
524
  | 3 | `resident-tabs-failure-isolation` (PR #747) | Two tabs, server replacement with unchanged holder/child identities, isolated holder/child failure and hang, slow client and blocked writer isolation, exclusive registry epochs, the session-writer lock wired into every A1 launch mode, and no duplicate starts. |
488
525
  | 4 | `resident-tabs-crash-recovery` (PR #748) | Crash restart into the session, durable-before-dispatch journaling, periodic drafts during continuous typing, correlation/retirement crash points, missing first-turn versus missing existing session, corruption/rename/disk failures, last-screen recovery, and sensitive-recovery/diagnostic separation. After reboot, previous sessions stay resumable, with no tab restore. |
489
- | 5 | `resident-tabs-tab-ux` (PR #749) | Strip, shortcuts, mouse, commands, rename/reorder/close, status and needs-input, detach and resume, two attached clients with the controller-transfer barrier, and extension text fidelity. Then auto-naming, one prewarmed tab, and 60-minute idle suspension with the recorded defaults. |
526
+ | 5 | `resident-tabs-tab-ux` (PR #749) | Approved v2-style strip with always-visible close buttons, per-agent accent status and needs-input, mouse, assignable shortcuts with unbound navigation/reorder actions, rename/reorder/close, local detach/resume, automatic two-terminal controller transfer, and extension text fidelity. Then animated auto-naming, `tabsMax`, and one internal prewarmed tab; open tabs are never idle-suspended. |
490
527
  | 6 | `resident-tabs-certification` (PR #750) | Packaging and release retention on every platform, maintenance commands, bounded resources, deterministic/property/crash/fuzz/chaos evidence and exact-package physical acceptance per platform, including SSH loss, forced attach death, conflict-safe direct rollback, and performance measured separately for cold launch, warm tab creation, reattach, and recovery. |
491
528
 
492
- A failed milestone keeps the preview disabled and its pull request open until fixed. Each milestone 2–6 PR is a draft that holds its full plan (proposal, design, tasks, spec deltas) and starts implementation only after the previous milestone merges.
529
+ A failed milestone keeps its pull request open until fixed and preserves safe direct fallback. Each milestone 2–6 PR is a draft that holds its full plan (proposal, design, tasks, spec deltas) and starts implementation only after the previous milestone merges.
530
+
531
+ ### Release state and known gaps
532
+
533
+ Per-platform state is recorded in `docs/architecture/evidence/resident-tabs/` using [the acceptance checklist](../manual-resident-tabs-acceptance.md). A platform is `CI-verified` until every physical item has a recorded pass on a published candidate, then `certified`.
493
534
 
535
+ | Platform | State | Automated evidence | Physical record |
536
+ |---|---|---|---|
537
+ | Windows x64 | CI-verified | terminal-host and resident-chaos lanes; publication build | [windows-x64](evidence/resident-tabs/windows-x64.md) |
538
+ | Linux x64 | CI-verified | terminal-host and resident-chaos lanes, bounded fuzzing; publication build | [linux-x64](evidence/resident-tabs/linux-x64.md) |
539
+ | macOS arm64 | CI-verified | terminal-host and resident-chaos lanes; publication build | [macos-arm64](evidence/resident-tabs/macos-arm64.md) |
540
+ | macOS x64 | CI-verified (build only) | publication build and identity check | [macos-x64](evidence/resident-tabs/macos-x64.md) |
541
+
542
+ Known gaps:
543
+
544
+ - Physical acceptance is pending on every platform; the user runs the macOS checks after milestone 6.
545
+ - The 24-hour soak per platform has not run and remains a separate authorization.
546
+ - Intel macOS has no automated behavior suite of its own: modular validation outcomes are keyed by platform without architecture, so the pull-request suites run on macOS arm64 only.
547
+ - Reboot and logout restore, CLI tabs, split layouts, remote attach, worktree isolation, agent coordination, automatic cohort handoff, and image protocols are follow-ups.
548
+ - Raw child stderr is not captured separately on any platform; milestone 4 provides only a tested private sink.
549
+ - Arbitrary child VT output is not fuzzed, because the fuzz crate cannot link the Zig-built `libghostty-vt`; the probe and terminal-fidelity steps cover that path.
550
+ - Downgrading to a release without the session-writer guard while tabs run is unsupported; stop the host first.
551
+ - A maintenance stop of an unresponsive host uses an ordinary replacement server that is told to stop every tab at once, rather than a dedicated maintenance-only server mode.
552
+ - Revisioned, atomic topology mutations remain a target requirement in [`resident-tabs-requirements.md`](resident-tabs-requirements.md).
494
553
  ### Follow-ups outside this roadmap
495
554
 
496
555
  - **Reboot and logout restore:** reopen the previous tab set after a new OS session.
497
556
  - **Work isolation:** explicit task-to-repository/worktree ownership, dirty-tree policy, concurrent-edit conflicts, and review/integration ownership. A tab's cwd is not an isolation guarantee.
498
557
  - **Coordination:** task identities, delegation, prompt/result exchange, cancellation, permission and cost limits, and parent/child lifecycle. Herdr's agent start/prompt/read/wait API is a reference, not functionality included here.
499
- - **Presentation and enablement:** generic CLI tabs, split layouts, remote attachment, automatic cohort handoff, and default enablement after a 24-hour soak per platform.
558
+ - **Presentation and lifecycle:** generic CLI tabs, split layouts, remote attachment, and automatic cohort handoff.
500
559
 
501
560
  Each follow-up needs its own approved change.
502
561
 
@@ -507,16 +566,29 @@ The user made these decisions on 2026-09-24:
507
566
  - Every tab is a terminal session running the full A1 UI, so extensions stay native and CLI tabs can follow. This replaces the structured-worker design.
508
567
  - There is one tab set per profile.
509
568
  - LLM auto-naming is on by default.
510
- - Idle suspension is on after 60 minutes.
511
- - Icons are the only attention signal, with no bell, sound, or notification: spinner = working, `✓` = finished, red `✗` = failed, yellow `?` = needs the user.
569
+ - Icons are the only attention signal, with no bell, sound, or notification: spinner = working, `✓` = finished, red `✗` = failed, yellow `?` = needs the user.
512
570
  - `Ctrl+C` twice leaves `a1` from any tab while tabs keep running. There is no separate detach key.
513
571
  - Reliability is a first-class, release-gating requirement (Decision 16).
514
572
 
573
+ On 2026-10-14 the user resolved the milestone-6 `darwin-x64` question:
574
+
575
+ - Add an Intel macOS runner (`macos-15-intel`) for both the process guardian and the terminal host, so every release carries `darwin-x64` binaries beside `darwin-arm64`. Intel macOS is certified separately from Apple silicon: it needs its own physical acceptance record, and the chaos group runs on macOS arm64 only.
576
+
577
+ On 2026-10-13 the user refined milestone 5:
578
+
579
+ - Match the v2 default strip appearance, add an always-visible `×`, and color each working spinner with that tab's resolved A1 accent.
580
+ - Keep `Alt+A`, `Alt+W`, `F2`, and numbered jumps; leave next/previous and move actions unbound but assignable.
581
+ - Add no tab-management slash commands; retain existing `/name`, `/quit`, and empty-editor `Ctrl+D` semantics.
582
+ - Automatically transfer control to the active terminal through the causal barrier without an `Enter` takeover or view-only banner.
583
+ - Remove the tab enable/disable setting, always show `tabsMax` and `tabsAutoName` under Tabs during resident launches, and add `a1 solo` for direct single-agent launches with no tab strip or Tabs settings section. Keep unresponsive-restart/stall timing, concurrent starts, and one prewarm internal.
584
+ - Keep every open tab process running until explicit close; remove idle suspension and its `◌` glyph.
585
+ - Use `1 agent still running run a1 to return.` / `N agents still running run a1 to return.` after local detach.
586
+
515
587
  On 2026-10-10 the user confirmed this resident design over in-process tabs and set the first version's scope:
516
588
 
517
- - Deliver as six milestone pull requests, each merged behind `residentTabs: false`; PR #586 carries this roadmap and milestone 1.
589
+ - Deliver as six milestone pull requests; milestones 1–4 merge behind `residentTabs: false`, while the later milestone-5 decision above removes that setting and makes resident tabs the bare-A1 route.
518
590
  - Target Windows x64, Linux x64, and macOS arm64 in the first version; every additional target requires its own approved plan and evidence.
519
591
  - Keep crash recovery (restart into the session, prompt journal, drafts); drop reboot and logout restore to a follow-up.
520
- - Keep auto-naming, idle suspension with prewarm, and two attached clients.
592
+ - Keep auto-naming, one prewarmed standby, and two attached clients.
521
593
  - Hosting only: worktree isolation and agent coordination are separate follow-ups.
522
594
  - Retire the earlier `evolve-bare-a1-into-multi-agent-workspace` plan (#745); split layouts are a follow-up idea.