How a permanent room attaches to a real host and a real visitor, and what each side reaches through the product
The previous pages describe the mediator on its own terms: rooms, participants, memory. This page is the seam — how those rooms are found from the product's real surfaces (an account, a dashboard, a public link), and what changes when a participant is a real person with a real account rather than a name typed into a playground.
Accounts have no stored user type. Bouncer and Scout are default capabilities;
Roster is inferred or explicitly enabled. Deal rooms use host / visitor
seats. See Capabilities, Seats, and Derived Role.
Every room still follows the same rules — participants never speak to each other, the planner mediates — but a room is now one of four kinds, and the kind is how the product finds the right one instead of creating a new one every time.
| Kind | Participants | Created when | Lifetime |
|---|---|---|---|
playground | Whoever an operator adds | An operator starts one, for testing behaviour | Disposable |
home | One host | The account's first agent setup | Permanent — exactly one per account |
deal | One host, one visitor (possibly still anonymous) | A visitor starts chatting through that host's link, or the talent brings an off-platform thread via Bring a conversation to Z | Permanent — one per conversation; a pair can hold several |
demo | One host (your account), one unbound simulated visitor | Test agent starts a run | One room per rehearsal; listed as past tests. See Test agent |
Only playground and demo rooms are meant to be thrown away. home and
deal are permanent by design: a host's relationship with their agent does
not end when one negotiation does, and a visitor returning to the same host
should find their conversation again, not a blank one. A host-visitor pair is
not forced down to a single room, though — each conversation a visitor actually
started is its own room, kept apart on purpose. After a deal is booked, or
after a written offer has sat quiet long enough (Z_AGENT_NEW_OFFER_QUIET_DAYS,
default 7), a new commercial ask does not rewrite that row. Code asks whether
to open a sibling deal room; chat yes never edits a booked offer
(ZLY-1933 / ZLY-1934). A demo rehearsal is a
throwaway inbound against the real host agent; it never mints a live offer.
The account-level home room carries service = agent. Bouncer and Scout share
that home conversation and its durable account memory.
A host has exactly one account-level home room, created during setup and
resolved by account id on every visit after. It is where setup happens the first time,
and it is also just their agent's inbox going forward: the same room hears
about a deal that closed, answers a question about their rates, and gets
updated when their situation changes, indefinitely.
During initial onboarding (/onboarding), this room is created for the visitor
as a guest with goals.account-setup. Interview turns skip the background
memory pass so they stay cheap. When the visitor signs in, the seat is bound
to their account, Bouncer and Scout are enabled, and one promotion pass runs
Distil plus the Arvist to promote the interview answers into durable account
memory. After that,
every Personalize turn Distils.
Binding that room's host participant to a real account is what connects the two layers: the account's single durable memory (whatever the Arvist has promoted so far) is seeded into the room the moment the binding happens, so a returning host's agent already knows them even though the room itself was just created. After the agent is live, each Update Agent Distil runs Arvist for the host so a new preference is durable before they test z-link (ZLY-1897). If the Arvist writes again later — a deal room went quiet, standing knowledge changed — AccUpdate re-merges that durable document into this room before the next planner run, so a permanent home room does not keep a stale seed. See The Agents and Agent Setup Onboarding.
The host's public link — the same /z/:slug a wizard used to sit behind —
now opens the chat for anyone, signed in or not. An anonymous visitor gets a
guest identity in an HttpOnly cookie the moment they arrive, and a fresh
deal room whose visitor participant carries that guest key instead of an
account. They negotiate as themselves; the room simply doesn't know their
name yet ("A visitor").
A bare revisit to /z/:slug reuses the visitor's newest guest room with that
host as long as it is still empty — refreshing the page never accumulates
blank rooms — and otherwise starts a fresh one, so a conversation they
already had keeps its history distinct from a new approach. Guests are
capped: at most 8 open guest rooms per cookie, and 11 messages per room
before signing in.
Every conversation is also addressable: /z/:slug/:roomId deep-links a
visitor's room and /dashboard/agent/update/:roomId a host's, which is what
push emails link to. A signed-in visitor with several conversations with the
same host gets a picker of them on the chat page, plus "new conversation" for
a fresh one.
A talent can also create a deal room from an off-platform thread —
Bring a conversation to Z. That URL is
still /z/:slug/:roomId, but the visitor seat starts unbound (no guest
cookie). The brand hits a welcome first; claiming that link must not mint a
second room via ensureDealRoomForGuest. A bare /z/:slug stays the inbound
path above and must not resume that invite room.
Binding a real account into a room — at creation for a signed-in visitor, at
claim for a guest — is what seeds it from durable memory, or from the home
room's host branch when that document is newer (the window between Distil
and Arvist). That is why a deal room never repeats host setup: the host's
terms were already settled in their home room. The deal room's goal is
narrowed to just the negotiation (goals.negotiation). Visitor account
memory is not seeded; see
Identity and Representation.
If the same brand already has a deal room with this host, claim folds the
guest sitting into that room (offer rooms first, otherwise the oldest pair
room) so login does not leave a duplicate inbox row. A second thread is the
signed-in New conversation action — or a
sibling room for a new offer when a
booked deal cannot be rewritten, or when they return after 7 days of silence
and choose a new offer. After that quiet period, a bare /z/:slug visit
keeps the real thread instead of starting a blank sitting.
Chatting works anonymously up to a point. The gate is not at the page — it is in the mechanism, and it fires exactly when the agent is ready to move the deal and can't, because moving it requires a real account on both ends:
new-offer action is parked the same way.loginRequired
with a reason — held ("I was just about to share an update…") or cap
(the free-message allowance ran out) — and the composer is replaced by a
sign-in card. The turn that triggered the hold still finishes; the next
one is refused.Signing in claims the room: the guest participant is bound to the account,
the room is renamed from "A visitor" to the visitor's real name, and every
held effect replays in the order it was parked — the pre-composed pushes
deliver verbatim (no second model call), and the held offer is minted.
Returning to the room signed-in is the claim: openVisitorRoom claims on
the way in when the caller's cookie matches, so the first authenticated
visit never 404s. Once claimed, the guest cookie no longer opens the room —
only the account that claimed it does.
A related, small policy rides along: the first time an account signs in through a host's Z Link, they are acting as a visitor. That does not write an account type — role is inferred from the seat they occupy in this room, not stored on the account. Their own account already has Bouncer and Scout available.
Internally, "room" is a plumbing concept — the unified dashboard shows a list
of deal conversations instead, most recently active first, while the
permanent home thread is accessible via Update agent (/dashboard/agent/update).
Selecting a deal opens the live conversation thread with toolbar controls.
Deal rooms whose visitor side is still an anonymous guest stay hidden — every
send into them is held anyway — and surface, with the visitor's real name,
the moment the guest claims them.
The inbox displays both inbound and outbound communications with clear directional indicators:
A host can also archive a conversation they are done with using the folder filter (Active / Archived) and restore it at any time. Archiving is per-pane bookkeeping — the room stays fully alive, the agent keeps working in it, and the visitor's side notices nothing.
Inside a single conversation, several views are available:
?layout=split showing external and internal turns).?view=offer). The tab stays
disabled until Offer-Gen mints a row. Approve is the only physical
write. Counter, Reject, and Add switch to Conversation as chat
turns to that side's agent; Z asks last-look, and only a chat-confirmed
counter is then written to the offer, which shows as Countered. Brand-first
Approve is thanks only; after talent Approve the brand sees View payment
→ Send Payment. Details:
Offer approval and brand payment.?view=agreement).The list marks conversations with something new in them — a count, the name in
bold. That cannot be derived from the transcript: the rooms are permanent and
the agent speaks unprompted, so "the last message is from the agent" says
nothing about whether anyone has read it. Each participant therefore carries a
last_read_at, and unread is what the agent said in that pane after it.
Reading a conversation and fetching its pane are treated as the same act: the route that opens a host's pane marks it read. The chat already re-reads the stored transcript when a turn finishes and when the tab regains focus (that is how an unprompted push appears in an open pane), so the marker stays current without a separate "mark read" call the client could forget to send. The chat turn route deliberately does not mark anything — a turn is followed by an answer the reader has not seen yet.
Two consequences worth naming. The list polls, because a push can land in a room the reader is not in, and nothing else would notice. And the open conversation never shows a badge whatever the last count said, which keeps a refresh that raced the read-marking write from decorating the row the reader is looking at.
Every route that opens a conversation resolves whose it is from the authenticated session — never from a room id or participant id the client happens to send. A host's request for a conversation is checked against their own account id; a visitor's request for a deal room is checked against theirs. For an anonymous visitor, the guest cookie plays the account's role: it opens a room only while that room's visitor participant is still unclaimed and carries the same guest key. A mismatch is reported as not-found, identically to a room that never existed — there is no separate "forbidden" response that would confirm someone else's conversation exists at all.
This is why a deep-linked room id in a shared email is safe to have in a URL: the id alone opens nothing. The internal playground and ops APIs — which do operate on bare room ids, by design — are admin-gated for the same reason; they are operator tools, not a second door into someone's conversation.
This is the same principle the planner's private notes and the guidance routing already rely on (see Memory): a boundary that matters is enforced by the mechanism checking an id against the session, not by a client behaving honestly.
Every reply is stored with the agent-facing context that produced it: the memory slice recall chose, any account-memory merge, and the planner's instruction behind a wake. That record is what tells a bad prompt from missing context when a reply looks wrong, and the playground renders it.
It never reaches the person the agent is talking to. The recall snapshot is streamed only to an admin-gated operator audience, and a pane served to its own participant has those parts stripped — otherwise a visitor chatting through a z-link could read the guidance the agent is negotiating under straight out of the response body, which is the leak the branch stripping on the way in exists to prevent in the first place. Whether the snapshot is stored does not depend on who was watching; only whether it is sent does. A wake keeps its marker so the pane can still badge the message as agent-initiated, but not the instruction text behind it.
Nothing about how a negotiation concludes changes because the room it
happened in is permanent. The planner still rules the negotiation concluded,
Offer-Gen still drafts the formal offer from the agreed terms, and the
host's persona still delivers it to them as terms agreed in chat — the
written offer is next, and a booking only when that document reaches a
BOOKED stage. The full lifecycle on the previous page holds exactly as
written, just inside a room that will still be there for the next visitor,
or the next negotiation with this one. And when the conversation keeps
moving after the offer was drafted, Offer-Update brings the document back
in line before the host hears the news — and a material rewrite of an
APPROVED offer clears a one-sided brand stamp so both must Approve again.
AgreementAg then projects the
current Offer into the legal draft shown in the agreement view — it never
re-reads the room (see The Agents).
A delivered push also leaves the app: every wake that lands sends the
recipient an email with the message and a deep link straight back into
that conversation — /dashboard/agent/update/:roomId for the host,
/z/:slug/:roomId for the visitor. In the app, the conversation list marks
the push unread without the recipient having to open the pane and find it. An
unbound participant (a guest) gets no email — there is no address to send to,
which is one more reason their pushes are held until they claim.
The host's public link used to lead to a step-by-step campaign wizard. That flow still exists in code, reachable at a separate route, but the link itself now leads to the chat described on this page. Nothing about the wizard was removed — it was disconnected from the primary path, in case the chat needs a fallback while it proves itself.
On This Page
Four kinds of room, one mechanismThe host's one ongoing roomThe visitor's link opens a chat — no account requiredAnonymous until the agent needs to act: the login gateSessions divide the conversation; the account never sees the roomUnread is a stored marker, not a guess about the messagesOwnership is derived from the session, never trusted from the clientWhat a participant's browser is allowed to receiveFrom a settled negotiation to a real offerThe wizard, replaced but not deleted