The Agents

The roles, what each one can see, and how a turn works

The cast

Nine roles on the critical path, deliberately unequal. Only the persona agents talk to people, and they also drive: each one rules the room's goals and sends what other participants are owed, in the same turn as its reply. DistilMemAg records facts in the background; and the quiet-chain and back-office roles never appear in a live conversation at all. Two readers sit outside this cast — the asset agents below — because they judge nothing about a deal: they distil one file or one page, and are handed the room only to disambiguate a name.

Persona agents

Every participant in a room has their own persona agent — the one voice they ever hear. It is the same kind of agent in every seat; what differs is the instructions it carries and who it faces.

The host-facing persona's first job is to understand the person — not just their commercial rules but enough about them to represent them well and to talk to them like someone who knows them. Later it delivers outcomes: terms have been negotiated, here is what they are, do you want these terms?

The welcome screen does not use that host persona. It uses routerAG, listed in the admin agents page as router-ag. See Welcome.

The visitor-facing persona negotiates. It reads the whole visible memory, including host.mandate and pendingPrincipalDecisions, plus the live DEAL STAGE block from the offers row. It asks for whatever the visitor has not supplied, refuses terms that break a stated rule, and settles when a mandate or principal instruction covers the decision. If no mandate covers a price or yes-no, it says it is checking with the principal and collects what is missing — it never names the floor or that a mandate system exists. What it settles in chat is negotiation, not a booking, and it says so at the moment of closing. It is strictly forbidden by code directives from declaring an engagement booked, official, or signed unless the DEAL STAGE block shows a BOOKED stage.

Persona agents see the visible memory plus the live DEAL STAGE block. They record no facts — that is the background memory writer's job — and they are grounded hard: the conversation, the memory, the DEAL STAGE block, and the dispatch record are the only things that exist for them. They confirm nothing on their own authority; a booking is confirmed only when the DEAL STAGE block shows a BOOKED stage.

What a persona does act on is the negotiation itself. In the same turn as its reply it can:

  • rule a goal — met, abandoned, blocked, or reopened — which fires the goal's effect (a draft offer, a saved campaign) when the reply finishes;
  • send a message to another participant, who is woken to hear it in their own agent's voice;
  • keep its working plan — where things stand and what it does next — which it reads back on its next turns and nobody else sees;
  • record the principal's answer to a decision the other side is waiting on.

Before anything goes to the other side, it still asks once whether there is anything to add. Once that is answered, the message goes out in that same turn, with any missing terms named as open questions rather than held back. An answer another agent asked for about the counterparty in this room is a message too: once the principal gives it, the persona sends it on.

The one thing a persona can do beyond talking is read what the person put in front of it. That is perception, not action: it cannot go looking for anything nobody handed it, it has no inbox, and it still has no signing authority. If a page or file cannot be read it must say so — "I've reviewed the brief" is false by construction unless the brief actually reached the model.

A file attached to a message arrives as a native input. A link is read by scrapePublicUrl, which returns the page's readable text: it checks the URL is public first — no localhost, private addresses, or embedded credentials — then reads the page with Gemini URL Context on google/gemini-3.1-flash-lite, and falls back to a bounded direct fetch. It fails closed: if the provider does not report having fetched that URL, the link is unreadable rather than described from something else. Google Search is deliberately not part of it, so an unindexed brief is never answered from whatever the web says about the brand instead — see Design Decisions.

A persona does not have to ask for a link, either. Any turn carrying a message — web chat or an inbound email reply — reads up to three links found in its text into room assets before the planner runs, so the reply to that very message can already use the page. Scripted rooms skip it: a replay must not call an external page reader.

Attachments live in media storage and travel through the transcript as a URL, so a conversation does not carry megabytes of bytes around; the bytes are pulled back at call time. Text-like files are reduced to their readable content in the browser before they are ever sent.

The asset readers

A file or link shared in a room becomes an immutable room asset, and two agents read it. Both run on google/gemini-3-flash. A roster import is the exception: the page read itself writes the profile on Gemini 3.1 Flash Lite, the account summary uses that same lighter model, and the second distillation does not run. Adding someone by hand still writes the summary on Gemini 3 Flash.

AssetAg distils the asset once, into a fixed intelligence layer — what it is, what it is for, its important details, and what is ambiguous or missing. The asset is the only evidence: the room's memory and transcript are supplied purely to disambiguate a name or a term, and anything a reader could not derive from the asset alone is audited out before it is stored. A link the reader could not open is stored as a failed asset rather than dropped, so the planner can tell the person their page was not accessible instead of silently knowing nothing about it.

