diff --git a/package.json b/package.json
index 03840538e..00320cbf0 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"overrides": {
"ajv@8": "^8.18.0",
"esbuild@>=0.27.0 <0.28.1": "^0.28.1",
+ "@jeswr/guarded-fetch>undici": "8.9.0",
"nodemailer": "^9.0.1",
"postcss": "^8.5.10",
"ws": "^8.21.0"
diff --git a/packages/agentic-legacy-bridge/.gitignore b/packages/agentic-legacy-bridge/.gitignore
new file mode 100644
index 000000000..704d1c115
--- /dev/null
+++ b/packages/agentic-legacy-bridge/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+coverage/
+*.tsbuildinfo
+.DS_Store
+.vitest/
+# dist/ is COMMITTED (GitHub-installable under ignore-scripts=true — do NOT ignore it).
+
+# Built output is rebuilt locally; published mirrors carry dist/.
+/dist/
diff --git a/packages/agentic-legacy-bridge/LICENSE b/packages/agentic-legacy-bridge/LICENSE
new file mode 100644
index 000000000..2f396fc37
--- /dev/null
+++ b/packages/agentic-legacy-bridge/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Jesse Wright
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/agentic-legacy-bridge/PROTOCOL.md b/packages/agentic-legacy-bridge/PROTOCOL.md
new file mode 100644
index 000000000..d812dd82b
--- /dev/null
+++ b/packages/agentic-legacy-bridge/PROTOCOL.md
@@ -0,0 +1,137 @@
+# The legacy-bridge wire protocol
+
+
+
+How an agentic reply advertises itself over a legacy channel and how two bridge-capable parties
+negotiate off email onto a better channel. This is the concrete, documented form of
+`agentic-solid-vision/docs/LEGACY-INTEROP.md` §4–§5. M1 ships the **pure decision logic**
+(`detectBridgeCapability`, `highestMutualChannel`, `decideUpgrade`); the **live transport** (fetching
+a peer's agent card, running the handshake over the wire) is the M2 adapter and composes
+`@jeswr/solid-agent-card` + `@jeswr/solid-a2a` through `@jeswr/guarded-fetch`.
+
+## 1. Advertising a structured reply
+
+An outbound reply built by `buildReply` carries the structured payload in three redundant places
+(all the same canonical graph — see `docs/DECISIONS.md` D2):
+
+| Carrier | Where | For |
+|---|---|---|
+| Inline JSON-LD | `` in the HTML body | any client (survives forwarding) — the primary |
+| MIME part | a `multipart/alternative` `application/ld+json` part | agent-aware clients (byte-exact) |
+| Header pointer | `X-Agentic-Reply: ` | a recipient agent locating the authoritative pod copy |
+
+A reply MAY also advertise its supported upgrade channels:
+
+```
+X-Agentic-Channels: rdf, dpop-sk, a2a
+```
+
+The values are drawn from the fixed channel vocabulary (§3). `email` is implicit (it is how the
+message arrived) and need not be listed.
+
+## 2. Detecting a bridge-capable counterparty
+
+`detectBridgeCapability({ headers, jsonLd })` returns `{ capable, channels, podCopyUrl? }`. A
+counterparty is **capable** if an inbound message shows any bridge marker:
+
+- an `X-Agentic-Reply` header (a safe http(s) pod-copy URL), or
+- an `X-Agentic-Channels` header, or
+- an inline JSON-LD block whose `type` includes `AgenticReply`.
+
+`channels` is the advertised set intersected with the known vocabulary, always including `email` as
+the floor, returned in preference order. Everything is parsed from what an inbound message *already
+carries* — no network. (Full capability discovery — reading the peer's
+`.well-known/agent-card.json` A2A `capabilities.extensions` array — is M2.)
+
+## 3. The channel vocabulary + preference order
+
+Most-preferred first (`CHANNEL_PREFERENCE`):
+
+| Channel | A2A extension URI (`CHANNEL_EXTENSION_URI`) | Meaning |
+|---|---|---|
+| `rdf` | `https://w3id.org/jeswr/a2a-rdf/v1` | RDF-native structured exchange ([`jeswr/a2a-rdf-extension`](https://github.com/jeswr/a2a-rdf-extension)) |
+| `dpop-sk` | `https://w3id.org/jeswr/dpop-sk/v1` | the DPoP-SK browser fast-path ([`jeswr/dpop-sk-spec`](https://github.com/jeswr/dpop-sk-spec)) |
+| `a2a` | `https://w3id.org/jeswr/nl2rdf-upgrade/v1#a2a-json` | plain A2A JSON — the [`nl2rdf-upgrade-spec`](https://github.com/jeswr/nl2rdf-upgrade-spec) baseline-A2A mode identifier |
+| `email` | `urn:agentic:channel:email` | the floor — always works |
+
+`highestMutualChannel(local, peer)` returns the highest channel both sides support, falling back to
+`email`. Both sides implicitly support `email`.
+
+## 4. The upgrade handshake (fail-closed)
+
+Once both parties have agents, the initiator sends an **upgrade offer** naming the target protocol
+document (mirrors `@jeswr/solid-a2a`'s `encodeUpgradeOffer`):
+
+```
+UpgradeOffer = { targetChannel, protocolHash?, protocolSource?, required }
+UpgradeResponse = { accept, protocolHash? }
+```
+
+`decideUpgrade(offer, response, currentChannel)` decides, **fail-closed**:
+
+| Peer response | `offer.required` | Outcome |
+|---|---|---|
+| accept, hash matches (or no hash set) | — | `upgrade` to `targetChannel` |
+| accept, hash **mismatch** | — | **`abort`** (tampered/ambiguous protocol binding) |
+| decline | `true` | **`abort`** — a security-bearing step must NOT proceed in unsigned prose |
+| decline | `false` | `stay` at `currentChannel` (the floor still works) |
+
+Two invariants, non-negotiable:
+
+1. **No silent downgrade of a security-bearing step.** A declined `required` offer aborts rather than
+ quietly continuing in prose — the reliability model's always-human-confirm rule expressed on the
+ transport (a `required`/security datum can *propose* but never *authorise*).
+2. **The floor always works.** Every non-abort outcome leaves a working channel underneath — worst
+ case, email with inline structured data any future agent can still read.
+
+## 5. The metadata protocol — deterministic first, LLM last
+
+The concrete form of `agentic-solid-vision/docs/NOW-PERSONAL-AGENT.md` §5 (three rules), so peer
+agents can learn each exchange shape once and thereafter interoperate with **zero LLM inference**.
+
+**Rule 1 — read structured metadata before any model reads anything.** The channel parse surfaces
+(count/size-capped, control-stripped): embedded `