Tutorials Logic, IN info@tutorialslogic.com

LangGraph Functional API and Subgraphs: Entrypoints, Tasks, Replay, and Composition

Replay Safety: Durable execution resumes by replaying workflow code from a saved point. Isolate side effects and non-deterministic work so replay does not repeat completed external actions.

Graph API Versus Functional API

LangGraph provides two complementary ways to express orchestration. The Graph API makes state, nodes, and edges explicit. The Functional API lets ordinary Python control flow become durable by decorating an entrypoint and the operations that must be recorded as tasks.

Neither API is universally better. The Graph API is strongest when workflow topology should be visible and inspectable. The Functional API is attractive when the process reads more naturally as conditionals, loops, and function calls.

Subgraphs add composition. A parent graph can delegate a bounded workflow to another graph while preserving clear state and persistence rules. This is useful for reusable specialists, domain modules, and multi-agent systems.

Choose the Graph API when named nodes, routes, visualization, and state transitions are central to understanding the business process. Choose the Functional API when the workflow is easier to read as normal Python and you still need persistence, streaming, interrupts, and durable execution.

The two APIs can coexist. A Functional API entrypoint can call graph-backed components, and a graph node can delegate work to a function or subgraph.

  • Graph API: explicit topology and state transitions.
  • Functional API: ordinary control flow with durable primitives.
  • Use the representation that makes failures and ownership easiest to explain.
  • Do not convert deterministic helper functions into graph nodes without a reason.

Entrypoints Define Durable Workflow Boundaries

An `@entrypoint` function represents the workflow invocation boundary. Its input and return value form the public contract, while a checkpointer can persist progress for a thread.

Keep entrypoint inputs serializable and explicit. Hidden global state makes replay, testing, and production recovery harder to reason about.

  • Accept a typed request object or dictionary.
  • Return a stable result shape.
  • Use thread configuration when runs must resume.
  • Separate user-facing result data from operational metadata.

Tasks Protect Side Effects and Non-Deterministic Work

Durable execution may replay workflow code from an earlier point when a run resumes. Operations such as API calls, random values, timestamps, file writes, and payments should therefore be wrapped in tasks or isolated nodes.

Recorded task results can be reused during replay instead of repeating completed work. Write operations should still be idempotent because a task that started but failed before recording completion may run again.

  • Wrap network calls and side effects in `@task` functions.
  • Use idempotency keys for writes.
  • Avoid reading the current time or randomness directly in replayed control flow.
  • Keep one meaningful side effect per task where practical.

Subgraphs Create Reusable Workflow Modules

A subgraph is a compiled graph used inside another graph. It is a good fit when one part of the system has its own state transitions, tests, ownership, or reuse across several parent workflows.

State can be shared directly when parent and child use common keys, or mapped through a wrapper node when their schemas differ. Explicit mapping is often safer for independently owned modules.

  • Use shared schemas for tightly coupled parent and child graphs.
  • Use adapter nodes when child state should remain private.
  • Document which fields cross the boundary.
  • Test the subgraph independently before parent integration.

Persistence and Interrupts Across Subgraphs

Stateful subgraphs can inherit the parent checkpointer, which allows checkpointing, interrupts, and state inspection to work across the composed execution.

When a subgraph pauses or fails, resumption may replay the parent node that invoked it as well as work inside the child. That makes side-effect isolation and idempotency important at both levels.

  • Use one thread identity for the composed run.
  • Assume the parent call site may replay during resume.
  • Keep interrupt payloads JSON-serializable.
  • Avoid placing unprotected side effects before an interrupt.

Choose Boundaries That Improve Operations

A subgraph boundary should make deployment, testing, tracing, permissions, or team ownership clearer. Splitting every node into a subgraph adds ceremony without improving control.

Trace parent and child names distinctly so operators can see whether a failure came from routing, state mapping, or the child workflow itself.

Functional API and Subgraphs in the Right Order

Choose the Functional API when the workflow is easiest to understand as ordinary Python control flow but still needs durable execution. The entrypoint describes the main workflow, and tasks wrap work that may need checkpointing, replay awareness, retries, or safe side-effect handling. This is especially useful for sequential workflows that would look awkward if forced into many tiny graph nodes.

Choose subgraphs when a part of the workflow deserves its own internal structure. A research workflow, approval workflow, or specialist-agent workflow may have enough state, routes, and tests to justify a subgraph. The parent graph should not need to know every internal step; it should only know the subgraph contract: input state, output update, failure behavior, and trace boundary.

The expert decision is not Graph API versus Functional API versus subgraph forever. Many systems combine them. Use explicit graphs where architecture review matters, functional entrypoints where Python-shaped orchestration is clearer, and subgraphs where composition improves ownership, testing, or reuse.

  • Use Functional API for durable Python-shaped workflows.
  • Wrap side effects and non-deterministic work in tasks.
  • Use subgraphs when a workflow module has its own state and routes.
  • Document parent-child state mapping clearly.
  • Test replay and resume behavior around task and subgraph boundaries.

Replay-Safe Composition