AssetReadAg answers one focused question against the original asset later. It is deliberately given no memory and no transcript: a read is an asset-only question, and handing it the conversation lets it answer from chat instead of from the file.

Persona

The speaking agent, and the driver of its conversation. After a message is stored it reads the whole visible room document — everything except plannerNotes, flags, and other agents' plans — plus the roster, the room's goals, the live DEAL STAGE block, the dispatch record, asset intelligence, and clock, then replies and takes the actions described above. Host, visitor, onboarding, and campaign personas share the same voice, memory contract, and deal-stage vocabulary; the visitor persona also runs the mandate check itself. The stage table lives in Design Decisions.

PlannerDistilMemAg (planner-distil-mem)

The background memory writer. It starts at turn start and runs as a fire-and-forget HTTP sub-request (/api/z-agent/distil/callback) on a deep reasoning model (spacexai/grok-4.7). It does not delay the active persona's first token or hold the reply stream open after streaming finishes. It used to be the planner that decided goals and pushes; those decisions now belong to the personas (see Design Decisions), and this role only records.

It records:

  1. Facts — updates structured memory with newly learned facts, offer positions, pricing, and specs. It never writes a deal stage. The stage is the live offer block; memory may hold offers.<key>.offerId only.
  2. Mandates — standing authorities the principal grants in their own pane, with their exact scope and limits.
  3. Operator flags — raises frustration flags if a participant loses patience.

It never touches what the personas own: the goals, each persona's plan, and the principal's one-time answers. It never sends a message. Code enforces this in live rooms: anything Distil writes to those branches is dropped, and any push or room action it returns is ignored. Only scripted replays still apply recorded planner beats. Abandoned of a live new-offer goal is still not a no-op: code maps it to reject-offer and closes the written row (ZLY-1935); met of new-offer still mints via Offer-Gen — whichever agent ruled it.

It sees the full context: the memory document, roster, declared goals, live physical-layer facts, the dispatch record of actually delivered messages, and the recent transcript. Anything a visitor-facing persona must not say belongs under plannerNotes, or is worded in the host branch as a boundary, not a floor.

Interview turns skip Distillation

Chat turns Distil by default. The onboarding interview (/onboarding, and its talent, brand and agency variants) does not run the background memory pass after each message, so guest turns stay cheap; the persona marks the setup goal in its own reply, code saves typed names, and one promotion pass runs at sign-in. Every other surface — Personalize, deal rooms, email, SMS — Distils beside the reply. Distillation always runs in the background: the reply stream closes once persona tokens finish, and a chat turn never waits for it.

Synchronous Distillation for Scripts and Simulations (awaitDistil: true)

Callers that immediately assert or inspect room memory upon stream completion — such as Test agent (demo-simulation.ts), scenario replays (zreplay), and CLI test scripts — pass awaitDistil: true to run and await distillation in-process rather than dispatching it to an external HTTP invocation.

AccUpdate

Rooms are permanent, so a seed at bind goes stale the next time the Arvist writes. Bind itself copies durable memory, or the home host branch when that document is newer. AccUpdate is the inbound counterpart: before the planner runs, if a bound account's durable memory is newer than the last merge into this room, AccUpdate copies standing knowledge into that participant's branch of room memory — who they are, their rules, how they like to be spoken to — and the planner waits behind the write.

It is not a second planner. It does not steer conversations, touch goals, or reorganise the document. It may only write under the participant's own key; reserved branches and everyone else's knowledge are discarded by the mechanism, not by instruction. On conflict the account wins, because durable memory is the curated long-term record. Room-only facts the account document does not mention stay.

A successful merge is announced in the transcript as a system line naming the account and the paths written, so the planner, the guard and the Arvist can see where that knowledge came from. The playground shows the same event as a banner with the change list; the product panes ignore it, so the person in the conversation never sees it.

The planner is told to treat that content as verified standing knowledge from previous rooms — never to delete or quarantine it as unsourced just because this sitting's messages did not mention it.

ZGuard

The auditor. Each agent acts on its own judgement, and the memory writer only records, which leaves a structural blind spot: the last agent message of every sitting is never reviewed by anyone. If the conversation goes quiet after the agent's reply — exactly what happens when a person says "great, do it" and walks away — whatever that reply promised goes unchecked. ZGuard closes the gap: a few minutes after a room's last exchange, it reads the whole room — every pane's recent conversation, the full memory, and the dispatch record, the system's ground-truth list of messages actually delivered — and verifies that the record holds up.

