← MCP / Object envelope v2

Every object arrives in an envelope

A transfer reads like something handed to you: framed, addressed, stamped with a state, closed. A stream reads like a paragraph. The grammar that makes the difference is already specced and already implemented — 2 of ~29 cards were migrated onto it, and the rest were left hand-rolled. This is the visual reference for finishing that migration and extending it past materials to every object type.

Left column in each pair reproduces the string the live MCP returned on 2026-08-12 — structure, spacing and glyphs verbatim. Right column is grammar v2. Plan of record: relay-board docs/product/mcp-object-envelope-display.md.

Sample data is synthetic. Names, titles, Codes and ids are stand-ins at the real strings’ shape and length — this repo publishes, and every branch gets a preview URL. The unredacted capture lives in the board doc, behind Access.

card grammar v2 upstream: SPEC.relay-display-grammar · DESIGN-NOTE.relay-card-display proposal · 2026-08-12
G1
The frame opens and closes

A card that never closes is a card the model talks over. Grouped sections render inside the frame, and the closing rule is followed by a signal line — the cue that the card ended and prose may begin.

G2
Every object shows an anchor

⇄ then the address, on its own line, never inline. Transfer → Code. Stream and session → short_id. Series → series id. Without an anchor an object is not in an envelope; it is a paragraph with a heading.

G3
Custody is grammar, not a transfer privilege

Who holds it, who touched it last, when. Every field is already in the payload. The pitch is a verifiable trail; the card is the cheapest place it has ever been shown.

G4
Plain loses styling and nothing else

Same anchor, same state, same custody, same cap footer, same signal — unstyled. Both branches ship in the same commit, enforced by a paired test. A rich-only card is an unfinished card.

G5
The card states its own incompleteness

Capped sections carry the ratio — ACTIVE (3 of 17) — and the overflow footer names the app. Neither silent truncation nor recitation is the model’s call to make.

G6
One glyph, one meaning

▸ means top pick and nothing else. Item types get their own documented table. Nine competing glyphs is what the grammar was written to end; a second undocumented set restarts it.

Anatomy of the envelope

grammar v2 · every object type

Six parts, in fixed order. An object card that drops one of them drops it in every host, on every model — there is no client-side renderer to compensate, only the string the model chooses to echo.

Rich
Relay Search›
1--- 2🌀 Pilot Feedback & Support 3 Stream k7m2df · active · ▒░░░░░░░░░ 9% · 1 live 4👤 J. Okafor · created Jul 24 · touched 3d ago by you 5━━━━━ ACTIVE (1 of 4) ━━━━━
Northwind — Dave & Neil debrief 7C4E19 Contacts: Dana Reyes (Ops, pilot champion), Priya Shah (onboarded this call)…
━━━━━ MARKERS (3) ━━━━━ 🔔 Northwind 07/24 — follow-ups pending approval ⚙ Maintenance suggested — 3 items stale 14d+ 1--- 6stream loaded — 1 of 4 items shown · app.relayctx.com/stream/k7m2df
1
Frame — open and close

The pair that makes it an object and not a paragraph. Sections live inside it. Today the stream card’s pair wraps the header only, and everything below trails outside.

2
Sigil + name

One glyph per object type from _display.py — ⚡ transfer, 🌀 stream, 📂 session, 🗃 series. Single transfer cards lead with the claim-state icon instead.

3
Anchor line — state — meta

The anchor never shares a line with the title, so it can never be buried. State uses one vocabulary; meta carries short fragments only.

4
Custody

Holder, creation, last touch, and whether the last touch was a human or an agent. New in v2 and net-neutral on line count — it absorbs meta scattered elsewhere on the card.

5
Body — grouped, capped, honest

Section rules with the (n of m) ratio when capped. Caps are per the grammar: 3–5 active items, 5 markers, 8–15 list rows.

6
Signal line

Tells the model the card is complete and content follows. Its absence is the mechanical reason a stream read comes back as a screenful of agent-written bullets.

What ships today, beside what should

measured 2026-08-12 · rich mode

Amber gutter is live output, verbatim. Teal gutter is v2. Every difference below is grammar already written down in SPEC.relay-display-grammar.md — none of it is new design.

