diff --git a/apps/website/content/docs-v2/api/fetch-stream-transport.mdx b/apps/website/content/docs-v2/api/fetch-stream-transport.mdx index 86856dc4f..afb74b5f2 100644 --- a/apps/website/content/docs-v2/api/fetch-stream-transport.mdx +++ b/apps/website/content/docs-v2/api/fetch-stream-transport.mdx @@ -1,3 +1,24 @@ # FetchStreamTransport +`FetchStreamTransport` is the production-ready transport that opens a real server-sent event connection using the browser's `fetch` API and reads a `ReadableStream` response body. It is the default transport you register with `provideStreamResource` in production builds. + +You rarely need to interact with `FetchStreamTransport` directly — simply provide it once at the application level and every `streamResource` will use it automatically. You would reach for it explicitly only when constructing a resource outside the normal DI tree or when you need to override the transport for a single resource while keeping the global default intact. + +```ts +import { inject } from '@angular/core'; +import { streamResource, FetchStreamTransport } from '@ngxp/stream-resource'; + +// Override transport for a single resource +const events = streamResource({ + url: () => '/api/events', + transport: inject(FetchStreamTransport), +}); +``` + + + `FetchStreamTransport` implements the `StreamTransport` interface. You can + create custom transports (e.g. WebSocket-backed) by implementing the same + interface and providing them in place of this class. + + {/* Auto-rendered from api-docs.json — see page component */} diff --git a/apps/website/content/docs-v2/api/mock-stream-transport.mdx b/apps/website/content/docs-v2/api/mock-stream-transport.mdx index 3ed2407e0..fbf014cd6 100644 --- a/apps/website/content/docs-v2/api/mock-stream-transport.mdx +++ b/apps/website/content/docs-v2/api/mock-stream-transport.mdx @@ -1,3 +1,32 @@ # MockStreamTransport +`MockStreamTransport` is a test-friendly transport that replaces real network calls with an in-memory event emitter. Use it in unit and component tests to push values on demand and assert against your component's reactive state without a running server. + +```ts +import { TestBed } from '@angular/core/testing'; +import { + provideStreamResource, + MockStreamTransport, +} from '@ngxp/stream-resource'; + +beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideStreamResource({ transport: MockStreamTransport })], + }); +}); + +it('reflects streamed value', () => { + const transport = TestBed.inject(MockStreamTransport); + // Emit a value into the stream + transport.emit('/api/repos/42', { id: 42, name: 'my-repo' }); + // Assert your component's signal updated accordingly +}); +``` + + + Because `MockStreamTransport` is synchronous by default, you can emit values + and assert state changes in the same test tick — no `fakeAsync` or `tick` + required. + + {/* Auto-rendered from api-docs.json — see page component */} diff --git a/apps/website/content/docs-v2/api/provide-stream-resource.mdx b/apps/website/content/docs-v2/api/provide-stream-resource.mdx index 59d469a11..6e618bdb1 100644 --- a/apps/website/content/docs-v2/api/provide-stream-resource.mdx +++ b/apps/website/content/docs-v2/api/provide-stream-resource.mdx @@ -1,3 +1,28 @@ # provideStreamResource() +`provideStreamResource` is the provider factory that registers `stream-resource` in Angular's dependency injection system. Call it inside `bootstrapApplication` (or an `ApplicationConfig`) to configure the transport and any global defaults used by every `streamResource` in your app. + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { + provideStreamResource, + FetchStreamTransport, +} from '@ngxp/stream-resource'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, { + providers: [ + provideStreamResource({ + transport: FetchStreamTransport, + }), + ], +}); +``` + + + Swap `FetchStreamTransport` for `MockStreamTransport` (or any custom class + implementing the `StreamTransport` interface) to change the transport for all + resources at once — useful for testing or SSR. + + {/* Auto-rendered from api-docs.json — see page component */} diff --git a/apps/website/content/docs-v2/api/stream-resource.mdx b/apps/website/content/docs-v2/api/stream-resource.mdx index fa1c3417c..719efab1f 100644 --- a/apps/website/content/docs-v2/api/stream-resource.mdx +++ b/apps/website/content/docs-v2/api/stream-resource.mdx @@ -1,3 +1,26 @@ # streamResource() +`streamResource` is the core primitive of the library. It creates a reactive resource that opens a server-sent event stream, tracks loading and error states, and exposes the latest emitted value — all within Angular's signal-based reactivity model. + +```ts +import { streamResource } from '@ngxp/stream-resource'; + +// Inside a component or service with injection context +const repo = streamResource({ + url: () => `/api/repos/${this.repoId()}`, + transport: inject(FetchStreamTransport), +}); + +// Use in template +// repo.value() — latest emitted value (or undefined) +// repo.status() — 'idle' | 'loading' | 'streaming' | 'error' +``` + + + `streamResource` must be called during construction, inside an injection + context (e.g. a component constructor, field initializer, or a function + passed to `runInInjectionContext`). Calling it outside an injection context + will throw. + + {/* Auto-rendered from api-docs.json — see page component */} diff --git a/apps/website/content/docs-v2/concepts/agent-architecture.mdx b/apps/website/content/docs-v2/concepts/agent-architecture.mdx index 3d84d0d24..32a334de6 100644 --- a/apps/website/content/docs-v2/concepts/agent-architecture.mdx +++ b/apps/website/content/docs-v2/concepts/agent-architecture.mdx @@ -53,3 +53,17 @@ streamResource() supports these patterns through the `subagents()` and `activeSu Most applications only need a single agent with tools. Add subagents when you need true task delegation with isolated state. + +## What's Next + + + + Learn the graph, node, and edge model that agents are built on. + + + Compose agents into multi-agent pipelines using subgraphs. + + + Pause agent execution and wait for human approval mid-run. + + diff --git a/apps/website/content/docs-v2/concepts/angular-signals.mdx b/apps/website/content/docs-v2/concepts/angular-signals.mdx index cd9677f72..5fb2a5887 100644 --- a/apps/website/content/docs-v2/concepts/angular-signals.mdx +++ b/apps/website/content/docs-v2/concepts/angular-signals.mdx @@ -59,3 +59,17 @@ Unlike traditional Angular HTTP patterns, streamResource doesn't use Observables Signals are simpler for UI state. They synchronously read the latest value, compose with computed(), and integrate with Angular's template syntax. streamResource handles the async SSE connection internally and surfaces results as Signals. + +## What's Next + + + + Understand how LangGraph agent state flows into Angular Signals. + + + See Signals in action with token-by-token streaming responses. + + + Full reference for every Signal exposed by streamResource. + + diff --git a/apps/website/content/docs-v2/concepts/langgraph-basics.mdx b/apps/website/content/docs-v2/concepts/langgraph-basics.mdx index 046e64cf2..4a8e27d94 100644 --- a/apps/website/content/docs-v2/concepts/langgraph-basics.mdx +++ b/apps/website/content/docs-v2/concepts/langgraph-basics.mdx @@ -49,3 +49,17 @@ streamResource({ ... }) For deeper LangGraph concepts (persistence, interrupts, memory), see the individual guide pages. + +## What's Next + + + + Understand the planning, tool-calling, and execution lifecycle. + + + Stream token-by-token responses from your LangGraph agent. + + + Learn how streamResource exposes agent state as Angular Signals. + + diff --git a/apps/website/content/docs-v2/concepts/state-management.mdx b/apps/website/content/docs-v2/concepts/state-management.mdx index 8e9274cad..6c1c9e94a 100644 --- a/apps/website/content/docs-v2/concepts/state-management.mdx +++ b/apps/website/content/docs-v2/concepts/state-management.mdx @@ -66,4 +66,18 @@ Every state update from the agent creates a new signal value. Angular's change d const hasErrors = computed(() => agent.value().analysis.issues.length > 0 ); + +## What's Next + + + + Learn how streamResource uses Signals for reactive rendering. + + + Persist thread state so users can resume conversations later. + + + Preserve context across sessions with LangGraph's memory store. + + ``` diff --git a/apps/website/content/docs-v2/guides/deployment.mdx b/apps/website/content/docs-v2/guides/deployment.mdx index ff23ad567..91abe0c21 100644 --- a/apps/website/content/docs-v2/guides/deployment.mdx +++ b/apps/website/content/docs-v2/guides/deployment.mdx @@ -88,3 +88,20 @@ Store threadId in localStorage or a backend so users can resume conversations. Set `throttle` option if token-by-token updates are too frequent for your UI. + +## What's Next + + + + Test agent interactions deterministically before deploying. + + + Store thread IDs so users can resume conversations across sessions. + + + Tune streaming options like throttle for production performance. + + + Full reference for provideStreamResource configuration options. + + diff --git a/apps/website/content/docs-v2/guides/interrupts.mdx b/apps/website/content/docs-v2/guides/interrupts.mdx index b17d8f5ef..8063b852f 100644 --- a/apps/website/content/docs-v2/guides/interrupts.mdx +++ b/apps/website/content/docs-v2/guides/interrupts.mdx @@ -76,3 +76,20 @@ interruptCount = computed(() => agent.interrupts().length); Use the BagTemplate generic parameter to type your interrupt payloads for full TypeScript safety. + +## What's Next + + + + Resume conversations across page refreshes with thread persistence. + + + Stream token-by-token responses and tool progress in real time. + + + Script interrupt events deterministically with MockStreamTransport. + + + Full reference for streamResource options and returned signals. + + diff --git a/apps/website/content/docs-v2/guides/memory.mdx b/apps/website/content/docs-v2/guides/memory.mdx index b72d55fe2..f2da7b9d5 100644 --- a/apps/website/content/docs-v2/guides/memory.mdx +++ b/apps/website/content/docs-v2/guides/memory.mdx @@ -63,3 +63,20 @@ const agent = streamResource({ The agent controls what gets stored in memory. streamResource() just surfaces the current state. Design your agent's state schema to include the fields you want to persist. + +## What's Next + + + + Save thread IDs and resume conversations across sessions. + + + Replay and branch agent runs from any past checkpoint. + + + Understand how agent state flows into Angular Signals. + + + Test memory and state behavior with MockStreamTransport. + + diff --git a/apps/website/content/docs-v2/guides/persistence.mdx b/apps/website/content/docs-v2/guides/persistence.mdx index 3132b920c..2eb55eca9 100644 --- a/apps/website/content/docs-v2/guides/persistence.mdx +++ b/apps/website/content/docs-v2/guides/persistence.mdx @@ -87,3 +87,20 @@ When a connection drops, streamResource() can rejoin an in-progress run. await chat.joinStream(runId, lastEventId); // Picks up from where the connection was lost ``` + +## What's Next + + + + Pause agent execution and wait for human input with interrupt signals. + + + Preserve context across sessions using LangGraph's memory store. + + + Stream token-by-token responses and tool progress in real time. + + + Test agent interactions deterministically with MockStreamTransport. + + diff --git a/apps/website/content/docs-v2/guides/streaming.mdx b/apps/website/content/docs-v2/guides/streaming.mdx index 55d2fde6b..b3dc03962 100644 --- a/apps/website/content/docs-v2/guides/streaming.mdx +++ b/apps/website/content/docs-v2/guides/streaming.mdx @@ -1,6 +1,6 @@ # Streaming -StreamResource provides token-by-token streaming from LangGraph agents via Server-Sent Events (SSE). Every update lands directly in Angular Signals. +StreamResource provides token-by-token streaming from LangGraph agents via Server-Sent Events (SSE). Every update lands directly in Angular Signals — no subscriptions, no manual change detection. Make sure you've completed the Installation guide first. @@ -8,29 +8,45 @@ Make sure you've completed the Installation guide first. ## Basic streaming - - +Create a `streamResource` in your component, pass it a message, and bind to the resulting signals. + + + ```typescript -// chat.component.ts -const chat = streamResource<{ messages: BaseMessage[] }>({ - assistantId: 'chat_agent', -}); +import { Component, computed } from '@angular/core'; +import { streamResource } from '@stream-resource/angular'; +import { BaseMessage } from '@langchain/core/messages'; + +@Component({ selector: 'app-chat', templateUrl: './chat.component.html' }) +export class ChatComponent { + readonly chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', + }); -// Status updates as streaming progresses -const isStreaming = computed(() => chat.status() === 'streaming'); + readonly isStreaming = computed(() => this.chat.status() === 'streaming'); + + send(text: string) { + this.chat.stream({ messages: [{ role: 'user', content: text }] }); + } +} ``` - + ```html - + + @for (msg of chat.messages(); track $index) {

