05 · Lifecycle

From trigger to send

A single outbound message moves through the AWS-native pipeline — each stage owned by a different system, each handoff recorded in the DynamoDB status field of the single-table store hv-hub-v2-development-tasks (us-east-2). The audit trail is the status timeline plus the Undo audit items and Audit_Log.

01
Trigger · n8n "Auto Tasks" cron fires
scheduleTrigger node activates and POSTs to the producer. Body context: playbook_id, list_id, source_workflow. (Re-pointed off the VPS 2026-06-08.)
n8n · POST /generate
02
Producer · API GW → SQS
The task-producer Lambda ACKs 202 in <1s and enqueues onto SQS tasks-ingest (+ DLQ, maxReceiveCount 5). Decouples the cron from the slow Bedrock draft.
Lambda · SQS tasks-ingest
03
Consumer · pull + enrich
task_consumer Lambda dedups (task_guid#idx), pulls the audience (HubSpot list membership / Airtable Orders view), enriches (deal, owner, last touch), and loads Bot Settings (Gabrielle / Sales_Assistant) for the system prompt + guardrails.
Lambda · HubSpot · Airtable
04
Governor gate · ALLOW / DEFER / BLOCK
Before drafting, the consumer calls POST /api/governor/check (GOVERNOR_ENABLED; fail-closed by default). Hard suppress / opt-out → BLOCK; out-of-family or over-cap → DEFER (writes a held task), or DROP when the config is set to drop; otherwise ALLOW. HOLD is reserved in the type but never emitted. Every decision logged to the GOVDECISION audit partition.
Governor · /governor/check
05
Generate · Bedrock draft
Prompt + context → draft via Bedrock Converse (claude-sonnet-4-6). Confidence scored; below the threshold (the Governor confidenceThreshold, default 60, authoritative for all playbooks — the per-playbook SSOT "Confidence Threshold" is deprecated for gating; the consumer's hard-coded fallback is DEFAULT_CONFIDENCE_THRESHOLD=60) routes to needs_review with no draft shown. Guardrail failures route to manual review, never auto-discard.
Bedrock · Converse
06
Queue · PutItem DynamoDB
Item written PK=TASK#<id>, SK=META, status=pending_review (GSI1PK=STATUS#pending_review). Idempotent on task_guid#idx. The consumer only ever writes — it never sends.
DynamoDB · single-table
07
Review · Hub v2 Tasks queue
Reviewer (Jess / Mario / Farid) works the tasks-v2 queue at hubv2.apphappenventures.com, one at a time. Six actions: Approve & Send, Edit (incl. Edit-with-AI), Reject, Snooze, Comment, Escalate. Approve & Send → status=resolved with an optional, non-blocking sign-off reason (DEV-471).
Hub v2 · /tasks-v2 · human
08
Send · outbound_sender Lambda (Unified Sender)
A sending reviewer action enqueues a typed message onto SQS outbound-messages; the message-dispatcher Lambda claims it idempotently (OUTBOX# in DynamoDB) and routes by channel to the matching adapter — email (Gmail DWD), Slack (chat.postMessage), WhatsApp (Meta Graph — adapter real, token secret not yet populated); SMS is still a stub. The send gates now live in the Governor config (edited from /admin/governor, no deploy), not just env vars: whether a real email leaves is the Governor sendDevelopmentMode flag — ON redirects every recipient to the developmentTestEmails inboxes, OFF sends to the real client. As last verified (2026-07-30) it is OFF, so email reaches the real recipient; read /admin/governor for today's value. (The legacy maestro_sender Gmail worker still exists but ships inert: SEND_DRY_RUN=true and its EventBridge tick DISABLED.)
Lambda outbound_sender · SQS · multi-channel

Dual send-worker risk

A legacy n8n "AWS Send Worker (Maestro)" polls the approved sales queue (GET /queue/approved) and was never confirmed retired at the Unified-Sender cutover. The inert maestro_sender Lambda (dry-run + its EventBridge rule DISABLED) is a third, dormant poll worker on the same queue. With the SQS-based message-dispatcher now the live sender, any approved task the n8n worker also picks up can be sent twice to the client. Retiring the n8n worker is the pending step — see Risks.

Life of an agent run — the status states
Animated · interactive — the same lifecycle as a state machine: how a task moves through pending_review, needs_review, held, resolved and the send states. Open full ↗
DynamoDB status field

The status states

A task's status is the single source of truth for where it sits in the queue. Only TASK# items write GSI1PK=STATUS#<status>, so the Hub lists the queue by querying that GSI per status. Review states are mutated by reviewer actions; the send-lifecycle states (sending / sent / failed) are flipped only by the Sender.

pending_review

Drafted by the consumer, awaiting human review. Default state on PutItem. Has a Bedrock draft + confidence.

needs_review

Confidence below the Governor confidenceThreshold (default 60, applied to all playbooks; consumer hard fallback 60). Routed to manual review with no AI draft shown. Re-suggest can promote it back to pending_review.

held

Governor DEFER result (GOV-01). Carries deferUntil + heldReason + governorDecision/family. NOT sendable. Surfaces for Send now / Reschedule / Cancel.

resolved

Reviewer hit Approve & Send (optional sign-off reason). The ONLY status the Sender picks up. Action tasks instead go to applied on write-back.

sent

Outbound succeeded (Sender flipped sending→sent). sentAt timestamp populated. Terminal for message tasks.

snoozed

Reviewer snoozed; snoozeUntil set (America/New_York). The resurfacer brings it back on that date. Terminal until resurfaced.

escalated

Reviewer escalated; reassigned to a teammate (original assignee recorded), notification fired, escalation history shown on the card (#372). Terminal.

rejected

Reviewer rejected (with reason). exitReason captured. Terminal.

failed

Sender error (Gmail bounce, throttle). Flipped sending→failed by the Sender. Manual retry only.

The six reviewer actions

Every task in the Hub v2 queue exposes the same source-agnostic action surface (QueueActionsService): Approve & Send (→ resolved, two-click + sign-off), Edit (manual inline edit, or Edit-with-AI — a Bedrock in-place rewrite loop; both capture edited_content as a learning signal), Reject, Snooze, Comment (notifies + appends to the thread, no longer auto-advances the queue — #376), and Escalate (reassigns to a teammate, records the original assignee, notifies them, and writes escalation history — #372). Approve & Send / Snooze / Reject accept an optional, non-blocking reason (DEV-471); Reject & Escalate additionally validate a structured reason code. Every action writes a before/after Undo audit item and logs to Audit_Log.

Governor decisions (GOV-01)

Before a task is drafted, the consumer asks the Contact Governor for a verdict: ALLOW (draft normally), DEFER (over a frequency cap or out of the governing family → written as a held task with a deferUntil; the same collision becomes DROP when the config's collision_default is set to drop), BLOCK (hard suppress / opt-out — no task). HOLD is reserved in the type but the policy never emits it. The gate is fail-closed by default (an unreachable Governor records FAIL_CLOSED and skips the contact). Decisions run in spec order (suppress → family → caps → channel → priority → per-account cap) and every one is logged to the GOVDECISION audit partition.

Walkthrough

A task, end to end

The task_guid (and the DynamoDB TASK#<id> key it forms) is the stable identifier that spans the n8n cron run, CloudWatch logs, the DynamoDB item and the outbound send record.

TASK#g7f3a… · playbook CW-01 · Gabrielle (Sales_Assistant) ────────────────────────────────────────────────────────────── 01 2026-06-22 09:00:03 n8n "Auto Tasks" cron → POST /generate 02 2026-06-22 09:00:04 producer: 202 ACK · SendMessage → SQS tasks-ingest 03 2026-06-22 09:00:08 consumer pulls list membership · dedup on task_guid#idx 04 2026-06-22 09:00:09 Governor /check → ALLOW (family GROW, under cap) 05 2026-06-22 09:00:14 Bedrock Converse draft · confidence 0.86 06 2026-06-22 09:00:15 PutItem · status = pending_review (GSI1PK=STATUS#pending_review) 07 2026-06-22 14:22:18 Mario clicks Approve & Send (optional sign-off) 08 2026-06-22 14:22:18 status = resolved · before/after written to Undo audit 09 2026-06-22 14:22:45 outbound_sender claims (OUTBOX#) · status = sending 10 2026-06-22 14:22:46 status = sent · sentAt captured · terminal
The deal lifecycle in HubSpot — which playbook fires at each stage
Animated · interactive — zooming out: the deal lifecycle in HubSpot and which sales playbook fires the tasks at each stage. Open full ↗