Learn by watching a conversation
Send a message, watch partial text arrive, and wait for the response to finish.
Recorded in the real example with fixture responses paced for readability. No live model requests or narration. Actual-speed footage with reading pauses. Capture details
Read the walkthrough
- Enter a message and choose Send.
- The conversation displays partial response text while the request is active.
- The completed response remains in the conversation.
A response arrives over time
An agent conversation is a sequence of observations. The traveler sends a message, the application starts a request, and the answer arrives in pieces. The interface can display those pieces before the response is complete.
The video above shows that behavior in the React streaming example. In this lesson, you will use a trip conversation to inspect what changes between the first action and the completed answer.
Separate actions from observations
An action asks the application to do something: submit a message, stop an active request, or open a conversation. An observation describes what the application currently knows: its messages, whether an operation is active, and the outcome it has received.
Keep the application responsible for actions. Let the view observe snapshots and render them. A partially written answer is still an observation; its presence does not prove that the request succeeded. The completion outcome is a separate piece of information.
Follow one turn
A turn begins with a user action and ends when the application knows its outcome. The submitted message, the response text, and the work in progress describe different parts of that turn. Several updates can extend the same assistant message; each update does not need a new chat bubble.
Receiving text tells you that part of a response arrived. It does not tell you that the turn completed successfully. Keep the progress indicator tied to the application's busy state, and inspect the final outcome separately. Stopping a response and losing the connection are different outcomes too.
Meet the trip notebook
The workbench below records a prompt about a supplied Paris itinerary and a streamed response. In Example session, choose Recorded example, then Play, pause halfway through, and step backward. The conversation and inspector follow the same observation.
Watch a turn unfold.
// eslint-disable-next-line @nx/enforce-module-boundaries -- The isolated consumer copies this internal example layout.import { ExampleFooter, ExampleLayout } from '../../../../shared/react-example';import { useEffect, useRef, useState } from 'react';import { useAgent } from '@threadplane/react';import { ChatInput, MessageList } from '@threadplane/react/chat';import { createStreamingInspection } from './inspection';import type { InspectionFrame } from './inspection';import { createConnectedApplication, type streamingConnection,} from './connection'; export function StreamingDemo({ connection, onReady, onInspection,}: { readonly connection: ReturnType<typeof streamingConnection>; readonly onReady: () => void; readonly onInspection?: (frame: InspectionFrame) => void;}) { const publisher = useRef(onInspection); publisher.current = onInspection; const [inspection] = useState(() => createStreamingInspection((frame) => publisher.current?.(frame)) ); const [application, setApplication] = useState(() => createConnectedApplication(connection) ); const [resetting, setResetting] = useState(false); const [generation, setGeneration] = useState(0); const live = useRef(true); const snapshot = useAgent(application); useEffect(() => inspection.attach(application), [application, inspection]); useEffect(() => { onReady(); }, [onReady]); useEffect( () => () => { void application.dispose(); }, [application] ); useEffect( () => () => { live.current = false; inspection.dispose(); }, [inspection] ); async function reset() { if (snapshot.busy || resetting) return; setResetting(true); inspection.reset(); await application.dispose(); if (!live.current) return; setApplication(createConnectedApplication(connection)); setGeneration((value) => value + 1); setResetting(false); } const status = snapshot.busy ? snapshot.creation === 'pending' ? 'Creating conversation…' : 'Receiving response…' : snapshot.outcome === 'success' ? 'Response complete.' : snapshot.outcome === 'aborted' ? 'Stopped. Start a new conversation to continue.' : !snapshot.canSubmit ? 'Start a new conversation to continue.' : 'Ready.'; return ( <ExampleLayout className="streaming-demo"> <header> <div> <h1>LangGraph streaming</h1> </div> <button type="button" disabled={snapshot.busy || resetting} onClick={() => void reset()} > New conversation </button> </header> <p>Watch an answer arrive as the agent writes it.</p> <MessageList rows={snapshot.rows} label="Conversation" /> <ExampleFooter> {snapshot.error && <p role="alert">{snapshot.error}</p>} <ChatInput key={generation} label="Message" placeholder="Ask about LangGraph…" disabled={resetting || (!snapshot.canSubmit && !snapshot.busy)} busy={snapshot.busy} onStop={snapshot.busy ? () => void application.stop() : undefined} onSubmit={(text) => { if (!application.getSnapshot().canSubmit) return false; void inspection.submit(application, text); }} /> <p role="status">{status}</p> </ExampleFooter> </ExampleLayout> );} A little time in Paris.
Follow a traveler’s prompt as a response arrives. Pause to see what the application knows.
Press Play to follow the recorded conversation, or move through it one observation at a time.
Observed application fields, not the provider’s internal state.
State has not been observed at this point.
About this recording
Real streaming application and LangGraph SDK observations with a deterministic local HTTP/SSE fixture response. State is an allowlisted application state projection (creation, busy, canSubmit, outcome), not itinerary or provider state. Requests are observed application HTTP at the fixture server, with status when its response headers are sent. elapsedMs is measured capture time; playback pacing is separate.
Observation times are capture offsets. Playback is slowed for reading; it does not measure model performance.
Partial text is an observation.
The view can show part of an answer while the application is still busy. Completion is separate: compare busy with outcome at the final observation.
const snapshot = useAgent(application);This is a real SDK/session capture with deterministic fixture responses, not a live model conversation. The shared teaching trace uses the React example’s application owner; selecting a framework changes the video and source links, not the recorded frontend. Replay reads captured observations and sends no requests. The trip response does not demonstrate itinerary edits or client tools.
Try a live conversation
Choose Live conversation to mount the real React streaming application. Send a prompt about your trip in that application. The inspector follows its messages and state as they change. Runtime settings lets you use the shared development target or your own LangGraph deployment; changing targets starts a fresh live session.
Select an observation or move the timeline to inspect history. The running application stays mounted while the workbench shows a read-only snapshot. Return to live reveals the same conversation without submitting again. Use the application’s own controls to stop a response or start a new conversation.
In live mode, Requests shows Application calls: the observed submit input and whether that invocation is pending, settled, or rejected. Settled means the call returned, not that the model succeeded; read the application’s state for the outcome. HTTP and provider requests are not captured in live mode. Capture stays in this page’s memory and is bounded; the inspector tells you when older observations or long fields are omitted.
Read the workbench
- In Events, select the initial application snapshot. State shows the application’s
creation,busy,canSubmit, andoutcomefields. Before the initial snapshot, the state is unavailable; it is not an empty object. - Move forward until the request begins. Requests shows the application HTTP request to the local fixture server. A response status appears only after its headers were observed. Provider/model requests were not captured.
- Find a partial answer in the conversation. Messages shows the captured message text at that same point. Move backward to see the earlier text restored.
- Move to the final observation. Compare the completed text with the settled
busyandoutcomefields. Playback visits these snapshots at a reading pace; the millisecond offsets in Events describe the capture, not a performance benchmark.
The State panel describes the recorded application owner’s fields. These are example application fields, not a promise that every Threadplane framework exposes the same public API.
Follow it into your application
Choose View example source in the workbench to read the actual React streaming files. Your selected session stays active; use Return to recorded example or Return to live conversation to resume inspection at the same observation. The highlighted line observes the application with useAgent(application); rendering a new snapshot does not submit another request. See it run below the panel returns to the partial-response moment in the recording. View source opens the matching implementation.
In app.tsx, follow the snapshot into MessageList and the busy state into ChatInput. Follow the input's submit and stop handlers back to the application. The application owns commands and cleanup; the view renders observations.
The native React packages are experimental, private source-build packages. These lesson files are real repository examples, not a public npm installation recipe.
These controls inspect this guide’s example. Continue with the React streaming example and use the page-level Docs, Run, Code, and API controls to explore the complete project and connect it to your backend.
The same trip notebook will give later lessons a familiar place to explain tools, approvals, and saved conversations.
Continue with Conversation turns for the vocabulary behind these observations. To work from a checkout, follow Installation and Quickstart. The chat quickstart then follows the same ownership boundary into message presentation and controls.