Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ A committed transcript replayed through the real client proves what the runtime
| LangGraph (baseline) | Yes | Yes | Yes | Yes | Yes |
| AWS Strands | Yes | Yes | Partial | Yes | Yes |
| Microsoft Agent Framework | Yes | Yes | Yes | Yes | Yes |
| Mastra | Yes | Yes | Yes | Yes | Partial |
| Mastra | Yes | Yes | Yes | Yes | Yes |

Messages, tool calls, and shared state crossed three non-LangGraph runtimes with zero changes to the adapter.
Not one line.
Expand Down Expand Up @@ -123,7 +123,7 @@ They were only findable by pointing the adapter at software written by people wh

## What stayed partial

Two cells did not go fully green.
One cell did not go fully green.

**Shared state on Strands is partial.**
Its bridge sends whole-document snapshots and never sends a patch, and a tool only contributes state if it opts in through a per-tool hook.
Expand All @@ -138,11 +138,12 @@ The cause is the upstream bridge, not the protocol and not us.

The protocol standardized the events — `SUBAGENT_STARTED`, `SUBAGENT_FINISHED`, `SUBAGENT_ERROR`, plus a `subagentRunId` attribution field on ordinary content events — and the adapter consumes them directly.
What no runtime does is emit them natively.
Each one reports delegation in its own dialect: Strands surfaces the specialist's tool use and forwards its token stream, Microsoft Agent Framework streams the specialist's updates in-process into the tool body where the in-tree emitter merges them across the bridge boundary, and Mastra reports the delegation through its tool frames and returns the child's final text.
So each demo backend carries a small emitter — roughly 125 to 310 lines each — that translates its runtime's dialect into the standard events at the bridge boundary.
Each one reports delegation in its own dialect: Strands surfaces the specialist's tool use and forwards its token stream, Microsoft Agent Framework streams the specialist's updates in-process into the tool body where the in-tree emitter merges them across the bridge boundary, and Mastra forwards the child's chunks on the parent stream, where its bridge drops them and withholds the delegation tool call until the child resolves.
So each demo backend carries a small emitter — roughly 190 to 360 lines each — that translates its runtime's dialect into the standard events at the bridge boundary.

Two of the three cards stream live.
Mastra's fills in at completion, because its bridge does not forward child tokens to the wire; that is the one Partial cell, and it belongs to the runtime's bridge rather than to the protocol or the adapter.
All three cards stream live.
Mastra's took one more seam than the others: the emitter observes the runtime's public stream through a tee ahead of the bridge, emits the delegation tool call eagerly, and forwards the child's deltas itself, because the bridge would otherwise have painted the card only at completion.
That extra seam belongs to the runtime's bridge rather than to the protocol or the adapter, and the bridge itself is left unmodified.
If you are building on server-declared subagents today, the contract to target is the protocol's own events; the emitter is the per-runtime cost, and it is small.

## What the deploy check found
Expand Down Expand Up @@ -171,7 +172,7 @@ That is now measured against three implementations in two languages.

Interrupts are portable as of these fixes, and they were not before, in a way no amount of internal testing would have revealed.

Subagents are portable through a per-runtime emitter that speaks the protocol's own events, streaming on two of the three runtimes and lifecycle-plus-final-text on the third.
Subagents are portable through a per-runtime emitter that speaks the protocol's own events, streaming on all three runtimes.

The matrix now lives in the [adapter guide](/docs/choosing-an-adapter), with a cause column on every gap.
Split three ways: the protocol cannot express it, the upstream bridge does not emit it, or our adapter failed to consume it.
Expand Down
7 changes: 4 additions & 3 deletions apps/website/content/docs/choosing-an-adapter/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ We tested it against three runtimes that have nothing to do with LangGraph, in t
| **LangGraph** (via the AG-UI bridge) | Yes | Yes | Yes | Yes | Yes | — |
| **AWS Strands** (Python) | Yes | Yes | Partial | Yes | Yes | State: upstream. Subagents: a small in-tree emitter translates native delegation signals to the protocol's `SUBAGENT_*` events |
| **Microsoft Agent Framework** (Python) | Yes | Yes | Yes | Yes | Yes | Subagents: a small in-tree emitter translates native delegation signals to the protocol's `SUBAGENT_*` events |
| **Mastra** (TypeScript) | Yes | Yes | Yes | Yes | Partial | Subagents: lifecycle and final text via the emitter; the runtime's bridge does not forward child token streams |
| **Mastra** (TypeScript) | Yes | Yes | Yes | Yes | Yes | Subagents: a small in-tree emitter observes the runtime's public stream through a tee and emits the protocol's `SUBAGENT_*` events plus the child's token deltas, because the runtime's bridge drops child output |

