Role: universal HubSpot deal creator. Any source produces a parsedDeal object → this skill handles the rest.
This skill wraps existing code, it doesn’t reimplement it. Canonical implementation lives at /home/opsadmin/.openclaw/workspace/scripts/hubspot-deal-creator.js (1006 lines, 11 exported functions).
⚡ 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). NEVER reimplement createDealFromParsed() — always call it via the canonical script. Verify each subagent report against acceptance criteria before marking complete.
Source-stage mapping (from hubspot-deal-creator.js line 64):
Source
First stage
investorlift
new_deal_investorlift
facebook
new_deal_facebook
connected_investors
new_deal_connected_investors
asset_column
new_deal_asset_column
salesmsg
new_deal_salesmsg
openphone_inbound / openphone
new_deal_openphone
referral / cold_outreach / email
new_deal_email
Capabilities (entry points)
The skill exposes createDealFromParsed() from hubspot-deal-creator.js. Callers (adapters) invoke it like:
const { createDealFromParsed } = require('/home/opsadmin/.openclaw/workspace/scripts/hubspot-deal-creator.js');const parsedDeal = { // see docs/PARSED-DEAL-CONTRACT.md for full shape address: '123 Main St, Phoenix, AZ 85001', askingPrice: '250000', arv: '380000', beds: 3, baths: 2, sqft: 1450, wholesalerName: 'ACME Wholesalers LLC', sourcePhone: '+15551234567', dealSource: 'investorlift', // ... etc};const result = await createDealFromParsed(parsedDeal);// result = { deal, contact, isNewContact, isDuplicate, dispoDeal }
For the IL adapter specifically, see /home/opsadmin/.openclaw/workspace/scripts/workers/acq-deals-hs-syncer.js — it reads from acquisition_deals Supabase, transforms via supabaseDealToParsed(), then calls createDealFromParsed().
Full catalog in workspace-acquisitions/FIELDS.md and agents/{acquisitions,atlas}/agent/FIELDS.md.
Triggers
User says any of:
“push this deal to HubSpot”
“create HS deal from ”
“sync deal candidate”
“add to acquisition pipeline”
“create both acq and dispo deals for X”
For source-specific bulk syncs (e.g., “sync all IL deals to HubSpot”), route to the source’s adapter worker (acq-deals-hs-syncer.js for IL), not directly to this skill — the adapter handles pagination + Supabase state tracking.
Capabilities NOT in this skill
Capability
Lives in
Source data ingestion
per-source skill (e.g., il-marketplace-pull for IL)
Deal qualification / 5-gate compute
acquisitions-outreach skill (gate-computer.js)
Initial outbound SMS
acquisitions-outreach skill
Follow-up cadence
acquisitions-followup skill
Dispo blast to buyers
dispo-blast skill
Stage transitions on conversation
acq-deals-hs-syncer.js workers + stage-sync.js
Failure modes
Symptom
Cause
Recovery
HUBSPOT_PAT missing
env not loaded
set -a; source /home/opsadmin/.openclaw/master.env; set +a
429 from HubSpot
rate limit (100 req/10s; each create ≈ 6 requests)
adapter must throttle to ≥500ms/deal
Deal created but no contact
parsedDeal.sourcePhone was null
adapters must populate sourcePhone OR skip contact creation
isDuplicate: true
dedup hit on source+source_id or address
result.deal points at existing deal; update it instead of creating
Dispo deal failed but acq created
non-fatal in current code (logs warning)
re-run sync; findExistingDeal will find acq, only create dispo
LLM extraction → HS field mapping (new 2026-05-03)
When acquisition_deals has LLM-extracted data (from enrich-descriptions-llm.js), the sync pipeline maps it to HubSpot in three tiers:
Tier A — Existing HS props that get AUTO-FILLED (~22 props)
These properties already exist in HubSpot. The adapter writes LLM-extracted values to them only when blank (never overwrites human-edited fields). Always updates investorlift_scraped_at timestamp.
Henry approved 2026-05-03. These require a one-time HubSpot Properties API call (CREATE-HS-PROPS-SPEC.json). Until created, values are written to acquisition_deals.meta JSONB and surfaced via Acquisitions Control Center.
8 unmapped IL detail fields worth wiring as new HS custom properties: matterport_url, zestimate, roof_age, foundation_condition, heating_system_age, hunter_email_score, hunter_linkedin_url, apollo_employee_count. Requires HS admin to create the properties first; deferred until consolidation phases 1-3 confirmed via Kimi audit.
Owner assignment policy — currently aurora@reri.co for all deals. Future: route by state, or to per-source “Inbox” owner.
Re-ingest cadence — currently overwrites all fields on every run via upsert. Need to confirm whether human-edited fields should be preserved.