Hi @gdrouet
great question. It’s been confusing to me for a long time though, I think I can explain it this way:
short: config is the legacy/runtime-plumbing channel (RunnableConfig: configurable, tags, recursion_limit), context is its typed v0.6+ successor for user-defined values, and - contrary to your current understanding - both can be available during graph construction if you register a graph factory instead of a pre-compiled graph.
- What is the assistant-level
config field for?
It is a persisted RunnableConfig fragment. Per the Create Assistant API reference, assistant config has exactly three keys:
configurable: dict - arbitrary key/value configuration (the pre-v0.6 way to parameterize a graph)
tags: string[] - tracing tags applied to every run of this assistant
recursion_limit: int - execution knob for the Pregel loop
When a run is created, the server merges the assistant’s config into the run’s RunnableConfig. In the dev server (langgraph_runtime_inmem/ops.py, Runs.put) the run config is built as:
"config": Runs._merge_jsonb(
assistant["config"], # assistant-level config
config, # run-level config (wins on conflicts)
{"configurable": configurable}, # assistant -> thread -> run configurable
{"metadata": merged_metadata},
),
and the configurable dict itself is merged as assistant.configurable -> thread.configurable -> run.configurable -> {run_id, thread_id, graph_id, assistant_id, user_id} - later sources win, so run-level values override assistant-level values.
So the intended purposes of assistant config today are:
- carrying values for graphs that still read
config["configurable"] (the pre-0.6 pattern - still fully supported for backward compatibility)
- pinning execution knobs that
context cannot express: tags and recursion_limit
- feeding the graph-factory path (see Q3)
2. How does it differ from context in practice?
context is the typed replacement for user-defined config["configurable"] values, introduced in LangGraph v0.6:
- You declare a
context_schema on the graph (StateGraph(State, context_schema=Context) / create_agent(..., context_schema=Context)). In the langgraph source: libs/langgraph/langgraph/graph/state.py - context_schema parameter; the old config_schema parameter is deprecated since v0.6.0 with removal planned in v2.0.0
- It travels as a separate channel, not inside
config: graph.invoke(input, context=...) (libs/langgraph/langgraph/pregel/main.py, invoke(..., context: ContextT | None = None)), is coerced to your dataclass/Pydantic/TypedDict schema (_coerce_context), and is exposed to nodes/tools/middleware as runtime.context on the frozen Runtime dataclass (libs/langgraph/langgraph/runtime.py). Internally the Runtime rides along in config["configurable"]["__pregel_runtime"], which is why the two systems interoperate
- On the assistant object,
config and context are stored as two separate fields (AssistantBase in libs/sdk-py/langgraph_sdk/schema.py: config: Config, context: Context; the latter was added in SDK v0.6.0 - “Static context to add to the assistant”)
- At run creation the server merges them independently:
"context": Runs._merge_jsonb(assistant.get("context", {}), kwargs.get("context", {})) - run-level context wins. The merged context is then passed to the graph’s astream/ainvoke as the context argument (the server even filters it against your graph’s context JSON schema first - _filter_context_by_schema in langgraph_api/stream.py)
Practical rule of thumb:
|
config |
context |
| Typing |
untyped configurable dict |
validated against context_schema |
| Access in graph code |
config["configurable"]["x"] (legacy) |
runtime.context.x (current) |
| Extra knobs |
tags, recursion_limit |
- |
| Status |
supported, legacy for user values |
recommended since v0.6 |
| Merge at run time |
assistant → thread → run (run wins) |
assistant → run (run wins) |
The docs (Assistants, configuration how-to) now describe assistants almost entirely in terms of context - that is the recommended channel for “assistant settings” your nodes read. The v1 migration guide explicitly notes the old config["configurable"] pattern still works but context is recommended.
3. Can config/context be accessed during graph initialization?
Yes - if you register a graph factory. This is the part your current understanding misses.
If your langgraph.json points at a callable instead of a compiled graph ("my_graph": "./graph.py:make_graph"), the server rebuilds the graph via that factory every time it needs it. From langgraph_api/graph.py (installed server, v0.4.20):
FACTORY_ACCEPTS_CONFIG[graph_id] = len(inspect.signature(graph).parameters) > 0
...
value = value(config) if factory_accepts_config(value, graph_id) else value()
and from langgraph_api/stream.py - when a run executes, the factory receives the merged run config (assistant config → run config, including the merged configurable):
context = kwargs.pop("context", None)
config = cast(RunnableConfig, kwargs.pop("config")) # already merged with assistant config
graph = await stack.enter_async_context(
get_graph(configurable["graph_id"], config, ...)
)
So inside make_graph(config: RunnableConfig) you can read config["configurable"]["my_setting"] and it will reflect the assistant’s configurable (overridden by run-level values if provided). Note the factory is also invoked for non-run endpoints (assistant schemas, subgraphs, state reads) - e.g. langgraph_api/api/assistants.py loads assistant["config"] and calls get_graph(graph_id, config) for the schema endpoints - so assistant config is available there too.
context in the factory: on recent agent-server versions the factory can declare a second parameter, runtime: ServerRuntime (Rebuild graph at runtime; langgraph_sdk/runtime.py in the langgraph repo). The server inspects type hints and injects it. ServerRuntime exposes:
access_context - why the factory is being called ("threads.create_run", "assistants.read", "threads.read", …),
execution_runtime - non-None only when actually executing a run, and it carries context (the merged assistant → run context), the authenticated user, and the store.
async def make_graph(config: RunnableConfig, runtime: ServerRuntime):
if ert := runtime.execution_runtime:
ctx = ert.context # merged assistant/run context - execution paths only
...
(The 2-arg factory shape is exercised end-to-end in the langgraph repo’s integration fixture libs/sdk-py/integration/graph/factory_graph.py.) On older servers (1-arg factory), context is not passed to the factory at all - it is only handed to the graph at invoke time - so on those versions only config["configurable"] is usable for construction-time decisions.
If you export a pre-compiled graph (no factory), then your statement is correct: the module is imported once at server startup and nothing assistant-specific exists at compile time.