Interfaces

An .a2ui artifact is an interface the reader uses: a form, a dashboard, a checklist, a picker, a control panel. The assistant describes it as data in the A2UI protocol, version 1.0, and the workspace draws it with its own components, in the reader's theme, light or dark. The file holds no code and no styling, and it can run only what its document declares.

What the reader does in an interface comes back to the conversation as a message from them, so the assistant can answer, change the interface and publish it again.

The document

A .a2ui file is a JSON object. Only messages is required, and a bare array of messages also works.

{
  "title": "Expense approvals",
  "files":   { "invoices": "/work/invoices.csv" },
  "sources": [ { "id": "sales", "tool": "Sales Report", "function": "RegionalSales", "into": "/sales" } ],
  "messages": [
    { "version": "1.0", "createSurface":    { "surfaceId": "main", "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json", "sendDataModel": true } },
    { "version": "1.0", "updateComponents": { "surfaceId": "main", "components": [ ] } },
    { "version": "1.0", "updateDataModel":  { "surfaceId": "main", "path": "/amount", "value": 120 } }
  ]
}
Key Meaning
title The name on the chip.
createSurface Names the surface and the catalog, which is always the basic catalog above. sendDataModel: true makes every action carry the whole data model.
updateComponents A flat list of components that point at each other by id. One of them has the id root.
updateDataModel Sets a value at a JSON pointer such as /user/name.
sources Tool and sandbox calls run when the interface opens.
files Sandbox files published with the interface.

publish checks the document and reports what is wrong with it: no surface, a tool the conversation does not have, a missing file. Nothing is shown until it passes.

Components

The basic catalog has Text (Markdown), Image, Icon, Video, AudioPlayer, Row, Column, List, Card, Tabs, Modal, Divider, Button, TextField, CheckBox, ChoicePicker, Slider and DateTimeInput. Extension catalogs add the components that tie an interface to the workspace. A component from an extension catalog names it with @catalogId.

Catalog Components Use
https://curiosity.ai/a2ui/catalogs/tesserae/1.0 Table, Grid, Metric, Banner, Badge, PropertyGrid, Accordion, Toggle, Rating, TagsInput, DateRange, JsonViewer, CodeDiff, Mermaid Reports, dashboards and richer inputs.
https://curiosity.ai/a2ui/catalogs/flint-chart/1.0 FlintChart A chart drawn by Flint from rows and an encoding.
https://curiosity.ai/a2ui/catalogs/search-area/1.0 SearchArea The workspace search box, facets and results, searched as the reader.
https://curiosity.ai/a2ui/catalogs/graph-view/1.0 GraphView Nodes and the edges between them, from a list of UIDs.
https://curiosity.ai/a2ui/catalogs/nodes/1.0 Node, NodePicker Nodes drawn from UIDs, and a picker for choosing nodes of given types.

The children of an extension container, such as a Grid or an Accordion, are ordinary basic-catalog components.

{ "id": "sales_chart", "component": "FlintChart", "@catalogId": "https://curiosity.ai/a2ui/catalogs/flint-chart/1.0",
  "chartType": "Bar Chart", "title": "Sales per product", "height": 280,
  "data": { "@path": "/sales" },
  "encodings": { "x": "Product", "y": "Sales" } }

Data binding

A value that should follow the data is {"@path": "/pointer"} in place of a literal.

  • Inputs are two-way. A TextField bound to /amount writes what is typed there and shows whatever /amount holds.
  • Lists come from data. "children": {"componentId": "row", "path": "/items"} draws row once per item. Inside the template, a path without a leading slash is relative to the item.
  • Functions are {"@call": "name", "args": {...}}: formatString, formatNumber, formatCurrency, formatDate, pluralize, openUrl (in an action only) and the checks required, regex, length, numeric, email, and, or, not.
  • Validation. @checks on an input or a button shows the failing message and disables the button.

Actions

A button's action is an event or a function call.

An event is a message to the conversation. Give it a userMessage, which is what the transcript shows as the reader's message; without one the reader sees the bare event name.

"action": { "event": { "name": "approve_expense", "userMessage": "Approve the ${/vendor} invoice",
                       "context": { "amount": {"@path":"/amount"} } } }

context is resolved when the reader clicks. The assistant receives a message that names the interface, the action and the context as JSON, and answers it as it would any other turn. Only the owner's conversation receives events; see who runs what.

A function call runs inside the interface and tells nobody: openUrl, or invokeTool.

Tools and data

An interface can call the AI tools and sandboxes that its conversation has. publish records them on the artifact. A tool the conversation does not have is refused at publish time.

Load data when the interface opens with sources. Each entry names a tool, optionally a function, its arguments, and the data-model path to write the result to:

"sources": [
  { "id": "sales", "tool": "Sales Report", "function": "RegionalSales", "arguments": { "region": "EMEA" }, "into": "/sales", "refreshSeconds": 30 },
  { "id": "rows",  "tool": "Ledger", "script": "ledger list --json", "into": "/rows" }
]
  • refreshSeconds is 5 or more and runs the call again while the interface is open.
  • A sandbox call carries a script. The script is bash, and python3 is available through bash. Each call gets a fresh session, so nothing one call writes is there for the next.
  • The outcome is at /_tools/<id> as { "status": "loading" | "done" | "error", "error": "...", "value": ... }.
  • A button runs a call with invokeTool, with into for the result: {"@call":"invokeTool","args":{"id":"refresh","tool":"Sales Report","function":"RegionalSales","into":"/sales"}}.

For anything with a side effect, such as sending, deleting or paying, use an event and let the assistant do it after the reader confirms. A button that runs a destructive tool directly runs it by accident.

Who runs what

Every call comes from the viewer's browser and runs as the person looking at the interface, not as its author. Access to the tool is checked for them, and a tool they may not use fails with an error shown above the interface.

The server also checks each call against the artifact. It accepts the tool and function that the document names, a script that is a literal string in the document, and arguments where a literal value is unchanged and a value bound with @path may be whatever the reader chose. It rejects a key the document did not write. A script therefore cannot be built from what a reader types, and publish refuses a script or function that is bound to data.

Do not put a key or a secret in the document. Everyone who can open the artifact can read it.

Files

"files": { "sales": "/work/sales.csv" } publishes sandbox files with the interface, up to 24 files of 20 MB each. The file name may use letters, digits, ., - and _. Anyone who can open the interface can read its files, which appear in the data model:

Path Holds
/files/<name>/url Where to load it from, for an Image, Video or AudioPlayer.
/files/<name>/text The text of a text file up to 2 MB.
/files/<name>/json The parsed value of a .json file.
/files/<name>/rows An array of objects keyed by the header, for .csv and .tsv.
/files/<name>/type, /size The content type and the size in bytes.

Revising an interface

Writing the same file name and publishing again replaces the interface in place and keeps its link. When an event arrives, the assistant answers it and, if the interface should change, rewrites the file and publishes again. The surface id stays the same.

Sharing

The owner shares a published interface from their Artifacts space. A person it is shared with can open and use it, with the tools running as them. They cannot change it or delete it, and what they do in it does not reach the owner's conversation, because they have no seat in it. An interface meant for other people therefore puts its work in tools and sandboxes that each person can run, not in events.

Check it before publishing

render people.a2ui draws a published interface with its data loaded and returns the pictures. See Look at it before publishing.