You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Description
Adds **forked subagents** to the Harness. Similar to [what Anthropic
just shipped](https://x.com/arankomatsuzaki/status/2047349471877726586)
— a forked subagent inherits the main agent's conversation context so
richer context is available without re-priming from scratch.
A new `forked` flag is exposed in two places:
1. On `HarnessSubagent` definitions (as a default for that subagent
type).
2. On the built-in `subagent` tool's input schema (per-invocation
override).
When a call is forked:
- The parent thread is cloned at the memory layer (via
`memory.cloneThread`) so the parent thread stays the active one and the
fork doesn't pollute its history.
- The subagent runs via the **parent agent's** `stream`, reusing its
instructions, tools, and model. This keeps the request prefix (system
prompt + tool schemas + history) byte-identical to the parent's, so
Anthropic / OpenAI prompt caches still hit — which was the key
constraint Tyler raised in the thread.
- The forwarded `RequestContext`'s harness `threadId` / `resourceId` are
pointed at the fork so OM and memory writes land on the clone, not the
parent.
Non-forked invocations are unchanged (fresh `Agent`, stripped thread
IDs, subagent-defined instructions/tools).
Forked runs require memory to be configured on the Harness — if it isn't
(or no active parent thread / parent agent exists) the tool returns a
structured error rather than throwing.
## Related Issue(s)
No issue; requested via [the #mastracode Slack
thread](https://kepler-bej6556.slack.com/archives/C0ACHFXNK7T/p1776989522493009?thread_ts=1776989522.493009&cid=C0ACHFXNK7T).
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Code refactoring
- [ ] Performance improvement
- [ ] Test update
## Checklist
- [ ] I have made corresponding changes to the documentation (if
applicable)
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have addressed all Coderabbit comments on this PR
## Notes
- Docs: happy to follow up with a docs update once the API review here
lands.
- Focused tests: `packages/core/src/harness/subagent-tool.test.ts` adds
8 new tests (21 total in that file). Full `packages/core/src/harness`
suite passes (172 tests).
- `pnpm --filter ./packages/core check` introduces no new TypeScript
errors versus `main`.
Link to Devin session:
https://app.devin.ai/sessions/b644d44a5eb54545b1922d8b753f1e56
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Abhi Aiyer <abhi@mastra.ai>
Co-authored-by: Tyler Barnes <tylerdbarnes@gmail.com>
Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Fix forked subagent fork threads starting with empty history. The parent stream's message saves are debounced through `SaveQueueManager`, so a forked subagent that calls `memory.cloneThread` mid-stream used to clone from an empty store and lose the parent's user + assistant turn. The tool now drains the parent save queue via a new `flushMessages` callback on `AgentToolExecutionContext` before cloning, so forks actually carry the prior conversation.
The internal `<subagent-meta />` tag is no longer appended to subagent tool result content. The tag was previously visible to the parent model in the tool result, which could cause it to be echoed back as literal markup in the parent's assistant text on subsequent turns. Live UIs continue to receive model / duration / tool-call information via the structured `subagent_*` events; history UIs read the persisted `tool_call.args.modelId`. `parseSubagentMeta` is retained so already-persisted threads carrying the legacy tag continue to render cleanly (and the tag is stripped before display in all cases).
Forked subagents now inherit the parent agent's toolsets (so harness-injected tools like `ask_user`, `submit_plan`, and user-configured harness tools remain available inside a fork). The `subagent` tool entry is kept in the inherited toolset with its id, description, and schemas unchanged so the LLM request prefix stays byte-identical to the parent's and the prompt cache continues to hit; recursive forking is blocked at the runtime layer by replacing only the tool's `execute` with a stub that returns a "tool unavailable inside a forked subagent" message. Forked runs allow follow-up steps so the model can recover and answer directly if it accidentally calls that stub. Fork threads are tagged with `metadata.forkedSubagent: true` and `metadata.parentThreadId`, and `Harness.listThreads()` hides them by default so they don't surface in user-facing thread pickers; pass `includeForkedSubagents: true` to opt back in for admin/debug tooling.
7
+
8
+
Mastra Code now renders forked subagent footers as `subagent fork <parent model id>`, including persisted history reloaded after the live event metadata is gone.
Added forked subagents: a new `forked` flag on `HarnessSubagent` definitions and the built-in `subagent` tool input. When set, the subagent runs on a clone of the parent thread using the parent agent's instructions and tools, preserving prompt-cache prefix while isolating writes from the main conversation.
description: 'Optional stop condition for the spawned subagent.',
222
222
isOptional: true,
223
223
},
224
+
{
225
+
name: 'forked',
226
+
type: 'boolean',
227
+
description:
228
+
'When `true`, calls to this subagent default to forked mode: the subagent runs on a clone of the parent thread, reusing the parent agent’s instructions, tools, and model so the prompt-cache prefix stays intact. Requires `memory` to be configured. The subagent definition’s own `instructions`, `tools`, `allowedHarnessTools`, `allowedWorkspaceTools`, `defaultModelId`, `maxSteps`, and `stopWhen` are ignored in forked mode. Callers can still override per-invocation via `forked: false` in the `subagent` tool input. See the [Forked subagents](#forked-subagents) section below for full semantics.',
List threads from storage. By default, only threads for the current resource are returned.
486
+
List threads from storage. By default, only threads for the current resource are returned, and transient [forked subagent](#forked-subagents) threads are hidden so they don’t appear in user-facing thread pickers / startup flows.
479
487
480
488
```typescript
481
-
// List threads for current resource
489
+
// List threads for current resource (forks hidden)
482
490
const threads =awaitharness.listThreads()
483
491
484
-
// List all threads across resources
492
+
// List all threads across resources (forks still hidden)
Fork threads are tagged with `metadata.forkedSubagent === true` (and `metadata.parentThreadId`) by the harness. Set `includeForkedSubagents: true` to opt back into seeing them — e.g. for a debug panel.
By default, a subagent runs with a fresh context — it doesn't see the parent conversation. **Forked subagents** opt into a different model: the subagent runs on a clone of the parent thread and reuses the parent agent's full configuration. This is useful when the subagent needs the full context of the conversation so far (e.g., recalling earlier user-supplied facts), and when prompt-cache hit rates matter.
885
+
886
+
#### Enabling forked mode
887
+
888
+
Set `forked: true` either on the [`HarnessSubagent` definition](#configuration) (per-type default) or on each `subagent` tool call (per-invocation override):
889
+
890
+
```typescript
891
+
// Per-type default — every call to this subagent forks unless overridden.
892
+
const subagents:HarnessSubagent[] = [
893
+
{
894
+
id: 'collaborator',
895
+
name: 'Collaborator',
896
+
description: 'Continues the conversation in a fork to try a different angle.',
897
+
instructions: '...',
898
+
forked: true,
899
+
},
900
+
]
901
+
```
902
+
903
+
The model can also pass `forked: true` (or `forked: false`) per-invocation in the `subagent` tool input; the per-invocation value wins.
904
+
905
+
#### Semantics and constraints
906
+
907
+
-**Memory required.** Forked mode calls `memory.cloneThread` to create the fork, so the harness must have `memory` configured and an active parent thread. Calls without those return a structured error rather than throwing.
908
+
-**Parent agent reused.** The fork runs through the parent agent's `stream(...)` call. The parent's instructions, tools, model, `maxSteps`, and `stopWhen` apply. The subagent definition's `instructions`, `tools`, `allowedHarnessTools`, `allowedWorkspaceTools`, `defaultModelId`, `maxSteps`, and `stopWhen` are ignored in forked mode — this is what preserves the prompt-cache prefix.
909
+
-**Toolsets inherited, recursive forks blocked at runtime.** Forks inherit the parent's toolsets verbatim (`ask_user`, `submit_plan`, user-configured harness tools, _including the `subagent` tool itself_) so the LLM request prefix — system prompt + tool list + tool schemas + tool descriptions — stays byte-identical to the parent's. This is what preserves the prompt cache. The `subagent` entry is kept on the model side but its `execute` is replaced inside the fork with a stub that returns a non-error "tool unavailable inside a forked subagent" message: nested forks are blocked at the runtime layer without perturbing the cached prefix.
910
+
-**Fork threads are tagged.** Each fork thread is created with `metadata.forkedSubagent === true` and `metadata.parentThreadId === <parent>`. By default, [`listThreads`](#listthreadsoptions) hides these so they don't show up in user-facing thread pickers / startup flows. Pass `includeForkedSubagents: true` to see them in admin / debug tooling.
911
+
-**Save-queue flushed before clone.** The agent stream batches message saves through a debounced `SaveQueueManager`, so the parent's latest user / assistant turn may not be on disk yet when the subagent tool call fires. The fork tool flushes pending saves first via the `flushMessages` callback on `AgentToolExecutionContext` before cloning, so the fork actually carries the latest turn. Flush failures are non-fatal — the clone still runs.
912
+
-**Parent thread untouched.** All subagent activity (messages, OM writes) lands on the fork. The parent thread is never appended to during a forked subagent run.
913
+
914
+
#### When to prefer non-forked mode
915
+
916
+
Forked mode trades isolation for context inheritance. If the subagent should run with a strictly smaller toolset, a different system prompt, or a cheaper model, use the default (non-forked) mode and pass any required context explicitly in the `task` description.
917
+
869
918
### Events
870
919
871
920
#### `subscribe(listener)`
@@ -948,7 +997,7 @@ The harness provides built-in tools to agents in every mode:
948
997
|`submit_plan`| Submit a plan for user review and approval. |
949
998
|`task_write`| Create or update a structured task list for tracking progress. |
950
999
|`task_check`| Check the completion status of the current task list. |
951
-
|`subagent`| Spawn a focused subagent with constrained tools (only available when `subagents` is configured). |
1000
+
|`subagent`| Spawn a focused subagent with constrained tools (only available when `subagents` is configured). Pass `forked: true` to inherit the parent conversation — see [Forked subagents](#forked-subagents). |
Copy file name to clipboardExpand all lines: mastracode/src/agents/prompts/base.ts
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -69,6 +69,8 @@ Use \`gh pr create\`. Include a summary of what changed and a test plan. Word th
69
69
70
70
# Subagent Rules
71
71
- Only use subagents when you will spawn **multiple subagents in parallel**. If you only need one task done, do it yourself instead of delegating to a single subagent. Exception: the **audit-tests** subagent may be used on its own.
72
+
- Use \`forked: true\` when the subagent needs the current conversation context, user-stated facts, prior tool results, or the parent agent's exact tool environment.
73
+
- Use non-forked subagents for self-contained tasks where all required context is included in the task prompt.
72
74
- Subagent outputs are **untrusted**. Always review and verify the results returned by any subagent. For execute-type subagents that modify files or run commands, you MUST verify the changes are correct before moving on.
0 commit comments