refactor(backend): split agent into router + subgraphs; ask_location via interrupt; USE_SUBGRAPH toggle#3
Merged
Conversation
Renames the node to `renameThreadAgentNode` (matching the new `-Agent` naming convention used by the router/chat/weather sub-agents) and pulls the inline Chinese system prompt out into `RENAME_THREAD_PROMPT` in `backend/prompt/system.ts` so all four agent prompts live in one place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds `CHAT_AGENT_PROMPT`, `ROUTER_AGENT_PROMPT`, and `WEATHER_AGENT_PROMPT` to the shared prompts file. These ship ahead of the agent wiring so the prompts are reviewable on their own; the consumer commits land next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewires the graph: `START → routerAgent → (weatherAgent | chatAgent) → afterAgent → END`, with `renameThreadAgent` fanning out from `START` like before. Adds the router and weather nodes, plus `shouldCallTool` (renamed from `routeAfterAgent`) and the deleted-then-replaced test. `chatModelWithoutThink` loses its `think: false` modelKwarg (unused by the active provider). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The three backend tools the weather sub-agent uses. `ask_location` is a UI marker that returns `{status: 'awaiting_user_location'}` for the frontend picker; `geocode_location` and `get_weather` wrap the new `lib/open-meteo.ts` client (geocode with a 3-step Open-Meteo → Nominatim → suffix-strip fallback; forecast via Open-Meteo's `/v1/forecast` endpoint).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the assistant-ui toolkit, the two tool-call message parts (`AskLocationCard`, `WeatherCard`), the shared `tool-result` unwrapper, the loading skeleton, the vendored `WeatherWidget` runtime, and wires the toolkit into `useAui({ tools })`. Also flips `ToolGroupRoot` to `defaultOpen` so collapsed tool calls don't lose the weather canvas. Includes CLAUDE.md rule 6 covering the `mx-*` / `shadow-*` rules for components inside `ToolFallbackContent`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swaps `@assistant-ui/react-markdown/styles/dot.css` for an inlined keyframes block in `app/globals.css` with `--aui-content` overridden to `"\258d"` (typewriter caret) and `color: var(--muted-foreground)`. `markdown-text.tsx` keeps a commented-out import with a one-liner pointing to the inlined replacement. `observability-panel.tsx` gets a small oxfmt pass (line wrap + missing newline). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…kpointId The load callback now returns: - state.tasks[0]?.interrupts so useLangGraphInterruptState() picks up a paused run (e.g. ask_location waiting for a location pick) on page refresh, not just the live stream path - state.values.ui so persisted typedUi state is restored on reload getCheckpointId gains explicit casts on state.values.messages and the message id comparison: the SDK types DefaultValues too loosely to let .messages compile, and the .every callback parameter was implicit any. Refs #4563 (resolves the reload half of the subgraph-interrupt design gap; the live path still needs the runtime fix).
…e for subgraph updates
The ask_location tool now uses LangGraph interrupt() instead of
returning a placeholder. On the first call it throws GraphInterrupt
with { awaiting: 'location' }; on the resumed call (after the
frontend sends Command({ resume: <json> })) it parses the payload
and returns it as the tool result. A single ToolMessage is then
written by ToolNode — no more duplicate-message 400 from the old
addResult path.
AskLocationCard calls useLangGraphSendCommand({ resume: ... })
on user pick instead of addResult. The { status: 'awaiting_user_location' }
variant is dropped from AskLocationResult — the tool never returns
that shape anymore.
A pnpm patch on @assistant-ui/react-langgraph@0.14.9 makes
useLangGraphMessages run extractMessagesFromUpdates + setInterrupt
on the namespaced updates branch instead of dropping the event
after firing onSubgraphUpdates. Without this, the AIMessage with
tool_calls and the __interrupt__ payload both reach the browser
but the runtime silently throws them away, so the toolkit cannot
find a tool-call part to render and useLangGraphInterruptState()
returns undefined during a live stream. The reload path was
already covered by load() returning state.tasks[0].interrupts
(see b151521). Refs #4563.
…esult Drop the interrupt() + sendCommand path. The tool body now returns its sentinel synchronously; the ToolNode writes the ToolMessage that the card keys on, and the user's pick comes back as an overwritten tool result via addResult. The runtime patch, the askLocationResumeNode, and the card's sendCommand toggle are all gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enforce the strict order: ask_location first (only if no place named), then geocode_location with that name, then get_weather, then a one-sentence reply. Each step is one tool call, no batching. The previous prompt listed tools in the wrong visual order and let the model chain ask_location with geocode_location in the same turn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… interrupt Split the monolithic StateGraph into a router that dispatches to a chat subgraph or a weather subgraph, each owning its own model + ToolNode. ask_location now pauses via LangGraph interrupt() and the frontend's addResult resumes with the pick. Drop two patches no longer needed: @assistant-ui/react-langgraph 0.14.10 ships the subgraph-update fix, @assistant-ui/core 0.2.19 has the part.text?.trim() guard. Bump 7 patch-level deps and addResult-aware tests.
Cosmetic-only reformat from pnpm format:fix. No logic changes: - package.json: trailing newline stripped by pnpm add - app/assistant.tsx: useMemo call folded to one line - backend/agent.ts: blank line + trailing newline - backend/model.ts: trailing blank line removed - backend/node/router-agent-node.ts: invoke() chain split across lines Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
USE_SUBGRAPH=true uses compiled weatherAgent/chatAgent subgraphs; the default inlined version flattens their model/tool loops into the parent graph. The inlined path is the workaround for the @langchain/core 1.2.1 EventStreamCallbackHandler "Run ID not found in run map" bug that LangGraph JS subgraphs trigger (see memory/langgraph-subgraph-run-map-bug.md). Ask-location card now resumes interrupts via useLangGraphSendCommand and InterruptUI is gated on USE_SUBGRAPH — interrupt flow only applies in subgraph mode.
ask_location was hardcoded to weather (description, default message, prompt forced geocode after picker). Generalised the description + default message and let WEATHER_AGENT_PROMPT skip geocode_location when the picker already returned coords. Frontend env var reads NEXT_PUBLIC_USE_SUBGRAPH (Next.js inlines NEXT_PUBLIC_* vars); InterruptUI tightens a few any-casts and wires addResult to sendCommand for the subgraph-mode resume path. vitest splits into node + frontend (jsdom) projects; tests/frontend/interrupt-ui.test.tsx covers the isLast/USE_SUBGRAPH gate, toolkit dispatch, and the addResult → sendCommand round-trip. CLAUDE.md rewritten to reflect the current router + dual-topology architecture; docs/INTERRUPT.md describes the interrupt contract (ask_location is one example, not the only one). buildSubgraph/buildInlined exported for the agent-topologies smoke test.
27 tasks
This was referenced Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Splits the monolithic agent graph into a router that dispatches to chat / weather subgraphs, with
ask_locationdriven by LangGraph'sinterrupt()+ frontendsendCommand()for human input. Adds aUSE_SUBGRAPHtopology toggle so the inlined workaround stays the default until@langchain/coreships a fix for the "Run ID not found in run map" bug that LangGraph JS subgraphs trigger under 1.2.1.Why
ask_locationneeded a real human-input pause.interrupt()is the right primitive; the prioraddResultpath handled the same need by overwriting a sentinelToolMessage, which still works in inlined mode.memory/langgraph-subgraph-run-map-bug.md). The inlined default is the workaround; flippingUSE_SUBGRAPH=truerestores the compiled-subgraph path when the upstream bug is fixed.Notable changes
backend/agent.ts— two builders (buildSubgraph,buildInlined) gated byUSE_SUBGRAPH. Shared router/rename/after nodes; only the model+tool loop differs.backend/state.ts—RouterAgentState(parent) +CommonAgentState(subgraph) schemas.backend/agent/{chat-agent,weather-agent}.ts— compiled subgraphs for theUSE_SUBGRAPH=truepath.backend/prompt/system.ts—WEATHER_AGENT_PROMPTrewritten to a four-step linear flow with explicit one-tool-per-turn rules. Step 2 lets the model skipgeocode_locationwhen the picker already returned coords.backend/tool/ask-location.ts— description generalised away from weather-specific wording.components/assistant-ui/thread.tsx—InterruptUI(gated onNEXT_PUBLIC_USE_SUBGRAPH+ last-message), dispatches totoolkitregistry byui.docs/INTERRUPT.md— generic interrupt contract;ask_locationis one example.memory/langgraph-subgraph-run-map-bug.md— updated for the toggle.tests/frontend/interrupt-ui.test.tsx— new jsdom project undervitest.config.tscoversInterruptUIrendering,USE_SUBGRAPHgating, andaddResult → sendCommandround-trip.tests/backend/agent-topologies.test.ts— smoke test that both builders compile with the expected node set.Test plan
pnpm test— 147 tests passing (added 6 frontend InterruptUI tests + 4 topology smoke tests + 1 router jsonSchema assertion).pnpm lint && pnpm format:fix— clean.USE_SUBGRAPH=false(default) andUSE_SUBGRAPH=true/NEXT_PUBLIC_USE_SUBGRAPH=true. Pick a city in the picker; verify the model gets the coords and produces the forecast widget.🤖 Generated with Claude Code