acquisitions-outreach skill
Role: the first-touch outreach lane. Takes a deal that’s been ingested (via hubspot-deal-ingest) and sends compliance-gated initial SMS to the contract holder.
This skill wraps existing code across workspace/scripts/workers/, workspace/scripts/lib/, and deal-qualification/templates/sms/. It does not reimplement the engines.
⚡ 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 send regardless of which model drafted the call. Verify each subagent report against acceptance criteria before marking complete.
Architecture
acquisition_deals row
│
▼
[checkApprovalGate()] — B7 three-zone gate (Green auto / Yellow 4h hold / Red CLI-only)
│ approved
▼
[unified-outreach-engine.js] — coordinator
│
├─→ [gate-computer.js] — 5-gate qualification progression (thread-context-aware)
├─→ [compliance-gate.js] — quiet hours, state TZ, TCPA/CTIA
├─→ [blast-safety.js] — cross-deal dedup, silence penalty
├─→ [thread-context.js] — prior message history (gate skip + dedup)
├─→ [mentor-voice.js] — touch→mentor cadence (Ferry→Damji→Norton→Cardone→Terry)
├─→ [response-generator.js] — v3 Dispo Conversion Agent (5,138-msg trained)
│ OR
├─→ template lookup: deal-qualification/templates/sms/{scenario}.json
│
▼
[openphone-sms.js] — OpenPhone send (acq is OpenPhone-only)
│
▼
[quo-note-builder.js] — §4.1 canonical 7-line Quo internal note (fail-closed on missing lineId)
│
▼
openphone_events Supabase table (audit trail)
B7 Approval Gate (three zones)
Checked before every first_touch send via checkApprovalGate(deal):
| Zone | Condition | Behavior |
|---|---|---|
| 🟢 Green | reri_arv_flag is null or 'ok' | Auto-approve, engine picks up immediately |
| 🟡 Yellow | reri_arv_flag='pre_negotiation' | Hold 4h — deal-outreach-auto-approver.js (timer every 30 min) flips qualification_status='approved' after window |
| 🔴 Red | reri_arv_flag='manual_review_required' | CLI only: node deal-outreach-worker.js --deal-id=X --approve |
qualification_status='approved' overrides any flag. qualification_status='rejected' blocks permanently.
5-gate qualification progression (gate-computer.js)
Gates reflect what data we still need from the CH. Thread context is checked first — if thread mentions a gate item, that gate is skipped.
| Gate | Name | Needs |
|---|---|---|
| 1 | Identity & Interest | Full address + asking price |
| 2 | Visual & Access | Photos or access scheduled |
| 3 | Vacancy & Possession | Occupancy status |
| 4 | Condition Assessment | Repair scope + known issues |
| 5 | Full Qualification | Price, timeline, urgency, access all confirmed |
resolveStage(deal, gateResult) maps gate number → template scenario, with reri_pre_negotiation_triggered=true always routing to 'pre-negotiation' regardless of gate.
Mentor voice cadence (mentor-voice.js)
One question per send (Mike Ferry rule). Mentor changes with touch count:
| Touch | Mentor | Voice |
|---|---|---|
| T1-2 | Mike Ferry | Direct, confident, one question, no filler |
| T3-6 | Mandeep Damji | Relational, warm, references their situation |
| T7-13 | David Norton | Certain, anchors commitment, no hedging |
| T14-20 | Grant Cardone | Urgency, scarcity framing, action-oriented |
| T21+ | Sean Terry | Objection-counter, floor-probing |
SMS templates (5 scenarios)
| Scenario | Template file | Trigger |
|---|---|---|
| initial-outreach | sms/initial-outreach.json | Gate 1, no pre-negotiation flag |
| detail-request | sms/detail-request.json | Gate 2-3 |
| gap-fill | sms/gap-fill.json | Gate 4 |
| pre-negotiation | sms/pre-negotiation.json | reri_pre_negotiation_triggered=true (tight spread) |
| offer-inquiry | sms/offer-inquiry.json | Gate 5 / qualifying deal |
Stage 1 (first_touch) is the only one this skill sends; stages 2-4 cadence is handled by acquisitions-followup.
Capabilities (entry points)
Bulk worker — unified-outreach-engine.js (canonical)
node /home/opsadmin/.openclaw/workspace/scripts/workers/unified-outreach-engine.js \
[--state=FL] # filter to deals in this state
[--limit=N] # max deals per run
[--dry-run] # gate + compose, NO send
[--phase=new|followup|all] # filter by action typeSingle-deal trigger — deal-outreach-worker.js
node /home/opsadmin/.openclaw/workspace/scripts/workers/deal-outreach-worker.js \
--deal-id=<acquisition_deals.id> \
[--approve] # also flip qualification_status='approved' (red-zone override)
[--dry-run]Yellow-zone auto-approver (background)
node /home/opsadmin/.openclaw/workspace/scripts/workers/deal-outreach-auto-approver.js \
[--dry-run] # preview without DB writes
[--hours=4] # review window (default 4)Runs automatically via deal-outreach-auto-approver.timer (every 30 min).
Compliance rules (from OUTREACH-RULES.md)
- Quiet hours: 8am-9pm local to CH’s state TZ
- Max 1 send per CH per 24h
- Max 1 send per deal per send-window
- Suppression list: STOP → opt-out persists across deals
- Line routing: state →
STATE_LINESmap in engine (CA/NV/FL/GA/AZ) - Dup content: don’t resend same template if last send <72h
- Thread hygiene: don’t interrupt a human-active thread
- One gate question per send (Mike Ferry rule)
- 10DLC / carrier segmentation compliance
- Every send →
openphone_eventsaudit log
Quo internal note (§4.1 format)
After every outbound send, quo-note-builder.js posts a 7-line emoji note to the Quo conversation:
📍 City / State / Zip
📈 Stage: <label>
🔗 IL URL
📁 HubSpot link
👤 N total deals from this CH on file
🗺️ STATE · STATE
⏳ Only deal on file from this CH (or +N more...)
Fail-closed: if lineId cannot be resolved from the fromNumber, no note is posted (no hardcoded fallback).
Triggers
User says any of:
- “send initial outreach for deal X”
- “kick off acquisitions on deal X”
- “approve deal X for outreach”
- “trigger Stage 1 SMS”
- “run outreach engine”
For follow-ups (touch 2+), route to acquisitions-followup skill.
Failure modes
| Symptom | Cause | Recovery |
|---|---|---|
gate failed: compliance | quiet hours or suppression hit | check CH state TZ; suppression list in Supabase messaging_suppression |
approval_gate: red_zone_needs_cli_approval | reri_arv_flag='manual_review_required' | CLI: deal-outreach-worker.js --deal-id=X --approve |
approval_gate: yellow_zone_pending_review_window | reri_arv_flag='pre_negotiation', <4h old | auto-approver fires every 30 min; or --approve to override |
[quo-note] FAIL-CLOSED: no lineId | fromNumber not in ALL_LINES | check LINE_DEAL_SOURCE_MAP; fromNumber must match an OpenPhone line |
OpenPhone 429 | line rate limit | throttle; rotate line in STATE_LINES |
Related code
workspace/scripts/workers/unified-outreach-engine.js— coordinator (canonical)workspace/scripts/workers/deal-outreach-worker.js— single-deal triggerworkspace/scripts/workers/deal-outreach-auto-approver.js— yellow-zone 4h timerworkspace/scripts/workers/smart-outreach-worker.js— batch (legacy, superseded by engine)workspace/scripts/lib/gate-computer.js— 5-gate qualification progressionworkspace/scripts/lib/mentor-voice.js— touch→mentor cadence mappingworkspace/scripts/lib/compliance-gate.js— TCPA/quiet hoursworkspace/scripts/lib/blast-safety.js— dedup + silence penaltyworkspace/scripts/lib/thread-context.js— prior msg history + convId lookupworkspace/scripts/lib/response-generator.js— v3 Dispo Conversion Agentworkspace/scripts/lib/quo-note-builder.js— §4.1 Quo internal note builderworkspace/scripts/lib/openphone-sms.js— OpenPhone send wrapperdeal-qualification/templates/sms/initial-outreach.json— Gate 1 templatedeal-qualification/templates/sms/detail-request.json— Gate 2-3 templatedeal-qualification/templates/sms/gap-fill.json— Gate 4 templatedeal-qualification/templates/sms/pre-negotiation.json— Pre-negotiation (B1)deal-qualification/templates/sms/offer-inquiry.json— Gate 5 templateworkspace/knowledge-base/openphone/MENTORS.md— 10 mentors + Anti-Patternsworkspace/knowledge-base/openphone/OUTREACH-RULES.md— 10 compliance rules
Memory cross-references
- feedback_acq_mentor_voice_cadence.md — touch→mentor cadence
- feedback_acq_no_pass_and_pivot.md — banned pass+pivot, probe first
- feedback_acq_thread_history_aware_gates.md — skip satisfied gates
- feedback_quo_lineid_required.md — fail-closed on missing lineId
- feedback_never_send_without_auth.md — explicit auth required for any live send
- feedback_aurora_outbound_guardrails.md — outbound rules per dispatch
Invokes / Invoked by
Invokes: SKILL, compliance-gates Invoked by: _summary, _summary, SKILL