LangGraph/Persistence
Intermediate16 min

Runtime & Context: Dependency Injection

Runtime[Context] is LangGraph's typed dependency injection system — giving every node and tool access to per-run configuration, cross-thread storage, execution metadata, and transient state through a single parameter. This article covers the full data placement decision (including UntrackedValue for non-serializable objects), the corrected subgraph propagation behavior, and the production failure modes that trip teams on first deployment.

Quick Reference

  • Runtime[T] properties: context, store, stream_writer, previous, execution_info, server_info — state is NOT on Runtime
  • State is a separate function parameter: def node(state: State, runtime: Runtime[Context])
  • ToolRuntime[T] adds: state, config, tools, tool_call_id — import from langgraph.prebuilt, NOT langchain.agents
  • context_schema defines immutable per-run data (user IDs, plan tier, feature flags) — validated at invocation
  • UntrackedValue marks state fields that must NOT be checkpointed — use for DB connections, caches, HTTP clients
  • Context auto-propagates to compiled subgraphs (LangGraph >= 0.6.0) — wrapper functions break propagation
  • runtime.execution_info gives you thread_id, run_id, attempt_number without parsing config
  • runtime.previous (functional API only) returns the previous return value for checkpoint recovery

Context vs State vs Store vs UntrackedValue

Before writing any Runtime code, answer one question: where does this data belong? Getting it wrong means either using mutable data as if it were immutable (context mistake), storing ephemeral data across threads (store mistake), accessing runtime properties that don't exist (state mistake), or putting non-serializable objects into checkpoints (UntrackedValue mistake).

Where does this data belong?node function, tool, or cross-thread store?Mutableduring run?YESstateseparate function paramnode(state, runtime)NOPersists acrossthreads?YESruntime.storecross-thread storagepersists across runsNOSerializable /checkpoint-safe?YESruntime.contextimmutable per-runfrozen at invocationNOUntrackedValuetransient state fieldnot checkpointed

state = changes during run · store = cross-thread persistence · context = fixed per-run · UntrackedValue = transient, not checkpointed

Data exampleCorrect placeWhy
user_id, org_id, plan tierruntime.contextSet once at invocation, never changes during the run
Messages accumulated so farstate (function param)Grows with each node step
Running token countstate (function param)Updated by each node
User preferences learned over timeruntime.storeNeeds to survive across separate runs/threads
API key reference (NOT the key itself)runtime.contextFixed per-run, but store secrets in env vars
DB connection poolUntrackedValue (state field)Must not be serialized; reconstructed each run
HTTP client sessionUntrackedValue (state field)Transient, not checkpoint-safe
Current tool call IDToolRuntime.tool_call_idInjected automatically in tools
runtime.state does not exist

A common mistake from reading old articles: Runtime does not have a .state property. State is the FIRST parameter of your node function — def node(state: MessagesState, runtime: Runtime[UserContext]). Runtime gives you context, store, stream_writer, previous, execution_info, and server_info. That's it.

UntrackedValue: where non-serializable objects live

UntrackedValue is a LangGraph state annotation that marks a field as transient — it exists during execution but is never written to a checkpoint. Use it for database connections, HTTP clients, or caches that can't be serialized. Define it as a state field (not on context) and reconstruct it at the start of each run.

UntrackedValue for transient, non-serializable objects