Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1f8d8b9
Define the Dart a2ui_agent API and its tests, limited to protocol v0.9
polina-c Aug 26, 2026
95f1d78
Fix CI: format with the SDK CI uses, drop non-portable conformance cases
polina-c Aug 26, 2026
3445073
Stop DataModel notifying observers whose value did not change
polina-c Aug 27, 2026
d9cb4a8
-
polina-c Aug 27, 2026
5fdcf5b
Create message-processor.conformance.test.ts
polina-c Aug 27, 2026
acd980d
Merge branch 'main' into dart-a2ui-agent-api-and-tests
polina-c Aug 27, 2026
589c824
-
polina-c Aug 27, 2026
8e88169
Merge remote-tracking branch 'refs/remotes/origin/dart-a2ui-agent-api…
polina-c Aug 27, 2026
b0d68de
-
polina-c Aug 27, 2026
992db3a
Update pruning.dart
polina-c Aug 27, 2026
f427071
-
polina-c Aug 27, 2026
f496522
Update minimal_snippet.dart
polina-c Aug 27, 2026
410199f
-
polina-c Aug 27, 2026
478867c
Update README.md
polina-c Aug 27, 2026
3b75ebf
Merge branch 'main' into dart-a2ui-agent-api-and-tests
polina-c Aug 27, 2026
417e18a
move tests to web_core/tests/conformance/data-model.conformance.test.ts
polina-c Aug 27, 2026
9c21cb5
Merge remote-tracking branch 'refs/remotes/origin/dart-a2ui-agent-api…
polina-c Aug 27, 2026
29f79da
Merge branch 'main' into dart-a2ui-agent-api-and-tests
polina-c Aug 27, 2026
84ef633
add missing implementation to a2ui_core
polina-c Aug 27, 2026
433c6cd
Merge remote-tracking branch 'refs/remotes/origin/dart-a2ui-agent-api…
polina-c Aug 27, 2026
54938de
-
polina-c Aug 28, 2026
079fb2c
Restore message processor conformance in the v1_0 case shape
polina-c Aug 28, 2026
18a692c
-
polina-c Aug 28, 2026
21149cd
Extend a2ui_core for agent SDKs, limited to protocol v0.9
polina-c Aug 28, 2026
39be96a
Merge dart-a2ui-core: a2ui_core prerequisites for the agent SDK (#2439)
polina-c Aug 28, 2026
1d7c91e
Correct the claim that only agents narrow catalogs
polina-c Aug 28, 2026
562329b
Drop a vague qualifier and fix formatting
polina-c Aug 28, 2026
d417269
Merge dart-a2ui-core: catalog-narrowing correction (#2439)
polina-c Aug 28, 2026
f447155
Correct the claim that only agents narrow catalogs (agent side)
polina-c Aug 28, 2026
01bb545
Itemise the core non-goals instead of explaining them
polina-c Aug 28, 2026
7dc087a
Merge dart-a2ui-core: itemised core non-goals (#2439)
polina-c Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/flutter_packages_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Generate testing matrix
id: generate_matrix
run: |
DIRS_TO_TEST=$(find samples -name pubspec.yaml -not -path "*/.dart_tool/*" -not -path "*/e2e_test/*" -exec dirname {} \;)
DIRS_TO_TEST=$(find samples dart -name pubspec.yaml -not -path "*/.dart_tool/*" -not -path "*/e2e_test/*" -exec dirname {} \;)

JSON_MATRIX="["
FIRST=true
Expand Down
13 changes: 11 additions & 2 deletions agent_sdks/python/a2ui_agent/tests/conformance/test_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,11 @@ def test_validator_conformance(name, test_case):


# --- Catalog Conformance ---
cases_catalog = get_conformance_cases("core/catalog.yaml")
# `agent/catalog_transformer.yaml` holds the pruning cases that belong to the
# agent SDK rather than to core; both suites drive the same `prune` action.
cases_catalog = get_conformance_cases("core/catalog.yaml") + get_conformance_cases(
"agent/catalog_transformer.yaml"
)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -329,7 +333,12 @@ def test_catalog_conformance(name, test_case):


# --- Schema Manager Conformance ---
cases_schema_manager = get_conformance_cases("agent/inference_format.yaml")
# `agent/catalog_provider.yaml` holds the catalog loading cases, which used to
# sit in `inference_format.yaml`; both suites drive the same `load_catalog`
# action.
cases_schema_manager = get_conformance_cases(
"agent/inference_format.yaml"
) + get_conformance_cases("agent/catalog_provider.yaml")


@pytest.mark.parametrize(
Expand Down
144 changes: 119 additions & 25 deletions blueprints/modules/a2ui_agent.blueprint.md

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions blueprints/modules/a2ui_core.blueprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ Its core responsibilities include:
7. **Resolution:** Resolves bound context paths and binds state variables to components for local evaluation.
8. **Multi-Version Protocol Branching:** Supports multiple versions of the protocol.

### Package Boundary & Non-Goals

`a2ui_agent` depends on `a2ui_core`; the dependency never runs the other way. A change here made to unblock an agent SDK should be the smallest one that works, and land as its own reviewable unit.

Not in core — specified in the [a2ui_agent blueprint](./a2ui_agent.blueprint.md), with conformance data under `conformance/agent/`:

- `CatalogTransformer`, `ComponentPruningTransformer`, `FunctionPruningTransformer`, and the `CatalogConfig` pipeline that applies them
- Prompt generation
- Response parsing
- Capability negotiation

---

### A. High-Level Layer Architecture
Expand Down Expand Up @@ -193,6 +204,12 @@ export interface Catalog<TComponent extends ComponentApi, TFunction extends Func
}
```

A `Catalog` is immutable once constructed. A renderer or an agent that needs a narrower contract — an agent prompting against a subset, a renderer serving a use case that calls for a smaller catalog — derives a new catalog from it rather than mutating it in place. Core provides the derivation (`copyWith`, `catalogSchema`); the named transformer rules that drive it are agent SDK surface, see below.

`protocolVersion` is a property of **each catalog, not of the process**. A renderer or an agent may hold catalogs for several protocol versions at once, so anything derived from a set of catalogs, including a capabilities payload, must be grouped by each catalog's own `protocolVersion`. Reaching for a single constant because today's set happens to be uniform produces an object that is wrong the first time it is not.

Parsing a catalog document is parsing untrusted input: raise `A2uiCatalogError` for a missing or non-object document or a `catalogId` conflict, and `A2uiValidationError` for an unsupported protocol version.

#### `ComponentApi`

The framework-agnostic definition of a component. It defines the name and the exact JSON schema footprint of the component, without any rendering logic. It acts as the single source of truth for the component's contract.
Expand Down Expand Up @@ -370,6 +387,21 @@ When a surface is created with `sendDataModel: true`, the renderer is responsibl
- **Surface Lifecycle**: It is an error to receive a `createSurface` message for a `surfaceId` that is already active; `surfaceId` must be globally unique per client session. The processor MUST throw an error or report a validation failure if this occurs.
- **Component Lifecycle**: If an `updateComponents` message provides an existing `id` but a _different_ `type`, the processor MUST remove the old component and create a fresh one to ensure framework renderers correctly reset their internal state.

#### Capabilities Objects

Both sides of the handshake advertise a capabilities object, and **both have a normative JSON Schema**. Build and parse them from that schema rather than from memory or from a neighbouring implementation:

| Direction | Object | Normative schema (v0.9, v0.9.1) | Renamed in v1.0 |
| :--------------- | :----------------------- | :------------------------------------------------------ | :--------------------------------------------------- |
| Renderer → agent | `a2uiClientCapabilities` | `specification/<version>/json/client_capabilities.json` | `specification/v1_0/json/renderer_capabilities.json` |
| Agent → renderer | `a2uiServerCapabilities` | `specification/<version>/json/server_capabilities.json` | `specification/v1_0/json/agent_capabilities.json` |

Resolve the filename for the version you are implementing rather than assuming one — v0.8 uses a third spelling (`a2ui_client_capabilities_schema.json`) and publishes no server-side counterpart.

The top level of each is **keyed by protocol version** — `{"v0.9": { … }}`, `{"v1.0": { … }}` — with `supportedCatalogIds`, `inlineCatalogs` and `acceptsInlineCatalogs` living _inside_ the version entry, and the version key is required. A flat object carrying a sibling list of version strings is a different, invalid shape.

When parsing one: reject an object that declares no entry for any version this SDK implements, and keep the unrecognised version keys rather than failing on them, so a renderer that also speaks a newer protocol can still be served.

#### Generating Renderer Capabilities and Schema Types

To dynamically generate the `A2uiRendererCapabilities` payload (specifically `inlineCatalogs`), the processor must convert internal component schemas into valid JSON Schemas.
Expand Down Expand Up @@ -751,4 +783,50 @@ export class A2uiRecursionError extends A2uiError {
this.name = 'A2uiRecursionError';
}
}

/** Raised when a data model read or write cannot be satisfied at the given path. */
export class A2uiDataError extends A2uiError {
/* name = 'A2uiDataError' */
}

/** Raised when the surface or component state machine is asked for an illegal transition. */
export class A2uiStateError extends A2uiError {
/* name = 'A2uiStateError' */
}

/** Raised when raw model output cannot be extracted or decoded into a payload. */
export class A2uiParseError extends A2uiError {
/* name = 'A2uiParseError' */
}

/** Raised when a source syntax (e.g. the EXPRESS DSL) cannot be compiled to A2UI messages. */
export class A2uiCompileError extends A2uiError {
/* name = 'A2uiCompileError' */
}

/** Raised when a bound expression cannot be evaluated. */
export class A2uiExpressionError extends A2uiError {
/* name = 'A2uiExpressionError' */
}
```

**Parsing wire JSON raises from this hierarchy, never from the language.** Every entry point that accepts a payload, a capabilities object or a catalog document from outside the process is handling untrusted input: a member may be absent, null, or the wrong type. Check the shape before casting and raise `A2uiValidationError` with the offending value attached. A raw cast failure — `TypeError`, `ClassCastException`, a null dereference — escapes the hierarchy a caller can catch, and turns a malformed message into a crash.

**These categories are shared with the conformance suite.** `expect_error.category` in `conformance/conformance_schema.json` names these classes without the `A2ui` prefix, so the two must agree. Adding an error class means adding the matching category alongside the first suite that asserts it — not in advance, and not silently under a category that already exists.

---

## 4. Conformance Test Plan

Behavioural parity across implementations is pinned by a language-agnostic conformance suite. For setup, harness requirements and schema definitions, see [Conformance README](../../conformance/README.md).

### Where a case belongs

Suites under `conformance/core/` cover this module: the reactive data model, the message processor's state machine, catalog documents, and the validator. Agent-side behaviour — prompt generation, response parsing, catalog narrowing, capability negotiation — belongs under `conformance/agent/`, **even when the case is about a catalog**. A case filed under `core/` obliges every renderer to implement it, so the directory is a statement about ownership, not about subject matter. Section 6 of the [a2ui_agent blueprint](./a2ui_agent.blueprint.md#6-conformance-test-plan) carries the agent-side map.

### Rules for adding cases

1. **Look for an existing suite before creating one — on every active spec branch, not just the one you are on.** The repository maintains parallel branches per protocol version (`main`, `v1_0`, …). A suite that already exists elsewhere has an established case shape; a second file with the same name and a different shape is a merge conflict rather than extra coverage. Extend the existing one.
2. **Reuse the established case keys** defined by `conformance/conformance_schema.json`, and extend the schema in the same change as the suite that needs the new shape.
3. **`expect_error.category` names a class from the exception hierarchy above**, without the `A2ui` prefix.
4. **Migrating an implementation's own tests into the shared suite will surface real disagreements** between implementations. Each one is a decision, not a formatting problem: fix the implementation that is wrong, or record why the case is excluded. Language-level differences (sparse versus dense arrays, `null` versus absent, prototype pollution) stay out of the shared suite and remain in the implementation's own tests.
6 changes: 6 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ Test suites are organized by functional domain:
- `core/catalog.yaml`: Contains test cases for catalog operations (prune, render, load).
- `core/accessibility.yaml`: Contains test cases for accessibility attributes and checks.
- `core/validator.yaml`: Contains test cases for schema and structural validators, verifying structural integrity, cycle detection, and reachability.
- `core/data_model.yaml`: Contains test cases for the reactive data model, verifying JSON Pointer reads and writes, container creation, deletion, and observer notification.
- `core/message_processor.yaml`: Contains test cases for the message processor's state machine. Written in the case vocabulary of the `v1_0` branch, whose suite of the same name is the primary one, so the two converge rather than conflict.

### Agent (`agent/`)

- `agent/streaming_parser.yaml`: Contains test cases for streaming parser implementations, verifying chunk buffering, incremental yielding, and edge cases like cut tokens.
- `agent/parser.yaml`: Contains test cases for non-streaming parsing and payload fixing.
- `agent/inference_format.yaml`: Contains test cases for inference formats and schema managers (select_catalog, load_catalog, generate_prompt).
- `agent/catalog_provider.yaml`: Contains test cases for loading a catalog document into a catalog, whatever backing store it came from.
- `agent/catalog_transformer.yaml`: Contains test cases for narrowing a catalog before prompting, including the `$defs` unions that reference the pruned entries.
- `agent/catalog_resolver.yaml`: Contains test cases for `resolve_catalogs`, which negotiates renderer capabilities against the catalogs an agent registered. It supersedes the legacy single-catalog `select_catalog` helper.
- `agent/request_processor.yaml`: Contains test cases for a whole agent turn: negotiate catalogs, render the prompt snippet, parse the model response.

### Extensions (`extensions/`)

Expand Down
33 changes: 33 additions & 0 deletions conformance/agent/catalog_provider.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Behaviour of the catalog providers described by
# `blueprints/modules/a2ui_agent.blueprint.md` section 3.F: turning a catalog
# document into a `Catalog`, whatever backing store it came from. Prompt
# rendering and capability negotiation are covered by `inference_format.yaml`
# and `catalog_resolver.yaml` respectively.
#
# These cases load the published basic catalog schema from
# `specification/v0_9_1/catalogs/basic/catalog.json` rather than any SDK's own
# catalog, so implementations are measured against the same document. Paths are
# resolved relative to the `conformance/` directory.

- name: test_load_basic_catalog_v0_9
description: Loads the published v0.9 basic catalog document from disk.
action: load_catalog
catalog_configs:
- name: basic
path: "../specification/v0_9_1/catalogs/basic/catalog.json"
expect:
supported_catalog_ids: ["https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json"]
Loading
Loading