agent-embassy 3.0.0 → 4.0.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/CONTRIBUTING.md +92 -200
  3. package/README.md +159 -138
  4. package/SECURITY.md +191 -335
  5. package/dist/src/gateway/broker-check.d.ts +26 -0
  6. package/dist/src/gateway/broker-check.js +89 -0
  7. package/dist/src/gateway/broker-check.js.map +1 -0
  8. package/dist/src/gateway/broker-control.d.ts +72 -0
  9. package/dist/src/gateway/broker-control.js +164 -0
  10. package/dist/src/gateway/broker-control.js.map +1 -0
  11. package/dist/src/gateway/broker.d.ts +117 -0
  12. package/dist/src/gateway/broker.js +186 -0
  13. package/dist/src/gateway/broker.js.map +1 -0
  14. package/dist/src/gateway/claude-peer.d.ts +1 -124
  15. package/dist/src/gateway/claude-peer.js +62 -1053
  16. package/dist/src/gateway/claude-peer.js.map +1 -1
  17. package/dist/src/gateway/codex-stateless-transport.d.ts +0 -12
  18. package/dist/src/gateway/codex-stateless-transport.js +8 -142
  19. package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
  20. package/dist/src/gateway/config.d.ts +2 -6
  21. package/dist/src/gateway/config.js +17 -27
  22. package/dist/src/gateway/config.js.map +1 -1
  23. package/dist/src/gateway/coordinator.d.ts +57 -0
  24. package/dist/src/gateway/coordinator.js +217 -0
  25. package/dist/src/gateway/coordinator.js.map +1 -0
  26. package/dist/src/gateway/core-cli.d.ts +15 -0
  27. package/dist/src/gateway/core-cli.js +282 -0
  28. package/dist/src/gateway/core-cli.js.map +1 -0
  29. package/dist/src/gateway/core-service-command.d.ts +16 -0
  30. package/dist/src/gateway/core-service-command.js +71 -0
  31. package/dist/src/gateway/core-service-command.js.map +1 -0
  32. package/dist/src/gateway/endpoint-directory.d.ts +33 -0
  33. package/dist/src/gateway/endpoint-directory.js +223 -0
  34. package/dist/src/gateway/endpoint-directory.js.map +1 -0
  35. package/dist/src/gateway/federation.d.ts +89 -0
  36. package/dist/src/gateway/federation.js +632 -0
  37. package/dist/src/gateway/federation.js.map +1 -0
  38. package/dist/src/gateway/ledger-codec.d.ts +3 -0
  39. package/dist/src/gateway/ledger-codec.js +159 -0
  40. package/dist/src/gateway/ledger-codec.js.map +1 -0
  41. package/dist/src/gateway/ledger.d.ts +138 -0
  42. package/dist/src/gateway/ledger.js +319 -0
  43. package/dist/src/gateway/ledger.js.map +1 -0
  44. package/dist/src/gateway/local-control.d.ts +23 -0
  45. package/dist/src/gateway/local-control.js +489 -0
  46. package/dist/src/gateway/local-control.js.map +1 -0
  47. package/dist/src/gateway/native-destinations.d.ts +34 -0
  48. package/dist/src/gateway/native-destinations.js +272 -0
  49. package/dist/src/gateway/native-destinations.js.map +1 -0
  50. package/dist/src/gateway/owned-state.d.ts +57 -0
  51. package/dist/src/gateway/owned-state.js +398 -0
  52. package/dist/src/gateway/owned-state.js.map +1 -0
  53. package/dist/src/gateway/provenance-envelope.d.ts +3 -3
  54. package/dist/src/gateway/provenance-envelope.js +3 -23
  55. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  56. package/dist/src/gateway/runtime.d.ts +41 -0
  57. package/dist/src/gateway/runtime.js +175 -0
  58. package/dist/src/gateway/runtime.js.map +1 -0
  59. package/docs/CONFIGURATION.md +178 -266
  60. package/docs/DELIVERY.md +133 -82
  61. package/docs/GATEWAY-ARCHITECTURE.md +276 -943
  62. package/package.json +5 -5
  63. package/skills/embassy-peer/SKILL.md +38 -194
  64. package/skills/embassy-peer/agents/openai.yaml +2 -2
  65. package/dist/src/gateway/claude-helper-client.d.ts +0 -2
  66. package/dist/src/gateway/claude-helper-client.js +0 -2
  67. package/dist/src/gateway/claude-helper-client.js.map +0 -1
  68. package/dist/src/gateway/claude-helper-protocol.d.ts +0 -117
  69. package/dist/src/gateway/claude-helper-protocol.js +0 -122
  70. package/dist/src/gateway/claude-helper-protocol.js.map +0 -1
  71. package/dist/src/gateway/claude-helper-supervisor.d.ts +0 -87
  72. package/dist/src/gateway/claude-helper-supervisor.js +0 -347
  73. package/dist/src/gateway/claude-helper-supervisor.js.map +0 -1
  74. package/dist/src/gateway/claude-helper.d.ts +0 -1
  75. package/dist/src/gateway/claude-helper.js +0 -252
  76. package/dist/src/gateway/claude-helper.js.map +0 -1
  77. package/dist/src/gateway/cli.d.ts +0 -57
  78. package/dist/src/gateway/cli.js +0 -1503
  79. package/dist/src/gateway/cli.js.map +0 -1
  80. package/dist/src/gateway/codex-socket-holder.d.ts +0 -26
  81. package/dist/src/gateway/codex-socket-holder.js +0 -76
  82. package/dist/src/gateway/codex-socket-holder.js.map +0 -1
  83. package/dist/src/gateway/control.d.ts +0 -261
  84. package/dist/src/gateway/control.js +0 -903
  85. package/dist/src/gateway/control.js.map +0 -1
  86. package/dist/src/gateway/peer-client.d.ts +0 -58
  87. package/dist/src/gateway/peer-client.js +0 -217
  88. package/dist/src/gateway/peer-client.js.map +0 -1
  89. package/dist/src/gateway/peer-mailbox.d.ts +0 -78
  90. package/dist/src/gateway/peer-mailbox.js +0 -168
  91. package/dist/src/gateway/peer-mailbox.js.map +0 -1
  92. package/dist/src/gateway/peer-protocol.d.ts +0 -105
  93. package/dist/src/gateway/peer-protocol.js +0 -70
  94. package/dist/src/gateway/peer-protocol.js.map +0 -1
  95. package/dist/src/gateway/peer-stdio.d.ts +0 -23
  96. package/dist/src/gateway/peer-stdio.js +0 -100
  97. package/dist/src/gateway/peer-stdio.js.map +0 -1
  98. package/dist/src/gateway/providers.d.ts +0 -163
  99. package/dist/src/gateway/providers.js +0 -972
  100. package/dist/src/gateway/providers.js.map +0 -1
  101. package/dist/src/gateway/server.d.ts +0 -47
  102. package/dist/src/gateway/server.js +0 -219
  103. package/dist/src/gateway/server.js.map +0 -1
  104. package/dist/src/gateway/service.d.ts +0 -373
  105. package/dist/src/gateway/service.js +0 -2274
  106. package/dist/src/gateway/service.js.map +0 -1
  107. package/dist/src/gateway/status-view.d.ts +0 -167
  108. package/dist/src/gateway/status-view.js +0 -488
  109. package/dist/src/gateway/status-view.js.map +0 -1
  110. package/dist/src/gateway/store.d.ts +0 -185
  111. package/dist/src/gateway/store.js +0 -2359
  112. package/dist/src/gateway/store.js.map +0 -1
  113. package/dist/src/gateway/types.d.ts +0 -528
  114. package/dist/src/gateway/types.js +0 -341
  115. package/dist/src/gateway/types.js.map +0 -1
@@ -1,960 +1,293 @@
1
- # Embassy Gateway Architecture
2
-
3
- Local bidirectional routing between Claude Code sessions, Codex CLI tasks,
4
- and universal shell peers, with optional federation of named routes between
5
- the operator's own machines over a fixed attach-only SSH transport. The
6
- published package supports macOS, the only platform exercised end to end.
7
-
8
- This document uses three evidence labels:
9
-
10
- - **Official**: documented by Anthropic or OpenAI.
11
- - **Implemented**: present in this tree and covered by deterministic tests.
12
- - **Observed**: established by a bounded, read-only local probe.
13
-
14
- ## Purpose and boundary
15
-
16
- The gateway lets already-running Claude Code sessions and explicitly
17
- registered native Codex tasks address one another by short aliases. The
18
- permission to message is the boundary the broker already sits inside: the same
19
- UID, the same host — or a host the operator listed in the private `nodes.json`
20
- — and an exact alias. There is no separate, revocable grant between two
21
- endpoints, because no such record could defend against software already
22
- running as that UID; what the broker guarantees instead is attribution, and a
23
- discovered Claude session's route installs on its first use rather than by a
24
- separate command. It provides
25
- a single private operational view across the two products without rebuilding
26
- either agent runtime.
27
-
28
- Provider versions are best-effort diagnostic metadata, never routing authority.
29
- The OS boundary plus the exact owned route and session identity authorizes an
30
- attempt; the current per-operation transport, strict wire, capability, and
31
- correlated operation facts decide its result. Unsafe OS evidence
32
- for Embassy-owned or executed artifacts and Embassy callback, control, or state
33
- paths refuses broker startup; unsafe UID or mode evidence on Claude's external
34
- sessions registry root quarantines only Claude. A Claude session record whose
35
- native peer protocol is not 1 is rejected in isolation and included in bounded
36
- rejection evidence. Missing optional providers and interface drift degrade only
37
- their own routes while the broker and other providers remain available.
38
-
39
- It is deliberately:
40
-
41
- - personal, local, same-OS-user software;
42
- - single-user and non-hosted;
43
- - an alias router and bounded message broker, not an agent runtime;
44
- - unable to create Codex sidebar task cards or Claude session UI.
45
-
46
- Embassy uses one private same-user Unix-domain control socket for its thin
47
- clients. `embassy serve` does not add a TCP listener, HTTP server, or public
48
- API.
49
-
50
- ### Why this uses the new feature, but is not skill-only
51
-
52
- The gateway does not use Claude Channels. Claude's official cross-session
53
- feature supplies genuine Claude-session discovery, inbound policy, and native
54
- message delivery. It does not define a third-party session kind or make Codex
55
- tasks appear in `ListAgents`, so it cannot by itself provide the symmetric
56
- Claude–Codex address book the user wants.
57
-
58
- The repo-shipped skill is the lightweight user/agent interface. A persistent
59
- local broker is still required to own the private control socket, retain
60
- transient reply correlation, publish native advertisements, and queue while a
61
- Codex task is busy. The skill does not wrap, replace, or recreate either
62
- provider.
63
-
64
- ## What is official and what is internal
65
-
66
- ### Claude Code
67
-
68
- **Official:** Claude Code documents cross-session messaging on macOS
69
- and Linux. Real Claude sessions can use `ListAgents` to find other real Claude
70
- sessions and `SendMessage` to contact them. A target can accept, hold, or
71
- refuse inbound cross-session messages through `crossSessionInbound`. Messages
72
- do not bypass the receiver's tool permissions or approval boundary.
73
-
74
- **Best-effort internal boundary:** the installed Claude Code build advertises
75
- live sessions through registry records and transports peer frames over
76
- per-session Unix-domain sockets using peer protocol 1. Those registry and wire
77
- shapes are not documented as a stable third-party integration API. The gateway
78
- therefore validates every consumed field, frame, socket, generation, and
79
- correlated result immediately before use. Unknown top-level registry fields are
80
- tolerated because Embassy never consumes them; malformed required fields and
81
- records whose peer protocol is not 1 remain isolated and counted. Version
82
- metadata describes what was observed but grants no runtime authority.
83
-
84
- For the lowest-impedance native path, the gateway publishes one process-owned
85
- registry record whose name is visibly prefixed `codex-` and which carries the
86
- supported explicit versioned Embassy-advertisement marker. The listener remains
87
- gateway-owned and does not claim to be a Claude model session; the marker, not
88
- the name prefix alone, distinguishes Embassy's advertisement. The record uses
89
- the validated native peer shape so Claude's own `ListAgents` and
90
- `SendMessage` tools work unchanged.
91
-
92
- Consequences:
93
-
94
- - Native Claude `ListAgents` discovers real Claude sessions plus the one
95
- explicitly marked `codex-*` gateway peer.
96
- - The gateway discovers compatible real Claude sessions as transient
97
- candidates, but publishes only sanitized aliases and state. A send from a
98
- registered Codex task addresses a session by its current name or its UUID,
99
- and the broker installs that session's logical route on this first use.
100
- Per-message consent stays native: delivery lands in the
101
- Claude session's own `crossSessionInbound` policy and approval flow.
102
- - Codex aliases are discovered through the gateway CLI/skill, not through
103
- `ListAgents`.
104
- - A gateway-owned anonymous callback UDS can receive a correlated reply. It
105
- does not need, and must not create, a Claude registry record.
106
-
107
- ### Codex
108
-
109
- **Official:** Codex App Server is the JSON-RPC interface used by rich Codex
110
- clients. Its Unix-socket transport is WebSocket over a standard HTTP Upgrade.
111
- The documented protocol includes loaded-thread discovery, thread resume,
112
- turn start, turn steer, turn interrupt, and notifications. Embassy exposes
113
- `turn/steer` only behind the exact Claude-to-Codex `STEER:` contract described
114
- below; there is no generic RPC surface.
1
+ # Gateway architecture
2
+
3
+ ## Product contract
4
+
5
+ Embassy connects live Claude Code sessions and registered Codex CLI tasks by
6
+ name, locally or across directly configured SSH gateways. All four provider
7
+ pairs are supported. Sending is one `embassy send` command; receiving wakes the
8
+ target through its native interface. A receipt proves delivery machinery, not
9
+ model comprehension.
10
+
11
+ The design optimizes for this steady state and deliberately excludes native
12
+ Claude `SendMessage` advertisement helpers, shell-peer mailboxes, automatic
13
+ Codex output forwarding, persistent remote mirrors, general activity streams,
14
+ and migration compatibility.
115
15
 
116
16
  ## Topology
117
17
 
118
18
  ```text
119
- real Claude sessions (this Mac)
120
- │ genuine session sockets
121
-
122
- ├──────────────┐
123
- │ │ callback replies
124
- ▼ ▼
125
- ┌──────────────────────── local singleton gateway ───────────────────────┐
126
- private control UDS │ retained bodies │ metadata state │ public snapshot │
127
- └──────────┬──────────┴──────────────────┴────────────────┴─────────────────┘
128
-
129
- ├─ local Codex App Server ─ registered native local tasks
130
-
131
- └─ optional federation ─ ssh <node> embassy peer-stdio ─ peer broker
132
-
133
- Claude-side skill/CLI ─ private control UDS ─ gateway
134
- Codex-side skill/CLI ─ private control UDS gateway
19
+ Claude/Codex CLI
20
+ |
21
+ | private control UDS
22
+ v
23
+ +---------------- local broker ----------------+
24
+ | endpoint directory -> ledger -> coordinator |
25
+ | / | \ |
26
+ | Claude socket Codex op SSH|
27
+ +------------------------------------------------+
28
+ |
29
+ ssh node embassy peer-stdio
30
+ |
31
+ remote broker ledger
32
+ ```
33
+
34
+ There is one broker per login user and host. The broker owns one schema-6 JSON
35
+ document and one private control socket. It does not listen on a network port.
36
+ launchd may supervise the same foreground `serve` entry point.
37
+
38
+ ## Endpoint directory
39
+
40
+ The directory translates a current selector into an endpoint:
41
+
42
+ ```text
43
+ { id: opaque reg_ value, host, provider, alias, private native handle }
135
44
  ```
136
45
 
