> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Thinking in the Functional API

> Learn how to design agents with the LangGraph Functional API using entrypoints, tasks, and ordinary control flow.

When you build an agent with the [Functional API](https://docs.langchain.com/oss/javascript/langgraph/functional-api.md), you break the process into discrete units of work called **tasks**. You compose those tasks inside an **entrypoint** using ordinary language control flow (`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](https://docs.langchain.com/oss/javascript/langgraph/thinking-in-graph-api.md). To choose between the APIs, see [Choosing between Graph and Functional APIs](https://docs.langchain.com/oss/javascript/langgraph/choosing-apis.md).

## 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:

```txt theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
The agent should:

- Read incoming customer emails
- Classify them by urgency and topic
- Search relevant documentation to answer questions
- Draft appropriate responses
- Escalate complex issues to human agents

Example scenarios to handle:

1. Simple product question: "How do I reset my password?"
2. Bug report: "The export feature crashes when I select PDF format"
3. Urgent billing issue: "I was charged twice for my subscription!"
4. Feature request: "Can you add dark mode to the mobile app?"
5. Complex technical issue: "Our API integration fails intermittently with 504 errors"
```

To implement an agent with the Functional API, follow these five steps. Steps 1 and 2 are planning. Steps 3 through 5 turn that plan into types, tasks, and an entrypoint you can run.

## 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.

![Functional API control flow: gather and draft on the left, review and send on the right](https://mintcdn.com/langchain-5e9cc07a/fVUPpLd2squx_ubj/images/langgraph-functional-thinking-flow.svg?fit=max\&auto=format\&n=fVUPpLd2squx_ubj\&q=85\&s=1909c205ed0b1d9932a59dc8a1714bac)

That sketch mirrors the entrypoint: sequential calls and `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 content
* `Classify Intent`: Use an LLM to categorize urgency and topic
* `Doc Search`: Query your knowledge base for relevant information
* `Bug Track`: Create or update an issue in a tracking system
* `Draft Reply`: Generate an appropriate response
* `Human Review`: Escalate to a human agent for approval or handling
* `Send Reply`: Dispatch the email response

> [!TIP]
>
> Some steps always run in sequence (`Read Email` then `Classify Intent`). Others are conditional (`Doc Search` for questions and feature requests, `Bug Track` only for bugs). Encode those branches with ordinary control flow in the entrypoint.

## 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.

> [card] [LLM steps](#llm-steps)
>
> Use when you need to understand, analyze, generate text, or make reasoning decisions

> [card] [Data steps](#data-steps)
>
> Use when you need to retrieve information from external sources

> [card] [Action steps](#action-steps)
>
> Use when you need to perform external actions

> [card] [User input steps](#user-input-steps)
>
> Use when you need human intervention

### LLM steps

When a step needs to understand, analyze, generate text, or make reasoning decisions:

> [accordion] 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

> [accordion] 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:

> [accordion] 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:

> [accordion] 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

> [accordion] 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:

> [accordion] 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:

> [card] 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.

> [card] 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.

For the email agent, the entrypoint needs to carry:

* 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)

> [!TIP]
>
> Store and pass raw data, not formatted prompt text. Format prompts inside the task that calls the model. That keeps tasks reusable and makes checkpoints easier to inspect.

Define types for the values tasks exchange. These are ordinary TypedDicts or TypeScript types, not a Graph API state schema. Add them to your file now; the tasks and entrypoint in the next steps use them.

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
type EmailClassification = {
  intent: "question" | "bug" | "billing" | "feature" | "complex";
  urgency: "low" | "medium" | "high" | "critical";
  topic: string;
  summary: string;
};

type EmailInput = {
  emailContent: string;
  senderEmail: string;
};
```

## 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`](https://reference.langchain.com/javascript/langchain-langgraph/index/task) is a function that does one unit of work and returns a result. Call it from an entrypoint; the call returns a promise you can `await`.

### Handle errors appropriately

Before you fill in task bodies, consider how each task can fail. Attach retries on the task for transient errors. Use `interrupt()` in the entrypoint for human input. Use this table as a guide:

| Error type | Who fixes it | Strategy | When to use |
| - | - | - | - |
| Transient errors (network issues, rate limits) | System (automatic) | Retry policy on the task | Temporary failures that usually resolve on retry |
| LLM-recoverable errors (tool failures, parsing issues) | LLM | Return the error and branch or retry in the entrypoint | The model can adjust with more context |
| User-fixable errors (missing information, unclear instructions) | Human | Pause with `interrupt()` | Need user input to proceed |
| Unexpected errors | Developer | Fail the run | Bugs that need a code fix |

### Implementing the email agent tasks

Add one task per step from Step 1 (except human review, which uses `interrupt()` 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.

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { task } from "@langchain/langgraph";
import type { RetryPolicy } from "@langchain/langgraph";

const retryPolicy: RetryPolicy = { maxAttempts: 3 };

const readEmail = task("readEmail", async (emailContent: string, senderEmail: string) => {
  return { emailContent, senderEmail };
});

const classifyIntent = task(
  "classifyIntent",
  async (emailContent: string, senderEmail: string): Promise<EmailClassification> => {
    // Placeholder: replace with a real LLM call.
    // This heuristic matches the try-out sample so interrupt() runs.
    const lowered = emailContent.toLowerCase();
    if (lowered.includes("charged") || lowered.includes("billing")) {
      return {
        intent: "billing",
        urgency: "high",
        topic: "billing",
        summary: "Customer reports a billing problem",
      };
    }
    if (lowered.includes("crash") || lowered.includes("bug")) {
      return {
        intent: "bug",
        urgency: "medium",
        topic: "product bug",
        summary: "Customer reports a bug",
      };
    }
    return {
      intent: "question",
      urgency: "low",
      topic: "general",
      summary: "Customer question",
    };
  }
);

const searchDocumentation = task(
  "searchDocumentation",
  async (topic: string, summary: string): Promise<string[]> => {
    return [`Docs related to ${topic}`];
  },
  { retry: retryPolicy }
);

const createBugTicket = task(
  "createBugTicket",
  async (emailContent: string, senderEmail: string, summary: string): Promise<string> => {
    return "TICKET-123";
  },
  { retry: retryPolicy }
);

const draftResponse = task(
  "draftResponse",
  async (
    emailContent: string,
    classification: EmailClassification,
    context: string[]
  ): Promise<string> => {
    return `Thanks for your email about ${classification.topic}.`;
  }
);

const sendReply = task(
  "sendReply",
  async (draft: string, senderEmail: string): Promise<void> => {
    return;
  }
);
```

You now have callable tasks. Next, compose them in an entrypoint.

## Step 5: Compose the entrypoint

The entrypoint is the workflow. Add it below the tasks. It calls those tasks in order, branches with `if`/`else` (the control flow from Step 1), and pauses for humans with `interrupt()`. Provide a [checkpointer](https://docs.langchain.com/oss/javascript/langgraph/persistence.md) when you need durable execution and human-in-the-loop.

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import {
  MemorySaver,
  entrypoint,
  interrupt,
} from "@langchain/langgraph";

const checkpointer = new MemorySaver();

const emailAgent = entrypoint(
  { checkpointer, name: "emailAgent" },
  async (inputs: EmailInput) => {
    const parsed = await readEmail(inputs.emailContent, inputs.senderEmail);
    const classification = await classifyIntent(
      parsed.emailContent,
      parsed.senderEmail
    );

    let context: string[] = [];
    if (
      classification.intent === "question" ||
      classification.intent === "feature"
    ) {
      context = await searchDocumentation(
        classification.topic,
        classification.summary
      );
    } else if (classification.intent === "bug") {
      const ticketId = await createBugTicket(
        parsed.emailContent,
        parsed.senderEmail,
        classification.summary
      );
      context = [`Ticket created: ${ticketId}`];
    }

    let draft = await draftResponse(
      parsed.emailContent,
      classification,
      context
    );

    // Pause for human review on billing, urgent, or complex issues
    if (
      classification.intent === "billing" ||
      classification.intent === "complex" ||
      classification.urgency === "high" ||
      classification.urgency === "critical"
    ) {
      const humanDecision = interrupt({
        emailContent: parsed.emailContent,
        draftResponse: draft,
        classification,
      });
      if (!humanDecision?.approved) {
        return { status: "escalated", draft };
      }
      draft = humanDecision.editedResponse ?? draft;
    }

    await sendReply(draft, parsed.senderEmail);
    return { status: "sent", draft, classification };
  }
);
```

### 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 hits `interrupt()` and you can practice resume. Use the same `thread_id` for the first invoke and the resume invoke.

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { Command } from "@langchain/langgraph";

const config = { configurable: { thread_id: "customer_123" } };

const result = await emailAgent.invoke(
  {
    emailContent: "I was charged twice for my subscription! This is urgent!",
    senderEmail: "customer@example.com",
  },
  config
);
// The workflow pauses at interrupt() when urgency is high

// Resume with the human decision
await emailAgent.invoke(
  new Command({
    resume: {
      approved: true,
      editedResponse:
        "We sincerely apologize for the double charge. I've initiated an immediate refund...",
    },
  }),
  config
);
```

The workflow pauses at `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:

> [card] [Break into discrete tasks](#step-1-map-out-your-workflow-as-discrete-steps)
>
> Each task does one thing well. Task boundaries are where results checkpoint, so failures and resumes redo less work.

> [card] [Pass values, do not share graph state](#step-3-decide-what-data-to-pass-between-tasks)
>
> Return raw data from tasks and pass it as arguments. Keep prompt formatting inside the task that needs it.

> [card] [Entrypoints own control flow](#step-5-compose-the-entrypoint)
>
> Use `if`/`else`, loops, and calls. You do not declare edges or conditional edge maps.

> [card] [Errors are part of the flow](#handle-errors-appropriately)
>
> Transient failures get task retry policies, user-fixable problems pause with `interrupt()`, and unexpected errors fail the run.

> [card] [Human input is first-class](https://docs.langchain.com/oss/javascript/langgraph/interrupts.md)
>
> `interrupt()` pauses execution, saves completed task results, and resumes when you provide input.

> [card] [Composition stays in code](#step-5-compose-the-entrypoint)
>
> 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.

Merging `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:

> [card] [Build checklist](https://docs.langchain.com/oss/javascript/langgraph/use-functional-api.md)
>
> Decision guide for workflows, tasks, streaming, interrupts, and memory

> [card] [Thinking in the Graph API](https://docs.langchain.com/oss/javascript/langgraph/thinking-in-graph-api.md)
>
> The same email agent with nodes, edges, and shared state

> [card] [Compose workflows](https://docs.langchain.com/oss/javascript/langgraph/functional-api-workflows.md)
>
> Parallel tasks, calling graphs, and nesting entrypoints

> [card] [Configure tasks](https://docs.langchain.com/oss/javascript/langgraph/functional-api-tasks.md)
>
> Retries, timeouts, caching, and resume after errors

> [card] [Human-in-the-loop](https://docs.langchain.com/oss/javascript/langgraph/functional-api-human-in-the-loop.md)
>
> Interrupt patterns for approval and tool review

> [card] [Add memory](https://docs.langchain.com/oss/javascript/langgraph/functional-api-memory.md)
>
> Short-term checkpointers and long-term stores

***

[Connect these docs](https://docs.langchain.com/use-these-docs.md) to your agent of choice via MCP for real-time answers.

[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/langgraph/thinking-in-functional-api.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
