projmux 0.15.3 → 0.16.0

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.
package/README-ko.md CHANGED
@@ -72,8 +72,8 @@ projmux create claude --project mobile-client -- "마이그레이션 계획 초
72
72
 
73
73
  - [Resource Inspector](docs/resource-attribution.md) — 프로젝트·창·pane별 CPU와
74
74
  RSS를 실시간으로 봅니다.
75
- - [Session State](docs/session-restore.md) — 창 배치와 작업 디렉터리를 스냅샷으로
76
- 남깁니다.
75
+ - [Project Startup](docs/session-restore.md) — 닫힌 프로젝트를 Registry에 저장된
76
+ 창 구성으로 이어 열거나 새로 만듭니다.
77
77
  - 검색 루트, 키 설정, 업데이트는 설정에서 바꿉니다.
78
78
 
79
79
  ## 요구 사항
@@ -93,8 +93,7 @@ projmux create claude --project mobile-client -- "마이그레이션 계획 초
93
93
  [상태 표시줄](docs/statusbar.md) ·
94
94
  [훅](docs/hooks.md) ·
95
95
  [사용량 추적](docs/usage-tracking.md) ·
96
- [운영 진단](docs/operational-diagnostics.md) ·
97
- [에이전트 작업 흐름](docs/agent-workflow.md)
96
+ [운영 진단](docs/operational-diagnostics.md)
98
97
 
99
98
  ## 개발
100
99
 
package/README.md CHANGED
@@ -72,7 +72,8 @@ Templates and naming conventions are in
72
72
 
73
73
  - Live per-project, per-window, and per-pane CPU/RSS in the
74
74
  [Resource Inspector](docs/resource-attribution.md).
75
- - Layout and cwd snapshots in [Session State](docs/session-restore.md).
75
+ - Registry-based Continue project and Clear layout and open in
76
+ [Project Startup](docs/session-restore.md).
76
77
  - Search roots, keybindings, and updates in Settings.
77
78
 
78
79
  ## Requirements
@@ -92,8 +93,7 @@ Templates and naming conventions are in
92
93
  [Statusbar](docs/statusbar.md) ·
93
94
  [Hooks](docs/hooks.md) ·
94
95
  [Usage tracking](docs/usage-tracking.md) ·
95
- [Operational Diagnostics](docs/operational-diagnostics.md) ·
96
- [Agent Workflow](docs/agent-workflow.md)
96
+ [Operational Diagnostics](docs/operational-diagnostics.md)
97
97
 
98
98
  ## Development
99
99
 
@@ -33,12 +33,33 @@ execution guard, use the existing bounded command without that flag:
33
33
  projmux agent message send uid:<original-source-agent> --reply-to <original-request-ref> -- '<corrected reply>'
