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):

ZoneConditionBehavior
🟢 Greenreri_arv_flag is null or 'ok'Auto-approve, engine picks up immediately
🟡 Yellowreri_arv_flag='pre_negotiation'Hold 4h — deal-outreach-auto-approver.js (timer every 30 min) flips qualification_status='approved' after window
🔴 Redreri_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.

GateNameNeeds
1Identity & InterestFull address + asking price
2Visual & AccessPhotos or access scheduled
3Vacancy & PossessionOccupancy status
4Condition AssessmentRepair scope + known issues
5Full QualificationPrice, 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:

TouchMentorVoice
T1-2Mike FerryDirect, confident, one question, no filler
T3-6Mandeep DamjiRelational, warm, references their situation
T7-13David NortonCertain, anchors commitment, no hedging
T14-20Grant CardoneUrgency, scarcity framing, action-oriented
T21+Sean TerryObjection-counter, floor-probing

SMS templates (5 scenarios)

ScenarioTemplate fileTrigger
initial-outreachsms/initial-outreach.jsonGate 1, no pre-negotiation flag
detail-requestsms/detail-request.jsonGate 2-3
gap-fillsms/gap-fill.jsonGate 4
pre-negotiationsms/pre-negotiation.jsonreri_pre_negotiation_triggered=true (tight spread)
offer-inquirysms/offer-inquiry.jsonGate 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 type

Single-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)

  1. Quiet hours: 8am-9pm local to CH’s state TZ
  2. Max 1 send per CH per 24h
  3. Max 1 send per deal per send-window
  4. Suppression list: STOP → opt-out persists across deals
  5. Line routing: state → STATE_LINES map in engine (CA/NV/FL/GA/AZ)
  6. Dup content: don’t resend same template if last send <72h
  7. Thread hygiene: don’t interrupt a human-active thread
  8. One gate question per send (Mike Ferry rule)
  9. 10DLC / carrier segmentation compliance
  10. Every send → openphone_events audit 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

SymptomCauseRecovery
gate failed: compliancequiet hours or suppression hitcheck CH state TZ; suppression list in Supabase messaging_suppression
approval_gate: red_zone_needs_cli_approvalreri_arv_flag='manual_review_required'CLI: deal-outreach-worker.js --deal-id=X --approve
approval_gate: yellow_zone_pending_review_windowreri_arv_flag='pre_negotiation', <4h oldauto-approver fires every 30 min; or --approve to override
[quo-note] FAIL-CLOSED: no lineIdfromNumber not in ALL_LINEScheck LINE_DEAL_SOURCE_MAP; fromNumber must match an OpenPhone line
OpenPhone 429line rate limitthrottle; rotate line in STATE_LINES
  • workspace/scripts/workers/unified-outreach-engine.js — coordinator (canonical)
  • workspace/scripts/workers/deal-outreach-worker.js — single-deal trigger
  • workspace/scripts/workers/deal-outreach-auto-approver.js — yellow-zone 4h timer
  • workspace/scripts/workers/smart-outreach-worker.js — batch (legacy, superseded by engine)
  • workspace/scripts/lib/gate-computer.js — 5-gate qualification progression
  • workspace/scripts/lib/mentor-voice.js — touch→mentor cadence mapping
  • workspace/scripts/lib/compliance-gate.js — TCPA/quiet hours
  • workspace/scripts/lib/blast-safety.js — dedup + silence penalty
  • workspace/scripts/lib/thread-context.js — prior msg history + convId lookup
  • workspace/scripts/lib/response-generator.js — v3 Dispo Conversion Agent
  • workspace/scripts/lib/quo-note-builder.js — §4.1 Quo internal note builder
  • workspace/scripts/lib/openphone-sms.js — OpenPhone send wrapper
  • deal-qualification/templates/sms/initial-outreach.json — Gate 1 template
  • deal-qualification/templates/sms/detail-request.json — Gate 2-3 template
  • deal-qualification/templates/sms/gap-fill.json — Gate 4 template
  • deal-qualification/templates/sms/pre-negotiation.json — Pre-negotiation (B1)
  • deal-qualification/templates/sms/offer-inquiry.json — Gate 5 template
  • workspace/knowledge-base/openphone/MENTORS.md — 10 mentors + Anti-Patterns
  • workspace/knowledge-base/openphone/OUTREACH-RULES.md — 10 compliance rules

Memory cross-references

Invokes / Invoked by

Invokes: SKILL, compliance-gates Invoked by: _summary, _summary, SKILL