Do we already know them?reads: entry payload · src: PLG capture / channel
🔥 Warm
Have email + name (maybe role). No email ask; enrichment pre-ran before arrival.
❄ Cold
Anonymous. Email is the one ask; enrichment fires after, on the critical path.
Fallback: "warm" but identity stale/unverified → soft-confirm instead of assuming.
sets identity.known · identity.{email,name,role?}
↓
Enrich — resolve the profilereads: email domain + public web · src: 6-agent swarm
The swarm
Domain→site · Classifier · Size · Role · Project-finder · Network-seeder → one profile.
Confidence-scored
Each field carries a confidence. High → assert (confirm). Low → ask that one field.
Fallback (the smart bit): thin data → don't guess; ask only the unresolved fields. ~100% bar — a wrong pre-fill is worse than a blank.
sets profile.{company,type,size,role,trade,pursuits[],network[],confidence}
↓
What kind of account?reads: profile.type · src: Business classifier
GC (contractor)
Picks sectors. Pipeline/portfolio surfaces.
Sub (sub_contractor)
Picks a trade → routes through G5.
Homeowner (consumer)
Different product (marketplace / milestones). Out of this funnel's scope.
Fallback: ambiguous → one tap "GC / Sub / Homeowner" (the only structural question we ever force).
sets profile.type ∈ {gc, sub, ho}
↓
Which track? (size / ACV)reads: profile.size + modeled ACV · src: Size estimator
⚡ Self-serve
<$10M. Agent is the salesperson — closes trial→card in-flow.
🤝 White-glove · mid
$10–100M sub / $10–500M GC. Land a seat, expand to the dept.
🤝 White-glove · enterprise
$100M+ sub / $500M+ GC. Outbound, exec buyer, full MEDDPICC.
Fallback: revenue vs ACV disagree → ACV wins (open Q). Borderline → start self-serve, flag for sales.
sets route.track ∈ {self_serve, wg_mid, wg_enterprise}
↓
Who landed? → the wow + surfacereads: profile.role · src: Role resolver
Estimator
Takeoff wow (Sarah) · Jobs. "What are you bidding this week?"
Owner / Exec
Portfolio + margin (Tom · Analytics). "Where did you lose your last 5 bids?"
PM / PE
Board + RFIs (Carlos · Network). "Your open RFIs + at-risk dates."
Foreman / Field
Route OUT of chat → Field Ops / mobile. Different door entirely.
Fallback: role unknown → the one allowed ask (warm = a tap; cold = captured at confirm).
sets route.persona ∈ {estimator, owner_exec, pm, field} · route.surface
↓
Which trade? (subs only)reads: profile.trade · src: Classifier · 34 canonical
Counts — e.g. Electrical
/electrical · device/gear counts + lead-time flag.
Volume — e.g. Concrete
/concrete · CY + formwork SF + rebar tons.
Area — e.g. Drywall
/drywall · wall area by type + LF of track.
Fallback: trade unclear → generic sub lander, ask trade. (GC skips this gate.)
sets trade.{lander, takeoff_recipe, value_framing}
↓
Compose the next questionderived: persona × size × trade
The conversion lever
One motivating ask that reveals $ AND pulls real data in. This is the line the team signs off on per cell.
Note: not a branch — a template filled from the gates above. The single highest-leverage string in the whole flow.
sets next_question (string template)
↓
How do we close?reads: route.track
⚡ In-flow
Self-serve → agent closes: trial auto-starts → card after the wow. One session.
🤝 AE + MEDDPICC
White-glove → agents arm the AE → MEDDPICC → annual enterprise contract + migration.
Fallback: self-serve stalls or expands past threshold → hand off to white-glove (assisted).
sets close.motion ∈ {in_flow_card, ae_meddpicc}
Each gate as a programmable unit. This is the backbone Brock asked for — wire a state machine + events onto these, and the data model is the union of the variables set.