diff --git a/apps/website/content/docs/ag-ui/concepts/architecture.mdx b/apps/website/content/docs/ag-ui/concepts/architecture.mdx index ecf3bf14..a712e6f5 100644 --- a/apps/website/content/docs/ag-ui/concepts/architecture.mdx +++ b/apps/website/content/docs/ag-ui/concepts/architecture.mdx @@ -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` 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 @@ -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 diff --git a/apps/website/content/docs/agent/guides/persistence.mdx b/apps/website/content/docs/agent/guides/persistence.mdx index 81f75199..35771959 100644 --- a/apps/website/content/docs/agent/guides/persistence.mdx +++ b/apps/website/content/docs/agent/guides/persistence.mdx @@ -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. + +"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. + + ## Python: Checkpointer Setup Every LangGraph agent needs a checkpointer to persist state between invocations. The checkpointer you choose depends on your environment. diff --git a/apps/website/content/docs/chat/guides/writing-an-adapter.mdx b/apps/website/content/docs/chat/guides/writing-an-adapter.mdx index b3ee3513..c8e31b70 100644 --- a/apps/website/content/docs/chat/guides/writing-an-adapter.mdx +++ b/apps/website/content/docs/chat/guides/writing-an-adapter.mdx @@ -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.