if/else, loops, and function calls). Values pass between tasks as return values, not through a shared graph state schema.
This walkthrough builds a customer support email agent with the Functional API. For the same scenario with the Graph API, see Thinking in the Graph API. To choose between the APIs, see Choosing between Graph and Functional APIs.
Start with the process you want to automate
Imagine that you need to build an AI agent that handles customer support emails. Your product team has given you these requirements:Step 1: Map out your workflow as discrete steps
Start by identifying the distinct steps in your process. Each step becomes a task (a function that does one specific thing). Then sketch how the entrypoint calls those tasks and branches. Do this before writing code.if/else on the left, then human review on the right. The Graph API would encode the same paths as nodes and edges.
Now that the components are identified, clarify what each step needs to do:
Read Email: Extract and parse the email contentClassify Intent: Use an LLM to categorize urgency and topicDoc Search: Query your knowledge base for relevant informationBug Track: Create or update an issue in a tracking systemDraft Reply: Generate an appropriate responseHuman Review: Escalate to a human agent for approval or handlingSend Reply: Dispatch the email response
Step 2: Identify what each step needs to do
For each step from Step 1, decide what kind of work it is and what inputs it needs. Use the categories below, then expand the email-agent examples for concrete fields and retry choices.LLM steps
Use when you need to understand, analyze, generate text, or make reasoning decisions
Data steps
Use when you need to retrieve information from external sources
Action steps
Use when you need to perform external actions
User input steps
Use when you need human intervention
LLM steps
When a step needs to understand, analyze, generate text, or make reasoning decisions:Classify intent
Classify intent
- Static context (prompt): Classification categories, urgency definitions, response format
- Dynamic context (arguments): Email content, sender information
- Desired outcome: Structured classification that drives later branches
Draft reply
Draft reply
- Static context (prompt): Tone guidelines, company policies, response templates
- Dynamic context (arguments): Classification results, search results
- Desired outcome: Professional email response ready for review
Data steps
When a step needs to retrieve information from external sources:Document search
Document search
- Parameters: Query built from intent and topic
- Retry strategy: Yes, with exponential backoff for transient failures
- Caching: Could cache common queries to reduce API calls
Action steps
When a step needs to perform an external action:Send reply
Send reply
- When to execute: After approval (human or automated)
- Retry strategy: Yes, with exponential backoff for network issues
- Should not cache: Each send is a unique action
Bug track
Bug track
- When to execute: Always when intent is “bug”
- Retry strategy: Yes, critical to not lose bug reports
- Returns: Ticket ID to include in the response
User input steps
When a step needs human intervention:Human review
Human review
- Context for decision: Original email, draft response, urgency, classification
- Expected input format: Approval boolean plus optional edited response
- When triggered: High urgency, complex issues, or quality concerns
Step 3: Decide what data to pass between tasks
In the Functional API, there is no shared graph state schema. Each task takes arguments and returns a value. The entrypoint holds intermediate results in local variables and passes them to the next task. Ask yourself these questions about each piece of data:Share it across tasks
Does a later task need this value? If yes, return it from the task that creates it, store it in an entrypoint local variable, then pass it as an argument to the task that needs it.
Keep it inside one task
Is it only used to format a prompt or make a call inside one task? If yes, keep it local to that task. Do not return it.
- The original email and sender info (inputs to the workflow)
- Classification results (needed by later branches and the draft step)
- Search results (expensive to re-fetch)
- The draft response (needed through review and send)
Step 4: Build your tasks
Turn each Step 1 unit of work into a task. Tasks checkpoint independently, can retry on failure, and return values the entrypoint can await. A@task is a Python function that does one unit of work and returns a result. Call it from an entrypoint and resolve it with .result() (or await in an async entrypoint).
Handle errors appropriately
Before you fill in task bodies, consider how each task can fail. Attach retries on the task for transient errors. Useinterrupt() in the entrypoint for human input. Use this table as a guide:
Implementing the email agent tasks
Add one task per step from Step 1 (except human review, which usesinterrupt() in the entrypoint in Step 5). The stubs below show the function signatures, return shapes, and retry policies for the email agent. Replace the placeholder bodies with your real LLM calls and API integrations. Put these tasks in the same file as the types from Step 3.
read_email is a passthrough here. Keeping it as its own task still gives you a checkpoint before classification and a place to add real MIME parsing later.
Step 5: Compose the entrypoint
The entrypoint is the workflow. Add it below the tasks. It calls those tasks in order, branches withif/else (the control flow from Step 1), and pauses for humans with interrupt(). Provide a checkpointer when you need durable execution and human-in-the-loop.
Try out your agent
After the entrypoint is defined, invoke it with a sample email. With the placeholder classifier above, this urgent billing message is treated as high urgency, so the run hitsinterrupt() and you can practice resume. Use the same thread_id for the first invoke and the resume invoke.
interrupt(), saves completed task results to the checkpointer, and waits. When you resume with Command(resume=...), completed tasks reload from the checkpoint instead of re-running. The thread_id keeps this conversation’s state together.
Summary and next steps
Key insights
Building this email agent shows the Functional API way of thinking:Break into discrete tasks
Each task does one thing well. Task boundaries are where results checkpoint, so failures and resumes redo less work.
Pass values, do not share graph state
Return raw data from tasks and pass it as arguments. Keep prompt formatting inside the task that needs it.
Entrypoints own control flow
Use
if/else, loops, and calls. You do not declare edges or conditional edge maps.Errors are part of the flow
Transient failures get task retry policies, user-fixable problems pause with
interrupt(), and unexpected errors fail the run.Human input is first-class
interrupt() pauses execution, saves completed task results, and resumes when you provide input.Composition stays in code
The workflow reads like a normal function. That fits existing procedural code and fast iteration.
Task granularity trade-offs
Most applications can use the breakdown above. Read this section when you are deciding whether to merge or split tasks. LangGraph checkpoints at task boundaries inside an entrypoint. When a run resumes after an interrupt or failure, completed task results load from the checkpoint. Work that lived in the entrypoint body outside a task may run again. Why this breakdown fits the email agent:- Isolation of external services: Doc search and bug tracking are separate tasks so retries and failures stay scoped to those API calls.
- Visibility: Classification is its own task, so you can inspect the structured result before branching.
- Different failure modes: LLM calls, database lookups, and email sends get different retry policies.
- Testing: Smaller tasks are easier to unit test and reuse.
read_email and classify_intent into one task is valid. You lose a checkpoint between them and repeat both on failure. For most agents, the observability benefit of separate tasks is worth it.
Where to go from here
This walkthrough introduces Functional API design. Extend it with:Build checklist
Decision guide for workflows, tasks, streaming, interrupts, and memory
Thinking in the Graph API
The same email agent with nodes, edges, and shared state
Compose workflows
Parallel tasks, calling graphs, and nesting entrypoints
Configure tasks
Retries, timeouts, caching, and resume after errors
Human-in-the-loop
Interrupt patterns for approval and tool review
Add memory
Short-term checkpointers and long-term stores
Connect these docs to your agent of choice via MCP for real-time answers.

