agent-embassy 3.1.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 (112) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +92 -202
  3. package/README.md +157 -155
  4. package/SECURITY.md +191 -348
  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 +61 -1052
  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 -949
  62. package/package.json +5 -5
  63. package/skills/embassy-peer/SKILL.md +38 -196
  64. package/skills/embassy-peer/agents/openai.yaml +2 -2
  65. package/dist/src/gateway/claude-helper-protocol.d.ts +0 -117
  66. package/dist/src/gateway/claude-helper-protocol.js +0 -122
  67. package/dist/src/gateway/claude-helper-protocol.js.map +0 -1
  68. package/dist/src/gateway/claude-helper-supervisor.d.ts +0 -87
  69. package/dist/src/gateway/claude-helper-supervisor.js +0 -374
  70. package/dist/src/gateway/claude-helper-supervisor.js.map +0 -1
  71. package/dist/src/gateway/claude-helper.d.ts +0 -1
  72. package/dist/src/gateway/claude-helper.js +0 -252
  73. package/dist/src/gateway/claude-helper.js.map +0 -1
  74. package/dist/src/gateway/cli.d.ts +0 -56
  75. package/dist/src/gateway/cli.js +0 -1532
  76. package/dist/src/gateway/cli.js.map +0 -1
  77. package/dist/src/gateway/codex-socket-holder.d.ts +0 -26
  78. package/dist/src/gateway/codex-socket-holder.js +0 -76
  79. package/dist/src/gateway/codex-socket-holder.js.map +0 -1
  80. package/dist/src/gateway/control.d.ts +0 -279
  81. package/dist/src/gateway/control.js +0 -933
  82. package/dist/src/gateway/control.js.map +0 -1
  83. package/dist/src/gateway/peer-client.d.ts +0 -58
  84. package/dist/src/gateway/peer-client.js +0 -217
  85. package/dist/src/gateway/peer-client.js.map +0 -1
  86. package/dist/src/gateway/peer-mailbox.d.ts +0 -78
  87. package/dist/src/gateway/peer-mailbox.js +0 -168
  88. package/dist/src/gateway/peer-mailbox.js.map +0 -1
  89. package/dist/src/gateway/peer-protocol.d.ts +0 -110
  90. package/dist/src/gateway/peer-protocol.js +0 -79
  91. package/dist/src/gateway/peer-protocol.js.map +0 -1
  92. package/dist/src/gateway/peer-stdio.d.ts +0 -23
  93. package/dist/src/gateway/peer-stdio.js +0 -100
  94. package/dist/src/gateway/peer-stdio.js.map +0 -1
  95. package/dist/src/gateway/providers.d.ts +0 -164
  96. package/dist/src/gateway/providers.js +0 -975
  97. package/dist/src/gateway/providers.js.map +0 -1
  98. package/dist/src/gateway/server.d.ts +0 -46
  99. package/dist/src/gateway/server.js +0 -216
  100. package/dist/src/gateway/server.js.map +0 -1
  101. package/dist/src/gateway/service.d.ts +0 -371
  102. package/dist/src/gateway/service.js +0 -2335
  103. package/dist/src/gateway/service.js.map +0 -1
  104. package/dist/src/gateway/status-view.d.ts +0 -119
  105. package/dist/src/gateway/status-view.js +0 -484
  106. package/dist/src/gateway/status-view.js.map +0 -1
  107. package/dist/src/gateway/store.d.ts +0 -188
  108. package/dist/src/gateway/store.js +0 -2360
  109. package/dist/src/gateway/store.js.map +0 -1
  110. package/dist/src/gateway/types.d.ts +0 -541
  111. package/dist/src/gateway/types.js +0 -332
  112. package/dist/src/gateway/types.js.map +0 -1
package/README.md CHANGED
@@ -1,210 +1,212 @@
1
- <p align="center">
2
- <img src="https://raw.githubusercontent.com/YuanpingSong/embassy/main/assets/social-preview.png" alt="Embassy — a local gateway for messaging between Claude Code sessions and Codex CLI tasks" width="720">
3
- </p>
4
-
5
1
  # Embassy
6
2
 
