projmux 0.15.3 → 0.16.1

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 (38) hide show
  1. package/README-ko.md +3 -4
  2. package/README.md +3 -3
  3. package/docs/agent-message-replies.md +149 -6
  4. package/docs/ai-agent-shortcuts.md +6 -5
  5. package/docs/architecture.md +467 -66
  6. package/docs/claude-coordination-endpoints.md +192 -20
  7. package/docs/cli-guide.md +872 -148
  8. package/docs/cli.md +1459 -485
  9. package/docs/codex-installed-compatibility.md +6 -11
  10. package/docs/codex-native-required-migration.md +1 -59
  11. package/docs/configuration.md +694 -222
  12. package/docs/globalization.md +18 -5
  13. package/docs/hooks.md +529 -62
  14. package/docs/keybindings.md +74 -4
  15. package/docs/legacy-cli-retirement.md +3 -3
  16. package/docs/legacy-diagnostics-inventory.md +4 -4
  17. package/docs/native-picker.md +3 -5
  18. package/docs/notify-queue.md +1 -1
  19. package/docs/npm-distribution.md +4 -0
  20. package/docs/operational-diagnostics.md +319 -34
  21. package/docs/pr-guideline.md +66 -22
  22. package/docs/release.md +101 -0
  23. package/docs/replacement-contract.md +72 -58
  24. package/docs/repo-layout.md +3 -0
  25. package/docs/resource-attribution.md +6 -2
  26. package/docs/session-restore.md +50 -80
  27. package/docs/settings-ia.md +14 -22
  28. package/docs/statusbar.md +38 -37
  29. package/docs/testing.md +83 -18
  30. package/docs/theme-palette.md +6 -6
  31. package/docs/tmux-surface-inventory.md +21 -10
  32. package/docs/troubleshooting.md +2 -4
  33. package/docs/upgrading.md +161 -24
  34. package/docs/usage-tracking.md +40 -49
  35. package/package.json +5 -5
  36. package/docs/agent-workflow.md +0 -2596
  37. package/docs/codex-generation-pool.md +0 -623
  38. package/docs/codex-stored-qualification.md +0 -45
@@ -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,51 @@ 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: every Agent create records on the new Agent and its
422
+ managed Pane, in the same transaction that commits the Agent, exactly one of
423
+ these, decided once per create (a fan-out records the same one on every
424
+ Agent). `projmux.io/creator-basis` names the kind of evidence, and only the
425
+ keys that evidence proves are written:
426
+ 1. `pane-chain`, with `projmux.io/creator-agent` (the creator Agent's bare
427
+ UID) and `projmux.io/creator-pane` (that Agent's managed Pane's bare
428
+ UID): an explicit `create agent` (every spelling, `--create-window`, and
429
+ each Agent of a fan-out) or `create window --provider` ran inside an
430
+ Agent's managed Pane. It is recorded only when the unmasked ambient `%N`
431
+ (`__PROJMUX_RUNTIME_ANCHOR_PANE`, then `TMUX_PANE`) is exactly one live
432
+ Registry Pane, that Pane round-trips with its owning Agent's
433
+ `status.paneRef`, one `display-message` confirms it on the create's own
434
+ app-owned socket and server pid, and the create process descends from its
435
+ `#{pane_pid}`. It always wins.
436
+ 2. `explicit`, with `projmux.io/creator-agent` only: the caller declared
437
+ `--creator uid:<agent>` and no pane chain was recorded. The declaration
438
+ must name an Agent in the Registry, or the create is refused before
439
+ anything changes. A declaration that disagrees with a recorded pane chain
440
+ is dropped, and stderr gets one `creator declaration not recorded:
441
+ pane-chain-disagrees (--creator uid:<agent>)` line.
442
+ 3. `operator`, with `projmux.io/creator-client` only: the create ran in
443
+ process for a named operator client. The UI intent creates (picker, pane
444
+ menu, `ai split`, launch choice, the new-Window key) record the client
445
+ `ui`; a client layered on projmux that runs creates in process uses the
446
+ `recordOperatorCreator` seam. Such a create observes no pane chain,
447
+ because its process environment says nothing about who asked. A
448
+ `--creator` on it is dropped with the token `operator-client`. There is no
449
+ argv spelling of this basis; `TestNoArgvPathBuildsAnOperatorCreator`
450
+ holds its producers. Client names follow the one rule in
451
+ `internal/core/operatorclient`: 1-32 bytes of lowercase ASCII letters,
452
+ digits, and `-`, starting with a letter.
453
+ 4. Nothing, when there is no evidence.
454
+
455
+ A failed pane-chain check changes nothing else about the create. When
456
+ nothing is recorded, stderr gets one `creator not recorded: <reason>` line
457
+ only if the ambient Pane is a live Agent Pane but a later check fails; an
458
+ ambient Pane that is malformed, unregistered, or a Window-owned shell is
459
+ silent. The creator Agent is never the created Agent. These keys are
460
+ provenance, not authentication, like a message `--source`: no permission,
461
+ route, or selector reads them, and `explicit` can be forged, which is what
462
+ its basis says. An absent key does not mean a human created the Agent: a
463
+ Codex Agent's own commands run under the app-server, not below the Pane's
464
+ process, so they record nothing unless they declare `--creator`. Nothing
465
+ backfills older Agents.
362
466
 
363
467
  Root lifecycle:
