Principle:Langchain ai Langgraph Task Definition
| Attribute | Value |
|---|---|
| Concept | Defining individual units of work as tasks using the functional API decorator pattern |
| Workflow | Functional_API_Workflow |
| Type | Principle |
| Repository | Langchain_ai_Langgraph |
| Source | libs/langgraph/langgraph/func/__init__.py
|
Overview
In LangGraph's functional API, a task is the fundamental unit of work. Tasks are defined by decorating ordinary Python functions with the @task decorator, transforming them into trackable, checkpointable operations that return futures instead of direct values. This abstraction enables lazy evaluation, automatic retry on failure, result caching, and parallel execution -- all while preserving the ergonomics of writing plain Python functions.
The task decorator bridges the gap between imperative Python code and LangGraph's underlying graph execution engine. Rather than requiring developers to define explicit graph nodes and edges, the @task decorator lets them write functions that are automatically wired into the execution graph when called from within an @entrypoint.
Description
Task Abstraction
A task in the functional API represents a discrete, self-contained operation that:
- Accepts arbitrary typed arguments and returns a typed result.
- Is only callable from within an
entrypointorStateGraphcontext -- calling a task outside these contexts raises an error. - Returns a future (
SyncAsyncFuture[T]) rather than the computed value directly, enabling the caller to continue dispatching work before blocking on results. - Has its inputs and outputs automatically serialized when a checkpointer is active, allowing the execution engine to skip re-execution of completed tasks on resume.
This abstraction separates the definition of work from its scheduling and execution, which is the core insight of the functional API: developers describe what to compute, and the runtime decides when and how to compute it.
Future-Based Execution
When a task-decorated function is called, it does not execute immediately. Instead, it registers the call with the LangGraph runtime and returns a SyncAsyncFuture. The caller can:
- Call
.result()to block until the task completes and retrieve the value. - Collect multiple futures and resolve them together, enabling parallel execution of independent tasks.
awaitthe future directly in async contexts.
This future-based model means that calling multiple tasks in sequence without immediately resolving them creates implicit parallelism. The runtime can execute these tasks concurrently, constructing a task DAG (directed acyclic graph) from the call pattern.
Retry and Caching Policies
Tasks support two orthogonal resilience mechanisms:
- Retry Policy: When a task raises an exception, the runtime can automatically retry it according to a
RetryPolicythat controls initial interval, backoff factor, maximum interval, maximum attempts, jitter, and which exception types trigger retries. Multiple retry policies can be composed as a sequence.
- Cache Policy: When a
CachePolicyis provided, the runtime caches task results keyed by the task's identity and input values. Subsequent calls with the same inputs skip execution and return the cached result. The cache policy includes a configurable key function and an optional TTL (time to live).
These policies are specified declaratively at decoration time and enforced transparently by the runtime.
Lazy Evaluation and Checkpointing
Tasks participate in LangGraph's checkpointing system. When a checkpointer is enabled:
- Task inputs and outputs must be serializable.
- On first execution, the runtime records the task's result in the checkpoint.
- If the workflow is interrupted (e.g., by a human-in-the-loop
interrupt()) and later resumed, previously completed tasks are not re-executed -- their results are retrieved from the checkpoint.
This lazy evaluation model is essential for long-running workflows that may span multiple sessions or require human approval at intermediate steps.
Usage
from langgraph.func import entrypoint, task
from langgraph.types import RetryPolicy
# Simple task with no configuration
@task
def add_one(x: int) -> int:
return x + 1
# Task with retry policy and custom name
@task(name="fetch_data", retry_policy=RetryPolicy(max_attempts=5))
def fetch_data(url: str) -> str:
# Simulate a network call that may fail
return f"data from {url}"
# Tasks are called from within an entrypoint
@entrypoint()
def my_workflow(numbers: list[int]) -> list[int]:
futures = [add_one(n) for n in numbers]
return [f.result() for f in futures]
my_workflow.invoke([1, 2, 3]) # Returns [2, 3, 4]
Theoretical Basis
The task abstraction draws from several well-established concepts in computer science:
- Futures and Promises: Introduced by Baker and Hewitt (1977), a future represents a value that may not yet be computed. LangGraph's
SyncAsyncFuturefollows this pattern, allowing callers to express dependencies between computations without forcing sequential execution. This is the same concurrency primitive used in Python'sconcurrent.futuresmodule, extended here to support both sync and async contexts.
- Task-Based Parallelism: Rather than requiring developers to manage threads or event loops, the task decorator enables structured concurrency where parallel execution emerges naturally from the call graph. Independent task calls that are not immediately resolved can execute concurrently, while
.result()calls introduce synchronization points.
- Memoization and Idempotent Execution: The caching and checkpointing behavior ensures that tasks are effectively idempotent from the workflow's perspective. Whether a result comes from fresh execution, the cache, or the checkpoint, the calling code is unaffected. This property is critical for reliable execution of long-running workflows.
- Decorator Pattern: The use of Python decorators to transparently augment function behavior follows the decorator design pattern, providing a non-invasive mechanism to add cross-cutting concerns (retry, caching, serialization) without modifying the function's core logic.