7
- **A local embassy for your AI agents.**
3
+ Embassy lets live Claude Code sessions and Codex CLI tasks message one another
4
+ by name, on one Mac or across user-owned Macs reached through SSH. The broker
5
+ wakes the receiving agent through its native interface; agents do not poll.
6
+ Claude→Claude, Claude→Codex, Codex→Claude, and Codex→Codex all use the same
7
+ command and receipt model.
8
8
 
9
- [![CI](https://github.com/YuanpingSong/embassy/actions/workflows/ci.yml/badge.svg)](https://github.com/YuanpingSong/embassy/actions/workflows/ci.yml)
10
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
11
- [![Node 20](https://img.shields.io/badge/node-%E2%89%A520-43853d)](package.json)
9
+ The core is deliberately small: one private ledger, one delivery coordinator,
10
+ and three write adapters (Claude socket, Codex App Server operation, and SSH
11
+ handoff). Aliases are lookup names. Opaque endpoint IDs are the routing
12
+ identity, so a rename or replacement never silently retargets queued work.
12
13
 
13
- Embassy is a small local broker that lets a [Claude Code](https://code.claude.com) session and a [Codex](https://chatgpt.com/codex) CLI task on the same Mac message each other by name, with a receipt for every delivery. It is personal software — one person, one macOS account, agents you already trust to run as that user — and an unofficial project, not affiliated with or endorsed by Anthropic or OpenAI.
14
+ ## Requirements
14
15
 
15
- ## Install
16
+ - macOS and Node.js 20 or newer.
17
+ - Claude Code and/or Codex CLI installed for the agents you use.
18
+ - A private `nodes.json` when choosing an explicit host name or federating;
19
+ first single-machine boot creates one from the short hostname.
20
+ - Key-based, non-interactive SSH between configured machines when federating.
16
21
 
17
- ```bash
18
- npm install -g agent-embassy
19
- embassy service install
20
- ```
22
+ Install one copy with one package manager and verify what the shell resolves:
21
23
 
22
- Use one global package manager for Embassy (npm or pnpm), not both. Check for shadowing installations in each launch environment:
23
-
24
- ```bash
24
+ ```sh
25
+ npm install -g agent-embassy
25
26
  which -a embassy
26
27
  embassy --version
27
- ssh <node> 'which -a embassy; embassy --version'
28
28
  ```
29
29
 
30
- For a configured federation peer, `<node>` is its OpenSSH Host alias; the SSH
31
- check uses federation's non-interactive launch environment. Update or remove a
32
- shadowing install through its owning package manager; updating npm does not update pnpm.
33
- The launchd plist pins the absolute Node and CLI paths used by `embassy service install`,
34
- not the next shell's PATH. After removing an installation, rerun `embassy service install`
35
- from the retained installation. `embassy service status` reports a plist program path
36
- that is no longer on disk.
37
-
38
- `service install` runs the broker as your user's launchd agent: it starts at login, restarts after a crash, and logs to `~/Library/Logs/agent-embassy/broker.log`. Prefer a process you start by hand? Run `embassy serve` in a terminal and leave it running instead. You need macOS, Node.js 20+, Claude Code (with its [`crossSessionInbound`](docs/CONFIGURATION.md#claude-codes-own-setting-crosssessioninbound) setting enabled on any session that should receive mail), and Codex CLI with the managed standalone App Server — the official installer `curl -fsSL https://chatgpt.com/codex/install.sh | sh`, then `codex app-server daemon start`. From source: `git clone https://github.com/YuanpingSong/embassy && cd embassy && npm ci && npm run build && npm link`.
30
+ The launchd service records the absolute installation path used by
31
+ `embassy service install`. After replacing or removing that installation, run
32
+ the install command again.
39
33
 
40
- Every alias below ends in `@your-host`. Replace `your-host` with this machine's host the `hostId` on the broker's ready line (in the log, under launchd); name the wrong host and the CLI says which one this machine uses.
34
+ The agent starts at login and uses launchd's crash-only keepalive policy. A
35
+ verified `SIGABRT` crash relaunches it. A clean exit, boot refusal, `SIGTERM`,
36
+ or deliberate `kill -9` leaves it stopped; inspect `embassy service status`
37
+ and install/start it deliberately rather than assuming every signal restarts it.
41
38
 
42
- ## Your first message in four commands
39
+ ## Quickstart
43
40
 
44
- **1. Register the Codex task.** Ask your Codex agent to run this as a shell step in its current turn; it must run inside the task so it inherits the task's identity. You should see `"accepted":true`.
41
+ For an explicit host name or federation, create `nodes.json` before starting
42
+ the broker. `host` is this machine's name; `nodes` lists directly reachable
43
+ Embassy hosts.
45
44
 
46
- ```bash
47
- embassy register-codex --alias codex-reviewer@your-host
45
+ ```json
46
+ {"version":1,"host":"studio","nodes":[]}
48
47
  ```
49
48
 
50
- **2. Read what the broker sees.** The `sessions` block lists your live Claude Code sessions by name. If it is empty, start a Claude Code session and run `embassy refresh`, then look again.
49
+ The file lives at
50
+ `$XDG_STATE_HOME/agent-embassy/nodes.json`, or
51
+ `~/.local/state/agent-embassy/nodes.json` when `XDG_STATE_HOME` is unset. It
52
+ must be owned by the current user, mode 0600, inside the private mode-0700 state
53
+ directory. If it is absent on first single-machine boot, Embassy derives a
54
+ lower-case name from the short hostname and atomically writes the equivalent
55
+ empty-node file. It never rewrites a present inventory.
51
56
 
52
- ```bash
53
- embassy status
54
- ```
57
+ Install the supervised broker:
55
58
 
56
- **3. Send.** From the Codex task, body on stdin, to the name `status` showed. The session's route installs on its first use — there is no step between reading a name and messaging it. You get a `conv_` conversation token and a `dlv_` delivery token back.
57
-
58
- ```bash
59
- embassy send --from codex-reviewer@your-host --to advisor@your-host --expects-reply <<'MSG'
60
- Please review the current approach and identify the main risk.
61
- MSG
59
+ ```sh
60
+ embassy service install
61
+ embassy health
62
62
  ```
63
63
 
64
- **4. Answer by conversation.** Every delivered body arrives inside one broker-owned `<cross-session-message>` frame naming the attested sender (on a federated hop, the sender is named by the sending node — see [SECURITY.md](SECURITY.md)), and its first `<embassy-reply-hint>` carries the full `conv_` token and this exact command; the recipient runs it with the answer on stdin.
64
+ A Codex task registers itself from that task's inherited identity. Embassy
65
+ never accepts or prints the task ID:
65
66
 
66
- ```bash
67
- embassy send --conversation conv_<token> --from advisor@your-host <<'MSG'
68
- The main risk is the double-write window; gate it behind the flag.
69
- MSG
67
+ ```sh
68
+ embassy register-codex --alias codex-reviewer@studio
70
69
  ```
71
70
 
72
- The same `send` runs from a Claude Code session, inheriting that session's identity, and the other direction needs no Embassy command at all: a Claude session finds `codex-reviewer` with its native `ListAgents` tool and messages it with `SendMessage`. `--to <session-uuid>` addresses a Claude session by its UUID. When two live sessions share a name, the send is refused with `PEER_ALIAS_COLLISION` rather than delivered to a guess — rename one and retry. A Claude session's reply to a `--expects-reply` send is routed back to the Codex task by itself.
71
+ Claude sessions are discovered and recorded by exact native identity when a
72
+ Claude caller sends or when a named Claude target is resolved. No helper or
73
+ native advertisement process is installed.
73
74
 
74
- ## After any Claude Code or Codex CLI update
75
+ From either a Claude session or a registered Codex task, send the body on
76
+ stdin. The sender is inferred from the calling session:
75
77
 
76
- ```bash
77
- embassy check
78
+ ```sh
79
+ printf '%s\n' 'Please review the change.' |
80
+ embassy send --to claude-reviewer@studio
78
81
  ```
79
82
 
80
- `check` is the upstream-drift tripwire. It registers an ephemeral shell peer of its own — its attributable rows and bodies are omitted from durable state and its route from the federation catalog, but aggregate counters still advance. Its native advertisement to Claude sessions is released with it; a broker that dies mid-check can leave that record until the alias is next registered and released. The check sends one marked message through the ordinary send path to the most recently observed registered Codex task (observed within ten minutes; a task never observed is not eligible), waits for `delivered`, awaits the echo on its own mailbox, releases the registration, and prints every hop with its timing; any failing hop exits non-zero with the safe code that explains it. `--to <alias>` picks a target and `--timeout <s>` bounds each wait. The peer answers because the shipped [skill](skills/embassy-peer/SKILL.md) tells it to — a message whose verified sender starts with `peer-check-` and whose body starts `[embassy check` is echoed in one line; either half alone is ordinary untrusted text. It costs the peer one model turn, so it is a deliberate command, not something to poll.
81
-
82
- ```text
83
- embassy check 50066f60 → codex-reviewer@this-mac
83
+ The recipient gets a provenance envelope and a conversation-bound reply
84
+ command:
84
85
 
85
- ok register peer-check-b0c963c9@this-mac (ephemeral, 2 min) 6 ms
86
- ok send accepted, conversation …89abcdef 15 ms
87
- ok delivered the peer's transport accepted it 256 ms
88
- ok reply codex-reviewer@this-mac echoed 50066f60 1407 ms
89
- ok cleanup temporary check identity removed
90
-
91
- check passed
86
+ ```sh
87
+ printf '%s\n' 'Review complete.' |
88
+ embassy send --conversation conv_example
92
89
  ```
93
90
 
94
- The operator copies the repo-shipped, packaged skill where each agent discovers skills — Codex tasks can then be prompted with `$embassy-peer`, and Claude Code finds it as a user skill:
95
-
96
- ```bash
97
- cp -R "$(npm root -g)/agent-embassy/skills/embassy-peer" ~/.codex/skills/
98
- cp -R "$(npm root -g)/agent-embassy/skills/embassy-peer" ~/.claude/skills/
99
- ```
91
+ Conversation references are identity-bound, are not aliases, and may survive a
92
+ broker restart while their retained ledger row and both exact endpoints remain
93
+ valid. They stop resolving after retirement, replacement, expiry, eviction, or
94
+ a state reset.
100
95
 
101
- For a pnpm installation, substitute `pnpm root -g` for `npm root -g` in both copy commands. Embassy does not install the skill automatically.
96
+ ## Delivery
102
97
 
103
- ## Shell-peer fallback
98
+ One native wake can carry a bounded FIFO batch, so a busy recipient catches up
99
+ without one wake per queued message. Every message keeps its own source,
100
+ destination, conversation, deadline, and receipt. Capacity and expiry remain
101
+ visible per message.
104
102
 
105
- Any local harness that can run the CLI can join as a `peer-*` route — no plugin, daemon, PID binding, token file, or Keychain entry — and it is the supported fallback channel when native Codex delivery is unavailable. `embassy register-peer --alias peer-reviewer@your-host` prints a `peer_` token exactly once; keep it in the agent's context, never in argv, and pass it as the first stdin line of every later peer command with `--token-stdin` (the remaining stdin bytes are the body; `--emit-env` exists only for a harness that really does keep one stable shell). `embassy await --alias peer-reviewer@your-host --token-stdin` long-polls the mailbox in bounded 30-second iterations — one waiter per route, 16 in total — writes the complete framed message to stdout, and acknowledges only after stdout has flushed. A missing acknowledgement settles `unconfirmed`; uncertainty after the write was armed settles `ambiguous`; neither is replayed.
103
+ The durable write phases are `queued`, `reserved`, `armed`, `accepted`, and
104
+ `terminal`. Work known not to have been written may return to the queue. An
105
+ uncertain armed or accepted write is never replayed.
106
106
 
107
- ## Federation
107
+ An exact leading `STEER:` from Claude to Codex targets the active accepted
108
+ Codex operation at its next safe tool-call boundary. It never interrupts a
109
+ generation. If that boundary is cleanly unavailable, the message remains in
110
+ the ordinary bounded queue. The global kill switch is
111
+ `EMBASSY_STEERING_ENABLED=0`.
108
112
 
109
- `nodes.json` in the state directory is optional. The broker writes it on first boot, naming this machine by its own hostname with an empty peer list, and from then on that file — not the hostname — is the broker's durable identity. To reach a second machine, add its OpenSSH `Host` alias to `nodes`, keep `host` exactly as written, and restart: the brokers exchange body-free route catalogs and destination-owned handoffs over `ssh <node> embassy peer-stdio`, your SSH configuration owns keys and users, and Embassy opens no listener. Remote routes appear as `alias@host` mirrors. Details in [Configuration](docs/CONFIGURATION.md).
113
+ See [Delivery semantics](docs/DELIVERY.md) for the phase and receipt contract.
110
114
 
111
- ## Observability
115
+ ## Multiple machines
112
116
 
113
- `embassy status` is the one command for "what is going on". It is read-only — it never rescans; `embassy refresh` does — and prints prose in a terminal. Piped or with `--json`, it emits `{ok,command,result}` with the snapshot under `result`: use `embassy status --json | jq .result.routes`. `--recent <n>` (1–100, default 10) sizes the message list.
117
+ List direct peers in each machine's `nodes.json`. The local broker launches:
114
118
 
115
119
  ```text
116
- embassy 3.1.0 broker ok · pid 41213 · snapshot just now
117
- state dir /Users/you/.local/state/agent-embassy
118
- sessions scanned 3s ago
119
-
120
- connectors
121
- claude ok
122
- codex ok
123
-
124
- sessions
125
- session state route last seen
126
- advisor@this-mac busy routed 3s ago
127
-
128
- routes
129
- alias provider state queue last seen
130
- advisor@this-mac claude busy 2 discovered 3s
131
- codex-reviewer@this-mac codex idle 0 12s ago
132
-
133
- recent (3 of 3)
134
- 12s ago advisor@this-mac → codex-reviewer@this-mac queued
135
- 2m ago codex-reviewer@this-mac → advisor@this-mac delivered 210 ms
136
- The risk is the double-write window; I would gate it behind…
137
- 5m ago advisor@this-mac → codex-reviewer@this-mac delivered 61 ms
138
- Please review the migration risk before the freeze.
120
+ /usr/bin/ssh <node> embassy peer-stdio
139
121
  ```
140
122
 
141
- When something is wrong it says the safe code **and** what to do about it, and one quiet corner never makes the whole broker look broken:
142
-
143
- ```text
144
- embassy 3.1.0 broker degraded · pid 41213 · snapshot just now
145
- state dir /Users/you/.local/state/agent-embassy
146
- sessions scanned 3s ago
147
-
148
- connectors
149
- claude ok
150
- codex degraded MANAGED_CODEX_UNAVAILABLE
151
- Either a process outside Embassy holds the managed Codex control socket — quit it — or the managed App Server standalone layout is missing, which starting the daemon alone does not create: follow the Codex prerequisite in the README (the official installer, then the daemon).
152
- peer-release@this-mac stale (token or await loop gone)
153
- 2 message(s) waiting: run `embassy await --alias peer-release@this-mac --token-stdin` in the shell holding its token, or `embassy unregister-peer --alias peer-release@this-mac --token-stdin`.
154
-
155
- sessions
156
- session state route last seen
157
- advisor@this-mac busy routed 3s ago
158
-
159
- routes
160
- alias provider state queue last seen
161
- advisor@this-mac claude busy 2 discovered 3s
162
- codex-reviewer@this-mac codex stale 1 30m ago
163
- peer-release@this-mac peer idle 2 never
164
- codex-reviewer@this-mac: That Codex task is gone. Run `embassy register-codex --alias <new-alias> --succeeds <this alias>` from the new task, or `embassy unregister-codex --alias <this alias>` from the old one.
165
-
166
- recent (3 of 3)
167
- 12s ago advisor@this-mac → codex-reviewer@this-mac queued
168
- 2m ago codex-reviewer@this-mac advisor@this-mac delivered 210 ms
169
- The risk is the double-write window; I would gate it behind…
170
- 5m ago advisor@this-mac → codex-reviewer@this-mac delivered 61 ms
171
- Please review the migration risk before the freeze.
172
-
173
- alerts
174
- PEER_TUNNEL_UNAVAILABLE studio 45s ago
175
- The SSH tunnel to that node is down; check the node is reachable and its broker is running.
123
+ The plain same-user SSH login is the trust boundary. The peer's claimed host
124
+ must be listed in the destination's `nodes.json`; that claim is trusted, not
125
+ independently bound to a physical machine. No forced command or dedicated
126
+ per-node key is required. Configure each node's host label accurately.
127
+ Federation has no listener or multi-hop routing. The destination owns the
128
+ queue and trusts the peer's source identity, so first contact does not wait
129
+ for a destination catalog poll. Catalogs are bounded memory-only observations,
130
+ never routing authority.
131
+
132
+ `embassy refresh` observes local Claude sessions and every configured SSH
133
+ catalog in parallel. `status` performs no provider or network I/O: it shows the
134
+ last per-node catalog rows and observation time, retains the last rows when a
135
+ later refresh fails, and labels that node `PEER_TUNNEL_UNAVAILABLE`. At most 128
136
+ remote rows are displayed; truncation is explicit. Named and exact sends still
137
+ ask the owner directly.
138
+
139
+ ## Operations
140
+
141
+ ```sh
142
+ embassy status
143
+ embassy status --json
144
+ embassy refresh
145
+ embassy delivery-status --token dlv_example
146
+ embassy wait-delivery --token dlv_example
147
+ embassy retire --alias codex-reviewer@studio
148
+ embassy check
149
+ embassy service status
150
+ embassy serve # foreground alternative
176
151
  ```
177
152
 
178
- `embassy watch` tails the broker until Ctrl-C: each new message and each settlement at most once (`accepted → delivered (61 ms)`), stamped with the local time, plus route installs and retirements as a secondary line; `--json` streams the same events as JSONL. At most once, not exactly once — a transition that passes entirely between two one-second polls is never seen, and rows that left the retained window before the tail reached them are announced as a one-line note. `embassy --help` lists all eighteen commands.
153
+ `status` reports the broker ledger, queue depth, recent message outcomes,
154
+ retirements, each local route's last native operation, and the last bounded SSH
155
+ catalog observation. It does not claim that an idle provider is ready.
179
156
 
180
- ## Safety in one minute
157
+ Machine output is one closed JSON line shaped as
158
+ `{"ok":true,"command":"status","result":{...}}`; route rows are therefore at
159
+ `.result.routes`. A terminal `embassy status` renders the same body for a
160
+ person, while `--json` keeps the envelope.
181
161
 
182
- - **The OS boundary is the permission.** Reaching the same-UID private control socket on this host — or on a host in your `nodes.json` — plus an exact alias is what lets a process message. There is no separate grant to hand out or revoke, because none could stop code already running as your user; a Claude session's route installs on its first use, and a Codex task registers explicitly only because Embassy must record its inherited identity. `embassy serve` binds no TCP port and serves no HTTP.
183
- - **Retirement is an explicit OS-boundary exception to owner unregister.** `embassy retire --alias codex-reviewer@your-host` requires no route credential and atomically removes any local Claude, Codex, or shell-peer route; it accepts no token, force, or remote option, refuses a federated mirror with `FEDERATED_ROUTE_READ_ONLY`, and prints settlement counts `{cancelled,ambiguous,unconfirmed}`.
184
- - **Every routed body carries the provenance envelope naming the sender.** It is a marker for the receiving model, not a signature: treat every delivered body as untrusted input. Native permissions stay native — Embassy answers no Codex approval and cannot override Claude's `crossSessionInbound`.
185
- - **Bounded by design.** Queues, bodies (16 KiB), conversations, rate windows, and deadlines are bounded, and an ambiguous write is never replayed. `delivered` means the provider boundary was crossed — toward Codex the App Server accepted the turn; toward Claude the native mailbox write completed (`transport_written`) — never that a model read it; `unconfirmed` and `ambiguous` mean the evidence is missing and are terminal. Claude-bound bodies are written immediately, busy or idle; Codex-bound bodies queue until the task is idle, and only an exact leading `STEER:` from Claude may enter the active turn at its next tool-call boundary.
186
- - **Bodies are retained locally and yours.** Message bodies and their opaque delivery token/status persist in the broker's private mode-0600 state under bounded retention, and what `status` prints is a status snapshot that includes retained message bodies: `embassy status` shows retained bodies; treat its output as sensitive as the messages themselves.
162
+ `check` is a broker-only loopback through the real ledger and coordinator. It
163
+ proves local control, persistence, routing, and receipt handling without
164
+ contacting a live Claude or Codex agent. It is not a provider-readiness test.
187
165
 
188
- See [SECURITY.md](SECURITY.md) for the boundary in depth and how to report a vulnerability.
166
+ `retire` removes one local endpoint identity. Queued and reserved work is
167
+ cancelled, armed work becomes ambiguous, and accepted work becomes unconfirmed.
168
+ Remote endpoints must be retired on their owning host.
169
+ If departed sessions share a name, retire one exactly with
170
+ `embassy retire --endpoint <public-id>` using its opaque ID from status.
189
171
 
190
- **Tested with** (cutover drill, 2026-09-03): Claude Code 2.1.259 and Codex CLI 0.152.0 on macOS, Node 22.23, on two machines — a Claude Code session ↔ Codex CLI task round trip in both directions, `embassy check` (register → send → delivered → reply → cleanup), and a cross-machine shell-peer handoff over ssh.
172
+ ## Safety
191
173
 
192
- ## Upgrading from 2.x
174
+ - The broker uses one private Unix socket and mode-0600 state inside a
175
+ mode-0700 directory. It does not listen on TCP or HTTP.
176
+ - Native task/session IDs, socket paths, credentials, transcripts, and raw
177
+ provider frames never appear in public output.
178
+ - Every native write is authorized against the exact current endpoint after
179
+ preparation. Names are never silently resolved again during an attempt.
180
+ - SSH uses the fixed system binary, batch mode, no forwarding, and no shell.
181
+ - `health` and `check` describe broker infrastructure, not model readiness or
182
+ comprehension.
193
183
 
194
- Version 3 accepts only fresh private state. Stop Embassy, move `gateway-state.json` aside, start 3.0, and re-register Codex tasks; Claude routes reinstall themselves on first use, and `nodes.json` is written for you if absent. The full [private state reset](docs/CONFIGURATION.md#private-state-reset) and every change are in the [changelog](CHANGELOG.md).
184
+ See [Security](SECURITY.md), [Configuration](docs/CONFIGURATION.md), and
185
+ [Architecture](docs/GATEWAY-ARCHITECTURE.md).
195
186
 
196
- ## Documentation
187
+ ## Upgrading to 4.x
188
+
189
+ Version 4 accepts only fresh private state schema 6 and private control
190
+ protocol 5. It does not migrate or read v3 state. Before upgrading, use the old
191
+ binary to inspect and settle work, stop the broker, preserve a backup of the
192
+ old state, then reset `gateway-state.json`. Keep `nodes.json`.
193
+
194
+ The rollback boundary is the preserved old state plus its matching old binary.
195
+ Do not point an old binary at schema-6 state or a v4 binary at old state.
196
+
197
+ ## Development
198
+
199
+ Routine tests use test-owned directories, fake Claude sockets, fake App Server
200
+ transports, and fake SSH processes:
201
+
202
+ ```sh
203
+ npm ci
204
+ TMPDIR=/tmp npm run check
205
+ ```
197
206
 
198
- | Document | What it covers |
199
- | --- | --- |
200
- | [Architecture](docs/GATEWAY-ARCHITECTURE.md) | Topology, adapters, control plane, federation, protocol versions, and the OS-boundary permission model |
201
- | [Delivery](docs/DELIVERY.md) | Delivery semantics, tokens, settlement states, and retry rules |
202
- | [Configuration](docs/CONFIGURATION.md) | Environment variables, `nodes.json`, the launchd service, and addressing |
203
- | [Security policy](SECURITY.md) | The boundary in depth, and how to report a vulnerability |
204
- | [Contributing](CONTRIBUTING.md) | Where changes go, and how to run the deterministic suite |
205
- | [Changelog](CHANGELOG.md) | What each release contains |
206
- | [Agent skill](skills/embassy-peer/SKILL.md) | The workflow an agent follows to operate Embassy |
207
+ No routine test connects a live provider or SSH host. See
208
+ [CONTRIBUTING.md](CONTRIBUTING.md).
207
209
 
208
210
  ## License
209
211
 
210
- [MIT](LICENSE)
212
+ MIT