ag-ui-protocol / ag-ui-protocol/ag-ui
forwardedProps.config.configurable is ignored by the self-hosted Python LangGraph adapter
- Dominant language
- Python
- Stars
- 15.9k
- Forks
- 1.4k
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 163
Description
## Summary
On the self-hosted Python adapter, `forwardedProps.config.configurable` never reaches a node's `RunnableConfig`. The LangGraph Platform (TypeScript) adapter does apply it, so the two adapters disagree on a documented input.
## Where it breaks
`integrations/langgraph/python/ag_ui_langgraph/agent.py` builds the run config from `self.config` (the constructor argument) plus `thread_id`, and nothing reads `forwarded_props["config"]`:
- `agent.py:1649` — `config["configurable"] = {**(config.get('configurable', {})), "thread_id": thread_id}`
- `agent.py:2197` — `config["configurable"]["thread_id"] = thread_id`
The forwarded `config` object is instead spread into the **graph input** alongside state (`agent.py:2377`), where it is meaningless to the node.
The TypeScript adapter, by contrast, merges it into the run config:
- `integrations/langgraph/typescript/src/agent.ts:586` and `:795` — `forwardedProps?.config` is passed to `mergeConfigs({...})`, and the result becomes the run payload's `config`.
CopilotKit's `LangGraphAGUIAgent` subclasses the Python `LangGraphAgent` and only injects its own `copilotkit` payload into `configurable` (`sdk-python/copilotkit/langgraph_agui_agent.py:318-326`), so it does not close the gap either.
## Reproduction
End-to-end against a real compiled `StateGraph` with a `MemorySaver`, driving `LangGraphAgent.run()` directly (no mocks), on `main` at `e929f557f`:
```python
FORWARDED = {"config": {"configurable": {"authToken": "tok-123"}}}
async def router(state: AgentState, config: RunnableConfig):
print("authToken =", (config.get("configurable") or {}).get("authToken"))
```
Two runs on one thread, both with the same `forwardedProps`:
```
run 1: config.configurable.authToken=None
run 2: config.configurable.authToken=None
```
Expected `tok-123` on both.
## Why this matters
This is the documented way to pass per-run values on this path. The CopilotKit docs page [Passing configurables](https://docs.copilotkit.ai/integrations/langgraph/configurable) (source: `showcase/shell-docs/src/content/docs/integrations/langgraph/configurable.mdx`) shows exactly this shape and a **Python** node reading `config['configurable'].get('authToken')`. Users following it on a self-hosted Python agent get `None`.
It is also the correct alternative to recommend to anyone trying to hydrate per-run values through state — see CopilotKit/CopilotKit#3168 and the fix in #2604 — so the gap blocks the natural workaround for that report.
## Suggested fix
Read `forwarded_props["config"]` in `prepare_stream` (and `prepare_regenerate_stream`) and merge it into the `RunnableConfig` the way the TypeScript adapter's `mergeConfigs` does, honoring the graph's declared config schema so undeclared keys are filtered out. `config` is already reserved in `ADAPTER_OWNED_FORWARDED_PROPS` by #2604, so it will no longer leak into the graph input once that lands.
Worth confirming as part of the fix whether `recursion_limit`, which the same docs page shows as a sibling of `configurable`, is meant to be honored here too.
Contributor guide
Assessment
This issue has not been assessed yet.