Skip to content
3 min read · 672 words

Multi-Agent Email Workflow

One email in; structured analysis, persisted tasks, a drafted reply, and a 0–1 quality verdict out — with a complete trace of every stage.

POST /agents/{id}/workflows/email · handler: run_multi_agent_email_workflow in routers/agents.py · agents: multi_agent_email_service

Request & gating

json
{ "email_text": "…", "create_tasks": true }
GuardFailure
Agent exists404
Agent has email_analyzer403 — the workflow's entry ticket

Then a WorkflowRun (multi_agent_email_workflow, status="running") is created and committed before any stage runs.

The four stages

Rendering diagram…

Stage 1 — Analysis Agent (LLM, temp 0.2)

Delegates to analyze_email — the same strict-JSON extraction used by the single-agent endpoint. Its output shape drives everything downstream. Persisted: analysis_agent step + multi_agent_analysis_agent ToolCall with cost estimates.

Stage 2 — Task Agent (deterministic, conditional)

Runs only when create_tasks=true and the agent has task_creator. For each action_item, a Task is built with the analysis's priority, a description citing the intent, and a deadline sanitized by normalize_due_date (keeps "today"/"tomorrow"-style words, drops past ISO dates, passes other text through). No LLM — the "agent" is a loop. Persisted: task_agent step + multi_agent_task_agent ToolCall (uncosted, correctly).

Stage 3 — Reply Agent (LLM, temp 0.3, with fallback)

With reply_generator enabled, run_reply_agent drafts a fresh professional reply seeing both the raw email and the analysis. Without it, the workflow falls back to the analysis's own suggested_reply — the stage never leaves the response empty. Persisted either way: reply_agent step + costed ToolCall.

Stage 4 — Reviewer Agent (LLM, temp 0)

Tasks are committed and refreshed first (so the reviewer sees real IDs), then run_reviewer_agent judges the entire package:

json
{ "approved": true, "quality_score": 0.92, "issues": [], "recommendation": "…" }

The verdict is advisory — nothing is blocked or rolled back on rejection (Evaluation). Persisted: reviewer_agent step + costed ToolCall.

Finalization

WorkflowRun gets status="success", quality_score=str(review.quality_score), and a final_output JSON snapshot (analysis, tasks with IDs, reply, review). The HTTP response mirrors that snapshot as MultiAgentEmailWorkflowResponse.

Tool gating summary

Tool on agentEffect
email_analyzerrequired — 403 without it
task_creatorstage 2 runs (when create_tasks=true)
reply_generatorstage 3 uses the dedicated Reply Agent instead of the fallback

Mix and match per agent — see Tools.

Failure semantics

Any exception in any stage triggers the triple record — failed multi_agent_email_workflow ToolCall, run flipped to failed with the error, workflow_error step — then 500. Stages already persisted keep their records, so the trace shows exactly how far the pipeline got. Note one consequence: tasks committed in stage 2 survive a stage-3/4 failure.

Single-agent vs. multi-agent

POST .../email/analyzePOST .../workflows/email
LLM calls13
Replyanalysis's suggested_reply onlydedicated Reply Agent (or fallback)
Quality verdictnonereviewer score + issues
TracingToolCalls onlyfull WorkflowRun + steps
Cost logging✅ per LLM stage
Latency~1–3 s~3–9 s

Rule of thumb: analyze for cheap triage, the workflow when you want a send-ready reply with a quality check and a full audit trail.