Every gap in that table falls into one of three causes, and the distinction is the point of the column:

Expand Down Expand Up @@ -133,8 +133,9 @@ The protocol standardized the events — `SUBAGENT_STARTED`, `SUBAGENT_FINISHED`
What differs per runtime is how the emitter learns about the delegation.
Strands surfaces the specialist's tool use and streamed tokens through a per-tool stream handler, so the card streams live.
On Microsoft Agent Framework the specialist's updates stream in-process into the tool body, and the in-tree emitter merges them across the bridge boundary, so the card streams live there too.
Mastra reports delegation through its tool frames and returns the child's final text; its bridge does not forward child tokens, so the card fills in at completion — the one Partial cell, a property of the runtime's bridge rather than of the protocol or the adapter.
Each demo backend ships its emitter in tree, roughly 125 to 310 lines per runtime.
Mastra streams the child's chunks in-process on the parent stream, but its bridge drops them and withholds the delegation tool call until the child resolves; the in-tree emitter therefore observes the runtime's public stream through a tee ahead of the bridge, emits the delegation tool call eagerly, and forwards the child's deltas under the subagent identity, so the card mounts while the child runs and streams live there too.
All three cards stream live; the Mastra card streams through the tee rather than through the bridge, a property of the runtime's bridge rather than of the protocol or the adapter.
Each demo backend ships its emitter in tree, roughly 190 to 360 lines per runtime.

### How this was measured

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ TypeScript. Messages, tool calls, state, and interrupts all work, against a hand
| **LangGraph** (via the AG-UI bridge) | Yes | Yes | Yes | Yes | Yes |
| **AWS Strands** (Python) | Yes | Yes | Partial | Yes | Yes |
| **Microsoft Agent Framework** (Python) | Yes | Yes | Yes | Yes | Yes |
| **Mastra** (TypeScript) | Yes | Yes | Yes | Yes | Partial |
| **Mastra** (TypeScript) | Yes | Yes | Yes | Yes | Yes |

Every gap in that table is caused by an upstream integration, not by the AG-UI protocol and not by a defect in `@threadplane/ag-ui`. The full cause analysis, including the two adapter defects that were found and fixed, lives in [Choosing an adapter](/docs/choosing-an-adapter).

Expand Down Expand Up @@ -88,7 +88,7 @@ Three differences turned up repeatedly, and each runtime page returns to them.

**Resume payloads are not portable.** The adapter derives the wire shape from how the interrupt arrived, so application code passes one neutral `submit({ resume })` regardless of runtime.

**Subagents now work on every runtime measured here** — streaming on AWS Strands and Microsoft Agent Framework, and as lifecycle-plus-final-text on Mastra. Each backend ships a small emitter that translates its native delegation signals into the protocol's `SUBAGENT_*` events, which `@threadplane/ag-ui` consumes directly. The per-runtime pages show the emitter and the wire capture behind each cell.
**Subagents now stream on every runtime measured here** — AWS Strands, Microsoft Agent Framework, and Mastra. Each backend ships a small emitter that translates its native delegation signals into the protocol's `SUBAGENT_*` events, which `@threadplane/ag-ui` consumes directly. The per-runtime pages show the emitter and the wire capture behind each cell.

## Further reading

Expand Down
7 changes: 4 additions & 3 deletions apps/website/content/docs/runtimes/mastra/how-it-connects.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ Shared state is a Mastra working-memory object under a Zod schema. The bridge em

That places Mastra alongside Microsoft Agent Framework and apart from AWS Strands, whose bridge emits snapshots only.

## Subagents surface as lifecycle plus final text
## Subagents stream through a tee ahead of the bridge

