Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/website/content/docs/ag-ui/concepts/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ provideAgUiAgent({

The config maps directly to the AG-UI `HttpAgent` options currently exposed by this package: `url`, `agentId`, `threadId`, and `headers`.

`threadId` here is a plain string consumed once at construction — the provider does not accept an Angular Signal, and the adapter does not observe changes to it at runtime. The AG-UI protocol carries events, not snapshots, and defines no server-side endpoint for "fetch the messages of thread X". To move a user between threads with their prior conversation restored, you have two options:

- **Recreate the provider.** Inject `provideAgUiAgent({ ..., threadId: newId })` from a fresh injector when the active thread changes. Any prior message history must come from your own host service — pre-populate `setMessages()` on the source before the adapter boots, or render a "loading…" surface while you fetch it.
- **Use the LangGraph adapter instead.** `@ngaf/langgraph` accepts `threadId: Signal<string | null>` and hydrates messages from the latest checkpoint on every change. See its [Persistence guide](/docs/agent/guides/persistence). Use AG-UI when your runtime publishes events without checkpoint storage; use LangGraph when the server owns durable thread state.

Use `provideFakeAgUiAgent()` when you need the UI to run without a backend:

```ts
Expand Down Expand Up @@ -150,9 +155,9 @@ These features are intentionally out of scope for the AG-UI adapter today:

- Interrupt workflows.
- Subagents.
- History and time-travel.
- **History and time-travel.** AG-UI is an event-stream protocol — it doesn't define a server-side "fetch state of thread X" endpoint, so the adapter can't hydrate prior messages on a `threadId` change the way a checkpoint-aware runtime can. The [Provider choices](#provider-choices) section above describes the two patterns AG-UI consumers use to work around this.

If those are central to your product, use the LangGraph adapter for that surface or build a custom adapter against the `@ngaf/chat` `Agent` contract.
If those are central to your product, use the LangGraph adapter for that surface or build a custom adapter against the `@ngaf/chat` `Agent` contract. The [Writing an Adapter guide](/docs/chat/guides/writing-an-adapter#hydrating-from-a-server-stored-thread) walks through the thread-loading design choice in detail.

## Next steps

Expand Down
4 changes: 4 additions & 0 deletions apps/website/content/docs/agent/guides/persistence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Thread persistence keeps conversations alive across page refreshes, browser rest
LangGraph checkpoints agent state at every super-step. Each checkpoint is keyed by a thread ID. agent() connects to these checkpoints automatically, so your users resume exactly where they left off — even if your server restarted between sessions.
</Callout>

<Callout type="info" title="Adapter-defined behavior">
"Restore a prior thread's messages when the user switches to it" is a behavior the `@ngaf/langgraph` adapter implements because the LangGraph protocol exposes per-thread checkpoint history. The runtime-neutral `Agent` contract in `@ngaf/chat` doesn't require this — adapters built on event-stream protocols (like `@ngaf/ag-ui`) typically can't offer it. If you're writing your own adapter, the [Writing an Adapter guide](/docs/chat/guides/writing-an-adapter#hydrating-from-a-server-stored-thread) covers the design choice.
</Callout>

## Python: Checkpointer Setup

Every LangGraph agent needs a checkpointer to persist state between invocations. The checkpointer you choose depends on your environment.
Expand Down
15 changes: 15 additions & 0 deletions apps/website/content/docs/chat/guides/writing-an-adapter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ import type { AgentWithHistory } from '@ngaf/chat';

Use `runAgentWithHistoryConformance` from `@ngaf/chat/testing` in your spec instead of `runAgentConformance` to cover the additional field.

### Hydrating from a server-stored thread

`AgentWithHistory` is a structural choice — exposing the checkpoint list. There is a parallel behavioral choice an adapter has to make:

**When `threadId` changes to a non-null id the consumer didn't just create, should `messages` and `values` re-populate from the server's record of that thread?**

The chat UI assumes "yes" implicitly — clicking a thread in a sidebar list, or rehydrating a saved id on reload, both rely on the adapter pulling the prior conversation back into view. If the adapter only clears local state on a thread switch, the UI shows an empty welcome surface and the user loses their conversation.

How adapters in this repo answer the question:

- **`@ngaf/langgraph`** answers *yes*. On every `threadId` change the bridge calls `transport.getHistory(id)` (LangGraph SDK `client.threads.getHistory`), takes the latest checkpoint, and seeds `messages$` and `values$` before any new user turn. The LangGraph protocol exposes a checkpoint endpoint per thread, so this is straightforward. See the [LangGraph persistence guide](/docs/agent/guides/persistence) for the consumer-side shape.
- **`@ngaf/ag-ui`** answers *no, by design*. The AG-UI protocol is event-stream-only — it doesn't define a server-side "get the messages on this thread" endpoint. The adapter's `threadId` is therefore a plain string accepted once at construction, and switching threads means tearing down the provider and creating a new one (or pre-loading messages from the host service before the agent boots). See [AG-UI architecture › Provider choices](/docs/ag-ui/concepts/architecture#provider-choices).

If your protocol does store thread state, follow the LangGraph adapter's pattern: react to `threadId` changes, fetch the latest checkpoint, and surface a `isThreadLoading` signal so the UI can show a skeleton while the fetch runs. If it doesn't, follow AG-UI and be explicit in your docs that consumers own the load step.

## Publishing Your Adapter

If you want to distribute your adapter as an npm package, keep the following in mind.
Expand Down