34
34
  ```
35
35
 
36
+ Operator input from the web client has no Agent route to reverse, so a reply
37
+ to it is refused with `explicit-reply-operator-origin` and stores nothing.
38
+
39
+ Within the same provider session, a reply also commits after the Claude lease
40
+ helper was replaced, for example by compact. The new helper did not push the
41
+ original, so it reads the original from the durable store and judges it by the
42
+ same checks. It reads the store only while its own route is the Registry's
43
+ current authority for the Agent; a replaced or unregistered helper reads and
44
+ writes nothing. `broker-reply-original-not-found` means the original is in
45
+ neither the helper nor the store. `invalid-explicit-reply-correlation` means the
46
+ reply's route or conversation does not match the original, or the helper is not
47
+ the current one.
48
+
36
49
  A same-ref call returns the original immutable receipt and never pushes again.
37
50
  Changing its payload is refused with the earlier ref and cause. A fresh ref
38
51
  allows one new attempt only when every previous attempt is known-zero. Failed
39
52
  records and the original request remain available; recovery never deletes or
40
- resets them. Store capacity can refuse a new attempt rather than discard its
41
- history.
53
+ resets them.
54
+
55
+ Store capacity treats the two kinds of attempt differently. A first attempt,
56
+ one whose original has no stored attempt yet, is a new acceptance rather than a
57
+ recovery, so a full store reclaims room for it under the same rule a new
58
+ message uses, while pinning the original and every attempt already stored
59
+ against it. A recovery attempt, which follows an earlier known-zero attempt, is
60
+ still refused with a capacity error rather than discarding the history it is
61
+ recovering from. Either way a reclaimed record moves to the history log below
62
+ instead of being deleted, and a store with nothing reclaimable refuses both.
42
63
 
43
64
  Delivered replies, partial writes, unknown outcomes, pending attempts, expired
44
65
  deadlines, and stale routes must not be resent. Inspect their status and the
@@ -47,3 +68,62 @@ the stored state and specific zero-write reason must agree. These rules also
47
68
  apply after store reload and to concurrent callers. Automatic resend is
48
69
  disabled, and replies whose delivery target is Codex do not gain a new retry
49
70
  policy here.
71
+
72
+ ## Reclaimed records move to a history log
73
+
74
+ The store is a bounded hot inbox. When it accepts a new message, or a first
75
+ explicit reply attempt, it first reclaims records that went terminal more than
76
+ 24 hours ago, and then, if it is still at its record limit, the oldest
77
+ unprotected terminal record. Before either step it expires any accepted or
78
+ held record whose deadline has passed, exactly as a status read would, so such
79
+ a record becomes terminal at that moment and follows the same reclaim rules;
80
+ its 24 hours count from that expiry, not from its deadline. Reclaiming is not
81
+ deleting: every reclaimed record is appended to
82
+ `<state>/agent-messages/history.jsonl`, one JSON object per line, under the same
83
+ file lock and with the same private directory and file permissions as the store.
84
+
85
+ The append is fsynced before the store renames its own new file into place, so a
86
+ crash in that window can leave a record in both the store and the log, but never
87
+ in neither.
88
+
89
+ Each line carries the envelope's own key names:
90
+
91
+ ```json
92
+ {"schemaVersion":1,"evictedAt":"…","reason":"retention","adapter":"claude-coordination",
93
+ "messageRef":"…","conversationRef":"…","replyTo":"…",
94
+ "state":"delivered","deliveryReason":"unspecified","handoffObserved":false,
95
+ "acceptedAt":"…","terminalAt":"…","payloadBytes":466,
96
+ "source":{"agentUID":"…","provider":"…"},
97
+ "target":{"agentUID":"…","provider":"…"}}
98
+ ```
99
+
100
+ `reason` is `retention` for the 24-hour rule and `capacity` for the record
101
+ limit. `replyTo` is omitted when the record is not a reply. `outcomeUnknown`
102
+ appears, as `true`, only on a failed record whose outcome is unknown; it is
103
+ absent otherwise. `source` and `target` carry only `agentUID` and `provider`:
104
+ the Pane and activation generation fence a live delivery, and the incarnation
105
+ follows the provider conversation; all three mean nothing once the record has
106
+ left the store. The envelope's `deadline` is not written. A line for operator
107
+ input (see [Operator input](claude-coordination-endpoints.md#operator-input)) carries
108
+ `"origin":{"kind":"operator","client":"web"}` in place of `source`; an Agent
109
+ message's line has no `origin`. Every other key is always present.
110
+
111
+ Lines written by earlier builds may still be in the same file. They carry full
112
+ routes (`paneUID`, `activationGeneration`, `incarnation`), a `deadline`, and
113
+ `outcomeUnknown` even when it is `false`, and they read the same way: a reader
114
+ ignores keys it does not expect and reads an absent key as its zero value, so
115
+ both shapes give the same edges, states and times. Both are `schemaVersion` 1.
116
+
117
+ `schemaVersion` starts at 1 and follows the same rule as the
118
+ coordination frame's field of that name: an absent or zero value reads as 1, and
119
+ a reader that meets a higher version reads the fields it knows rather than
120
+ discarding the line or failing. It is independent of the durable envelope
121
+ version and of the store file version.
122
+
123
+ The log does not keep the message body. It records `payloadBytes`, the original
124
+ payload length, and has no `payload` key at all: the log is unbounded in time,
125
+ and its intended consumers need the message graph, not its text.
126
+
127
+ The active log rotates to `history.jsonl.1` once it would pass 8 MiB, replacing
128
+ any earlier `history.jsonl.1`. At most two generations exist, so the log's disk
129
+ use is bounded even though its history is not complete.
@@ -53,11 +53,12 @@ Everything after `--` reaches the configured provider executable. Placeholder
53
53
  model, permission, and agent flags are private customization examples, not
54
54
  project defaults. Omit the separator when there is no payload.
55
55
 
56
- Settings > AI Settings > Enabled providers controls whether Claude, Codex, and
57
- Antigravity may launch. Canonical create routes respect that setting. There is
58
- no shared command-line override for a disabled provider; change Settings when
59
- the provider should become available. For a plain shell split use `projmux
60
- create pane`, not an Agent provider.
56
+ `Settings > Global > AI > Enabled providers` controls whether Claude, Codex,
57
+ and Antigravity may launch. Canonical create routes respect that setting.
58
+ There is no per-launch override for a disabled provider; enable it in Settings
59
+ or with `projmux config providers --enable <id>` (`--disable <id>` turns one
60
+ off), which goes through the same writer. For a plain shell split use
61
+ `projmux create pane`, not an Agent provider.
61
62
 
62
63
  Interactive provider and resume selection belong to the app's picker surfaces.
63
64
  CLI automation should choose an explicit provider or use `projmux agent resume
