Skip to content

feat: a saved A2UI card outlives its chat #86

Open

Description

@Lancetnik

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_surfaces
  • src/assistant/a2ui.py — durable_surfaces_from_messages, update_data_value, expanded_card_surface
  • src/assistant/gateway/wire.py — as_drawn; src/assistant/gateway/app.py — the click / server-action path emitting A2UISurfaceDataUpdated
  • src/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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions