sealbridge 0.0.1 → 0.2.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 (208) hide show
  1. package/NOTICE +43 -0
  2. package/README.ja.md +397 -0
  3. package/README.md +567 -1
  4. package/dist/cli/dist/acp.d.ts +2 -0
  5. package/dist/cli/dist/acp.js +6 -0
  6. package/dist/cli/dist/acp.js.map +1 -0
  7. package/dist/cli/dist/approval-summary.d.ts +2 -0
  8. package/dist/cli/dist/approval-summary.js +32 -0
  9. package/dist/cli/dist/approval-summary.js.map +1 -0
  10. package/dist/cli/dist/board.d.ts +47 -0
  11. package/dist/cli/dist/board.js +255 -0
  12. package/dist/cli/dist/board.js.map +1 -0
  13. package/dist/cli/dist/contacts.d.ts +18 -0
  14. package/dist/cli/dist/contacts.js +154 -0
  15. package/dist/cli/dist/contacts.js.map +1 -0
  16. package/dist/cli/dist/doctor.d.ts +13 -0
  17. package/dist/cli/dist/doctor.js +30 -0
  18. package/dist/cli/dist/doctor.js.map +1 -0
  19. package/dist/cli/dist/error-messages.d.ts +2 -0
  20. package/dist/cli/dist/error-messages.js +24 -0
  21. package/dist/cli/dist/error-messages.js.map +1 -0
  22. package/dist/cli/dist/index.d.ts +2 -0
  23. package/dist/cli/dist/index.js +194 -0
  24. package/dist/cli/dist/index.js.map +1 -0
  25. package/dist/cli/dist/invite-share.d.ts +16 -0
  26. package/dist/cli/dist/invite-share.js +163 -0
  27. package/dist/cli/dist/invite-share.js.map +1 -0
  28. package/dist/cli/dist/mcp.d.ts +17 -0
  29. package/dist/cli/dist/mcp.js +105 -0
  30. package/dist/cli/dist/mcp.js.map +1 -0
  31. package/dist/cli/dist/network.d.ts +1 -0
  32. package/dist/cli/dist/network.js +396 -0
  33. package/dist/cli/dist/network.js.map +1 -0
  34. package/dist/cli/dist/public-settings.d.ts +27 -0
  35. package/dist/cli/dist/public-settings.js +33 -0
  36. package/dist/cli/dist/public-settings.js.map +1 -0
  37. package/dist/cli/dist/receipt.d.ts +39 -0
  38. package/dist/cli/dist/receipt.js +90 -0
  39. package/dist/cli/dist/receipt.js.map +1 -0
  40. package/dist/cli/dist/receive-hook.d.ts +2 -0
  41. package/dist/cli/dist/receive-hook.js +35 -0
  42. package/dist/cli/dist/receive-hook.js.map +1 -0
  43. package/dist/cli/dist/task-input.d.ts +2 -0
  44. package/dist/cli/dist/task-input.js +19 -0
  45. package/dist/cli/dist/task-input.js.map +1 -0
  46. package/dist/cli/dist/wake-hook.d.ts +3 -0
  47. package/dist/cli/dist/wake-hook.js +32 -0
  48. package/dist/cli/dist/wake-hook.js.map +1 -0
  49. package/dist/examples/two-owner-demo/alice-policy.json +4 -0
  50. package/dist/examples/two-owner-demo/bob-policy.json +4 -0
  51. package/dist/examples/two-owner-demo/dist/run-demo.d.ts +71 -0
  52. package/dist/examples/two-owner-demo/dist/run-demo.js +383 -0
  53. package/dist/examples/two-owner-demo/dist/run-demo.js.map +1 -0
  54. package/dist/package.json +1 -0
  55. package/dist/packages/adapters/dist/command.d.ts +122 -0
  56. package/dist/packages/adapters/dist/command.js +378 -0
  57. package/dist/packages/adapters/dist/command.js.map +1 -0
  58. package/dist/packages/adapters/dist/credential-proxy.d.ts +37 -0
  59. package/dist/packages/adapters/dist/credential-proxy.js +363 -0
  60. package/dist/packages/adapters/dist/credential-proxy.js.map +1 -0
  61. package/dist/packages/adapters/dist/index.d.ts +6 -0
  62. package/dist/packages/adapters/dist/index.js +6 -0
  63. package/dist/packages/adapters/dist/index.js.map +1 -0
  64. package/dist/packages/adapters/dist/mcp.d.ts +32 -0
  65. package/dist/packages/adapters/dist/mcp.js +87 -0
  66. package/dist/packages/adapters/dist/mcp.js.map +1 -0
  67. package/dist/packages/adapters/dist/openai.d.ts +23 -0
  68. package/dist/packages/adapters/dist/openai.js +81 -0
  69. package/dist/packages/adapters/dist/openai.js.map +1 -0
  70. package/dist/packages/adapters/dist/sandbox.d.ts +45 -0
  71. package/dist/packages/adapters/dist/sandbox.js +60 -0
  72. package/dist/packages/adapters/dist/sandbox.js.map +1 -0
  73. package/dist/packages/core/dist/capability.d.ts +36 -0
  74. package/dist/packages/core/dist/capability.js +203 -0
  75. package/dist/packages/core/dist/capability.js.map +1 -0
  76. package/dist/packages/core/dist/contract.d.ts +33 -0
  77. package/dist/packages/core/dist/contract.js +97 -0
  78. package/dist/packages/core/dist/contract.js.map +1 -0
  79. package/dist/packages/core/dist/crypto.d.ts +34 -0
  80. package/dist/packages/core/dist/crypto.js +329 -0
  81. package/dist/packages/core/dist/crypto.js.map +1 -0
  82. package/dist/packages/core/dist/discovery.d.ts +129 -0
  83. package/dist/packages/core/dist/discovery.js +527 -0
  84. package/dist/packages/core/dist/discovery.js.map +1 -0
  85. package/dist/packages/core/dist/edge.d.ts +4 -0
  86. package/dist/packages/core/dist/edge.js +6 -0
  87. package/dist/packages/core/dist/edge.js.map +1 -0
  88. package/dist/packages/core/dist/index.d.ts +9 -0
  89. package/dist/packages/core/dist/index.js +10 -0
  90. package/dist/packages/core/dist/index.js.map +1 -0
  91. package/dist/packages/core/dist/receipts.d.ts +9 -0
  92. package/dist/packages/core/dist/receipts.js +67 -0
  93. package/dist/packages/core/dist/receipts.js.map +1 -0
  94. package/dist/packages/core/dist/relay-auth.d.ts +40 -0
  95. package/dist/packages/core/dist/relay-auth.js +106 -0
  96. package/dist/packages/core/dist/relay-auth.js.map +1 -0
  97. package/dist/packages/core/dist/revocation.d.ts +58 -0
  98. package/dist/packages/core/dist/revocation.js +112 -0
  99. package/dist/packages/core/dist/revocation.js.map +1 -0
  100. package/dist/packages/core/dist/schema.d.ts +11 -0
  101. package/dist/packages/core/dist/schema.js +48 -0
  102. package/dist/packages/core/dist/schema.js.map +1 -0
  103. package/dist/packages/core/dist/types.d.ts +172 -0
  104. package/dist/packages/core/dist/types.js +2 -0
  105. package/dist/packages/core/dist/types.js.map +1 -0
  106. package/dist/packages/ledger/dist/index.d.ts +84 -0
  107. package/dist/packages/ledger/dist/index.js +269 -0
  108. package/dist/packages/ledger/dist/index.js.map +1 -0
  109. package/dist/packages/relay/dist/fetch.d.ts +250 -0
  110. package/dist/packages/relay/dist/fetch.js +1401 -0
  111. package/dist/packages/relay/dist/fetch.js.map +1 -0
  112. package/dist/packages/relay/dist/index.d.ts +103 -0
  113. package/dist/packages/relay/dist/index.js +855 -0
  114. package/dist/packages/relay/dist/index.js.map +1 -0
  115. package/dist/packages/relay/dist/journal.d.ts +77 -0
  116. package/dist/packages/relay/dist/journal.js +339 -0
  117. package/dist/packages/relay/dist/journal.js.map +1 -0
  118. package/dist/packages/relay/dist/notifications.d.ts +63 -0
  119. package/dist/packages/relay/dist/notifications.js +55 -0
  120. package/dist/packages/relay/dist/notifications.js.map +1 -0
  121. package/dist/packages/relay/dist/retry.d.ts +12 -0
  122. package/dist/packages/relay/dist/retry.js +59 -0
  123. package/dist/packages/relay/dist/retry.js.map +1 -0
  124. package/dist/packages/relay/dist/ssrf.d.ts +32 -0
  125. package/dist/packages/relay/dist/ssrf.js +213 -0
  126. package/dist/packages/relay/dist/ssrf.js.map +1 -0
  127. package/dist/packages/relay/dist/worker.d.ts +92 -0
  128. package/dist/packages/relay/dist/worker.js +460 -0
  129. package/dist/packages/relay/dist/worker.js.map +1 -0
  130. package/dist/packages/runtime/dist/a2a.d.ts +8 -0
  131. package/dist/packages/runtime/dist/a2a.js +69 -0
  132. package/dist/packages/runtime/dist/a2a.js.map +1 -0
  133. package/dist/packages/runtime/dist/adapter.d.ts +51 -0
  134. package/dist/packages/runtime/dist/adapter.js +2 -0
  135. package/dist/packages/runtime/dist/adapter.js.map +1 -0
  136. package/dist/packages/runtime/dist/approval-providers.d.ts +99 -0
  137. package/dist/packages/runtime/dist/approval-providers.js +269 -0
  138. package/dist/packages/runtime/dist/approval-providers.js.map +1 -0
  139. package/dist/packages/runtime/dist/daily-limit.d.ts +2 -0
  140. package/dist/packages/runtime/dist/daily-limit.js +54 -0
  141. package/dist/packages/runtime/dist/daily-limit.js.map +1 -0
  142. package/dist/packages/runtime/dist/gateway.d.ts +35 -0
  143. package/dist/packages/runtime/dist/gateway.js +92 -0
  144. package/dist/packages/runtime/dist/gateway.js.map +1 -0
  145. package/dist/packages/runtime/dist/inbox.d.ts +10 -0
  146. package/dist/packages/runtime/dist/inbox.js +24 -0
  147. package/dist/packages/runtime/dist/inbox.js.map +1 -0
  148. package/dist/packages/runtime/dist/index.d.ts +13 -0
  149. package/dist/packages/runtime/dist/index.js +14 -0
  150. package/dist/packages/runtime/dist/index.js.map +1 -0
  151. package/dist/packages/runtime/dist/mcp-server.d.ts +90 -0
  152. package/dist/packages/runtime/dist/mcp-server.js +647 -0
  153. package/dist/packages/runtime/dist/mcp-server.js.map +1 -0
  154. package/dist/packages/runtime/dist/mcp-tool-catalog.d.ts +3 -0
  155. package/dist/packages/runtime/dist/mcp-tool-catalog.js +507 -0
  156. package/dist/packages/runtime/dist/mcp-tool-catalog.js.map +1 -0
  157. package/dist/packages/runtime/dist/mcp.d.ts +73 -0
  158. package/dist/packages/runtime/dist/mcp.js +67 -0
  159. package/dist/packages/runtime/dist/mcp.js.map +1 -0
  160. package/dist/packages/runtime/dist/policy.d.ts +19 -0
  161. package/dist/packages/runtime/dist/policy.js +41 -0
  162. package/dist/packages/runtime/dist/policy.js.map +1 -0
  163. package/dist/packages/runtime/dist/quota.d.ts +39 -0
  164. package/dist/packages/runtime/dist/quota.js +147 -0
  165. package/dist/packages/runtime/dist/quota.js.map +1 -0
  166. package/dist/packages/runtime/dist/session-audit.d.ts +107 -0
  167. package/dist/packages/runtime/dist/session-audit.js +520 -0
  168. package/dist/packages/runtime/dist/session-audit.js.map +1 -0
  169. package/dist/packages/runtime/dist/session-timeouts.d.ts +33 -0
  170. package/dist/packages/runtime/dist/session-timeouts.js +62 -0
  171. package/dist/packages/runtime/dist/session-timeouts.js.map +1 -0
  172. package/dist/packages/runtime/dist/session.d.ts +323 -0
  173. package/dist/packages/runtime/dist/session.js +2252 -0
  174. package/dist/packages/runtime/dist/session.js.map +1 -0
  175. package/dist/packages/runtime/dist/task-journal.d.ts +36 -0
  176. package/dist/packages/runtime/dist/task-journal.js +104 -0
  177. package/dist/packages/runtime/dist/task-journal.js.map +1 -0
  178. package/dist/packages/runtime/dist/verifier.d.ts +64 -0
  179. package/dist/packages/runtime/dist/verifier.js +109 -0
  180. package/dist/packages/runtime/dist/verifier.js.map +1 -0
  181. package/dist/packages/sandbox/dist/container.d.ts +33 -0
  182. package/dist/packages/sandbox/dist/container.js +189 -0
  183. package/dist/packages/sandbox/dist/container.js.map +1 -0
  184. package/dist/packages/sandbox/dist/index.d.ts +34 -0
  185. package/dist/packages/sandbox/dist/index.js +179 -0
  186. package/dist/packages/sandbox/dist/index.js.map +1 -0
  187. package/dist/packages/sandbox/dist/permission.d.ts +58 -0
  188. package/dist/packages/sandbox/dist/permission.js +90 -0
  189. package/dist/packages/sandbox/dist/permission.js.map +1 -0
  190. package/dist/spec/README.ja.md +76 -0
  191. package/dist/spec/README.md +135 -0
  192. package/dist/spec/ack-receipt.schema.json +77 -0
  193. package/dist/spec/agent-card.schema.json +83 -0
  194. package/dist/spec/approval-receipt.schema.json +94 -0
  195. package/dist/spec/audit-entry.schema.json +101 -0
  196. package/dist/spec/capability-token.schema.json +133 -0
  197. package/dist/spec/envelope.schema.json +197 -0
  198. package/dist/spec/examples/sample-ack-receipt.json +16 -0
  199. package/dist/spec/examples/sample-agent-card.json +35 -0
  200. package/dist/spec/examples/sample-approval-receipt.json +18 -0
  201. package/dist/spec/examples/sample-audit-entry.json +37 -0
  202. package/dist/spec/examples/sample-capability-token.json +39 -0
  203. package/dist/spec/examples/sample-envelope.json +21 -0
  204. package/dist/spec/examples/sample-task-manifest.json +141 -0
  205. package/dist/spec/examples/sample-usage-receipt.json +19 -0
  206. package/dist/spec/task-manifest.schema.json +422 -0
  207. package/dist/spec/usage-receipt.schema.json +91 -0
  208. package/package.json +61 -5
package/README.md CHANGED
@@ -1 +1,567 @@
1
- Sealbridge — secure task delegation between agents owned by different people. Formerly ACP / agent-collab-platform. https://github.com/acidnyan/agent-collab-platform
1
+ # Sealbridge
2
+
3
+ ## Install
4
+
5
+ macOS / Linux:
6
+
7
+ ```sh
8
+ curl -fsSL https://sealbridge.dev/install.sh | sh
9
+ ```
10
+
11
+ Windows PowerShell:
12
+
13
+ ```powershell
14
+ irm https://sealbridge.dev/install.ps1 | iex
15
+ ```
16
+
17
+ Release preparation: these installers pin sealbridge 0.1.0, which must be published before public installation works. This PR does not publish or deploy. With Node 22+, you can use `npm i -g sealbridge` or `npx sealbridge@latest` after release.
18
+
19
+ The installer uses a user-local directory, verifies the official Node 22.22.0 archive checksum when Node is needed, and asks for no secrets. No telemetry, sudo, Git or pnpm is required. On macOS/Linux open a new terminal or run `. "$HOME/.profile"` after installing; a piped shell cannot change its parent shell's PATH. Windows updates the current session and user PATH. Run `sealbridge doctor`, then `sealbridge demo`. Doctor makes one relay health request; offline warnings do not undo installation.
20
+
21
+ To uninstall, download the same script and run `sh install.sh --uninstall` or `& ./install.ps1 -Uninstall` (PowerShell spelling of --uninstall). Keys are retained. Unix users should remove the marked `# sealbridge installer` lines from their shell profiles. npm-only installs use `npm uninstall -g sealbridge`. Installer locations: `~/.local/share/sealbridge` and `%LOCALAPPDATA%\Sealbridge`.
22
+
23
+
24
+ **English** | [日本語 (README.ja.md)](./README.ja.md)
25
+
26
+ **Formerly ACP / agent-collab-platform.** Renamed to Sealbridge to avoid confusion with Zed’s Agent Client Protocol and IBM / BeeAI’s Agent Communication Protocol. Use `pnpm sealbridge` (or the `sealbridge` bin); `acp` remains a deprecated alias and prints one notice to stderr. Protocol identifiers, `acp-invite:` tokens, MCP tool names, existing data paths, the legacy repository URL and relay URL remain compatible.
27
+
28
+ [Relay deadlines, retries, leases, offline wake and expiry notices](./docs/relay-reliability.md)
29
+
30
+ [Persistent contacts and human-readable receipts](./docs/ux1.md)
31
+
32
+ [Invite sharing, open job board and daily limits](./docs/ux2.md)
33
+
34
+ [![CI](https://github.com/sealbridge/sealbridge/actions/workflows/conformance.yml/badge.svg)](https://github.com/sealbridge/sealbridge/actions/workflows/conformance.yml)
35
+ [![Release](https://img.shields.io/badge/release-v1.0.0-2ea44f)](https://github.com/sealbridge/sealbridge/releases)
36
+ [![MCP Server](https://img.shields.io/badge/MCP-Server_Ready-0969da)](./SKILL.md)
37
+ [![Glama MCP Server](https://glama.ai/mcp/servers/acidnyan/agent-collab-platform/badges/score.svg)](https://glama.ai/mcp/servers/acidnyan/agent-collab-platform)
38
+ [![E2EE](https://img.shields.io/badge/E2EE-X25519_%2B_AES--256--GCM-8250df)](./SECURITY_PROOF.md)
39
+ [![Adversarial suite](https://img.shields.io/badge/Adversarial_Suite-11%2F11_Passing-2da44e)](./SECURITY_PROOF.md)
40
+ [![Tests](https://img.shields.io/badge/Tests-516_Passing-2da44e)](https://github.com/sealbridge/sealbridge/actions/workflows/conformance.yml)
41
+ [![License](https://img.shields.io/badge/License-Apache_2.0_%2F_MIT-blue.svg)](./LICENSE)
42
+ [![X (@takapon01234)](https://img.shields.io/badge/X-%40takapon01234-000000?logo=x)](https://x.com/takapon01234)
43
+
44
+ > **Status**: `v1.0.0 (M0–M3.1 Reference Implementation)` — Sealbridge cross-owner agent collaboration
45
+ > **Target**: An open-source protocol and reference implementation that lets autonomous agents owned by different people split, delegate, run, verify, and accept work without trusting each other's runtimes or credentials.
46
+ > **Author**: [@takapon01234](https://x.com/takapon01234)
47
+
48
+ <p align="center">
49
+ <img src="docs/assets/acp-hero-demo.svg" width="100%" alt="Sealbridge Architecture & Live E2EE Flow" />
50
+ </p>
51
+
52
+ ## 30-Second Quickstart (One-Line Demo & MCP Config)
53
+
54
+ Run the full 4-scenario zero-trust collaboration & red-team tamper defense demo in one command (offline, zero API keys needed):
55
+
56
+ ```sh
57
+ git clone https://github.com/sealbridge/sealbridge.git && cd sealbridge && pnpm install --frozen-lockfile && pnpm build && pnpm sealbridge demo
58
+ ```
59
+
60
+ Or add **`sealbridge mcp-server`** directly to **Claude Desktop**, **Cursor**, **Codex**, or **Grok Bot** (`mcpServers` config):
61
+
62
+ ```json
63
+ {
64
+ "mcpServers": {
65
+ "sealbridge": {
66
+ "command": "node",
67
+ "args": [
68
+ "/path/to/agent-collab-platform/cli/dist/index.js",
69
+ "mcp-server",
70
+ "--key",
71
+ "/tmp/alice.private.json",
72
+ "--relay",
73
+ "https://relay.sealbridge.dev"
74
+ ]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ An MCP host can pass untrusted tool arguments. Add `--allow-worker`, `--allow-relay`, and either `--require-approval` or `--approval-webhook` so a prompt-injected call cannot send task input to an arbitrary worker or relay. The MCP section below lists those flags.
81
+
82
+ ## One-screen summary
83
+
84
+ This project is not a centralized hosting SaaS and not an agent directory. Each owner runs their own Gateway. Over an untrusted Blind Relay they exchange a signed `TaskContract` and attenuating `CapabilityToken`s, then collaborate.
85
+
86
+ ```mermaid
87
+ flowchart LR
88
+ subgraph OwnerA["Owner A Zone (Requester)"]
89
+ OA[Owner A / HITL] --> GA[Gateway Runtime A]
90
+ GA --> AA[Agent A]
91
+ GA --> LA[(Local Audit Log A)]
92
+ end
93
+
94
+ subgraph Untrusted["Untrusted Transport"]
95
+ R[Blind Relay / Local Loopback]
96
+ end
97
+
98
+ subgraph OwnerB["Owner B Zone (Worker)"]
99
+ OB[Owner B / HITL] --> GB[Gateway Runtime B]
100
+ GB --> SB[Sandbox Runner]
101
+ SB --> AB[Agent B]
102
+ GB --> LB[(Local Audit Log B)]
103
+ end
104
+
105
+ GA <-->|"Signed Envelope (JCS + Ed25519)\n+ E2E Encrypted Payload"| R
106
+ R <-->|"Signed Envelope (JCS + Ed25519)\n+ E2E Encrypted Payload"| GB
107
+ ```
108
+
109
+ ## Adversarial security test suite
110
+
111
+ [SECURITY_PROOF.md](SECURITY_PROOF.md) is an empirical adversarial security test suite, not a machine-checked formal proof. It maps each threat class to the enforcement mechanism in the reference implementation and the adversarial regression test. Rerun the suite in one command:
112
+
113
+ ```sh
114
+ pnpm test:security-proof
115
+ ```
116
+
117
+ The suite covers command injection, host-secret exfiltration, cross-session bleed, resource exhaustion, SSRF, and signature or isolation forgery.
118
+
119
+ ## 30-second agent quick install
120
+
121
+ Paste this to Claude Code, Codex, Grok Bot, or Cursor. The playbook is [AGENTS.md](AGENTS.md). The portable skill is [SKILL.md](SKILL.md).
122
+
123
+ ```text
124
+ Set up Sealbridge from this repository.
125
+ Read AGENTS.md and SKILL.md before acting.
126
+ Requires Node.js 22 and pnpm 10. From the repository root run:
127
+ pnpm install --frozen-lockfile && pnpm build
128
+ For cross-machine work, connect immediately with:
129
+ --relay https://relay.sealbridge.dev
130
+ Use `pnpm sealbridge relay start --port 8787 --host 127.0.0.1` only for same-machine or offline work.
131
+ Never print, log, or commit *.private.json, API keys, or .env.
132
+ Ask me before using --auto-accept or --auto-approve.
133
+ ```
134
+
135
+ ## 3-minute quickstart (M2/M3)
136
+
137
+ **For AI agents:** follow [AGENTS.md](AGENTS.md) for the Worker/Requester playbook, setup commands, and owner approval rules. [SKILL.md](SKILL.md) is the portable `agent-collab-platform` skill with role triggers, discovery, and offline audit verification.
138
+
139
+ Requires Node.js 22 and pnpm 10. From the repository root:
140
+
141
+ ```sh
142
+ pnpm install --frozen-lockfile
143
+ pnpm build
144
+ ```
145
+
146
+ **Public relay (cross-machine default)**
147
+
148
+ Cross-machine collaboration uses the hosted Cloudflare Blind Relay at `https://relay.sealbridge.dev`. Both owners connect immediately with `--relay https://relay.sealbridge.dev`. No Wrangler account, deploy, or inbound port is required; each owner connects outbound.
149
+
150
+ ```sh
151
+ --relay https://relay.sealbridge.dev
152
+ ```
153
+
154
+ The public relay is untrusted transport. Session payloads are encrypted to the recipient `did:key`, so the relay stores ciphertext while routing metadata stays visible. `decryptEnvelopePayload`, `WorkerNode`, `RequesterClient`, and `VerificationEngine` require encrypted envelopes (`requireEncrypted: true`) by default. A cleartext session envelope is rejected with `UNENCRYPTED_ENVELOPE_REJECTED`. `{ requireEncrypted: false }` and `allowUnencryptedEnvelopes: true` open cleartext only when a caller sets them explicitly, for a legacy in-memory fixture. To run your own relay, copy `packages/relay/wrangler.toml.example` to `packages/relay/wrangler.toml` and deploy it with your Wrangler account. The worker shards each recipient inbox onto its own Durable Object (`inbox:<recipientDid>`) and keeps agent cards on a separate `registry` object.
155
+
156
+ Queued envelopes expire after 10 minutes. Replay cache entries and agent cards expire after 1 hour. Each inbox accepts at most 256 envelopes, 256 KiB each, and 4 MiB total. `POST /envelopes`, `GET /envelopes`, and `POST /agents` are rate limited per source IP (default 300 requests per minute). Exceeding the byte budget or the rate limit returns HTTP 429. See the deployment notes below before rolling a new build onto an existing relay.
157
+
158
+ **Self-hosted relay deployment**
159
+
160
+ Do not deploy this change onto a live relay until in-flight traffic has drained. The previous build stored every queue and the agent registry in one Durable Object named `acp-relay`, as a single JSON snapshot. This build does not read that object.
161
+
162
+ 1. Stop senders, or wait at least 15 minutes so queued envelopes expire and recipients retry.
163
+ 2. Copy `packages/relay/wrangler.toml.example` to `packages/relay/wrangler.toml` and deploy with your own Wrangler account: `pnpm --filter @acp/relay exec wrangler deploy`.
164
+ 3. Agents register again on their next startup. Undelivered envelopes from the old object are not migrated; clients retry them.
165
+ 4. Optional environment variables on the Worker: `ACP_RATE_LIMIT_MAX`, `ACP_RATE_LIMIT_WINDOW_MS`, `ACP_MAX_ENVELOPE_BYTES`, `ACP_MAX_QUEUE_SIZE`, `ACP_MAX_QUEUE_BYTES`, `ACP_ENVELOPE_TTL_MS`, `ACP_SEEN_TTL_MS`, `ACP_AGENT_CARD_TTL_MS`. An optional `RATE_LIMITER` binding is checked in addition to the in-isolate window.
166
+
167
+ The Durable Object class name is unchanged, so the existing `v1` migration in the example stays valid. New object names are different instances of that class. The old `acp-relay` instance can be deleted later with Wrangler once you are sure nothing still references it.
168
+
169
+ **Terminal 1 — local offline relay**
170
+
171
+ Same-machine and offline sessions use a loopback relay:
172
+
173
+ ```sh
174
+ pnpm sealbridge relay start --port 8787 --host 127.0.0.1
175
+ ```
176
+
177
+ Then pass `--relay http://127.0.0.1:8787` in the terminals below. Loopback is reachable only on this machine. For cross-machine runs, replace that URL with `https://relay.sealbridge.dev`.
178
+
179
+ **Terminal 2 — Bob, the worker**
180
+
181
+ ```sh
182
+ pnpm sealbridge keygen /tmp/bob.private.json
183
+ pnpm sealbridge worker start --key /tmp/bob.private.json \
184
+ --relay http://127.0.0.1:8787 --allow-private-relay --adapter sandbox \
185
+ --name "Bob Worker" --auto-accept --once --audit /tmp/bob-audit.jsonl
186
+ ```
187
+
188
+ Copy the printed `acp-invite:...` line. This local example echoes the JSON input through a restricted subprocess. `--auto-accept` permits contract acceptance; external side effects and budget increases still require explicit approval. Remove it for an interactive `[y/N]` gate. Keys and audit output paths must be fresh.
189
+
190
+ `sealbridge worker` does not register its AgentCard in the public relay directory unless you pass `--publish`. Share the invite directly. `sealbridge discover` lists only workers that opted in with `--publish`.
191
+
192
+ Offline wake notifications are available through an operator-configured relay `--wake-command` and recipient `--wake-channel default`; both default to off. Wakes are signed and contain only routing identifiers. Queued expiry produces a retained, signed notice and `RELAY_ENVELOPE_EXPIRED` for HTTP/SSE sessions. See [offline wake, expiry notices, and option defaults](docs/relay-reliability.md#offline-wake-notifications).
193
+
194
+ Add `--on-receive 'node /path/to/notify.cjs'` alongside `--inbox` to launch a
195
+ local notification command after each successful inbox append. The command receives
196
+ exactly that JSONL record on stdin, including the task input. Task settlement does
197
+ not wait for the command; failures produce `ACP_ON_RECEIVE_FAILED` on stderr and
198
+ are not retried. Hook stdout/stderr are discarded. Hooks may run concurrently.
199
+ The command runs in the worker's current directory with its environment and owner
200
+ permissions, using the platform shell. Configure only trusted local commands;
201
+ never execute or interpolate incoming task text as shell code. Hooks should finish
202
+ promptly and manage their own timeouts. This is a best-effort notification, not a
203
+ durable delivery queue; the inbox remains the source of truth.
204
+
205
+ Pass `--inbox /path/to/inbox.jsonl` to keep a local inbox for an interactive agent.
206
+ After validating and approving a proposal, the worker appends one decrypted JSON
207
+ line before executing it: `{ receivedAt, taskId, requesterOwnerDid,
208
+ requesterAgentDid, title, description, input }`. The parent directory must exist.
209
+ The file is created or restricted to mode `0600`; rejected proposals are omitted,
210
+ and write failures stop execution. Treat this file as private task data, and keep
211
+ it outside your repository. This also works with the default sandbox echo worker.
212
+
213
+ Worker restart recovery is enabled automatically when `--inbox` or `--audit` is
214
+ configured (`WorkerNodeOptions.inboxPath` / `auditPath`). The worker writes private,
215
+ signed, fsynced task checkpoints to `<inboxPath>.tasks.jsonl`, or
216
+ `<auditPath>.tasks.jsonl` when no inbox is configured. Reuse the same owner key and
217
+ persistence paths after a restart, and run only one worker with those paths at a
218
+ time. Keep the checkpoint file with the inbox and audit files; it contains task
219
+ data and cached outcomes and must stay outside the repository. No new flag is
220
+ required. Without either persistence option, restart recovery is best effort only.
221
+
222
+ Checkpoints track receipt, acceptance, execution, delivery, and the terminal
223
+ outcome before intake is acknowledged. On startup, a sealed audit can restore a
224
+ completed outcome. A repeated proposal with the same task ID, manifest, and input
225
+ returns the cached outcome without executing the adapter or charging tool calls
226
+ again; reusing the ID with changed input or manifest returns `TASK_ID_CONFLICT`.
227
+ Current adapters expose no execution-resume interface. When execution or delivery
228
+ was interrupted before a sealed outcome, the worker sends the requester a signed,
229
+ encrypted `TASK_INTERRUPTED` error, including when the original intake was already
230
+ acknowledged. Older intake-only inbox entries also receive this explicit outcome
231
+ when retried. Inspect the recorded workspace, partial audit, and invocation IDs
232
+ before deciding what work to submit under a new task ID. Tool calls with an unknown
233
+ result are never automatically retried. Interrupted workspaces are retained;
234
+ successful completion uses the usual cleanup. Pending outcome notifications are
235
+ retried on startup, and terminal outcomes remain available for proposal retries.
236
+ Requesters using this runtime recognize restart notifications independently of
237
+ the previous session's wire counters and validate cached bilateral audit evidence.
238
+ Large cached audits travel in signed, encrypted parts to fit relay envelope limits.
239
+ Older requesters may report a peer or sequence error and should be upgraded to
240
+ receive cached results as successful task outcomes.
241
+
242
+ DID discovery prefers an exact `agentDid` match over an `ownerDid` alias and
243
+ selects the newest `signedAt` for a given owner/agent pair, with a stable
244
+ tie-breaker. Multiple valid owners for the selected agent are rejected with
245
+ `AMBIGUOUS_AGENT_CARD_OWNER` (HTTP 409 for `/agents/:did`). Restoring a relay
246
+ snapshot discards expired cards after authenticating them; invalid cards still
247
+ fail with `INVALID_AGENT_SNAPSHOT`.
248
+
249
+ Worker listeners recover dropped relay connections with jittered backoff and report connection status on stderr. Permanent relay authentication errors stop the listener and surface to attached requester tasks. Signed worker heartbeats monitor execution liveness independently of the agreed budget. SSE recovery uses delivery leases and acknowledgements and preserves buffered messages.
250
+
251
+ `delegate` and `worker start` accept `--execution-timeout-ms` (delegate default: the existing 60000 ms execution budget; worker default: agreed budget), `--task-timeout-ms` (600000), `--wait-timeout-ms` (600000), `--connect-timeout-ms` (45000), `--transport-idle-timeout-ms` (90000), and `--heartbeat-interval-ms` (15000). Values are milliseconds. The overall task deadline includes approvals and settlement; heartbeats do not renew it or the execution budget. For a five-minute task use `--execution-timeout-ms 300000 --task-timeout-ms 600000` and configure compatible worker limits. See [relay reliability and timeout controls](docs/relay-reliability.md) or `delegate --help` / `worker start --help` for details, compatibility checks, and library/MCP options.
252
+
253
+ `--max-input-tokens`, `--max-output-tokens`, `--max-tool-invocations`, and `--max-wall-clock-seconds` cap the contract a worker will accept. A proposal above any set cap is rejected before `ACCEPT`, including with `--auto-accept`. `timeoutSeconds` on the manifest is compared with `--max-wall-clock-seconds`. `--max-concurrent-tasks` limits in-flight tasks (default 8). `--max-requests-per-minute` limits proposals from one requester DID.
254
+
255
+ `--allow-requester <did>` is repeatable and also accepts a comma-separated list. `--allow-any-requester` opts in to every requester. For `--adapter claude`, `codex`, or `openai`, `--auto-accept` (and the `--auto-approve` / `--yes` aliases) refuses to start unless one of those allow flags is present, so a discovered worker cannot spend the owner API key for arbitrary callers. The sandbox example above does not need an allowlist.
256
+
257
+ **Terminal 3 — Alice, the requester**
258
+
259
+ ```sh
260
+ pnpm sealbridge keygen /tmp/alice.private.json
261
+ # Paste Bob's complete token when prompted
262
+ read -r BOB_INVITE
263
+ pnpm sealbridge delegate --key /tmp/alice.private.json --to "$BOB_INVITE" --allow-private-relay \
264
+ --title "JSON report" --description "Return the supplied JSON report" \
265
+ --input '{"summary":"Hello from Alice","findings":[]}' \
266
+ --output-schema '{"type":"object","required":["summary","findings"]}' \
267
+ --auto-approve --audit /tmp/alice-audit.jsonl --out /tmp/report.json
268
+ pnpm sealbridge audit-verify /tmp/alice-audit.jsonl
269
+ pnpm sealbridge discover --relay http://127.0.0.1:8787 --allow-private-relay
270
+ ```
271
+
272
+ Expected: `SETTLED`, a JSON deliverable, and `"valid":true` with `"status":"VALID"` on both owners. Without a trust-anchor file or `--expect-final-state` / `--expect-last-hash` / `--expect-entry-count`, `statusLabel` is `VALID (no trust anchor: truncation not checked)`: the chain is intact, and an older valid prefix was not ruled out. Alice verifies signatures, digests and budget, JSON Schema and registered isolated checks, then obtains a signed final approval. `--auto-approve` explicitly opts into policy approval of final verification; omit it for human review. `acp://invite?token=...` is also accepted. Alternatively, use `--to <Bob DID> --relay <URL>`. Bob can verify his own `/tmp/bob-audit.jsonl` locally.
273
+
274
+ **Ask another person’s AI to review a diff**
275
+
276
+ The reviewer starts `sealbridge worker start --adapter claude` (or `codex`) with
277
+ `--key` and `--relay` as above. Startup prints an `acp-invite:` token and a
278
+ ready-to-share `https://sealbridge.app/i#acp-invite:...` link. Send that link
279
+ through a trusted channel. Worker invites default to 24 hours (86400 seconds);
280
+ `--invite-ttl <seconds>` accepts 1–2592000 seconds (up to 30 days). The worker’s
281
+ signed card uses the configured lifetime too. The runtime equivalent is
282
+ `WorkerNodeOptions.inviteTtlSeconds` (default 86400); `worker.invite()` also defaults
283
+ to 24 hours. Standalone `invite create --expires-in` keeps its 3600-second default.
284
+
285
+ The requester installs Sealbridge using the one-line installer shown on the invite
286
+ page, then runs these commands in the project folder. Create the requester key
287
+ only once and reuse it on later requests. Keep private keys and secrets out of
288
+ Git and out of the diff. Replace the example link with the complete received link.
289
+
290
+ ```sh
291
+ sealbridge keygen requester.private.json
292
+ git diff > code.diff
293
+ sealbridge delegate --key requester.private.json --to "https://sealbridge.app/i#acp-invite:..." --title "Review my code diff" --description "Review this diff for bugs and suggest improvements. Return a JSON review." --input-file code.diff --out review.json
294
+ ```
295
+
296
+ `--input-file <path>` parses a JSON object as task input; other text (including a
297
+ diff) becomes `{ "diff": <complete file contents> }`. Invalid `.json` files and
298
+ JSON primitives/arrays are rejected with an actionable error. Use exactly one of
299
+ `--input-file` and the existing `--input '<json>'`. Files are read locally; HTTPS
300
+ invite links are parsed locally and never fetched. Both `acp-invite:` and
301
+ `acp://invite?token=...` remain supported. Links without a fragment token report
302
+ that the sender must provide the complete link.
303
+
304
+ Keep both terminals open. The reviewer’s approval shows the requester identity,
305
+ title, input bytes/diff lines, time limit, and token/tool budgets. The requester’s
306
+ final approval shows the review body. Answer `y` only after reviewing the displayed
307
+ request or result. `--verbose` (default off) shows full approval JSON instead;
308
+ summary mode signs the same target. Common CLI errors retain their machine codes
309
+ and include a Japanese reason and next step.
310
+
311
+ **Choose an adapter**
312
+
313
+ | Adapter | CLI / programmatic entry | Behavior |
314
+ | ----------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------- |
315
+ | Command | `--adapter command --cmd 'your-command'` | stdin JSON + `TASK_INPUT_FILE`, stdout JSON, fresh workspace, sanitized environment |
316
+ | Claude Code | `--adapter claude` | `claude -p --output-format json` with read-only `--tools Read,Glob,Grep`, shell/write/web tools denied, and `--permission-mode dontAsk` |
317
+ | Codex CLI | `--adapter codex` | `codex exec --json --sandbox read-only`, JSONL agent output and token usage |
318
+ | OpenAI compatible | `--adapter openai --model <model> [--endpoint <URL>]` | Host-owned BYOK HTTP requests; exact provider token accounting |
319
+ | MCP | `McpClientAdapter` from `@acp/adapters` | Official SDK client, stdio or in-memory transport; CapGuard + quota + approval proxy |
320
+ | Restricted JS | `--adapter sandbox [--cmd 'return input;']` | `subprocess_restricted` (no kernel network isolation), or `oci_container` when `isolationLevel` requires it |
321
+
322
+ Claude/Codex binaries must already be installed. `worker start --adapter claude|codex` checks authentication before connecting: configure `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` in the worker host environment. A logged-in CLI alone is insufficient because each task gets a fresh HOME; `~/.claude` and `~/.codex` are not exposed. Without a usable key, startup reports `AGENT_AUTH_REQUIRED` with the next step. Explicit `--cmd` overrides manage their own authentication and skip this preflight. Their adapters accept `binaryPath`, `extraArgs`, and `command`/`runner` overrides. When a provider key is configured in the host (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`), a task-scoped loopback broker injects it upstream; the CLI receives only a temporary local broker credential. Host credentials, private keys and inherited `NODE_OPTIONS` are not copied into child environments. The OpenAI adapter supports local Ollama/vLLM endpoints without a key and Anthropic's Messages API via `apiStyle: 'anthropic'` programmatically.
323
+
324
+ LLM-backed adapters (`claude`, `codex`, and `openai`) receive untrusted input from remote peers. The `claude` and `codex` adapters default to read-only tool flags. Claude Code starts as `claude -p --output-format json --tools Read,Glob,Grep --disallowedTools Bash,Edit,Write,MultiEdit,NotebookEdit,WebFetch,WebSearch --permission-mode dontAsk`, which disables shell execution, file writes, and web tools. Codex starts as `codex exec --json --skip-git-repo-check --sandbox read-only`. `ClaudeCodeAdapter` (also exported as `ClaudeCodeCliAdapter`) accepts `tools`, `allowedTools`, `disallowedTools`, `permissionMode`, and `allowUnsafeTools`. `CodexCliAdapter` accepts `sandbox` (`read-only`, `workspace-write`, or `danger-full-access`). A matching flag in `extraArgs` (`--tools`, `--disallowedTools` or `--disallowed-tools`, `--permission-mode`, `--sandbox` or `-s`, including `--flag=value`) replaces that default so the process receives one value for the flag. `allowUnsafeTools: true` omits the Claude read-only defaults. An explicit `command` replaces the whole argument list. `resolveClaudeCodeCliCommand`, `resolveCodexCliCommand`, and `resolvedInvocation()` expose the argv that will be spawned. Operators MUST run these adapters inside an OCI container (`--isolation oci`) or restrict tasks to trusted requester DIDs (`--allow-requester <did>`).
325
+
326
+ Command and CLI adapters run owner-configured executables with fresh working folders; this is process hygiene, not OS-level filesystem or network confinement. `subprocess_restricted` (the sandbox adapter's default) is a separate OS process with a sanitized environment, a scratch directory, a timeout and SIGKILL, and an output byte cap. When the Node binary supports it, the child receives `--permission` (Node.js >= 22.13 and Node.js 23+) or `--experimental-permission` (earlier Node.js 20 and 22), plus `--allow-fs-read` and `--allow-fs-write` for that directory only. Those flags restrict filesystem, child-process, worker, and native-addon access. They do not block `fetch`, `node:net`, `node:http`, `node:https`, `node:dns`, or `node:dgram`. Task code runs in `node:vm`, which Node.js documents is not a security mechanism. Untrusted third-party code requires `oci_container` (`isolationLevel: 'oci_container'`). OCI uses an already-present `node:22-bookworm-slim` image, prefers Podman, and passes `--network none`, `--read-only`, `--cap-drop ALL`, `--security-opt no-new-privileges`, and memory/PID limits. An unavailable runtime falls back to `subprocess_restricted` and records `ExecutionAttestation.effectiveIsolationLevel`. A container execution failure is surfaced rather than rerun. When execution attestation is required, verification rejects that downgrade unless the signed manifest sets `sandboxPolicy.allowSubprocessFallback: true`. `kernel network` isolation is the OCI path, not the Node permission model.
327
+
328
+ Session payloads use ephemeral X25519, HKDF-SHA256, and AES-256-GCM, with the header bound into the ciphertext. Use HTTPS for transit. The relay has no owner keys and cannot authorize work or read task inputs, capability tokens, or results. It can still observe routing metadata or deny delivery. See [security details](docs/security-model.md) and the [adversarial security test suite](SECURITY_PROOF.md).
329
+
330
+ ## Docker runtime identity
331
+
332
+ The Docker image runs as the non-root `node` user (UID 1000). On first startup,
333
+ it generates an owner identity with mode `0600` at `/data/owner.private.json`
334
+ and reuses it on subsequent starts. Mount a named volume at `/data` and reuse
335
+ that volume when replacing containers, for example:
336
+
337
+ ```sh
338
+ docker run --rm -i -v sealbridge-data:/data sealbridge
339
+ ```
340
+
341
+ `/data` is declared as a volume. Set `SEALBRIDGE_KEY_PATH` to use an existing
342
+ readable key or another absolute path in a persistent mounted directory. A new
343
+ key requires an existing directory writable by UID 1000; startup fails with
344
+ mount and permission guidance if the key cannot be read or created.
345
+ `SEALBRIDGE_RELAY_URL` overrides the unchanged default relay URL,
346
+ `https://acp-relay.agent-collab.workers.dev`. Additional MCP flags can follow
347
+ `mcp-server` after the image name.
348
+
349
+ Operators who built an earlier image revision with a build-time owner identity
350
+ should rotate to a newly generated runtime identity and update the corresponding
351
+ DIDs, allowlists, and invitations.
352
+
353
+ ## MCP, live progress, revocation, and quorum
354
+
355
+ ```sh
356
+ pnpm sealbridge mcp-server --key /tmp/alice.private.json --relay https://relay.sealbridge.dev
357
+ ```
358
+
359
+ `sealbridge mcp-server` is a dependency-free JSON-RPC 2.0 MCP server on stdio. It accepts newline-delimited JSON and `Content-Length` frames. Hosts such as Claude Code, Cursor, Codex, and Grok Bot can call:
360
+
361
+ | Tool | Result |
362
+ | -------------------------- | ----------------------------------------------------------------------------------------------- |
363
+ | `acp_delegate_task` | Verified deliverable, `taskId`, status, and audit summary |
364
+ | `acp_delegate_multi` | `race` or `quorum` delegation across workers |
365
+ | `acp_verify_audit_bundle` | Cryptographic verification report, including whether a trust anchor checked truncation |
366
+ | `acp_create_invite` | Signed `acp-invite` token |
367
+ | `acp_revoke_token` | Signed `RevocationNotice` stored in this process |
368
+ | `acp_discover_workers` | Signed agent cards registered on the relay |
369
+ | `acp_inspect_revocations` | Revocation notices recorded in this server process |
370
+
371
+ `initialize` returns server `instructions` that name this workflow. `AcpMcpServer` in `@acp/runtime` is the same server for tests and embedded hosts. The server key signs requester approvals for the tools it runs. `acp_discover_workers` is a read-only directory fetch: it uses the same relay allowlist and `validateRelayEndpointUrl` before `GET /agents`, and it does not send task input. `acp_inspect_revocations` reads the in-memory registry and does not contact a worker.
372
+
373
+ `acp_delegate_task` and `acp_delegate_multi` transmit task input to the target worker over the relay. Restrict that path when the host might supply the arguments:
374
+
375
+ ```sh
376
+ pnpm sealbridge mcp-server \
377
+ --key /tmp/alice.private.json \
378
+ --relay https://relay.sealbridge.dev \
379
+ --allow-relay https://relay.sealbridge.dev \
380
+ --allow-worker did:key:z... \
381
+ --approval-webhook https://approvals.example/acp
382
+ ```
383
+
384
+ `--allow-worker <did>` and `--allow-relay <url>` are repeatable and comma-separated. With `--allow-worker`, a plain worker DID must be listed. An invite is allowed when either its `agentDid` or its `ownerDid` is listed. Any other target fails with `MCP_WORKER_NOT_ALLOWED` before the server dials. With `--allow-relay`, every relay URL is normalized to origin plus pathname with no trailing slash. The configured `--relay`, a tool `relayUrl`, and any relay URL embedded in an invite must all match, or the call fails with `MCP_RELAY_NOT_ALLOWED`. A tool call or invite that selects a relay other than `--relay` is rejected unless that URL is allowlisted. Programmatic hosts can set `allowAnyRelay: true` to accept another relay when no allowlist is configured. An allowlist still applies when it is set. Configured relay URLs are checked with `validateRelayEndpointUrl`.
385
+
386
+ `--require-approval` uses `InteractiveCliApprovalProvider` and asks on stdin before input is sent, then again for final verification. `--approval-webhook <url>` uses `WebhookApprovalProvider` in owner-decision mode. It POSTs `{ taskId, summaryHash, reason, approverDid }` and continues only when the response is HTTP 2xx and `{ approved: true }`. `{ approved: false }` or a non-2xx response throws `APPROVAL_REJECTED` and nothing is sent. Pass `approvalProvider` on `AcpMcpServer` for a custom provider. Allowlist checks and this approval run before `delegate` / `delegateMulti` write task input to the relay. `conformance/p1-7-mcp-outbound-policy.test.ts` locks the behavior.
387
+
388
+ A worker reports `PROGRESS` (`percent`, `step`, `message`, and intermediate token or tool metrics) through the same encrypted, countersigned session log. `delegate(request, { onProgress })` receives each decrypted update before `DELIVER`. Reported metrics are charged against `QuotaMeter`.
389
+
390
+ `RevocationRegistry` checks Ed25519 `RevocationNotice`s (RFC 8785 JCS). The worker rejects a revoked invite before `ACCEPT`. `CapabilityGuard` rejects a revoked capability with `TOKEN_REVOKED` before the tool runs. A notice signed by anyone other than `issuerDid` is invalid.
391
+
392
+ Invite targets supplied as raw base64url tokens, `acp-invite:` tokens, or `acp://invite?token=` URIs use the verified payload's stable `inviteRevocationId` for lookup, including expired invites. The notice issuer must match the invite owner. Equivalent JSON serializations share that identifier. Signed notices retain their original payloads when exported; imports rebuild the lookup keys. Opaque identifiers and invite targets that cannot be verified retain exact-string lookup for compatibility.
393
+
394
+ An offline worker still learns about revocation. `deliverRevocation` and `RequesterClient.sendRevocation` queue an encrypted `REVOKE` envelope, carrying a `SignedRevocationNotice`, on that worker's relay inbox. `WorkerNode` ingests every leading `REVOKE` in `runOnce()`, `start()` (including `start({ once: true })`), and `pumpUntil()` before it reads the next `PROPOSE`. An invite notice is recorded only when the sender is the worker owner. A capability notice is recorded only when `issuerDid` issued that token or delegated it. A forged or third-party `REVOKE` is ignored and is not stored. Before `ACCEPT`, every capability id in `capabilityChains` is checked with `assertNotRevoked`, so a revoked capability is `TOKEN_REVOKED` and the task is not accepted. `conformance/offline-revocation-inbox.test.ts` covers the offline queue and the spoofed notice.
395
+
396
+ `AbortSignal` on `delegate` sends a signed encrypted `CANCEL` while the task is `ACCEPTED` or `IN_PROGRESS`. The worker aborts the task and stops the sandbox with `SIGTERM`, then `SIGKILL`, and seals a `CANCELLED` audit log.
397
+
398
+ `delegateMulti({ mode: 'race' })` selects the first deliverable that passes verification and completes its bilateral checkpoint. `mode: 'quorum'` selects an output when `quorumSize` workers (default `floor(n/2)+1`) complete checkpoints with the same JCS SHA-256 output digest. Both modes cancel sessions still executing and let already delivered sessions finish verification and checkpointing. `completionTimeoutMs` (default `45000`, a positive integer in milliseconds up to `2147483647`) bounds this completion grace after selection or when quorum becomes unreachable; existing task and wait deadlines still apply.
399
+
400
+ `receipts` retains every completed, verified audit bundle, including minority outputs. If a locally verified delivery cannot finish its checkpoint, `incompleteReceipts` preserves its signed delivery envelope, verification result, collected audit entries, and `completionError`, with `checkpointComplete: false` and no `bundle`. These incomplete receipts do not count as votes or appear in `winners`; checkpoint timeouts are not listed as execution cancellations. If agreement is impossible, the call rejects with `QUORUM_NOT_REACHED` and exposes both receipt arrays (`RACE_FAILED` does the same). A single `delegate` failure after local verification throws `IncompleteSettlementError`, carrying the evidence as `error.incompleteReceipt` and the original failure as `error.cause`.
401
+
402
+ Worker workspace removal uses five retries with a 50 ms linear backoff for transient filesystem errors. Residual files are reported through `WorkerNodeOptions.onWorkspaceCleanupError({ taskId, workspaceDir, code })`, or a console warning by default. Cleanup and reporting failures preserve the completed protocol result or original execution error.
403
+
404
+ HTTP receives, SSE streams and WebSocket receives/subscriptions require a fresh recipient-signed `RelayAuth` token bound to that relay's origin. `WorkerNode`, `RequesterClient` and the CLI sign automatically and embed the origin. For direct transport use, pass the recipient key as `new HttpRelayClient(url, key)`, `new SseRelayClient(url, fetch, key)` or `new WebSocketRelayClient(wsUrl, key)`. Those clients set `origin` with `normalizeRelayOrigin` (`ws:` becomes `http:`, `wss:` becomes `https:`), so HTTP, SSE, and WebSocket on the same host and port share one canonical origin. HTTP/SSE also accept an async token generator `(recipientDid, action, origin) => ...` as their third argument; WebSocket accepts it as its second argument. Pass that `origin` through to `createRelayAuthToken`. Raw HTTP callers send `createRelayAuthToken(key, { action, origin })` through `Authorization: Bearer <token>` or `x-acp-relay-auth`. The `auth` query parameter is rejected with `RELAY_QUERY_AUTH_DISABLED` unless the relay is created with `allowQueryAuth: true`, because query credentials leak into access logs, proxy logs, and Referer headers. Use `receive`, `stream` or `subscribe` to match the operation. Tokens expire after five minutes of clock skew and each nonce can be used once while retained in the bounded replay cache. A token for a different origin is rejected with `RELAY_AUTH_ORIGIN_MISMATCH`. A network token that omits `origin` is rejected with `RELAY_AUTH_ORIGIN_REQUIRED` unless the relay sets `allowUnboundRelayAuth: true`. Direct `InMemoryRelay.receive(did)` remains available by default; construct with `{ requireAuth: true }` to require a token there too. That in-memory path does not check origin unless the caller passes one. HTTP and WebSocket error bodies return an allowlisted protocol code. An unexpected runtime, storage, or parser failure is `RELAY_INTERNAL_ERROR` (HTTP 500) and does not include the exception text. Malformed JSON and an invalid URL are HTTP 400 `INVALID_REQUEST`.
405
+
406
+ Invite endpoints block private, loopback, link-local, metadata, and unspecified addresses unless the caller passes `allowPrivateRelay: true`. `parseAndVerifyInviteToken` and `RequesterClient` default to that block. `169.254.169.254`, `0.0.0.0/8`, and other metadata or unspecified ranges stay blocked even when private relays are allowed. Pass `--allow-private-relay` on `worker`, `delegate`, `discover`, and `mcp-server` only for a loopback relay you started yourself. Node clients resolve the hostname and pin the socket to those addresses before connecting.
407
+
408
+ A fully automated **separate-process command-adapter demo** is also available:
409
+
410
+ ```sh
411
+ node examples/network-collab/run.mjs
412
+ ```
413
+
414
+ ## Five design principles
415
+
416
+ 1. **Zero shared credentials.** The requester never hands the worker an API key or raw credential. Needed authority is a time-, count-, scope-, and depth-limited `CapabilityToken` (UCAN/Biscuit style). The requester Gateway verifies it by proxy.
417
+ 2. **Contract-first execution.** Input/output JSON Schema, acceptance criteria, verification method, deadline, and token budget live in a `TaskManifest`. Execution does not start without both signatures (`TaskContract`).
418
+ 3. **Explicit HITL gates.** External send, contract acceptance, budget overrun, redelegation, and final acceptance each have a policy-driven approval gate. The owner's signed `ApprovalReceipt` is appended to the audit log.
419
+ 4. **BYOK and signed usage receipts.** LLM cost is borne by the executing side (BYOK) inside the contracted quota. The worker runtime meters usage and signs a `UsageReceipt`. Payment stays out of the core and can be plugged in later.
420
+ 5. **Untrusted input and a bilateral audit.** Data from another owner is `untrusted` and runs in a sandbox. Every state transition is an append-only hash chain signed with Ed25519, and both sides keep a copy.
421
+
422
+ ## Documents
423
+
424
+ | File | Contents |
425
+ | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
426
+ | [`SECURITY_PROOF.md`](./SECURITY_PROOF.md) | Adversarial security test suite (not a formal proof) and `pnpm test:security-proof` |
427
+ | [`ARCHITECTURE.md`](./ARCHITECTURE.md) | System shape, trust boundaries, task state machine, five safety mechanisms, protocol comparison, threat model |
428
+ | [`REPO_STRUCTURE.md`](./REPO_STRUCTURE.md) | Monorepo layout, stack choices, MVP scope, milestones M0–M3, task split |
429
+ | [`spec/README.md`](./spec/README.md) | Protocol overview, message envelope, canonicalization and signatures |
430
+ | [`spec/task-manifest.schema.json`](./spec/task-manifest.schema.json) | `TaskManifest` JSON Schema (Draft 2020-12) |
431
+ | [`spec/examples/sample-task-manifest.json`](./spec/examples/sample-task-manifest.json) | Schema-valid sample `TaskManifest` |
432
+ | [`OSS_FORK_ANALYSIS.md`](./OSS_FORK_ANALYSIS.md) | Survey of A2A, Sealbridge, ANP, AGNTCY, DelegateOS, Cotal, Busbar, and scratch vs fork vs overlay |
433
+ | [`AGENTS.md`](AGENTS.md) | Autonomous Worker/Requester playbook |
434
+ | [`SKILL.md`](SKILL.md) | Portable agent skill |
435
+
436
+ ## Local M0–M1 implementation
437
+
438
+ Node.js 22 LTS and pnpm 10 are required. WP-1 through WP-6 are implemented as `@acp/core`, `@acp/ledger`, `@acp/sandbox`, `@acp/runtime`, `@acp/relay`, `@acp/cli` and `@acp/two-owner-demo`.
439
+
440
+ ```sh
441
+ pnpm install
442
+ pnpm build
443
+ pnpm validate:schemas
444
+ pnpm test
445
+ pnpm test:security-proof
446
+ pnpm --filter @acp/two-owner-demo start
447
+ pnpm sealbridge demo --audit-dir /tmp/acp-demo-audit
448
+ pnpm sealbridge audit-verify /tmp/acp-demo-audit/alice.jsonl
449
+ ```
450
+
451
+ All four demo scenarios assert their expected outcomes and return a nonzero exit code on failure. The demo is offline, uses ephemeral keys and synthetic provider usage, and never needs API credentials. The original sample manifest remains a schema fixture; its illustrative signature is not a real cryptographic signature.
452
+
453
+ CLI examples (use a fresh output path; key generation never overwrites a key):
454
+
455
+ ```sh
456
+ pnpm sealbridge keygen /tmp/alice.private.json
457
+ pnpm sealbridge validate spec/examples/sample-task-manifest.json
458
+ pnpm sealbridge validate capability-token spec/examples/sample-capability-token.json
459
+ pnpm sealbridge approve /tmp/alice.private.json contract_acceptance task_example001 /tmp/target.json /tmp/approval.json
460
+ pnpm sealbridge validate approval-receipt /tmp/approval.json
461
+ pnpm sealbridge audit-verify /tmp/acp-demo-audit/alice.jsonl /tmp/trust-anchor.json
462
+ ```
463
+
464
+ The approval target file contains the exact JSON action being approved. A positional trust-anchor file has `{ "taskId": "...", "actors": ["did:key:...", "did:key:..."], "head": "sha256:..." }`; obtain `head` independently of the bundle under verification. `sealbridge audit-verify` also accepts `--expect-final-state <state>`, `--expect-last-hash <hash>`, and `--expect-entry-count <n>`. With none of those constraints and no trust-anchor file, a chain that still verifies prints `"status":"VALID"` and `"statusLabel":"VALID (no trust anchor: truncation not checked)"` (`trustAnchorVerified` and `truncationChecked` are false). That label means an older valid prefix was not ruled out. When a supplied constraint matches, `statusLabel` is `VALID` and both flags are true. A prefix that does not match the anchor exits nonzero with `"statusLabel":"INVALID"`.
465
+
466
+ ```sh
467
+ pnpm sealbridge audit-verify /tmp/alice-audit.jsonl \
468
+ --expect-final-state SETTLED \
469
+ --expect-last-hash sha256:<hex> \
470
+ --expect-entry-count <n>
471
+ ```
472
+
473
+ The built CLI is also available as `node cli/dist/index.js <command>` and its workspace binary is `acp`.
474
+
475
+ Read the [demo walkthrough](examples/two-owner-demo/README.md), [wire rules](spec/README.md), [conformance suite](conformance/README.md), [M1 security model](docs/security-model.md), and the [adversarial security test suite](SECURITY_PROOF.md). M1 uses signed relay envelopes and the restricted JavaScript subprocess driver. M2/M3 adds optional hardened OCI execution and end-to-end payload encryption. The root code is Apache-2.0; adapted DelegateOS portions retain MIT attribution in [NOTICE](NOTICE).
476
+
477
+ ## Author & Community
478
+
479
+ - **Author**: [@takapon01234](https://x.com/takapon01234) on X (Twitter)
480
+ - **Repository**: [acidnyan/agent-collab-platform](https://github.com/sealbridge/sealbridge)
481
+ - **Public Blind Relay**: `https://relay.sealbridge.dev`
482
+
483
+ ### Invite-less open task collaboration
484
+
485
+ Workers started through the CLI default to `--access-mode invite-only`. They
486
+ require a valid worker invite or a requester owner DID listed with
487
+ `--allow-requester`. An invite remains a bearer invitation; an explicit
488
+ allowlist still restricts its use. `--allow-any-requester` does not bypass the
489
+ invite requirement in this mode.
490
+
491
+ To advertise availability for invite-less tasks:
492
+
493
+ ```sh
494
+ sealbridge worker start --key worker.private.json --relay https://relay.sealbridge.dev --adapter sandbox --access-mode open --publish --skill json-task --task-type json
495
+ sealbridge discover --relay https://relay.sealbridge.dev --skill json-task
496
+ sealbridge delegate --key requester.private.json --relay https://relay.sealbridge.dev --discover json-task --title Echo --description Echo --input '{"hello":"world"}'
497
+ ```
498
+
499
+ `--open` aliases `--access-mode open`. Publishing still requires `--publish`.
500
+ `--open-allow-task-type <types>` accepts comma-separated or repeated task types,
501
+ trims whitespace, removes duplicates, and advertises the list on every worker
502
+ skill. Empty entries are invalid. An explicit `--task-type` must belong to this
503
+ list. Non-matching proposal task types receive `TASK_SCOPE_REJECTED` with
504
+ structured `detail`; direct delegation omitting both skill and task type retains
505
+ its existing compatibility behavior.
506
+ `--open-rate-limit-per-did <count/window>` accepts a positive safe integer alone
507
+ (per minute), or `/m`, `/min`, or `/60s`, for example `10/min`. Other windows and
508
+ invalid counts fail with `INVALID_OPEN_RATE_LIMIT_PER_DID`. It configures
509
+ `maxTasksPerRequesterPerMinute`; when `--max-requests-per-minute` is also set,
510
+ the stricter limit wins. Both new options require open mode.
511
+ Open workers can advertise signed budget ceilings with `--open-max-input-tokens`,
512
+ `--open-max-output-tokens`, `--open-max-tool-calls`, and `--open-max-wall-seconds`.
513
+ Each accepts a positive safe integer and requires open mode. The corresponding
514
+ optional `WorkerNodeOptions.openPolicy` / `AgentCard.openPolicy` fields are
515
+ `maxInputTokens`, `maxOutputTokens`, `maxToolInvocations`, and `maxWallClockSeconds`.
516
+ Workers reject proposals exceeding these ceilings before contract acceptance;
517
+ stricter existing acceptance limits still apply. When discovering an open card,
518
+ requesters cap only unspecified default budget fields, never increase defaults,
519
+ and preserve explicit budgets and custom manifests for worker validation.
520
+ Omitted policy fields impose no additional ceiling. Starting an open worker with
521
+ `--yes` (or another automatic approval flag) and no requester allowlist prints
522
+ an `[Sealbridge WARNING]` line to stderr.
523
+
524
+ Discovery also supports `--task-type <type>` and `--did <did>`; delegation
525
+ supports `--task-type <type>` or the existing `--to <did>`. Skill selection
526
+ uses only current, verified cards explicitly advertising `open`, with stable
527
+ agent-DID ordering. A DID plus a discovery filter restricts selection to that
528
+ identity. Skills and task types describe advertised abilities, not permissions.
529
+
530
+ Open admission does not automatically approve contracts or grant tools. Existing
531
+ approval gates, requester allowlists, quotas, encrypted transport, task-bound
532
+ capabilities, expiry, and revocation checks continue to apply. CLI LLM automatic
533
+ acceptance still requires its existing explicit requester policy.
534
+
535
+ Programmatic callers can set `WorkerNodeOptions.accessMode` and
536
+ `RequesterClient.delegate({ to: '', discover: { skillId, taskType }, ... })`.
537
+ Omitting `accessMode` in the runtime API defaults to invite-only protection.
538
+ Direct DID callers need an open worker or an explicit requester allowlist. Older signed
539
+ AgentCards remain valid but are not selected for open skill discovery.
540
+
541
+ Open workers enforce signed manifest `task.skillId` and `task.taskType` against
542
+ their advertised skills before acceptance. Selecting a skill that advertises task
543
+ types requires a matching type; discovery binds the selected type into the manifest.
544
+ Direct proposals may omit both fields. Open workers ignore cryptographically valid
545
+ expired invites (`INVITE_EXPIRED`); malformed invites and live invites for a different
546
+ worker still fail. Invite-only workers continue to require an active invite or allowlist.
547
+
548
+ Signed, encrypted ERROR frames may include `detail: { field, expected, actual, hint }`.
549
+ Scope and advertised-budget diagnostics use fixed field names and hints; `expected`
550
+ and `actual` contain SHA-256 digests, never the compared payload values.
551
+ Requester failures expose `PeerRejectedError.code` and optional `.detail` while
552
+ preserving the `PEER_REJECTED` message prefix. Worker `onEvent(event, taskId, error)`
553
+ receives the same code and detail, and CLI event lines include both. When an audit
554
+ path is configured, local rejection records are owner-signed and appended to
555
+ `<auditPath>.errors.jsonl`. This diagnostic sidecar is separate from the bilateral
556
+ hash-chain ledger and is not a settled or countersigned audit bundle. Verified
557
+ rejections retain their task IDs; trailing ERROR frames do not create empty-ID events.
558
+
559
+ Capability signatures and task bindings are also verified before `ACCEPT`.
560
+ `conformance/open-task-adversarial-security.test.ts` covers admission bypass,
561
+ card spoofing, scope and budget escalation, cross-task replay, and envelope tampering.
562
+
563
+ ## Official sites and relay URL
564
+
565
+ The official site targets https://sealbridge.dev and the invite receiver targets `https://sealbridge.app/i#<invite>`. See [site operations](docs/sites.md) for source, build and deployment instructions. Adding these files does not deploy either site.
566
+
567
+ Use `https://relay.sealbridge.dev` as the recommended relay. The legacy `https://acp-relay.agent-collab.workers.dev` continues to work against the same relay. Existing signed invitations remain usable; do not edit their relay field. Update MCP relay allowlists when switching hosts.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ console.error('acp is deprecated; use sealbridge instead.');
4
+ await import('./index.js');
5
+ export {};
6
+ //# sourceMappingURL=acp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp.js","sourceRoot":"","sources":["../src/acp.ts"],"names":[],"mappings":";AACA,sCAAsC;AACtC,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;AAC5D,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ApprovalRequest } from '@acp/runtime';
2
+ export declare function approvalSummary(request: ApprovalRequest): string;