@@ -131,7 +131,7 @@ procfs collector supplies PID+starttime identity, SID, CPU ticks, RSS, and host
131
131
  capacity. Pure aggregation builds pane, unique-window, and project rows without
132
132
  using labels, topics, titles, or cwd-derived names as ownership keys.
133
133
 
134
- Resource snapshots are not Session State and are never saved or restored. See
134
+ Resource snapshots are in-memory only and are never saved or restored. See
135
135
  [resource-attribution.md](resource-attribution.md) for metric, partial-state,
136
136
  host-remainder, privacy, and measurement contracts.
137
137
 
@@ -144,7 +144,7 @@ CLI information architecture v2 resource routes.
144
144
  Packages:
145
145
 
146
146
  - `internal/core/metadata` is pure: the resource model, validation, name
147
- allocation, schema migration, snapshot reconciliation, and the operation
147
+ allocation, schema migration, and the operation
148
148
  transaction. It performs no I/O; the clock, uid source, and root-directory
149
149
  probe are injected through `Mutator`.
150
150
  - `internal/core/resourcegraph` is pure: the resolved resource graph that joins
@@ -171,7 +171,7 @@ Packages:
171
171
  fills a `resourcegraph.Inventory` from one exact server.
172
172
  - `internal/integrations/tmuxopts` is a dependency-free leaf holding the
173
173
  canonical spelling of every projmux-owned tmux option name, so the generated
174
- tmux config, session-state replay, and the resource mirror cannot drift.
174
+ tmux config and the resource mirror cannot drift.
175
175
 
176
176
  Resources and ownership:
177
177
 
@@ -216,6 +216,50 @@ Resources and ownership:
216
216
  live only in runtime inventory, outside the Project hierarchy. A
217
217
  ControlSession is not a counter-example: it is a root resource that *names* a
218
218
  session, and the session still carries no identity of its own.
