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
docs(runtimes): three overviews teach through their running examples (#1036)
* docs(runtimes): the three runtime overview pages leave the pending list
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): AWS Strands overview teaches through the running example
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): Mastra overview teaches through the running example
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): Microsoft Agent Framework overview teaches through the running example
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* chore(deployments): regenerate the microsoft-agent-framework copy; the plan notes which products mirror into deployments
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): AWS Strands page — what the panel shows after resume; leaner walk; portability thesis restored
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): Microsoft Agent Framework page — state schema and interrupt value precision; nine blocks; quickstart linked
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): Mastra page — interrupt value, the injector introduced, capture link restored
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(runtimes): the three overviews agree on the matrix wording, headings, and links
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
description: What the AWS Strands integration demonstrates through @threadplane/ag-ui, and where its shared-state support stops short.
3
+
description: How the AWS Strands example books meetings over AG-UI, and why its shared-state support is measured as partial.
4
4
---
5
5
6
6
# AWS Strands Overview
7
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>`.
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>`. The running example is a meeting scheduler that looks up open slots, pauses for human approval before it books anything, and delegates research to a specialist, and this page walks the files that make it work. The Angular component it renders is the same UI code the LangGraph-backed examples use; only the provider and the backend behind it differ.
9
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.
10
+
## What the demo does
11
11
12
-
<Callouttype="tip"title="See it live">
13
-
The hosted example runs the AWS Strands integration end to end.
12
+
The Run tab shows the prebuilt `<chat>` composition in front of a Strands agent served over AG-UI, with a side panel titled "Shared state — schedule" to the right of the transcript. Two welcome suggestions set it up: "Book the Q3 roadmap review" asks for a Tuesday meeting with the platform team, and "Book a design critique" runs the same two steps for the web team on Thursday.
14
13
15
-
<CalloutActions>
16
-
<CalloutActionhref="https://examples.threadplane.ai/runtimes/aws-strands/">Run the example</CalloutAction>
The agent calls `check_availability` for the requested weekday, and that day and its open slots appear in the side panel. Then it calls `book_meeting`, the run stops, and a modal card titled "Booking approval required" shows the topic and the chosen slot above two buttons, Cancel and Approve. Approve resumes the run and the agent confirms the booking in one sentence; Cancel resumes it with a rejection and nothing is booked. Either way the panel keeps showing the booking as it was snapshotted before the pause; the final booked or declined status lives only in the agent's confirmation sentence, because no hook emits state after the interrupt resolves.
15
+
16
+
Delegation is the third thing to try. Type a research request instead, for example "Find a slot for Ada and Grace next week — research their availability first", and the agent calls `research_availability`. That call renders as a subagent card carrying the specialist's own answer, streamed token by token inside the card rather than into the parent bubble.
17
+
18
+
## How it is built
19
+
20
+
Four files carry the example: a Python module holding the Strands agent and its tools, a FastAPI server that mounts it, an application config that registers the agent, and a component that renders the state panel and the approval card. Open the Code tab to read them in place. `subagent_emitter.py`, a fifth file in the same backend directory and not shown in the Code tab, holds the translation from the specialist's stream into the protocol's subagent events.
21
+
22
+
### An ordinary backend tool
23
+
24
+
`check_availability` is a plain Strands `@tool`. It executes server-side and never pauses, so on the wire it is a tool call with a result and nothing else. The function under it is the state hook that mirrors the lookup into shared state, registered against the tool further down.
The hook reads the tool result from `context.result_data` and tolerates the string form, because a result that has round-tripped through JSON arrives as text.
29
+
30
+
### Pausing for a human decision
31
+
32
+
`book_meeting` is a context tool: Strands hands it a `ToolContext`, and `tool_context.interrupt(...)` parks the tool mid-execution. The first argument is the name the client sees on the pending interrupt, and `reason` is the payload the approval card renders.
33
+
34
+
<ExampleCodefile="agent.py"region="book-meeting"title="agent.py — the interrupting tool" />
35
+
36
+
When the human answers, the call returns the decision and the rest of the function runs to completion in the resumed run.
37
+
38
+
<Callouttype="info"title="Two interrupt conventions, one adapter">
39
+
AWS Strands signals an interrupt only through the protocol-standard `RUN_FINISHED` outcome, `{ type: 'interrupt', interrupts: [...] }`, never through the LangGraph bridge's `CUSTOM` event named `on_interrupt`. It reads the decision back from the protocol-standard top-level `resume` array, one `{ interruptId, status, payload }` entry per interrupt. The adapter accepts either signal and sends the shape the runtime reads, so nothing in the component changes when the backend does.
19
40
</Callout>
20
41
42
+
### Delegating to a specialist
43
+
44
+
`research_availability` is an async-generator tool. It re-yields every event the specialist emits, keeps the text deltas as it passes them along, and yields the joined text last, because Strands takes the last yielded value as the tool result. Each yielded value crosses the bridge as a `tool_stream_event`, which is the seam the subagent emitter listens on.
45
+
46
+
<ExampleCodefile="agent.py"region="delegation-tool"title="agent.py — the delegation tool" />
47
+
48
+
The specialist itself is an ordinary Strands `Agent` with its own system prompt and no tools of its own.
49
+
50
+
<ExampleCodefile="agent.py"region="specialist"title="agent.py — the specialist" />
51
+
52
+
### Registering the per-tool behaviors
53
+
54
+
Everything that makes this example more than streamed text is registered in one place. `StrandsAgentConfig.tool_behaviors` maps a tool name to a `ToolBehavior`: `state_from_result` for the availability lookup, `state_from_args` for the booking, and `tool_stream_event_handler` for the delegation tool. Registering a stream handler for a tool gives that handler the whole child stream.
55
+
56
+
<ExampleCodefile="agent.py"region="agent-config"title="agent.py — the agent and its tool behaviors" />
57
+
58
+
The orchestrator binds all three tools and the `StrandsAgent` wrapper is what the server mounts.
59
+
60
+
### Serving the agent over AG-UI
61
+
62
+
The backend is a FastAPI application. `add_strands_fastapi_endpoint` from the `ag-ui-strands` package mounts the wrapped agent at a path that speaks the AG-UI event stream.
63
+
64
+
<ExampleCodefile="server.py"title="server.py" />
65
+
66
+
### Providing the agent
67
+
68
+
`provideAgent()` registers the agent once for the whole application, and `provideChat({})` registers the configuration the `<chat>` composition reads, here left at its defaults. The example passes a factory because it resolves its endpoint at runtime from the host that serves the demo. Your own application does not need the factory.
`injectAgent()` returns the adapter's agent, and `agent.state()` is the Signal the snapshots land on. The component narrows that object into two computed Signals, treating a half-filled entry as absent so the panel does not render an empty row.
83
+
84
+
<ExampleCodefile="aws-strands.component.ts"region="shared-state"title="aws-strands.component.ts — state signals" />
85
+
86
+
The panel itself is plain Angular template code reading those two Signals, with a placeholder line for the empty case.
87
+
88
+
### The approval card
89
+
90
+
`<chat-approval-card>` opens as a modal whenever the agent has a pending interrupt, and the `#body` template names what is being approved. This one renders the topic and the slot.
91
+
92
+
<ExampleCodefile="aws-strands.component.ts"region="approval-card"title="aws-strands.component.ts — the approval card" />
93
+
94
+
The class supplies that body from the pending interrupt and maps the card's two buttons onto resume payloads. The adapter stores the interrupt outcome as `{ interrupts: [...], runId }`; each Strands entry carries the tool name under `reason` and the tool's own payload under `metadata.reason`.
`submit({ resume })` is the only interrupt-specific call in the component, and it is the same call every other AG-UI example makes.
99
+
21
100
## What the integration demonstrates
22
101
23
102
| Surface | Status | How |
@@ -30,24 +109,43 @@ The hosted example runs the AWS Strands integration end to end.
30
109
31
110
## Shared state is partial, and the reason matters
32
111
33
-
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`.
112
+
The Strands bridge never emits `STATE_DELTA`. Outbound state exists only where a tool opts in through a per-tool `ToolBehavior` hook, which is why the example registers `state_from_result` on `check_availability` and `state_from_args` on `book_meeting` and gets nothing from `research_availability`.
113
+
114
+
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, so the example keeps one module-level object and composes the whole thing on every emission.
34
115
35
-
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.
116
+
<ExampleCodefile="agent.py"region="demo-state"title="agent.py — the complete state object" />
117
+
118
+
The booking hook shows what that costs in practice. It fires on the tool-call arguments, before the interrupt pauses the run, so the approval card can render a pending booking from shared state — and it still has to return both keys, not just the one it touched.
119
+
120
+
<ExampleCodefile="agent.py"region="booking-state"title="agent.py — the state_from_args hook" />
36
121
37
122
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.
38
123
39
124
## How subagents surface
40
125
41
-
Strands wraps every value an async-generator tool yields as a `tool_stream_event`, and the bridge dispatches those events to a per-tool `ToolBehavior.tool_stream_event_handler`. The example's delegation tool re-yields the specialist's `stream_async` output, and an in-tree emitter registered as that handler (`src/subagent_emitter.py`) translates it into `SUBAGENT_STARTED`, attributed `TEXT_MESSAGE_*` deltas carrying `subagentRunId`, and `SUBAGENT_FINISHED` — so the subagent card streams the specialist's tokens live.
126
+
Strands wraps every value an async-generator tool yields as a `tool_stream_event`, and the bridge dispatches those events to a per-tool `ToolBehavior.tool_stream_event_handler`. Natively the bridge forwards only the inner tool-call lifecycle, so a delegated run reaches the browser as one opaque result string with no child text at all.
42
127
43
-
The wire capture behind this cell is committed at [`cockpit/runtimes/aws-strands/python/docs/wire-capture-subagents.md`](https://github.com/cacheplane/angular-agent-framework/tree/main/cockpit/runtimes/aws-strands/python/docs/wire-capture-subagents.md). Multi-agent routes crash the stale PyPI wheel, which is one reason the example pins the bridge to a git reference instead.
128
+
The handler registered on `research_availability` closes that gap. `subagent_emitter.py` translates the specialist's stream into `SUBAGENT_STARTED`, attributed `TEXT_MESSAGE_*` deltas carrying `subagentRunId`, and `SUBAGENT_FINISHED`, deriving every identifier from the tool call identifier the bridge already put on the wire. The adapter routes those attributed events into a subagent entry instead of the parent transcript, which is why the card streams the specialist's tokens live.
44
129
45
-
## Model access
130
+
The wire capture behind that matrix cell is committed beside the backend at [`docs/wire-capture-subagents.md`](https://github.com/cacheplane/angular-agent-framework/blob/main/cockpit/runtimes/aws-strands/python/docs/wire-capture-subagents.md), before and after the emitter. Multi-agent routes crash the stale published wheel, which is one reason the example pins the bridge to a git reference instead.
46
131
47
-
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.
48
-
49
-
## Next steps
132
+
## Model access
50
133
51
-
-[Quickstart](/docs/runtimes/aws-strands/quickstart) — run the example locally.
52
-
-[How It Connects](/docs/runtimes/aws-strands/how-it-connects) — the measured wire conventions.
53
-
-[Choosing an adapter](/docs/choosing-an-adapter) — the full runtime matrix and its cause analysis.
134
+
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. `OPENAI_BASE_URL` is honored when it is set, which is how the end-to-end harness replays recorded model calls against this backend.
135
+
136
+
## What's Next
137
+
138
+
<CardGroupcols={2}>
139
+
<Cardtitle="How it connects"href="/docs/runtimes/aws-strands/how-it-connects">
140
+
The measured AG-UI wire behavior for this runtime.
141
+
</Card>
142
+
<Cardtitle="Choosing an adapter"href="/docs/choosing-an-adapter">
143
+
The same matrix with the cause analysis behind each partial cell.
0 commit comments