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,266 +1,178 @@
1
- # Configuration and provider contracts
2
-
3
- Embassy is configured primarily through environment variables read when each
4
- command starts. This document collects every variable, provider transport
5
- contract, provider runtime rule, and the addressing model. Values are env vars
6
- or CLI flags except for the private `nodes.json` federation inventory described
7
- below.
8
-
9
- ---
10
-
11
- ## Common configuration
12
-
13
- | Variable | Default | Purpose |
14
- | --- | --- | --- |
15
- | `EMBASSY_STATE_DIR` | `$XDG_STATE_HOME/agent-embassy`, or `$HOME/.local/state/agent-embassy` when `XDG_STATE_HOME` is unset | Private state and control socket; an override must be absolute and does not relocate the fixed host-wide lease |
16
- | `EMBASSY_STEERING_ENABLED` | `1` | Global Claude-to-Codex `STEER:` kill switch; set exactly `0` to treat every Claude-to-Codex body as an ordinary Codex-bound queued message; Claude-bound mailbox timing is unchanged |
17
- | `EMBASSY_DELIVERY_NOTICES` | `merged` | Claude sender notice policy: `merged` keeps stalls and folds terminal diagnostics into native status; `verbose` emits both; `quiet` emits no gateway user-frame notices |
18
- | `NO_COLOR` | unset | Not an Embassy setting — the [conventional one](https://no-color.org). Set it to any non-empty value and `embassy status`, `watch`, and `check` print no escape sequences. Colour is emphasis only: stripping it never removes information, and a non-terminal stdout is never coloured whatever this says |
19
-
20
- Before any Embassy client call that talks to the broker, the CLI reads the state
21
- directory and `nodes.json`, then connects to the control socket; grant a sandboxed
22
- Codex task that directory as a writable root, or approve equivalent local access,
23
- and do not relocate state or start a second broker to work around a denial.
24
-
25
- `nodes.json` in `EMBASSY_STATE_DIR` is optional — needed only for federation
26
- across machines. When it is absent at broker boot, Embassy writes it itself,
27
- once: mode 0600, naming this host by its own hostname (the first label before
28
- any dot, lower-cased; `localhost` if that name is not a valid host token),
29
- with an empty peer list. From that point on the file — not the hostname — is
30
- this broker's durable identity: a later hostname change (for example a
31
- network-triggered rename) does not rename it. Federation authority comes only from
32
- this file: it must be a current-user-owned mode-0600 regular file whose exact
33
- object shape is
34
- `{"version":1,"host":"<lowercase-host>","nodes":["<lowercase-ssh-alias>",...]}`.
35
- `host` names this broker; `nodes` contains 0 through 31 unique OpenSSH aliases,
36
- omits `host`, and keeps the federation at 32 total hosts or fewer. Each listed
37
- node is the fixed SSH destination for `embassy peer-stdio`. To adopt
38
- federation later, edit the existing file and add peers to `nodes` — keep
39
- `host` exactly as it already reads; every durable record (routes and
40
- retained bodies) is keyed by that value, so renaming `host` requires
41
- the [private state reset](#private-state-reset) below, the same as any other
42
- identity change.
43
- Removing a peer does not remove its durable mirrors; reset private state before restarting with that peer absent.
44
-
45
- Examples throughout this documentation write aliases as `name@your-host`;
46
- substitute your own host the `hostId` on the broker's ready line — wherever
47
- `your-host` appears. The commands that name a route this machine owns —
48
- `register-codex` (including `--succeeds`), `unregister-codex`, `register-peer`,
49
- `unregister-peer` and `await` refuse an alias naming any other host, and say
50
- which host this machine uses and the file that came from. `send` is not
51
- restricted this way: its `--to` and `--from` may name a federated peer on
52
- another host.
53
-
54
- ### Private state reset
55
-
56
- Version 3 accepts only fresh schema-5 private state; it does not convert or
57
- rewrite older state; the 2.x line's state is refused the same way. Before the reset, use the old running broker's `status`
58
- and delivery lookups to verify that no queued, armed, or accepted work remains
59
- and every delivery has settled. Then:
60
-
61
- 1. Stop the broker.
62
- 2. Move `gateway-state.json` aside so the old ledger remains recoverable.
63
- 3. `nodes.json`, if you use federation, is untouched.
64
- 4. Start the version-3 broker to create fresh state.
65
- 5. Re-register Codex tasks. Claude routes reinstall themselves on first use.
66
-
67
- An old or unknown schema refuses with `GATEWAY_STATE_SCHEMA_UNSUPPORTED` and
68
- does not mutate the state file. There is no conversion command or automatic
69
- recovery path.
70
-
71
- A schema-5 file that still carries a key this line removed — `consentEdges`,
72
- retired along with the permission records it held — refuses with `CORRUPT_GATEWAY_STATE`
73
- instead, because the loader accepts an exact key set rather than ignoring what
74
- it does not recognize. On this upgrade that refusal is expected, not damage:
75
- the file on disk is intact and the same five steps above are the whole
76
- recovery. The state file is never rewritten in place to make it loadable.
77
-
78
- A running broker holds `.gateway-controller.lock` in the state directory,
79
- recording its pid and the machine name at the time it started. What a later
80
- start does with a lock it finds depends only on that pid:
81
-
82
- | Lock found | What the next start does |
83
- | --- | --- |
84
- | Records a **live pid** | Refuses with `GATEWAY_STATE_IN_USE`, and prints the recorded host and pid. Embassy cannot tell a running broker from an unrelated process that inherited the pid number, and that is equally true of a lock written under this machine's earlier name, so it never assumes |
85
- | Records a **dead pid** | Recovers it automatically, whatever machine name the lock records — renaming a machine cannot wedge its own state directory |
86
- | Is **empty**, from a crash between creating the file and writing it | Recovers it automatically; it names no owner to check |
87
- | **Parses, but names no process** (no pid, or one that is not a positive integer) | Refuses with `GATEWAY_STATE_LOCK_UNVERIFIED` and leaves it alone — there is nothing to probe, so nothing may be claimed about it |
88
- | Is **neither empty nor a readable record** | Refuses with `GATEWAY_STATE_LOCK_UNVERIFIED` and leaves it alone |
89
-
90
- Both refusals print a hint naming the lock file: once you have confirmed no
91
- broker is running anywhere, remove `.gateway-controller.lock` and start again.
92
- A recovered lock is renamed to `.gateway-controller.lock.stale-<recovered-at>-<uuid>`
93
- rather than deleted, so a crash stays diagnosable; the timestamp in that name
94
- is when Embassy recovered it, and a later start removes it once that is more
95
- than seven days ago. (The file's own timestamps are the crashed broker's and
96
- can be arbitrarily old, so they are not used.) Never delete a lock while a
97
- broker is running.
98
-
99
- ## Service
100
-
101
- `embassy service install` registers the broker as a per-user macOS launchd
102
- agent instead of a foreground process kept alive by hand:
103
-
104
- - **Agent**: label `com.agent-embassy.broker`, written to
105
- `~/Library/LaunchAgents/com.agent-embassy.broker.plist` (mode 0644).
106
- `RunAtLoad` starts it at login. `KeepAlive` is `{ Crashed: true }`, which
107
- per `launchd.plist(5)` relaunches the job **only** when it died from a
108
- signal typically associated with a crash `SIGSEGV`, `SIGBUS`, `SIGILL`,
109
- `SIGABRT` throttled to at most once every 5 seconds. Nothing else brings
110
- it back: not a clean exit, not a non-zero exit, not a plain `kill`
111
- (`SIGTERM`), and not one of the broker's deliberate boot refusals (an
112
- unsupported state schema, another instance holding the lease). That is on
113
- purpose. A refusal exits once and stays down: run `embassy service status`
114
- and read the log rather than waiting for a relaunch that will not come.
115
- - **Logs**: stdout and stderr are both captured to
116
- `~/Library/Logs/agent-embassy/broker.log` (the log directory is created
117
- mode 0700). **There is no rotation** — Embassy never truncates or rolls this
118
- file, so a long-lived agent's log is yours to prune. Uninstalling leaves it
119
- in place.
120
- - **Environment**: every `EMBASSY_*` variable set in the installing shell,
121
- plus `XDG_STATE_HOME`, is captured into the plist at install time, and the
122
- install prints the captured key names. These are configuration, not
123
- secrets; nothing else no API key, no token, no `PATH` — is copied.
124
- `EMBASSY_STATE_DIR` and `XDG_STATE_HOME` must be absolute. The agent runs
125
- with exactly those captured values for its whole life: changing them in
126
- your shell afterwards has no effect, and the only way to change what the
127
- agent runs with is to run `embassy service install` again.
128
- - **Stop it**: `embassy service uninstall` boots the agent out of launchd and
129
- then waits — polling `launchctl print` every 250 ms for up to 10 seconds —
130
- until launchctl answers that the label is *not found*; only then does it
131
- remove the plist. launchd unloads asynchronously, so `bootout` returning 0
132
- is not proof. Any other answer leaves the plist in place. If launchctl
133
- reported an error, its stderr is quoted. If `bootout` returned 0 and the
134
- label is still there at the end of the wait, there is nothing of
135
- launchctl's to quote — `print` succeeded, and its stdout is never quoted —
136
- so the message says how long it waited instead.
137
- - **Check it**: `embassy service status` reports `loaded`, `not loaded`, or
138
- `unknown`. `unknown` launchctl could not run, or printed something this
139
- version does not recognize is reported as such with launchctl's *stderr*
140
- quoted, and exits non-zero; it is never rendered as "not loaded". Status
141
- also reports whether the plist exists and whether the program the plist
142
- points at is still on disk (a Node binary under a version manager can be
143
- removed out from under an installed agent). `service` is the one command
144
- that reports local paths — its plist, its log, a missing program — because
145
- managing those files is what it does; launchctl's *stdout* is never quoted
146
- anywhere, since a `print` dump contains the agent's environment values.
147
-
148
- Install replaces its own loaded agent: it boots the existing
149
- `com.agent-embassy.broker` out, waits for launchctl to confirm the label is
150
- gone, and only then probes the host-wide instance lease. So re-running
151
- `embassy service install` over a running launchd agent is the supported way to
152
- change its configuration.
153
-
154
- If the lease still cannot be taken after that, install refuses and quotes the
155
- lease's own message verbatim (bounded, like every quoted string on this
156
- path, at 512 bytes). That message is worth reading: `instance-lease`
157
- reports the same condition for about ten situations, most of which are not
158
- another broker at all a symlinked path component under `~/.local/state`, a
159
- non-empty lease root with no ownership marker, a lock file whose mode or owner
160
- drifted. A holder's pid is named only when the recorded pid is verifiably
161
- alive, because the lock record keeps the *last* holder and is routinely stale.
162
- Nothing has been written to disk at this point.
163
-
164
- **Rollback.** A failure after `launchctl bootstrap` a non-zero `bootstrap`,
165
- a `print` that cannot confirm the agent, a failing `kickstart` — is rolled
166
- back, and the error says exactly what the rollback achieved:
167
-
168
- - the new agent is booted out and confirmed gone; then
169
- - if a readable plist was already there, its previous bytes are written back,
170
- and it is re-bootstrapped **only if that agent was loaded when install
171
- started** — the restart is then confirmed by `launchctl print`, not by an
172
- exit code. A plist that was merely sitting on disk, unloaded, is restored
173
- and left unloaded: a failed install must not start a broker you did not
174
- have running, which would take the host lease behind your back;
175
- - if there was no previous plist, the one this install wrote is removed;
176
- - if the previous plist could not be read (it was oversized, or unreadable),
177
- it is **left in place rather than deleted** deleting it would be a silent
178
- uninstall and the error says the plist on disk is the one this install
179
- wrote;
180
- - if the new agent cannot be confirmed unloaded, the plist is **left alone**
181
- and the error says so — a half-installed agent is reported, never hidden.
182
-
183
- The post-install health check is *not* one of those failures. By then the
184
- agent is installed and loaded, and it stays that way: a broker that does not
185
- answer is reported, and the command exits non-zero, but nothing is undone.
186
-
187
- Install waits up to 10 seconds of wall clock for that health check, capping
188
- each attempt at 1 second. If the broker never answers, the command exits
189
- non-zero, names the log file, and reports the last code it observed. If that
190
- last code is a decisive refusal rather than silence — `CONTROL_STATE_UNSAFE`,
191
- `CONTROL_SOCKET_UNSAFE`, `CONTROL_CONNECT_DENIED`, `CONTROL_VERSION_MISMATCH`
192
- — it exits with that code's own class and points at `embassy health`, which
193
- explains it.
194
-
195
- ## Advanced bounds
196
-
197
- These variables retain conservative defaults:
198
-
199
- | Variable | Default |
200
- | --- | ---: |
201
- | `EMBASSY_MAX_ROUTES` | `128` |
202
- | `EMBASSY_EVENT_CAPACITY` / `EMBASSY_EVENT_TTL_MS` | `500` / `86400000` |
203
- | `EMBASSY_DEDUPE_CAPACITY` / `EMBASSY_DEDUPE_TTL_MS` | `2000` / `300000` |
204
- | `EMBASSY_MAX_QUEUE_MESSAGES` / `EMBASSY_MAX_QUEUE_PER_ROUTE` | `100` / `20` |
205
- | `EMBASSY_MAX_IN_FLIGHT` | `16` |
206
- | `EMBASSY_MAX_QUEUE_BYTES` / `EMBASSY_MAX_MESSAGE_BYTES` | `1048576` / `16384` |
207
- | `EMBASSY_MESSAGE_DEADLINE_MS` | `14400000` |
208
- | `EMBASSY_RATE_LIMIT` / `EMBASSY_RATE_WINDOW_MS` | `30` / `60000` |
209
-
210
- `EMBASSY_MAX_ROUTES` accepts 2 through 256. Every value in
211
- this table is validated at startup, and an out-of-range or non-integer setting
212
- fails closed with `INVALID_GATEWAY_CONFIGURATION` rather than being clamped.
213
-
214
- The stall notice is not separately configurable. It fires at
215
- `min(floor(EMBASSY_MESSAGE_DEADLINE_MS / 2), 120000)` milliseconds, so under the
216
- default four-hour deadline a pending delivery is reported at two minutes, not
217
- two hours.
218
-
219
- A CLI initiator receives the full `conv_` token in its result, and every routed recipient receives the same token in the inbound provenance envelope and reply hint. The token is a memory-only participant-scoped locator, not an authority credential: every conversation-addressed `send` rechecks caller identity, conversation membership, and the live route. The token no longer exists after a broker restart; it must likewise never be retried or reconstructed after route retirement or identity replacement.
220
-
221
- The public launcher remains host-local. Under allowlisted SSH federation, each broker serves the exact host identity attested by `nodes.json`. `register-codex` infers that host; the alias (and any `--succeeds` alias) must use the same suffix.
222
-
223
- ## Claude Code's own setting: `crossSessionInbound`
224
-
225
- `crossSessionInbound` is Claude Code's native setting for cross-session
226
- messaging: it decides whether a Claude session accepts, holds, or refuses
227
- messages arriving from another session. Embassy needs it enabled on any
228
- session used as a Codex-to-Claude destination, and it cannot override
229
- that decision. Configure it in Claude Code, not in Embassy.
230
-
231
- This is the one prerequisite you must actively toggle, and it is the most
232
- common first-run failure — because it fails *late*. A send to a discovered
233
- session is accepted and installs its route without consulting Claude's native
234
- inbound policy; the refusal appears only when the delivery reaches the Claude
235
- end. If the session appears in `embassy status` and the send was accepted but
236
- nothing arrives, check `crossSessionInbound` on the destination session before
237
- suspecting the route.
238
-
239
- ## Provider and runtime contract
240
-
241
- Embassy routes three providers: Claude over peer protocol 1, Codex over the managed App Server, and universal shell peers over the private control socket. A build or version fact never grants or withholds routing authority.
242
-
243
- Runtime is best effort: the OS boundary plus the exact owned route/session identity authorizes an attempt. The current per-operation transport, strict consumed wire fields, and correlated operation determine the result. Interface drift or a missing optional provider becomes provider-local degraded/offline health and an exact safe code; it does not create a compatibility tier or block unrelated providers.
244
-
245
- Only unsafe OS evidence for Embassy-owned or executed artifacts and Embassy callback, control, or state paths—such as an unsafe lease or state, swapped binary, ownership/path/symlink mismatch, or invalid generation—refuses broker startup. The Claude-owned external sessions registry root is read-side identity evidence: an unsafe UID or mode degrades only Claude with a loud observation while the broker and other providers remain available. Claude still requires native `peerProtocol: 1` per session record: a record that declares any other value is rejected in isolation and included in bounded rejection evidence without stopping the broker or hiding other usable sessions.
246
-
247
- Runtime parsing remains strict on every known registry field, frame, and response; unknown top-level Claude registry fields are ignored because Embassy never consumes them. The Claude connector row in public status carries optional bounded `registry` observations: `entriesScanned`, `parseableRecords`, monotonic `parseableRecordSeenSinceBoot`, bounded per-safe-code `rejected`, and `rejectedCodesOmitted`. `embassy status` reports the same evidence: if Claude is running but no record with parseable required fields has been observed since broker start, its registry layout may have changed.
248
-
249
- The managed Codex installation is resolved by exact verified path; a `codex` elsewhere on `PATH` is neither used nor modified. Claude registry and callback roots are derived from the verified current OS user; no Claude launcher or configuration file is read. Version strings, when present, are bounded diagnostic metadata only.
250
-
251
- ## Addressing
252
-
253
- Claude sessions are addressed by their current `name@host` or by a user-supplied native session UUID. The UUID is the stable logical identity; the current name is a live lookup alias. After a rename, the old name stops resolving immediately while an installed UUID-bound route continues to work under the new name. A rename becomes visible at the session's next status transition — typically its next turn boundary — because Claude Code rewrites the session's registry record on those transitions, not at the moment of the rename; Embassy reflects the record, never the rename itself.
254
-
255
- Names, old names, PIDs, registry paths, process generations, and socket generations never become alternate identity keys. Embassy refuses to guess when two live sessions share a current name: the shared *name* is refused with `PEER_ALIAS_COLLISION`, while each session stays reachable by its own UUID.
256
-
257
- Across a federated link, reachability is narrower than it is locally. A peer node addresses only the routes its neighbour published in its catalog, and a Claude session appears there only once it has a local route — that is, once it has sent a message or been sent one on its own host. The destination never installs a route on a handoff: an unmirrored sender or an unrouted target is refused, not created. To make a Claude session addressable from a peer node, use it locally once first.
258
-
259
- Codex routes use an explicit `codex-*` alias and the task's inherited thread identity. The private thread ID is never accepted as a command-line argument or printed. Registration performs no App Server operation. Every delivery opens and attests a fresh managed transport, initializes it, resumes the exact task with history excluded, and authorizes the body write once. App Server and broker restarts do not change logical route authority or require re-registration. A current unavailable or unobservable task reports an operation-local safe code while the registration remains.
260
-
261
- Shell routes use `peer-*` aliases and a `peer_` token minted at registration.
262
- The broker persists only its UID/alias/token hash route handle, never the raw
263
- token. Authenticated calls accept the token on the first stdin line with
264
- `--token-stdin`; body-bearing calls use the remaining bytes as the body.
265
- `--emit-env` remains optional for stable-shell harnesses. There is no PID
266
- binding, token file, Keychain entry, daemon, or alternate persistence path.
1
+ # Configuration
2
+
3
+ Embassy has one broker per login user and machine. Configuration is inherited
4
+ when the broker starts; changing it requires a broker restart. Provider build
5
+ or version metadata never grants routing authority.
6
+
7
+ ## State and node inventory
8
+
9
+ `EMBASSY_STATE_DIR` may set an absolute state directory. Otherwise Embassy
10
+ uses `$XDG_STATE_HOME/agent-embassy`, or
11
+ `$HOME/.local/state/agent-embassy` when `XDG_STATE_HOME` is unset.
12
+
13
+ The directory must be owned by the current user, mode 0700, and must not be a
14
+ symbolic link. `gateway-state.json`, `nodes.json`, and other broker-owned files
15
+ are mode 0600. The private control socket is inside this directory. A Codex or
16
+ Claude sandbox must be able to read and write the directory for commands that
17
+ contact the broker. If access was expected, also verify the configured state
18
+ directory belongs to this user; do not start a second broker to work around an
19
+ access denial.
20
+
21
+ `nodes.json` is the static host and federation inventory. A machine without
22
+ peers uses an empty list:
23
+
24
+ ```json
25
+ {"version":1,"host":"studio","nodes":[]}
26
+ ```
27
+
28
+ If the file is absent on the first single-machine boot, Embassy derives a
29
+ lower-case host from the machine's short hostname (or `localhost` when that is
30
+ not a valid token) and atomically installs a version-1 file with `nodes:[]`.
31
+ This default is transient until the file is written; a present file is never
32
+ rewritten. Create it before first boot when choosing an explicit host name or
33
+ configuring federation.
34
+
35
+ `host` is the canonical local host name used in aliases. `nodes` is the list
36
+ of directly reachable Embassy hosts. Entries must be unique, must not include
37
+ the local host, and must use lower-case host tokens. There is no environment
38
+ fallback, dynamic discovery, or multi-hop routing.
39
+
40
+ Local route aliases end in the inventory's exact host. `register-codex` and
41
+ `retire` refuse a different host. Remote routes are resolved through the owner
42
+ listed in `nodes`; they can be retired only on that owner.
43
+
44
+ ## Delivery settings
45
+
46
+ All integer settings are decimal integers. Invalid or inconsistent values stop
47
+ the broker with `INVALID_GATEWAY_CONFIGURATION`.
48
+
49
+ | Variable | Default | Accepted range |
50
+ |---|---:|---:|
51
+ | `EMBASSY_MAX_ROUTES` | 128 | 2–128 |
52
+ | `EMBASSY_MAX_QUEUE_MESSAGES` | 100 | 1–100 |
53
+ | `EMBASSY_MAX_QUEUE_PER_ROUTE` | 20 | 1–20 and no greater than the total queue |
54
+ | `EMBASSY_MAX_IN_FLIGHT` | 16 | 1–16 and no greater than the total queue |
55
+ | `EMBASSY_MAX_MESSAGE_BYTES` | 16384 | 1–16384 |
56
+ | `EMBASSY_MAX_QUEUE_BYTES` | 1048576 | 1024–1048576 and no smaller than one message |
57
+ | `EMBASSY_MESSAGE_DEADLINE_MS` | 14400000 | 1000–86400000 |
58
+ | `EMBASSY_RATE_LIMIT` | 30 | 1–10000 |
59
+ | `EMBASSY_RATE_WINDOW_MS` | 60000 | 1000–3600000 |
60
+ | `EMBASSY_EVENT_CAPACITY` | 500 | 10–500 |
61
+ | `EMBASSY_EVENT_TTL_MS` | 86400000 | 60000–604800000 |
62
+
63
+ The event settings bound recent terminal delivery and retirement evidence; they
64
+ do not enable a general activity journal. The state also has a fixed retained
65
+ byte budget.
66
+
67
+ `EMBASSY_STEERING_ENABLED` is `1` by default. Set exactly `0` to treat a
68
+ leading `STEER:` as an ordinary Claude-to-Codex message. No other value is
69
+ accepted.
70
+
71
+ ## Provider contracts
72
+
73
+ ### Claude Code
74
+
75
+ Embassy reads Claude's current-user session registry and connects to the exact
76
+ session's private Unix socket. Only interactive and background sessions using
77
+ Claude peer protocol 1 are eligible. A malformed or incompatible record is
78
+ rejected in isolation. Discovery and workspace/path evidence are checked again
79
+ for each native write.
80
+
81
+ A Claude caller is identified from its inherited absolute
82
+ `CLAUDE_CODE_MESSAGING_SOCKET`. The path is never accepted as a CLI option,
83
+ printed, or persisted. Claude sessions receive natively. They send in one CLI
84
+ step with `embassy send`; Embassy does not publish helper agents into Claude's
85
+ native agent list.
86
+
87
+ ### Codex CLI
88
+
89
+ A Codex task registers itself with `embassy register-codex --alias ...` using
90
+ its inherited `CODEX_THREAD_ID`. The ID is not a command argument or public
91
+ output. Registration is a logical state change and performs no App Server I/O.
92
+ Each delivery independently attests the current App Server interface and exact
93
+ task before authorization, resumes that task without retaining history, and
94
+ writes through a fresh operation.
95
+
96
+ `register-codex --succeeds <old-alias>` atomically retires a predecessor and
97
+ installs the caller. It never reanchors pending work to a new identity.
98
+
99
+ ## SSH federation
100
+
101
+ For each configured remote node Embassy runs the fixed system SSH client in
102
+ batch mode with forwarding and local commands disabled. Authentication is the
103
+ user's SSH configuration. The remote command is `embassy peer-stdio`; the two
104
+ installations must speak federation peer protocol 3.
105
+
106
+ Any plain same-user SSH login that can run that command is sufficient; Embassy
107
+ does not require a forced command, per-node key, or special SSH environment.
108
+ The peer claims its logical host in `initialize`, and the receiving broker
109
+ requires that host to be in its `nodes.json` peer list. The SSH login is
110
+ trusted, so the claim is trusted too. Keep the local `host` correct when
111
+ copying configuration: a wrong allowed host label can misattribute origin.
112
+
113
+ The non-interactive SSH environment must resolve the intended `embassy`
114
+ installation. Verify that environment with `which -a embassy`. Federation
115
+ does not accept a password, private key, host override, or arbitrary SSH
116
+ argument from Embassy configuration.
117
+
118
+ Remote endpoint catalogs are bounded memory-only caches. The owner is queried
119
+ again for exact identity resolution. A handoff is one correlated write, the
120
+ destination persists its queue before acceptance, and an uncertain result is
121
+ never replayed.
122
+
123
+ `embassy refresh` observes configured catalogs in parallel with local Claude
124
+ discovery. A successful observation replaces that node's bounded display rows
125
+ and timestamp. A failed observation retains its last rows and records
126
+ `PEER_TUNNEL_UNAVAILABLE`. `embassy status` reads that snapshot without SSH or
127
+ provider I/O. Display is capped at 128 remote rows across all nodes; exact and
128
+ named routing always queries the owner and is unaffected by display truncation.
129
+
130
+ ## launchd service
131
+
132
+ ```sh
133
+ embassy service install
134
+ embassy service status
135
+ embassy service uninstall
136
+ ```
137
+
138
+ The service is a per-user launchd agent. Installation captures the absolute
139
+ Node executable and Embassy CLI file, plus every nonempty `EMBASSY_*` value and
140
+ `XDG_STATE_HOME` from the installing shell. It captures no other environment
141
+ entry and no arbitrary `PATH`; do not put secrets in an `EMBASSY_*` variable.
142
+ Re-run installation after moving or replacing the package.
143
+ `service status` reports when a recorded program path no longer exists.
144
+
145
+ The plist uses `RunAtLoad` and `KeepAlive` with only `Crashed: true`. A verified
146
+ `SIGABRT` crash relaunches it. A clean exit, nonzero boot refusal, ordinary
147
+ `SIGTERM`, or a deliberate
148
+ `kill -9` leaves the service not running. Use `embassy service status` to
149
+ observe that state and start or reinstall it deliberately.
150
+
151
+ The foreground alternative is `embassy serve`. It does not daemonize or open
152
+ a network listener. Both forms acquire the same fixed host-wide advisory lease
153
+ before provider setup, so only one broker can run.
154
+
155
+ ## Private state reset
156
+
157
+ Version 4 accepts only schema-6 `gateway-state.json`. It deliberately contains
158
+ no v3 converter or compatibility reader. An older or unknown schema refuses
159
+ with `GATEWAY_STATE_SCHEMA_UNSUPPORTED`; invalid schema-6 bytes refuse with
160
+ `CORRUPT_GATEWAY_STATE`. Refusal does not mutate the installed file.
161
+
162
+ Reset procedure:
163
+
164
+ 1. With the old matching binary, inspect delivery state and settle or abandon
165
+ work deliberately.
166
+ 2. Stop the broker and confirm the service is not running.
167
+ 3. Copy the old `gateway-state.json` to an operator-owned backup.
168
+ 4. Move the installed state file aside. Keep the valid `nodes.json`.
169
+ 5. Start the v4 broker; it creates fresh schema-6 state.
170
+ 6. Re-register Codex tasks. Claude endpoints are recorded on discovery/use.
171
+
172
+ A reset abandons unsettled work and invalidates delivery tokens and
173
+ conversation references. Rollback means stopping v4 and restoring both the old
174
+ binary and its untouched old state. Never hand-edit either schema.
175
+ After v4 has accepted work, the old backup does not contain that work. Before
176
+ rolling back, inspect and drain or explicitly abandon v4 deliveries, and keep
177
+ a separate backup of the v4 state. Restoring v3 is not a rollback of those
178
+ delivery effects and must never silently discard unsettled v4 work.