projmux 0.14.2 → 0.15.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.
@@ -313,29 +313,42 @@ Identity and naming:
313
313
  - `metadata.uid` is opaque, immutable, and independent of tmux lifecycle. It
314
314
  survives snapshot/restore, runtime creation, and root rebind.
315
315
  - `metadata.name` is the stable unique-within-scope query key. Project names
316
- are unique across the registry; Window, Pane, and Agent names are unique
317
- within their `ownerRef` scope.
318
- - `metadata.displayName` may duplicate and is never a selector, an ownerRef, or
319
- identity. `metadata.labels` is key/value classification;
320
- `metadata.annotations` is non-identifying metadata such as an AI topic.
321
- - Name bases are assigned once, at create or migration time, and are never
322
- re-derived. A declared Window create uses explicit name → initial command
323
- basename → configured shell basename → `window`; a Window newly imported
324
- from tmux always uses the literal `window` allocator base. Its observed
325
- `window_name`, Pane label, provider, command, shell, topic, and title are all
326
- excluded from stable identity. Shell Pane uses command basename → shell
327
- basename → `pane`; a Pane managed by an Agent uses `<agent-name>-pane`; Agent
328
- uses explicit `--name` → normalized provider id → `agent`.
329
- - For a Window, the observed tmux `window_name` is
330
- `metadata.displayName`: duplicate-allowed, visible in `describe window`, and
331
- never a selector, ownerRef, or identity input. Existing Window uids, names,
332
- owners, and name reservations are preserved; there is no bulk naming
333
- migration.
334
- - Automatic collisions take the lowest free suffix (`Projmux-1`, `codex-1`)
335
- from the persisted `nameReservations` table, scanning integer suffixes rather
336
- than resource or map iteration order. An **explicit** `--name` or rename
337
- collision never receives an implicit suffix: it fails with exit code 2 and
338
- zero mutations.
316
+ and ControlSession names are unique within their own root kind across the
317
+ registry. Window, Pane, and Agent names are unique by
318
+ `(rootOwnerUID, kind, name)`, where the root is the Project or ControlSession
319
+ reached through the direct `ownerRef` chain. Different roots and different
320
+ kinds may use the same spelling.
321
+ - Automatic creation mints the opaque UID first and stores that exact full UID,
322
+ including its kind prefix and complete payload, as `metadata.name`. If that
323
+ name slot is already occupied, the unpublished UID is discarded and reminted
324
+ up to 100 times. There is no semantic stem, prefix truncation, sibling scan,
325
+ or numeric suffix allocator. An explicit `--name` keeps its original spelling
326
+ after validation; explicit create and rename collisions fail with exit code 2
327
+ and zero Registry, tmux, or provider writes.
328
+ - `create agent` supplies an **explicit** name for the Pane its Agent owns:
329
+ `<agent-name>-pane`, derived from the Agent's own name. That used to be a
330
+ documented follow-up `rename pane` a launcher had to remember, so a caller
331
+ that did not know the convention left the managed Pane addressed by a raw
332
+ UID. It is not a new automatic-name rule -- it goes through the same explicit
333
+ name path an operator's `--name` does, so a Pane with no Agent still gets its
334
+ exact full UID. Length is the only fallback: an Agent name may be the full 128
335
+ bytes, and `<agent-name>-pane` would then be 133 and invalid, so the Pane
336
+ falls back to automatic naming rather than refusing a create that works today.
337
+ A *collision* on the derived name never falls back; it is the same typed exit
338
+ 2 with zero writes an explicit `--name` collision produces, because the whole
339
+ metadata phase runs before the first tmux or provider call.
340
+ - The derivation runs once, at create, and is deliberately not an invariant.
341
+ `rename agent` does not follow the Pane and `rename pane` is neither forbidden
342
+ nor restricted, so the Pane name keeps exactly one owner and `rename agent`
343
+ keeps its `cardinality=exact-one` effect tuple. A launcher that still issues
344
+ the old follow-up `rename pane <agent-name>-pane` gets a successful no-op,
345
+ because a reservation slot the same uid already holds is not a conflict.
346
+ - Schema v4 stores no `metadata.displayName`, `status.displayTitle`, or renamed
347
+ presentation replacement. Human context is projected for one invocation from
348
+ the Project root, topic/provider/command, and exact live tmux title. That
349
+ context may duplicate and is never a selector, reservation, ownerRef, or
350
+ durable identity input. `metadata.labels` remains key/value classification;
351
+ `metadata.annotations` remains non-identifying metadata such as an AI topic.
339
352
 
