ag-ui-protocol / ag-ui-protocol/ag-ui

forwardedProps.config.configurable is ignored by the self-hosted Python LangGraph adapter

Open
#2,605 0 comments 0 reactions 0 assignees View on GitHub
bug Integration
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.