Alerts tell you something happened. Predictions tell you something will happen. Autonomous actions tell you what to do about it — and can do it for you if you approve.
The Action Pipeline
When a trigger fires, two things happen simultaneously:
- An alert is created in
realtime_alerts - A pending action is created in
pending_autonomous_actions
Trigger fires → Alert created → Pending action drafted → User approves/cancels → Action executesCreating Pending Actions
// src/domain/autonomous-actions/index.ts, lines 130-160
export async function createPendingAction(
params: CreatePendingActionParams,
preloadedSettings?: ActionSettings
): Promise<PendingAutonomousActionRow | null> {
// Build dedup key: action_type:company:deal:trigger
const dedupKey = `${params.actionType}:${params.companyId ?? 'none'}:${params.dealId ?? 'none'}:${params.triggerType}`;
// Check if auto-execute is enabled for this action type
const settings = preloadedSettings ?? await getActionSettings(params.userId);
let autoExecuteAt: Date | null = null;
if (canAutoExecute(params.actionType, settings)) {
autoExecuteAt = new Date(Date.now() + settings.autoExecuteDelayMinutes * 60 * 1000);
}
const result = await pool.query(
`INSERT INTO pending_autonomous_actions
(user_id, trigger_type, trigger_alert_id, action_type, action_params,
context_summary, company_id, deal_id, auto_execute_at, dedup_key)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT (user_id, dedup_key) WHERE status = 'pending' AND dedup_key IS NOT NULL
DO NOTHING
RETURNING *`,
[params.userId, params.triggerType, params.triggerAlertId, params.actionType,
JSON.stringify(params.actionParams), params.contextSummary,
params.companyId, params.dealId, autoExecuteAt, dedupKey]
);
if (result.rows.length === 0) {
console.log(`[Autonomous Actions] Deduplicated (key: ${dedupKey})`);
return null;
}
return result.rows[0];
}Dedup Key
The dedup key prevents duplicate actions for the same situation:
"draft_email:company-abc:deal-xyz:deal_stage_regression"If a deal_stage_regression alert fires twice for the same deal (e.g., the deal regresses, progresses, then regresses again within the dedup window), only one pending action exists. The ON CONFLICT DO NOTHING silently prevents the duplicate.
Auto-Execute
If the user has enabled auto-execute for certain action types, the action gets a scheduled execution time:
autoExecuteAt = new Date(Date.now() + settings.autoExecuteDelayMinutes * 60 * 1000);A separate cron job (execute-pending-actions, every 5 minutes) picks up actions where auto_execute_at < NOW() and status = 'pending' and executes them automatically. The delay (configurable, default 30 minutes) gives the user time to review and cancel if needed.
Action Types
Five action types cover the most common responses:
| Action Type | What It Does | Trigger Examples |
|---|---|---|
send_email | Sends an email to a contact | Activity slump, new high-fit contact |
create_followup_task | Creates a follow-up task in the CRM | Stage regression, competitor added |
log_touch | Records a touchpoint in the system | Meeting completed, form submission |
draft_email | Drafts an email for review (doesn’t send) | Value decrease, champion cold |
notify_rep | Sends a Slack notification to the rep | Close date pushed, multi-thread engagement |
The TRIGGER_ACTION_MAP connects triggers to their default action:
const TRIGGER_ACTION_MAP = {
deal_stage_regression: 'create_followup_task',
deal_value_decrease: 'draft_email',
deal_value_increase: 'notify_rep',
deal_close_date_pushed: 'create_followup_task',
deal_competitor_added: 'create_followup_task',
new_contact_high_fit: 'draft_email',
email_opened_multiple: 'create_followup_task',
activity_slump: 'draft_email',
champion_title_change: 'notify_rep',
};The Approval Flow
Pending actions appear in the UI as an approval queue. The user can:
- Approve — Execute the action immediately
- Edit — Modify the action parameters before approving
- Cancel — Discard the action with a reason
// POST /api/autonomous-actions
// Body: { actionId: "abc-123", action: "approve" }
if (action === 'approve') {
const result = await approveAction(userId, actionId);
return res.status(200).json(result);
}
if (action === 'cancel') {
const result = await cancelAction(userId, actionId, req.body.reason);
return res.status(200).json(result);
}Status Lifecycle
pending → approved → executed
pending → cancelled
pending → expired (auto-expire after 7 days)
pending → executed (auto-execute if enabled)
approved → executed
approved → failed (execution error)The status column has a CHECK constraint limiting values to: pending, approved, executed, cancelled, expired, failed.
The Action Parameters Object
Each action carries parameters that tell the executor what to do:
// Example: draft_email action for deal_value_decrease
{
"companyName": "Acme Corp",
"companyId": "uuid-123",
"dealId": "uuid-456",
"contactEmail": "sarah@acme.com",
"subject": "Following up on our discussion",
"reason": "Deal value decreased from $100,000 to $75,000",
"urgency": "high",
"suggestedTone": "consultative",
"contextSummary": "Review the Acme Corp deal — value dropped 25%"
}The context_summary field provides a human-readable explanation that appears in the approval queue, so the user can make a quick approve/cancel decision without digging into details.
Key Takeaways
-
Pending actions are drafted alongside alerts. Every trigger type maps to a default action type.
-
Dedup prevents duplicate actions for the same trigger + entity combination.
-
Auto-execute schedules actions for automatic execution after a configurable delay, giving users a review window.
-
Five action types cover the common response patterns: email, task, touch logging, draft, and notification.
-
Status lifecycle tracks actions from creation through execution or cancellation.
Next chapter: how AI content generation enriches alerts with analysis and recommendations.