364
468
 
@@ -452,6 +556,62 @@ Termination evidence transport:
452
556
  receipt is the post-delete diagnostic: source, classification, observed time,
453
557
  Pane/Agent uid, generation, and wait status only. No command, pane content,
454
558
  prompt, transcript, or provider payload is recorded.
559
+ - Deletion records: every explicit deletion that commits its Registry
560
+ transaction appends exactly one JSON line to
561
+ `<state>/deletion-records.jsonl`, next to `termination-receipts.jsonl` and the
562
+ Registry it changed (`${XDG_STATE_HOME:-$HOME/.local/state}/projmux` by default). The writers are
563
+ `delete window|pane|agent`, `unregister project` and its deprecated alias
564
+ `delete project`, and `prune agent|project --yes`. The line is
565
+ `{"schemaVersion":1,"at","operation","operationID","via","actor":{"agentUID","paneUID","basis"},"targets":[{"kind","uid","name"}],"affected":[{"kind","uid","name","action"}]}`,
566
+ where `at` is RFC 3339 UTC; `operation` is one of `delete-window`,
567
+ `delete-pane`, `delete-agent`, `unregister-project`, `delete-project`,
568
+ `prune-agent`, `prune-project` (the record's own spelling; the receipt line a
569
+ delete prints spells it `delete.pane` and so on, a separate surface that is
570
+ not unified with it); `operationID` is the delete's intentional termination
571
+ receipt id, or one minted the same way for unregister and prune;
572
+ and `via` is `cli`, `ui` (the generated Pane and Window delete keys), or
573
+ `prune`. `targets` are what the operator named; `affected` is every
574
+ Project, ControlSession, Window, Agent, and Pane present before the commit
575
+ and absent after it (targets included), ordered by kind and then uid, each
576
+ with action `deleted`. No explicit deletion route removes a ControlSession
577
+ today (only a rolled-back transaction does, and that writes no record), so
578
+ that kind is listed for completeness of the two root kinds.
579
+ - The actor is the same pane-chain judgment creator provenance uses, run
580
+ against the pre-commit Registry inside the delete's own transaction, so a
581
+ delete that removes its own Agent still names it. `basis` is `pane-chain`
582
+ when it succeeds, creation's skip token otherwise (`anchor-invalid`,
583
+ `anchor-pane-unregistered`, `anchor-pane-ambiguous`, `caller-pane-not-agent`,
584
+ `pane-ref-mismatch`, `anchor-server-unproven`, `anchor-query-failed`,
585
+ `anchor-server-mismatch`, `anchor-pane-mismatch`,
586
+ `process-chain-unobservable`, `not-pane-descendant`), and empty when there
587
+ was no ambient Pane. The server step is the delete's own: one
588
+ `display-message -t %N` through the route the delete addresses must answer
589
+ with the `$TMUX` socket and server pid, the same `%N`, and the Registry
590
+ Pane's mirrored uid. `prune` has no socket flags and judges on the inherited
591
+ `$TMUX`; `unregister project` judges on its `--socket`/`--socket-path` or
592
+ `$TMUX`. A `ui` deletion is never judged and records an empty actor.
593
+ - A deletion a detached daemon runs (a Codex app-server command, say) is never
594
+ recorded as an Agent's. With no ambient Pane in its environment the actor is
595
+ empty with an empty basis; with an Agent Pane's inherited `TMUX`/`TMUX_PANE`
596
+ but a process that does not descend from that Pane, `basis` is
597
+ `not-pane-descendant` and both uids are empty.
598
+ - **An empty actor does not mean a human ran the deletion.** The record is
599
+ provenance, not authentication.
600
+ - The file is appended with one framed `O_APPEND` write and `fsync`, like the
601
+ termination journal, and is never truncated, rotated, or read back by
602
+ projmux. It is written after the commit and before the result is printed or
603
+ a self-targeted kill is queued. A record that cannot be written never fails
604
+ or changes the delete: stdout and the exit code are unchanged and stderr gets
605
+ exactly one `deletion not recorded: <token>` line, where token is
606
+ `state-dir-unavailable`, `append-failed`, or `operation-id-unavailable`.
607
+ `--dry-run`, a refusal, a declined confirmation, and a failed commit write
608
+ nothing.
609
+ - Non-guarantees: automatic lifecycle teardown (exit reconciliation), internal
610
+ row cleanup (resume handoff, topology replay, initial shell retirement,
611
+ rollback, Project fresh start), and the supervisor's process-exit path write
612
+ no deletion record. A delete issued by a Codex Agent's command runs under the
613
+ app-server rather than below the Pane's process and records
614
+ `not-pane-descendant`. There is no retention policy and no reader CLI.
455
615
  - The supervisor resolves its state paths from the pane's own inherited
456
616
  environment, which is the tmux **server's** environment rather than the
457
617
  environment of the CLI call that created the pane. That is the correct
@@ -519,7 +679,8 @@ Exit reconciliation and lifecycle projection:
519
679
  - Abnormal, killed, unknown, whole-host absence, missing/empty server inventory,
520
680
  permission failure, foreign Window observation, stale generation, and an
521
681
  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.
682
+ retained lifecycle projection and the canonical exact-uid Registry-only
683
+ delete recovery of a paneless Offline or Failed Agent.
523
684
  - The closed Agent transition table stays the authority. An Agent that may not