{{ msg.content }}

} + +@if (chat.status() === 'error') { +

{{ chat.error()?.message }}

+} ```
@@ -38,16 +54,152 @@ const isStreaming = computed(() => chat.status() === 'streaming'); ## Stream status -The `status()` signal reports the current state: +The `status()` signal reports the current lifecycle state of the SSE connection: -No active stream. Ready to send a message. +No active stream. The resource is ready to accept a new message. -Tokens are arriving. Messages update in real-time. +Tokens are arriving over the SSE connection. Signal values update in real-time with each chunk. -Something went wrong. Check the error() signal for details. +The connection was interrupted or the agent returned an error. Inspect `error()` for the full details. + +## Stream modes + +LangGraph supports three stream modes. Pass `streamMode` to control what each SSE chunk contains. + + + + +```typescript +// Receives the full agent state after every node execution. +// Best for message-based chat interfaces. +const chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', + streamMode: 'values', +}); + +// chat.messages() always contains the complete message list +``` + + + + +```typescript +// Streams individual message tokens as they are generated. +// Best for token-by-token rendering with lowest perceived latency. +const chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', + streamMode: 'messages', +}); +``` + + + + +```typescript +// Emits raw LangGraph run events (on_chain_start, on_llm_stream, etc.). +// Best for advanced observability or custom progress indicators. +const chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', + streamMode: 'events', +}); +``` + + + + + +Use `values` for most chat UIs — it gives you a consistent, complete state snapshot. Switch to `messages` only when you need sub-token latency or are rendering a live typing cursor. + + +## Error handling + +If the SSE connection drops or the agent throws, `status()` transitions to `'error'` and `error()` is populated. Use these signals to render fallback UI and retry. + + + + +```typescript +import { Component, computed, effect } from '@angular/core'; +import { streamResource } from '@stream-resource/angular'; +import { BaseMessage } from '@langchain/core/messages'; + +@Component({ selector: 'app-chat', templateUrl: './chat.component.html' }) +export class ChatComponent { + readonly chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', + }); + + readonly hasError = computed(() => this.chat.status() === 'error'); + + retry() { + // Re-stream using the same thread so context is preserved + this.chat.stream(); + } +} +``` + + + + +```html +@if (hasError()) { +
+

