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
Copy file name to clipboardExpand all lines: apps/website/content/AGENTS.md.template
+12Lines changed: 12 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -70,5 +70,17 @@ export class ChatComponent {
70
70
- Scoped config: re-provide `provideAgent({ apiUrl, assistantId })` in a component `providers` array for a subtree
71
71
- Testing: use `MockAgentTransport` — never mock `injectAgent()` itself
72
72
73
+
74
+
## Interrupts and recovery
75
+
76
+
- Both adapters expose `interrupt()` and `submit({ resume })`; the backend defines the decision payload.
77
+
- AG-UI `auto` prefers native interrupt batches regardless of event order. Answer every pending native ID once; `status: 'cancelled'` entries omit `payload`.
78
+
- For Mastra, set `interruptTransport: 'mastra-command'`. The adapter sends the decision in `forwardedProps.command.resume` and observed correlation IDs in `command.interruptEvent`. The campsite example rejects with `{ approved: false }`.
79
+
- AG-UI restoration is opt-in through `persistence`: stable `threadId`, scoped namespace, and application-owned atomic compare-and-swap storage. Await `agent.ready` before rendering restored decisions.
80
+
- Capture `interruptSession().generation` when rendering and pass it as the `interruptGeneration` submit option to reject stale controls.
81
+
- Retry the retained decision only after proven non-dispatch or authoritative reconciliation. Configure `persistence.reconcile` and call `agent.reconcileInterrupt()` for uncertain outcomes; a network error alone does not prove non-execution.
82
+
- These recovery APIs are AG-UI extensions. LangGraph uses its own thread/checkpoint APIs. Client storage cannot recreate a lost backend checkpoint or guarantee exactly-once side effects.
83
+
- An approval card closing or local streaming stopping does not prove backend completion or cancellation.
84
+
73
85
## Version check
74
86
If this file is stale, fetch the latest: https://threadplane.ai/llms-full.txt
Copy file name to clipboardExpand all lines: apps/website/content/CLAUDE.md.template
+12Lines changed: 12 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -70,5 +70,17 @@ export class ChatComponent {
70
70
- Scoped config: re-provide `provideAgent({ apiUrl, assistantId })` in a component `providers` array for a subtree
71
71
- Testing: use `MockAgentTransport` — never mock `injectAgent()` itself
72
72
73
+
74
+
## Interrupts and recovery
75
+
76
+
- Both adapters expose `interrupt()` and `submit({ resume })`; the backend defines the decision payload.
77
+
- AG-UI `auto` prefers native interrupt batches regardless of event order. Answer every pending native ID once; `status: 'cancelled'` entries omit `payload`.
78
+
- For Mastra, set `interruptTransport: 'mastra-command'`. The adapter sends the decision in `forwardedProps.command.resume` and observed correlation IDs in `command.interruptEvent`. The campsite example rejects with `{ approved: false }`.
79
+
- AG-UI restoration is opt-in through `persistence`: stable `threadId`, scoped namespace, and application-owned atomic compare-and-swap storage. Await `agent.ready` before rendering restored decisions.
80
+
- Capture `interruptSession().generation` when rendering and pass it as the `interruptGeneration` submit option to reject stale controls.
81
+
- Retry the retained decision only after proven non-dispatch or authoritative reconciliation. Configure `persistence.reconcile` and call `agent.reconcileInterrupt()` for uncertain outcomes; a network error alone does not prove non-execution.
82
+
- These recovery APIs are AG-UI extensions. LangGraph uses its own thread/checkpoint APIs. Client storage cannot recreate a lost backend checkpoint or guarantee exactly-once side effects.
83
+
- An approval card closing or local streaming stopping does not prove backend completion or cancellation.
84
+
73
85
## Version check
74
86
If this file is stale, fetch the latest: https://threadplane.ai/llms-full.txt
Copy file name to clipboardExpand all lines: apps/website/content/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -203,7 +203,7 @@ That distinction lives in the composition.
203
203
9. The graph continues to `issue_refund` and finishes.
204
204
205
205
It is one thread and one persisted state.
206
-
If the operator closes the tab and returns later, the interrupt is still pending.
206
+
If the operator returns to the same thread and its server checkpoint is retained, the interrupt can still be pending. Preserve the thread ID and use durable backend storage; closing the tab does not itself configure restoration.
207
207
208
208
<figure>
209
209
<imgsrc="/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular/3.png"alt="The chat after approval, showing the agent's draft summary and a confirmation: 'Refund of $47.50 issued to cus_a8x2k. Refund ID: re_demo__a8x2k.'"width="1280"height="800" />
Copy file name to clipboardExpand all lines: apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,8 @@ featured: true
8
8
---
9
9
10
10
This is how to pause an AG-UI agent in Angular for human approval before it runs a high-stakes tool, using a `CUSTOM``on_interrupt` event and the `<chat-approval-card>` composition from `@threadplane/chat`.
11
+
12
+
**Updated September 9, 2026:** This tutorial uses the LangGraph compatibility bridge. The adapter also supports native interrupt batches, which take precedence in `auto` mode; Mastra requires its explicit `mastra-command` profile. Resume now claims a pending batch, and uncertain delivery requires authoritative reconciliation before retry. Client restoration is opt-in and cannot replace the server checkpointer.
11
13
The example is the same refund agent from [Human-in-the-Loop LangGraph Agents in Angular](/blog/2026-05-28-human-in-the-loop-langgraph-agents-in-angular) — wired through the AG-UI adapter instead.
12
14
The Angular component is byte-identical except the import.
13
15
@@ -406,7 +408,7 @@ When the operator clicks Approve:
The adapter clears `agent.interrupt()` immediately for snappy UX, then forwards the resume:
411
+
The adapter claims the pending interrupt before dispatching the resume. The card closing is a local UI transition, not proof that the refund completed. For this compatibility bridge, the request carries:
@@ -430,7 +432,7 @@ Different wire, same Angular surface.
430
432
The runtime-neutral `Agent` contract is not a marketing line.
431
433
It is the reason this post existed without rewriting the component.
432
434
`<chat-approval-card>`, `agent.interrupt()`, and `submit({ resume })` are the stable surface.
433
-
`on_interrupt` and `forwardedProps.command.resume` are the AG-UI-specific wire details the adapter hides.
435
+
`on_interrupt` and `forwardedProps.command.resume` are this LangGraph bridge's compatibility wire details. Other AG-UI runtimes can require native correlated entries or an explicit command profile.
434
436
435
437
My recommendation is simple: pick the adapter that matches your backend — LangGraph SDK direct → `@threadplane/langgraph`; anything AG-UI-fronted, including LangGraph-via-`ag-ui-langgraph` → `@threadplane/ag-ui`.
Copy file name to clipboardExpand all lines: apps/website/content/blog/2026-08-31-we-measured-the-runtime-swap.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -86,7 +86,7 @@ It failed silently, which only means it took longer to notice.
86
86
87
87
The fix was small once the cause was clear, and it landed in [#888](https://github.com/cacheplane/angular-agent-framework/pull/888).
88
88
Both conventions are now recognized.
89
-
Within one run the first signal to arrive wins, because Mastra emits both and a doubled interrupt helps nobody.
89
+
Updated September 9, 2026: the adapter retains both forms in one session. Native batches take precedence in `auto` mode regardless of arrival order; the Mastra integration explicitly selects `mastra-command` so the resume includes its decision and suspended-tool correlation.
90
90
91
91
I want to name the mistake precisely, because "we had a bug" is not the lesson.
92
92
We built a protocol adapter and then tested it exclusively against one bridge implementation of that protocol.
Copy file name to clipboardExpand all lines: apps/website/content/blog/2026-08-31-what-changes-when-the-runtime-changes.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -299,7 +299,7 @@ I believe it holds, because the contract is built on the protocol event vocabula
299
299
But belief is not measurement, and I am not going to dress one up as the other.
300
300
301
301
Two smaller caveats.
302
-
The interrupt path accepts two conventions — the protocol-standard `RUN_FINISHED` interrupt outcome, and the `CUSTOM``on_interrupt` event the LangGraph bridge emits — and within a single run the first signal wins.
302
+
The interrupt path accepts two conventions — the protocol-standard `RUN_FINISHED` interrupt outcome, and the `CUSTOM``on_interrupt` event the LangGraph bridge emits — and retains both in one session. Updated September 9, 2026: native batches take precedence in `auto` mode regardless of arrival order; Mastra uses the explicit `mastra-command` profile.
303
303
And the subagent path rests on the protocol's `SUBAGENT_*` events, which the adapter consumes directly; each measured runtime reaches them through a small in-tree emitter, as [the measurement post](/blog/we-measured-the-runtime-swap#what-stayed-partial) records.
Copy file name to clipboardExpand all lines: apps/website/content/docs/ag-ui/api/inject-agent.mdx
+9-1Lines changed: 9 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,6 +76,10 @@ The AG-UI adapter extends the neutral `Agent` contract with AG-UI-specific proto
76
76
|-------|------|-------------|
77
77
|`customEvents()`|`CustomStreamEvent[]`| Custom events emitted by the backend during a run. Accumulates per run; resets when `RUN_STARTED` arrives. |
78
78
|`clientTools`|`ClientToolsCapability`| Browser client-tool catalog, pending calls, and result resolution used by `<chat [clientTools]>`. |
79
+
|`ready`|`Promise<void>`| Resolves after configured persisted state is hydrated. |
80
+
|`interruptSession()`|`InterruptSessionSnapshot`| Full batch, generation, ownership phase, and retained resume attempt. |
81
+
|`reconcileInterrupt()`|`Promise<void>`| Applies authoritative recovery using the application-provided persistence reconciler. |
82
+
|`dispose()`|`void`| Stops local work; providers call it on injector destruction. Does not cancel backend checkpoints. |
79
83
|`subagents()`|`Map<string, Subagent>`| Subagent runs from `SUBAGENT_*` events, keyed by `subagentRunId`, plus the `ACTIVITY_*` convention (`activityType: 'subagent'`, keyed by `messageId`), projected to the neutral subagent contract. |
80
84
81
85
`injectAgent()` returns the `AgUiAgent` type — the neutral `Agent` contract plus these AG-UI-specific fields — so they are reachable directly, no cast required:
@@ -100,12 +104,16 @@ Use the runtime-neutral submit shape for normal chat input:
100
104
awaitchat.submit({ message: 'Summarize this document' });
101
105
```
102
106
103
-
Resume an interrupt by passing a `resume`payload:
107
+
Resume a pending single interrupt by passing the payload expected by the backend:
104
108
105
109
```ts
106
110
awaitchat.submit({ resume: { approved: true } });
107
111
```
108
112
113
+
Native batches require every pending ID exactly once; cancelled entries omit payload. Capture `chat.interruptSession().generation` when rendering approval controls and pass `{ interruptGeneration: generation }` as the second submit argument to reject stale decisions. For the Mastra integration, configure `interruptTransport: 'mastra-command'` on the provider.
114
+
115
+
Await `chat.ready` before showing restored controls. `retry()` can resend the retained decision after a proven pre-dispatch failure; uncertain delivery requires `chat.reconcileInterrupt()` with authoritative backend evidence before another attempt. These extensions do not imply the same recovery API on the LangGraph adapter.
116
+
109
117
## Regenerate semantics
110
118
111
119
`regenerate(assistantMessageIndex)` has replace semantics: it keeps the user message before the selected assistant message, removes the selected assistant message and all later messages, syncs the rollback to the AG-UI source, then reruns with no new user message appended.
|`url`|`string`| HTTP endpoint for the AG-UI backend agent. Required. |
47
47
|`agentId`|`string`| Agent identifier, when the endpoint serves more than one agent. |
48
48
|`threadId`|`string`| Thread to connect to on start. Omit to begin a fresh conversation. |
49
+
|`interruptTransport`|`InterruptTransport`|`auto` (default), `protocol`, `legacy-command`, or `mastra-command`. Native batches win in `auto`; select the Mastra command profile for the Mastra integration. |
50
+
|`persistence`|`AgUiInterruptPersistence`| Application-owned store and optional authoritative reconciler. Requires a stable configured `threadId` and scoped namespace. |
49
51
|`headers`|`Record<string, string>`| Extra HTTP headers sent with every request. |
50
52
|`telemetry`|`AgentRuntimeTelemetrySink \| false`| Omit for automatic development-only collection, pass `false` to disable it, or pass an app-owned sink to receive the runtime lifecycle events yourself. |
51
53
54
+
With persistence enabled, await `agent.ready` before displaying restored approval controls. The store must implement atomic `compareAndSwap`; browser state alone cannot restore a lost backend checkpoint. The provider disposes the adapter when its injector is destroyed, stopping local work without cancelling backend checkpoints.
55
+
52
56
## Static versus factory config
53
57
54
58
Pass a plain `AgentConfig` object when the URL is known up front. Pass a `() => AgentConfig` factory when the config depends on runtime DI state — the factory runs inside an Angular injection context, so it may call `inject()` to read services, route params, or environment tokens.
|`interruptTransport`|`InterruptTransport`|`auto` (default), `protocol`, `legacy-command`, or `mastra-command`. Native batches take precedence in `auto`. |
28
+
|`persistence`|`AgUiInterruptPersistence`| Application-owned durable storage and optional authoritative reconciliation. Requires a stable source `threadId` and scoped namespace. |
27
29
|`telemetry`|`AgentRuntimeTelemetrySink \| false`| Optional app-owned sink. Supply one to receive runtime lifecycle events. |
28
30
|`a2uiClientCapabilities`|`{ supportedCatalogIds: string[]; inlineCatalogs?: unknown[] }`| A2UI catalog negotiation to advertise to the agent. Seeded once into the AG-UI shared state under the `a2ui_client_capabilities` key, so every `RunAgentInput.state` carries it. Use `a2uiClientCapabilities()` from `@threadplane/chat` for the renderer's standard value. |
|`ready`|`Promise<void>`| Resolves after persisted state is hydrated; actions also wait for hydration. |
39
+
|`interruptSession()`|`InterruptSessionSnapshot`| Current batch, generation, phase, and retained attempt. |
40
+
|`reconcileInterrupt()`|`Promise<void>`| Applies authoritative recovery through the configured persistence reconciler. An unknown result leaves recovery blocked. |
41
+
|`dispose()`|`void`| Stops local work and unsubscribes. Call when a directly created adapter is no longer needed; it does not cancel backend checkpoints. |
36
42
|`customEvents()`|`Signal<CustomStreamEvent[]>`| Custom events accumulated during a run. Resets at the start of each new run. |
37
43
|`clientTools`|`ClientToolsCapability`| Browser client-tool catalog, pending calls, and result resolution. The chat composition uses this when you pass `<chat [clientTools]>`. |
38
44
|`subagents()`|`Signal<Map<string, Subagent>>`| Subagent runs from `SUBAGENT_*` events, keyed by `subagentRunId`, plus the `ACTIVITY_*` convention (`activityType: 'subagent'`, keyed by `messageId`), projected to the neutral subagent contract. |
39
45
40
46
The standard `Agent` signals (`messages`, `status`, `isLoading`, `error`, `toolCalls`, `state`, `interrupt`) and actions (`submit`, `retry`, `stop`, `regenerate`) are all present.
41
47
48
+
Capture `interruptSession().generation` when rendering a decision and pass it to `submit(input, { interruptGeneration })` to reject stale controls. A proven pre-dispatch failure retains the exact decision for `retry()`; uncertain delivery requires authoritative reconciliation first. These recovery extensions belong to `AgUiAgent`, not the neutral `Agent` contract.
49
+
42
50
## CustomStreamEvent
43
51
44
52
`CustomStreamEvent` is the element type of `AgUiAgent.customEvents`:
0 commit comments