524
685
  reach the implied phase keeps its phase, its `paneRef`, and its managed Pane;
525
686
  only the evidence is recorded. A refused transition is not a reason to discard
@@ -582,9 +743,21 @@ Agent provider session ref:
582
743
  - Codex's turn id is deliberately **not** stored. A turn addresses one turn
583
744
  inside the conversation and changes on every hook event, so it is not a
584
745
  pointer to the conversation.
585
- - Transcript **paths** are recorded as the hook reported them. Nothing reads
586
- provider config files or transcript **contents**; that is permanently out of
587
- scope.
746
+ - Transcript **paths** are recorded as the hook reported them, and no
747
+ transcript content is read to fill the ref. The Registry, hook ingest, and
748
+ Agent resume paths (`agent resume`, `agent relaunch`, and the topology replay
749
+ below) never read provider config files, and they read transcript
750
+ **contents** only in these documented places:
751
+ - The three bounded tail readers that
752
+ [hooks.md](hooks.md#claude-code-hook-ingest) lists and scopes: `Stop` hook
753
+ ingest, the held coordination message release, and the Agent's pane
754
+ supervisor. Each reads at most the last 256 KiB of one Claude transcript,
755
+ and none writes what it read to the Registry. `Stop` keeps the last
756
+ assistant text as its notification row's text; hooks.md says where that
757
+ text goes.
758
+ - The explicit, user-run `agent sessions backfill` (see "Agent session
759
+ history (Claude)" below), which reads Claude transcript contents read-only
760
+ and only to attribute past sessions from delivered coordination frames.
588
761
  - The field is additive inside `schemaVersion: 1`. It is an optional pointer
589
762
  with `omitempty`, so a registry written before it existed decodes with a nil
590
763
  ref, validates, and re-encodes byte-identically. Bumping the envelope would
@@ -606,6 +779,214 @@ Agent provider session ref:
606
779
  hook whose provider contradicts the Agent's `spec.provider` is refused with
607
780
  zero mutations.
608
781
 
782
+ Agent session history (Claude and Codex):
783
+
784
+ - `status.sessionRef` holds one conversation and is overwritten when an Agent
785
+ moves to another one. The conversations a Claude or Codex Agent left are kept in
786
+ an append-only file outside the Registry,
787
+ `<state>/agent-session-history.jsonl` (next to `deletion-records.jsonl`;
788
+ `${XDG_STATE_HOME:-$HOME/.local/state}/projmux` by default). The Registry
789
+ schema and its version are unchanged.
790
+ - One line is appended each time a committed Registry write binds or replaces
791
+ a Claude session id or Codex thread id. Re-observing the same conversation,
792
+ including a same-thread Codex resume or endpoint handover, appends nothing.
793
+ The writers are a closed set of four functions: `persistAgentSessionRef`
794
+ (hook ingest) and `persistManagedAgentInteractionWithActivationPolicy`
795
+ (managed-Agent interaction commit) in `internal/app/agent_session_ref.go`,
796
+ `openIntentAgent` (resume-picker create and intent native Codex create) in
797
+ `internal/app/create_intent.go`, and `createAgent` (native Codex fresh
798
+ create) in `internal/app/create_agent.go`.
799
+ `TestClaudeSessionRefWritersRecordHistory` pins the callers of
800
+ `RecordAgentSessionRef`, and
801
+ `TestSessionHistoryObservedRowWritersCarryAffiliation` pins the set of four,
802
+ that each builds its row with `sessionhistory.ObservedRecordFor` (directly or
803
+ through `claudeSessionHistoryRecord`) from its transaction's `working`
804
+ Registry, that each reaches a post-commit append helper, and that no other
805
+ code builds an `observed` row with `RecordFor`. The append runs after the
806
+ commit and never fails its caller: a hook logs one `session-history` line to
807
+ `ai-ingest.log`, a create prints one
808
+ `agent session history not recorded: append-failed` line on stderr.
809
+ - Each line is `{"agentUID","provider","sessionId","transcriptPath","observedAt","source"}`:
810
+ `provider` is `claude` or `codex`, `observedAt` is the ref's RFC 3339 UTC observation
811
+ time, and `transcriptPath` is the path the Claude hook reported (empty for
812
+ Codex; never read by the
813
+ writers or by `agent sessions list`). `source` is `observed` for every line
814
+ a writer appends; the read side adds `current` for the Registry's ref; the
815
+ only producer of `estimated` is `agent sessions backfill` (below), whose
816
+ lines add one key, `lastRecordAt`. Lines are one framed `O_APPEND` write under an
817
+ exclusive `flock`, then `fsync`; the file is `0600` in a `0700` directory.
818
+ Readers skip and count an unparsable line.
819
+ - An `observed` line also carries the Agent's affiliation in three trailing
820
+ keys, `projectUID`, `windowUID`, and `agentName`, so a session stays
821
+ attributable after its Agent is deleted from the Registry. The writer
822
+ resolves them inside the same transaction, from that transaction's working
823
+ Registry (`sessionhistory.ResolveAffiliation`), link by link: `agentName`
824
+ when the Agent exists, `windowUID` when its ownerRef is an existing Window,
825
+ `projectUID` when that Window's ownerRef is an existing Project. A key whose
826
+ link does not resolve is omitted; nothing is guessed, and a chain that does
827
+ not resolve never withholds the line or fails the writer. `current` and
828
+ `estimated` rows carry none of the three. When `agent sessions list` merges
829
+ a conversation seen more than once, each of the three is the non-empty value
830
+ of the latest row that carries one.
831
+ - A line `agent sessions attribute` (below) writes adds a fourth key after
832
+ them, `affiliationBasis: "registry"`: its affiliation is the one the current
833
+ Registry gave the Agent when the user ran it, not its writer's transaction.
834
+ No other writer sets the key; an `observed` line without it is recorded at
835
+ write time. Merging carries `affiliationBasis` with `projectUID`: it is the
836
+ basis of the row that supplied the kept `projectUID`.
837
+ - `projmux agent sessions list <agent-ref> [-o json]` and the Go read function
838
+ `sessionhistory.List(stateDir, agent)` return the same rows: history joined
839
+ with the Registry's current ref, one row per `(agentUID, provider, sessionId)` in
840
+ `observedAt` order. A conversation seen more than once keeps its latest
841
+ observation time and transcript path, and is `current` when the Registry
842
+ names it; otherwise it is `observed` if any of its rows is, and `estimated`
843
+ only when every row is, so a backfilled estimate never hides an observation.
844
+ The JSON envelope adds `agentName` and `corruptLines`.
845
+ - `projmux agent sessions project <project-ref> [-o json]` and the Go read
846
+ function `sessionhistory.ListProject(stateDir, registry, projectUID)` list
847
+ the sessions attributed to one exact Project (resolved like any Project
848
+ ref). It is read-only. A Project no longer in the Registry is queried by its
849
+ exact `uid:<uid>` ref, where `<uid>` has exactly the shape projmux mints for
850
+ a Project: the listing then comes from the history rows alone,
851
+ `projectName` is `""`, and the table names the Project `project/uid:<uid>`.
852
+ A name ref, or a `uid:` ref of another kind or shape, resolves against the
853
+ Registry only and fails as before when it matches nothing.
854
+ - Input: every `claude` or `codex` line of the history file, plus one
855
+ `current` row for each Registry Agent with a supported provider.
856
+ - Attribution, per row: a row with a `projectUID` is attributed with its
857
+ own `projectUID`, `windowUID`, and `agentName`, never re-resolved, with
858
+ basis `registry` when its `affiliationBasis` is `registry` (it holds after
859
+ the Agent is deleted, with `inRegistry: false`) and `recorded` otherwise;
860
+ otherwise, a row whose Agent is in the current Registry with a complete
861
+ Agent -> Window -> Project chain is attributed with basis `registry`,
862
+ using the current values; any other row is unattributed. Rows are never
863
+ attributed from deletion records, Registry backups, a transcript's folder
864
+ or cwd, or time proximity.
865
+ - A session is `(provider, sessionId)`. A session none of whose rows is
866
+ attributed counts in `unattributed`; one whose attributed rows name two or
867
+ more Projects counts in `ambiguous` and is listed under none; one naming
868
+ exactly the requested Project is listed. Both counts, and `corruptLines`,
869
+ are over the whole history, not per Project.
870
+ - Each listed session has `provider`, `sessionId`, `transcriptPath`,
871
+ `observedAt`, `lastRecordAt` (when any row has one), and `source`, merged
872
+ over its attributed rows with the `agent sessions list` rules, and
873
+ `agents`: one entry per distinct Agent of those rows, in first-appearance
874
+ order (history file order, then the current rows), with `agentUID`,
875
+ `agentName`, `windowUID`, `inRegistry` (the Agent exists in the current
876
+ Registry), and `basis` (`recorded` wins when an Agent has both). Sessions
877
+ are ordered by `observedAt`.
878
+ - JSON: `{"projectUID","projectName","sessions","unattributed","ambiguous","corruptLines"}`;
879
+ `sessions` is `[]` when empty. The table prints
880
+ `project/<name> has no recorded sessions` when empty, marks an Agent no
881
+ longer in the Registry `(deleted)`, and prints one stderr line for each
882
+ non-zero `corruptLines`, `unattributed`, and `ambiguous` count.
883
+ - `projmux agent sessions backfill [--dry-run] [-o json]` recovers a subset of
884
+ the conversations from before this history existed. It is the one explicit,
885
+ user-run reader of transcript contents. Apart from the bounded tail readers
886
+ that [hooks.md](hooks.md#claude-code-hook-ingest) lists (`Stop` hook ingest,
887
+ the held coordination message release, and the pane supervisor), the
888
+ Registry, hook ingest, Agent resume, and `agent sessions list` never read a
889
+ transcript, and none of those tail readers adds a row to this history.
890
+ - Input: the top-level Claude transcripts
891
+ `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects/<project>/<session>.jsonl`
892
+ (a blank `CLAUDE_CONFIG_DIR` is unset). Only regular files exactly one
893
+ directory deep are scanned: symlinks, directories, and deeper files such
894
+ as `<session>/subagents/*.jsonl` are not. Files are opened read-only and
895
+ never written; a missing projects directory scans nothing.
896
+ - Attribution: only `type: "user"` records that are not `isSidechain: true`
897
+ count, and within them only a string `message.content` or its
898
+ `type: "text"` blocks; tool results, assistant records, attachments, and
899
+ queue operations can quote a frame and are ignored. At each occurrence of
900
+ `{"kind":"projmux-coordination"` in that text, one JSON value is decoded;
901
+ a frame with that `kind` and a non-empty `target.agentUID` names its
902
+ target, and an occurrence that does not decode is skipped and counted.
903
+ A transcript naming exactly one distinct target Agent is attributed to
904
+ it; none is `noFrame`, two or more is `ambiguous`, and neither is ever
905
+ resolved from the folder name, the working directory, or time proximity.
906
+ - Meaning of `estimated`: the session received a delivered projmux message
907
+ addressed to that Agent. It is a subset of past sessions -- a session that
908
+ never received a message is not recovered -- and an attribution, not an
909
+ observation. `sessionId` is the file name, `transcriptPath` the absolute
910
+ file path, `observedAt` the transcript's first record `timestamp`, and
911
+ `lastRecordAt` its last one (both over every parseable record, RFC 3339
912
+ UTC). A candidate with no parseable timestamp is `unreadable`.
913
+ - Precedence and idempotency: a session already `observed` in the history
914
+ or `current` in the Registry for any Agent is `skippedObserved`; one
915
+ already `estimated` is `alreadyEstimated`; only the rest are appended, as
916
+ framed lines like every other writer's. The run holds the history file's
917
+ exclusive `flock` across the read, the check, and the append (not the
918
+ transcript scan), so concurrent runs cannot add a session twice and a
919
+ second run appends nothing.
920
+ - Report: `scanned`, `attributed`, `alreadyEstimated`, `skippedObserved`,
921
+ `ambiguous`, `noFrame`, `unreadable` (each transcript lands in exactly one
922
+ of these six), `malformedLines` (transcript lines that are not JSON
923
+ objects, skipped), `malformedFrames`, plus `dryRun`, `projectsDir`,
924
+ `historyPath`, and `rows` (the attributed rows; `[]` when none).
925
+ `--dry-run` reports exactly what a real run would append and writes
926
+ nothing, not even the state directory.
927
+ - Removing the estimated rows: with no projmux command running that could
928
+ write the history, keep every other line and swap the file in atomically
929
+ (the blank lines of the framed format are simply dropped):
930
+
931
+ ```sh
932
+ h="${XDG_STATE_HOME:-$HOME/.local/state}/projmux/agent-session-history.jsonl"
933
+ jq -c 'select(.source != "estimated")' "$h" > "$h.tmp" && chmod 600 "$h.tmp" && mv "$h.tmp" "$h"
934
+ ```
935
+
936
+ A line `jq` cannot parse stops it with an error before the `mv`; readers
937
+ already skip such lines, so remove or repair it first.
938
+ - `projmux agent sessions attribute [--dry-run] [-o json]` persists what the
939
+ current Registry proves about the conversations whose history carries no
940
+ Project, so `agent sessions project` keeps listing them after their Agent
941
+ is deleted. It reads only the history file and the current Registry: never
942
+ deletion records, Registry backups, or transcripts. It persists only what
943
+ the current Registry proves, and never restores an Agent that is no longer
944
+ in it.
945
+ - Input: every `(agentUID, provider, sessionId)` of the file's `claude` and
946
+ `codex` lines, in file order, then each Registry Agent's current ref
947
+ (supported providers only) whose key the file does not name.
948
+ - Precedence: a key with a line that has a `projectUID` is
949
+ `alreadyAttributed`; a key whose Agent is not in the Registry, or whose
950
+ Agent -> Window -> Project chain does not resolve, is `unresolved`; both
951
+ are left untouched. Every other key is `attributed`: one line is appended
952
+ for it, with `observedAt`, `transcriptPath`, and `lastRecordAt` merged
953
+ over its lines and its current ref with the `agent sessions list` rules;
954
+ `source` the highest-ranked source of its lines, or `observed` for a key
955
+ only the Registry names (`current` is never written); `projectUID`,
956
+ `windowUID`, and `agentName` from the current Registry; and
957
+ `affiliationBasis: "registry"`.
958
+ - Report: `scanned` (`attributed` + `alreadyAttributed` + `unresolved`),
959
+ `attributed`, `alreadyAttributed`, `unresolved`, `corruptLines`, plus
960
+ `dryRun`, `historyPath`, and `rows` (the attributed lines; `[]` when
961
+ none).
962
+ - Idempotency and locking: like backfill, a run holds the history file's
963
+ exclusive `flock` across the read, the classification, and one write of
964
+ every framed line, and releases it before the `fsync`. A second run
965
+ appends nothing, because every key it attributed now has a `projectUID`.
966
+ Existing lines are never rewritten. A run with nothing to append creates
967
+ nothing, and `--dry-run` reads without the lock, writes nothing, and
968
+ reports exactly what a real run would append.
969
+ - Undoing it, the same way as removing estimated rows:
970
+
971
+ ```sh
972
+ h="${XDG_STATE_HOME:-$HOME/.local/state}/projmux/agent-session-history.jsonl"
973
+ jq -c 'select(.affiliationBasis != "registry")' "$h" > "$h.tmp" && chmod 600 "$h.tmp" && mv "$h.tmp" "$h"
974
+ ```
975
+ - Non-guarantees: no session end time is recorded for observed rows;
976
+ conversations from before this history existed appear only as the `current`
977
+ row unless `agent sessions backfill` attributes them; Claude and Codex
978
+ conversation changes are recorded, Antigravity's are not; a line edited or
979
+ deleted by hand is not recovered; the history is kept after the Agent is deleted; the Registry
980
+ commit and the append are not atomic, so a crash between them loses that one
981
+ line. There is no retention policy. Lines written before the affiliation
982
+ keys existed carry none, so once their Agent is deleted they count only in
983
+ `unattributed` unless `agent sessions attribute` persisted their
984
+ affiliation while the Agent was still in the Registry; `agentName` is a
985
+ write-time snapshot and does not follow a later rename; a deleted Project
986
+ can no longer be named by name, only by its exact `uid:` ref, and then only
987
+ the history rows that carry its `projectUID` are listed; `agent sessions
988
+ project` computes no token totals.
989
+
609
990
  Agent launch argv (workspace / task boundary):
610
991
 
611
992
  - One Agent launch hands the provider CLI two independent things in a single
@@ -736,9 +1117,8 @@ Registry file and schema:
736
1117
  directory-existence and uid adapters.
737
1118
  - The canonical anchor is a schema-v2 write invariant. Until the separately
738
1119
  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.
1120
+ prune-to-zero transaction fails validation and commits zero Registry bytes
1121
+ and performs no tmux mutation.
742
1122
  This fail-closed ordering is not Phase 3 authority to redesign Project start.
743
1123
  - Downgrade writes remain unsupported. Unversioned, malformed, and future
744
1124
  envelopes still fail closed before backup, staging, or replace.
@@ -960,6 +1340,10 @@ Resolved resource graph (`internal/core/resourcegraph`):
960
1340
  first and the inherited `$TMUX` socket path second. There is no implicit
961
1341
  default-server probe: with no transport the graph is a Registry-only snapshot
962
1342
  whose runtime answers are all `unknown`, and a sibling socket is never read.
1343
+ The Registry views `get` and `describe` add one rung after `$TMUX`: they
1344
+ have no socket flag, so outside tmux they observe the app socket
1345
+ `-L projmux` (see *Runtime observation and resource status*). That is still
1346
+ one exact server, never the default one.
963
1347
  - **Bounded and pure.** One observation costs one option probe plus three list
964
1348
  queries whatever the size of the server, is memoized for the invocation rather
965
1349
  than cached with a TTL — closing a pane must make the *next* command report it
@@ -967,21 +1351,11 @@ Resolved resource graph (`internal/core/resourcegraph`):
967
1351
  process, and no tmux, so the same inputs always produce byte-identical output
968
1352
  and a read can never materialize state.
969
1353
 
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.
1354
+ Saved Project state:
1355
+
1356
+ - The Registry is the only saved Project state. projmux keeps no separate
1357
+ Project snapshot store, and a closed Project starts only from its Registry
1358
+ desired state through the ordinary Project materializer.
985
1359
 
986
1360
  tmux transport mirror:
987
1361
 
@@ -1000,7 +1374,7 @@ tmux transport mirror:
1000
1374
  every other mirror goes through, on the same plain `tmux` transport the session
1001
1375
  was created on. The write is gated strictly on "this open registered the
1002
1376
  Project": every already-registered Project converges through the Registry
1003
- topology engine, including desired state previously committed from a snapshot,
1377
+ topology engine,
1004
1378
  and opening `$HOME` mints no managed identity at all, so neither writes a mirror
1005
1379
  option through this first-open gate. That gate is also what makes repeating an
1006
1380
  open write nothing. Repairing a session that is already live without its
@@ -1062,14 +1436,25 @@ Runtime observation and resource status:
1062
1436
  live tmux object still mirrors its `@projmux_window_uid` /
1063
1437
  `@projmux_pane_uid`. A registry object bound to nothing live is an **orphan**,
1064
1438
  and an orphan is not live.
1065
- - `selector.ObservedStatus(missingRoot, bound)` is the **single** derivation
1066
- rule in the codebase, and every kind goes through it. `missing-root` outranks
1067
- everything, then `bound` decides `live` vs `offline`. The MissingRoot
1068
- precedence contract is unchanged, and it now applies to a Window or Pane whose
1069
- owning Project lost its root even while tmux is still running them.
1439
+ - `selector.ObservedStatus(missingRoot, bound, unobserved)` is the **single**
1440
+ derivation rule in the codebase, and every kind goes through it.
1441
+ `missing-root` outranks everything, then `bound` is `live`, then a resource
1442
+ whose observation could not be taken is `unknown`, and only a readable
1443
+ observation with nothing bound is `offline` -- the same precedence the
1444
+ resource graph applies. The MissingRoot precedence contract is unchanged, and
1445
+ it now applies to a Window or Pane whose owning Project lost its root even
1446
+ while tmux is still running them.
1447
+ - `get` and `describe` build their observation from the resolved resource
1448
+ graph, so a row's STATUS and the ACTIONS `registryview` offers for it come
1449
+ from one judgement. An `unknown` row offers only `delete`: it was not seen
1450
+ live, so `open` has nothing to move to, and it was not seen absent, so
1451
+ `start`/`resume` could target something already running.
1070
1452
  - 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.
1453
+ has no `@projmux` uid of its own. On `get` and `describe` its status is the
1454
+ graph's observation of that session; an identity-only resolver that took no
1455
+ observation (`selector.New`) still reads the stored `status.session`
1456
+ projection (see *Resource metadata model* for its writers) and never reports
1457
+ `unknown`.
1073
1458
  - An **Agent** owns no tmux object of its own — there is no `@projmux_agent_uid`
1074
1459
  and there must not be one, because an Agent outlives the managed Pane it is
1075
1460
  bound to. Its runtime object is **that managed Pane**, named by
@@ -1096,6 +1481,15 @@ Runtime observation and resource status:
1096
1481
  exactly that. It is also not a per-route reconcile, because the read verbs
1097
1482
  load the registry read-only and must never materialize
1098
1483
  `<state>/projmux/metadata/`.
1484
+ - **The server a view observes** is the one its caller is attached to: the
1485
+ inherited absolute `$TMUX` socket, even when that is not the app server.
1486
+ With no `$TMUX` -- another terminal, an IDE, a script -- `get` and `describe`
1487
+ observe the app socket `-L projmux`, the server `create` and
1488
+ `start project` run resources on, so a view outside tmux renders the same
1489
+ rows as the same view inside the app server. An app socket with no server
1490
+ behind it is observed as an absent server: everything reads `offline`, which
1491
+ is what is running there. `get runtime` and `reconcile resources` keep their
1492
+ own rule (flag or `$TMUX`, otherwise no transport).
1099
1493
  - A failed inventory query yields an **empty** observation, never a fallback to
1100
1494
  a stored value. Empty can only downgrade a resource to offline; it can never
1101
1495
  invent a live one, and "nothing is live" is the truthful answer for a machine
@@ -1284,7 +1678,7 @@ Lifecycle trigger convergence:
1284
1678
  transaction deletes exactly that Window, its Panes, its owned Agents, and
1285
1679
  their reservations. A non-last Project Window reanchors to its existing
1286
1680
  sibling. The last Project Window leaves the exact Project uid, root,
1287
- reservation, pins, and snapshot bytes in the valid zero-Window state. A
1681
+ reservation, and pins in the valid zero-Window state. A
1288
1682
  ControlSession likewise loses only the Window and keeps its root uid.
1289
1683
  Abnormal/killed/unknown exits, stale generations, unpaired or foreign handles,
1290
1684
  unavailable/empty observations, and missing-server or permission failures
@@ -1298,11 +1692,9 @@ Lifecycle trigger convergence:
1298
1692
  managed runtime and preserves every desired UID. Continue on retained-window
1299
1693
  writes only runtime and materializes the same descendant UIDs; Continue on
1300
1694
  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
1695
+ Project UID before runtime materialization. The deleted+Continue cell is
1696
+ always an unavailable zero-write `project-is-not-registered` refusal that
1697
+ points to Fresh and never falls back to it. Fresh atomically replaces either
1306
1698
  registered state with a new Project/Window/shell UID chain and exactly one
1307
1699
  same-root claimant. Within this runtime/startup lifecycle table, canonical
1308
1700
  `delete project --yes` alone unregisters the Project graph; the separately
@@ -1370,6 +1762,25 @@ Projmux split UI:
1370
1762
  The provider and shell branches render the exact argv an operator would type,
1371
1763
  so a UI action and a typed command cannot disagree about what `--placement
1372
1764
  down` means.
1765
+ - A split picker running in a popup does not call that route itself. tmux
1766
+ closes a popup only when the process inside it exits, so the picker hands its
1767
+ intent to a detached `run-shell -b` continuation on the same server
1768
+ (`internal agent-pane launch-selection`, carrying the origin Pane, client,
1769
+ and context directory as env) and exits. The continuation
1770
+ calls the same create funnel; a success writes nothing and anything else is
1771
+ one bounded line on the pressing client. Every selection travels: it is a
1772
+ plain value with no live handle left in the picker process.
1773
+ - A generated Window create asks before it commits. It opens the split picker in
1774
+ answer mode (`internal tmux popup-toggle --answer <file>`) on the Pane the key
1775
+ was pressed in; the picker writes the selection -- the same argv the
1776
+ continuation carries, read back by the same parser -- to a 0600 file its
1777
+ producer made, and creates nothing. The producer then commits the Window and
1778
+ the answer in one Registry transaction -- for an Agent, the same shape
1779
+ `create window --provider` commits: the Agent Pane splits off the new shell
1780
+ and the shell is retired -- and moves the pressing client onto it last. An
1781
+ Agent that cannot be opened rolls the whole Window back and leaves one line on
1782
+ the pressing client. A cancelled picker leaves the file empty and nothing is
1783
+ created.
1373
1784
  - Only the materializer runs `split-window`. Before this convergence the saved
1374
1785
  default and both pickers descended into a legacy split that called tmux
1375
1786
  directly, so a pane opened from the UI was a runtime object the Registry had
@@ -1564,7 +1975,7 @@ a discovery input *and* the only record that a directory mattered. They are five
1564
1975
  separate authorities now, and the boundaries are the point.
1565
1976
 
1566
1977
  - **Workdirs and project roots are scan roots.** `PROJMUX_MANAGED_ROOTS`,
1567
- `PROJMUX_PROJDIR` and `~/.config/projmux/workdirs` name directories to look
1978
+ `PROJMUX_PROJDIR` and `$HOME/.config/projmux/workdirs` name directories to look
1568
1979
  inside. Looking inside a directory registers nothing. On Windows they are
1569
1980
  OS-native paths and stay OS-native paths; nothing normalizes them into identity.
1570
1981
  - **A discovered child is an unregistered candidate.** It is a filesystem fact
@@ -1681,15 +2092,14 @@ Explicit Registry topology materialization:
1681
2092
  deletion removes that desire. Exact uid/name/owner mirrors are retained. Stored
1682
2093
  Pane CWD drives only that Pane's detached runtime cwd, while Project root
1683
2094
  remains the session path anchor and `PROJMUX_CWD` hook value.
1684
- `Pane.spec.command`, snapshot recipes, notifications, and ephemeral sessions
2095
+ `Pane.spec.command`, notifications, and ephemeral sessions
1685
2096
  are never execution inputs.
1686
2097
  - An Agent whose managed Pane is not live is replayed into a new managed Pane on
1687
2098
  its Window's proven anchor, through the same allocation, activation ledger,
1688
2099
  ownership-checked adoption, and rollback the shell half uses. The **only**
1689
2100
  replay identifier is Registry `status.sessionRef`: no provider conversation
1690
2101
  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
2102
+ consulted. The launch argv comes from the two seams `create agent` already
1693
2103
  owns -- `PlanAgentResume` for a ref that names a conversation, `PlanAgentLaunch`
1694
2104
  with no payload otherwise -- so the topology engine holds no launch builder of
1695
2105
  its own and the Settings enabled-agents gate still applies. An Agent that
@@ -1703,14 +2113,7 @@ Explicit Registry topology materialization:
1703
2113
  the Window from it, and then replays the anchor Agent while preserving the
1704
2114
  Agent Pane uid. The default shell is bootstrap, not a replacement anchor. A
1705
2115
  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
2116
+ - `Clear layout and open` replaces the exact same-root Project graph, after an
1714
2117
  explicit confirmation, in one Registry
1715
2118
  commit. It always allocates a new Project UID plus one new canonical Window
1716
2119
  and direct shell UID, whether the old Project retained Windows or had zero.
@@ -1784,7 +2187,10 @@ Plan-only runtime mutation boundary:
1784
2187
  arbitrary Pane as invocation evidence. Generated popup/menu producers pass an exact Pane anchor which is
1785
2188
  reobserved on that same socket rather than trusting a targetless current
1786
2189
  Pane. Before a stage writes, the same `-S` runner refuses path, generation,
1787
- class, or containment drift. Only a create-session
2190
+ class, or containment drift, and names which of them happened: tmux answers a
2191
+ `display-message -t %N` for a Pane that is gone with exit 0 and blank
2192
+ `$N`/`@N`/`%N` columns, so an absent anchor Pane is reported as absent rather
2193
+ than as drift on a socket and a server generation that both still match. Only a create-session
1788
2194
  stage may accept the typed no-server observation, because its explicit route
1789
2195
  and absent-session ownership preflight are the facts required to create the
1790
2196
  first server.
@@ -1912,8 +2318,11 @@ Resource-first create:
1912
2318
  `select-window`, `select-pane`, or `attach-session`. `focus pane` and
1913
2319
  `-o pane-id` are how a caller ends up in the new pane. One exception: the
1914
2320
  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.
2321
+ menu New At End) asks for the saved launch default before its create
2322
+ commits, fills the committed shell Pane with the answer -- an Agent through
2323
+ the same canonical create funnel a split uses plus a canonical delete of the
2324
+ shell -- and then moves exactly the pressing client to the new Window; public
2325
+ `create` stays detached and never reads that saved default.
1917
2326
  - **Focus is navigation-only.** `focus project|window|pane` reads live tmux
1918
2327
  inventory and may move an existing client, but has no Registry store and
1919
2328
  issues no session/Window/Pane creation, identity-marker, rename, respawn, or
@@ -1955,8 +2364,8 @@ Selector and the implicit active target:
1955
2364
  hit. The describe family remains the intentional Project-only difference;
1956
2365
  Phase 14 extends only the generic rename family to ControlSession.
1957
2366
  `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
2367
+ resume` are outside that reference scope, and notifications belong to a
2368
+ separate store. Delete's exact live preflight nevertheless follows
1960
2369
  either root kind through the selected descendant's owner chain.
1961
2370
  - `--all-projects` is the explicit registry-wide escape for those three reads.
1962
2371
  It is deliberately different from destructive `delete --all`, whose existing
@@ -2033,14 +2442,6 @@ Projmux keeps visible naming separate from source metadata:
2033
2442
  user pane labels.
2034
2443
  - **Git branch** belongs in the statusbar git segment. Branch-based terminal
2035
2444
  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
2445
 
2045
2446
  ## Notify queue
2046
2447
 
@@ -2183,7 +2584,7 @@ The maintained product table in `internal/app/runtime_mutation_surface.go` maps
2183
2584
  generated catalog/menu producers, native provider/resume picker selections,
2184
2585
  sidebar/session-picker stops, and app lifecycle entrypoints in both directions
2185
2586
  to their handler and plan verb. It also records exact semantic exemptions for
2186
- focus, labels, operator-requested layout, mouse forwarding, snapshot replay,
2587
+ focus, labels, operator-requested layout, mouse forwarding,
2187
2588
  ephemeral maintenance, app quit, and human runtime maintenance. Managed argv
2188
2589
  verbs are selected only by the typed executor seam; generated Window
2189
2590
  create/rename, Pane-menu create/delete, and automatic post-split layout writes