Skip to content

feat(website): add 'LangGraph Subgraphs: When to Split a Graph' blog post - #840

Closed
blove wants to merge 1 commit into
mainfrom
blove/langgraph-subgraphs-blog-post
Closed

feat(website): add 'LangGraph Subgraphs: When to Split a Graph' blog post#840
blove wants to merge 1 commit into
mainfrom
blove/langgraph-subgraphs-blog-post

Conversation

@blove

@blove blove commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Publishes the post drafted on the local blove/langgraph-subgraphs-post branch, rebased onto main and updated for what #838 changed.

Why now

The post's thesis: a subgraph gives you an observable boundary, not a state one — and "subgraph" and "tracked subagent" are different things. Until #838 the repo could only demonstrate half of that. Both graphs that populate subagents() (examples/chat and cockpit/chat/subagents) invoke their compiled child from inside a @tool body, because the tool call is what the tracker registers. cockpit/langgraph/subgraphs is now the plain-node counterpart, so the post can cite both mechanisms instead of describing one and asserting the other.

Edits beyond the draft

How the boundary actually gets drawn. LangGraph passes state into and out of a subgraph node through the keys the two schemas share. The boundary is the shape of the overlap, not a wall — a child with no messages key can't reach the transcript because no channel exists for it, not because anything blocks it.

Corrected the transcript section. The draft implied filterSubagentMessages is the lever for keeping child tokens out of messages(). It isn't, for a plain subgraph node:

if (isSubagentNamespace(namespace)) {   // only matches `tools:`
  
  if (options.filterSubagentMessages) return;
}

A research:<uuid> namespace never enters that branch, so the option is a no-op for that shape. transcriptNodeNames is the lever. I also added that the failure mode is mid-stream with a clean end state — the parent's final values event rewrites the message list from authoritative graph state, so the stray bubble vanishes on its own and a final-state assertion cannot catch it. That's not theory: it's what I measured against a live model while building #838 (message count transiently 3, settling to 2).

Claim verification

Re-checked every technical claim against the code rather than trusting the draft:

Claim Result
streamSubgraphs defaults true, option named streamSubgraphs fetch-stream.transport.ts:154, agent.types.ts:115
filterSubagentMessages optional, off by default ✅ and its guard placement documented above
Attribution: exact description → substring either direction → unmapped pending/running fallback subagent-tracker.ts:130-175, exactly as described
Only the first tools: segment is read extractToolCallIdFromNamespace
A test asserts the namespaced-terminal guard stream-manager.bridge.spec.ts:478
Docs say plain subgraph nodes don't appear in subagents() guides/subgraphs.mdx:114
All four outbound links ✅ 200 in production

One claim tightened: the draft said the parent message "settles as interrupted rather than complete." The test asserts phase: 'complete', outcome: 'interrupted' — phase is complete. Reworded to "outcome interrupted rather than a clean completion."

Scope note

Excludes HANDOFF-blog-sequence.md from the source branch — it covers a broader blog sequence rather than this post, and there's no top-level handoff convention under docs/superpowers/ (only audits/, context/, plans/, specs/).

Verification

  • nx build website green; post prerenders at /blog/langgraph-subgraphs-when-to-split with its OG image, and appears in the blog index and sitemap.
  • blog.spec.ts + sitemap-dates.spec.ts: 26/26 pass.
  • Full website suite shows 5 failed files / 10 failed tests — identical with and without this post (confirmed by removing the file and re-running). Pre-existing drift: apps/website has no Nx test target, so these never run in CI. Unrelated to this change and left alone.

🤖 Generated with Claude Code

…post

Publishes the post drafted on blove/langgraph-subgraphs-post, updated for
what #838 changed and for two claims that needed sharpening.

The post's thesis is that a subgraph gives you an observable boundary, not a
state one, and that "subgraph" and "tracked subagent" are different things.
Until #838 the repo could only demonstrate half of that: both graphs that
populate `subagents()` invoke their compiled child from inside a `@tool` body.
`cockpit/langgraph/subgraphs` is now the plain-node counterpart, so the post
cites it as the other half of the pair.

Two substantive edits beyond the draft:

- Added how the state boundary is actually drawn. LangGraph passes state
  through the keys the two schemas *share*, so the boundary is the shape of
  the overlap rather than a wall — a child with no `messages` key cannot reach
  the transcript because no channel exists, not because anything blocks it.

- Corrected the transcript section. The draft implied `filterSubagentMessages`
  is the lever for keeping child tokens out of `messages()`. It isn't, for a
  plain subgraph node: the filter sits inside a branch guarded by
  `isSubagentNamespace()`, which only matches `tools:`, so a `research:<uuid>`
  namespace never reaches it. `transcriptNodeNames` is the lever for that
  shape. Also noted the failure mode is mid-stream with a clean end state,
  which is why a final-state assertion cannot catch it.

Every technical claim re-verified against the code: `streamSubgraphs ?? true`,
`filterSubagentMessages` default-off and its guard placement, the attribution
heuristic (exact `description`, then substring either direction, then unmapped
pending/running fallback), first-`tools:`-segment-only namespace parsing, and
the namespaced-terminal-evidence guard test. Tightened that last claim: the
test asserts outcome `interrupted`, while phase is still `complete`.

Excludes the handoff doc from the source branch — it covers a broader blog
sequence and there is no top-level handoff convention under docs/superpowers.

Verified: website builds; post prerenders at
/blog/langgraph-subgraphs-when-to-split with its OG image; blog + sitemap
specs pass (26/26); all four outbound links return 200 in production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview Aug 27, 2026 8:01pm

Request Review

@blove

blove commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #839, which landed the same post from a later, more developed draft while this was in flight — it already covers the state-boundary mechanism, cites cockpit/langgraph/subgraphs, and adds a section on context windows and error boundaries that this version lacks. Merging this would regress the post.

One correction from here isn't in #839 and I'm opening it separately: filterSubagentMessages is a no-op for plain subgraph nodes, which is the exact shape #839 showcases.

@blove blove closed this Aug 28, 2026
@blove
blove deleted the blove/langgraph-subgraphs-blog-post branch August 28, 2026 04:05
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