Zettleinby Happy Sandboy
Menu

05 · Orchestration

From a listing page to a signed deal — without a single re-introduction.

Brokher.com already exists. Proassistly already exists. This page defines the choreography that lets Zettlein pick up the visitor mid-scroll, inherit everything Brokher knows, act inside Proassistly on their behalf, and hand the thread back — with full context intact on every surface.

A · The handoff problem

Three systems, one continuous transaction.

Brokher.com

Discovery surface

Public, SEO, anonymous

Renders a read-model of listings, agents and markets. Owns URL, locale, device, referrer, utm, and any anonymous browsing signal.

Zettlein

Conversation surface

Stateful, multilingual

The agent runtime. Holds a session, a memory, a tool belt, and a plan. Never touches the DB — only calls Proassistly RPC.

Proassistly

System of record

Auth, ledger, workflow

Owns identity, listings, leads, offers, escrow, documents, payouts. The single write path. Emits domain events to everyone else.

Losing context happens at three seams: anonymous → identified, conversation → transaction, and agent → human operator. The orchestration below is designed so no seam ever asks the user to repeat themselves.

B · The context envelope

One signed payload that every surface can read.

When Brokher hands a visitor to Zettlein, it does not pass a URL. It passes a signed Context Envelope — a short-lived JWT + JSON blob that captures who, where, what and why. Zettlein hydrates its session from it; Proassistly trusts it as a first-party claim.

 ┌─────────────────────────  context envelope (JWT, 10 min TTL)  ─────────────────────────┐
 │ sub          : anon:visitor-8f1c…  |  user:pa_01HK…    (upgrades in-place on sign-in) │
 │ tenant       : kh-phnompenh                                                            │
 │ locale       : km-KH   fallback: en                                                    │
 │ surface      : brokher.com   route: /listing/BKK1-2BR-riverside                        │
 │ intent       : buyer.inquire | seller.list | investor.brief | landlord.rent            │
 │ subject_ref  : listing:lst_01HK7Z…  (or  agent:…, project:…, none)                     │
 │ signals      : { budget_hint, viewed_ids[8], time_on_page, saved_search_id }           │
 │ consent      : { pii:false, comms:false, marketing:false }                             │
 │ resume_token : zt_res_9c4a…   ← lets any surface reopen this exact thread              │
 │ sig (HS256, Proassistly-issued, verified by Zettlein and by any embedded widget)       │
 └────────────────────────────────────────────────────────────────────────────────────────┘

Identity ladder

anon → known → verified. The sub upgrades in-place. No thread is ever orphaned when the visitor signs in — the same resume_token keeps working.

Subject binding

Every envelope pins a subject_ref. Zettlein always opens scoped to that listing/agent/project, never as a generic chatbot.

Consent-scoped

Zettlein cannot ask for phone/ID/email unless consent.piiflips true. Guardrails read this flag, not the prompt.

C · The choreography

Twelve steps from hover to signature.

#ActorActionWhat actually moves
01Brokher.comMints envelopeOn listing view, calls POST /rpc/v1/session.mint on Proassistly with anon cookie + page context. Receives JWT + resume_token.
02Brokher.comBoots Zettlein widget<script src='zettlein.js' data-envelope='…'/> mounts a collapsed launcher. Zero re-render of the page.
03VisitorOpens ZettleinWidget expands. Zettlein greets in envelope.locale, referencing subject_ref by name: 'The BKK1 2BR — want a quick tour of the numbers?'
04ZettleinHydrates memoryCalls tools.read.listing.get, tools.read.market.brief, tools.read.saved_search.get. Reads only — no PII collected yet.
05VisitorAsks to see itZettlein proposes 3 slots. Needs contact → asks consent inline. On accept, consent.pii flips; envelope re-signed.
06ZettleinWrites to Proassistlytools.write.lead.upsert + tools.write.viewing.request. Proassistly returns lead_id and viewing_id — both stitched to sub.
07ProassistlyEmits eventslead.created, viewing.requested → CRM inbox for agent, calendar hold, SMS/Telegram OTP. Brokher's read-model updates via projection.
08VisitorLeaves the tabNothing lost. resume_token persists in httpOnly cookie + email magic link. Thread state saved server-side on Proassistly.
09VisitorReturns via email linkAny surface — Brokher, Zettlein standalone, Proassistly buyer portal — accepts the resume_token and rehydrates the exact turn.
10ZettleinAdvances the dealtools.write.offer.draft → tools.write.reservation.create. Zettlein assembles the reservation agreement from Proassistly's legal DSL.
11ProassistlyEscrow + signatureEscrow ledger opens; ABA/Wing payment intent issued. DocuSign-equivalent sends. Zettlein watches doc.signed event.
12Zettlein → HumanWarm handoffAgent inbox receives full transcript, envelope history, action log. Zettlein stays in the thread as co-pilot, not replaced.

