A booked offer stays frozen; a new deal for the same talent and brand gets its own conversation
Tickets: ZLY-1933 (booked change), ZLY-1940 (rejected offer), and ZLY-1934 (quiet period). One-sided rewrite before booking is ZLY-1932 — out of scope here.
The same talent and brand can have several deal rooms. A booked (or paid) or rejected written offer is frozen in its conversation. A later campaign, or a change they still want after booking or reject, is a new conversation — not a rewrite or reopen of the old Offer tab.
Code decides when to ask and when to mint the sibling room. The persona only writes the sentence. The chat turn picks the room action in code; a room action Distil returns is ignored in live rooms. The model has no create-room tool.
This is not Bring a conversation to Z (an
outbound invite from an off-platform thread) and not a bare /z/:slug
inbound that resumes the oldest pair room.
| Situation | What Z does | If they say yes | If they say no |
|---|---|---|---|
| Brand asks to change a booked, paid, or rejected offer | Refuses. Asks whether they want a new offer. Does not rewrite or reopen this Offer tab. | New conversation + working Z-Link. Inbox opens it. | Stop. The old offer stays. |
| Talent asks to change a booked, paid, or rejected offer | Refuses. Asks whether they want Z to send a message to the brand. Does not create a new conversation. | Code messages the brand: this offer is frozen or rejected; they can start a new offer. | Stop. Do not contact the brand. |
| Brand asks for a new offer / new conversation (any stage, including before booking) | Creates the sibling room. Does not freeze or refuse this offer. | — | — |
| Talent asks for a new offer | Same as the talent booked-change path. Only the brand can start a new offer. | Message the brand. | Stop. |
| First brand message after 7 days of silence (any previous outcome) | Asks only: new offer or continuation of the previous one. | Same sibling room + Z-Link. | Stay. Resume this room. |
| First talent message after 7 days of silence | Does not ask new vs continuation. Continues, or offers to message the brand if they want a new deal. | — | — |
The original Offer tab is unchanged. The new room starts a fresh negotiation — it does not copy the old offer, campaign, or transcript.
Titles look like Volt Hydration × Maya Ortiz · 17 Sep so the inbox can tell
rooms apart.
The brand gets a Z-Link (/z/{slug}/{roomId}). Talent dashboard links
are not used to mint this fork. Chat posts that URL as a real markdown link;
the UI also navigates when the assistant message carries data-open-room.
Both seats must be signed in. If the brand is still a guest, Z tells them to sign in and try again — it does not invent a fork.
The booked document will be signed later. A silent rewrite (or a new version on the same row) would make that signature meaningless and, after pay-in, collide with money already held. A second conversation keeps the booked deal intact and lets a new deal start clean.
The same split already existed for inbound sittings: a pair is not forced down to one room. See A deal room per conversation started. This page is the product fork: booked or rejected change, or a new campaign after silence.
7 days with no human message. Agent-only wakes do not reset the
clock. One env: Z_AGENT_NEW_OFFER_QUIET_DAYS (default 7). Do not scatter
the number in prompts.
Applies regardless of the previous offer’s outcome (open, booked, paid, declined, expired).
A message before 7 days does not ask new vs continuation. If that old offer is already booked and the brand then asks to change it, the booked-refuse path still applies. The talent is never asked new vs continuation — only the brand can start a new offer.
A bare /z/:slug visit normally ensureDealRooms (oldest pair room) and may
start a fresh sitting, which hides history and keeps the old Offer tab.
After 7 days of silence that looks like a new deal in the old room.
If the pair room is quiet and there is still only one room: skip the fresh sitting so the first message can ask new vs continuation.
After the brand has already minted a sibling new-offer room, a bare
/z/:slug visit may land on a blank sitting. That is the current
new-offer conversation — not a defect, and not a requirement to reopen
the old thread. Deep links with an explicit roomId stay on that room.
State branch (persona-owned, like pendingPrincipalDecisions):
newDealFork.pending: { kind: "booked-change" | "quiet-period", askedAt }Guard JSON includes roomAction (Distil's is ignored in live rooms):
none | ask-quiet-fork | refuse-booked-change | create-sibling-room | stay-in-room | relay-visitor
applyRunEffects allows that action only when the room state matches:
| Proposed action | Code allows it only when |
|---|---|
create-sibling-room | Visitor seat, and newDealFork.pending is set and this turn is a yes, or the visitor explicitly asked for a new offer/conversation (any stage) |
relay-visitor | Talent seat, pending booked-change, and they said yes to messaging the brand |
stay-in-room | pending kind is quiet-period (or they declined a booked new-offer / relay ask) |
ask-quiet-fork | Visitor seat, room is quiet and pending is empty and they did not already ask for a new offer |
refuse-booked-change | live stage is BOOKED_* (or paid / held / completed / disputed) or DECLINED_BY_TALENT |
If the room is quiet, code asks the brand in the chat turn.
If the stage is booked and a persona or Guard tries to push an amendment of this offer,
Guard / applyRunEffects drops that push. Offer-Update still only
touches DRAFT / APPROVED. Guard never mints a sibling room.
Last-look / close-flow is gated: not on a booked or rejected offer, not while a quiet-period question is open.
createSiblingDealRoomFrom resolves host + visitor account ids and
calls insertDealRoom (always insert). It does not use ensureDealRoom
(that resumes the oldest pair). The brand + New conversation HTTP path
already used this insert; the sibling is the same primitive.
Adaptive prompts (split so a booked rewrite is not used as the first quiet-period message). Only the brand can start a new offer.
tests/manual/zly-1933-booked-offer-refuse-new-room.md — freeze this
conversation’s booked offer; talent is offered a message to the brand;
the brand mints a sibling room.tests/manual/zly-1940-rejected-offer-frozen-new-room.md — freeze a
rejected offer the same way; talent is offered a message to the
brand; the brand mints a sibling room. The rejected Offer tab stays
Rejected.tests/manual/zly-1934-quiet-new-offer-or-continuation.md — after
silence on an unbooked offer (no talent Approve), a brand
greeting asks new vs continuation; new offer gets its own room.To exercise the 7-day ask without waiting a week, settle from the room page:
{ "guard": false, "arvist": false, "simulateNewDealQuiet": true }
POST /api/z-agent/my/rooms/:roomId/settle — session cookie, caller’s own
room. This is the same settle hook tests already use for quiet-chain work.
The extra flag stamps newDealFork.simulateQuiet and ages every sitting
in the room past the 30-minute session gap. Real 7-day silence already
opens a new sitting on the next human line; the flag alone used to leave
the current sitting open, so the agent kept negotiating that thread.
The room memory helper
returns isQuiet, quietDays, lastParticipantMessageAt, and
newDealForkPending (session + Bearer CRON_SECRET).
| Surface | When | Room |
|---|---|---|
| Sibling new offer | Brand wants a new offer (after a freeze relay, or after quiet) | Always-insert deal for the same pair. Fresh negotiation. |
| + New conversation | Signed-in brand / talent inbox | Same insert. Manual. |
Bare /z/:slug | Brand opens the public link | Resume oldest pair (ensureDealRoom). Fresh sitting unless the room is quiet. |
| Bring a conversation to Z | Talent pastes an off-platform thread | Outbound deal, unique invite URL. |
| Piece | Where |
|---|---|
Quiet check, fork answers, roomAction | packages/z-agent/srv/src/sibling-deal-room.ts |
Insert + createSiblingDealRoomFrom | packages/z-agent/srv/src/rooms.ts |
| Distil / Guard parse | packages/z-agent/srv/src/agents/planner.ts, guard.ts |
| Prompt rules | NEW_DEAL_FORK_PROMPT_RULES in default-prompts.ts; DEAL STAGE block in offer-stage.ts / persona-prompt.ts |
| Open-room part + navigate | packages/z-agent/client/src/lib/open-room.ts, Conversations + Z-Link chat |
| Dashboard path | DASHBOARD.agent.conversationRoom |
Deal stages live in
Deals move through explicit physical stages.
How /z/:slug normally opens is in
Rooms in the Product.
Dual Approve and pay-in are in
Offer approval and brand payment.