In wealth and advisory the relationship isn't a person — it's a household, and growth comes from referrals, not cold outbound. And no lead advances on score alone: an un-cleared KYC status stops it cold, compliance over judgement. A generic CRM models none of this — it scores an individual contact on fit and lets a high score win. This overlay makes the household the unit, the referral network a graph, and the KYC check a hard gate the model can never override.
The problem — and the money
The buyer is the advisory firm's RevOps/compliance lead who must roll pipeline up to the household,
grow through referrals, and never let a lead auto-qualify while its client's KYC is unresolved. A
vanilla CRM will happily qualify the highest-scoring lead — even one that compliance has flagged. The
payoff here is a refusal: a lead scoring 0.88 but with kyc_status = 'review' is routed to a human
as compliance_hold, ranked below a cleared lead scoring only 0.86. The hard gate overrides the
score, exactly as compliance requires. That's the why — in finserv, the deterministic gate is the
feature.
This is the CRM suite + the finserv overlay. The seven base skills — core, qualification-scoring, account-360 and the rest — are unchanged. The overlay only adds two tables, KYC/AUM columns, two graph edge kinds, and a deterministic gate layered over the MEDDIC verdict. Same bucket, one copy of the rows.
What finserv adds
A small, additive diff on top of the suite (skill: crm/finserv → the
crm/overlays/finserv contract):
- Two tables —
households(keyhousehold_id: the relationship unit, withtotal_aumandadvisor) andreferrals(keyreferral_id: the finserv growth engine, from-contact → to-contact). - KYC / AUM columns — on
crm/corecontacts(household_id,kyc_status,risk_rating) andaccounts(aum,client_since) — added withALTER TABLE … ADD COLUMNover the pg wire. - Two graph edge kinds —
member_of_household(contact → household) andreferred_by(contact → contact), inserted into the suite'scrm_edge. - One gate change — qualification-scoring defaults to MEDDIC and layers a deterministic KYC
hard gate over the model verdict:
compliance_holdwhenkyc_status <> 'cleared', regardless of score. The model never overrides compliance.
Apply the diff — the ALTERs, the two tables, and the KYC seed that drives the gate:
In the crm bucket, apply the finserv overlay: ALTER contacts ADD household_id, kyc_status, risk_rating; ALTER accounts ADD aum, client_since; create households + referrals; seed a household with Rory (kyc_status review) and Gwen (kyc_status cleared), and a gwen->rory referral.
data_pg→data_table_create→data_table_upsertAdded household_id/kyc_status/risk_rating to contacts and aum/client_since to accounts (ALTER over pg — the masters, not a copy). Created households + referrals. Seeded household hh-1 with Gwen (kyc_status cleared) and Rory (kyc_status review), and a gwen->rory referral — the inputs the hard gate reads.
export BUCKET=crm # the SAME bucket the CRM suite built — the overlay only ADDs
# 1) KYC / AUM column-adds on the crm/core masters — ALTER over the pg wire, never a second copy
dodil data pg -b "$BUCKET" "ALTER TABLE contacts ADD COLUMN household_id VARCHAR"
dodil data pg -b "$BUCKET" "ALTER TABLE contacts ADD COLUMN kyc_status VARCHAR"
dodil data pg -b "$BUCKET" "ALTER TABLE contacts ADD COLUMN risk_rating VARCHAR"
dodil data pg -b "$BUCKET" "ALTER TABLE accounts ADD COLUMN aum DOUBLE"
dodil data pg -b "$BUCKET" "ALTER TABLE accounts ADD COLUMN client_since VARCHAR"
# 2) the relationship unit + the growth engine
dodil data table create households -b "$BUCKET" --merge-key household_id \
--columns-json '[{"name":"household_id","type":"string","nullable":false},{"name":"name","type":"string"},{"name":"primary_contact_email","type":"string"},{"name":"total_aum","type":"double"},{"name":"advisor","type":"string"}]'
dodil data table create referrals -b "$BUCKET" --merge-key referral_id \
--columns-json '[{"name":"referral_id","type":"string","nullable":false},{"name":"from_contact","type":"string"},{"name":"to_contact","type":"string"},{"name":"household_id","type":"string"},{"name":"status","type":"string"},{"name":"value","type":"double"}]'
# 3) seed the household + the KYC status the hard gate reads (partial merge onto existing contacts)
dodil data table upsert households -b "$BUCKET" --row '{"household_id":"hh-1","name":"The Okoro Household","primary_contact_email":"[email protected]","total_aum":2400000,"advisor":"m.stone"}'
dodil data table upsert contacts -b "$BUCKET" --merge --row '{"email":"[email protected]","household_id":"hh-1","kyc_status":"cleared","risk_rating":"low"}'
dodil data table upsert contacts -b "$BUCKET" --merge --row '{"email":"[email protected]","household_id":"hh-1","kyc_status":"review","risk_rating":"medium"}'
dodil data table upsert referrals -b "$BUCKET" --row '{"referral_id":"ref-1","from_contact":"[email protected]","to_contact":"[email protected]","household_id":"hh-1","status":"open","value":50000}'Now the payoff — the KYC hard gate overriding a higher score, and the household traversal:
In crm, compute each lead's verdict as CASE WHEN kyc_status <> 'cleared' THEN 'compliance_hold' WHEN score >= 0.80 THEN 'qualified' ELSE 'working' END, joining leads to their contact's kyc_status. Then list the members of household hh-1 over the graph.
data_sql→data_boltRory (score 0.88, kyc_status review) = compliance_hold — routed to a human DESPITE the higher score. Gwen (score 0.86, kyc_status cleared) = qualified. The hard gate overrides score, exactly as compliance requires. Household traversal from hh-1 returns both members (Gwen, Rory).
# the deterministic KYC hard gate — layered OVER the MEDDIC model verdict
dodil data sql -b "$BUCKET" "
SELECT c.email, l.score, c.kyc_status,
CASE WHEN c.kyc_status <> 'cleared' THEN 'compliance_hold'
WHEN l.score >= 0.80 THEN 'qualified'
ELSE 'working' END AS verdict
FROM leads l JOIN contacts c ON c.email = l.contact_email
ORDER BY l.score DESC"
# [email protected] | 0.88 | review | compliance_hold <- higher score, still held
# [email protected] | 0.86 | cleared | qualified
# household membership over the graph (insert the edges, then re-CREATE crm_graph; anchor the LEFT node)
dodil data bolt -b "$BUCKET" -g crm_graph \
"MATCH (h)<-[:member_of_household]-(m) WHERE id(h)=200001 RETURN m"
# -> 2 household members (Rory 10002, Gwen 10001)The MEDDIC scorer still runs on kimi-k2.6 with its six-dim JSON contract unchanged — but its verdict
can never promote a lead past the compliance check. The hard gate is a deterministic SQL CASE, not a
model call; that's the point.
Scaffold it — the one-shot
With the DODIL MCP connected, one prompt composes the suite and this overlay:
Scaffold a CRM for my wealth-advisory firm — the CRM suite plus the finserv overlay.
Base: the full crm/core suite (accounts, contacts, leads, opportunities, activities; the
account-hierarchy graph; qualification-scoring + account-360).
Then apply the finserv overlay on the SAME bucket:
1. ALTER contacts ADD household_id, kyc_status, risk_rating; ALTER accounts ADD aum, client_since
(pg wire — the masters, not a copy).
2. Create households (key household_id) and referrals (key referral_id).
3. Insert member_of_household + referred_by edges into crm_edge, then (re-)CREATE crm_graph.
4. Default qualification-scoring to MEDDIC and layer a DETERMINISTIC KYC hard gate over the verdict:
compliance_hold when kyc_status <> 'cleared', regardless of score.
Seed the demo above and show the hard gate holding the higher-scoring un-cleared lead.Verify
Every result below was live-validated on 2026-09-02 (org IHDIASH, throwaway bucket, torn down after):
# columns land
dodil data pg -b "$BUCKET" "ALTER TABLE contacts ADD COLUMN kyc_status VARCHAR" # + household_id/risk_rating
# the hard gate holds the HIGHER score: Rory (0.88, review) = compliance_hold; Gwen (0.86, cleared) = qualified
dodil data sql -b "$BUCKET" "SELECT c.email, l.score, c.kyc_status FROM leads l
JOIN contacts c ON c.email = l.contact_email ORDER BY l.score DESC"
# household graph: 2 members under hh-1
dodil data bolt -b "$BUCKET" -g crm_graph "MATCH (h)<-[:member_of_household]-(m) WHERE id(h)=200001 RETURN m"The ALTER-adds, the two tables, the KYC hard gate overriding score, and the household graph traversal are proven live. The suitability/compliance prompt clauses in the MEDDIC gate reuse the six-dim gate already proven in the base qualification-scoring skill.
Connect your tools
Everything the overlay wrote lives in the one DataK3 bucket, reachable by your own stack — a compliance
dashboard reads contacts.kyc_status over the Postgres wire; a referral analytics tool walks the
referred_by graph over Bolt. data connect crm prints the endpoints. Full, live-validated
walkthrough: Connect your tools.
Composes
This page is not a fork of the CRM — it is a composition:
- Base: the seven CRM suite skills (
crm/core,crm/qualification-scoring,crm/account-360, …) — the system of record, unchanged. - Overlay:
crm/overlays/finserv— the additive diff above (households + referrals, the KYC/AUM columns, the household/referral graph, the deterministic KYC hard gate over MEDDIC).
Read the base to learn the mechanics; this overlay is the small, industry-specific diff on top.