card-action-handler — Aurora Reasoning for Card Actions
When a user clicks [Send via OpenPhone], [Email via Gmail], [Send via SalesMsg], [Group SMS], or [Hand off to Aurora] in the OpenPhone Call Intelligence tab card, the action gateway invokes this skill to generate Aurora’s reasoning + draft + alternatives. Result renders in the iframe modal where user reviews, edits, and confirms.
When this skill fires
Endpoint /api/ctie/aurora/draft (port 18790) calls this skill. Trigger surfaces:
- Iframe modal load (initial draft)
- “Regenerate” button click in the iframe modal
- Direct API call from any other surface that wants Aurora’s reasoning on a CTIE call
NEVER fires for:
- HubSpot task creation (CTIE task content already exists, no Aurora needed)
- Native HubSpot composer launches (HubSpot’s own UI, not Aurora-mediated)
- Note creation, meeting scheduling (HubSpot native, not Aurora-mediated)
Inputs (from /api/ctie/aurora/draft POST body)
{
"action_type": "sms" | "email" | "salesmsg" | "group-sms" | "handoff",
"call_id": "AC...", // OpenPhone external_call_id
"context": {
"contact_name": "Robert",
"contact_phone": "+15623203611",
"line_name": "BetterFiles",
"rep_name": "Henry Hill III",
"temperature": "WARM",
"role": "SELLER_LEAD",
"confidence": 78,
"situation": "Robert under contract, two tenants signed cash-for-keys...",
"ctie_suggested_sms": "Hey Robert, send the lease docs and we move fast.",
"score_deal": 6,
"score_relationship": 70,
"thread_history_summary": "Last 3 SMS replies came on this line within 12min",
"deal_stage": "EVALUATING",
"previous_sends": 0
}
}Outputs
{
"channel_recommendation": "openphone-sms",
"channel_reasoning": "Contact's last 3 replies came on SMS within 12 min — fastest reply channel for them. Email avg reply: 2 days.",
"primary_draft": {
"label": "Aurora's recommended",
"subject": null, // only for email
"body": "Hey Robert, getting buyer feedback on the tenanted side. Can you send the lease docs and any eviction filings? Want to move fast if numbers work.",
"tone": "casual + action-oriented"
},
"alternatives": [
{ "label": "More formal", "subject": null, "body": "...", "tone": "formal" },
{ "label": "Higher urgency", "subject": null, "body": "...", "tone": "urgent" }
],
"compliance_precheck": {
"tcpa_window_ok": true,
"suppression_status": "clean",
"dedup_window_ok": true,
"notes": "Within 8am-9pm contact local time. No opt-out flags. No identical message in last 7d."
},
"model_used": "gemma-3-27b",
"ms_elapsed": 2340
}Model routing
- Primary: Gemma 3 27B via Portkey config
pc-gemma-card-action(route to local M1 Ultra Ollama once available, or OpenRoutergoogle/gemma-2-27b-ituntil then). - Fallback: Sonnet via Portkey
pc-anthropic-sonnetif Gemma errors or response time > 8s. - NEVER use: Opus (cost prohibitive for per-click usage).
Cost budget
- Per skill invocation target: $0.001 or less via Gemma.
- Sonnet fallback: ~$0.003 per invocation (still acceptable).
- Hard ceiling: any skill invocation exceeding $0.01 is logged as anomaly.
Compliance integration
This skill does NOT execute the send. It only generates the draft + reasoning. The actual send goes through:
- Compliance gate (
scripts/lib/compliance-gate.js) — runs at executor level, not here - Channel executor (
openphone-sms.js,gmail-client.js,salesmsg-send.js) — sends the message - Audit log (
scripts/lib/action-audit.js) — records the send
If the skill’s compliance_precheck flags any issue, the iframe modal SHOULD warn the user before they click Send. The actual gate enforcement is at executor level — defense in depth.
Constraints (Gemma quirks)
- Gemma 4 fails on multi-step tool chains — this skill makes ZERO tool calls, just text generation
- Gemma may hallucinate phone numbers, contact names, or HubSpot IDs — the skill MUST output content the user can edit before send
- Gemma’s training cutoff is Jan 2025 — don’t ask it about current events / weather / market conditions
- The user ALWAYS reviews and edits before sending — Gemma is the drafter, human is the gatekeeper
Prompt template (sent to Gemma)
You are Aurora, an AI sales assistant for a real estate wholesaling team.
A team member just clicked "{action_type}" on a call card with a contact.
Your job: generate the most effective draft message and explain your reasoning.
CALL CONTEXT:
- Contact: {contact_name} ({contact_phone})
- Rep handling: {rep_name} via {line_name}
- Call temperature: {temperature} | Role: {role} | Confidence: {confidence}%
- Situation: {situation}
- Last reply pattern: {thread_history_summary}
- Deal stage: {deal_stage}
- CTIE suggested SMS (baseline): {ctie_suggested_sms}
YOUR TASK:
1. Recommend the best channel (sms / email / salesmsg) with reasoning grounded in the context above.
2. Write a primary draft (under 320 chars for SMS; full email body for email).
3. Write 2 alternative drafts: one more formal, one more urgent.
4. Flag any compliance concerns (TCPA quiet hours, recent send to same contact).
OUTPUT (strict JSON):
{
"channel_recommendation": "...",
"channel_reasoning": "...",
"primary_draft": { "label": "Aurora's recommended", "subject": null, "body": "...", "tone": "..." },
"alternatives": [
{ "label": "More formal", "subject": null, "body": "...", "tone": "formal" },
{ "label": "Higher urgency", "subject": null, "body": "...", "tone": "urgent" }
],
"compliance_precheck": { "tcpa_window_ok": true, "suppression_status": "clean", "dedup_window_ok": true, "notes": "..." }
}
NEVER:
- Make up the contact's address, phone, or any factual detail not in the context
- Use the contact's first name unless it's in the context
- Promise things outside RERI's scope (financing, legal advice, etc.)
- Send anything that violates TCPA (after-hours, opt-out ignored)
Failure modes
- Gemma returns malformed JSON → skill catches, returns CTIE suggested_sms as primary draft with reasoning=“Aurora unavailable, using CTIE baseline”
- Gemma timeout (>8s) → fallback to Sonnet
- Both fail → return CTIE suggested_sms with reasoning=“Both Gemma and Sonnet unavailable”
Audit + observability
Every skill invocation writes:
tool_callsrow (per CHOKEPOINT-1) with model + tokens + costaction_audit_log.reasoningfield (the channel_reasoning + compliance_precheck) — persists the Aurora output even if the user never clicks Send
This makes “what would Aurora have said?” queryable for audit and training data.