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.
- package/CHANGELOG.md +42 -0
- package/CONTRIBUTING.md +92 -200
- package/README.md +159 -138
- package/SECURITY.md +191 -335
- package/dist/src/gateway/broker-check.d.ts +26 -0
- package/dist/src/gateway/broker-check.js +89 -0
- package/dist/src/gateway/broker-check.js.map +1 -0
- package/dist/src/gateway/broker-control.d.ts +72 -0
- package/dist/src/gateway/broker-control.js +164 -0
- package/dist/src/gateway/broker-control.js.map +1 -0
- package/dist/src/gateway/broker.d.ts +117 -0
- package/dist/src/gateway/broker.js +186 -0
- package/dist/src/gateway/broker.js.map +1 -0
- package/dist/src/gateway/claude-peer.d.ts +1 -124
- package/dist/src/gateway/claude-peer.js +62 -1053
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/codex-stateless-transport.d.ts +0 -12
- package/dist/src/gateway/codex-stateless-transport.js +8 -142
- package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
- package/dist/src/gateway/config.d.ts +2 -6
- package/dist/src/gateway/config.js +17 -27
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/coordinator.d.ts +57 -0
- package/dist/src/gateway/coordinator.js +217 -0
- package/dist/src/gateway/coordinator.js.map +1 -0
- package/dist/src/gateway/core-cli.d.ts +15 -0
- package/dist/src/gateway/core-cli.js +282 -0
- package/dist/src/gateway/core-cli.js.map +1 -0
- package/dist/src/gateway/core-service-command.d.ts +16 -0
- package/dist/src/gateway/core-service-command.js +71 -0
- package/dist/src/gateway/core-service-command.js.map +1 -0
- package/dist/src/gateway/endpoint-directory.d.ts +33 -0
- package/dist/src/gateway/endpoint-directory.js +223 -0
- package/dist/src/gateway/endpoint-directory.js.map +1 -0
- package/dist/src/gateway/federation.d.ts +89 -0
- package/dist/src/gateway/federation.js +632 -0
- package/dist/src/gateway/federation.js.map +1 -0
- package/dist/src/gateway/ledger-codec.d.ts +3 -0
- package/dist/src/gateway/ledger-codec.js +159 -0
- package/dist/src/gateway/ledger-codec.js.map +1 -0
- package/dist/src/gateway/ledger.d.ts +138 -0
- package/dist/src/gateway/ledger.js +319 -0
- package/dist/src/gateway/ledger.js.map +1 -0
- package/dist/src/gateway/local-control.d.ts +23 -0
- package/dist/src/gateway/local-control.js +489 -0
- package/dist/src/gateway/local-control.js.map +1 -0
- package/dist/src/gateway/native-destinations.d.ts +34 -0
- package/dist/src/gateway/native-destinations.js +272 -0
- package/dist/src/gateway/native-destinations.js.map +1 -0
- package/dist/src/gateway/owned-state.d.ts +57 -0
- package/dist/src/gateway/owned-state.js +398 -0
- package/dist/src/gateway/owned-state.js.map +1 -0
- package/dist/src/gateway/provenance-envelope.d.ts +3 -3
- package/dist/src/gateway/provenance-envelope.js +3 -23
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/runtime.d.ts +41 -0
- package/dist/src/gateway/runtime.js +175 -0
- package/dist/src/gateway/runtime.js.map +1 -0
- package/docs/CONFIGURATION.md +178 -266
- package/docs/DELIVERY.md +133 -82
- package/docs/GATEWAY-ARCHITECTURE.md +276 -943
- package/package.json +5 -5
- package/skills/embassy-peer/SKILL.md +38 -194
- package/skills/embassy-peer/agents/openai.yaml +2 -2
- package/dist/src/gateway/claude-helper-client.d.ts +0 -2
- package/dist/src/gateway/claude-helper-client.js +0 -2
- package/dist/src/gateway/claude-helper-client.js.map +0 -1
- package/dist/src/gateway/claude-helper-protocol.d.ts +0 -117
- package/dist/src/gateway/claude-helper-protocol.js +0 -122
- package/dist/src/gateway/claude-helper-protocol.js.map +0 -1
- package/dist/src/gateway/claude-helper-supervisor.d.ts +0 -87
- package/dist/src/gateway/claude-helper-supervisor.js +0 -347
- package/dist/src/gateway/claude-helper-supervisor.js.map +0 -1
- package/dist/src/gateway/claude-helper.d.ts +0 -1
- package/dist/src/gateway/claude-helper.js +0 -252
- package/dist/src/gateway/claude-helper.js.map +0 -1
- package/dist/src/gateway/cli.d.ts +0 -57
- package/dist/src/gateway/cli.js +0 -1503
- package/dist/src/gateway/cli.js.map +0 -1
- package/dist/src/gateway/codex-socket-holder.d.ts +0 -26
- package/dist/src/gateway/codex-socket-holder.js +0 -76
- package/dist/src/gateway/codex-socket-holder.js.map +0 -1
- package/dist/src/gateway/control.d.ts +0 -261
- package/dist/src/gateway/control.js +0 -903
- package/dist/src/gateway/control.js.map +0 -1
- package/dist/src/gateway/peer-client.d.ts +0 -58
- package/dist/src/gateway/peer-client.js +0 -217
- package/dist/src/gateway/peer-client.js.map +0 -1
- package/dist/src/gateway/peer-mailbox.d.ts +0 -78
- package/dist/src/gateway/peer-mailbox.js +0 -168
- package/dist/src/gateway/peer-mailbox.js.map +0 -1
- package/dist/src/gateway/peer-protocol.d.ts +0 -105
- package/dist/src/gateway/peer-protocol.js +0 -70
- package/dist/src/gateway/peer-protocol.js.map +0 -1
- package/dist/src/gateway/peer-stdio.d.ts +0 -23
- package/dist/src/gateway/peer-stdio.js +0 -100
- package/dist/src/gateway/peer-stdio.js.map +0 -1
- package/dist/src/gateway/providers.d.ts +0 -163
- package/dist/src/gateway/providers.js +0 -972
- package/dist/src/gateway/providers.js.map +0 -1
- package/dist/src/gateway/server.d.ts +0 -47
- package/dist/src/gateway/server.js +0 -219
- package/dist/src/gateway/server.js.map +0 -1
- package/dist/src/gateway/service.d.ts +0 -373
- package/dist/src/gateway/service.js +0 -2274
- package/dist/src/gateway/service.js.map +0 -1
- package/dist/src/gateway/status-view.d.ts +0 -167
- package/dist/src/gateway/status-view.js +0 -488
- package/dist/src/gateway/status-view.js.map +0 -1
- package/dist/src/gateway/store.d.ts +0 -185
- package/dist/src/gateway/store.js +0 -2359
- package/dist/src/gateway/store.js.map +0 -1
- package/dist/src/gateway/types.d.ts +0 -528
- package/dist/src/gateway/types.js +0 -341
- package/dist/src/gateway/types.js.map +0 -1
|
@@ -1,960 +1,293 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Claude
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
|
814
|
-
|
|
|
815
|
-
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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.
|