What it checks: claims of completion that nothing supports, memory notes saying something "is being delivered" with no matching dispatch, participants left waiting for a promised message that never went out, deal stage mismatches (an agent declaring a booking while the live offer block shows no BOOKED stage — a relay of the principal's chat instruction is not a mismatch), goal statuses that contradict the transcripts. When it finds a failure it makes the smallest repair: correct the false note, clear the stale instruction, and — only when someone is genuinely owed an undelivered message or an explicit walk-back of an erroneous booking claim — push it through the same mechanism the planner uses. There is no quiet-window or word-overlap filter on those pushes: if a participant is owed an update, Guard says it, and the prompts keep the wording concise.

Guard never writes memory beside Distil. When the quiet-room callback fires, it waits for Distil's lease, claims it, then audits. If Distil is still running after a full lease, Guard re-arms instead of racing it.

Its expected answer, most of the time, is "healthy — no action". The verdict of every run is recorded and shown to operators, so a room's health is visible even when nothing needed fixing. And it deliberately stays in its lane: no strategy changes, no second-guessing judgement calls — a proofreader with a red pen, not a co-author.

Arvist

Rooms are permanent — a host's home room and every visitor's deal room stay open for as long as the relationship does — but a sitting inside one still ends. On a live home room, Arvist runs after Distil for the host so Update Agent commits standing preferences immediately (ZLY-1897). On other rooms — and as a catch-up if that per-turn home run fails — a few minutes after the room goes quiet, on the same debounced signal ZGuard uses and right after ZGuard's own pass, the Arvist decides what the system keeps about each participant who took part, beyond this sitting. It maintains one durable memory document per account — who this person is, their standing rules, how they like to be spoken to, and the deals they have closed — which seeds every room that participant is ever bound to, so the planner never starts from zero. A new deal room can also seed the host branch from the home room when that document is newer than durable, so a z-link opened in the next second still sees the update.

The Arvist is deliberately selective where the planner is generous: the planner records everything that might matter within a room; the Arvist keeps only what will still be true and useful months later. What it drops is gone — this is the only document that survives the room — so a learning left out of it is a learning the system never had.

It is also shown, at call time, when each standing leaf of that document last changed. Those dates are written by code when a value actually changes, not by the Arvist, and they are not part of the document — see When a standing fact was recorded. A floor that has stood since March is a different judgement from a floor the account just moved. The planner does not see those dates today; putting a human look on extremely old facts is future work.

Offer-Gen

The bridge from understanding to record. When the planner rules a negotiation concluded, Offer-Gen reads the room and extracts the exact agreed terms — deliverables, amounts, timeline, usage boundaries — into a formal draft offer in the physical layer. Precision beats completeness: every field it fills must be a term that was actually agreed, and anything not discussed stays empty rather than being padded with an assumption.

The memory then holds only a reference to the offer, never a copy of its terms, so the document of record cannot drift out of sync with the memory's paraphrase of it. Minting the row unlocks the conversation Offer tab. Both sides Approve on that document in the product; brand pay-in is Send Payment after talent Approve. Offer-Gen never takes a card or walks Connect.

Offer-Update

Offer-Gen's counterpart for a negotiation that kept moving. An offer is written once, but the conversation around it does not stop — a price gets raised, a deliverable added — and a host who is told the news must never open an offer that still shows the old terms. So whenever a push message is about to reach the host and the room already references an offer, Offer-Update re-extracts the terms as they stand now, against the same strict schema Offer-Gen used, and brings the live offer row up to date before the host's persona speaks.

It is deliberately conservative in three ways. It diffs before it writes: fields the negotiation never touched keep their stored values, and an extraction identical to the row skips the write entirely. It only touches offers still in DRAFT or APPROVED. Once talent has booked (ACCEPTED and later), or the row is already COUNTERED / REJECTED via the respond API, the terms belong to the parties and it refuses without a model call. A material rewrite of an APPROVED row clears brand_approved_at (BRAND_APPROVAL_CLEARED) so a one-sided brand yes cannot survive new terms (ZLY-1932). And an offer minted by the very run that is pushing is left alone — it was just extracted from the same state and cannot be stale.

An Offer-tab Counter, Reject, or Add click does not write the row on the click and does not block Offer-Update. A counter is a chat line first: Z asks last-look, and only after the answer the persona records it through a post-reply action, which moves the row to Countered through the same offers-system transition, attributed to the side that countered. The other side is always told about the counter, even if the persona did not also send it a message in that reply. Distil still rewrites the row when the agent agrees the terms changed.

Like every back-office role, each invocation — including the refusals and the no-change skips — is recorded as a run with its cost, so there is always an answer to "why does this offer say $15,000 now".

AgreementAg

Offer-Gen's next projection: the Offer is already the record, and AgreementAg turns that record into a lawyer-reviewable English draft. It never sees the room. The only input is the Offer's commercial fields — names, amount, currency, deliverables, usage, sharing — and it must not invent a missing term: no governing law, venue, indemnity, payment schedule, or exclusivity that the Offer did not already hold.

It runs whenever the Offer is minted or its negotiated terms change, and again if someone opens the agreement view while the draft is missing or stale. A fingerprint of those commercial fields decides whether the stored draft is still current; if the Offer changes mid-generation it retries once. A failed agreement run is recorded and must never roll back a valid Offer.

The application, not the model, adds the disclaimer and signature blocks. The draft is never described as signed, binding, or legally approved — those words belong to the parties, not to this agent.

How a turn works

The order matters more than it looks.

The persona answers from the whole visible document. Distil writes after the reply so first token is not stalled on the memory rewrite:

  1. After the message is stored, AccUpdate may merge stale durable memory, then the persona reads the whole visible document (plannerNotes and flags stripped) plus the roster, the live DEAL STAGE block, the dispatch record, and clock.
  2. The persona stream begins immediately. Any goal rulings, messages for other participants, and plan changes the persona made while replying are applied the moment its reply finishes.
  3. Simultaneously, PlannerDistilMemAg is dispatched as a detached background sub-request (/api/z-agent/distil/callback), running on spacexai/grok-4.7 to distill new facts.
  4. When the persona stream finishes, the HTTP response closes immediately so the client chat composer becomes ready. Distil may still be writing. ZGuard is armed with a 1-minute quiet-room debounce. When Guard fires, it waits for Distil's lease, claims it, and only then audits — Distil and Guard never write the same room in parallel. On a live home room, Distil then runs Arvist for the host (not on the reply path). If the user submits another message in the meantime, the new turn does not wait for it. The persona answers from the memory already stored, and the earlier distillation finishes in the background. Writers still take turns on the room's memory lease, so two of them never write the same room at once.

When an agent reaches out

Most turns are reactive: a person writes, the system answers. But some things must not wait for the person to write — a deal came together, information they were waiting for arrived, the principal gave an instruction. For these, the persona speaking where it happened sends a message as part of its turn: who to tell, and the substance that must be conveyed.

The recipient's persona agent is then woken to deliver it in its own voice. The sender supplies the what — everything the recipient's agent needs, since the recipient may know nothing yet — and the recipient's agent supplies the how. The substance is always written from the recipient's point of view: participants do not know other conversations exist, so nothing that arrives ever refers to "the visitor's chat" — from their side, news simply arrives.

The full lifecycle

The host appears at the beginning and the end. Everything between is handled without them — but nothing books them until the written offer reaches a BOOKED stage.

Who can see what

This table is the security model in one place.

RoleSees a participant's wordsSees other conversationsSees full memoryCan write memory
Persona agentTheir own participant onlyNeverYes — minus plannerNotes, flags, and other agents' plansGoal rulings, its own plan, and the principal's one-time answers — never facts
PlannerDistilMemAgEveryone'sYesYesRoom memory — facts only, never goals or plans
AccUpdateNoNoThe room, plus that account's durable memoryYes — that participant's branch only
ZGuardEveryone's, when the room is quietYesYesRepairs only — room memory
ArvistHome host after Distil; otherwise when a room goes quietYesYesDurable account memory only
Offer-GenEveryone's, when triggeredYesYesThe formal offer only
Offer-UpdateEveryone's, before a host pushYesYesThe existing offer's terms only
AgreementAgNo — the stored Offer onlyNoNoThe agreement document only
AssetAgCurrent conversation, to disambiguate a nameNoYes, for disambiguation onlyThe asset's fixed intelligence only
AssetReadAgNoNoNoNo

Two boundaries are enforced by the mechanism rather than by instructions: plannerNotes and flags are stripped before any persona sees the document, and each persona is shown only its own plan. Plans are never offered to the person as memories and never carried into durable account memory. The visitor persona is told not to reveal the host floor or that a mandate system exists; that line is prompt-guarded. What Distil writes into plannerNotes stays private.

Prompts are the code

The instructions each role follows live in the codebase, not in a room setting. The playground and ops dashboard still let an operator pick models; they do not edit prompts. A prompt change is a deploy, so every existing room — a host's home included — picks it up on the next turn. See Design Decisions.