Dialogue · link unfurl

Paste a link

Live mock, real pipeline. Paste a real URL into the composer below. Before send — the server fetches the page and a preview chip appears above the input (✕ to dismiss). After send — the message carries the frozen card, and the right panel shows the model context as an append-only timeline: the T1 skeleton digest goes in at send; click an outline pill to play a persona ordering a full section via the in-band <read> tag (T2, appended next turn).

Backend not reachable — start it with scripts\serve-unfurl-8013.bat then open http://127.0.0.1:8013/.

the room — composer + bubble

room · mock
your messages appear here
try:

what the persona sees — real extraction

no link sent yet
After you hit send, the model context grows here as an append-only timeline — [L1 · T0+T1 digest] injected at send: title/source header, outline, lede, first paragraph per section, conclusion (≤ 6,000 chars) [L1 · T2 「section」] appended ONLY when a persona orders it by emitting <read link="L1" section="…"> Click an outline pill inside a digest block to play the persona and order a section.
architectureThe server fetches, the message freezes. Browsers can't read foreign pages (CORS), so the box does the fetch — Telegram's model. But the result is stored into the message at send time and never re-fetched — WhatsApp's persistence. That combination is what keeps the §8 append-only prompt cache valid.
tiersDepth follows engagement, not position. T0 header (~150 chars) + T1 skeleton digest (outline · lede · first paragraph per section · conclusion, ≤ 6,000 chars) injected at send — uniform shallow coverage of the whole article instead of a hard head-cut. T2 full sections appended only when ordered, so links nobody engages with stay cheap. Pages without heading structure fall back to the 12,000-char paragraph-boundary cap. At §9 compaction a link collapses back to T0 + a one-line gist.
read tagHow a persona orders T2. It emits <read link="L3" section="…"> in its output — the same in-band trick as <speak>/<confer>; our code validates the id + heading against the frozen extract and appends the section next turn (no second model call; a one-beat delay is natural in a chat room). The T1 outline is the menu it can order from. Guardrails: max 2 fulfilments per turn, dedup, ids anchored — never re-matched from names.
guardhttp/https only · private IPs refused on every redirect hop · 10 s timeout · 2.5 MB read cap. The box fetches user-supplied URLs, so the SSRF guard is part of the pipeline from day one, not an afterthought.
dismiss✕ means nothing is attached. No card for humans, no context for personas — the URL goes out as plain linkified text. Same URL won't re-unfurl until it changes.
link unfurl mock · backend: docs/unfurl_server.py (port 8013) · production target: lib/run_room.py