D · Sequence

What the wire looks like.

 Visitor        Brokher.com          Zettlein Runtime         Proassistly RPC
   │                │                     │                        │
   │─── view ──────▶│                     │                        │
   │                │── session.mint ────────────────────────────▶ │
   │                │◀────── envelope (JWT + resume_token) ────────│
   │                │── boot widget(envelope) ──▶│                 │
   │◀── greeting ──────────────────────────────  │                 │
   │─── "book a viewing" ───────────────────────▶│                 │
   │                                             │── read.listing.get ─▶│
   │                                             │◀──── listing DTO ────│
   │                                             │── read.availability ─▶│
   │                                             │◀──── slots ──────────│
   │◀── proposes 3 slots ────────────────────────│                 │
   │─── "yes, +855 12…" ────────────────────────▶│                 │
   │                                             │── write.lead.upsert ─▶│
   │                                             │── write.viewing.req ─▶│
   │                                             │◀── ids + events ─────│
   │◀── confirmed, calendar hold ────────────────│                 │
   │                                             │                 │
   │  (closes tab)                               │  thread persisted ▲
   │                                             │                 │
   │─── clicks email link (resume_token) ─────────────────────────▶│
   │                │◀── redirect to brokher.com/thread/… ─────────│
   │                │── boot widget(resume) ────▶│                 │
   │◀── "welcome back, the viewing is Thu 3pm — want to draft an offer?"

E · RPC contract

Zettlein never touches the DB. Proassistly exposes /rpc/v1.

Read plane — idempotent, cacheable

Projection reads

  • session.mint · session.resume
  • read.listing.get · read.listing.search
  • read.market.brief · read.comparables
  • read.availability.get · read.saved_search.get
  • read.thread.get · read.legal.template.get

Write plane — audited, event-sourced

Guarded by consent + role

  • write.lead.upsert · write.consent.grant
  • write.viewing.request · write.message.send
  • write.offer.draft · write.offer.submit
  • write.reservation.create · write.escrow.open
  • write.document.assemble · write.signature.request

Every write returns a command_id. Zettlein stores it on the turn so retries are safe. Every write emits a domain event so Brokher's read-model and Zettlein's RAG index stay in lockstep.

F · Continuity guarantees

The four rules that make context untouchable.

01

One thread, many surfaces

thread_id is issued by Proassistly at session.mintand travels with the envelope. Brokher, Zettlein and the Proassistly portal all render the same thread — never a copy.

02

State on the server, not the tab

Zettlein persists turns to Proassistly after every tool call. Closing the browser, switching devices, or a 3G drop cannot lose progress.

03

Anon → identified is a merge, not a restart

When the visitor signs in, Proassistly merges the anon subinto the user account. Lead, viewings, offers and transcripts follow.

04

Human takeover keeps the agent alive

When an agent opens the CRM thread, Zettlein flips from actor to co-pilot: it drafts replies, summarizes, and executes tools only under agent_approved flag.

G · Failure modes we design against

Where handoffs usually die — and how this one doesn't.

FailureWhere it strikesMitigation
Envelope expiry mid-chatLong viewings, mobile sleepSilent refresh via resume_token; user never sees a login prompt.
Duplicate lead on re-entryAnon → known merge racewrite.lead.upsert is idempotent on (sub, subject_ref).
Zettlein hallucinated priceRAG stale, projection lagPrices are never in prompt — always fetched fresh via read.listing.get at answer time.
Agent responds in CRM, visitor doesn't see itSplit inboxAgent messages travel through the same thread_id — pushed into Zettlein UI as an actor turn.
Payment intent orphanedEscrow opened, user dropsescrow.opened event triggers reminder sequence across email + Telegram, resumable to the same thread.

H · Rollout

Ship the orchestration in four surgical drops.

Drop 1

Envelope + widget

2 weeks

session.mint, JWT signing, embed script on brokher.com/listing/*. Zettlein greets, no writes yet.

Drop 2

Read plane

3 weeks

All read.* RPCs wired. Zettlein can answer factually with zero hallucination surface on price and availability.

Drop 3

Write plane — soft

3 weeks

lead.upsert, viewing.request,message.send. Human agent still closes the deal.

Drop 4

Write plane — deep

6 weeks

offer.*, reservation.*, escrow.*,signature.*. Zettlein now walks a buyer to signature under agent supervision.

One sentence to remember.

Brokher owns discovery, Proassistly owns truth, and Zettlein owns the thread that carries the visitor from the first hover to the last signature — without ever asking them where they left off.