The Functional API uses `@entrypoint` for the durable workflow and `@task` for discrete work whose result should be recorded. Keep non-deterministic calls and external side effects inside tasks. When execution resumes, recorded task results can be reused, but the entrypoint control flow must follow a compatible sequence so the runtime can match prior work.

Idempotency still matters. A task may be retried or re-executed after a failure boundary, and a remote call may complete even when its response is lost. Give writes a stable operation ID, check for an existing result, and return the committed outcome. Do not place a payment or email directly in untracked entrypoint code.

A subgraph deserves a clear input, output, state mapping, checkpointer policy, and failure contract. Per-invocation persistence fits independent calls; per-thread persistence supports an agent that retains state across calls but cannot be invoked concurrently without careful namespace design. Inspect subgraph state during debugging instead of flattening every internal channel into the parent.

Replay-safe functional code must make the same task decisions from recorded input and state. Avoid current-time, random, or environment-dependent branching outside tasks unless those values are first captured durably. Parallel tasks may complete in a different wall-clock order, so combine their results by stable key rather than accidental completion order.

Choose the API at module boundaries, not by personal preference across the whole codebase. An explicit parent graph can call a functional workflow, and a functional entrypoint can invoke a compiled subgraph. Keep the boundary typed and traceable so composition does not hide ownership.

  • Wrap non-deterministic and side-effecting work in tasks.
  • Keep resumed entrypoint control flow compatible with recorded tasks.
  • Make external mutations idempotent outside checkpoint assumptions.
  • Document parent and subgraph state ownership.
  • Test interrupt, replay, failure, and concurrent invocation boundaries.

Functional Composition Examples

Beginner Example: Functional API Entrypoint and Task

This workflow records the external lookup as a task while keeping the orchestration in normal Python control flow.

Beginner Example: Functional API Entrypoint and Task
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.func import entrypoint, task

@task
def lookup_order(order_id: str) -> dict:
    # External API call belongs inside a task.
    return {"order_id": order_id, "status": "shipped"}

@entrypoint(checkpointer=InMemorySaver())
def order_status_workflow(request: dict) -> dict:
    order = lookup_order(request["order_id"]).result()
    return {
        "answer": f"Order {order['order_id']} is {order['status']}.",
        "order": order,
    }

config = {"configurable": {"thread_id": "order-17"}}
print(order_status_workflow.invoke({"order_id": "ORD-17"}, config))
  • The entrypoint is the durable workflow boundary.
  • The external lookup is isolated in a task.
  • The thread ID identifies persisted execution state.

Intermediate Example: Idempotent Write Task

A stable idempotency key prevents duplicate side effects if work must be retried.

Intermediate Example: Idempotent Write Task
from langgraph.func import task

@task
def create_ticket(run_id: str, summary: str) -> dict:
    idempotency_key = f"{run_id}:create-ticket"

    # Pass the key to the backend so a retry returns the existing ticket.
    return {
        "ticket_id": "INC-1042",
        "idempotency_key": idempotency_key,
        "summary": summary,
    }
  • Task recording reduces repeated work during replay.
  • Backend idempotency still protects ambiguous failure cases.
  • Use stable run or operation IDs instead of random keys created during retry.

Advanced Example: Parent Graph Calling a Subgraph

The child graph owns research-specific state while a wrapper maps data across the boundary.

Advanced Example: Parent Graph Calling a Subgraph
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class ResearchState(TypedDict):
    query: str
    findings: list[str]

def search(state: ResearchState) -> dict:
    return {"findings": [f"Result for {state['query']}"]}

research_builder = StateGraph(ResearchState)
research_builder.add_node("search", search)
research_builder.add_edge(START, "search")
research_builder.add_edge("search", END)
research_graph = research_builder.compile()

class ParentState(TypedDict):
    question: str
    evidence: list[str]

def run_research(state: ParentState) -> dict:
    child_result = research_graph.invoke({
        "query": state["question"],
        "findings": [],
    })
    return {"evidence": child_result["findings"]}

parent_builder = StateGraph(ParentState)
parent_builder.add_node("research", run_research)
parent_builder.add_edge(START, "research")
parent_builder.add_edge("research", END)
parent_graph = parent_builder.compile()
  • The wrapper makes parent-to-child state mapping explicit.
  • The subgraph can be tested independently.
  • Compile the parent with persistence when the composed run must be durable.
Before you move on

LangGraph Functional API and Subgraphs: Entrypoints, Tasks, Replay, and Composition Mastery Check

5 checks
  • Choose Graph or Functional API based on workflow readability and operational needs.
  • Wrap non-deterministic work and side effects in tasks or isolated nodes.
  • Make write operations idempotent.
  • Use subgraphs for meaningful reusable workflow boundaries.
  • Document state and persistence behavior across parent-child calls.

LangGraph Questions Learners Ask

Yes. They are complementary orchestration styles and can call shared functions or graph-backed components.

No. Tasks improve durable replay, but writes should still use idempotency because failures can occur before completion is recorded.

Often it can inherit the parent checkpointer. Use separate persistence only when the ownership and lifecycle genuinely require it.

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.