Skip to content

fix(langgraph, ag-ui): give each AgentRef its own agent instead of aliasing one token - #1000

Merged
blove merged 1 commit into
mainfrom
blove/langgraph-agent-ref-isolation
Sep 4, 2026
Merged

fix(langgraph, ag-ui): give each AgentRef its own agent instead of aliasing one token#1000
blove merged 1 commit into
mainfrom
blove/langgraph-agent-ref-isolation

Conversation

@blove

@blove blove commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Two provideAgent(ref, …) calls in one providers array silently returned the same agent, built from the last config. No error, no warning — you simply got the wrong agent.

AGENT and AGENT_CONFIG are module-scope tokens, so a second call re-registers both (last wins) and every ref.token was useExisting: AGENT, so all refs aliased that single winner. Both @threadplane/langgraph and @threadplane/ag-ui had the identical shape; both are fixed.

We found this building the homepage hero, which needs a replay agent and a live agent side by side and had to work around it with one child EnvironmentInjector per agent. Any consumer with two agents in one injector hits it.

The fix

Each ref now gets a per-call private config token and its own factory, so N refs coexist at one injector level. The factory body was extracted so both paths share it.

The alias direction is deliberate and load-bearing: the ref path aliases AGENT → useExisting ref.token, rather than providing a second independent factory. That is what keeps single-ref behaviour byte-identical — injectAgent() and injectAgent(ref) still return the same instance and the config factory still evaluates once. The naive "provide both separately" fix would have quietly double-instantiated for every existing single-ref consumer.

With multiple refs the bare injectAgent() necessarily stays last-wins, since one token can only mean one thing. That is now stated explicitly in the doc comment instead of being implicit, along with a two-agent example.

Tests

New isolation blocks in both adapters assert two refs yield distinct instances and that each got its own config, on something observable rather than object identity: in langgraph a submit() on A lands only in A's transport; in ag-ui, which exposes no source handle, a stubbed fetch proves each agent hit its own URL. Also covered: single-ref identity, AGENT_CONFIG still resolving, and the lazy factory form still evaluating inside an injection context (asserted by call count before and after injection), which the hero and the demo shell both depend on.

Mutation-checked: reverting each fix turns exactly the isolation test red in both packages.

Verification

langgraph 403 tests, ag-ui 261, both green; langgraph, ag-ui and chat green together; examples-chat-angular green apart from one pre-existing router flake confirmed identical on a clean baseline; 0 lint errors; both packages build. No new public exports, so no API-docs regeneration.

For the reviewer

Provider array order changed in the ref path — ref.token now precedes AGENT. Existing specs that index into the returned array only do so for the no-ref form, which is unchanged, but a consumer indexing a ref call's array would notice.

The hero's scopedAgent() workaround still works and is untouched. It can be removed once this ships; deliberately left for a separate change.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 4, 2026 9:56pm UTC

Request Review

@blove
blove enabled auto-merge (squash) September 4, 2026 21:42

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

…iasing one token

Two provideAgent(ref, ...) calls in one providers array silently returned the
same agent, built from the last config. AGENT and AGENT_CONFIG are module-scope
tokens, so a second call re-registers both (last wins) and every ref.token was
useExisting: AGENT, so all refs aliased that single winner. No error, no
warning. Both adapters had the identical shape; both are fixed.

Each ref now gets a per-call private config token and its own factory. The
alias direction is load-bearing: the ref path aliases AGENT to ref.token rather
than providing a second independent factory, which is what keeps single-ref
behaviour byte-identical — one instance, one config-factory evaluation. With
several refs the bare injectAgent() necessarily stays last-wins, and the doc
comment now says so.

Includes the api-docs regeneration for the changed doc comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/langgraph-agent-ref-isolation branch from be60797 to 8538c51 Compare September 4, 2026 21:52
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 971581c into main Sep 4, 2026
77 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant