projmux 0.12.1 → 0.13.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/docs/cli-guide.md CHANGED
@@ -71,13 +71,24 @@ The resource routes (`get`, `describe`, `create`, `rename`, `rebind`, `delete`,
71
71
  The grammar in one paragraph: a value is either `uid:<uid>` or a bare
72
72
  `metadata.name`. There is no bare-uid form, values are never split on commas,
73
73
  and a `displayName`, a `spec.root` path, or a raw tmux `%N`/`@N`/`$N` handle
74
- never resolves anything. `--project` occurs at most once and fixes the scope;
75
- `--window` and `--pane` repeat and union in argv order; `--selector key=value`
74
+ never resolves anything. `--project`/`-p` occurs at most once and fixes the
75
+ Project scope; `--window`/`-w` and `--pane` repeat and union in argv order;
76
+ `--selector key=value`
76
77
  repeats and ANDs. A singular route also accepts the target as a positional
77
78
  `<ref>`, which may appear before or after the flags. How many resolved targets
78
79
  each `<verb, kind>` pair accepts is a declared matrix, not a per-route rule, and
79
80
  a violation is exit `2` with a candidate listing bounded to five rows.
80
81
 
82
+ `-p` always means Project, never Pane; Pane has no shorthand. The three plural
83
+ reads `get windows|panes|agents` also accept `-A` as the exact alias of
84
+ `--all-projects`. That escape is mutually exclusive with either spelling of the
85
+ Project selector and is not registered on any other route. Long-only,
86
+ short-only, and mixed scope occurrences share the same value sinks, so repeated
87
+ Window order, cardinality, output, and mutation plans do not depend on spelling.
88
+ For resource-backed `create pane|agent|<provider>`, only aliases before the
89
+ first `--` are Projmux scope flags; every token after it, including `-p`, `-w`,
90
+ `--project`, and `--window`, remains opaque payload in its original order.
91
+
81
92
  ### Empty selector: the active tmux target
82
93
 
83
94
  Inside tmux, an invocation that carries **no selector at all** addresses the
@@ -100,13 +111,16 @@ The contract:
100
111
  --name current` succeeds — so a sentinel token would silently shadow a real
101
112
  resource.
102
113
  - **"No selector at all" means exactly that.** Any positional `<ref>`, any
103
- `--project`/`--window`/`--pane`, or any `--selector` label keeps the
104
- pre-existing behavior unchanged. The fallback is never blended into a
105
- partially specified selector.
106
- - **Only the singular routes.** The plural reads (`get projects|windows|panes|
107
- agents`) stay 0..N inventories over their whole scope. `delete` and `create`
108
- are unchanged: `create`'s omitted `--pane` selects a split anchor inside an
109
- already-chosen Window, which is not the same question.
114
+ `--project`/`-p`, `--window`/`-w`, `--pane`, or any `--selector` label keeps
115
+ picking the target itself. The active *target* is never blended into a
116
+ partially specified selector. The active *Project* is a separate rule and does
117
+ apply to a reference -- see [Reference scope](#reference-scope-the-active-project-namespace)
118
+ below.
119
+ - **Only the singular routes and `create`.** The plural reads (`get
120
+ projects|windows|panes|agents`) stay 0..N inventories over their whole scope,
121
+ and `delete` is unchanged. `create` has its own spelling of the same rule --
122
+ see [Create scope](#create-scope) -- because a create resolves a scope to put
123
+ something *into* rather than a target to act *on*.
110
124
  - **Inside tmux is decided by `$TMUX_PANE` plus `$TMUX`**, not by whether a tmux
111
125
  server answers. A bare `display-message` from outside a client still succeeds
112
126
  and answers for the most-recently-used session; projmux never uses that.
@@ -125,6 +139,155 @@ and `@projmux_window_uid` on its window — and derives every ancestor from
125
139
  registry `ownerRef`. The session-scoped `@projmux_project_uid` is deliberately
126
140
  not consulted.
127
141
 
142
+ ### Plural read scope: the active managed root
143
+
144
+ Inside tmux, selector-omitted `get windows|panes|agents` derives one exact
145
+ managed root from the active Window's Registry `ownerRef`:
146
+
147
+ - a Project-owned Window lists only that Project's descendants;
148
+ - a ControlSession-owned Window, including Home, lists only that
149
+ ControlSession's descendants;
150
+ - a missing Window mirror, a foreign Window uid, or a Window without one exact
151
+ existing Project or ControlSession owner is exit `2` with zero stdout. It
152
+ never falls through to the whole Registry.
153
+
154
+ The default narrows the Window universe and does not pick a target. Name and
155
+ label selectors keep their existing meaning inside that root. An applicable
156
+ explicit `uid:` occurrence is opaque Registry-global authority: like explicit
157
+ `--project`/`-p` and `--all-projects`/`-A`, it bypasses active-target observation
158
+ entirely, including from a foreign tmux Pane. The two whole-set spellings retain
159
+ their existing meaning and include ControlSession descendants. Outside tmux, an
160
+ omitted root keeps the historical whole-Registry inventory without probing a
161
+ default server.
162
+
163
+ ### Reference scope: the active Project namespace
164
+
165
+ A `metadata.name` is unique inside its owner scope, never across the registry: a
166
+ Window name is unique inside its Project, a Pane name inside its Window or
167
+ Agent, an Agent name inside its Window. So inside a managed Project a reference
168
+ is resolved inside the Project that owns the active Window, which is the same
169
+ universe the plural reads use in that context:
170
+
171
+ ```
172
+ projmux describe window zsh # the Window named zsh in *this* Project
173
+ projmux describe pane log # the Pane named log in this Project
174
+ projmux describe agent codex # the Agent named codex in this Project
175
+ projmux rename window zsh --name review
176
+ projmux rename pane log --name build
177
+ ```
178
+
179
+ The Project-only read matrix is `describe window|pane|agent`. Generic
180
+ `rename window|pane|agent` uses the same narrowing rule but accepts either the
181
+ active Project or ControlSession as the exact owner root. `describe project`
182
+ and `rename project` are not in either matrix — a Project has no enclosing
183
+ root — and `delete`, `rebind`, and `agent resume` keep their selector meaning.
184
+
185
+ The contract:
186
+
187
+ - **It narrows the search, it does not pick the target.** The Project fixes the
188
+ universe and nothing else. The active Window and the active Pane are not used
189
+ to break a tie, so two same-named resources inside the one Project stay the
190
+ ordinary bounded `matched N ..., want exactly one` ambiguity. Resolving that
191
+ is what `--window`/`--pane` and a `uid:` reference are for.
192
+ - **Explicit `--project`/`-p` wins, with zero observations.** Naming a Project
193
+ never costs a tmux round trip and never depends on the pane you are sitting
194
+ in.
195
+ - **A `uid:` reference is scoped too.** A uid that belongs to another Project is
196
+ a no-match, not a cross-Project hit. Pass `--project` to address it.
197
+ - **Outside tmux nothing changes.** The whole registry is searched and the
198
+ previous `matched N ..., want exactly one` ambiguity is unchanged. No default
199
+ tmux server is probed.
200
+ - **Inside tmux a broken owner chain refuses.** A pane carrying no
201
+ `@projmux_window_uid`, a mirrored Window uid the registry does not hold, or a
202
+ Window with no exact existing Project or ControlSession owner is exit `2`
203
+ with zero bytes on stdout and zero mutations for generic rename. It is never
204
+ a silent fallback to the whole registry.
205
+
206
+ ### Create scope
207
+
208
+ `create window|pane|agent|<provider>` is resource-backed on every spelling.
209
+ There is no mode flag and no second parser: the same argv means the same thing
210
+ whether or not `--project` is present, and `-w`, `--create-window`, `--pane`,
211
+ `--selector`, `--placement`, `--name`, `--label`, `--cwd`, `--add-dir`, `-o`,
212
+ and the `--` payload all reach the same parser either way.
213
+
214
+ The scope resolves in two branches:
215
+
216
+ - **Explicit `--project`/`-p` wins**, inside tmux and outside it. The active
217
+ tmux target is not consulted at all.
218
+ - **With no `--project`, the Project comes from the active managed runtime**:
219
+ the `@projmux_window_uid` mirrored on the pane you are in, and that Window's
220
+ registry `ownerRef`. This is the same seam the empty-selector reads use.
221
+
222
+ The Window and the anchor Pane follow the *whole* scope rather than the Project
223
+ flag alone:
224
+
225
+ ```
226
+ projmux create codex # active Project, active Window, split from the active Pane
227
+ projmux create codex -w hi --create-window # active Project, new Window "hi"
228
+ projmux create codex -p beta -w main # everything explicit
229
+ projmux create pane -p alpha # every Window of alpha; a deliberate fan-out
230
+ ```
231
+
232
+ One explicit scope occurrence (`--project`, `--window`, `--pane`, or
233
+ `--selector`) makes the whole scope explicit, so naming a Window never picks up
234
+ an anchor from somewhere you did not address. With a scope but no `--pane`, the
235
+ split anchor is the target Window's role-agnostic `spec.anchorPaneRef`. A
236
+ shell-required offline operation may plan a lazy direct
237
+ `spec.defaultShellPaneRef` without replacing an Agent anchor. A missing or stale
238
+ anchor is exit `2` rather than a silent alternate-Pane repair.
239
+
240
+ Refusals are exit `2` with zero Registry writes and zero tmux mutations, and
241
+ they name `--project` as the fix:
242
+
243
+ - outside tmux with no `--project` — no default server is probed;
244
+ - inside Home, a control session, an unattributed pane, or a foreign pane —
245
+ none of those carry a managed identity, and projmux never invents a Project
246
+ from `$HOME`, a session name, or a cwd;
247
+ - a mirrored uid the Registry does not hold, or a Window whose owning Project is
248
+ gone — a `recoverable` runtime is reported, never adopted.
249
+
250
+ Every create is **detached**: no create moves the client. Use `focus pane` or
251
+ `-o pane-id` when you want to end up in the new pane. Creates run against the
252
+ inherited exact socket inside tmux; outside tmux an explicit `--project` is
253
+ required before anything live is touched.
254
+
255
+ #### Splits started from a popup
256
+
257
+ The split UI's own pickers (`M-7`, `M-4`/`C-r`, the pane context menu, and the
258
+ default split key when the saved mode is `selective` or `resume`) run inside a
259
+ `display-popup`. tmux exports `$TMUX` to a popup job and deliberately exports no
260
+ `$TMUX_PANE`, because a popup is not a pane — so the picker has no inherited
261
+ target of its own while still knowing, from the keypress that opened it, which
262
+ pane the operator was in. That pane travels on the create intent as an explicit
263
+ anchor and resolves Project, Window and split anchor through the same identity
264
+ mirror a pane-hosted invocation reads.
265
+
266
+ The anchor is something the split UI hands to `create`, never something `create`
267
+ reads from the environment. `$TMUX_SPLIT_TARGET_PANE` is not a scope override:
268
+ typing `projmux create pane --placement right` inside a popup is still an
269
+ invocation with no target and still refuses with the `--project` usage error
270
+ above, and no read, rename, or delete verb consults it.
271
+
272
+ #### Managed pane context menu
273
+
274
+ The generated `MouseDown3Pane` menu treats its Horizontal Split, Vertical
275
+ Split, and Kill entries as Projmux resource actions. Both splits pass the exact
276
+ clicked pane through the popup-origin anchor above and reach the same canonical
277
+ `create pane` materializer as the CLI, so the new pane receives a Registry uid.
278
+ Kill resolves that anchor's mirrored uid and reaches canonical `delete pane`,
279
+ including its printed delete result. The menu never falls back to a raw tmux
280
+ mutation when either route refuses. The reason is displayed on the exact client
281
+ that opened the menu instead of being lost as a `run-shell` exit code.
282
+
283
+ tmux Respawn has no equivalent in the current resource model: it preserves the
284
+ same pane handle, layout, Registry uid, and original command, while canonical
285
+ delete plus create removes that identity and creates another one (and may end
286
+ the Window or Project session when it deletes the last pane). The generated
287
+ menu therefore omits Respawn entirely and does not expose a refusal handler or
288
+ invent a replace operation. User-authored tmux bindings remain outside this
289
+ managed-menu contract.
290
+
128
291
  ### Rename and rebind live convergence
129
292
 
130
293
  `rename project|window|pane` commits the selected Registry `metadata.name` and
@@ -155,6 +318,26 @@ duplicate UID claims remain fail-closed.
155
318
 
156
319
  ### Agent topic, interaction, activation, and workspace
157
320
 
321
+ `agent turn start`, `agent turn steer`, `agent turn interrupt`, and
322
+ `agent approval review` use the live Codex app-server connection only when the
323
+ selected Agent, its owned Pane, activation generation, thread, current turn,
324
+ and connection epoch still match exactly. `start` sends only the exact thread
325
+ id and one text input; `steer` supplies the current expected turn id; and
326
+ `interrupt` supplies that exact turn id. These commands never install sticky
327
+ model, effort, cwd, sandbox, permission, or collaboration overrides.
328
+
329
+ Approval review shows only the safe one-shot intersection supplied by the
330
+ exact pending request. Command, file, and network requests are limited to
331
+ `accept`, `decline`, and `cancel`; permission grants echo the received supported
332
+ profile with `scope=turn` and `strictAutoReview=null`. Session grants, policy or
333
+ network amendments, unstable root grants, legacy approval mutations, and
334
+ automatic approval are unavailable. The request envelope and raw JSON-RPC id
335
+ remain only in connection memory; reconnect, resolution, ambiguity, or any
336
+ identity mismatch produces no provider write. Approval queue rows advertise
337
+ `Review pending approval` only while that responder exists, otherwise they
338
+ advertise the exact-Agent `Open Codex` focus fallback; resolution removes the
339
+ row. Neither route stores prompt, command, path, permission, or request content.
340
+
158
341
  `agent topic get|set|clear` and `agent status get|set` resolve exactly one
159
342
  Agent, either from an explicit Agent reference or from the Agent-owned active
160
343
  managed Pane. Topic is a non-identifying Registry annotation. Interaction is a
@@ -182,6 +365,40 @@ and shell Pane manual attention share the existing priority reducer, while
182
365
  `dot`/`emoji`/`off`, glyphs, colors, and the Window aggregate are never stored
183
366
  in resource metadata.
184
367
 
368
+ A clean managed process exit is topology authority only through the exact
369
+ generated `pane-exited` hook. The supervisor must have durably journaled a
370
+ same-generation `normal` receipt, the hook must name the exact `%N` Pane and
371
+ the owner Window must carry its exact last-positive `$N/@N` binding on the same
372
+ socket, and fresh preflight plus locked observations must still resolve the
373
+ same Registry owner chain. A non-last Pane is removed while its directly owning
374
+ Agent is retained Offline with its conversation identity. For a last Pane, that evidence is retained until a
375
+ matching `window-unlinked` hook removes the Window; a final Project Window also
376
+ removes its Window descendants while retaining the exact Project uid, root,
377
+ reservation, pins, snapshots, and external assets as a valid zero-Window
378
+ Project. Managed runtime Stop is different: it stops only the exact runtime and
379
+ keeps the complete desired Project/Window/Pane graph closed for a same-UID
380
+ Continue. Shell and Claude/Codex clean exit have the same result; `/exit`, pane
381
+ content, prompt, history, and transcript are never parsed. `abnormal`,
382
+ `killed`, `unknown`, stale/resumed bindings, empty or unavailable inventory,
383
+ permission failure, and foreign-host/window observations keep their diagnostic
384
+ rows and produce no automatic delete plan.
385
+
386
+ For `pane-exited`, tmux supplies `%N` as `#{hook_pane}`. Its current-context
387
+ session/window formats may already name a surviving client Window, so the owner
388
+ pair comes from the Window's last live Registry observation. The separate
389
+ `window-unlinked` hook supplies exact `#{hook_session}` and `#{hook_window}`;
390
+ only a matching causal pair authorizes last-Pane Window deletion.
391
+
392
+ An offline historical Window with no stored causal last-Pane receipt is not
393
+ absence-only migration authority and is never auto-deleted. Recover it with the
394
+ canonical exact route, `projmux delete window uid:<window-uid> --socket <name>
395
+ --yes` (or the corresponding `--socket-path`/inherited absolute `$TMUX` route).
396
+ Deleting a Project's last exact Window retains the Project with zero Windows;
397
+ within the runtime/startup lifecycle table, explicit `delete project --yes` is
398
+ the unregister operation. Runtime absence, managed Stop, ordinary Window close,
399
+ and Fresh never invoke the separately scoped filesystem-missing `prune project`
400
+ administrative policy.
401
+
185
402
  Resource-backed Agent create accepts provider-neutral `--cwd <absolute>` and
186
403
  repeatable `--add-dir <absolute>`. Explicit paths must exist, resolve without a
187
404
  symlink escape, and remain inside a registered Project tree; only Codex and
@@ -199,8 +416,15 @@ owner Project root from `get`/`describe`, and a successful resume persists that
199
416
  normalized effective workspace without changing Window Project ownership.
200
417
 
201
418
  When `create agent -- <initial-prompt>` is used, normal resource creation and
202
- provider activation are distinct. Projmux waits for bounded hook/lifecycle
203
- metadata only; it never captures pane content or stores the prompt. If
419
+ provider activation are distinct. Projmux first waits up to five seconds for an
420
+ exact provider `SessionStart`; that readiness evidence leaves activation
421
+ `pending` and opens an independent five-second initial-task acknowledgement
422
+ window. A `UserPromptSubmit` acknowledgement may also arrive directly before
423
+ the readiness observer sees `SessionStart`. The two stages are independently
424
+ bounded, so provider startup plus an acknowledgement later than two seconds may
425
+ take more than five but never more than ten seconds. Neither stage captures pane
426
+ content or stores the prompt. Acknowledgement returns success and the requested
427
+ exact `%N` output. If
204
428
  activation cannot be confirmed, the command exits nonzero while naming the
205
429
  exact Agent UID and Pane plus safe provider retry and `delete agent ... --yes`
206
430
  cleanup options. The live resources remain explicit and retryable rather than
@@ -208,7 +432,13 @@ being reported as an ordinary success.
208
432
 
209
433
  Activation metadata is bounded to provider-hook provenance and fixed
210
434
  acknowledged/timed-out/failed diagnostics. Provider error strings and initial
211
- prompt text are never stored. Resource-backed Agent create and resume do not
435
+ prompt text are never stored. `pending` can become `unconfirmed` and a later
436
+ exact hook can refine either state to `acknowledged`; acknowledged never moves
437
+ backward. Late refinement must quote the same Agent, Pane uid, activation
438
+ generation, and recorded live `%N`, so a hook from a replaced materialization
439
+ cannot acknowledge its replacement. Codex and Claude initial payloads use the
440
+ same acknowledgement and result contract; provider-specific prompt content is
441
+ not evidence. Resource-backed Agent create and resume do not
212
442
  start the legacy title/content watcher; that watcher remains only for legacy
213
443
  non-resource panes and exits before reading title or capture content if resource
214
444
  identity appears.
@@ -246,7 +476,7 @@ source, different output, different failure surface — both stay.
246
476
  ## reconcile resources
247
477
 
248
478
  ```text
249
- projmux reconcile resources [--dry-run] [--socket <name> | --socket-path <absolute>] [-o json]
479
+ projmux reconcile resources [--dry-run] [--materialize-project <name|uid:uid>] [--socket <name> | --socket-path <absolute>] [-o json]
250
480
  ```
251
481
 
252
482
  `reconcile resources` is the explicit repair boundary for drift between the
@@ -288,6 +518,238 @@ It does not run `config apply`, reload unrelated configuration, reconstruct a
288
518
  missing Registry, heuristically merge identities, or turn a read verb into a
289
519
  write transaction.
290
520
 
521
+ `--materialize-project <name|uid:uid>` is the explicit activation opt-in for
522
+ one exact Registry Project. Its separate pure plan treats Registry insertion
523
+ order as desired topology and reports a missing persistent session, each
524
+ missing Window, and each missing Window-owned `role=shell` Pane. Execute creates
525
+ only that missing subset on the selected socket, mirrors the existing Registry
526
+ UID/name/owner graph, preserves each Pane's stored CWD, and is a true no-op when
527
+ the graph is already live. The option is exact-one: repeating it is a usage
528
+ error, and the reported retry preserves both the selector and socket route.
529
+
530
+ Opening a closed Project is the other activation authority for that same
531
+ engine. The Alt-1 sidebar/`switch open` path materializes the selected Project's
532
+ declared shell topology on the app's own `-L projmux` socket and moves the
533
+ client only after it converges; a refusal, a failed preflight, or a rolled-back
534
+ partial leaves the client where it was and reports the exact stage. The
535
+ activation is pinned to the session the open targets, so a Project whose
536
+ Registry projects a different session name is refused instead of populating a
537
+ session the open never reaches. The closed-Project startup screen has exactly
538
+ two neutral actions. `Continue project` materializes current Registry desired
539
+ state with the same Project UID. A retained graph keeps descendant UIDs; a
540
+ zero-Window Project atomically receives a new canonical Window/shell UID chain.
541
+ A deleted Project may use only the exact usable snapshot compatibility path;
542
+ an unavailable Continue is an explicit zero-write refusal with no Fresh
543
+ fallback. `Open fresh` is one step with no danger styling, confirmation, or
544
+ delete counts: it atomically replaces the same-root graph with a new Project
545
+ UID and new canonical Window/shell UIDs, then hands off only after ordinary
546
+ materialization. A repeat replaces identity again. The root, git/worktrees,
547
+ trust decision, unrelated roots, and snapshot bytes remain unchanged.
548
+
549
+ Materialization launches or resumes declared Agents through the canonical
550
+ provider/trust path and creates their managed Agent-owned Panes. An individual
551
+ Agent refusal preserves the committed desired Registry and emits an item notice
552
+ for retry. It never directly executes `Pane.spec.command`; that field remains a
553
+ one-time name seed. A new Window binds only its own tmux-created primary Pane. On an existing Window,
554
+ every pre-existing uid-less Pane is refused rather than adopted; foreign,
555
+ duplicate, wrong-owner, or otherwise ambiguous UID state is refused before the
556
+ first create. Layout uses the existing deterministic right-axis equalizer and does
557
+ not promise historic geometry. Canonical Registry deletion removes desired
558
+ topology, so a deleted Window or Pane is not recreated; raw runtime loss while
559
+ the resource remains is drift and is materialized. A non-implicit offline
560
+ Window target (an explicit reference or `--all`) can be canonically deleted
561
+ Registry-only: its complete Agent/Pane cascade is shown under `--dry-run`, no
562
+ tmux object is killed, and unrelated live objects and sockets are untouched. A
563
+ unique live mirror keeps the exact-kill path, while duplicate, foreign,
564
+ stale-owner, inventory-failure, and revalidation-race states are refused.
565
+ The selected Window's Registry `ownerRef` is authoritative in that preflight:
566
+ a Project uses its `status.session` plus the optional matching Project uid
567
+ mirror, while a ControlSession uses its exact `spec.session` and requires that
568
+ no Project uid mirror contaminate the control session. Window, Pane, and Agent
569
+ deletes preserve that `(root kind, root uid)` chain in the signed live plan and
570
+ report a final-window cascade with its actual root kind.
571
+
572
+ Pane and Agent Registry-only deletion is deliberately narrower. It accepts
573
+ only an explicit exact `uid:` selector: a Pane must carry durable
574
+ `MissingRuntime=True/RuntimeUnbound` evidence, and an Agent must be `Offline`
575
+ with no `paneRef` (with every retained descendant Pane also marked
576
+ `MissingRuntime`). The exact routed server must answer with a non-empty socket
577
+ identity and a non-empty Pane inventory that proves the target has zero mirrors.
578
+ A missing server, empty or failed inventory, unavailable or permission-denied
579
+ transport, implicit/name/scope/`--all` selection, and duplicate or foreign
580
+ mirrors are not absence authority and make zero writes. Dry-run and apply sign
581
+ the same socket, owner/root chain, lifecycle evidence, Pane activation
582
+ generation, and Agent binding; locked revalidation refuses zero-to-live,
583
+ live-to-zero, owner, generation, duplicate, or foreign changes. A successful
584
+ Registry-only result reports that no tmux Pane was killed, preserves the owning
585
+ Window/root/socket and all siblings, and repeating the exact apply returns the
586
+ ordinary no-match result.
587
+
588
+ `delete window|pane|agent` names the server its live half addresses the same
589
+ way `reconcile resources` does: `--socket <name>`, `--socket-path <absolute>`,
590
+ or the inherited absolute `$TMUX`. Outside tmux with neither flag it refuses
591
+ rather than reaching for the app's own socket, so a delete issued against an
592
+ isolated server can never inventory one host and kill objects on another.
593
+
594
+ Before it kills anything, a delete commits an intentional termination receipt
595
+ against every Pane whose process it is about to end, in its own Registry
596
+ transaction. If that write fails, nothing live is touched; if the delete then
597
+ refuses for any other reason, the receipt is withdrawn again. The receipt is
598
+ what tells a later reader that a process disappeared because someone asked for
599
+ it, rather than because it crashed.
600
+
601
+ ## get runtime
602
+
603
+ ```text
604
+ projmux get runtime sessions|windows|panes [--socket <name> | --socket-path <absolute>] [-o json|none]
605
+ ```
606
+
607
+ `get runtime` is the read-only escape hatch onto one exact tmux server. The
608
+ resource reads (`get projects|windows|panes|agents`) enumerate the Registry;
609
+ this one enumerates the machine, including everything projmux does not own, and
610
+ it accepts no selector because most of what it reports has no name to resolve.
611
+ Its kinds are tmux object kinds, not resource kinds, and they have no singular
612
+ spelling for the same reason.
613
+
614
+ Every row carries the attribution the resolved resource graph decided from exact
615
+ evidence -- `managed`, `recoverable`, `control`, `ephemeral`, `unattributed`,
616
+ `foreign`, `conflict` -- the reason for it, the stable tmux id, the fully
617
+ qualified coordinate (`<session>`, `<session>:@N`, `<session>:@N.%N`), and, for a
618
+ managed object, the Registry resource it is bound to. A refused object is named
619
+ and explained and is never handed a resource identity.
620
+
621
+ Socket selection is the same fail-closed rule `reconcile resources` uses, with
622
+ one difference at the end:
623
+
624
+ - `--socket <name>` means exactly `tmux -L <name>`.
625
+ - `--socket-path <absolute>` means exactly `tmux -S <absolute>`.
626
+ - The two flags are mutually exclusive.
627
+ - With neither flag, an invocation inside tmux inherits only the absolute socket
628
+ path from `$TMUX` and uses `-S`.
629
+ - With neither flag outside tmux the read still succeeds. It returns the
630
+ unavailable projection: no items, every scope reported unobservable with a
631
+ reason, and zero tmux calls. There is no default-socket guess, and a sibling
632
+ socket is never read.
633
+
634
+ The default projection is a table preceded by a header line naming the host mode
635
+ and the exact transport, plus one line per scope that could not be observed. The
636
+ header is always printed, even when the table is empty: "no sessions" is only
637
+ trustworthy next to which server was asked and whether the answer could be taken
638
+ at all. `-o json` emits the same data as a stable `Runtime{Session,Window,Pane}List`
639
+ envelope; `-o none` prints nothing. The Registry projections (`uid`, `name`,
640
+ `ref`, `metadata`) are deliberately not offered, because most of what this route
641
+ returns has none of them.
642
+
643
+ The read writes nothing: the Registry is opened without being created, the
644
+ observation issues one option probe and three list queries whatever the size of
645
+ the server, and no write verb is ever sent.
646
+
647
+ ## runtime diagnostics
648
+
649
+ ```text
650
+ projmux runtime diagnostics [--socket <name> | --socket-path <absolute>] [--ui=popup|sidebar]
651
+ ```
652
+
653
+ The interactive half of the same read. It lists every tmux object on the exact
654
+ server in containment order with an attribution tally, and it is deliberately
655
+ separate from `projmux runtime sessions`: that picker lists recent sessions so
656
+ you can open one, this one lists everything so you can understand it.
657
+
658
+ Selecting a row opens its action menu, which offers only routes that already
659
+ exist:
660
+
661
+ - **Focus** hands `projmux focus` the row's exact coordinate and the server's own
662
+ `#{socket_path}`. It moves a client and never materializes anything.
663
+ - **Attach** forwards to `projmux attach project uid:<uid>`, and is offered only
664
+ for a session bound to a Registry Project while you are outside tmux.
665
+ - **Open Resource Inspector** opens `projmux resources` unchanged.
666
+
667
+ An action that does not apply is listed with the reason instead of being hidden:
668
+ "no Registry Project claims this session; diagnostics never adopts one" is the
669
+ diagnostic. There is no adopt, import, rename, or kill action, and opening the
670
+ surface writes nothing.
671
+
672
+ ## reconcile registry
673
+
674
+ ```text
675
+ projmux reconcile registry [--dry-run] [--source <name|absolute-path>] [--expect-source-checksum <sha256:hex>] [--expect-current-checksum <sha256:hex>] [--socket <name> | --socket-path <absolute>] [-o json]
676
+ ```
677
+
678
+ `reconcile registry` is the recovery boundary for the Registry itself. It is a
679
+ sibling of `reconcile resources`, not a stronger version of it: `reconcile
680
+ resources` converges a Registry that loads, and this route runs when the
681
+ Registry is the thing that is wrong.
682
+
683
+ Planning writes nothing. With no `--source` — and with `--dry-run` at any time —
684
+ the command reads the current `registry.json`, the `registry.initialized`
685
+ marker, and the bounded copies under `recovery/`, then reports:
686
+
687
+ - the current state as `valid`, `first-use`, `missing`, `empty`, `malformed`,
688
+ `schema-too-new`, `invalid`, or `unreadable`, with a `sha256:` digest of the
689
+ exact bytes;
690
+ - every candidate newest first, each marked `eligible` or `rejected` with the
691
+ reason, its digest, size, mtime, schema version, and the
692
+ projects/windows/panes/agents/reservations it holds;
693
+ - the exact guarded command that would restore the candidate it suggests.
694
+
695
+ No lock is taken, no permission is repaired, and `<state>/projmux/metadata/` is
696
+ not created — a preview is safe against a first-use state directory and against
697
+ one nobody should be writing to yet.
698
+
699
+ Restoring requires `--source`. There is no "restore the newest" mode: which copy
700
+ is the truth is a judgment about which mutations were wanted. A source is an
701
+ exact copy name, a unique fragment of one, or an absolute path to a copy carried
702
+ from elsewhere; a fragment matching several copies is refused rather than ranked.
703
+ An explicit path gets exactly the same verification as a bounded copy.
704
+
705
+ Verification is fail closed. Malformed JSON, an empty file, an envelope newer
706
+ than this build, and a graph with a duplicate uid, a dangling `ownerRef`, or a
707
+ broken name reservation are all refused with the current Registry byte-identical.
708
+ The verified bytes are then published **verbatim**, so uids, owner relations, and
709
+ name reservations are preserved exactly rather than re-encoded, and a
710
+ known-older-but-valid envelope stays readable through the normal safe read and
711
+ migrates on the next semantic write.
712
+
713
+ The bytes being replaced are kept first, at
714
+ `recovery/replaced-<stamp>-<seq>.json`. Unlike the write-side copies this keeps
715
+ content that does not verify: a damaged Registry is the only remaining evidence
716
+ if the restore turns out to be the wrong call, and the preserved copy is offered
717
+ back as a candidate. Replaced copies are their own bounded family, so a restore
718
+ never consumes the automatic write history.
719
+
720
+ Race guards are the operator's tie to the plan they read:
721
+
722
+ - `--expect-source-checksum <sha256:hex>` refuses unless the source still hashes
723
+ to that digest.
724
+ - `--expect-current-checksum <sha256:hex>` refuses unless the current Registry
725
+ still does.
726
+ - Both are what the printed `next:` command already carries, so copy-pasting the
727
+ preview's suggestion is guarded by construction.
728
+
729
+ Underneath, the source is re-read and re-verified under the store lock, the
730
+ staged copy is re-validated, and both inputs are re-hashed immediately before the
731
+ single atomic rename. Anything that moved refuses with nothing published, no
732
+ preserved copy, and no staged file left behind, and says to re-run the preview. A
733
+ repeat restore is a byte no-op: no rename, no preserved copy, no marker write.
734
+ Restoring into a state directory with no marker publishes one, so a later loss on
735
+ that machine reads as state loss rather than as a fresh first use.
736
+
737
+ When recovery is needed and no verified copy exists, the report adds a mirror
738
+ diagnostic — and it is **only** a diagnostic. It reports the Projmux identity the
739
+ one exact tmux server still carries (Project/Window/Pane uids, mirrored names,
740
+ the Project root, and containment resolved from stable tmux ids) beside a fixed
741
+ statement of what no mirror can return: offline resources, every Agent (no tmux
742
+ option carries an Agent uid), an Agent-owned Pane's `ownerRef`, the name
743
+ reservation table, `spec.anchorPaneRef`, `spec.defaultShellPaneRef`, and labels/annotations/timestamps/
744
+ status. Panes carrying a provider option are counted as proof that Agents existed
745
+ whose uids are nowhere on the server. Nothing is imported and no Registry is
746
+ generated from fragments. Socket selection follows the same `--socket` /
747
+ `--socket-path` / inherited-`$TMUX` rule as `reconcile resources`, except that
748
+ having no exact target is reported as a reason rather than being a usage error:
749
+ a restore is a filesystem operation, so recovery must work on a machine with no
750
+ tmux server. The diagnostic is skipped entirely when a verified copy exists or
751
+ the Registry is healthy.
752
+
291
753
  ## Internal plumbing (`projmux internal ...`)
292
754
 
293
755
  `internal` is a hidden namespace for the routes generated tmux config, tmux
@@ -545,8 +1007,8 @@ back into the unredacted Doctor Go types.
545
1007
 
546
1008
  ```
547
1009
  projmux focus project <ref> [--socket <path>] [--client <tty>] [--json]
548
- projmux focus window <ref> --project <ref> [--socket <path>] [--client <tty>] [--json]
549
- projmux focus pane <ref> --project <ref> --window <ref> [--socket <path>] [--json]
1010
+ projmux focus window <ref> -p <project-ref> [--socket <path>] [--client <tty>] [--json]
1011
+ projmux focus pane <ref> -p <project-ref> -w <window-ref> [--socket <path>] [--json]
550
1012
  ```
551
1013
 
552
1014
  Moves one attached client to an exact live Project, Window, or Pane. It never
@@ -702,10 +1164,12 @@ core-equivalent CPU. Project and window rows count panes, while pane rows count
702
1164
  the attributed processes. Pane rows and detail share the resolved pane identity
703
1165
  and label the tmux current command, PID/SID, pane id, and TTY separately. Memory
704
1166
  is explicitly an RSS sum (shared pages can be counted more than once) plus its
705
- host ratio. `No project match`, `Multiple project matches`, and non-drillable
706
- `Other / unattributed` remain explicit. The first two are display labels over
707
- stable internal attribution keys. Warming, partial, unavailable, unknown, and
708
- overage states also remain explicit. No process
1167
+ host ratio. Concrete projects and `No project match` are the only project
1168
+ drill-down groups; non-drillable `Other / unattributed` remains explicit.
1169
+ Defensive ambiguous attribution stays in its stable internal bucket, is
1170
+ included in Attributed totals, and appears only as a bounded CPU/RSS/pane
1171
+ diagnostic rather than a project row. Warming, partial, unavailable, unknown,
1172
+ and overage states also remain explicit. No process
709
1173
  command list, mutation, history, graph, daemon, persistence, or Session State
710
1174
  telemetry is created. Linux/tmux provides attribution; unsupported platforms
711
1175
  show an unavailable reason rather than zero metrics.
@@ -816,8 +1280,8 @@ supplied window.
816
1280
  ## Agent creation and hook ingress
817
1281
 
818
1282
  ```
819
- projmux create agent --provider <claude|codex|antigravity> [--placement right|down] ...
820
- projmux create pane [--placement right|down] ...
1283
+ projmux create agent --provider <claude|codex|antigravity> [--project <ref>] [--window <ref>]... [--create-window] [--placement right|down] ...
1284
+ projmux create pane [--project <ref>] [--window <ref>]... [--create-window] [--placement right|down] ...
821
1285
  projmux config edit [--get|--set <mode>]
822
1286
  projmux agent status set <thinking|waiting|idle> [pane]
823
1287
  projmux agent topic ...
@@ -848,34 +1312,60 @@ yellow respectively. That palette is independent from notify queue
848
1312
  can still render a non-red action-required status badge.
849
1313
 
850
1314
  `create agent --provider ...` selects a provider without changing the saved
851
- default. Concrete provider invocations create a new managed
852
- agent pane every time; existing managed AI panes in the same project/session are
853
- not selected or reused.
854
- The provider picker and plain Pane creation remain available through the
855
- canonical create workflow. Arguments after `--` are extra arguments appended to
1315
+ default. Concrete provider invocations create a new Agent and a new managed
1316
+ Pane every time; existing managed AI panes in the same project/session are
1317
+ not selected or reused, and rebinding an existing conversation is `agent
1318
+ resume`, a different verb. The scope of the new resources follows
1319
+ [Create scope](#create-scope). The provider picker remains available through
1320
+ `internal agent-pane picker`. Arguments after `--` are extra arguments appended to
856
1321
  the resolved `claude`, `codex`, or `agy` executable inside the managed wrapper;
857
- projmux still sets the context directory, tmux title, AI pane metadata, title
858
- watcher, and split layout.
859
-
860
- Automation callers can add `--print-pane-id` to an explicit direct
861
- `--agent claude|codex|antigravity|shell` launch. On success, stdout contains
862
- exactly the new `%N` pane id followed by one newline. The value comes directly
863
- from tmux's existing
864
- `split-window -P -F '#{pane_id}'` result. If tmux returns no valid pane id, the
865
- command fails non-zero with tmux-specific guidance and writes no
866
- success value. Without `--print-pane-id`, successful split invocations keep the
867
- existing empty-stdout behavior.
868
-
869
- `--print-pane-id` is not available for the saved default mode or for
870
- `--agent selective|resume`, because those paths may open a picker and launch
871
- only after a later user selection. Those combinations fail before opening a
872
- picker or creating a pane. Arguments after `--` keep their existing argv-tail
873
- meaning when the flag is used with a concrete AI agent.
1322
+ projmux still sets the context directory, tmux title, AI pane metadata, and
1323
+ split layout.
1324
+
1325
+ Automation callers get the new pane's handle from `-o pane-id` on the canonical
1326
+ create routes: `projmux create agent --provider <p> --placement right -o pane-id`
1327
+ and `projmux create pane --placement right -o pane-id` each print exactly the
1328
+ managed Pane's `%N` followed by one newline. See
1329
+ [AI Agent Shortcuts](ai-agent-shortcuts.md) for the shortcut spellings.
1330
+
1331
+ Every Projmux split surface produces the same canonical create intent. The
1332
+ default `ai-split-right/down` binding reads the saved split mode and turns it
1333
+ into one intent -- a provider Agent, a shell Pane, or one of the two pickers --
1334
+ and the `Alt-7` picker and the resume picker do the same with what the operator
1335
+ selected. Only the create route's materializer runs tmux's `split-window`, so a
1336
+ pane opened from the UI is a Registry resource on the same terms as one asked for
1337
+ by name, and a failed launch leaves zero Registry and zero tmux mutations. A raw
1338
+ unmanaged split exists only where you make one yourself.
1339
+
874
1340
  The resume picker lists the newest deduplicated Claude, Codex, and Antigravity
875
1341
  resume sessions for the current project, with `[+ New Session]` pinned first.
876
1342
  If there are no resume sessions it goes straight to the existing selective
877
- picker. Selecting a row directly starts `claude --resume <id>`,
878
- `codex resume <id>`, or `agy --conversation <uuid>`.
1343
+ picker. Selecting a row creates a managed Agent whose pane joins that
1344
+ conversation -- `claude --resume <id>`, `codex resume <id>`, or
1345
+ `agy --conversation <uuid>`. Rebinding an Agent the Registry already has is
1346
+ `projmux agent resume`, a different verb that never falls back to a fresh
1347
+ conversation.
1348
+
1349
+ Codex rows come from one source per picker invocation. A healthy app-server is
1350
+ primary: `thread/list` is paged with opaque cursors, non-archived and explicit
1351
+ `cli`/`vscode`/`appServer` source filters, provider recency ordering, and exact
1352
+ cwd/depth filtering. Rows preserve the exact thread id plus provider name,
1353
+ branch, and runtime status; an unnamed thread uses only its short id, never its
1354
+ prompt preview. The row suffix displays native or rollout source, confidence,
1355
+ status, and any closed fallback reason. Unsupported, unavailable, protocol, or
1356
+ malformed-pagination results discard the native partial result and run the
1357
+ existing rollout scan once. Selecting a native row resumes that same thread id
1358
+ through the native lane; selecting a fallback row keeps the current CLI lane.
1359
+ Claude and Antigravity discovery and launch semantics are unchanged.
1360
+
1361
+ `projmux agent review [<agent-ref>]` starts a native Codex review for
1362
+ uncommitted changes by default. Use exactly one of `--base <branch>`, `--commit
1363
+ <sha>`, or `--instructions <text>` to choose another review target. The action
1364
+ is available only when the selected Running Codex Agent still has an exact live
1365
+ Pane/thread binding and the current app-server supports `review/start`; every
1366
+ other case reports review as unavailable without changing the Agent. This route
1367
+ projects only the initial response into interaction status. It does not claim
1368
+ the later notification-driven completion lifecycle.
879
1369
 
880
1370
  Live Antigravity hook/session-state resume metadata remains a separate,
881
1371
  high-confidence lane; it is not enumerated from disk by the picker. Within the
@@ -895,10 +1385,8 @@ Settings > AI Settings > Enabled agents controls Claude/Codex/Antigravity launch
895
1385
  visibility. Disabled agents are hidden from the selective picker and from the
896
1386
  default-mode picker. A saved default that later becomes disabled fails clearly
897
1387
  instead of falling back to another agent. Direct
898
- `--agent claude|codex|antigravity` launches also fail when disabled, including
899
- shortcuts that call the same command. For a deliberate one-shot direct CLI
900
- launch, pass `--force-agent`; picker and saved default paths do not use this
901
- override. If all AI agents are disabled, the selective picker still offers the
1388
+ Canonical `create agent --provider <p>` launches and the provider shortcuts also
1389
+ fail when disabled. If all AI agents are disabled, the selective picker still offers the
902
1390
  plain `shell` split and shows guidance to re-enable Claude/Codex/Antigravity.
903
1391
  For user-level skill, slash-command, editor, or launcher registrations that
904
1392
  call this contract, see [AI Agent Shortcuts](ai-agent-shortcuts.md).
@@ -1354,7 +1842,8 @@ returns a usage error.
1354
1842
 
1355
1843
  The live tmux inventory is under `runtime`: `runtime sessions`, `runtime
1356
1844
  attach`, `runtime stop`, `runtime tag`, and `runtime prune`. Project pins use
1357
- `pin project list|add|remove|toggle|clear`. Resource retention uses `prune
1845
+ `pin project list|add|remove|toggle|clear|migrate`; `list` takes `--kind
1846
+ project|candidate` and `migrate` takes `--dry-run`. Resource retention uses `prune
1358
1847
  project|snapshot`, while explicit snapshot deletion uses `delete snapshot`.
1359
1848
 
1360
1849
  Popup-marker, preview, status, and tmux configuration plumbing is hidden under
@@ -1368,11 +1857,16 @@ human configuration work should prefer `config render` and `config apply`.
1368
1857
  generated config. The generated app config uses absolute `$SHELL` as the
1369
1858
  tmux default shell when set, otherwise `/bin/sh`. `shell` starts or attaches
1370
1859
  the app session directly after resolving the target app session name and
1371
- startup directory. Alt-1 sidebar project open defaults to `Empty session`; the
1372
- Session State `Sidebar startup picker` opt-in shows `Latest snapshot`, `Named
1373
- snapshot`, and `Empty session` before creating a closed project session.
1374
- `Latest snapshot` is auto-saved; named snapshots are fixed until the user
1375
- saves or replaces them.
1860
+ startup directory. Alt-1 sidebar project open defaults to `Continue project`,
1861
+ which materializes the Project's Registry Windows, shell Panes, and Agents
1862
+ before the client moves. When the startup picker is enabled it contains exactly
1863
+ `Continue project` and `Open fresh`; Esc returns to Projects. `Continue
1864
+ project` restores a deleted Project only from its usable exact snapshot and
1865
+ otherwise refuses with zero Registry writes. `Open fresh` is a neutral,
1866
+ confirmation-free one-step action that atomically replaces the Project with
1867
+ a new Project/Window/shell UID chain and one same-root claimant. Repeating it
1868
+ allocates another new identity. Neither action modifies snapshot
1869
+ bytes, the project directory, git/worktrees, unrelated roots, or trust state.
1376
1870
  - `quit` — open an action picker with `Quit projmux` and `Cancel`. Selecting
1377
1871
  `Quit projmux` terminates only a `tmux -L projmux` runtime whose global
1378
1872
  `@projmux_app` option is set by the generated app config. Missing servers,