137
- The local singleton is necessary even though user interaction can be packaged
138
- as a skill. A skill runs during an agent turn; it cannot remain discoverable,
139
- hold transient correlation state, accept an inbound socket connection, or
140
- wake a different idle runtime after that turn ends.
141
-
142
- ## Component status
143
-
144
- Everything below is exercised by the deterministic suite; live validation is
145
- a separately authorized operator action (see [Validation boundary](#validation-boundary)).
146
-
147
- | Component | Current evidence |
148
- | --- | --- |
149
- | Neutral gateway types, schema-5 metadata store, bounded attempt state machine, queues, dedupe, rate limits, and public projection | **Implemented**, deterministic tests; message bodies persist under bounded retention |
150
- | Private JSONL control protocol over a controller-owned UDS | **Implemented**, deterministic synthetic tests; no provider connection required |
151
- | Claude registry/peer adapter with strict peer protocol 1 and per-operation validation | **Implemented**; deterministic tests cover discovery, native status frames, cancellation, and accessible-workspace validation |
152
- | Claude current-user runtime roots | **Implemented**; derives the registry and callback roots from the verified OS user without inspecting a launcher or configuration file |
153
- | Stateless allowlisted Codex App Server transport with bounded busy behavior | **Implemented**; every operation opens and attests its own transport, and the conformance suite covers idle gating, exact `STEER:` behavior, clean retry, and ambiguous no-replay settlement |
154
- | Attach-only local Codex proxy transport and exact-owned cleanup | **Implemented**, five deterministic tests; no live App Server connection in routine tests |
155
- | Local provider adapters and Embassy-node federation | **Implemented**, focused synthetic tests cover Claude discovery, exact Codex ownership, plus bounded catalog reconciliation and destination-owned handoff over the fixed attach-only SSH transport |
156
- | Universal shell peer mailbox | **Implemented**, alias-plus-token same-UID attribution, hash-only durable ownership, bounded long polling, stdout-flush receipts, and restart uncertainty tests; no PID binding, token file, Keychain entry, or daemon |
157
- | Gateway service composition | **Implemented**, including private control-server startup, synthetic cross-provider route installation/dispatch/reply correlation, bounded public-snapshot projection, and restart attempt-phase tests |
158
- | Delivery receipt/status lifecycle | **Implemented**, deterministic synthetic tests cover stable-UUID native receipt re-resolution, the merged/verbose/quiet Claude notice policy, one bounded stall notice with pending age where enabled, opaque private-v5 correlation handles, restart continuity, the closed status/terminal schema, and one-shot/bounded-wait CLI behavior |
159
- | Broker-owned cross-provider provenance framing | **Implemented**, deterministic tests cover exact Codex and Claude wire shapes, bounded long-alias attribution, recipient reply hints, reserved-tag neutralization, single wrapping across clean retries, and pre-write failure |
160
- | Operator/agent client CLI and package binary | **Implemented**, deterministic private-UDS tests cover the closed command family, inherited provider identity, bounded stdin-only bodies, normalized output, and ambiguous no-retry behavior |
161
- | Repo-shipped cross-provider skill | **Implemented** as a repo-scoped workflow over the client CLI; it is not installed into either provider's global configuration |
162
- | Foreground local broker launcher and provider assembly | **Implemented** as `embassy serve`; local-host-only with native messaging enabled |
163
-
164
- Synthetic tests do not scan `~/.claude`, connect `/tmp/cc-socks`, attach to a
165
- live App Server, invoke SSH, or make a model request.
166
-
167
- ## Identity, discovery, and opt-in
168
-
169
- Users address Codex routes by strict aliases and Claude routes by either the
170
- session's latest alias or its native session UUID, for example:
46
+ The `(id, host, provider)` tuple is identity. `alias` is mutable lookup and
47
+ display data. `handle` stays private and is required only for the owning
48
+ provider's final attestation.
49
+
50
+ The ID is minted randomly for a new registration, then retained across rename
51
+ and restart. Retirement retains a bounded private hash of the native binding
52
+ to fence immediate re-enrollment. After that evidence is evicted, a later
53
+ registration gets a new ID; old replies and remote references cannot revive.
54
+
55
+ Codex endpoints are explicitly registered by the task that inherits the exact
56
+ task UUID. Claude endpoints are discovered by exact session UUID and recorded
57
+ when a Claude caller or target is resolved. A same-UUID rename updates one
58
+ endpoint; a different identity never inherits work. Two live Claude sessions
59
+ may share a display name, but name resolution then refuses with
60
+ `PEER_ALIAS_COLLISION`. An exact user-supplied Claude UUID can disambiguate
61
+ selection without making UUIDs public output.
62
+ Partial discovery cannot clear an observed collision. The bounded collision
63
+ proof sets fail closed on overflow until a complete scan; exact UUID lookup
64
+ remains available. Operator retirement can use `--endpoint <public-id>` when
65
+ departed sessions share a name and can no longer rename themselves.
66
+
67
+ Remote name and identity resolution calls the endpoint's owner. Catalog replies
68
+ and any local cache are bounded and memory-only; neither grants lookup or write
69
+ authority. Remote endpoint rows contain opaque IDs and aliases, not native
70
+ handles.
71
+
72
+ `refresh` runs local Claude discovery and all configured catalog observations
73
+ in parallel. Each successful node observation replaces its rows and timestamp.
74
+ A failure retains the last timestamped rows with `PEER_TUNNEL_UNAVAILABLE`.
75
+ The status projection reads this cache without network I/O and caps the combined
76
+ remote display at 128 rows, reporting truncation. Routing still uses the owner
77
+ RPC even when the cache is fresh.
78
+
79
+ ## Ledger
80
+
81
+ `ledger.ts` is the pure transition core. It has no provider I/O, filesystem
82
+ operations, callbacks, timers, or alias re-resolution. A state transaction
83
+ supplies a draft and a timestamp; the ledger validates and mutates that draft.
84
+
85
+ The document holds:
86
+
87
+ - endpoint bindings;
88
+ - deliveries and their exact source/target identities;
89
+ - recent bounded retirement evidence;
90
+ - bounded per-source rate windows, partitioned by host (128 source rows per
91
+ host, at most 33 hosts including this gateway);
92
+ - a commit sequence and random commit identity.
93
+
94
+ Terminal body pruning leaves a receipt/reply stub and a SHA-256 body proof;
95
+ the count/time receipt bounds are independent of the retained-body byte
96
+ budget. Retirement evidence has its own count bound. A peer exhausting its
97
+ source-rate partition cannot consume the local host's source slots.
98
+
99
+ Each delivery contains its body, opaque message/conversation/delivery IDs,
100
+ deadline, STEER classification, and one phase:
171
101
 
172
102
  ```text
173
- codex-reviewer@this-mac
174
- codex-builder@build-mac
175
- codex-release-check@lab-mac.example
176
- claude-advisor@this-mac
177
- 123e4567-e89b-42d3-a456-426614174000
103
+ queued -> reserved -> armed -> accepted -> terminal
104
+ \ \ \
105
+ proven no-write ambiguous unconfirmed/ambiguous
178
106
  ```
179
107
 
180
- Claude's native `sessionId` UUID is its sole logical identity. Its current
181
- name is a mutable lookup alias for that UUID; the gateway keeps no historical
182
- name index. A rename therefore makes the old name stop resolving immediately,
183
- while the UUID and an already installed UUID-bound route continue to identify
184
- the same session. PID, registry path, process generation, and socket generation
185
- are replaceable delivery coordinates, not identity, and are refreshed from the
186
- live registry before a write. The gateway rejects duplicate current names and
187
- duplicate live UUIDs rather than choosing between them.
188
-
189
- The controller binds a Codex route to an exact host, thread ID, and owner
190
- lease. Other provider-native IDs, PIDs,
191
- working directories, endpoint paths, and socket paths are never public
192
- selectors or output fields. A Claude UUID may be supplied explicitly as a
193
- destination, but the gateway never prints or invents one for the caller.
194
-
195
- Codex registration is explicit. A task registers its own alias and
196
- authoritative `CODEX_THREAD_ID`; the gateway does not enumerate global Codex
197
- history to invent routes. Registration performs no provider I/O. Each delivery
198
- opens a fresh attested App Server transport, initializes it, resumes the exact
199
- private task with history excluded, and authorizes one body write. An App
200
- Server restart is therefore a transport fact, not a logical route transition;
201
- an unavailable or duplicate exact task fails that operation with a safe code
202
- without retargeting the alias or replaying an ambiguous write.
203
-
204
- Claude discovery is passive and limited to currently advertised genuine
205
- Claude session records. Only a validated native record bearing the supported
206
- explicit versioned Embassy-advertisement marker is classified as a gateway
207
- advertisement and excluded as a Claude destination. A genuine unmarked Claude
208
- session remains selectable even when its current name begins `codex-`.
209
- Discovery produces a bounded, sanitized `availablePeers` inventory keyed for
210
- display by the latest name. The adapter
211
- strictly validates every required and consumed registry field, session UUID,
212
- process identity and liveness, record/socket type, PID and socket-path
213
- correlation, allowed roots, and file/socket generations while tolerating
214
- unknown top-level fields. The existing public Claude connector row may carry
215
- bounded `registry` evidence: `entriesScanned`, `parseableRecords`, monotonic
216
- `parseableRecordSeenSinceBoot`, bounded per-safe-code `rejected`, and
217
- `rejectedCodesOmitted`. A registry directory that has yielded no record with
218
- parseable required fields since broker start is therefore a loud bounded
219
- observation rather than a healthy-looking empty list; if Claude is running,
220
- its registry layout may have changed.
221
- Before that enumeration, the Claude-owned external sessions registry root must
222
- belong to the current UID with exact mode 0700; failure quarantines and
223
- write-fences only Claude. Within an admitted root, individual registry records
224
- and peer sockets retain the schema, file/socket type, PID/path and allowed-root
225
- correlation, accessibility, liveness, and generation checks above without an
226
- invented additional owner or mode rule. A current name resolves to a UUID but
227
- never substitutes for it.
228
-
229
- An installed Claude route keeps its UUID as durable identity until a
230
- different session claims its alias and displaces it; there is no operator
231
- command that retires one.
232
- Discovery publishes only bounded sanitized candidates and current
233
- lookup aliases; it never changes the installed UUID. Immediately before a
234
- Claude-bound write, Embassy performs a fresh bounded registry scan, resolves
235
- that byte-identical UUID exactly once, and revalidates its current workspace,
236
- process, socket, and used-artifact generation. An incomplete scan, duplicate
237
- UUID, changed UUID, or unsafe current coordinate fails that operation closed.
238
- A duplicate display name is fenced from listing and from every send that
239
- addresses it BY NAME, and the fence is re-evaluated inside the
240
- send path, never only on the discovery timer. The fence is a fence on names:
241
- a session UUID is unambiguous, so an operator-supplied UUID remains the
242
- recovery selector and reaches the session even while its name collides, and a
243
- sender whose own display name collides is never silenced, because its identity
244
- was attested rather than typed. A pre-bound route retains its identity-pinned
245
- binding, and a name alone never restores or retargets a durable route.
246
-
247
- `embassy status` is the single pane for the human, and it is read-only: it
248
- makes exactly one call, `list_snapshot`, and never `refresh_discovery`, so the
249
- scan it reports is the broker's own and a status loop cannot journal anything.
250
- Its `sessions` block is the sanitized `availablePeers` inventory — every live
251
- Claude session the last scan found, by current name, marked `routed` once a
252
- route exists for it and its header says how old that scan is, offering
253
- `embassy refresh` when it is older than a minute — that age is read from the
254
- newest discovered session's own stamp, because the connector's `lastSeenAt`
255
- also moves on a delivery. Below the sessions come the
256
- routes: routed Claude aliases and explicitly registered Codex aliases, with
257
- provider, current state, queue depth, and last-seen age. The same
258
- pane also carries the bounded ledger's retained message bodies, so its output
259
- is as sensitive as the messages themselves.
260
- The thin skill/CLI exposes the same safe alias list to either provider.
261
-
262
- That pane has two forms of the same snapshot. Piped, or with `--json`, it is
263
- the snapshot verbatim the form every script and the skill read. On a
264
- terminal it is rendered by `status-view.ts`, a pure function of the snapshot
265
- and the reader's clock: it derives one of four plain words per connector
266
- (`ok`, `stale`, `degraded`, `offline`) and pairs every word that is not `ok`
267
- with its safe code and a one-line remedy. Two clocks govern staleness, and
268
- they are deliberately far apart. The broker's own connector window is 35
269
- seconds (`CONNECTOR_OBSERVATION_STALE_AFTER_MS`), and it is crossed routinely
270
- and innocently the Codex observer polls one route at a time every 15
271
- seconds, and Claude routes are not polled at all: their evidence is
272
- edge-triggered, written when a discovery scan or a delivery observes the
273
- session, so a quiet Claude session's `lastSeenAt` stops advancing while the
274
- session is perfectly alive. The view therefore keeps the broker's word until
275
- a route has gone unobserved for ten minutes, and only then calls it `stale`;
276
- past that backstop a Codex route earns the succession remedy only when it
277
- also carries a code that says the task itself is gone (`THREAD_NOT_OBSERVED`)
278
- — bare silence says the task may be busy or the app-server slow. A Claude
279
- route whose alias the latest scan still lists is never `stale`: the scan is
280
- fresher evidence than the route's own observation, so the discovered state is
281
- shown with `discovered <age>` as its last-seen cell, and a Claude route past
282
- the backstop that the scan no longer lists says the session exited or renamed.
283
- A connector that merely has nothing to observe is `stale`, never `degraded`,
284
- and a shell peer with unclaimed mail never moves the overall word at all:
285
- silence in a pull mailbox is a fact about the operator's other terminal.
286
- `embassy watch` tails the same snapshot through `observe_snapshot` once a
287
- second, printing each new message row and each settlement at most once a
288
- transition passed through entirely between two polls is never seen, and rows
289
- evicted from the retained window before the tail read them are announced as
290
- a note. `embassy check` proves the whole path end to end — it registers an
291
- ephemeral `peer-*` principal of its own, sends one marked body through the
292
- ordinary send path, waits for `delivered`, then awaits the correlated reply on
293
- its own mailbox, and releases the registration again; the broker retires the
294
- registration itself if the check never gets to.
295
-
296
- ## Message flows
297
-
298
- ### Codex to Claude
299
-
300
- 1. A registered Codex task calls the repo-shipped gateway skill/CLI with its own
301
- thread identity, source alias, target Claude current name or session UUID,
302
- and bounded text.
303
- 2. The gateway checks thread ownership, selector state, rate and size limits,
304
- deadline, and dedupe state.
305
- 3. It resolves the selector against a discovery scan run inside this send,
306
- installing the session's route if this is its first use, then refreshes the
307
- UUID's current process/socket coordinates and revalidates that Claude
308
- peer's canonical workspace access and exact generation before every send.
309
- 4. Immediately before the native write, it composes one broker-owned canonical
310
- `cross-session-message` textual frame with bounded sender attribution and a
311
- first-child reply hint containing the full conversation token, exact aliases,
312
- and reply command. It then opens a short-lived connection and writes one
313
- peer-protocol-1 frame immediately, regardless of whether the current
314
- Claude registry observation says `idle`, `busy`, or `waiting`. A reply
315
- request carries the gateway's own
316
- anonymous callback UDS as the transport reply address; that path is never
317
- exposed in the content frame.
318
- 5. It records only normalized delivery metadata. It does not retry an
319
- ambiguous write automatically.
320
- 6. A reply received on the callback listener is correlated in memory and
321
- routed to the owning explicitly registered Codex task.
322
-
323
- A Claude-bound peer socket is a native mailbox, not an idle gate. Once the
324
- pre-write route checks pass, Embassy attempts that mailbox write immediately;
325
- an observed busy state never queues the body. `transport_written` proves the
326
- mailbox write and is reduced to terminal `delivered` for this direction. That
327
- still does not prove Claude read, consumed, or acted on the body. The adapter
328
- distinguishes this transport boundary from Claude-to-Codex native `held`, which
329
- is a progress signal only. Neither boundary permits a retry.
330
-
331
- ### Claude to Codex
332
-
333
- This path is enabled for each explicitly registered `codex-*` task. The
334
- gateway publishes a process-owned native registry entry per task, accepts
335
- Claude's native `SendMessage`, starts an App Server turn, and returns the
336
- final reply.
337
-
338
- 1. The gateway advertises one process-owned `codex-*` record per registered
339
- task in Claude's native registry. The broker owns the advertisement,
340
- callback socket, state, queue, and dispatch; provider process lifecycle is
341
- not persisted as route authority.
342
- 2. A real Claude session uses native `ListAgents` and `SendMessage`; the
343
- gateway validates that exact live registry/socket generation and treats the
344
- text as untrusted user-role input. This inbound observation grants only a
345
- transient, in-memory capability for the correlated reply. The sending
346
- session's own route is installed here, from the exact identity the adapter
347
- attested, so the Codex task's reply travels the ordinary path; the alias the
348
- adapter reports must be the one discovery shows for that UUID, and a
349
- mismatch is refused rather than silently renamed. Installing the route
350
- authorizes nothing beyond what addressing already implied.
351
- 3. The Claude process's inherited messaging-socket value may be accepted as a
352
- transient reply address after strict validation. Claude Code exports
353
- `CLAUDE_CODE_MESSAGING_SOCKET` as a raw absolute socket path; the CLI
354
- converts it in memory to the gateway's internal `uds:` capability. A user
355
- never sets, prefixes, or passes that value manually. It is never logged,
356
- persisted, rendered, or copied into normalized events.
357
- 4. The gateway resolves the Codex alias to its private exact-thread binding.
358
- 5. The resumed task retains its existing native permissions. The gateway does
359
- not supply policy overrides.
360
- 6. Immediately before `turn/start` or `turn/steer`, the delivery attempt wraps
361
- the raw body once in Embassy's authoritative Codex-bound
362
- `cross-session-message` frame. It opens and attests a fresh App Server
363
- transport, resumes the exact task with history excluded, and starts one
364
- dedicated turn only if the task is idle. Ordinary messages received while
365
- it is active or awaiting approval remain queued. An exact leading `STEER:`
366
- body in this direction is marked as a
367
- steering message. If the connector has a positively observed active turn
368
- and no RPC already in flight, it sends the closed `turn/steer` request with
369
- that exact ID as `expectedTurnId`; App Server admits the input at the next
370
- tool-call boundary. Embassy never calls `turn/interrupt` for this path and
371
- never injects text mid-generation. A clean non-steerable or unavailable
372
- boundary silently returns the same body to the normal queue. It does not
373
- emit Claude's approval-specific native `held` control frame for ordinary
374
- queueing.
375
- 7. In `merged` and `verbose` notice modes, if the delivery remains pending for
376
- exactly `min(floor(messageDeadlineMs / 2), 120_000)` milliseconds, the
377
- gateway may send the originating Claude session at most one nonterminal
378
- `<gateway-delivery-stall>` user frame for that receipt. It contains only an
379
- allowlisted reason and a bounded `queued-for-ms` age; it is not a native
380
- `held` receipt and does not settle the delivery. The two-minute ceiling is
381
- deliberate: stall visibility must not scale with the deadline, so under the
382
- default four-hour deadline the notice fires at two minutes, not two hours.
383
- `quiet` suppresses this gateway-authored frame without changing native status
384
- or public snapshot state.
385
- 8. A later bounded attempt opens a new transport and starts the held message
386
- after it observes the exact task idle. A route retains at most three queued steering
387
- messages; accepting a fourth atomically cancels the oldest with safe code
388
- `STEER_QUEUE_SUPERSEDED`, a normal terminal receipt, and a `STEER`-marked
389
- journal event. Explicit registration is sufficient authorization; Embassy
390
- does not run an additional workspace or policy classifier.
391
- 9. Successful App Server acceptance returns Claude's native `delivered`
392
- receipt. A route or delivery error returns native `expired` with one safe
393
- error code retained in its `reason` field. The default `merged` mode omits
394
- the duplicate terminal user frame; `verbose` additionally sends a static
395
- `<gateway-delivery-diagnostic>` user frame so the reason is readable in
396
- Claude Code versions that do not render the native control reason. `quiet`
397
- also omits gateway-authored stall frames. The diagnostic never contains a
398
- socket path, session UUID, raw exception, or message body. `denied` is
399
- reserved for an actual user or policy refusal.
400
- A transient clean pre-dispatch failure returns the same message to the queue
401
- instead of terminally failing it.
402
- 10. Completion is summarized into bounded normalized state and the correlated
403
- reply is returned only to the same originating Claude session UUID after
404
- its current coordinates are uniquely re-resolved and revalidated.
405
-
406
- The native receipt retains the originating Claude session's stable UUID, not
407
- its mutable name, PID, registry record, or socket. Before every stall or
408
- terminal receipt write, the adapter performs bounded discovery and revalidates
409
- the UUID's current exact coordinates. This permits a receipt to follow ordinary
410
- process/socket rotation without writing to a stale generation. If the UUID is
411
- not uniquely re-observed with peer protocol 1, the write fails closed. A terminal
412
- write whose outcome is ambiguous is never replayed; only a proven pre-write
413
- failure may be retried while the bounded in-memory receipt remains live. The
414
- receipt correlation does not add the UUID or receipt handle to public output
415
- or durable state; a separately installed route may already persist that same
416
- Claude UUID as its private native route handle.
417
-
418
- Delivery callback arrival is timestamped at the service boundary. A terminal
419
- callback observed strictly before its message deadline is applied before the
420
- deadline sweep even when event-loop scheduling delays its worker; a callback
421
- observed at or after the exact deadline cannot reopen the expired attempt.
422
- Shutdown is likewise two-phase: provider ingress is first quiesced so no new
423
- user-message callback can enter and every already admitted callback completes,
424
- while receipt writes remain available. The service then drains callbacks,
425
- terminally settles accepted work, joins its bounded receipt writes, and only
426
- then closes provider adapters. This orders `GATEWAY_SHUTDOWN` receipts ahead of
427
- listener teardown instead of silently dropping late admitted work.
428
-
429
- Claude's native peer socket is itself a mailbox, so every Claude-bound body,
430
- including a correlated Codex reply, is written regardless of Claude's observed
431
- busy or idle state. The gateway still serializes its own writes, but it never
432
- waits for Claude to become idle and thereby deadlocks a Claude turn that is
433
- waiting for the reply. This does not change the opposite direction: ordinary
434
- Codex-bound bodies remain idle-gated, and exact leading `STEER:` bodies keep the
435
- next-tool-call-boundary rules above.
436
-
437
- ### Provenance framing and conversation continuation
438
-
439
- The broker classifies `STEER:`, enforces raw-byte body limits, deduplicates,
440
- and queues before presentation framing.
441
- The store therefore retains only the raw unframed body, never the composed
442
- envelope. A pure composer runs at the final semantic provider-write boundary so
443
- a clean retry produces the same bytes with exactly one authoritative outer
444
- wrapper. Provider connection setup and per-operation artifact validation,
445
- receipt frames, and diagnostics do
446
- not use this path.
447
-
448
- Both provider directions use Claude-compatible textual framing with a
449
- broker-owned `cross-session-message` outer element and an
450
- `embassy-reply-hint` as the first body element:
451
-
452
- - Codex-bound content uses the exact validated source alias as `from-name` and
453
- the full conversation token as the outer `conversation` attribute.
454
- - Claude-bound content uses only Claude Code's canonical bounded `from-name`
455
- attribute. For a source alias over 64 characters, the display label is a
456
- deterministic 47-character prefix, `~`, and 16 hexadecimal SHA-256
457
- characters. The hint carries the exact source as `from-alias`. The outer
458
- Claude wrapper intentionally omits `conversation`, which its pinned parser
459
- does not accept.
460
- - In either direction, the first hint carries the full token in `conversation`
461
- and the exact recipient alias in `reply-as`, followed by an exact stdin-based
462
- `embassy send --conversation ... --from ...` instruction and the statement
463
- that caller, conversation, and route policy are rechecked.
464
-
465
- Embassy does not synthesize `from`, `from-session`, or `from-mode` attributes:
466
- those names have provider-native meanings the broker cannot truthfully claim.
467
- Native socket addresses, Codex thread IDs, Claude session UUIDs, endpoint
468
- generations, and route handles never enter the content frame.
469
-
470
- The outer structure and hint come only from validated broker metadata. Before
471
- composition, the untrusted body case-insensitively neutralizes boundary-shaped
472
- opening or closing occurrences of Embassy's reserved framing tags by inserting
473
- `\` immediately after the leading `<`.
474
- Everything else remains raw text. This is not general XML, cryptographic
475
- authentication, or proof that the message content is trustworthy; it is a
476
- consistent structural provenance marker at the model input boundary. A native
477
- Claude wrapper already present in an inbound body is untrusted nested text
478
- beneath the Embassy wrapper.
479
-
480
- The full token delivered in the hint lets the recipient answer with
481
- `send --conversation`, but it is only a participant-scoped conversation
482
- locator. The service still validates
483
- the inherited caller, current conversation membership, and current route
484
- policy. The full token remains confined to the accepted control result
485
- and transient provider payload, and is memory-only: it is never persisted,
486
- journaled, logged, snapshotted, placed in a receipt,
487
- or returned from suffix-only public correlation. Formatter,
488
- provenance-metadata, and framed-size failures are clean pre-write terminal
489
- failures; they can never become ambiguous writes or replay authorizations.
490
-
491
- The gateway exposes `turn/steer` only through an exact leading `STEER:` body in
492
- the Claude-to-Codex direction. The global `EMBASSY_STEERING_ENABLED` switch is
493
- on by default and exact `0` disables classification. The 0.147.0 schema this
494
- adapter targets requires `expectedTurnId`, rejects a nonmatching active turn, reports a clean
495
- `activeTurnNotSteerable` condition, and returns the accepted turn ID. Embassy
496
- validates all of those temporal correlations before settlement. `turn/interrupt`
497
- is never called or exposed, and there is no generic App Server RPC escape
498
- hatch.
499
-
500
- ### Delivery status and bounded waits
501
-
502
- Every accepted control-plane `send`
503
- result contains both its conversation ID and a fresh opaque delivery
504
- correlation handle called a delivery token.
505
- The token has the closed form `dlv_` followed by exactly 24 base64url
506
- characters (`A-Z`, `a-z`, `0-9`, `_`, or `-`). It addresses one bounded
507
- private schema-5 message/status row and is not a provider receipt handle or a
508
- provider native identifier. It is stored only in the mode-0600 broker state
509
- and never appears in a public snapshot, normal log, or provider receipt.
510
-
511
- The read-only `delivery_status` method accepts only that token and returns one
512
- of these closed results:
513
-
514
- - `{ found: false }`; or
515
- - `{ found: true, state, terminal, updatedAt, deadlineAt, ... }`, where `state`
516
- is one of `queued`, `stalled`, `delivered`, `unconfirmed`, `expired`,
517
- `failed`, `ambiguous`, or `cancelled`. `terminal` is false exactly for
518
- `queued` and `stalled`, and true for every other state. `pendingForMs` may
519
- report the nonnegative age since gateway acceptance, including time spent in
520
- flight, and `safeErrorCode` may report one shape-constrained broker code.
521
-
522
- `updatedAt` and `deadlineAt` are ISO timestamps. A terminal result guarantees
523
- only that this gateway delivery attempt will not transition again. It does not
524
- guarantee a model reply or make an ambiguous outcome safe to retry. A stalled
525
- result is progress only, even after the one sender-visible stall notice when
526
- the configured notice policy permits it.
527
-
528
- The CLI exposes `delivery-status --token <token>` for one read and
529
- `wait-delivery --token <token>` for a bounded wait. The waiter uses the same
530
- read-only method every 250 ms, emits only the terminal result, and stops no
531
- later than the delivery deadline plus the control client's 3-second allowance.
532
- An unknown token fails immediately. A wait timeout is not a terminal delivery
533
- state and does not authorize a resend.
534
-
535
- `unconfirmed` and `ambiguous` are distinct terminal outcomes. `unconfirmed`
536
- means the transport write itself was confirmed but terminal provider evidence
537
- was never observed; `ambiguous` means the write outcome is unknown. Both are
538
- terminal, neither is a retry authorization, and both exit `6`.
539
-
540
- `wait-delivery` exits `0` only for `delivered`. It exits `6` for every other
541
- terminal state (`unconfirmed`, `expired`, `failed`, `ambiguous`, or
542
- `cancelled`) while preserving the exact terminal result in its JSON output. An
543
- unknown token exits `3`; a local bounded-wait timeout exits `4` and is not a
544
- terminal state.
545
-
546
- The status table is bounded. Under capacity pressure Embassy evicts only the
547
- oldest terminal correlation handle; active `queued` or `stalled` handles are
548
- never displaced to admit a new send. A pressure-evicted handle returns
549
- `{ found: false }`, just like a handle whose retention window elapsed.
550
-
551
- ### Replies and process restarts
552
-
553
- Conversation IDs correlate replies, and callback addresses exist only in
554
- memory, but message bodies and their bounded attempt phase are durable. After a
555
- gateway restart, queued or reserved work may resume once against the same
556
- logical route. Work that crossed the armed boundary settles
557
- `ambiguous`; provider-accepted work without terminal evidence settles
558
- `unconfirmed`. Neither is replayed. Work already past its deadline settles
559
- `expired`.
560
-
561
- The delivery token and status of each retained message survive the restart: a
562
- queued or reserved attempt remains inspectable while it resumes, and armed or
563
- accepted work remains inspectable after it settles ambiguous or unconfirmed.
564
- Pending replies, callbacks, native receipt handles, and conversation
565
- capabilities do not survive. Logical registrations and installed Claude routes
566
- remain, while each subsequent provider operation must attest its
567
- own current transport facts.
568
-
569
- ## Gateway control plane
570
-
571
- The control plane is newline-delimited JSON on one Unix-domain socket inside a
572
- controller-owned mode-0700 state directory. The socket and state files are
573
- mode 0600. Frames are size-bounded and closed against unknown keys, methods,
574
- versions, and enum values.
575
-
576
- The closed version 3 method family is exactly these fourteen methods:
577
-
578
- - `health` and `list_snapshot`, a safe public snapshot;
579
- - `observe_snapshot`, a read-only projection that may settle already-due
580
- delivery deadlines before projecting;
581
- - `register_codex` and `unregister_codex` — explicit Codex registration with
582
- atomic `--succeeds` replacement, and owner unregister;
583
- - `delivery_status`, a lookup by an opaque correlation handle retained only in
584
- bounded private v5 state;
585
- - `send`, whose direction follows the inherited principal — who is sending —
586
- rather than the route table, and which installs a discovered Claude
587
- session's route on its first use. It addresses either a route by name
588
- (`toAlias`) or an open conversation by its token (`conversationId`), never
589
- both: the conversation form is the correlated reply operation, and the
590
- caller must already own one end of that conversation;
591
- - `refresh_discovery`, which rescans for Claude sessions;
592
- - `peer_catalog` and `peer_handoff`, the private federation catalog and
593
- destination-owned handoff operations; and
594
- - `register_peer`, `unregister_peer`, `await_peer`, and `peer_receipt`, the
595
- shell-peer registration, mailbox, and flush-before-receipt operations. A
596
- first registration may ask to be `ephemeral` with a bounded `ttlMs`
597
- (default five minutes): the route is real in memory — it routes, queues,
598
- and settles like any other — but it is projected out of the durable state
599
- document together with every row that names it (its messages, dedupe and
600
- rate rows, and journal entries), excluded from the federation catalog, and
601
- retired by the broker's own clock, so it cannot survive a restart or be
602
- restored on another node. Removal — by `unregister_peer` or by that clock —
603
- takes the same rows out of the live state in the same write, so nothing of
604
- it is written afterwards either; a retirement that fails is retried twice,
605
- five seconds apart, then left to the next restart. The clock is guarded by
606
- the registration's identity, so a durable route that later takes the same
607
- alias is never retired by it. `embassy check` uses one.
608
-
609
- The installed binary is `embassy`, and it is the only installed binary. Its
610
- seventeen implemented commands are
611
- `serve`, `service`, `health`, `status`, `watch`, `check`, `delivery-status`,
612
- `wait-delivery`, `refresh`, `register-codex`, `unregister-codex`, `send`,
613
- `reply`, `register-peer`, `unregister-peer`, `await`, and
614
- `peer-stdio`. `reply --conversation <token> --alias <own-alias>` is a
615
- deprecated alias for `send --conversation <token> --from <own-alias>`: it
616
- builds the same `send` request and is kept only until the reply hints already
617
- delivered in older envelopes have aged out. Message bodies are non-empty
618
- UTF-8 from standard input only, with a 16 KiB ceiling; they are never accepted
619
- in an argument or file. The client emits one bounded normalized JSON line, and
620
- for every broker-protocol command it never returns a thread ID,
621
- provider-native ID, path, or address, and only `status` and `watch` return
622
- message bodies — the snapshot's own. There are three deliberate
623
- exceptions, each of them about something the operator alone is looking at.
624
- `service` reports its own plist path, its log path, and any program path in
625
- the plist that is no longer on disk, because managing local files is what it
626
- does. `status` on a terminal, `watch`, and `check` render for a person instead
627
- of emitting that JSON line: `status` and `watch` show the snapshot's own
628
- retained bodies (previewed to one control-free line) and the resolved state
629
- directory, and `check` prints its own hops. Piped or with `--json`, `status`
630
- and `watch` are machine-readable again, and every other command is unchanged. Every command but `serve` and `service` requires a running
631
- broker: `serve`
632
- starts one in the current terminal, and `service install|uninstall|status`
633
- manages the macOS launchd agent that runs one, contacting no broker itself
634
- apart from the bounded health check `install` reports at the end. The launcher
635
- never daemonizes itself.
636
-
637
- `register-codex --alias <new> --succeeds <current>` is one atomic logical-route
638
- transaction. It verifies the inherited identity of the replacement task,
639
- settles the outgoing route's work according to recorded write phase, removes
640
- its transient capabilities, and publishes only the
641
- new registration. There is no prepared, activated, endpoint-generation, or
642
- manual-recovery state.
643
-
644
- `send --to <current-name@host>` and `send --to <uuid>` address the same logical
645
- session, and either form installs its route on first use. UUID
646
- input is normalized to lowercase. No command returns the
647
- UUID, and no historical name remains routable after a rename.
648
-
649
- Provider-authorized registration and send operations require one exclusive inherited principal.
650
- Codex registration, unregister, and Codex-to-Claude send require only a valid
651
- `CODEX_THREAD_ID`; they fail if a non-empty Claude messaging socket is also
652
- inherited. Claude-to-Codex send requires only the raw inherited Claude socket
653
- path and fails if a non-empty Codex thread ID is also present. A
654
- conversation-addressed send likewise fails with both identities or neither.
655
-
656
- Installing a Claude route is a side effect of a send, not a command: the
657
- sending principal is already attested, the addressed session is resolved
658
- against a discovery scan performed inside that send, and the store binds the
659
- route under the live-peer registration mode. A session already bound under the
660
- same (host, session UUID) keeps its registration — and therefore its in-flight
661
- conversations — and is renamed in place if its display name changed; a route
662
- whose alias now names a different session is displaced, its work settled
663
- `cancelled` with `ENDPOINT_RETIRED`, and both outcomes are journaled so
664
- `embassy status` shows them. Agents are instructed to send only where the user
665
- pointed them; that is an operating norm, not an additional gateway identity
666
- check.
667
-
668
- The foreground command is:
108
+ `queued` is durable admission. `reserved` freezes a FIFO prefix under one
109
+ attempt. `armed` records the exact framed byte evidence after identity
110
+ revalidation. `accepted` records provider acceptance and the correct loss
111
+ outcome. `terminal` is first-wins.
112
+
113
+ Only positive no-write evidence may return work to the queue. Restart returns
114
+ reserved work, never armed or accepted work. This is the no-replay boundary.
115
+
116
+ One ledger row is one deliberate message. Equal bodies do not deduplicate.
117
+ Federated retry idempotence uses the owner-minted message ID and accepts a
118
+ duplicate only when every identity and message field agrees.
119
+
120
+ ## Owned state
121
+
122
+ `OwnedStateFile` provides one typed atomic document rather than provider- or
123
+ feature-specific stores. The schema codec validates every consumed field and
124
+ global bound before the state is exposed.
125
+
126
+ Transactions are synchronous functions over detached clones. Provider I/O
127
+ cannot run under a transaction. A changed draft is encoded, written to an
128
+ exclusive mode-0600 temporary file, synced, renamed, and followed by a
129
+ directory sync. The installed commit is reconciled against the prior/current
130
+ commit identities; an unknown write result poisons the process rather than
131
+ guessing. The live host lease is checked before a transaction, before
132
+ persistence, and immediately before rename.
133
+
134
+ No-op transactions write nothing. Unsupported or corrupt state refuses before
135
+ mutation. Schema 6 is reset-only; no older loader or converter is linked into
136
+ the v4 binary.
137
+
138
+ ## Coordinator
139
+
140
+ The coordinator is the only delivery scheduler. It keys one active operation
141
+ by exact destination and normal-versus-STEER class. It reserves the oldest
142
+ bounded prefix, resolves exact private endpoint facts, builds one provenance
143
+ envelope per message, and asks one destination adapter to deliver the batch.
144
+
145
+ Provider work happens outside the state transaction. The adapter first prepares
146
+ immutable wire evidence. Its authorization callback opens a new transaction,
147
+ checks the live lease, revalidates all local endpoint IDs, aliases, handles, and
148
+ body hashes, and advances the exact batch to `armed`. Its acceptance callback
149
+ persists `accepted` before the provider operation is treated as admitted.
150
+
151
+ Messages that arrive while a successful operation is running are drained by
152
+ the same coordinator loop in the next bounded batch. A clean busy response
153
+ stops the loop until a fixed retry cadence; it never spins at caller speed.
154
+ The number of native operations remains bounded even if an endpoint is retired
155
+ while one is still running.
156
+
157
+ The coordinator records only each endpoint's last bounded operation outcome
158
+ and safe code for status. It does not maintain an analytics stream.
159
+
160
+ ## Destination adapters
161
+
162
+ ### Claude socket
163
+
164
+ The Claude adapter rediscovers the exact compatible session, validates its
165
+ registry record, same-user socket and workspace/state-root separation, prepares
166
+ the complete peer-protocol-1 frame, and performs one native write after the
167
+ coordinator authorizes it. The native socket wakes Claude immediately.
168
+
169
+ There is no forked helper, callback socket, advertisement record, or native
170
+ Claude sending shim. A Claude session sends by invoking `embassy send`; its
171
+ native socket is still used for receive and reply wake-up.
172
+
173
+ ### Codex operation
174
+
175
+ The Codex adapter creates a fresh App Server connection per operation, checks
176
+ the current interface, resumes the exact registered thread with history
177
+ excluded, and starts one turn carrying the bounded batch. Returned history and
178
+ model output are not retained or forwarded.
179
+
180
+ An accepted operation remains tracked until its terminal lifetime notification.
181
+ An exact leading Claude-to-Codex `STEER:` may use that same accepted
182
+ operation's `turn/steer` method at the next safe tool-call boundary. It never
183
+ uses `turn/interrupt` and does not detach an operation early merely because
184
+ input acceptance occurred.
185
+
186
+ ### SSH handoff
187
+
188
+ Federation starts `/usr/bin/ssh` directly with fixed safe options and the
189
+ remote `embassy peer-stdio` command. Protocol 3 has four correlated methods:
190
+
191
+ - `initialize`exact version and host agreement;
192
+ - `catalog` bounded public endpoint rows;
193
+ - `resolve` owner-authoritative name or identity lookup;
194
+ - `handoff` one bounded batch admitted to the destination ledger.
195
+
196
+ The SSH login is the trust boundary. `initialize.host` is a peer claim: it
197
+ must be in the receiver's `nodes.json` peer list, but Embassy does not bind it
198
+ independently to an SSH key, network address, or physical machine. Each handoff
199
+ source host must match that accepted claim. The source identity supplied by
200
+ the trusted peer enables first contact without reverse catalog propagation.
201
+ The destination still validates the exact local target and owns admission,
202
+ storage, scheduling, and receipts. A definite refusal is exposed only when
203
+ proved before enqueue; transport or post-commit uncertainty is not replayed.
204
+
205
+ ## Provenance and replies
206
+
207
+ Each message in a native wake has a structural
208
+ `<cross-session-message>` envelope with bounded aliases, provider identity, and
209
+ a reply hint. User text that begins a reserved gateway tag is neutralized. The
210
+ envelope is not a cryptographic signature; provider content stays untrusted.
211
+
212
+ Replies use `embassy send --conversation <reference>`. The calling endpoint is
213
+ inferred again. The ledger verifies that it is one exact participant and sends
214
+ to the other exact participant. References may survive process restart while
215
+ the bounded delivery row and exact endpoint bindings remain. They do not
216
+ survive endpoint retirement/replacement, retention expiry, eviction, or state
217
+ reset.
218
+
219
+ ## Local control and CLI
220
+
221
+ The private control protocol is version 5. Each connection carries one bounded
222
+ JSON request and one closed JSON response over the expected private Unix
223
+ socket. A mutating request whose reply is lost after write reports
224
+ `CONTROL_WRITE_OUTCOME_AMBIGUOUS`; the CLI does not retry it.
225
+
226
+ The public CLI is:
669
227
 
670
228
  ```text
671
- embassy serve
229
+ register-codex send status
230
+ refresh delivery-status wait-delivery
231
+ retire check health
232
+ serve service peer-stdio
233
+ --version --help
672
234
  ```
673
235
 
674
- `serve` takes no options. There is no inbound mode to choose: the broker has
675
- one posture, and it is the OS boundary.
676
-
677
- Before provider validation, listener creation, or App Server attachment, the
678
- launcher acquires one fixed host-wide crash-reclaimable owner lease under the
679
- verified login home. The lease is independent of `EMBASSY_STATE_DIR`, so two foreground
680
- controllers cannot be started for the same login account by choosing different
681
- state roots. It is the only instance lock Embassy takes: the pre-rename
682
- prototype state root is no longer read, locked, or mutated.
683
-
684
- It emits one normalized ready line and holds the process until `SIGINT` or
685
- `SIGTERM`, when exact-owned resources are
686
- closed. Startup validates exact owned provider paths and binds controller-owned
687
- UDS listeners. Missing optional providers or a provider-local interface failure
688
- degrades only that surface. Unsafe ownership, path, symlink, lease, state,
689
- or generation evidence for Embassy-owned or executed artifacts and Embassy
690
- callback, control, or state paths aborts startup; unsafe UID or mode evidence
691
- on Claude's external sessions registry root quarantines only Claude. The bounded read-only Claude registry
692
- scan records only connector-level schema, rejection, and empty evidence; it
693
- does not publish candidates, select or connect to a peer, write a provider
694
- socket, request provider history, start a model turn, or contact a remote host.
695
- Validated target bindings may retain private native and socket-derived evidence
696
- memory-only until rescan or close, but none enters public state or persistence.
697
- Its ready result reports the exact local host — from `nodes.json`, or this
698
- host's own hostname when that file is absent — and `codexMode: "native_messaging"`
699
- without exposing paths.
700
-
701
- There is no arbitrary filesystem operation, shell command, SSH command, App
702
- Server method, Claude registry mutation, credential argument, approval reply,
703
- or raw diagnostic method.
704
-
705
- Reaching the same-UID private control socket is the permission to message —
706
- and it is a local containment boundary, not proof of a trusted agent process.
707
- Every mutation additionally checks route ownership, exact
708
- thread/session generation, source alias, bounds, and conversation state. A name
709
- currently shared by more than one live Claude session is refused with
710
- `PEER_ALIAS_COLLISION` inside the send that addressed it BY NAME; the broker
711
- never resolves an ambiguous name by picking first. The fence is a fence on
712
- names only: a session UUID is unambiguous, so a UUID selector still reaches its
713
- session, and a sender is never fenced by its own display name because its
714
- identity was attested rather than typed.
715
-
716
- ## Codex connector and federation
717
-
718
- Each broker's local connector has an explicit host identity, from `nodes.json` when federating or from this host's own hostname otherwise; `this-mac` has no reserved meaning. Configured peers exchange only body-free local catalogs and destination-owned handoffs over fixed SSH.
719
-
720
- The local connector resolves the managed standalone Codex release by exact
721
- owned path; it does not use `PATH`. That installation is separate from
722
- any NVM-managed `codex` on the user's `PATH` (for example
723
- `~/.nvm/versions/node/*/bin/codex`), does not replace
724
- it, and does not edit a shell profile. The two installations therefore do not
725
- conflict.
726
-
727
- The stateless transport has a fixed App Server method allowlist. One attempt
728
- may initialize, resume the exact registered task, start a dedicated turn, or
729
- steer the exact positively observed active turn. Loaded-task enumeration,
730
- unsubscribe, interrupt, archive,
731
- delete, history, shell, configuration, authentication, plugin,
732
- approval-response, and generic RPC methods remain excluded everywhere.
733
-
734
- The App Server 0.147.0 schema this adapter targets gates the privacy-preserving
735
- `thread/resume.excludeTurns` field behind initialization capability
736
- `experimentalApi: true`. The transport therefore hard-codes that one
737
- non-configurable capability solely to suppress history retrieval. The attempt's
738
- resume sends exactly `threadId` plus `excludeTurns: true`, then requires an
739
- exact empty `thread.turns` array.
740
- Missing, malformed, or nonempty turns fail closed and are never emitted or
741
- persisted. The capability does not add an experimental client method or change
742
- the closed RPC allowlist.
743
-
744
- Registration records the exact inherited task identity and establishes logical
745
- reachability without provider I/O. For each `turn/start`, including a queued
746
- drain, Embassy opens and attests a fresh transport, initializes the closed
747
- interface, resumes that exact task with history excluded, requires it to be
748
- idle, and authorizes one body write with no policy overrides. Version metadata
749
- does not participate. Embassy does not read or retain reported
750
- working-directory or policy fields, and a transport failure cannot discard the
751
- registration or its accepted queue.
752
-
753
- Embassy never changes or independently classifies a Codex task's approval or
754
- sandbox policy. Offline `TurnStartParams` schema evidence from the App
755
- Server 0.147.0 schema this adapter targets shows that
756
- policy overrides persist for the current and subsequent turns, so using them
757
- as per-message restrictions would silently mutate the native task. Embassy
758
- therefore starts the turn without overrides and leaves approval, sandbox, and
759
- tool enforcement to the registered task's native Codex configuration. Explicit
760
- `codex-*` registration plus exact per-operation task and transport validation is the
761
- gateway reachability boundary; native task policy remains Codex's concern.
762
-
763
- ### Federation
764
-
765
- Federation is the only cross-machine path. Each entry in `nodes.json` is an
766
- OpenSSH `Host` alias; the broker dials `ssh <node> embassy peer-stdio` and
767
- speaks the fixed peer protocol — version 2, three methods: `initialize`,
768
- `catalog/get`, `handoff`. A peer's catalog is body-free (aliases, providers,
769
- states), and its routes appear locally as `alias@host` mirrors behind opaque
770
- references; a handoff is `delivered` when the destination durably accepts it,
771
- after which the destination owns provider delivery, and anything lost after
772
- that acceptance settles ambiguous and is never replayed. One hop only, no
773
- forwarding. A node answering `initialize` with another protocol version
774
- surfaces `PEER_PROTOCOL_MISMATCH` on its mirrored routes and in `embassy
775
- status` instead of a tunnel fault. SSH aliases are fixed operator
776
- configuration, never model-provided strings; normal OpenSSH host-key
777
- validation applies, your SSH configuration owns keys, users, and ports, and
778
- Embassy opens no listener of its own.
779
-
780
- ### Offline App Server schema evidence
781
-
782
- The connector requires turn notifications to carry the exact `threadId` and
783
- correlates the exact `turn.id`; `item/completed` must carry the exact
784
- top-level `threadId` and `turnId`. Public protocol examples do not establish
785
- every live notification field. Isolated no-model schema generation from
786
- managed Codex App Server 0.147.0 now confirms that its v2 `TurnStarted` and
787
- `TurnCompleted` notifications require `threadId` plus `turn`, and
788
- `ItemCompleted` requires `threadId`, `turnId`, and `item`; an
789
- `agentMessage` item includes `id`, `text`, and `type`. This clears the
790
- correlation-shape question for exact version 0.147.0 without connecting to App
791
- Server or a provider. Live multi-client notification fanout and writable
792
- behavior remain untested, and any runtime mismatch still fails closed.
793
-
794
- The same offline 0.147.0 schema generation confirms that
795
- `TurnStartParams.approvalPolicy` and `sandboxPolicy` are persisted for the
796
- current and subsequent turns. That no-model evidence is why version 1 sends no
797
- seemingly temporary policy override.
798
-
799
- Offline 0.147.0 schema generation also confirms that `TurnSteerParams` requires
800
- exact `threadId`, `input`, and `expectedTurnId`; the precondition fails
801
- when that ID is not the current active turn. `TurnSteerResponse` returns the
802
- accepted `turnId`, and the closed App Server error shape includes
803
- `activeTurnNotSteerable`. Embassy validates this schema at its use boundary, delegates the
804
- next-tool-call timing boundary to App Server, treats a clean refusal as normal
805
- queue fallback, and treats malformed or write-ambiguous results as terminally
806
- uncertain without replay.
236
+ `send` accepts exactly one of `--to` and `--conversation`; it has no `--from`.
237
+ Human `status` is a rendering of the same closed body-free JSON shape. Its
238
+ health word describes control/ledger health, local route rows expose their last
239
+ native operation, and the federation section exposes only the last bounded
240
+ catalog observation. `health` is a control-path probe. `check` creates temporary
241
+ private loopback endpoints and uses the real ledger/coordinator/receipt path,
242
+ then retires them; no provider or model is contacted. It is not a
243
+ provider-readiness test.
244
+
245
+ Machine-facing CLI success is one `{ok, command, result}` JSON line. The
246
+ snapshot is at `.result` and its endpoint rows at `.result.routes`; native
247
+ handles and message bodies cannot hitchhike through the closed result decoder.
248
+
249
+ ## Startup and shutdown
250
+
251
+ Startup order is ownership-sensitive:
252
+
253
+ 1. load the private node inventory, or derive a transient first-boot default,
254
+ and load configuration;
255
+ 2. acquire the fixed host-wide kernel lease;
256
+ 3. open and validate schema-6 state without changing the inventory;
257
+ 4. atomically install and reload the default inventory when first boot needs
258
+ one;
259
+ 5. construct native and SSH adapters;
260
+ 6. bind and validate the private control socket;
261
+ 7. clean any exact temporary loopback residue;
262
+ 8. apply restart settlement and begin scheduling;
263
+ 9. accept semantic control requests.
264
+
265
+ This order prevents recovered messages reaching a provider before control and
266
+ ownership are established. Cancellation or lease loss fences new writes.
267
+
268
+ Shutdown first rejects new semantic work, closes control, applies phase-derived
269
+ restart settlement, waits for active coordinator operations, closes every
270
+ destination, then releases state and the host lease. Closing never invokes a
271
+ model interrupt.
807
272
 
808
273
  ## Protocol and schema versions
809
274
 
810
- Each number below is bumped once per release line, at first need, and a
811
- mismatch is refused rather than adapted to:
812
-
813
- | Surface | Version | On mismatch |
814
- | --- | --- | --- |
815
- | Private state schema (`gateway-state.json`) | 5 | An older or unknown schema refuses with `GATEWAY_STATE_SCHEMA_UNSUPPORTED`; reset only, never rewritten |
816
- | Private control protocol (CLI ↔ broker) | 3 | `CONTROL_VERSION_MISMATCH` at the client; keep the CLI and broker on one installation. A method the broker does not implement is `UNKNOWN_METHOD`, and the CLI says to rebuild or update the client |
817
- | Federation peer protocol (`peer-stdio`) | 2 | `PEER_PROTOCOL_MISMATCH` on that node's mirrored routes and in `embassy status` |
818
- | Native Claude helper IPC protocol | 2 | Internal to one installation; the helper and broker ship together |
819
- | Public snapshot schema (`embassy status --json`) | 2 | Unchanged across the 3.0 line |
820
- | Claude peer protocol (consumed, not owned) | 1 | A session record declaring another value is rejected in isolation and counted |
821
-
822
- ## Persistence and privacy
823
-
824
- The private store may retain:
825
-
826
- - schema-5 logical registrations with aliases, registration IDs, and exact
827
- provider-native route handles inside the closed private binding schema;
828
- - bounded messages with explicit `queued`, `reserved`, `armed`, `accepted`, or
829
- `terminal` attempt phase and normalized activity used for accounting and
830
- public projection;
831
- - timestamps, counters, dedupe/rate-limit records, and safe error codes.
832
-
833
- It also retains message bodies under bounded caps — the queued body of every
834
- undelivered message and the recent delivery ledger's retained bodies, evicted
835
- oldest-first against a 1 MiB budget by default. It must never retain provider
836
- output, tool input/output, raw App Server or Claude frames, stderr, histories,
837
- credentials, Claude registry payloads, or callback/socket paths. The public
838
- snapshot is a
839
- strict projection that removes private route handles, registration IDs, and
840
- operation-local endpoint evidence. The state directory is mode 0700 and state is mode 0600;
841
- provider-native identifiers never enter normalized events, public snapshots,
842
- CLI arguments/output, aliases, logs, or error text. On restart,
843
- logical routes remain unchanged. Queued and reserved bodies
844
- may resume once only after their exact registration authority is
845
- rechecked; armed and accepted work settles without replay. Callback, native
846
- receipt, conversation, and reply capabilities are not reconstructed.
847
-
848
- Private schema 5 is the binary's only native store format; the bounded public
849
- snapshot deliberately remains schema version 2. The runtime performs no
850
- migration or best-effort rewrite. An old or unknown private schema refuses with
851
- `GATEWAY_STATE_SCHEMA_UNSUPPORTED` without mutating the state file; the operator
852
- must follow the reset-only runbook in `docs/CONFIGURATION.md`. A malformed
853
- schema-5 document produces the ordinary strict corrupt-state error.
854
-
855
- ## Minimum filesystem and process access
856
-
857
- The production gateway does not need broad home-directory access or the
858
- user's interactive Claude history. The narrow live boundary is:
859
-
860
- - read/enumerate only the exact Claude live-session registry directory;
861
- - stat/connect only validated peer sockets inside the exact Claude socket
862
- directory;
863
- - create and later remove only its exact-owned callback socket inside the
864
- accessible Claude peer-socket directory, with inode/generation checks;
865
- - create its control socket plus metadata files only inside its
866
- separate controller-owned mode-0700 state directory;
867
- - attach to the managed local Codex App Server; and
868
- - optionally execute the fixed `ssh <node> embassy peer-stdio` argv for each
869
- node listed in `nodes.json`, with no shell and no model-supplied command or
870
- hostname.
871
-
872
- It does not read Claude transcripts, settings, project state, credentials,
873
- Keychain, shell history, or unrelated user files. It does not copy, print,
874
- persist, or manipulate authentication material. Routine tests replace all of
875
- the boundaries above with test-owned temporary directories, fake UDS peers,
876
- and fake App Server transports.
877
-
878
- ### Exact default roots on macOS
879
-
880
- Provider setup derives these paths from the current OS user's verified home; it
881
- does not scan the home directory. These are the reviewed
882
- boundaries exercised by the live gateway; routine tests substitute synthetic
883
- paths, peers, and transports:
884
-
885
- | Path/capability | Minimum purpose |
886
- | --- | --- |
887
- | `~/.claude/sessions` | Derive from the verified current OS user's normalized home; read/enumerate only live registry JSON during the separately authorized passive-discovery gate, and validate exact records, PIDs, workspaces, and peer sockets used by the current operation. An absent or unsafe root degrades only Claude |
888
- | `/tmp/cc-socks` | At foreground startup, validate the private directory and create/remove only `/tmp/cc-socks/<gateway-pid>.sock` after inode/generation checks; search/stat genuine peers at passive discovery and connect one validated target only at the separately authorized send gate |
889
- | `~/.local/state/agent-embassy/.agent-embassy-state` | Validate or establish the exact ownership marker before creating the fixed host lease; an existing non-empty unmarked root is rejected without mutation |
890
- | `/usr/bin/lockf` and `/bin/cat` | Hold one fixed, non-waiting macOS advisory lease for the foreground controller; the helper receives no shell text, provider data, or model-supplied argument |
891
- | `~/.local/state/agent-embassy/.gateway-host.lock` | Fixed per-login kernel-held lease acquired before provider setup; it remains here even when `EMBASSY_STATE_DIR` is overridden. Its bounded PID/token record is exact-cleanup metadata, not a path-only stale-lock authority; a crash releases the kernel lock and the next foreground process may acquire the existing file |
892
- | `~/.local/state/agent-embassy` (or explicit `EMBASSY_STATE_DIR`) | Default controller-owned store, control UDS, and state lock; an explicit absolute configuration may replace only these state surfaces |
893
- | `~/.codex/packages/standalone` and `~/.codex/app-server-control/app-server-control.sock` | Resolve the exact owned managed Codex binary and attach to the already-running private local App Server; never bootstrap or unlink it |
894
-
895
- No grant to `~/.claude/projects`, the rest of
896
- `~/.claude`, Keychain APIs, the full home directory, or
897
- unrelated temporary files is required. Cross-machine reach is federation over
898
- `ssh <node> embassy peer-stdio`, configured only in `nodes.json`.
899
-
900
- A routed Claude session's workspace may contain the private controller-state
901
- directory. The filesystem root and configured temporary roots are still
902
- rejected as deliberately broad Claude workspaces, and that refusal happens
903
- before any route is installed. The user's home is addressable with the
904
- default controller-state root beneath it. A narrower project directory remains
905
- the preferred least-context setup, but it is not mandatory.
906
-
907
- ## Failure and upgrade policy
908
-
909
- - Provider versions are best-effort metadata and never grant or remove routing
910
- authority. A session record whose peer
911
- protocol is not 1 is rejected per record and counted without stopping the
912
- broker; interface drift degrades only its responsible provider.
913
- - Unsafe ownership, path, symlink, lease, state, or generation evidence for
914
- Embassy-owned or executed artifacts and Embassy callback, control, or state
915
- paths refuses broker startup. Unsafe UID or mode evidence on Claude's
916
- external sessions registry root quarantines only Claude. A malformed message version, required App Server response
917
- shape, or used-artifact generation fails closed on its current operation.
918
- - Alias collisions, stale ownership leases, PID/socket races, unsafe
919
- gateway-owned state, unexpected paths, queue overflow, deadline expiry, and ambiguous writes are
920
- normalized failures, never raw diagnostics.
921
- - A provider disconnect fails or defers only the current operation. The next
922
- eligible attempt opens and attests a new transport; logical registration
923
- does not depend on a connector lifecycle.
924
- - The first successful Codex registration locks its exact alias, task, and host
925
- until that registration is explicitly replaced or unregistered. Exact
926
- re-registration remains idempotent.
927
- - `register-codex --alias <new> --succeeds <current>`, issued from inside the
928
- successor task on the same host with its own inherited `CODEX_THREAD_ID`, is
929
- the one atomic transaction that changes the registered Codex identity. A
930
- replacement must name the exact current registration on the same host, with
931
- a different alias and thread. Embassy settles the outgoing registration's
932
- work from its durable attempt phase, removes its transient
933
- capabilities, and publishes only the replacement. No conversation, reply
934
- capability, or queued body transfers to the new identity, and no
935
- intermediate generation or manual-recovery state exists.
936
- - No ambiguous mutation is retried automatically.
937
- - A queued or reserved body survives process loss under bounded retention and
938
- may resume once after exact logical authority is rechecked. Armed or accepted
939
- work settles `ambiguous` or `unconfirmed` and is never replayed.
940
- - A provider update that changes an internal interface degrades its
941
- responsible route while the broker and other providers remain available. A
942
- Claude record outside peer protocol 1 is rejected per record, and every
943
- current provider artifact used for an operation is re-attested before effect.
944
-
945
- ## Validation boundary
946
-
947
- Routine validation is deterministic and synthetic: it does not inspect live
948
- provider state, connect a provider socket, attach to App Server, invoke SSH, or
949
- make a model request. Live validation is a separately authorized operator
950
- action — `embassy check` is its everyday form — and is never part of CI.
951
-
952
- Only the synthetic layer is routine validation. Server startup,
953
- discovery, and callback binding remain no-send operations; step 4 is the first
954
- provider write. A real provider message is never enabled in CI.
955
-
956
- ## References
957
-
958
- - [Anthropic: Message your other Claude Code sessions](https://code.claude.com/docs/en/cross-session-messaging)
959
- - [OpenAI: Codex App Server](https://learn.chatgpt.com/docs/app-server)
960
- - [OpenAI: Remote connections and SSH hosts](https://learn.chatgpt.com/docs/remote-connections)
275
+ | Surface | Version | Compatibility policy |
276
+ |---|---:|---|
277
+ | Private state (`gateway-state.json`) | 6 | Reset only; older and unknown schemas refuse without mutation |
278
+ | Private control (CLI ↔ broker) | 5 | CLI and broker must come from one installation |
279
+ | Federation (`peer-stdio`) | 3 | Exact version and host handshake; no compatibility mode |
280
+ | Consumed Claude peer protocol | 1 | Incompatible records are rejected in isolation |
281
+
282
+ Native provider protocols remain owned by their providers and are validated at
283
+ each use boundary. Version or build metadata is never routing authority.
284
+
285
+ ## Responsibility exclusions
286
+
287
+ The v4 core intentionally has no shell-peer registration/token/mailbox/await
288
+ system, no native Claude advertisement helper, no automatic provider-output
289
+ reply capture, no persisted remote route mirror, no pair/selection graph, no
290
+ dashboard/watch event system, no notice-mode machinery, and no v3 migration
291
+ reader. The responsibilities that remain are endpoint identity, bounded
292
+ delivery, native wake, exact replies, direct federation, status, retirement,
293
+ service supervision, and loopback verification.