219
+ - `Project.status.session` is the **last recorded** projection, not a live
220
+ read. It holds `name`, `live`, and `socketPath`: the exact absolute socket
221
+ path of the tmux server the session was last created or observed live on,
222
+ as the writing route verified it against the server's own `#{socket_path}`
223
+ (never derived from a socket name; empty when that route had no verified
224
+ path). Registration records the name with `live=false` and no path. Create,
225
+ materialize, Project startup, and legacy import write `live=true` with the
226
+ path of their route. A successful managed Project stop writes `live=false`
227
+ only after the exact Session is observed absent, keeping the name and
228
+ `socketPath`; deleting a Project's last valid primary Window lowers `live`
229
+ the same way. The full reconciler pass (`refreshSessionProjections`, run by
230
+ the create routes and by controller convergence) records `live=true` with
231
+ this pass's path for every Project whose session is on the server it
232
+ observed. It lowers by absence only a projection recorded on that server or
233
+ on no server, keeping its name and path; a projection recorded on another
234
+ server is left as it is, and a pass without a verified path lowers none that
235
+ record a path. The rule is `SessionAbsenceAttributableTo`.
236
+ `reconcile resources` scopes that pass to Projects with an observed
237
+ live Session, and outside that scope it lowers a `live=true` projection
238
+ whose `socketPath` is exactly the reconciled server's path and whose session
239
+ is absent there, keeping its name and `socketPath`; Projects with another or
240
+ no `socketPath` are untouched, because absence from one server is not
241
+ evidence about a session recorded on another. The one judgement is
242
+ `Mutator.LowerProjectSessionsEndedOnServer`.
243
+ The `window-unlinked` hook, which a raw `kill-session` fires, applies the
244
+ same judgement on its fast path: once its lifecycle stage has observed the
245
+ host, it lowers a live projection recorded on the hook's exact verified
246
+ server whose session is gone there, re-reading that server's sessions under
247
+ the Registry lock. It does not run the full pass, and when no live
248
+ projection records that server it costs the hook no extra tmux call.
249
+ - When the exact server `reconcile resources` targets is not running (its
250
+ runtime authority read fails with a missing-server signature, and only
251
+ then), the same judgement runs with an empty present set against the exact
252
+ target path: the `--socket-path` or inherited `$TMUX` path itself, or for
253
+ `--socket <name>` the path tmux would use for that label (the first existing
254
+ `tmux-<uid>` directory under `$TMUX_TMPDIR`, then `/tmp`, resolved through
255
+ symlinks). Every projection recorded `live=true` on exactly that path is
256
+ lowered in one Registry commit, keeping its name and `socketPath`, and the
257
+ receipt states the absent server and the lowered count; nothing else is
258
+ planned, written, or reobserved, and `--dry-run` previews the same items.
259
+ When nothing is left to lower, or the path cannot be resolved, the command
260
+ fails at the runtime authority stage exactly as before and writes nothing.
261
+ Any other authority failure (a refused or unreadable socket, a timeout, a
262
+ server that is not app-owned) never lowers anything.
219
263
  - `Window` and `Pane` carry **no stored liveness field**, deliberately. Their
220
264
  `status` block holds observed conditions only; live/offline is derived from a
221
265
  live tmux observation at read time. See *Runtime observation and resource
@@ -321,7 +365,7 @@ support reports expose those counts but redact item reasons and identifiers.
321
365
  Identity and naming:
322
366
 
323
367
  - `metadata.uid` is opaque, immutable, and independent of tmux lifecycle. It
324
- survives snapshot/restore, runtime creation, and root rebind.
368
+ survives runtime stop/Continue, runtime creation, and root rebind.
325
369
  - `metadata.name` is the stable unique-within-scope query key. Project names
326
370
  and ControlSession names are unique within their own root kind across the
327
371
  registry. Window, Pane, and Agent names are unique by
@@ -335,6 +379,21 @@ Identity and naming:
335
379
  or numeric suffix allocator. An explicit `--name` keeps its original spelling
336
380
  after validation; explicit create and rename collisions fail with exit code 2
337
381
  and zero Registry, tmux, or provider writes.
382
+ - A newly registered Project is the one exception. `create project` without
383
+ `--name`, and every implicit registration (first open of a directory,
384
+ `projmux shell`, Open fresh), name the Project after its root directory: the
385
+ root basename run through the same sanitizer as any name seed (`my repo`
386
+ becomes `my-repo`). If that basename sanitizes to nothing (the filesystem
387
+ root) or another Project already holds it, the Project falls back to the
388
+ exact-UID rule above -- never a numbered variant, never a `project`
389
+ placeholder. Legacy/orphan import and every Window, Pane, Agent, and
390
+ ControlSession keep exact-UID automatic names, and no stored name is ever
391
+ rewritten.
392
+ - Open fresh replaces the Project and carries its name over only when that
393
+ name is not shaped like a minted Project UID (`proj-` plus a full canonical
394
+ UID payload). An operator-chosen name such as `proj-front` survives; a
395
+ UID-shaped name -- the old exact-UID automatic name, or one copied from a
396
+ predecessor -- is dropped so the replacement is named after its root.
338
397
  - `create agent` supplies an **explicit** name for the Pane its Agent owns:
339
398
  `<agent-name>-pane`, derived from the Agent's own name. That used to be a
340
399
  documented follow-up `rename pane` a launcher had to remember, so a caller
@@ -359,6 +418,27 @@ Identity and naming:
359
418
  context may duplicate and is never a selector, reservation, ownerRef, or
360
419
  durable identity input. `metadata.labels` remains key/value classification;
361
420
  `metadata.annotations` remains non-identifying metadata such as an AI topic.
421
+ - Creator provenance: when an explicit `create agent` (every spelling,
422
+ `--create-window`, and each Agent of a fan-out) or `create window --provider`
423
+ runs inside an Agent's managed Pane, the new Agent and its managed Pane carry
424
+ `projmux.io/creator-agent` (the creator Agent's bare UID),
425
+ `projmux.io/creator-pane` (that Agent's managed Pane's bare UID), and
426
+ `projmux.io/creator-basis: pane-chain`, written in the same transaction that
427
+ commits the Agent. They are recorded only when the unmasked ambient
428
+ `%N` (`__PROJMUX_RUNTIME_ANCHOR_PANE`, then `TMUX_PANE`) is exactly one live
429
+ Registry Pane, that Pane round-trips with its owning Agent's
430
+ `status.paneRef`, one `display-message` confirms it on the create's own
431
+ app-owned socket and server pid, and the create process descends from its
432
+ `#{pane_pid}`. Any failed check writes none of the keys and changes nothing
433
+ else about the create. Stderr gets one `creator not recorded: <reason>` line
434
+ only when the ambient Pane is a live Agent Pane but a later check fails; an
435
+ ambient Pane that is malformed, unregistered, or a Window-owned shell is
436
+ silent. These keys are provenance, not
437
+ authentication, like a message `--source`. An absent key does not mean a
438
+ human created the Agent: UI intent creates (picker, pane menu, `ai split`,
439
+ launch choice) and creates a Codex Agent issues (its
440
+ commands run under the app-server, not below the Pane's process) leave them
441
+ empty, and nothing backfills older Agents.
362
442
 
363
443
  Root lifecycle:
364
444
 
@@ -519,7 +599,8 @@ Exit reconciliation and lifecycle projection:
519
599
  - Abnormal, killed, unknown, whole-host absence, missing/empty server inventory,
520
600
  permission failure, foreign Window observation, stale generation, and an
521
601
  Agent that now binds a resumed Pane all produce delete-plan zero. They keep the
522
- retained lifecycle projection and canonical explicit Offline delete recovery.
602
+ retained lifecycle projection and the canonical exact-uid Registry-only
603
+ delete recovery of a paneless Offline or Failed Agent.
523
604
  - The closed Agent transition table stays the authority. An Agent that may not
524
605
  reach the implied phase keeps its phase, its `paneRef`, and its managed Pane;
525
606
  only the evidence is recorded. A refused transition is not a reason to discard
@@ -736,9 +817,8 @@ Registry file and schema:
736
817
  directory-existence and uid adapters.
737
818
  - The canonical anchor is a schema-v2 write invariant. Until the separately
738
819
  planned Project-start projection lands, the legacy `New` startup path's
739
- prune-to-zero transaction fails validation and commits zero Registry bytes.
740
- The Registry verdict precedes snapshot deletion, so that rejection also
741
- preserves the latest snapshot byte-for-byte and performs no tmux mutation.
820
+ prune-to-zero transaction fails validation and commits zero Registry bytes
821
+ and performs no tmux mutation.
742
822
  This fail-closed ordering is not Phase 3 authority to redesign Project start.
743
823
  - Downgrade writes remain unsupported. Unversioned, malformed, and future
744
824
  envelopes still fail closed before backup, staging, or replace.
@@ -967,21 +1047,11 @@ Resolved resource graph (`internal/core/resourcegraph`):
967
1047
  process, and no tmux, so the same inputs always produce byte-identical output
968
1048
  and a read can never materialize state.
969
1049
 
970
- Session State interoperability:
971
-
972
- - Session snapshots carry resource identity through additive `omitempty`
973
- `metadata` blocks at the unchanged snapshot `version: 1` — one for the owning
974
- Project at the top level, one per Window, and one per Pane, each with
975
- `uid`, `name`, `labels`, `owner_kind`, and `owner_uid` in the snapshot's own
976
- snake_case spelling. No schema bump was needed, and a snapshot written
977
- without resource metadata still serializes byte-identically to the older form.
978
- - Snapshots written before resource metadata existed still project
979
- deterministically into an explicitly selected, closed Registry Project:
980
- existing Windows and Panes are reused positionally in Registry order and any
981
- additional descendants receive new stable identities. Restore validates a
982
- pure Project-scoped plan, atomically commits that desired subtree, and only
983
- then invokes the ordinary Project materializer. It never directly replays
984
- snapshot topology into tmux and never replaces the global Registry.
1050
+ Saved Project state:
1051
+
1052
+ - The Registry is the only saved Project state. projmux keeps no separate
1053
+ Project snapshot store, and a closed Project starts only from its Registry
1054
+ desired state through the ordinary Project materializer.
985
1055
 
986
1056
  tmux transport mirror:
987
1057
 
@@ -1000,7 +1070,7 @@ tmux transport mirror:
1000
1070
  every other mirror goes through, on the same plain `tmux` transport the session
1001
1071
  was created on. The write is gated strictly on "this open registered the
1002
1072
  Project": every already-registered Project converges through the Registry
1003
- topology engine, including desired state previously committed from a snapshot,
1073
+ topology engine,
1004
1074
  and opening `$HOME` mints no managed identity at all, so neither writes a mirror
1005
1075
  option through this first-open gate. That gate is also what makes repeating an
1006
1076
  open write nothing. Repairing a session that is already live without its
@@ -1068,8 +1138,8 @@ Runtime observation and resource status:
1068
1138
  precedence contract is unchanged, and it now applies to a Window or Pane whose
1069
1139
  owning Project lost its root even while tmux is still running them.
1070
1140
  - A **Project** is the one kind whose runtime object is a tmux *session*, which
1071
- has no `@projmux` uid of its own, so Project status still reads
1072
- `status.session` as refreshed by the reconciler.
1141
+ has no `@projmux` uid of its own, so Project status still reads the stored
1142
+ `status.session` projection (see *Resource metadata model* for its writers).
1073
1143
  - An **Agent** owns no tmux object of its own — there is no `@projmux_agent_uid`
1074
1144
  and there must not be one, because an Agent outlives the managed Pane it is
1075
1145
  bound to. Its runtime object is **that managed Pane**, named by
@@ -1284,7 +1354,7 @@ Lifecycle trigger convergence:
1284
1354
  transaction deletes exactly that Window, its Panes, its owned Agents, and
1285
1355
  their reservations. A non-last Project Window reanchors to its existing
1286
1356
  sibling. The last Project Window leaves the exact Project uid, root,
1287
- reservation, pins, and snapshot bytes in the valid zero-Window state. A
1357
+ reservation, and pins in the valid zero-Window state. A
1288
1358
  ControlSession likewise loses only the Window and keeps its root uid.
1289
1359
  Abnormal/killed/unknown exits, stale generations, unpaired or foreign handles,
1290
1360
  unavailable/empty observations, and missing-server or permission failures
@@ -1298,11 +1368,9 @@ Lifecycle trigger convergence:
1298
1368
  managed runtime and preserves every desired UID. Continue on retained-window
1299
1369
  writes only runtime and materializes the same descendant UIDs; Continue on
1300
1370
  zero-window atomically allocates one canonical Window/shell below the same
1301
- Project UID before runtime materialization. The deleted+Continue cell accepts
1302
- only a usable-snapshot precondition, then atomically creates a new Project UID
1303
- and restores new descendant UIDs from that snapshot; without the precondition
1304
- the same cell is an unavailable zero-write refusal and never falls back to
1305
- Fresh. Fresh atomically replaces either
1371
+ Project UID before runtime materialization. The deleted+Continue cell is
1372
+ always an unavailable zero-write `project-is-not-registered` refusal that
1373
+ points to Fresh and never falls back to it. Fresh atomically replaces either
1306
1374
  registered state with a new Project/Window/shell UID chain and exactly one
1307
1375
  same-root claimant. Within this runtime/startup lifecycle table, canonical
1308
1376
  `delete project --yes` alone unregisters the Project graph; the separately
@@ -1370,6 +1438,25 @@ Projmux split UI:
1370
1438
  The provider and shell branches render the exact argv an operator would type,
1371
1439
  so a UI action and a typed command cannot disagree about what `--placement
1372
1440
  down` means.
1441
+ - A split picker running in a popup does not call that route itself. tmux
1442
+ closes a popup only when the process inside it exits, so the picker hands its
1443
+ intent to a detached `run-shell -b` continuation on the same server
1444
+ (`internal agent-pane launch-selection`, carrying the origin Pane, client,
1445
+ and context directory as env) and exits. The continuation
1446
+ calls the same create funnel; a success writes nothing and anything else is
1447
+ one bounded line on the pressing client. Every selection travels: it is a
1448
+ plain value with no live handle left in the picker process.
1449
+ - A generated Window create asks before it commits. It opens the split picker in
1450
+ answer mode (`internal tmux popup-toggle --answer <file>`) on the Pane the key
1451
+ was pressed in; the picker writes the selection -- the same argv the
1452
+ continuation carries, read back by the same parser -- to a 0600 file its
1453
+ producer made, and creates nothing. The producer then commits the Window and
1454
+ the answer in one Registry transaction -- for an Agent, the same shape
1455
+ `create window --provider` commits: the Agent Pane splits off the new shell
1456
+ and the shell is retired -- and moves the pressing client onto it last. An
1457
+ Agent that cannot be opened rolls the whole Window back and leaves one line on
1458
+ the pressing client. A cancelled picker leaves the file empty and nothing is
1459
+ created.
1373
1460
  - Only the materializer runs `split-window`. Before this convergence the saved
1374
1461
  default and both pickers descended into a legacy split that called tmux
1375
1462
  directly, so a pane opened from the UI was a runtime object the Registry had
@@ -1681,15 +1768,14 @@ Explicit Registry topology materialization:
1681
1768
  deletion removes that desire. Exact uid/name/owner mirrors are retained. Stored
1682
1769
  Pane CWD drives only that Pane's detached runtime cwd, while Project root
1683
1770
  remains the session path anchor and `PROJMUX_CWD` hook value.
1684
- `Pane.spec.command`, snapshot recipes, notifications, and ephemeral sessions
1771
+ `Pane.spec.command`, notifications, and ephemeral sessions
1685
1772
  are never execution inputs.
1686
1773
  - An Agent whose managed Pane is not live is replayed into a new managed Pane on
1687
1774
  its Window's proven anchor, through the same allocation, activation ledger,
1688
1775
  ownership-checked adoption, and rollback the shell half uses. The **only**
1689
1776
  replay identifier is Registry `status.sessionRef`: no provider conversation
1690
1777
  store is read, `ClaudeSessionRef.TranscriptPath` in particular is never
1691
- consulted, and snapshot recipe `resumeID` is a separate value that never feeds
1692
- this path. The launch argv comes from the two seams `create agent` already
1778
+ consulted. The launch argv comes from the two seams `create agent` already
1693
1779
  owns -- `PlanAgentResume` for a ref that names a conversation, `PlanAgentLaunch`
1694
1780
  with no payload otherwise -- so the topology engine holds no launch builder of
1695
1781
  its own and the Settings enabled-agents gate still applies. An Agent that
@@ -1703,14 +1789,7 @@ Explicit Registry topology materialization:
1703
1789
  the Window from it, and then replays the anchor Agent while preserving the
1704
1790
  Agent Pane uid. The default shell is bootstrap, not a replacement anchor. A
1705
1791
  successful repeat is a Registry-write-free and topology-write-free no-op.
1706
- - Snapshot restore is a target-Project subtree projection, never a Registry
1707
- restore. Metadata-bearing v1 snapshots preserve surviving final-v2
1708
- anchor/default refs; metadata-free snapshots choose the first Window-local
1709
- Pane as anchor and the first direct shell as optional default. Agent-only
1710
- desired Windows remain Agent-anchored and acquire a shell only through the
1711
- ordinary materializer. Source snapshot bytes and unrelated roots are never
1712
- rewritten, and a second projection is byte-stable.
1713
- - `Recreate Project` replaces the exact same-root Project graph, after an
1792
+ - `Clear layout and open` replaces the exact same-root Project graph, after an
1714
1793
  explicit confirmation, in one Registry
1715
1794
  commit. It always allocates a new Project UID plus one new canonical Window
1716
1795
  and direct shell UID, whether the old Project retained Windows or had zero.
@@ -1784,7 +1863,10 @@ Plan-only runtime mutation boundary:
1784
1863
  arbitrary Pane as invocation evidence. Generated popup/menu producers pass an exact Pane anchor which is
1785
1864
  reobserved on that same socket rather than trusting a targetless current
1786
1865
  Pane. Before a stage writes, the same `-S` runner refuses path, generation,
1787
- class, or containment drift. Only a create-session
1866
+ class, or containment drift, and names which of them happened: tmux answers a
1867
+ `display-message -t %N` for a Pane that is gone with exit 0 and blank
1868
+ `$N`/`@N`/`%N` columns, so an absent anchor Pane is reported as absent rather
1869
+ than as drift on a socket and a server generation that both still match. Only a create-session
1788
1870
  stage may accept the typed no-server observation, because its explicit route
1789
1871
  and absent-session ownership preflight are the facts required to create the
1790
1872
  first server.
@@ -1912,8 +1994,11 @@ Resource-first create:
1912
1994
  `select-window`, `select-pane`, or `attach-session`. `focus pane` and
1913
1995
  `-o pane-id` are how a caller ends up in the new pane. One exception: the
1914
1996
  human intent route `internal tmux window-create` (`window.create` key, Window
1915
- menu New At End) moves exactly the pressing client to the new Window after
1916
- its create commits; public `create` stays detached.
1997
+ menu New At End) asks for the saved launch default before its create
1998
+ commits, fills the committed shell Pane with the answer -- an Agent through
1999
+ the same canonical create funnel a split uses plus a canonical delete of the
2000
+ shell -- and then moves exactly the pressing client to the new Window; public
2001
+ `create` stays detached and never reads that saved default.
1917
2002
  - **Focus is navigation-only.** `focus project|window|pane` reads live tmux
1918
2003
  inventory and may move an existing client, but has no Registry store and
1919
2004
  issues no session/Window/Pane creation, identity-marker, rename, respawn, or
@@ -1955,8 +2040,8 @@ Selector and the implicit active target:
1955
2040
  hit. The describe family remains the intentional Project-only difference;
1956
2041
  Phase 14 extends only the generic rename family to ControlSession.
1957
2042
  `get projects`, `describe|rename project`, `delete`, `rebind`, and `agent
1958
- resume` are outside that reference scope, and notifications and snapshots
1959
- belong to separate stores. Delete's exact live preflight nevertheless follows
2043
+ resume` are outside that reference scope, and notifications belong to a
2044
+ separate store. Delete's exact live preflight nevertheless follows
1960
2045
  either root kind through the selected descendant's owner chain.
1961
2046
  - `--all-projects` is the explicit registry-wide escape for those three reads.
1962
2047
  It is deliberately different from destructive `delete --all`, whose existing
@@ -2033,14 +2118,6 @@ Projmux keeps visible naming separate from source metadata:
2033
2118
  user pane labels.
2034
2119
  - **Git branch** belongs in the statusbar git segment. Branch-based terminal
2035
2120
  title overwrites are not promoted to the primary Projmux pane or window name.
2036
- - **Session snapshots** store source metadata separately: `window_name`, raw
2037
- `pane_title`, user `label`, `@projmux_ai_topic`, manual topic ownership, and
2038
- agent resume metadata. Old snapshots decode with an absent label and absent
2039
- ownership; title/topic equality never infers either. Replay writes each
2040
- semantic field to the exact pane id returned by tmux creation and restores
2041
- raw title from `Pane.Title` after launch/startup replay. Snapshots do not
2042
- store a resolved `display_label`; visible labels are recomputed by display
2043
- policy.
2044
2121
 
2045
2122
  ## Notify queue
2046
2123
 
@@ -2183,7 +2260,7 @@ The maintained product table in `internal/app/runtime_mutation_surface.go` maps
2183
2260
  generated catalog/menu producers, native provider/resume picker selections,
2184
2261
  sidebar/session-picker stops, and app lifecycle entrypoints in both directions
2185
2262
  to their handler and plan verb. It also records exact semantic exemptions for
2186
- focus, labels, operator-requested layout, mouse forwarding, snapshot replay,
2263
+ focus, labels, operator-requested layout, mouse forwarding,
2187
2264
  ephemeral maintenance, app quit, and human runtime maintenance. Managed argv
2188
2265
  verbs are selected only by the typed executor seam; generated Window
2189
2266
  create/rename, Pane-menu create/delete, and automatic post-split layout writes