Skip to main content

UI Specifications

The interface of an application is made of UI plugins: Reactor plugins of Agent Runtimes β€” the chat, the composer, the notebook and its toolbar, the document and its toolbar, the page an application draws, its rules and approvals, its activity, the Sign in at a company card, the page layout… β€” the ones the Loop editor assembles. A UI spec is one of them: one YAML per plugin under agentspecs/ui/.

A UI renderer is something else: how a plugin's interface reaches a host that is not Datalayer's own page β€” A2UI, MCP Apps, MCP UI. Renderers are their own specs, under agentspecs/ui-renderers/ (Renderers). Each UI plugin says the renderers it may be exposed through, and an agent or an application names its UI plugins and, per plugin, the renderer. A renderer listed as a UI β€” the old shape, ui: a2ui β€” is refused, with no compatibility reading.

A plugin's Slots, Contributions, Commands and Contribution Points are its registration in Agent Runtimes, read from it: a test of Agent Runtimes (src/apps/__tests__/ui-specs.test.ts) builds each plugin on a reactor and holds the spec and the registration to each other, both ways β€” and holds every plugin folder of src/apps/plugins/ to a UI spec, or to the reason it has none.

The plugin that draws A2UI surfaces (a2ui-surface) hosts the catalog of visual components: the 18 components of A2UI's v0.9 basic catalog and Datalayer's own. The catalog is one for the three editors of an application β€” the Appspec, the Canvas and Python β€” and a component the catalog does not have is reached by none of them. Every component, with its properties, bindings, events and an example, is listed in the catalog of visual components, generated from the plugins' files.

Fields​

FieldMeaning
idWhat an agent's or an application's ui names: chat, notebook, a2ui-surface. The file is named for it.
versionThe spec's version.
nameWhat a Maker reads.
descriptionWhat it is, in a Maker's words.
emoji, octiconIts faces.
pluginThe name the Reactor plugin registers under: @datalayer/loop-plugin-chat.
folderThe plugin's folder in Agent Runtimes, under src/apps/plugins/.
perFor a plugin registered once per something, application, source or ui: it registers as <plugin>-<its id>.
contributionsIts Contributions: the slots it fills, the Contribution Points it contributes to (points), and the commands it adds β€” Reactor Commands by id, the composer's / commands by name.
contribution_pointsThe Contribution Points it offers to other plugins.
settingsIts settings, as the JSON Schema of an object; each default is the plugin's.
renderersThe UI renderers it may be exposed through, ids of agentspecs/ui-renderers; any other is refused.
catalog, componentsFor a plugin that renders components: the catalog they are written in (a2ui/v0.9) and the components; see below.
# agentspecs/ui/app-rules.yaml
id: app-rules
version: 0.0.1
name: Rules and approvals
description: >-
When your application acts alone, when it asks you, and the approvals waiting for your yes.
emoji: "πŸ›‘οΈ"
octicon: shield-check
plugin: "@datalayer/loop-plugin-app-rules"
folder: app-rules
per: application
contributions:
slots: [loop.sidebar]
points: []
commands: []
contribution_points: []
renderers: []

The catalogue, with each plugin's registration and renderers, is listed on the components page.

A component​

Each entry of a2ui-surface's components:

FieldMeaning
idThe name a surface gives it, as the protocol names it: Text, ChoicePicker, Table.
nameWhat a builder reads on the palette: Select for ChoicePicker.
categorytext, input, action, data, conversation, media or layout.
emojiIts face on the palette.
descriptionWhat it is for, in a sentence.
standardtrue for a component of the protocol's own catalog: its properties are the protocol's, and not copied here.
propertiesFor Datalayer's own: its properties as a JSON Schema (an object; each property with a title, a description, its type, and its default, bounds and choices when it has them). The Canvas draws the component's properties form from it, with @datalayer/primer-rjsf.
bindingsFor Datalayer's own: what it can be bound to β€” what it shows (the rows of a table, the points of a chart) and what it sends (the row selected, the files given).
eventsWhat it reports: select, upload, send, open, submit.
exampleA configuration of it, valid against its properties.
# agentspecs/ui/a2ui-surface.yaml (one of Datalayer's own)
- id: Table
name: Table
category: data
emoji: πŸ“‹
description: Rows the application found or keeps, with the columns the builder chooses.
standard: false
properties:
type: object
required: [columns]
properties:
columns: { type: array, title: Columns, description: The columns, in order., minItems: 1, items: { type: string } }
page_size: { type: integer, title: Rows per page, description: How many rows show at once., minimum: 1, maximum: 500, default: 20 }
bindings:
shows: [rows]
sends: [selected]
events: [select]
example:
columns: [model, pass rate, cost]

The tests (tests/test_ui_components.py) hold that A2UI's standard components are exactly its basic catalog, that each component says what it is, that each of Datalayer's own has a valid JSON Schema whose every property is titled and described, bindings and events, and an example its schema accepts β€” and that the catalog's page is the one python -m agentspecs.ui writes.

In an agent spec, and in an Appspec​

ui:
- plugin: a2ui-surface
renderer: a2ui
- plugin: chat

Each entry names a UI plugin, once, and the renderer it is exposed through when it is one: one the plugin's renderers lists, and β€” for an application that is offered β€” an enabled one. agentspecs.ui.ui_choices_problems says what is wrong, in sentences:

from agentspecs.ui import ui_choices_problems

ui_choices_problems(["a2ui"], to_ship=True)
# ["`ui` lists UI plugins, each as `{plugin, renderer}`: 'a2ui' is not one ('a2ui' is a UI renderer: …)."]

In an Appspec: how a surface names a component​

An application's interface lists the components its surface may use, and its surface is a tree of them, written as A2UI's updateComponents carries them: each node has an id and names its component by the catalog's id, and the tree starts from the node whose id is root. A node's other keys are the component's properties.

# agentspecs/apps/quote-calculator.yaml (abridged)
interface:
layout: page
accent: sun
components: [Card, Column, Text, Slider, Button]
surface:
protocol: a2ui/v0.9
components:
- id: root
component: Column
children: [title, seats, run]
- id: title
component: Text
text: Quote
variant: h2
- id: seats
component: Slider
label: Seats
value: { path: /inputs/seats }
min: 1
max: 1000
- id: run
component: Button
child: run-label
variant: primary
action: { event: { name: run } }
- id: run-label
component: Text
text: Compute the quote

app_problems (agentspecs.apps) refuses an application that names a component the catalog does not have β€” in interface.components, or as the component of a node of its surface β€” in a sentence each:

from agentspecs.apps import app_problems, load_app

app_problems(load_app("apps/my-app.yaml"))
# ["There is no component named 'Gauge' in the catalog.",
# "The surface's 'speed' is a 'Gauge', which the catalog does not have."]

A component is in the catalog when a UI plugin renders it (agentspecs.apps.component_named). What is not checked yet: a node's properties against its component's JSON Schema, and a version per component.

In Python and TypeScript​

Agent Runtimes generates both kinds into its catalogues with make specs: UI_CATALOGUE (agent_runtimes.specs.ui, src/specs/ui.ts) and UI_RENDERER_CATALOGUE (agent_runtimes.specs.ui_renderers, src/specs/uiRenderers.ts), and COMPONENT_CATALOGUE with get_component / list_components in Python and getComponent / listComponents in TypeScript β€” the components of the UI plugins, by the name a surface gives them.