Repository navigation
feat: a saved A2UI card outlives its chat #86
Description
Is your feature request related to a problem?
A rendered card lives only inside the chat that produced it. At the end of a turn the gateway projects the turn's A2UI messages into an A2UISurface event (durable_surfaces_from_messages(), Gateway._emit_a2ui_surfaces) and stores it in that chat's event log; later data updates (A2UISurfaceDataUpdated, from a click or a server action) are events on the same stream.
So ask the assistant in a new chat about the tracker it drew yesterday and there is nothing to address; a scheduled task cannot refresh a dashboard card; delete the chat and the card is gone.
Describe the solution you'd like
Every card the agent creates becomes a Card instance file in the profile's Files space at the moment it is created, and the thread renders that file. One class of object — no "saved" versus "unsaved" card, no pin gesture.
An instance is small, and it is data:
card: WeatherPanel # the Card (definition) it is drawn with — #85
params: {location: Berlin} # the arguments of the Card's data source, if it has one — #122
data: {…} # the latest fields, validated against the Card's schema
updated: 2026-10-01T09:00Z- It holds the Card's fields, not the expanded primitives: the server already expands an instance with its Card's layout on the way to a client (
gateway/wire.py::as_drawn), so an instance redraws when its Card's layout changes. - Its data is updated by the backend, not by the LLM redrawing it. A Card with a data source (feat: an A2UI card fetches its own data, without the LLM #122) refreshes its instance deterministically — when shown, on its interval, from a button, or from a scheduled Task — and writes the result into this file. The model's part is creating the instance: choosing the Card and its parameters. This is where we deliberately differ from "live artifacts" that the model re-renders.
- A model-filled Card without a source (a brief, a comparison) is an instance too; it changes only when the agent — or the user — edits it.
- The chat, a screen (feat: screens — user-composed dashboards of live A2UI cards #123) and a task all address the same file, so one refresh reaches every place the card is shown.
Consequences that are the point:
- the card is visible in the Files tree like any other artifact, reachable outside the chat it came from, and survives the chat being deleted
- a card can be read, diffed and copied between profiles
- the agent reaches it with ordinary file tools, and so does the user
Existing chats keep working. Surfaces already persisted in chat logs keep drawing and their clicks keep working — an adoption step per AGENTS.md ("breaking changes land backward-compatibly"). Turning rich views off never breaks a chat's history.
Alternatives considered
- Only explicitly saved cards get a file — rejected: it splits cards into two classes and adds a gesture to remember.
- The model re-renders a card to update it (the live-artifacts model) — rejected: slow, costs a turn per refresh, and is non-deterministic for data the app can fetch itself (feat: an A2UI card fetches its own data, without the LLM #122).
- Snapshot-on-write archive, chat stays the source of truth — rejected: a dead copy solves reading, not updating.
Additional context
src/assistant/gateway/core.py—_watch_a2ui,_emit_a2ui_surfacessrc/assistant/a2ui.py—durable_surfaces_from_messages,update_data_value,expanded_card_surfacesrc/assistant/gateway/wire.py—as_drawn;src/assistant/gateway/app.py— the click / server-action path emittingA2UISurfaceDataUpdatedsrc/assistant/coding/surface.py— the server-filled CodingSession: one layout emit, then data-only updates, already the shape an instance takes- AG2 upstream: updating a rendered surface outside a turn ([Feature Request]: Update a rendered A2UI surface outside a turn ag2#3323)
- ADR 0001 (single root directory), ADR 0012 (file references resolve by path), ADR 0036 (Cards are layered files)
See also: #122 (a card fetches its own data), #123 (screens), #68 (tasks as editable files — same instinct, a stored entity gets a file form).
Activity