{{ chat.error()?.message }}

+ +
+} +``` + +
+
+ + +`error()` surfaces both transport-level failures (lost connection, 5xx) and application-level errors returned by the agent graph. Check `error().cause` for the underlying HTTP status when you need to distinguish them. + + +## Throttle configuration + +By default StreamResource emits a signal update for every incoming SSE chunk. On fast connections this can trigger hundreds of renders per second. Use the `throttle` option to coalesce updates. + +```typescript +const chat = streamResource<{ messages: BaseMessage[] }>({ + assistantId: 'chat_agent', + // Batch incoming chunks and flush at most once every 50 ms + throttle: 50, +}); +``` + +The value is in milliseconds. A `throttle` of `0` (default) disables batching and passes every chunk through immediately. Good starting values: + +| Use case | Recommended throttle | +|---|---| +| Token-by-token typing effect | 0 ms (disabled) | +| Standard chat bubble | 50 ms | +| Background summarisation | 150 ms | + + +Each call to `chat.stream()` opens a new SSE connection. Connections are automatically closed when the agent run completes or when the Angular component is destroyed — you do not need to manage the lifecycle manually. + + +## What's Next + + + + Resume conversations across page reloads using thread IDs and checkpointers. + + + Pause agent execution mid-stream to collect human input before continuing. + + + Unit-test components that use streamResource with the built-in test harness. + + + Full option reference for streamResource(), including all configuration keys. + + diff --git a/apps/website/content/docs-v2/guides/subgraphs.mdx b/apps/website/content/docs-v2/guides/subgraphs.mdx index ed76391bf..2663160e1 100644 --- a/apps/website/content/docs-v2/guides/subgraphs.mdx +++ b/apps/website/content/docs-v2/guides/subgraphs.mdx @@ -8,7 +8,7 @@ LangGraph calls them subgraphs (modular graph composition). Deep Agents calls th ## Tracking subagent execution -The `subagents()` signal contains a Map of active subagent streams. +The `subagents()` signal contains a Map of active subagent streams. Use it to inspect the full set of delegated tasks and their current state. ```typescript const orchestrator = streamResource({ @@ -16,20 +16,25 @@ const orchestrator = streamResource({ subagentToolNames: ['research', 'analyze', 'summarize'], }); -// All subagent streams +// All subagent streams (active and completed) const subagents = computed(() => orchestrator.subagents()); // Only active ones const running = computed(() => orchestrator.activeSubagents()); const runningCount = computed(() => running().length); + +// React to count changes +effect(() => { + console.log(`${runningCount()} subagents currently running`); +}); ``` ## Subagent stream details -Each `SubagentStreamRef` provides its own signals. +Each `SubagentStreamRef` exposes its own reactive signals — status, messages, and errors — so you can surface granular progress in your UI. ```typescript -// Access a specific subagent +// Access a specific subagent by its tool call ID const researchAgent = computed(() => orchestrator.subagents().get('research-tool-call-id') ); @@ -37,7 +42,89 @@ const researchAgent = computed(() => // Track its progress const researchStatus = computed(() => researchAgent()?.status()); const researchMessages = computed(() => researchAgent()?.messages() ?? []); +const researchError = computed(() => researchAgent()?.error()); +``` + +## Orchestrator pattern + +The orchestrator pattern delegates specialised work to subagents and merges their results. Each subagent runs its own graph independently while the parent coordinates. + +```typescript +const pipeline = streamResource({ + assistantId: 'pipeline-orchestrator', + subagentToolNames: ['fetch-data', 'transform', 'validate', 'publish'], + filterSubagentMessages: true, +}); + +// Derive a summary of all subagent states +const pipelineStatus = computed(() => { + const agents = pipeline.subagents(); + const entries = [...agents.entries()]; + + return { + total: entries.length, + pending: entries.filter(([, a]) => a.status() === 'pending').length, + running: entries.filter(([, a]) => a.status() === 'streaming').length, + done: entries.filter(([, a]) => a.status() === 'complete').length, + failed: entries.filter(([, a]) => a.status() === 'error').length, + }; +}); +``` + +## Subagent progress UI + +Render live progress for each subagent using the signals above. + + + +```typescript +import { computed } from '@angular/core'; + +@Component({ + selector: 'app-subagent-progress', + template: ` + @for (entry of subagentEntries(); track entry[0]) { +
+ {{ entry[0] }} + {{ entry[1].status() }} + @if (entry[1].status() === 'error') { + {{ entry[1].error()?.message }} + } +
+ } + `, +}) +export class SubagentProgressComponent { + orchestrator = inject(OrchestratorService).resource; + + subagentEntries = computed(() => + [...this.orchestrator.subagents().entries()] + ); +} +``` +
+ +```html + +@for (entry of subagentEntries(); track entry[0]) { +
+ {{ entry[0] }} + + {{ entry[1].status() }} + + + @if (entry[1].status() === 'streaming') { + + } + + @if (entry[1].status() === 'error') { +

{{ entry[1].error()?.message }}

+ } +
+} ``` +
+
## Filtering subagent messages @@ -57,3 +144,55 @@ const parentMessages = computed(() => orchestrator.messages()); Set `subagentToolNames` to the tool names that spawn subagents. streamResource() uses this to identify which tool calls create subagent streams. + +## Error handling per subagent + +Each subagent exposes its own `error()` signal so failures are isolated — one subagent failing does not stop the others. + +```typescript +const agents = orchestrator.subagents(); + +for (const [id, agent] of agents) { + effect(() => { + const err = agent.error(); + if (err) { + console.error(`Subagent ${id} failed:`, err.message); + // Retry, surface to user, or fall back gracefully + } + }); +} + +// Collect all failed subagents reactively +const failedAgents = computed(() => + [...orchestrator.subagents().entries()].filter( + ([, agent]) => agent.status() === 'error' + ) +); +``` + + +Always check `failedAgents()` before presenting final results. A completed orchestrator can still have subagents that errored — success at the top level does not guarantee all delegates succeeded. + + +## When to use subagents vs a single agent + + +Use **subagents** when tasks are independent and can run in parallel, when each task needs its own context window, or when you want isolated error boundaries. Use a **single agent** for sequential reasoning, tasks that share tightly coupled state, or when latency from spawning subagents outweighs the parallelism benefit. + + +## What's Next + + + + Understand how streamResource() surfaces tokens, status, and errors in real time. + + + Write unit and integration tests for orchestrator graphs and subagent interactions. + + + Full reference for streamResource() options, signals, and subagent configuration. + + + Patterns for retries, fallbacks, and surfacing errors from deeply nested agents. + + diff --git a/apps/website/content/docs-v2/guides/testing.mdx b/apps/website/content/docs-v2/guides/testing.mdx index 5b1cad068..0d0f7f80f 100644 --- a/apps/website/content/docs-v2/guides/testing.mdx +++ b/apps/website/content/docs-v2/guides/testing.mdx @@ -104,3 +104,20 @@ it('should surface errors', () => { streamResource() must be called within an Angular injection context. In tests, wrap calls in `TestBed.runInInjectionContext()`. + +## What's Next + + + + Understand the SSE event model your tests simulate. + + + Test human-in-the-loop approval flows with scripted interrupt events. + + + Configure streamResource() for production LangGraph Cloud. + + + Full reference for MockStreamTransport options and methods. + + diff --git a/apps/website/content/docs-v2/guides/time-travel.mdx b/apps/website/content/docs-v2/guides/time-travel.mdx index d45cab714..37501fbb9 100644 --- a/apps/website/content/docs-v2/guides/time-travel.mdx +++ b/apps/website/content/docs-v2/guides/time-travel.mdx @@ -1,14 +1,14 @@ # Time Travel -Time travel lets you inspect earlier states and replay alternate execution paths. streamResource() exposes the full checkpoint history and branch navigation through Angular Signals. +Time travel lets you inspect earlier states and replay alternate execution paths. `streamResource()` exposes the full checkpoint history and branch navigation through Angular Signals. Use it to debug agent decisions, explore alternate paths, and build undo/redo experiences. -Debug agent decisions, explore alternate paths, and build undo/redo experiences for your users. +Debug agent decisions, explore alternate paths, and build undo/redo experiences for your users. Time travel works with any LangGraph agent that persists checkpoints to a thread. ## Browsing execution history -The `history()` signal contains an array of `ThreadState` checkpoints. +The `history()` signal contains an array of `ThreadState` checkpoints ordered from oldest to newest. Each checkpoint captures the complete agent state at that point in execution, including messages, intermediate results, and any custom state fields. ```typescript const agent = streamResource({ @@ -19,14 +19,21 @@ const agent = streamResource({ // Full execution timeline const checkpoints = computed(() => agent.history()); const checkpointCount = computed(() => agent.history().length); + +// Access a specific checkpoint +const latestCheckpoint = computed(() => { + const history = agent.history(); + return history[history.length - 1]; +}); ``` +Each `ThreadState` entry exposes `checkpoint`, `metadata`, `created_at`, and the full `values` snapshot, giving you complete visibility into every step of execution. + ## Forking from a checkpoint -Submit with a specific checkpoint to branch execution from an earlier state. +Submit with a specific checkpoint to branch execution from an earlier state. This creates a new branch in the thread graph while leaving the original path intact. ```typescript -// Fork from the 3rd checkpoint with new input forkFromCheckpoint(index: number) { const checkpoint = this.agent.history()[index]; this.agent.submit( @@ -34,22 +41,134 @@ forkFromCheckpoint(index: number) { { checkpoint: checkpoint.checkpoint } ); } + +// Fork with a completely different input +retryWithAlternative(index: number, newInput: string) { + const checkpoint = this.agent.history()[index]; + this.agent.submit( + { messages: [{ role: 'user', content: newInput }] }, + { checkpoint: checkpoint.checkpoint } + ); +} ``` ## Branch navigation -Use `branch()` and `setBranch()` to navigate between execution branches. +Use `branch()` and `setBranch()` to navigate between execution branches. Branches are automatically created when you fork from a checkpoint. ```typescript -// Current branch +// Current branch identifier const activeBranch = computed(() => agent.branch()); +// All available branches (if exposed by your graph) +const allBranches = computed(() => agent.history() + .map(s => s.metadata?.branch) + .filter(Boolean) +); + // Switch to a different branch selectBranch(branchId: string) { agent.setBranch(branchId); } ``` +## Building a history UI + +Expose checkpoint history directly in your component to let users scrub through execution steps or rewind to any earlier state. + + + +```typescript +import { Component, inject, computed } from '@angular/core'; +import { streamResource } from '@stream-resource/angular'; +import { AgentService } from './agent.service'; + +@Component({ + selector: 'app-history-viewer', + templateUrl: './history-viewer.component.html', +}) +export class HistoryViewerComponent { + private agentService = inject(AgentService); + readonly agent = this.agentService.agent; + + readonly checkpoints = computed(() => this.agent.history()); + readonly activeIndex = computed(() => + this.checkpoints().length - 1 + ); + + fork(index: number) { + const checkpoint = this.checkpoints()[index]; + this.agent.submit( + { messages: [{ role: 'user', content: 'Try a different approach' }] }, + { checkpoint: checkpoint.checkpoint } + ); + } + + formatTime(isoString: string): string { + return new Date(isoString).toLocaleTimeString(); + } +} +``` + + +```html +
    + @for (cp of checkpoints(); track cp.checkpoint.id; let i = $index) { +
  • + Step {{ i + 1 }} + {{ formatTime(cp.created_at) }} + +
  • + } +
+``` +
+
+ +## Comparing checkpoints + +Diff two checkpoints to understand exactly what changed between execution steps. This is useful for understanding tool call results, message additions, or state mutations. + +```typescript +compareCheckpoints(indexA: number, indexB: number) { + const history = this.agent.history(); + const stateA = history[indexA]?.values; + const stateB = history[indexB]?.values; + + if (!stateA || !stateB) return null; + + // Compare message counts + const messagesAdded = (stateB.messages?.length ?? 0) + - (stateA.messages?.length ?? 0); + + // Identify changed keys + const changedKeys = Object.keys({ ...stateA, ...stateB }).filter( + key => JSON.stringify(stateA[key]) !== JSON.stringify(stateB[key]) + ); + + return { messagesAdded, changedKeys }; +} +``` + +Use the comparison result to render a diff view, highlight changed fields in your UI, or log what the agent modified during a specific step. + -Time travel is most useful during development. Inspect why an agent chose a particular path, then fork to test alternatives without restarting the conversation. +Time travel is most useful during development. Inspect why an agent chose a particular path by comparing adjacent checkpoints, then fork to test alternatives without restarting the conversation. Combine `history()` with Angular DevTools to watch checkpoint arrays update in real time as the agent streams. + +## What's Next + + + + Configure thread storage so checkpoints survive page reloads and are available across sessions. + + + Understand how streamResource() surfaces incremental updates and how history integrates with live streaming state. + + + Full reference for streamResource() options, signals, and the submit() API including checkpoint parameters. + + + Deep dive into branch management, merging strategies, and presenting multi-branch UIs to end users. + + diff --git a/docs/superpowers/plans/2026-04-04-docs-pages-improvement.md b/docs/superpowers/plans/2026-04-04-docs-pages-improvement.md new file mode 100644 index 000000000..f45b73d92 --- /dev/null +++ b/docs/superpowers/plans/2026-04-04-docs-pages-improvement.md @@ -0,0 +1,171 @@ +# Docs Pages Improvement Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Bring all 18 docs pages up to the quality level of the introduction page — expanded content, proper MDX components, navigation sections, and consistent design patterns. + +**Architecture:** Each task updates one or more MDX files. Changes are content-only (no new components needed). All pages should use: glass Callouts with SVG icons, labeled Tabs, code blocks with copy buttons (automatic via Pre component), and "What's Next" CardGroup at the bottom. + +**Baseline:** The introduction page (292 lines) sets the quality bar with: animated diagram, FeatureChips, expanded code examples, Callouts, Steps, Tabs with labels, and CardGroup navigation. + +--- + +## Audit Summary + +| Quality | Pages | Action | +|---------|-------|--------| +| **THIN (needs major expansion)** | streaming (53), time-travel (55), subgraphs (59), 4 API stubs (3 each) | Double or triple content | +| **ADEQUATE (needs polish)** | persistence (89), interrupts (78), memory (65), testing (106), deployment (90), langgraph-basics (51), agent-architecture (55) | Add nav section, expand examples, add Callouts | +| **GOOD (minor polish)** | quickstart (130), installation (102), angular-signals (61), state-management (69) | Add nav section where missing | + +## Common Improvements for ALL Pages + +Every page should get: +1. **"What's Next" CardGroup** at the bottom (links to 2-4 related pages) +2. **At least one Callout** (tip, info, or warning) for key insights +3. **Tab-labeled code examples** where showing TypeScript + Template patterns + +--- + +### Task 1: Expand Streaming Guide (THIN → GOOD) + +**File:** `apps/website/content/docs-v2/guides/streaming.mdx` + +Expand from 53 to ~120 lines. Add: +- Stream modes explanation (values, messages, events) +- Error handling during streaming +- Throttle configuration +- Template patterns with `@if` / `@for` +- Callout about SSE connection behavior +- "What's Next" CardGroup + +--- + +### Task 2: Expand Time Travel Guide (THIN → GOOD) + +**File:** `apps/website/content/docs-v2/guides/time-travel.mdx` + +Expand from 55 to ~100 lines. Add: +- UI pattern for building a history timeline +- Tabs showing TypeScript + Template for history display +- Comparing checkpoints +- Callout about debugging workflow +- "What's Next" CardGroup + +--- + +### Task 3: Expand Subgraphs Guide (THIN → GOOD) + +**File:** `apps/website/content/docs-v2/guides/subgraphs.mdx` + +Expand from 59 to ~100 lines. Add: +- Orchestrator pattern with code example +- Tabs showing TypeScript + Template for subagent UI +- Error handling per subagent +- Callout about when to use subagents vs single agent +- "What's Next" CardGroup + +--- + +### Task 4: Expand API Reference Stubs (THIN → ADEQUATE) + +**Files:** +- `apps/website/content/docs-v2/api/stream-resource.mdx` +- `apps/website/content/docs-v2/api/provide-stream-resource.mdx` +- `apps/website/content/docs-v2/api/fetch-stream-transport.mdx` +- `apps/website/content/docs-v2/api/mock-stream-transport.mdx` + +Each API page should have a brief intro paragraph and a usage example before the auto-generated content. ~15-20 lines each. + +--- + +### Task 5: Polish Persistence Guide (ADEQUATE → GOOD) + +**File:** `apps/website/content/docs-v2/guides/persistence.mdx` + +Add: +- "What's Next" CardGroup +- Callout about production persistence patterns +- Tab labels using `label` prop if not already + +--- + +### Task 6: Polish Interrupts Guide (ADEQUATE → GOOD) + +**File:** `apps/website/content/docs-v2/guides/interrupts.mdx` + +Add: +- Multi-step approval pattern +- "What's Next" CardGroup +- Callout about timeout handling + +--- + +### Task 7: Polish Memory Guide (ADEQUATE → GOOD) + +**File:** `apps/website/content/docs-v2/guides/memory.mdx` + +Add: +- Tabs for TypeScript + Template patterns +- "What's Next" CardGroup +- Callout about memory best practices + +--- + +### Task 8: Polish Testing Guide (ADEQUATE → GOOD) + +**File:** `apps/website/content/docs-v2/guides/testing.mdx` + +Add: +- "What's Next" CardGroup +- Integration test example with TestBed + +--- + +### Task 9: Polish Deployment Guide (ADEQUATE → GOOD) + +**File:** `apps/website/content/docs-v2/guides/deployment.mdx` + +Add: +- "What's Next" CardGroup +- Monitoring/observability section +- CORS configuration callout + +--- + +### Task 10: Polish Concept Pages (ADEQUATE → GOOD) + +**Files:** +- `apps/website/content/docs-v2/concepts/langgraph-basics.mdx` +- `apps/website/content/docs-v2/concepts/agent-architecture.mdx` + +Add: +- "What's Next" CardGroup to both +- Code examples with Tabs where appropriate + +--- + +### Task 11: Add Navigation to Good Pages + +**Files:** +- `apps/website/content/docs-v2/concepts/angular-signals.mdx` +- `apps/website/content/docs-v2/concepts/state-management.mdx` + +Add: +- "What's Next" CardGroup (these are the only good pages missing it) + +--- + +### Task 12: Final Build Verification + +- [ ] Build website: `npx nx build website --skip-nx-cache` +- [ ] Verify all 19 pages render +- [ ] Spot-check 5 pages for CardGroup, Callouts, and code blocks + +--- + +## Execution Strategy + +Tasks 1-3 (THIN pages) are the priority — these need the most work. +Tasks 4-11 are polish passes that can be parallelized. +All tasks are independent of each other.