340
353
  Root lifecycle:
341
354
 
@@ -656,10 +669,9 @@ Registry file and schema:
656
669
  bounded retry and stale-lock breaking, matching the notify queue and
657
670
  recent-windows stores. Explicit Registry repair uses its own recovery lock;
658
671
  see the recovery boundary below.
659
- - The envelope carries `schemaVersion: 3`. Version 1 is the first Registry
660
- envelope projmux wrote; this build migrates v1 through v2 and then performs
661
- the lossless v2 → v3 envelope advance that admits Project-stop
662
- `interrupted/control-action` evidence.
672
+ - The envelope carries `schemaVersion: 4`. Version 1 is the first Registry
673
+ envelope projmux wrote; this build migrates v1 through v2 and v3 before the
674
+ v3 → v4 root-scoped naming and presentation-field cutover.
663
675
  - Everything else fails closed: the file is refused as unreadable and **no
664
676
  write happens at all** — no rewrite, no backup, no staged temp file. This
665
677
  covers a **newer** schemaVersion (which would destroy state a newer build
@@ -673,9 +685,9 @@ Registry file and schema:
673
685
  registry yet" case **only before the first successful write**; see the durable
674
686
  envelope below. Only a file with actual content and no usable `schemaVersion`
675
687
  is refused as unknown.
676
- - A normal locked `Load` of v1 or v2 runs the production migration chain,
688
+ - A normal locked `Load` of v1, v2, or v3 runs the production migration chain,
677
689
  validates the repaired graph, writes the versioned backup, and publishes the
678
- v3 bytes through the existing temp-file atomic replace. A failed migration
690
+ v4 bytes through the existing temp-file atomic replace. A failed migration
679
691
  leaves the source bytes unchanged. Every successful first migrator (`Load`,
680
692
  `Update`, `UpdateConvergent`, or explicit `Migrate`) also atomically publishes
681
693
  a 0600 `<exact-backup>.migration-report.json` beside the versioned backup
@@ -685,11 +697,21 @@ Registry file and schema:
685
697
  A failed migration removes any staged/published report before returning while
686
698
  leaving the source bytes unchanged. `LoadWithMigrationResult` and `Migrate`
687
699
  additionally return both exact paths from the same locked transaction. A
688
- second pass sees v3 and writes neither Registry, backup, nor report bytes. An
689
- existing invalid v3 document is validated and refused byte-identically even
700
+ second pass sees v4 and writes neither Registry, backup, nor report bytes. An
701
+ existing invalid v4 document is validated and refused byte-identically even
690
702
  when explicit `Migrate` has no version step to run.
691
703
  Explicit read-only inspection migrates only its returned in-memory view and
692
704
  never publishes it.
705
+ - The v3 → v4 step validates the UID/direct-owner graph, rebuilds reservations
706
+ from that graph, and changes descendant scope to the owning Project or
707
+ ControlSession. Every member of a root-wide same-kind duplicate group moves
708
+ to its exact UID name. If one of those destinations is held by a resource
709
+ outside the set, the holder is added until destination closure reaches a
710
+ fixed point; every unique name outside that closure is preserved. The sorted
711
+ name mapping and content-free receipts for removed `metadata.displayName` and
712
+ `status.displayTitle` keys are recorded beside the exact-byte v3 backup. A
713
+ present empty key records zero bytes and SHA-256 of empty content without
714
+ being counted as information loss.
693
715
  - The v1 repair is deterministic over Registry order apart from injected opaque
694
716
  uid generation. It preserves every existing uid, ownerRef, reserved name, and
695
717
  Agent conversation/session pointer. A valid Project anchor is never
@@ -717,8 +739,8 @@ Registry file and schema:
717
739
  Mixed legacy/final authority is refused; the final writer emits no
718
740
  `primaryPaneRef`; and a second final-v2 pass writes zero bytes.
719
741
  - **Field spelling:** the registry file intentionally uses the resource-model
720
- camelCase spelling (`apiVersion`, `schemaVersion`, `metadata`, `displayName`,
721
- `ownerRef`, `anchorPaneRef`, `defaultShellPaneRef`, `spec`, `status`) rather than the snake_case
742
+ camelCase spelling (`apiVersion`, `schemaVersion`, `metadata`, `ownerRef`,
743
+ `anchorPaneRef`, `defaultShellPaneRef`, `spec`, `status`) rather than the snake_case
722
744
  used by the older projmux on-disk JSON. The two spellings coexist on purpose:
723
745
  existing snake_case files are **not** retro-changed, and the resource registry
724
746
  follows the resource-model contract.
@@ -831,11 +853,14 @@ Registry recovery boundary (`projmux reconcile registry`):
831
853
  `ownerRef`, or a broken name reservation are all refused. Restoring an
832
854
  unverified source would replace a known-damaged registry with an
833
855
  unknown-damaged one, and the second state is worse because it looks healthy.
834
- - **Byte-semantic restore.** The verified bytes are published verbatim rather than
835
- re-encoded, so uids, owner relations, and name reservations are preserved
836
- exactly, a repeat restore is a byte comparison instead of a normalization
837
- argument, and an older-but-known schema stays readable through the existing safe
838
- read and migrates on the next semantic write.
856
+ - **Byte-semantic current restore and canonical v3 import.** A verified v4
857
+ source is published verbatim. A verified v3 source is never made live in its
858
+ legacy form: its exact bytes are first written to a versioned backup, a
859
+ checksum-bearing content-free migration report is published beside that
860
+ backup, and the same deterministic v3 → v4 migration used by normal Registry
861
+ loading supplies the staged live bytes. Repeat restore compares the live
862
+ Registry with that canonical publish checksum, so it writes no new Registry,
863
+ backup, or report bytes.
839
864
  - **The bytes being replaced are kept.** A restore copies the current registry to
840
865
  `recovery/replaced-<stamp>-<seq>.json` before replacing it, and unlike the
841
866
  write-side copy it keeps content that does **not** verify — that damaged
@@ -974,15 +999,15 @@ tmux transport mirror:
974
999
  - `rename pane` changes `Pane.metadata.name` and its `@projmux_pane_label`
975
1000
  mirror only. It never writes the raw tmux `pane_title`.
976
1001
  - `rename window` is the explicit stable-identity path: it changes only
977
- `Window.metadata.name`, its scoped name reservation, and the exact live
978
- `@projmux_window_name` transport mirror. It does not
979
- change `metadata.displayName` or tmux `window_name`.
1002
+ `Window.metadata.name`, its root-scoped same-kind name reservation, and the
1003
+ exact live `@projmux_window_name` transport mirror. It does not change tmux
1004
+ `window_name`.
980
1005
  - `rename project` likewise writes only `Project.metadata.name` and the exact
981
1006
  live session's `@projmux_project_name`; it never renames the tmux session.
982
1007
  `rebind project` preserves the Project uid and session name while updating
983
1008
  `spec.root` and the exact live session's `@projmux_project_path`. Neither
984
1009
  operation moves files.
985
- - `rename agent` changes only the Window-scoped Agent `metadata.name` and its
1010
+ - `rename agent` changes only the root-scoped Agent `metadata.name` and its
986
1011
  reservation. Agent topic annotations, provider, lifecycle status, and the
987
1012
  managed Pane's name and raw title are independent and receive no tmux write.
988
1013
  - Rename/rebind commits the authoritative Registry transaction before its
@@ -997,24 +1022,23 @@ tmux transport mirror:
997
1022
  UID claim is nonzero and reports that Registry state committed plus
998
1023
  `projmux reconcile resources` as the retry boundary.
999
1024
  - The configured `window.rename` action (`Ctrl-M` by default) is the runtime
1000
- display path and invokes tmux `rename-window` directly. Reconciliation
1001
- observes that `window_name` back into `metadata.displayName` without changing
1002
- stable identity.
1025
+ display path and invokes tmux `rename-window` directly. Reads may observe that
1026
+ exact live `window_name` as invocation-scoped context; reconciliation never
1027
+ persists it as a Registry address or presentation field.
1003
1028
  - Registry-managed Windows are set to `automatic-rename off` so a focused-Pane
1004
1029
  change cannot overwrite the Window name. The **global** `automatic-rename on`
1005
1030
  plus visible-pane-label `automatic-rename-format` default in the generated
1006
1031
  app config is unchanged, so unmanaged windows keep their existing behavior.
1007
- - Legacy import gives every newly discovered Window the literal `window` base,
1008
- uniquified in Project scope (`window`, `window-1`, ...), and projects its
1009
- current `window_name` into duplicate-allowed `metadata.displayName` before
1010
- switching it to `automatic-rename off`. Re-observing an existing Window may
1011
- refresh only that display field; it never changes the uid, stable name,
1012
- ownerRef, or name reservation. An existing `@projmux_pane_label` remains the
1013
- migration seed and transport mirror for the Pane **name**; Pane naming is a
1014
- separate contract and is unchanged here.
1015
- - `Pane.metadata.name` is the primary pane display source. The derived
1016
- `Pane.status.displayTitle` (Agent topic → known shell → raw pane title) is
1017
- secondary and is never a selector, an identity, or a Window name source.
1032
+ - Legacy import gives every newly discovered Window, Pane, and Agent its exact
1033
+ minted UID as the automatic Registry name and switches managed Windows to
1034
+ `automatic-rename off`. Observed `window_name`, Pane label/title, command,
1035
+ shell, provider, and topic are never persisted as an address or presentation
1036
+ field. Re-observing an existing resource preserves its UID, stable name,
1037
+ ownerRef, and reservation. `@projmux_window_name` and
1038
+ `@projmux_pane_label` remain live mirrors of the durable name.
1039
+ - `Pane.metadata.name` is the stable Pane address. Human-readable Pane context
1040
+ is derived per invocation from the owner Agent topic/provider, command, and
1041
+ exact live title; it is never a selector, identity, or Window name source.
1018
1042
 
1019
1043
  Runtime observation and resource status:
1020
1044
 
@@ -1154,13 +1178,12 @@ Binding reapply and adoption:
1154
1178
  and neither does a **refused** pane — a refusal means a real registry Pane sits
1155
1179
  on the other side of the ambiguity, so minting beside it would leave two Panes
1156
1180
  describing one tmux pane.
1157
- - The registered Pane is named from `FallbackPaneNameBase` (`pane`, `pane-1`, …)
1158
- through the registry's own allocator, never from `pane_current_command`:
1159
- `metadata.name` is not derived from a runtime attribute, and the command
1160
- changes the moment the operator runs something else. The runtime reading goes
1161
- to `status.displayTitle` instead. The mint itself never creates an Agent: it
1162
- adds one Pane and stops. Linking that Pane to an Agent is the separate step
1163
- below, which runs on the Pane the walk just settled on.
1181
+ - The registered Pane uses its exact minted UID as `metadata.name`, never
1182
+ `pane_current_command`, the Pane label/title, or a numeric suffix. Those
1183
+ runtime values may contribute only to invocation-scoped context. The mint
1184
+ itself never creates an Agent: it adds one Pane and stops. Linking that Pane
1185
+ to an Agent is the separate step below, which runs on the Pane the walk just
1186
+ settled on.
1164
1187
  - **Nothing is ever re-identified.** Adoption changes no uid, merges no uid, and
1165
1188
  reassigns no uid; it only decides which registry object a live tmux object is
1166
1189
  the runtime of, and then writes that object's existing uid. Adopted objects
@@ -1673,7 +1696,8 @@ Explicit Registry topology materialization:
1673
1696
  desired Windows remain Agent-anchored and acquire a shell only through the
1674
1697
  ordinary materializer. Source snapshot bytes and unrelated roots are never
1675
1698
  rewritten, and a second projection is byte-stable.
1676
- - `Open fresh` replaces the exact same-root Project graph in one Registry
1699
+ - `Recreate Project` replaces the exact same-root Project graph, after an
1700
+ explicit confirmation, in one Registry
1677
1701
  commit. It always allocates a new Project UID plus one new canonical Window
1678
1702
  and direct shell UID, whether the old Project retained Windows or had zero.
1679
1703
  The preimage remains the durable recovery state when the replacement commit
@@ -1849,11 +1873,12 @@ Resource-first create:
1849
1873
  through the same `@projmux_window_uid` mirror and registry `ownerRef` chain
1850
1874
  the read verbs use.
1851
1875
  - **Window and anchor follow the whole scope, not the Project flag.** They are
1852
- derived only when the argv named no `--project`, `--window`, `--pane`, and no
1853
- `--selector` at all. That keeps a bare `create pane --placement right` -- the
1854
- generated keybinding body -- a split of the Window the operator is looking at,
1855
- instead of a fan-out over every Window of the Project, while one explicit
1856
- occurrence still fixes the whole target set. An explicit `--pane` or popup
1876
+ derived only when the argv named no `--project`, `--window`, `--pane`,
1877
+ `--selector`, `--all-windows`, and no `--primary-window` at all. That keeps a
1878
+ bare `create pane --placement right` -- the generated keybinding body -- a
1879
+ split of the Window the operator is looking at, rather than of the Project's
1880
+ primary Window, which is what the same route resolves once a `--project` scope
1881
+ is typed, while one explicit occurrence still fixes the whole target set. An explicit `--pane` or popup
1857
1882
  origin is the exact split anchor. Only a scope with no Pane consumes the
1858
1883
  target Window's role-agnostic `spec.anchorPaneRef`; a missing, stale, dead, or
1859
1884
  cross-Window ref refuses with no alternate-live-Pane inference.
@@ -1880,7 +1905,7 @@ Resource-first create:
1880
1905
  Selector and the implicit active target:
1881
1906
 
1882
1907
  - A selector value is either `uid:<uid>` or a `metadata.name`. There is no
1883
- bare-uid form; `displayName`, `spec.root`, and tmux `%N`/`@N`/`$N` handles are
1908
+ bare-uid form; ephemeral context, `spec.root`, and tmux `%N`/`@N`/`$N` handles are
1884
1909
  structurally unmatchable. `--project` is at-most-once and fixes the scope,
1885
1910
  `--window`/`--pane` repeat and union, `--selector key=value` repeats and ANDs,
1886
1911
  and how many targets a `<verb, kind>` pair accepts comes from one declared
Binary file
@@ -0,0 +1,254 @@
1
+ # Claude coordination endpoint registration
2
+
3
+ Phase 1 binds a Claude-created endpoint to a managed Agent activation. It does
4
+ not implement message delivery, a provider message schema, a message/wait parser,
5
+ or a new public provider command. `agent capabilities` reports the exact Agent's
6
+ `runtimeEligibility.coordination` readiness and evidence. Static provider queries
7
+ remain static, and existing action cells stay unchanged.
8
+
9
+ `AgentRouteRef` shares stable Agent UID, Pane UID, and activation generation.
10
+ Its sealed provider authority union consumes the existing Codex thread and
11
+ `CodexAuthorityRef` composite unchanged. Claude authority instead consists of the
12
+ actual public SessionStart session ID, kernel process birth identity, a fresh
13
+ registration generation, and the kernel identity of the helper retaining its
14
+ registration lease. No Claude connection/binding epochs are synthesized.
15
+
16
+ Managed Agent/Pane names and Claude session titles are not route authority.
17
+ Renaming an unchanged UID/activation preserves its registration. Phase 1 does
18
+ not discover unmanaged provider names or observe live Claude `/rename` events.
19
+ Competing session/process/helper claims for an exact activation are refused;
20
+ the exact child's next SessionStart atomically claims a new registration
21
+ generation before launching its helper. Delayed admission and cleanup from an
22
+ older generation cannot overwrite or clear that newer registration.
23
+
24
+ The activation gate records the actual child PID and kernel birth identity
25
+ before replacing itself with Claude. The separate managed SessionStart hook
26
+ uses `exec`, making the registered provider its direct parent. The helper
27
+ verifies the complete helper → hook → provider process chain while the hook
28
+ waits for a bounded startup acknowledgement. A nested unmanaged Claude cannot
29
+ register its own endpoint through inherited activation environment variables.
30
+ The creator-selected Registry path travels only in private Claude activation
31
+ context, independent of a tmux server's older XDG environment.
32
+
33
+ The hook obtains the socket and credential solely from Claude's documented
34
+ `CLAUDE_CODE_MESSAGING_SOCKET` and `CLAUDE_CODE_MESSAGING_TOKEN` environment.
35
+ These values pass to the helper over an anonymous pipe and remain in process
36
+ memory. They are absent from Registry, helper argv, helper environment, public
37
+ capabilities, diagnostic messages, and the content-free cleanup receipt.
38
+ Projmux does not predict, issue, read a vendor registration file for, connect
39
+ to, or write to the Claude inbox.
40
+
41
+ Readiness validates exact Registry ownership/generation, provider and helper
42
+ kernel birth identity, socket type/owner/mode and unchanged inode. The read-only
43
+ capability query probes only a short, private Projmux readiness socket and
44
+ authenticates its kernel peer PID and birth. Linux birth identity includes boot
45
+ ID and start ticks; Darwin uses kernel process start time. The private socket
46
+ path fits both supported Unix socket limits and does not depend on TMPDIR.
47
+
48
+ The helper invalidates on provider exit, socket replacement, or loss of exact
49
+ Registry authority. The supervisor independently detects helper death while
50
+ the provider remains alive, clears only that exact lease, and removes orphaned
51
+ helper files. Provider exit keeps the existing lock-free termination journal
52
+ boundary; it never waits for the cleanup watcher's Registry transaction.
53
+ Provider sockets are never removed by Projmux. Generation replacement and
54
+ termination convergence discard old registration observations.
55
+
56
+ The required deterministic process integration is
57
+ `test/integration/claude-endpoint-binding.sh`. It is included in
58
+ `make test-integration`. The opt-in `TestClaudeEndpointInstalledSourceGate`
59
+ requires `PMX_TEST_CLAUDE_ENDPOINT_BIN`, `PMX_TEST_REAL_CLAUDE_BIN`, and a prepared
60
+ disposable authenticated `PMX_TEST_REAL_CLAUDE_CONFIG_DIR`. The harness creates
61
+ its own provider config with an authentication symlink; it never reads or copies
62
+ the caller's credentials. Its one-shot uses
63
+ strict empty MCP, empty tools, empty setting sources, and no session persistence;
64
+ it checks public init tools/MCP/plugins and tool-use are all zero. Raw public
65
+ stream and messaging credentials stay in memory; only allowlisted receipts
66
+ are reported. The token scan covers all owned files, including provider source
67
+ state. Upstream may store its own public locator; the test observes only a
68
+ boolean and removes the entire owned provider config before reporting cleanup.
69
+ Projmux Registry, helper files, diagnostics, and evidence contain neither value.
70
+ Inbound cross-session messaging is explicitly refused during the one-shot.
71
+ This is historical Phase 1 registration-source evidence only. It does not
72
+ establish or relax Phase 4 qualification: safe mode disables hooks and cannot
73
+ substitute for the separately owned, long-lived, hook-enabled current-version
74
+ public-init and marker gate.
75
+
76
+ The live canary accepts only paired, zero-output lifecycle events for its two
77
+ owned SessionStart callbacks before init, correlated by exact session, hook ID,
78
+ name, and event. It strips the empty output fields before storing evidence.
79
+ Unobserved hook names, Setup, plugin installation, unknown fields/events,
80
+ nonzero output, and incomplete pairs fail closed before any peer push. This
81
+ startup ordering follows the [public stream contract](https://code.claude.com/docs/en/headless#read-session-metadata)
82
+ and [hook lifecycle schemas](https://code.claude.com/docs/en/agent-sdk/typescript#sdkhookstartedmessage).
83
+ The observed 2.1.263 public init also carries `capabilities` (protocol feature
84
+ identifiers), `fast_mode_disabled_reason` (`sdk_opt_in_required`), and boolean
85
+ `analytics_disabled` / `product_feedback_disabled` metadata. These fields are
86
+ validated by type and known shape; none grants tool, plugin, or messaging
87
+ authority. The owned wrapper sets the documented
88
+ [`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`](https://code.claude.com/docs/en/data-usage)
89
+ opt-out. A feedback metadata boolean is not a record of external traffic.
90
+ The observed `system/thinking_tokens` event carries only numeric progress
91
+ estimates. The collector accepts its exact public shape after init for the same
92
+ session, validates finite nonnegative numbers, and gives it no reply or tool
93
+ authority. It does not carry or collect thinking text.
94
+ Public assistant `request_id` and timestamp are validated metadata, never reply
95
+ selectors. The observed `context_management`, `diagnostics`, and `stop_details`
96
+ fields are accepted only as null. A public Messages API thinking block is
97
+ validated in memory, then replaced by an omitted-content marker before disk;
98
+ neither reasoning text nor signature is retained or sent to the provider. Only
99
+ text blocks can provide semantic reply evidence; tool and unknown blocks fail
100
+ closed. See the public [Messages schema](https://platform.claude.com/docs/en/api/http/beta/messages/create)
101
+ and [ThinkingBlock schema](https://platform.claude.com/docs/en/api/typescript/messages).
102
+
103
+ ## Phase 4 push ingress and heterogeneous dialogue
104
+
105
+ The registration helper owns the existing private `coord-*` Unix socket for
106
+ the same lease. Its address is derived from the exact `AgentRouteRef`; names,
107
+ tmux `%N`, the provider socket, and the provider token do not enter that address
108
+ or its protocol. Every operation revalidates Agent UID, Pane UID, activation
109
+ generation, provider/helper process births, registration generation, and route
110
+ incarnation. Normal exit and cleanup unlink only the exact owned coord socket
111
+ inode. Projmux never creates a replacement provider listener or relay.
112
+
113
+ Claude ingress is immediate push. There is no receiver waiter, pending ingress
114
+ queue, `asyncRewake`, `begin-handoff`, or `no-waiter` state. The helper retains
115
+ the opaque `CLAUDE_CODE_MESSAGING_SOCKET` and token only from the original
116
+ anonymous bootstrap pipe, removes both keys from its environment, validates the
117
+ unchanged 0600 provider socket and exact provider peer process, then performs
118
+ one write containing the documented auth line followed by the single frozen
119
+ current-version user frame:
120
+
121
+ ```json
122
+ {"type":"user","message":{"role":"user","content":"..."}}
123
+ ```
124
+
125
+ No control, status, reply, or inferred vendor frame is implemented. A known
126
+ failure before any byte is a non-ambiguous `provider-write-zero`; partial bytes,
127
+ a write error after bytes, or a lost helper response is ambiguous and has
128
+ `autoResend=false`. A complete write plus helper return is only a transport
129
+ handoff, not proof that Claude parsed, displayed, processed, or answered it.
130
+
131
+ The frozen frame is closed to Claude Code `2.1.263`. A different provider
132
+ version, helper replacement, provider restart, registration replacement, or
133
+ activation restart inherits no qualification. An ordinary send cannot qualify
134
+ the endpoint. The dedicated opt-in command requires a private 0600 sanitized
135
+ collector record bound to the same public init session/process/Pane/generation
136
+ and helper route, exact version, empty tools/MCP/plugins, zero pre-marker tool
137
+ use/stderr, frozen stream, and inbound policy `accept`:
138
+
139
+ ```sh
140
+ projmux agent message qualify uid:<claude-agent> \
141
+ --evidence /absolute/owned/private-init.json \
142
+ --confirm-isolated-provider-push -o json
143
+ ```
144
+
145
+ The helper pushes a unique non-secret qualification marker. Only the exact
146
+ ordinary official `Stop.last_assistant_message` at the unchanged safe-boundary
147
+ epoch opens helper-memory eligibility. A missing/mismatched/recursive Stop,
148
+ `UserPromptSubmit`, timeout, target exit, or post-write uncertainty closes the
149
+ attempt as ambiguous/no-auto-resend. Late Stop cannot reopen it; retry requires
150
+ a new explicit command. A version string supplied by the caller is never proof.
151
+
152
+ After qualification, `agent message send` persists the exact immutable broker
153
+ handoff before any provider byte. A missing, mismatched, or unwritable durable
154
+ record therefore writes zero. The pushed content is a structured
155
+ `projmux-coordination` object with `untrusted-coordination-only` authority and
156
+ the exact source/target routes, `messageRef`, `conversationRef`, `replyTo`, and
157
+ payload. It cannot start or steer a user turn, answer an approval, interrupt a
158
+ turn, execute a tool, call a connector, or write Codex app-server/model history.
159
+
160
+ Reply egress uses only documented official `Stop.last_assistant_message` plus
161
+ one delivered Projmux-owned pending record at the same boundary. Push ingress
162
+ correlates only an ordinary Stop (`stop_hook_active=false`); recursive Stop,
163
+ `UserPromptSubmit`, multiple candidates, or authority drift fails closed.
164
+ Assistant wording is never a selector. The reply reverses the routes, preserves
165
+ `conversationRef`, sets `replyTo`, and completes only when the original exact
166
+ Codex Agent self-claims it.
167
+
168
+ | Receiver state or transition | Product result |
169
+ | --- | --- |
170
+ | Before readiness or qualification | Refused/failed terminal, held count zero, provider writes zero |
171
+ | Ready and idle | Immediate one-frame push; model visibility is separate evidence |
172
+ | Active tool/turn | Push never interrupts; next official human boundary makes reply correlation ambiguous |
173
+ | Provider exit or activation restart | Old generation writes and claims zero |
174
+ | Same-generation registration/helper replacement | Old incarnation writes zero; fresh exact-version qualification required |
175
+ | Provider version replacement | Old qualification is cleared; unqualified writes zero |
176
+ | Codex endpoint replacement | Old incarnation self-claim zero; exact new incarnation may claim |
177
+
178
+ Message state is receipt-only. It performs no Registry Agent-interaction or
179
+ tmux badge write, so it cannot overwrite `in_progress`, approval-required, or
180
+ their badges. Public terminal states remain terminal-once.
181
+
182
+ Explicit integration installs the SessionStart registration hook, one short
183
+ synchronous Stop reply hook, and one short synchronous UserPromptSubmit boundary
184
+ hook. It installs no long-lived ingress waiter and does not use `asyncRewake`.
185
+ Capability reads never modify settings. Preview, enable, and remove only the
186
+ owned hook entries:
187
+
188
+ ```sh
189
+ projmux agent integrate claude --dry-run
190
+ projmux agent integrate claude
191
+ projmux agent integrate claude --remove
192
+ ```
193
+
194
+ For a pre-install Running Claude whose activation has no registration, install
195
+ the hooks, let that process exit normally, then run
196
+ `projmux agent resume uid:<same-agent-uid>`. Resume preserves the same Agent UID
197
+ and creates no replacement Agent. Delivery remains zero until the resumed
198
+ process has a current registration and fresh exact-version qualification.
199
+ `agent capabilities uid:<agent> -o json` reports source registration readiness
200
+ separately from target `runtimeEligibility.coordination` and includes the exact
201
+ recovery action and non-secret route incarnation.
202
+
203
+ The only selectorless required E2E is offline scenario `L20`. It uses one
204
+ synthetic auth+frozen-frame fixture, official-hook-shaped Stop input, semantic
205
+ barriers, exact structured receipts, and no model or installed provider. The
206
+ real-provider observation is opt-in through
207
+ [`heterogeneous-dialogue-canary.md`](heterogeneous-dialogue-canary.md) and
208
+ `scripts/agent-dialogue-live-canary.sh`; each version-stress row must qualify
209
+ independently.
210
+
211
+ Provider sources: [SessionStart and Stop hooks](https://code.claude.com/docs/en/hooks)
212
+ and [cross-session messaging](https://code.claude.com/docs/en/cross-session-messaging).
213
+
214
+ Once a delivered message has unresolved reply ambiguity (an overlapping human
215
+ turn, multiple pending messages, expiry, or an uncertain write/reply outcome),
216
+ a later idle Stop cannot restore automatic reply correlation. The helper keeps
217
+ push ingress available but refuses automatic replies for that incarnation.
218
+ Use the documented public recovery on the same Agent UID and qualify its new
219
+ activation before resuming automatic dialogue. Source and target routes are
220
+ revalidated after durable handoff and immediately before the provider write.
221
+
222
+ The final source check also asks the existing Codex broker to verify the exact
223
+ runtime, connection and binding lease. This read-only IPC observation binds
224
+ nothing and sends no provider request. An older broker that does not support
225
+ this observation refuses coordination; it is never restarted implicitly.
226
+ Helper store lock contention fails immediately. Concurrent official hooks
227
+ invalidate reply correlation without waiting for another hook to finish.
228
+
229
+ The live harness begins with the benign `Reply READY.` control. The later broker
230
+ payload contains its own exact acknowledgement request; the initial user turn
231
+ does not authorize hypothetical future messages. Qualification keeps the same
232
+ frozen user frame and its existing one-shot marker. Observed rate/result metadata
233
+ is validated by closed shape and reduced before persistence. A refusal, API
234
+ error, queued user turn, permission denial, or nonzero subagent activity closes
235
+ the pre-inbound gate. Neither rate status nor timing/cost metadata is authority.
236
+
237
+ Usage is validated and discarded before persistence. Server tool counters must
238
+ be zero and tool/subagent iterations empty. After the semantic reply, the harness
239
+ requires three successful results in the same session, the public assistant
240
+ reply marker, and empty provider/collector stderr through owned cleanup.
241
+
242
+ Public model IDs and usage labels are bounded strings, including provider aliases
243
+ and context suffixes. They are discarded without imposing identifier syntax or
244
+ using pricing metadata as a permission or correlation source.
245
+
246
+ Failed live-canary cleanup captures exact Linux process births before unregister
247
+ and tmux teardown, including pane supervisors, their descendants, and the
248
+ detached registration helper. Supervisors write termination/operation receipts
249
+ after their provider exits. Both success and failure cleanup wait for captured
250
+ pidfds to report exit before removing the owned root. An unproven or stubborn
251
+ writer leaves the root for diagnosis and fails cleanup; no quiet-file interval
252
+ or process-name kill substitutes for exit proof. The owned credential copy is
253
+ removed even when cleanup cannot complete. The actual reply-origin qualification
254
+ blocker is unchanged by this cleanup guarantee.