02 · Current Flow

How tasks happen today

The ClawBot→Maestro cutover is done. n8n crons no longer hit the VPS — they POST /generate to an AWS producer Lambda that enqueues to SQS. The task_consumer Lambda pulls contacts from HubSpot or Airtable, drafts with Bedrock, applies guardrails + the Governor gate, and writes pending_review tasks to a DynamoDB single-table. Reps review in Hub v2; approved sends are delivered by the message-dispatcher Lambda off the outbound-messages queue (email is live to real clients today — Governor globalSendEnabled=true, sendDevelopmentMode=false as last verified 2026-08-07; flip sendDevelopmentMode=true in /admin/governor to redirect every email to internal test inboxes). The config/brain stays in Airtable; only the task store moved to AWS.

01
n8n Cron
scheduleTrigger fires (every X hrs/days) and POSTs to the producer. No transform, no draft logic.
n8n · scheduleTrigger
02
POST /generate
Producer Lambda (API Gateway) ACKs 202 in <1s and enqueues to SQS.
Lambda · API GW
03
SQS + consumer
tasks-ingest queue (+ DLQ, maxReceiveCount 5) → task_consumer Lambda pulls + enriches the audience.
SQS tasks-ingest · Lambda
04
Draft + guardrails + Governor
Contact Governor gate, then a Bedrock Converse draft under guardrail rules.
Governor · Bedrock
05
DynamoDB
Write pending_review to the single-table; the consumer only ever writes, never sends.
DynamoDB · single-table
06
Hub v2 → Sender
Reviewer hits Approve & Send → outbound-messages SQS → message-dispatcher Lambda (live to real clients today; Governor sendDevelopmentMode flips it to redirect to test inboxes).
Hub v2 · message-dispatcher
Trigger to send — end to end
Animated · interactive — a scenario trigger travelling all the way to an approved send. Open full ↗
Ingestion

One endpoint · two source modes

mode: list (HubSpot)

HubSpot-sourced playbooks

Body: {list_id, playbook_id, source_workflow, [limit]}

A HubSpot ILS list_id drives the contact set. The consumer pulls /crm/v3/lists/{id}/memberships then batch-reads contacts. Close Lost, Stale Contacts, Cross-Sell, Growth/VIP and the COLD playbooks differ mainly by playbook_id.

mode: orders (Airtable)

Airtable-sourced playbooks

Body: {playbook_id, view_id, source_workflow, mode:"orders", [limit]}

An Airtable view_id drives the order set. Used for rebooking and post-completion flows that depend on internal CRM state, not HubSpot lists.

POST https://5pauixfui5.execute-api.us-east-2.amazonaws.com/generate Content-Type: application/json { "list_id": "7638", "playbook_id": "recXXXXXXXXXXX", "source_workflow": "n8n-wf-id-here", "limit": 25 } # → producer Lambda enqueues to SQS, returns 202 ACK in <1s # → task_consumer Lambda drafts → DynamoDB pending_review
The drafting pipeline
Animated · interactive — how the task_consumer turns an enqueued job into a pending_review draft. Open full ↗
Playbook Catalog

Playbooks & source modes

