Skip to content

feat(sdk): add subagent conversation forking - #5714

Open
Thushanth Bengre (thushanth-bengre-langchain) wants to merge 16 commits into
mainfrom
bengret/feat-subagent-forking
Open

feat(sdk): add subagent conversation forking#5714
Thushanth Bengre (thushanth-bengre-langchain) wants to merge 16 commits into
mainfrom
bengret/feat-subagent-forking

Conversation

@thushanth-bengre-langchain

@thushanth-bengre-langchain Thushanth Bengre (thushanth-bengre-langchain) commented Aug 21, 2026

Copy link
Copy Markdown

Closes #4668.

Adds ForkedSubAgent, a subagent spec that inherits the parent's effective conversation history and exact system prompt instead of only seeing the task description — useful when a subagent is delegated deep into an investigation and shouldn't have to re-derive context the parent already gathered.

mode="fork" is required rather than optional on ForkedSubAgent, since SubAgent.system_prompt is also optional — making both optional would let the two specs collapse into the same shape and get silently misrouted between forking and isolated behavior.

Reconstructs history using the same summarization-cutoff logic as isolated subagents (now shared via a common helper), drops the parent's still-unresolved trailing tool-call message so the forked prefix matches what was actually cached, and mirrors in a fresh memory middleware instance when memory= is configured. Experimental, flagged as such in the docstrings.

Note: #4668 proposed a per-call fork: bool flag on the task tool; this implements the capability as a spec-level ForkedSubAgent type instead. A per-call flag can be layered on as a follow-up if the tool-level ergonomics are wanted.

@github-actions github-actions Bot added deepagents Related to the `deepagents` SDK / agent harness feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: M 200-499 LOC labels Aug 21, 2026

@open-swe open-swe 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.

Open SWE Review found 2 potential issues.

Open in WebView Open SWE trace

Comment thread libs/deepagents/deepagents/graph.py
Comment thread libs/deepagents/deepagents/middleware/subagents.py Outdated
@github-actions github-actions Bot added size: L 500-999 LOC and removed size: M 200-499 LOC labels Aug 21, 2026

@open-swe open-swe 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.

Open SWE Review found 2 potential issues.

Open in WebView Open SWE trace

Comment thread libs/deepagents/deepagents/middleware/subagents.py Outdated
Comment thread libs/deepagents/deepagents/middleware/subagents.py Outdated
Comment thread libs/deepagents/deepagents/graph.py Outdated
Comment thread libs/deepagents/deepagents/middleware/subagents.py Outdated
Comment thread libs/deepagents/deepagents/graph.py
@mdrxy

Copy link
Copy Markdown
Member

Closes #4668 — with a shape difference worth noting:

Same underlying capability: forked subagent gets the parent's effective history (same summarization-cutoff logic as isolated subagents) plus the task description. Isolated-by-default is unchanged.

Ninaad R. Rao (@NinaadRao) — if the per-call fork= flag itself matters beyond the capability, that can be layered on as a follow-up.

Unaffected related issues: #1359, #2440, #3838, #2512.