relay_search(id='K4WPX2MTQD')transfer · read card

The reference envelope is the most broken card in the surface. No frame, no sigil, no claim-state icon, no anchor line — a **Code:** label instead — and no signal. The one --- it emits separates the header from the body, the exact inverse of its specced role as a frame. The full 6,146-character material body is inlined behind it.

Today
Relay Search›
no frame · no sigil · no anchor## Partner program — working handoff Code: K4WPX2MTQD · Role: Sent by you · From: erik Tags: handoff, partner-program, gtm --- # Partner & vendor program You are building the commercial program for partners and… …6,146 chars inlined · no closing frame · no signal
Grammar v2
Relay Search›
--- 👤 Partner program — working handoff Relay K4WPX2MTQD · open · 1d ago · warm 👤 Sent by you · created Aug 10 · read 1d ago Tags: handoff · partner-program · gtm Greenfield commercial program for partners and vendors: a classification of partner kinds, and what we actually get per kind. Constrained by the settled referral program. --- content loaded — opening the full material as a document
● The body becomes a bounded preview and the bytes ride the resource_link that already ships (relay://material/{code}). Inlining 50k characters into display is what the resource was built to stop — and every inlined character raises the odds the model summarises the card away.
relay_search(id='pc_3f81aa402c17')stream · read card

The nicest surface Relay has, and still not an envelope. The --- pair frames the two header lines only, so the card never ends. No anchor at all — the stream carries short_id: k7m2df, already what /stream/ links and relay_search(id=…) accept, and the card links the raw pc_ id instead. No custody. No signal.

Today
Relay Search›
--- 🌀 Pilot Feedback & Support 9% ▒░░░░░░░░░ · 1 live ---↑ frame closes here — everything below is outside it ━━━━━ ACTIVE (1) ━━━━━
Northwind — Dave & Neil debrief (2026-07-24) 7C4E19 Contacts: Dana Reyes (Ops, pilot champion — runs Relay on b
━━━━━ MARKERS (3) ━━━━━ 🔔 Northwind 07/24 — follow-ups pending approval ▸ ⚙ Maintenance suggested — Pilot Feedback & Support 📝 Log format — one entry per EAP session no anchor · no custody · no close · no signal · ▸ overloaded
Grammar v2
Relay Search›
--- 🌀 Pilot Feedback & Support Stream k7m2df · active · ▒░░░░░░░░░ 9% · 1 live 👤 J. Okafor · created Jul 24 · touched 3d ago by you ━━━━━ ACTIVE (1 of 4) ━━━━━
Northwind — Dave & Neil debrief 7C4E19 · very stale Contacts: Dana Reyes (Ops, pilot champion), Priya Shah…
━━━━━ MARKERS (3) ━━━━━ 🔔 Northwind 07/24 — follow-ups pending approval ⚙ Maintenance suggested — 3 items stale 14d+ 📝 Log format — one entry per EAP session --- stream loaded — 1 of 4 items shown · app.relayctx.com/stream/k7m2df
● The gate marker stays first in the section — the cap can only ever hide a plain marker, never a gate. And the deep link moves to the anchor (/stream/k7m2df), so the address a user reads is the address the link uses.
relay_sessionssession · list card

Session rows carry someone else’s Code — from H2NQVBRK4P, the origin handoff — as a bare untyped token, while the session’s own short_id never appears. So the one identifier on the row addresses a different object than the row.

Today
Relay Sessions›
--- 📂 Sessions · 3 ▸ 📂 web-platform / release-week — QA feedback loop — H2NQVBRK4P · ● unread · 2026-08-11 · from H2NQVBRK4P · most recent open session 📂 Partner program — working handoff — K4WPX2MTQD · 2026-08-11 · from K4WPX2MTQD --- own anchor absent · origin Code untyped · title repeats it
Grammar v2
Relay Sessions›
--- 📂 Sessions · 3 open ▸ 📂 q8vt3n web-platform / release-week — QA feedback loop ● unread · Aug 11 · continues from H2NQVBRK4P · most recent 📂 w5rk9p Partner program — working handoff Aug 11 · continues from K4WPX2MTQD 📂 d2jhx6 Multi-profile identity — workspace binding ● unread · Aug 10 · continues from T7BXQD3MCV ---
● continues from types the origin Code instead of leaving it bare, and the trailing — CODE comes out of the title — it was only ever there because the row had nowhere else to put an identifier.
relay_search(mode='list', type='stream')stream · list card

Relay rows carry a Code; stream rows carry no identifier of any kind, so a stream can be named in a card and then not be addressable from it. The closing frame is also missing here and present on the relay and session lists — three list renderers, three answers.

Today
Relay Search›
--- 🌀 Streams · 50 ▸ 🌀 Onboarding Drip — Lifecycle Relays · ● unread · 4 item(s) · most recently active stream 🌀 Platform — apex build · ● unread · 1 item(s) 🌀 Quarterly Planning · ● unread · 16 item(s) … +34 more · app.relayctx.com/stream no anchors · no closing frame · “item(s)” shipped to users
Grammar v2
Relay Search›
--- 🌀 Streams · 16 of 50 ▸ 🌀 b3nf8s Onboarding Drip — Lifecycle Relays · ● unread · 4 items · 28% · most recent 🌀 c9pw2r Platform — apex build · ● unread · 1 item · 0% 🌀 k7m2df Quarterly Planning · ● unread · 16 items · 75% … +34 more · app.relayctx.com/stream ---
● The header count states its own scope (16 of 50) rather than printing the total above a truncated list — the same scope-honesty rule the status-vocabulary note asks of the dashboard. Weight is on the row because it is the number that decides which stream needs attention.

Plain tier — same facts, no styling

RELAY_PLAIN · terminals · weak-render hosts

Plain is not an abbreviation and not a downgrade path — it is the fallback for terminals and for every host whose model flattens the rich card, which per the client matrix includes claude.ai on long-context Sonnet. It carries the anchor, the state, the custody, the ratio, the overflow and the signal. Rules and emoji are the only things it loses.

relay_search(id=…)stream · both tiers
Rich
Relay Search›
--- 🌀 Pilot Feedback & Support Stream k7m2df · active · ▒░░░░░░░░░ 9% · 1 live 👤 J. Okafor · created Jul 24 · touched 3d ago by you ━━━━━ ACTIVE (1 of 4) ━━━━━
Northwind — Dave & Neil debrief 7C4E19 · very stale
━━━━━ MARKERS (3) ━━━━━ 🔔 Northwind 07/24 — follow-ups pending approval --- stream loaded — 1 of 4 items shown
Plain
Relay Search›
[Stream] Pilot Feedback & Support k7m2df · active · 9% weight · 1 live J. Okafor · created Jul 24 · touched 3d ago by you -- Active (1 of 4) -- 7C4E19 Northwind — Dave & Neil debrief (very stale) -- Markers (3) -- (gate) Northwind 07/24 — follow-ups pending approval … +2 more · app.relayctx.com/stream/k7m2df Stream loaded — 1 of 4 items shown.
● Bracketed type label instead of a sigil, -- Section -- instead of a heavy rule, (gate) instead of 🔔. In a text transcript a glyph is indistinguishable from a truncation artifact — the same finding as code presence, applied to item types.

Field map — one grammar, six objects

what fills each slot

The grammar does not change per object; only what fills it does. An object with no resolvable anchor does not get a card — it gets a list row.

ObjectSigilAnchorStateMetaSignal
Transfer⚡ / ✅👤⏳Codeopen · received · expiredage · freshness · seriescontent loaded — see below
Stream🌀short_idactive · archivedweight meter · live countstream loaded — n of m shown
Session📂short_idopen · closeddate · depth · workspacesession loaded — card above
Series🗃series idactive · expiring · expiredparts · next expiryseries loaded — n parts
Resource📄name—type · sizeresource loaded — see below
Request📥request idpending · approved · deniedrequester · ageawaiting your decision
● State vocabulary is an open decision. The table shows open / received from the status-vocabulary note rather than today’s pending / claimed. This sweep is the cheapest carrier that change will ever get; shipping a fourth spelling now guarantees a second sweep later. Not yet ratified — treat the column as the recommendation, not the decision.

The fallback ladder

rich display · per-harness gated

Each rung degrades to the one below with no loss of the anchor, the state, or the custody line. That invariant is the whole spec. Attachment is gated per harness through the shipped relay.mcp.display_config lever — one mechanism, not a second one for each new rung.

Apps ui://
Planned · W4

Sandboxed iframe panel with postMessage back to the server. The stream panel is the highest-value one Relay could ship — weight meter, items, gates and timeline live in the conversation — and it is the one that later becomes editable. Host-gated to Claude web/Desktop; sequences alongside the MCP 2.0 migration, never ahead of it.

resource_link
Partial · W3

relay://material/{code} shipped Jul 30 and works. relay://stream/{short_id} and relay://session/{short_id} do not exist, so “open in detail” covers exactly one object type. Same read-through pattern, same auth, no new storage, no residency surface.

structuredContent
Partial · W3

Typed models exist for materials only. Streams, sessions and series hand the host prose to re-parse — and relay_sessions hands it a double-encoded string, the degenerate {result: string} wrapper relay_search already suppressed. Cheapest win in the plan; helps hosts that never render a card at all.

Rich card
Shipped · W1–W2

The grammar on this page. Rendering is instruction-driven — there is no client-side renderer, so a card renders richly only because the model echoed it. Everything above this rung is enhancement; this rung is the product.

Plain card
Shipped · W1

The floor. Terminals, RELAY_PLAIN harnesses, and any model that flattens the rich tier. Ships in the same commit as its rich branch, enforced by a paired test.

Anti-patterns

all four observed live, 2026-08-12

Not hypothetical failure modes — each of these is a string the production MCP returned during the audit that produced this page.

🌀 Pilot Feedback & Support no ⇄ line at all ⇄ k7m2df
A card with no anchor

The single worst one, because it is silent: the card looks complete. A user who wants to hand the stream to another agent has to leave for the app to find its address — and the address existed the whole time. An object with no anchor is a paragraph with a heading.

--- 🌀 Stream 9% · 1 live --- ← closes after the header ACTIVE… MARKERS… (nothing)
A frame that closes early

Worse than no frame, because it teaches the model that the card ended two lines in and everything after is fair game to re-flow. This is the documented root cause of both June 2026 card bugs, still live on the stream card.

Code: K4WPX2MTQD ⚡ `CDMB3YWXXT` Title — H2NQVBRK4P (in the title) ⇄ K4WPX2MTQD
Four spellings of the anchor

A bold label, a bare backtick run inline before the title, a suffix inside the title, and the specced ⇄ line — all shipping simultaneously. A user learns to look in a different place on every surface, which is the same as not learning at all.

▸ ⚙ Maintenance suggested 4 item(s) Streams · 50 (showing 16) ⚙ Maintenance suggested 4 items Streams · 16 of 50
Overloaded glyphs, leaked plurals, unscoped counts

▸ means top pick on list cards and something else on the stream card. item(s) is a serialiser artifact reaching users. And a header that prints the total above a truncated list is the dashboard’s scope problem, re-created in the MCP.

What has to be true before this ships

gates, in order
1
The anchor decision

Confirm short_id is the user-facing address for streams and sessions and that it is stable. The anchor line hard-codes the answer, and every deep link on every card follows it. A fifteen-minute decision blocking a five-day wave.

2
The conformance audit

All ~29 display call sites scored against the grammar, rich and plain. Including the claim card — this entire page assumes it is the good one, and that assumption is documented rather than measured.

3
The paired-test contract

A rich card with no plain branch fails the build, alongside the existing render-tier CI guard. This lands before the migration wave, because ~29 display-string swaps with no contract is how the June bugs happened.

4
A net-neutral line count

Custody adds a line; it must absorb meta already scattered across the card rather than growing it. Every byte added to a card competes with the instruction that protects the card — which today runs ~700 characters of preamble per call, longer than the card itself.

Plan: relay-board docs/product/mcp-object-envelope-display.md Grammar: relay-platform SPEC.relay-display-grammar.md · DESIGN-NOTE.relay-card-display.md Siblings: Relay Card Display · Code Presence