Shared State
Create a two-way connection between your UI and agent state.
What is shared state?#
Agentic Copilots maintain a shared state that seamlessly connects your UI with the agent's execution. This shared state system allows you to:
- Display the agent's current progress and intermediate results
- Update the agent's state through UI interactions
- React to state changes in real-time across your application
When should I use this?#
Use shared state when you want to facilitate collaboration between your agent and the user. Updates flow both ways — the agent's outputs are automatically reflected in the UI, and any inputs the user updates in the UI are automatically reflected in the agent's execution.
Reading agent state#
Subscribe a component to the agent's state with useAgent. Any time the agent
mutates its state — for example via a tool call — the hook fires and your UI
re-renders with the new values.
// Subscribe the component to agent state changes. Any time the agent // mutates its state (e.g. via its `set_notes` tool) this hook fires, // we re-render, and the sidebar panels reflect the new values. const { agent } = useAgent({ agentId: "shared-state-read-write", updates: [UseAgentUpdate.OnStateChanged], });The returned agent.state is just a plain object. Read it like any other
piece of React state and render the parts you care about — agent-written
notes, structured outputs, progress indicators, anything the agent has put
there.
Writing agent state#
The same agent object exposes a setState setter. Calling it from a UI
event handler pushes the new value into shared state, and the agent reads it
back on its next turn — so the UI's writes visibly steer the model.
// WRITE: every edit in the sidebar goes straight into agent state. // On the agent's next turn, `PreferencesInjectorMiddleware` reads this // back out of state and adds it to the system prompt — so the UI's // writes visibly steer the model. const handlePreferencesChange = (next: Preferences) => { agent.setState({ preferences: next, notes, // preserve what the agent has written } as RWAgentState); };This is what makes the channel two-way: the UI doesn't just observe the agent, it can hand the agent fresh inputs (preferences, selections, partial work) without going through the chat thread.
Rendering shared state in the UI#
Because agent.state is plain React data, the UI layer is whatever you'd
normally build. The demo on this page wires the agent's outputs into a
small card component and feeds user edits back through setState.
// Read-side render: this card reflects the agent-authored `notes` slice// of shared state. The parent page passes `state.notes` in; we never// touch agent state ourselves — we just render it. The Clear button is// a small write-back, exposed as an `onClear` prop.export function NotesCard({ notes, onClear }: NotesCardProps) { return ( <Card data-testid="notes-card" className="w-full"> <CardHeader> <div className="flex items-start justify-between gap-3"> <div className="space-y-1.5"> <CardTitle>Agent Scratch pad</CardTitle> <CardDescription> The agent writes here via its{" "} <code className="font-mono text-[11px] text-[#010507]"> set_notes </code>{" "} tool. The UI re-renders from shared state. </CardDescription> </div> {notes.length > 0 && ( <Button type="button" onClick={onClear} data-testid="notes-clear-button" variant="destructive" size="sm" className="uppercase tracking-[0.14em] text-[10px]" > Clear </Button> )} </div> </CardHeader> <CardContent> {notes.length === 0 ? ( <div data-testid="notes-empty" className="text-sm text-[#838389] italic min-h-[160px] flex items-center justify-center text-center px-4 border border-dashed border-[#E9E9EF] rounded-xl bg-[#FAFAFC]" > the agent will make observations about you and note them here! </div> ) : ( <ul data-testid="notes-list" className="space-y-2 text-sm text-[#010507]" > {notes.map((note, i) => ( <li key={i} data-testid="note-item" className="flex gap-2 rounded-lg border border-[#E9E9EF] bg-[#FAFAFC] px-3 py-2" > <span className="text-[#838389] font-mono text-xs leading-5 select-none"> {String(i + 1).padStart(2, "0")} </span> <span className="flex-1">{note}</span> </li> ))} </ul> )} </CardContent> </Card> );}Streaming partial state updates#
By default, agent state only updates between node transitions, so a long-running tool call appears as one big burst at the end. State streaming forwards a specific tool argument straight into a state key as it's being generated, so the UI can watch the answer assemble token-by-token.
See State streaming for the full walkthrough,
including the corresponding useAgent subscription on the frontend.
Read-only context#
When the value is UI-owned and the agent should read it but never write
it back — current user, selected record, scroll position — reach for
useAgentContext instead of full shared state. It publishes values as a
one-way UI → agent channel that auto-unregisters on unmount.
See Agent read-only context for the full pattern.