Source of truth: Workflows & Automations SSOT · tbl38RfIpWTRJKyiQ (base appUDQ65M1lSnSM5p). The consumer resolves the SSOT row directly by playbook_id (the crons pass the SSOT record id, not a Templates id — the {{merge}} bodies live in a separate Templates table tblHhh4HxgWj6AiHU). That same row's Status is the consumer's kill-switch key: a Status of Paused / Needs Fix / Deprecated / Cancelled / Back to Drawing Board / Blocked makes the consumer skip generation. The cutover made the kill-switch Status-only (#268) — the Enabled checkbox is deprecated and ignored.

CodeModeSource (cron list/view)Live output · last 7d
CL-01 Close LostlistHubSpot 7661 (shared)Generating
DFU-01 Deals Follow UplistHubSpot 7626Generating
COLD Facilities / Procurement / SustainabilitylistHubSpot 7685 / 7678 / 7677Generating
AG-01 Account Growth · account_touchlistHubSpot 7669Generating
SC-01 Stale ContactslistHubSpot 7661 / spec 7632 (#235)Idle · 0 drafts
RF-01 ReferrallistHubSpot 7679Idle · TEST shell
RB-01 Customer RebookingordersAirtable viewIdle · last 06-20
CW-01 Post CompletionordersAirtable (no view_id)Idle · last 06-20
GE-01 Cross-SelllistHubSpot 7661 (shared)Killed · Deprecated
CW-03 Review & TestimonialordersAirtable viewKilled · Deprecated
TQ-01 Growth / VIPlistHubSpot 7638Killed · Deprecated
InCharged Deals FU / Rebooking / Lost Re-Eng DannylistHubSpot 7649 / 7655 / 7653Killed · Back to Drawing Board

Status is empirical · verify by playbook_id, not by name

The status column is the live output — DynamoDB hv-hub-v2-development-tasks, last 7 days, read 2026-06-23 — cross-checked against the SSOT kill-switch. The 10 Auto Tasks — crons were re-pointed VPS→/generate on 2026-06-08 (DFU-01 is a separate Gabrielle — cron, the 11th). The SSOT carries multiple generations of each playbook (the _HV_All spec rows, older ClawBot — rows, and the live generators), so name searches mislead — the consumer keys off the exact playbook_id the cron sends, and that row's Status gates it. Today the steady producers are Close Lost, the three COLD playbooks, Deals Follow Up and Account Growth; Cross-Sell, Review & Testimonial and Growth/VIP are Deprecated → zero drafts, and the InCharged (Danny) set is Back to Drawing Board. The SSOT has no owner field (reps are assigned downstream); only the InCharged set is reliably tagged to Danny.

Review · Gabrielle's drafts

What the rep does with a draft

A draft never sends itself. Before it is even written the Contact Governor gates it; once it lands in pending_review the rep works it in Hub v2 with one of six source-agnostic actions (QueueActionsService). Approve & Send is the only path to the Sender.

Gate first · the Contact Governor

Before drafting, the consumer calls POST /api/governor/check (GOVERNOR_ENABLED; fail-closed by default — an unreachable Governor skips the contact rather than drafting ungated). Verdicts: ALLOW (draft normally), DEFER (over a frequency cap or out of the governing family → written as a held task with a deferUntil; DROP is the same branch when the config is set to drop instead of defer), BLOCK (hard suppress / opt-out — no task). HOLD is reserved in the type but the policy never emits it. Every verdict is logged to the GOVDECISION audit partition.

Approve & Send

Two-click, with an optional sign-off reason (non-blocking — DEV-471). Sets status=resolved — the only state the Sender picks up. The send reaches the real client today (Governor sendDevelopmentMode=false as last verified 2026-08-07); flipping sendDevelopmentMode=true would redirect it to internal test inboxes, and the confirm dialog surfaces the active send mode (#376).

Edit

Inline manual edit, plus Edit-with-AI (a Bedrock in-place rewrite). Both capture edited_content as a learning signal. Non-resolutive — the task stays in the queue until approved.

Reject

Terminal. Optional free-text reason (non-blocking); only a structured reason code is validated (DEV-471). Captures exitReason; the task leaves the queue.

Snooze

Sets snoozeUntil (America/New_York) with an optional, non-blocking reason (DEV-471); the resurfacer brings it back on that date.

Comment

Non-resolutive — appends to the thread and notifies, but does not advance the queue (#376). Comments now appear immediately in the modal (#374).

Escalate

Reassigns to a teammate, records the original assignee, notifies them, and writes the escalation history onto the card (#372).

Auth

HTTPS endpoints · secrets in AWS

The old URL-embedded HTTP Basic credential to the VPS is gone. Ingestion is now HTTPS to API Gateway; the send/status loop between the Hub and the Lambdas uses a Bearer secret from AWS Secrets Manager.

Ingestion · /generate

HTTPS to API Gateway

https://5pauixfui5.execute-api.us-east-2.amazonaws.com/generate

The producer currently has no authorizer on /generate — anyone with the URL can trigger generation (Bedrock cost + queue pollution; drafts still need human review, so no send risk). Post-cutover hardening is tracked in #228: API-key / IAM auth on the route + a shared header on the crons, plus a fail-open suppression gate and single-page guardrail reads to fix.

Send / status loop

Bearer · QUEUE_HOOK_SECRET

The legacy maestro_sender Lambda polled the Hub's /api/queue/approved and /:id/status with Authorization: Bearer <QUEUE_HOOK_SECRET>, read from Secrets Manager development/hv-hub-v2/api; the live path instead enqueues to the outbound-messages SQS for the message-dispatcher Lambda. The Gmail send service-account key lives in Secrets Manager too — never in code.

VPS leaked-credential rotation tracked in Risks + #152 (the ClawBot VPS has since been shut down).

What n8n still does

Timer + glue · the brain is on AWS

After the cutover, n8n's sales role shrank to a timer that POSTs /generate. The drafting brain (prompts, guardrails, source pulls, the Governor gate) now lives in the task_consumer Lambda + Bedrock — not in any FastAPI worker. n8n still owns operational routing and integration glue.

Role 1 · ~10 crons

Timer for Maestro

scheduleTrigger fires every X hrs/days → POST /generate over HTTPS to API Gateway. No transform, no draft logic. Just timer + body.

Now: producer Lambda → SQS → task_consumer Lambda does all the work on AWS.

Role 2 · ops + finance

Event router (Robin · Frank)

Receives webhooks (Gmail, forms, Slack) → parses → writes tasks. Has logic: email parsing, rule-based routing. Robin (R3 ops) + Frank (finance) tasks now mirror to /ingest → DynamoDB (source='robin', #259 cut over).

Role 3 · misc

Integration glue

HubSpot ↔ Airtable sync, Aircall sync, notifications, ad-hoc syncs. Mix of cron + webhook. Plus the Sentinel V2 error poller that surfaces n8n failures to #hv-alerts.

Dual send-worker risk

The legacy maestro_sender Lambda is inert (dry-run + its EventBridge tick DISABLED), and the live message-dispatcher sends for real: with SEND_DRY_RUN=false and the Governor sendDevelopmentMode=false (as last verified 2026-08-07), email reaches the real client — setting sendDevelopmentMode=true in /admin/governor would redirect every email to the developmentTestEmails inboxes instead. (The ALLOWED_INBOXES list only restricts which reps we send from — it does not protect the recipient.) A legacy n8n AWS Send Worker (Maestro) (id tdMWyF0SjCi2c1RM; DRY-RUN export at docs/n8n/aws-send-worker.workflow.json) still polls the same approved queue — now that email is live to clients, an approved task it also delivers would double-send to the client. Retiring the n8n worker is the pending single-window step (deactivate n8n so the dispatcher is the sole sender).