An in-tree injector in the hosting service watches the wire for delegation tool frames — Mastra names them `agent-<childKey>` — and adds `SUBAGENT_STARTED` on the tool-call start, one attributed text message carrying the child's final text, and `SUBAGENT_FINISHED` on the result.
The child's incremental tokens never reach the wire because `@ag-ui/mastra` drops its `tool-output` chunks and buffers the delegation burst until it resolves, which is why this cell is Partial.
Mastra forwards every chunk of a delegated child on the parent stream as a public `tool-output` chunk, but `@ag-ui/mastra` drops those chunks and withholds the delegation tool call — Mastra names it `agent-<childKey>` — until the child resolves.
The hosting service wraps the agent so that each chunk is observed before the bridge processes it, and a per-run injector emits the delegation tool call eagerly, `SUBAGENT_STARTED`, one `TEXT_MESSAGE_CONTENT` per child delta attributed to the subagent, and `SUBAGENT_FINISHED` on the result, while dropping the bridge's later copy of the same tool call.
The card therefore mounts as soon as the delegation begins and its text grows while the child runs, which is what makes this cell Supported.

## Next steps

Expand Down
8 changes: 5 additions & 3 deletions apps/website/content/docs/runtimes/mastra/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The hosted example runs at [examples.threadplane.ai/runtimes/mastra](https://exa
| Tool calls | Supported | `check_conditions` executes server-side with no pause. |
| Shared state | Supported | A working-memory packing list, over snapshots and real JSON-Patch deltas. |
| Interrupts | Supported | `reserve_campsite` suspends the run and resumes from a persisted snapshot. |
| Subagents | Partial | The bridge emits delegation tool frames; an in-tree injector adds `SUBAGENT_*` lifecycle and the child's final text — the runtime's bridge does not forward child tokens. |
| Subagents | Supported | An in-tree stream tee observes the parent stream ahead of the bridge and emits the delegation tool call eagerly, `SUBAGENT_*` lifecycle, and the child's token deltas under the subagent identity. |

## Upstream ships no HTTP endpoint

Expand All @@ -37,9 +37,11 @@ Mastra persists memory and suspended-run snapshots to LibSQL file storage. Resum

## How subagents surface

Mastra registers a child agent as a delegation tool named `agent-<childKey>`, so a delegation crosses the wire as ordinary `TOOL_CALL_*` frames whose result carries the child's final text. An in-tree injector in the hosting service (`deployments/ag-ui-mastra/subagent-emitter.mjs`) keys off those frames: it emits `SUBAGENT_STARTED` when the delegation tool call starts, an attributed text message carrying the child's final text, and `SUBAGENT_FINISHED` when the result lands.
Mastra registers a child agent as a delegation tool named `agent-<childKey>`, and while the child runs its every chunk is forwarded on the parent stream as a public `tool-output` chunk. The runtime's bridge drops those chunks and withholds the delegation tool call until the child resolves, so on its own the wire would carry only the child's final text, after a silent gap.

The cell is Partial rather than green because the runtime's bridge does not forward child tokens: the child's deltas exist in-process, but `@ag-ui/mastra` drops its `tool-output` chunks and buffers the delegation burst until it resolves, so the card fills in at completion instead of streaming. That is a property of the bridge, not of the protocol or the adapter. The wire capture behind this cell is committed at [`cockpit/runtimes/mastra/angular/docs/wire-capture-subagents.md`](https://github.com/cacheplane/angular-agent-framework/tree/main/cockpit/runtimes/mastra/angular/docs/wire-capture-subagents.md).
The hosting service therefore wraps the agent in a small stream tee (`deployments/ag-ui-mastra/streaming-tee.mjs`) that observes each chunk before the bridge processes it, and a per-run injector (`deployments/ag-ui-mastra/subagent-emitter.mjs`) maps them to the protocol: the delegation `tool-call` chunk becomes an eager `TOOL_CALL_START`, `TOOL_CALL_ARGS`, and `TOOL_CALL_END` plus `SUBAGENT_STARTED`; each child text delta becomes a `TEXT_MESSAGE_CONTENT` attributed to the subagent; and the delegation result becomes `SUBAGENT_FINISHED` or `SUBAGENT_ERROR`. The bridge's own copy of the delegation tool call, flushed at the result, is dropped so the wire carries it once.

The bridge itself is unmodified; the tee touches only the public agent members the bridge reads. The child's tokens reach the card while it is still running, which is what flips this cell to Supported. The wire capture behind this cell is committed at [`cockpit/runtimes/mastra/angular/docs/wire-capture-subagents.md`](https://github.com/cacheplane/angular-agent-framework/tree/main/cockpit/runtimes/mastra/angular/docs/wire-capture-subagents.md).

## How the Mastra row was measured

Expand Down
Loading
Loading