Skip to content

Commit 8c48de1

Browse files
bloveclaude
andauthored
docs(website): add an agent runtimes docs section (#913)
* docs(website): add an agent runtimes docs section One `runtimes` docs library with a section per measured AG-UI runtime — AWS Strands, Microsoft Agent Framework, and Mastra — each carrying an overview, a local quickstart, and a page recording the wire conventions measured on 2026-08-31. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(website): present the Mastra hosted demo as live The Mastra Railway service and AG_UI_MASTRA_URL are now provisioned, and POST examples.threadplane.ai/runtimes/mastra/agent returns a real RUN_STARTED -> TEXT_MESSAGE_CHUNK -> RUN_FINISHED stream. Drop the hosted-backend caveat and the local-quickstart-as-workaround framing, and match the See-it-live callout the Strands and MAF overviews use. Also correct the now-false 'not yet running in the hosted demo deployment' note on the runtime matrix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 472d97a commit 8c48de1

14 files changed

Lines changed: 776 additions & 4 deletions

File tree

apps/website/content/docs/choosing-an-adapter/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ AWS Strands and Microsoft Agent Framework both read the protocol-standard top-le
123123
The LangGraph bridge reads `forwardedProps.command.resume`.
124124
You pass one neutral `submit({ resume })`, and the adapter derives the wire shape from how the interrupt arrived.
125125

126-
**The Mastra row was measured, but not in the hosted demo.**
126+
**The Mastra row is hosted on its own lane.**
127127
Its cells come from a real Mastra server driven with live model calls, and its transcripts are committed and replayed like the others.
128-
Unlike the Strands and Microsoft Agent Framework rows, it is not yet running in the hosted demo deployment.
128+
Unlike the Strands and Microsoft Agent Framework rows, it is not served by the shared FastAPI deployment: upstream ships no plain AG-UI HTTP endpoint, so its backend is a separate Node service.
129129

130130
**Subagents are red for every third-party runtime, and none of those reds are a bug.**
131131
The three runtimes do not fail to implement one thing; they model delegation three different ways.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: How It Connects
3+
description: The AG-UI wire conventions measured for AWS Strands: outcome interrupts, top-level resume entries, and snapshot-only state.
4+
---
5+
6+
# How AWS Strands Connects
7+
8+
This page records the AG-UI wire behavior measured for the Strands bridge on 2026-08-31. It describes what the runtime emitted, not what the protocol permits in general. The captured Server-Sent Events are committed at [`libs/ag-ui/fixtures/runtime-transcripts/`](https://github.com/cacheplane/angular-agent-framework/tree/main/libs/ag-ui/fixtures/runtime-transcripts) and replayed by the adapter test suite on every run.
9+
10+
## Transport
11+
12+
The example serves a single AG-UI endpoint from FastAPI.
13+
14+
| Route | Method | Notes |
15+
|---|---|---|
16+
| `/agent` | `POST` | `RunAgentInput` JSON in, Server-Sent Events out. |
17+
| `/ok` | `GET` | Unauthenticated health check. |
18+
19+
On the Angular side that is an ordinary `provideAgent({ url: '/agent' })`. Nothing about the adapter configuration is Strands-specific.
20+
21+
## Interrupts use the outcome convention
22+
23+
Strands signals an interrupt through the protocol-standard run outcome and never through a `CUSTOM` event:
24+
25+
```json
26+
{
27+
"type": "RUN_FINISHED",
28+
"outcome": {
29+
"type": "interrupt",
30+
"interrupts": [{ "interruptId": "...", "value": { } }]
31+
}
32+
}
33+
```
34+
35+
This is the opposite of the LangGraph bridge, which signals interrupts only through a `CUSTOM` event named `on_interrupt` and never sets an outcome. The adapter detects either convention; within a single run, the first signal it sees wins.
36+
37+
The reducer originally keyed interrupts on `on_interrupt` alone, which meant a Strands run finalized as a success with a dangling approval call and an undefined `interrupt()`. That was an adapter defect, and it is fixed.
38+
39+
## Resume uses top-level entries
40+
41+
Strands reads resume data from the protocol-standard top-level `resume` array, one entry per interrupt, keyed by `interruptId`:
42+
43+
```json
44+
{
45+
"resume": [
46+
{ "interruptId": "...", "status": "accepted", "payload": { } }
47+
]
48+
}
49+
```
50+
51+
Application code does not assemble that. You call the neutral `submit({ resume })`, and the adapter derives the wire shape from how the interrupt arrived. The same call against a Mastra backend produces `forwardedProps.command.interruptEvent` instead, and against the LangGraph bridge produces `forwardedProps.command.resume`.
52+
53+
## State is snapshot-only
54+
55+
The bridge emits `STATE_SNAPSHOT` and never `STATE_DELTA`. State reaches the wire only where a tool opts in with a `ToolBehavior` hook:
56+
57+
```python
58+
StrandsAgentConfig(
59+
tool_behaviors={
60+
"check_availability": ToolBehavior(state_from_result=availability_state),
61+
"book_meeting": ToolBehavior(state_from_args=booking_state),
62+
},
63+
)
64+
```
65+
66+
`state_from_result` fires after the tool returns. `state_from_args` fires as the tool call's arguments finish streaming, which is what puts a pending booking into state *before* the interrupt pauses the run.
67+
68+
Because the adapter applies a snapshot as a full replacement, both hooks return the complete state object. A hook that returns a partial object silently drops whatever the other hook had written.
69+
70+
## Subagents emit nothing the adapter can read
71+
72+
Delegation is routed through a `CUSTOM` `MultiAgentHandoff` event plus `STEP_*` events, with no `ACTIVITY` events at any point. The Threadplane subagent projection keys on an `activityType` of `subagent`, so there is nothing to project.
73+
74+
The AG-UI protocol has carried dedicated `SUBAGENT_STARTED`, `SUBAGENT_FINISHED`, and `SUBAGENT_ERROR` events since `@ag-ui/core` 0.0.59. No runtime measured here emits them yet.
75+
76+
## Next steps
77+
78+
- [Overview](/docs/runtimes/aws-strands/overview) — what the integration supports.
79+
- [Microsoft Agent Framework — How It Connects](/docs/runtimes/microsoft-agent-framework/how-it-connects) — the same outcome convention, a different resume requirement.
80+
- [Choosing an adapter](/docs/choosing-an-adapter) — the full matrix and its cause analysis.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: Overview
3+
description: What the AWS Strands integration demonstrates through @threadplane/ag-ui, and where its shared-state support stops short.
4+
---
5+
6+
# AWS Strands Overview
7+
8+
[AWS Strands](https://strandsagents.com) is an open-source Python agent SDK from AWS. Its AG-UI bridge, `ag-ui-strands`, turns a Strands `Agent` into an AG-UI event stream, which is all `@threadplane/ag-ui` needs in order to bind it to `<chat>`.
9+
10+
The Threadplane example is a meeting scheduler. It runs a Strands agent behind FastAPI, streams to an ordinary Angular app, and pauses for human approval before it books anything.
11+
12+
<Callout type="tip" title="See it live">
13+
The hosted example runs at [examples.threadplane.ai/runtimes/aws-strands](https://examples.threadplane.ai/runtimes/aws-strands/). The source is [`cockpit/runtimes/aws-strands`](https://github.com/cacheplane/angular-agent-framework/tree/main/cockpit/runtimes/aws-strands).
14+
</Callout>
15+
16+
## What the integration demonstrates
17+
18+
| Surface | Status | How |
19+
|---|---|---|
20+
| Messages | Supported | Streamed assistant text from a Strands `Agent`. |
21+
| Tool calls | Supported | `check_availability` executes server-side with no pause. |
22+
| Shared state | Partial | Snapshot-only, and opt-in per tool. See below. |
23+
| Interrupts | Supported | `book_meeting` parks in `tool_context.interrupt(...)`. |
24+
| Subagents | Not available | The bridge emits no `ACTIVITY` events at all. |
25+
26+
## Shared state is partial, and the reason matters
27+
28+
The Strands bridge never emits `STATE_DELTA`. Outbound state exists only where a tool opts in through a per-tool `ToolBehavior` hook: the example wires `state_from_result` on `check_availability` and `state_from_args` on `book_meeting`.
29+
30+
Because the adapter applies a `STATE_SNAPSHOT` as a full replacement, every hook has to return the **complete** state object. A hook that returns only the keys it changed clobbers its siblings.
31+
32+
Shared state does work on Strands. It is snapshot-only, it is opt-in per tool, and it puts the burden of assembling the whole object on each hook. That is a real constraint to design around, not a rounding error, which is why the measured matrix records it as partial rather than green.
33+
34+
## Subagents are not available
35+
36+
The Strands bridge routes delegation through a `CUSTOM` `MultiAgentHandoff` event plus `STEP_*` events, and emits zero `ACTIVITY` events. The Threadplane subagent projection keys on an `activityType` of `subagent`, so there is nothing for it to consume.
37+
38+
This is an upstream gap rather than an adapter defect, and it is shared by every third-party runtime measured so far. Multi-agent routes also crash the stale PyPI wheel, which is one reason the example pins the bridge to a git reference instead.
39+
40+
## Model access
41+
42+
Strands' native OpenAI provider is used on a plain `OPENAI_API_KEY`. No AWS credentials are involved anywhere in this example, despite the runtime's name.
43+
44+
## Next steps
45+
46+
- [Quickstart](/docs/runtimes/aws-strands/quickstart) — run the example locally.
47+
- [How It Connects](/docs/runtimes/aws-strands/how-it-connects) — the measured wire conventions.
48+
- [Choosing an adapter](/docs/choosing-an-adapter) — the full runtime matrix and its cause analysis.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: Quickstart
3+
description: Run the AWS Strands runtime example locally, backend and Angular app, on ports 5331 and 4331.
4+
---
5+
6+
# AWS Strands Quickstart
7+
8+
This runs the example from a clone of the [monorepo](https://github.com/cacheplane/angular-agent-framework). The backend is a uvicorn process on port 5331; the Angular dev server is on port 4331 and proxies `/agent` to it.
9+
10+
<Callout type="info" title="Prerequisites">
11+
Node.js 20 or newer, Python 3.11 or newer, [`uv`](https://docs.astral.sh/uv/), and an OpenAI API key. No AWS account and no AWS credentials are required.
12+
</Callout>
13+
14+
<Steps>
15+
<Step title="Install workspace dependencies">
16+
17+
```bash
18+
git clone https://github.com/cacheplane/angular-agent-framework.git
19+
cd angular-agent-framework
20+
npm ci
21+
```
22+
23+
</Step>
24+
<Step title="Configure the backend environment">
25+
26+
Copy the example file and fill in a real key.
27+
28+
```bash
29+
cp cockpit/runtimes/aws-strands/python/.env.example \
30+
cockpit/runtimes/aws-strands/python/.env
31+
```
32+
33+
| Variable | Required | Purpose |
34+
|---|---|---|
35+
| `OPENAI_API_KEY` | Yes | Strands' native OpenAI provider. |
36+
| `OPENAI_CHAT_MODEL` | No | Model name. Defaults to `gpt-4o-mini`. |
37+
| `OPENAI_BASE_URL` | No | Redirects the OpenAI client. The end-to-end fixture harness sets this to replay recorded calls. |
38+
| `OTEL_SDK_DISABLED` | No | Already set to `true` in `src/agent.py`. Strands wires OpenTelemetry unconditionally and logs exporter noise without a collector. |
39+
| `OTEL_PYTHON_DISABLED_INSTRUMENTATIONS` | No | Same reason, set to `all` by default. |
40+
41+
</Step>
42+
<Step title="Start both halves">
43+
44+
One command starts the Python backend and the Angular dev server together.
45+
46+
```bash
47+
npx tsx apps/cockpit/scripts/serve-example.ts --capability=rt-strands
48+
```
49+
50+
The script runs `uv sync` in `cockpit/runtimes/aws-strands/python` on the way, so the first start takes longer than later ones.
51+
52+
</Step>
53+
<Step title="Open the app">
54+
55+
Visit `http://localhost:4331`. The backend answers on `http://localhost:5331/agent`, with an unauthenticated health check at `http://localhost:5331/ok`.
56+
57+
</Step>
58+
<Step title="Exercise every surface">
59+
60+
Three prompts cover the measured surfaces in order.
61+
62+
1. *"What is my availability on Thursday?"* — streams a message and calls `check_availability`, which mirrors its result into shared state.
63+
2. *"Book the 2pm slot to talk about the roadmap."* — calls `book_meeting`, which parks in an interrupt and renders an approval card.
64+
3. Approve or decline the card — the run resumes and the agent confirms the outcome.
65+
66+
</Step>
67+
</Steps>
68+
69+
## About the bridge pin
70+
71+
`pyproject.toml` pins `ag-ui-strands` to a git reference of the [`ag-ui-protocol/ag-ui`](https://github.com/ag-ui-protocol/ag-ui) repository, subdirectory `integrations/aws-strands/python`, through `[tool.uv.sources]`. The exported requirements file carries the matching `git+https://...#subdirectory=...` line.
72+
73+
The published PyPI release, `ag-ui-strands` 0.3.0, is stale: it predates the interrupt and resume contract this example depends on, and it crashes on multi-agent routes. Installing from PyPI instead of the pin will not reproduce the behavior documented here.
74+
75+
## Next steps
76+
77+
- [How It Connects](/docs/runtimes/aws-strands/how-it-connects) — the wire conventions this example relies on.
78+
- [Overview](/docs/runtimes/aws-strands/overview) — what the integration does and does not support.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
title: Introduction
3+
description: Measured AG-UI runtime support for AWS Strands, Microsoft Agent Framework, and Mastra behind one Angular adapter.
4+
---
5+
6+
# Introduction
7+
8+
`@threadplane/ag-ui` is protocol-first: it consumes the [AG-UI](https://github.com/ag-ui-protocol/ag-ui) event vocabulary rather than any one runtime's SDK. That makes "any AG-UI backend plugs in" a claim that can be tested instead of asserted.
9+
10+
This section documents what happened when it was tested. On 2026-08-31 the adapter was run against three runtimes that have nothing to do with LangGraph, in two languages, with no adapter changes for messages, tool calls, or state. Each runtime has a standalone Angular example, a real backend, and a committed transcript of its wire traffic.
11+
12+
<Callout type="info" title="What this section is not">
13+
These pages document runtimes as *backends measured against the adapter*. They are not a substitute for each vendor's own documentation, and Threadplane does not maintain any of the upstream AG-UI bridges described here.
14+
</Callout>
15+
16+
## The runtimes
17+
18+
<CardGroup>
19+
<Card title="AWS Strands" href="/docs/runtimes/aws-strands/overview">
20+
Python. Messages, tool calls, and interrupts work. Shared state is snapshot-only and opt-in per tool.
21+
</Card>
22+
<Card title="Microsoft Agent Framework" href="/docs/runtimes/microsoft-agent-framework/overview">
23+
Python. Messages, tool calls, state, and interrupts all work. Azure OpenAI by default.
24+
</Card>
25+
<Card title="Mastra" href="/docs/runtimes/mastra/overview">
26+
TypeScript. Messages, tool calls, state, and interrupts all work, against a hand-written Node hosting service.
27+
</Card>
28+
</CardGroup>
29+
30+
## Measured support
31+
32+
| Runtime | Messages | Tool calls | State | Interrupts | Subagents |
33+
|---|---|---|---|---|---|
34+
| **LangGraph** (via the AG-UI bridge) | Yes | Yes | Yes | Yes | Yes |
35+
| **AWS Strands** (Python) | Yes | Yes | Partial | Yes | No |
36+
| **Microsoft Agent Framework** (Python) | Yes | Yes | Yes | Yes | No |
37+
| **Mastra** (TypeScript) | Yes | Yes | Yes | Yes | No |
38+
39+
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).
40+
41+
## What is the same everywhere
42+
43+
The Angular side does not change between these three runtimes. Each example uses the same provider call and the same component body:
44+
45+
```ts
46+
// app.config.ts
47+
import { ApplicationConfig } from '@angular/core';
48+
import { provideAgent } from '@threadplane/ag-ui';
49+
import { provideChat } from '@threadplane/chat';
50+
51+
export const appConfig: ApplicationConfig = {
52+
providers: [
53+
provideAgent({ url: '/agent' }),
54+
provideChat({}),
55+
],
56+
};
57+
```
58+
59+
```ts
60+
import { Component } from '@angular/core';
61+
import { ChatComponent } from '@threadplane/chat';
62+
import { injectAgent } from '@threadplane/ag-ui';
63+
64+
@Component({
65+
selector: 'app-root',
66+
imports: [ChatComponent],
67+
template: `<chat [agent]="agent" />`,
68+
})
69+
export class App {
70+
protected readonly agent = injectAgent();
71+
}
72+
```
73+
74+
What changes is the backend, its hosting lane, and the wire conventions it happens to use. The **How It Connects** page for each runtime records those conventions as they were measured.
75+
76+
## What is different everywhere
77+
78+
Three differences turned up repeatedly, and each runtime page returns to them.
79+
80+
**Interrupts arrive by two different conventions.** AWS Strands and Microsoft Agent Framework signal an interrupt only through the protocol-standard `RUN_FINISHED` outcome. The LangGraph bridge signals it only through a `CUSTOM` event named `on_interrupt`. Mastra emits both. The adapter accepts either, and within a single run the first signal wins.
81+
82+
**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.
83+
84+
**Subagents are unavailable on every third-party runtime.** The three runtimes model delegation in three different ways, and none of them emits the dedicated `SUBAGENT_*` events that `@ag-ui/core` has carried since 0.0.59. Treat server-declared subagents as a capability of LangGraph and of backends you control.
85+
86+
## Further reading
87+
88+
- [Choosing an adapter](/docs/choosing-an-adapter) — the full measured matrix, cause-by-cause.
89+
- [AG-UI adapter introduction](/docs/ag-ui/getting-started/introduction) — the adapter these runtimes bind through.
90+
- [What changes when the runtime changes](/blog/what-changes-when-the-runtime-changes) — the argument for measuring portability.
91+
- [We measured the runtime swap](/blog/we-measured-the-runtime-swap) — the results write-up.
92+
- [`libs/ag-ui/fixtures/runtime-transcripts/`](https://github.com/cacheplane/angular-agent-framework/tree/main/libs/ag-ui/fixtures/runtime-transcripts) — the captured Server-Sent Events, verbatim from the wire.

0 commit comments

Comments
 (0)