dispo-blast skill
Role: the buyer-side broadcast lane. After a deal moves to dispo (HubSpot dispo pipeline 816046), this skill blasts to qualified buyer cohorts across 4 channels.
This skill wraps existing code: dispo-blast-engine.js (canonical 4-channel engine) + per-channel scripts + the InvestorBase scoring lib.
⚡ Execution-mode model routing (G-MODEL-ROUTING-AT-EXEC)
This skill is execution-mode by definition. Per CLAUDE.md “MANDATORY: Execution-Mode Model Routing” + feedback_model_routing_at_exec.md:
Step 1 of every invocation: decompose the work and dispatch via the Agent tool with model: "sonnet" using the 8-element prompt contract (task goal, evidence, tool to use, test requirements, acceptance criteria, rollback command, rule constraints, output format).
Opus (main conversation) role = dispatcher + verifier + Henry-gate. Direct Opus script invocation / code edits = governance violation unless the work fails the Sonnet rubric (novel architecture, rule arbitration, Henry decision gate, first instance of a new pattern). Action-gate per CLAUDE.md still requires Henry approval for any live blast regardless of which model drafted the call — NEVER run without --dry-run unless user explicitly authorizes live blast. Verify each subagent report against acceptance criteria before marking complete.
Architecture
HubSpot dispo deal (status: dispositioning) OR scheduled showing
│
▼
[dispo-blast-engine.js] — unified 4-channel coordinator
│
├─ Channel 1: Showing-day blast (ALL prior buyers + watchlist)
│ └─ [showing-blast-generic.js] / [showing-blast-compiled.js]
│
├─ Channel 2: CRMLS agents
│ └─ [dispo-crmls-agent-blast.js]
│ └─ Targets agents who recently sold in zip + price band
│
├─ Channel 3: InvestorBase end-buyers
│ └─ [dispo-investorbase-blast.js]
│ └─ [ib-blast-scorer.js] — fit score per buyer (zip, beds, price band, condition prefs)
│ └─ [ib-blast-history.js] — 4 dedup checks before blast
│ └─ [ib-blast-message.js] — channel-specific message builder
│
├─ Channel 4: PropStream flippers (cold)
│ └─ [dispo-propstream-blast.js]
│ └─ Targets flippers in market with 3+ recent purchases
│
▼
[dispo-send.js] — same chokepoint as acquisitions-outreach
│
▼
SalesMsg / OpenPhone
PRE-BLAST PREVIEW:
[blast-preview.js] — shows estimated count, cost, dedup hits before triggering
4-channel breakdown
| # | Channel | Buyer cohort | Source data | Dedup window |
|---|---|---|---|---|
| 1 | Showing-day | All prior buyers + watchlist | HubSpot Contacts tagged ‘buyer’ | 24h pre-showing |
| 2 | CRMLS agents | Active LA agents | CRMLS API (recent sales by zip+price) | 30d per agent |
| 3 | InvestorBase | End-buyers in our InvestorBase platform | InvestorBase API (workspace/scripts/lib/investorbase/) | 7d per (deal, buyer) |
| 4 | PropStream | Cold flippers w/ recent activity | PropStream API (zip + recent_purchases ≥3) | 90d per flipper |
InvestorBase scoring (Channel 3 detail)
ib-blast-scorer.js fit score per buyer:
| Factor | Weight | Threshold |
|---|---|---|
| Zip match | 25% | exact zip OR adjacent zip |
| Bed count | 15% | ±1 bed |
| Price band | 20% | within ±20% of asking |
| Condition pref | 20% | matches buyer’s stated rehab tolerance |
| Recent activity | 10% | bought in last 90d |
| Deal velocity | 10% | hot deal (views > 100 in 24h) |
Buyers with score ≥60 receive blast. Buyers below threshold are logged but skipped.
Pre-blast history checks (ib-blast-history.js — 4 checks)
Before any send to a buyer:
- Already-blasted check — sent this exact deal to this buyer in last 7d?
- Recent-engagement check — buyer responded to ANY blast in last 14d? (yes = quieter, lower priority)
- Saturation check — sent ≥3 blasts to this buyer in last 7d? (yes = skip)
- Suppression check — buyer in suppression list?
Any check fails → blast skipped, reason logged.
Capabilities (entry points)
Unified 4-channel blast — dispo-blast-engine.js
node /home/opsadmin/.openclaw/workspace/scripts/dispo-blast-engine.js \
--deal-id=<HS dispo deal ID> \
[--channels=1,2,3,4] # default: all 4
[--limit-per-channel=200]
[--dry-run]Per-channel scripts (use when targeting a single channel)
# Channel 2 — CRMLS agents
node /home/opsadmin/.openclaw/workspace/scripts/dispo-crmls-agent-blast.js \
--deal-id=X --dry-run
# Channel 3 — InvestorBase end-buyers
node /home/opsadmin/.openclaw/workspace/scripts/dispo-investorbase-blast.js \
--deal-id=X --min-score=60 --dry-run
# Channel 4 — PropStream flippers
node /home/opsadmin/.openclaw/workspace/scripts/dispo-propstream-blast.js \
--deal-id=X --zip=33101 --dry-runShowing-day blasts
# Generic showing-day announcement
node /home/opsadmin/.openclaw/workspace/scripts/showing-blast-generic.js --deal-id=X --dry-run
# Compiled (multi-deal, weekly)
node /home/opsadmin/.openclaw/workspace/scripts/showing-blast-compiled.js --week=2026-W17 --dry-runWeekly inventory broadcast — w4-broadcast-blast.js
Wednesday morning summary of all deals in pipeline to all buyer cohorts:
node /home/opsadmin/.openclaw/workspace/scripts/w4-broadcast-blast.js --dry-runPre-blast preview — blast-preview.js
Shows estimated count, cost, dedup hits before triggering the actual blast:
node -e "
const p = require('/home/opsadmin/.openclaw/workspace/scripts/lib/blast-preview.js');
p.preview({ dealId: 'X', channels: [3, 4] }).then(r => console.log(JSON.stringify(r, null, 2)));
"Blast response audit — audit-blast-responses.js
Post-blast: which buyers replied, how, classification:
node /home/opsadmin/.openclaw/workspace/scripts/audit-blast-responses.js \
--deal-id=XTriggers
User says any of:
- “blast deal X to buyers”
- “send to InvestorBase”
- “showing day blast for X”
- “weekly w4 broadcast”
- “PropStream cold blast”
- “CRMLS agent blast”
- “audit blast responses for X”
For Stage 1 outreach to CH (acquisitions side), route to acquisitions-outreach skill.
Capabilities NOT in this skill
| Capability | Lives in |
|---|---|
| CH-side acquisition outreach | acquisitions-outreach skill |
| Reply handling for blast responses | acquisitions-followup skill (handles inbound from any channel) |
| HubSpot deal creation | hubspot-deal-ingest skill |
| Buyer onboarding to InvestorBase | InvestorBase platform UI (separate) |
Failure modes
| Symptom | Cause | Recovery |
|---|---|---|
0 buyers matched for IB blast | scoring threshold too high | lower --min-score; check buyer database freshness |
429 from SalesMsg/OpenPhone | line saturation | lower --limit-per-channel; rotate lines |
CRMLS API auth fail | token rotated | refresh from 1P; check master.env CRMLS section |
PropStream rate limit | hit daily quota | retry tomorrow; check quota in master.env |
| Blast went to suppressed buyer | history check missed | check messaging_suppression table; surface for manual review |
| Inbound replies not routing back | webhook handler down | check acquisitions-followup skill (it handles all inbound) |
Memory cross-references
- feedback_blast_inbox_required.md — blast must hit inbox not spam
- feedback_sms_160_char_at_scale.md — 160-char limit at scale
- feedback_dispo_scorer_rules.md — IB scoring rules
- feedback_no_em_dash.md — no em-dashes in outbound
- feedback_salesmsg_mention_syntax.md — mention syntax
Related code (the implementation)
workspace/scripts/dispo-blast-engine.js— unified 4-channel coordinatorworkspace/scripts/dispo-crmls-agent-blast.js— Channel 2workspace/scripts/dispo-investorbase-blast.js— Channel 3workspace/scripts/dispo-propstream-blast.js— Channel 4workspace/scripts/showing-blast-generic.js/showing-blast-compiled.js— Channel 1workspace/scripts/w4-broadcast-blast.js— weekly inventoryworkspace/scripts/audit-blast-responses.js— post-blast auditworkspace/scripts/lib/investorbase/ib-blast-scorer.js— fit scoringworkspace/scripts/lib/investorbase/ib-blast-history.js— 4-check dedupworkspace/scripts/lib/investorbase/ib-blast-message.js— message builderworkspace/scripts/lib/blast-preview.js— pre-blast statsworkspace/knowledge-base/investorbase/API.md— InvestorBase API reference