(Also editing the PR description to include the Closes #4668 line.)

@mdrxy Mason Daugherty (mdrxy) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  • Forks probably shouldn't run prompt-producing middleware. Today a fork builds its middleware stack like any other subagent. MemoryMiddleware, SkillsMiddleware, and any profile extras each append to the system prompt. But then _ForkSystemMessageMiddleware runs last and replaces the whole thing with the captured parent message. So that work happens on every call and is thrown away every time. Since a fork's prompt is definitionally the parent's captured prompt, I think we can skip prompt-producing middleware at fork construction and set the captured message as the fork's static prompt?
  • A forked spec with system_prompt should raise, not silently drop it. The Never annotation only helps typed users; at runtime _resolved_declarative_spec filters the key out, so ForkedSubAgent(system_prompt="You are a SQL expert...") runs without complaint and the prompt is silently discarded (the kind of misrouting required mode="fork" seems to be designed to prevent). A ValueError in _validate_subagent_mode (or a sibling validator) would make bad config loud.
  • Reject duplicate subagent names. The context leak Open SWE flagged is currently avoided only because subagents_by_name and subagent_graphs both happen to resolve duplicates last-write-wins. That agreement is incidental: a ValueError on duplicate names removes the whole class of bug instead of relying on two maps staying in lockstep.

Comment thread libs/deepagents/deepagents/middleware/subagents.py
Comment thread libs/deepagents/deepagents/middleware/subagents.py Outdated
Comment thread libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py Outdated

@open-swe open-swe 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.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/deepagents/deepagents/graph.py
@thushanth-bengre-langchain

Copy link
Copy Markdown
Author
  • Forks probably shouldn't run prompt-producing middleware. Today a fork builds its middleware stack like any other subagent. MemoryMiddleware, SkillsMiddleware, and any profile extras each append to the system prompt. But then _ForkSystemMessageMiddleware runs last and replaces the whole thing with the captured parent message. So that work happens on every call and is thrown away every time. Since a fork's prompt is definitionally the parent's captured prompt, I think we can skip prompt-producing middleware at fork construction and set the captured message as the fork's static prompt?
  • A forked spec with system_prompt should raise, not silently drop it. The Never annotation only helps typed users; at runtime _resolved_declarative_spec filters the key out, so ForkedSubAgent(system_prompt="You are a SQL expert...") runs without complaint and the prompt is silently discarded (the kind of misrouting required mode="fork" seems to be designed to prevent). A ValueError in _validate_subagent_mode (or a sibling validator) would make bad config loud.
  • Reject duplicate subagent names. The context leak Open SWE flagged is currently avoided only because subagents_by_name and subagent_graphs both happen to resolve duplicates last-write-wins. That agreement is incidental: a ValueError on duplicate names removes the whole class of bug instead of relying on two maps staying in lockstep.
  • Fixed — forks now skip building MemoryMiddleware/SkillsMiddleware at construction instead of computing then discarding them.
  • Fixed — a forked spec with system_prompt set now raises ValueError in _validate_subagent_mode
  • Fixed — duplicate subagent names now raise ValueError before anything gets compiled

Comment thread libs/deepagents/deepagents/middleware/subagents.py Outdated
Comment thread libs/deepagents/deepagents/middleware/subagents.py

@open-swe open-swe 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.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/deepagents/deepagents/middleware/subagents.py
Comment thread libs/deepagents/deepagents/middleware/summarization.py
@github-actions github-actions Bot added size: XL 1000+ LOC and removed size: L 500-999 LOC labels Aug 28, 2026
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Skip building Memory/Skills middleware for forks (their prompt
contribution is always overwritten anyway), reject a ForkedSubAgent
that sets system_prompt, reject duplicate subagent names, fold
_PARENT_SYSTEM_MESSAGE_KEY into _EXCLUDED_STATE_KEYS, and narrow
_is_forked_subagent with TypeIs to drop an unnecessary cast.
A fork's skills index would only feed a prompt fragment that gets
discarded when the parent's captured system message overwrites it
(same issue open-swe caught for system_prompt). Raise instead of
silently dropping it, and document the constraint alongside
system_prompt's.
…ompt

Not needed for type safety -- ty rejects system_prompt on a forked spec
either way, whether or not the field is declared. Runtime validation in
_validate_subagent_mode already enforces this independently. Removing it
also brings system_prompt in line with how skills is already handled
(runtime-only, no type-level override), since skills can't get the same
treatment -- it's declared on the shared _SubAgentBase, and TypedDict
doesn't allow overriding an inherited field's type.
@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label Aug 29, 2026
Thushanth Bengre (thushanth-bengre-langchain) added a commit to langchain-ai/deepagentsjs that referenced this pull request Aug 30, 2026
…fety fixes (#800)

## Summary

This PR backports a set of fixes that landed later in Python
`deepagents`'s equivalent feature (langchain-ai/deepagents#5714)

## What's included

- **Dynamic system-message capture and replay** — a fork now gets the
parent's actual composed system message (skills, memory, etc.), not a
stale static copy, so the prompt cache can hit.
- **Identity-preserving preamble** — tells a fork it's continuing a
prior conversation, not receiving a fresh request.
- **Recursion refusal** — a fork's task tool refuses re-delegation
instead of recursing.
- **Tool-description hint for the parent** — marks forked subagents in
the task tool listing so the parent knows they don't need context
restated.
- **Memory-middleware parity fix** — forks no longer get
memoryMiddleware mirrored in, matching Python.

## Testing

- New unit tests: skills-injected system message replay into a fork,
rejection of `skills` on a `ForkedSubAgent` at construction, a fork's
own
  attempt to delegate again being refused rather than recursing, and
`parentSystemMessageMiddleware` only being installed when a declarative
  fork is actually present.

---------

Signed-off-by: Thushanth Bengre <thushanth.bengre@langchain.dev>
Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deepagents Related to the `deepagents` SDK / agent harness dependencies Pull requests that update a dependency file feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: XL 1000+ LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add fork option to the task tool so subagents can inherit parent conversation history

4 participants