The compiled graph in apps/api/app/agents/graph.py contains five active nodes. WhatsApp delivery is handled by the communication service after approval rather than by a disconnected graph node.
stateDiagram-v2
[*] --> RouteEntry
RouteEntry --> Sales: phase new or unknown
RouteEntry --> Research: phase sales_complete
RouteEntry --> EmailDraft: phase research_complete
RouteEntry --> ApprovalGate: phase email_complete
RouteEntry --> Report: approved action or phase approved
RouteEntry --> FailedNode: phase failed plus resume_node
Sales --> Research: success
Sales --> Failed: exception captured
Research --> EmailDraft: success
Research --> Failed: exception captured
EmailDraft --> ApprovalGate: success
EmailDraft --> Failed: exception captured
ApprovalGate --> WaitingApproval: outbound_email not approved
ApprovalGate --> Report: resumed with outbound_email approved
Report --> Completed: PDF-ready report state
Report --> Failed: exception captured
WaitingApproval --> Report: manager or admin approves
WaitingApproval --> Cancelled: rejects
WaitingApproval --> EmailDraft: requests changes and retries
Failed --> FailedNode: retry endpoint
FailedNode --> Failed: retry fails
FailedNode --> NextNode: retry succeeds
Completed --> [*]
Cancelled --> [*]
| Node | Inputs | Outputs | External model use |
|---|---|---|---|
| Sales Agent | Lead identity, contact completeness, intent score | lead_score, priority, qualification, recommended action |
No; deterministic scoring |
| Research Agent | Company, industry, configured prompt and model route | Summary, industry insight, competitors, risks, LLM usage event | Yes; provider-neutral route |
| Email Draft Agent | Lead and research context, email prompt | Subject, body, follow-up sequence, LLM usage event | Yes |
| Approval Gate | Email draft and approved action list | Pending approval or approved phase | No; deterministic policy |
| Report Agent | Approved lead intelligence and report prompt | Structured report, completion event, usage event | Yes |
The mock provider is the safe default. Research output explicitly labels unverified external facts in mock mode.
AutomationState is a typed dictionary containing these groups:
- Identity:
tenant_id,run_id,lead. - Sales: score, qualification, priority, recommended action.
- Research: summary, insights, competitors, risks.
- Communication drafts: email, follow-up sequence, WhatsApp message.
- Control: phase, pending approval, approved actions, resume node, errors.
- Report: structured report object.
- Observability: events, LLM calls, node timings.
- Configuration snapshot: model routes and active prompt templates.
The configuration snapshot ensures a run records which route/prompt influenced its output even if administrators later update defaults.
sequenceDiagram
participant API as FastAPI
participant Graph as LangGraph
participant DB as PostgreSQL
actor Reviewer
API->>Graph: ainvoke(initial state)
Graph-->>API: phase awaiting_approval plus pending payload
API->>DB: Persist state_snapshot
API->>DB: Insert approval if absent
API->>DB: Insert email draft if absent
API-->>Reviewer: Pending approval
Reviewer->>API: approve, reject, or request_changes
API->>DB: Lock/read tenant-scoped approval
alt Same decision already stored
API-->>Reviewer: Return existing workflow state
else Conflicting final decision
API-->>Reviewer: 409 conflict
else Approved
API->>DB: Store decision and audit event
API->>Graph: Resume snapshot with outbound_email approved
Graph-->>API: report phase completed
API->>DB: Persist report and completed run
else Rejected
API->>DB: Cancel run and append audit event
else Changes requested
API->>DB: Record decision and return run for controlled retry
end
Each node is wrapped by guarded(name, node):
- Record start time and attempt number.
- Execute the node.
- On success, append completion timing and latency.
- On exception, return
phase=failed, setresume_node, append a sanitized error, and record the exception class aserror_category. - Persist the failed execution, trace, deterministic evaluation, workflow error audit event, and durable snapshot.
POST /workflows/{run_id}/retryloads the tenant-scoped snapshot.route_entrysends execution directly toresume_node.
Completed upstream nodes are not repeated. Retry count is derived from the recorded attempt number.
workflow_runs.state_snapshotis the durable checkpoint.graph_thread_idis unique and provides a stable execution identity.- Redis caches the latest active state for one hour under a tenant/run key.
- Redis failure is non-fatal; retry/resume uses PostgreSQL.
- Every new agent event becomes an
agent_executionsrecord, execution trace, evaluation, andagent.step_completedaudit event. - Report generation is idempotent by the unique report
run_idand an existence check.
The default routes seeded per tenant are:
| Agent | Primary | Fallback principle |
|---|---|---|
| Research | Google Gemini | OpenAI → Anthropic → mock, excluding the primary |
| OpenAI | Anthropic → Google → mock, excluding the primary | |
| Report | Anthropic Claude | OpenAI → Google → mock, excluding the primary |
Administrators can update primary and fallback candidates through the model-routing API. Each completion records provider, model, token usage, estimated cost, latency, and fallback history.
To connect WhatsApp as a graph node, add whatsapp_agent and whatsapp_gate after approved email, then preserve the same deterministic consent/approval rule. Do not let a prompt decide whether a recipient may be contacted. For asynchronous production execution, publish run_id to Redis Streams or SQS; keep the API as the only writer of workflow intent and make workers idempotent by run ID.