# Serving an agent

`a2ui_agent` is the agent's side of A2UI. The agent declares the catalogs it
can write for and agrees with each renderer on the ones they share. It puts
those catalogs' schemas in the model's prompt and reads the model's output
back as messages a renderer will accept. `A2UIAgent.A2A.Plug` serves the
result over A2A. Each example continues from the one before it.

## Catalogs

An agent registers each catalog it can write for as an
`A2UIAgent.CatalogConfig`. Transformers narrow a catalog to what the agent
needs. A smaller catalog makes a shorter prompt and gives the model fewer
ways to go wrong:

```elixir
alias A2UIAgent.{CatalogConfig, CatalogTransformer, Generator, RequestProcessor}

basic = A2UICore.Catalog.Basic.catalog("v1.0")

config =
  CatalogConfig.new(basic,
    transformers: [
      CatalogTransformer.component_pruning(["Column", "Text", "TextField", "Button"])
    ]
  )

config.transformed.components |> Map.keys() |> Enum.sort()
#=> ["Button", "Column", "Text", "TextField"]
```

`A2UIAgent.CatalogConfig.from_path/2` loads a catalog from a JSON file
instead. The MCP catalog registers like any other, for surfaces that call MCP
tools or embed MCP Apps:

```elixir
mcp = CatalogConfig.new(A2UICore.Catalog.MCP.declared("v1.0"))

Map.keys(mcp.transformed.components)
#=> ["McpApp"]
```

A `A2UIAgent.Generator` holds the registered catalogs and the few-shot
examples for the prompt. An example is a list of messages, and it must be
valid in the agent's catalogs:

```elixir
example = [
  %{
    "version" => "v1.0",
    "createSurface" => %{"surfaceId" => "hello", "catalogId" => basic.catalog_id}
  },
  %{
    "version" => "v1.0",
    "updateComponents" => %{
      "surfaceId" => "hello",
      "components" => [%{"id" => "root", "component" => "Text", "text" => "Hello!"}]
    }
  }
]

generator = Generator.new([config], examples: [example])
```

Build the generator once, when the agent starts.

## Each request

A renderer says which catalogs it supports, keyed by protocol version.
`A2UIAgent.Generator.create_processor/3` keeps the agent's catalogs that the
renderer names and returns an `A2UIAgent.RequestProcessor` for them:

```elixir
capabilities = %{"v1.0" => %{"supportedCatalogIds" => [basic.catalog_id]}}
{:ok, processor} = Generator.create_processor(generator, capabilities)
```

When the renderer names none of the agent's catalogs, there is nothing to
write for. The agent should answer in text:

```elixir
{:error, error} =
  Generator.create_processor(generator, %{
    "v1.0" => %{"supportedCatalogIds" => ["https://example.com/catalogs/other.json"]}
  })

error.category
#=> :catalog
```

A renderer can also send catalogs inline, under `"inlineCatalogs"`. The
generator uses them only when it was built with
`accepts_inline_catalogs: true`.

## The prompt

`A2UIAgent.RequestProcessor.prompt/1` returns the part of the system prompt
that teaches the model A2UI. It has the rules of the output format, the JSON
schemas of the negotiated catalogs, and the examples:

```elixir
{:ok, snippet} = RequestProcessor.prompt(processor)

snippet |> String.split("\n") |> hd()
#=> "## Workflow Description:"
```

Add the rest of the system prompt yourself: who the agent is and what it does.

## Reading the model's output

The model writes direct JSON: ordinary text, with lists of A2UI messages
between `<a2ui-json>` and `</a2ui-json>` tags.
`A2UIAgent.RequestProcessor.parse_response/3` splits a complete response into
text and messages, and checks the messages the way a renderer holding the
negotiated catalogs would:

```elixir
model_output = """
Here is a form.
<a2ui-json>
[
  {"version": "v1.0",
   "createSurface": {"surfaceId": "form",
                     "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}},
  {"version": "v1.0",
   "updateComponents": {"surfaceId": "form", "components": [
     {"id": "root", "component": "Column", "children": ["name"]},
     {"id": "name", "component": "TextField", "label": "Name", "value": {"@path": "/name"}}
   ]}}
]
</a2ui-json>
"""

{:ok, [{:text, text}, {:a2ui, messages}]} = RequestProcessor.parse_response(processor, model_output)

text
#=> "Here is a form."

Enum.map(messages, &(&1 |> Map.delete("version") |> Map.keys()))
#=> [["createSurface"], ["updateComponents"]]
```

The reader forgives small JSON mistakes models make, such as trailing commas,
raw line breaks in strings, curly quotes and a Markdown code fence around a
block. It does not accept messages a renderer would reject. A component the
transformers pruned away, for example, is an error:

```elixir
{:error, error} =
  RequestProcessor.parse_response(processor, """
  <a2ui-json>
  [{"version": "v1.0",
    "createSurface": {"surfaceId": "pic",
                      "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}},
   {"version": "v1.0",
    "updateComponents": {"surfaceId": "pic", "components": [
      {"id": "root", "component": "Image", "url": "https://example.com/cat.png"}]}}]
  </a2ui-json>
  """)

error.category
#=> :validation
```

The error message says what is wrong. Giving it back to the model is a good
way to get a corrected response.

### Streaming

A model's response usually arrives in chunks. The parser that
`A2UIAgent.RequestProcessor.parser/1` returns reads one chunk at a time with
`A2UIAgent.DirectJson.Parser.parse_chunk/3`. It emits text as it arrives, and
each message as soon as it is valid:

```elixir
alias A2UIAgent.DirectJson.Parser

chunks = Regex.split(~r/(?<=\n)/, model_output)

{_parser, parts} =
  Enum.reduce(chunks, {RequestProcessor.parser(processor), []}, fn chunk, {parser, parts} ->
    {:ok, parser, new_parts} = Parser.parse_chunk(parser, chunk)
    {parser, parts ++ new_parts}
  end)

summary =
  Enum.map(parts, fn
    {:text, text} ->
      text

    {:a2ui, [%{"createSurface" => %{"surfaceId" => id}}]} ->
      {:create, id}

    {:a2ui, [%{"updateComponents" => %{"components" => components}}]} ->
      {:components, Enum.map(components, & &1["id"])}
  end)

summary
#=> ["Here is a form.", {:create, "form"}, {:components, ["root"]}, {:components, ["root", "name"]}]
```

A message can be emitted again as it grows. Here the `updateComponents`
message comes out first with the components read so far, then again when the
next one arrives, so the renderer can draw the surface while the model is
still writing it. A `createSurface` is held back until it is complete, and so
is every message after it. When a block closes, it is checked as a whole, so
a block that turns out to be invalid fails at that point.

## Serving over A2A

`A2UIAgent.A2A.Plug` serves an agent over the JSON-RPC binding of
[A2A](https://a2a-protocol.org). The agent is a module implementing
`A2UIAgent.A2A.Agent`:

```elixir
defmodule GreetingAgent do
  @behaviour A2UIAgent.A2A.Agent

  alias A2UIAgent.A2A.Request

  # No catalog negotiated: answer in text.
  @impl true
  def run(%Request{processor: nil, text: text}), do: {:ok, "Hello, #{text}!"}

  # `events` holds the renderer's messages, such as the actions of its users.
  def run(%Request{processor: processor, text: text, events: _events}) do
    {:ok, prompt} = A2UIAgent.RequestProcessor.prompt(processor)
    {:ok, call_model(prompt, text)}
  end

  @impl true
  def options do
    basic = A2UIAgent.CatalogConfig.new(A2UICore.Catalog.Basic.catalog("v1.0"))
    [generator: A2UIAgent.Generator.new([basic])]
  end

  @impl true
  def card do
    %{
      "name" => "Greeting agent",
      "capabilities" => %{"extensions" => [A2UIAgent.A2A.agent_extension("1.0")]}
    }
  end

  # Stands in for the model, which gets the prompt and the user's text.
  defp call_model(_prompt, text) do
    """
    Hello!
    <a2ui-json>
    [{"version": "v1.0",
      "createSurface": {"surfaceId": "hello",
                        "catalogId": "https://a2ui.org/specification/v1_0/catalogs/basic/catalog.json"}},
     {"version": "v1.0",
      "updateComponents": {"surfaceId": "hello", "components": [
        {"id": "root", "component": "Text", "text": "Hello, #{text}!"}]}}]
    </a2ui-json>
    """
  end
end
```

`run/1` gets an `A2UIAgent.A2A.Request`. When the renderer's capabilities
match a catalog of the generator, `processor` is the request processor for
them. Otherwise it is `nil`, and the answer is text. The model's output can
be a string, or an enumerable of chunks as the model streams them.

Mount the plug in a Phoenix router or a `Plug.Router`. It needs the optional
`plug` dependency, which Phoenix applications already have:

```elixir
forward "/a2a", A2UIAgent.A2A.Plug, agent: GreetingAgent
```

`POST` answers JSON-RPC: `message/send` with the finished task, and
`message/stream` with server-sent events as the output arrives. `GET` serves
the agent card. The renderer sends its capabilities in the message metadata,
as `a2uiRendererCapabilities` (`a2uiClientCapabilities` in protocols v0.9
and v0.9.1).
The answer has a text part for each run of text and a data part for each
A2UI message.

`A2UIAgent.A2A.Server.handle/3` does the same work without HTTP. It is
convenient in tests:

```elixir
body = %{
  "jsonrpc" => "2.0",
  "id" => 1,
  "method" => "message/send",
  "params" => %{
    "message" => %{
      "kind" => "message",
      "messageId" => "m1",
      "role" => "user",
      "parts" => [%{"kind" => "text", "text" => "Ada"}],
      "metadata" => %{
        "a2uiRendererCapabilities" => %{
          "v1.0" => %{"supportedCatalogIds" => [basic.catalog_id]}
        }
      }
    }
  }
}

{:reply, response, _extensions} =
  A2UIAgent.A2A.Server.handle(body, GreetingAgent, GreetingAgent.options())

parts = response["result"]["status"]["message"]["parts"]
Enum.map(parts, & &1["kind"])
#=> ["text", "data", "data"]

A2UIAgent.A2A.a2ui_messages(parts) |> List.last() |> get_in(["updateComponents", "components"])
#=> [%{"id" => "root", "component" => "Text", "text" => "Hello, Ada!"}]
```

`A2UIAgent.A2A` also has the helpers for building and reading A2UI parts and
for negotiating the A2UI extension.

## MCP and AG-UI

`A2UIAgent.MCP` gives the values an MCP server puts in its responses, as the
A2UI project's guide to A2UI over MCP describes them. A tool call returns
A2UI messages in an embedded resource with the MIME type
`application/a2ui+json`, after a text fallback:

```elixir
result = A2UIAgent.MCP.tool_result(messages, uri: "a2ui://form", text: "Here is a form.")

Enum.map(result["content"], & &1["type"])
#=> ["text", "resource"]

A2UIAgent.MCP.a2ui_messages(result) == {:ok, messages}
#=> true
```

The client states its renderer capabilities in `initialize`, or in the
`_meta` of each tool call. `A2UIAgent.MCP.renderer_capabilities/1` reads
them for the generator:

```elixir
params = %{"capabilities" => %{"a2ui" => %{"clientCapabilities" => capabilities}}}

{:ok, _processor} =
  Generator.create_processor(generator, A2UIAgent.MCP.renderer_capabilities(params))
```

Users' actions come back as calls of the `a2ui_action` tool, which
`A2UIAgent.MCP.action_tool/0` defines and `A2UIAgent.MCP.action/2` reads.

`A2UIAgent.AGUI` does the same for AG-UI, following the conventions of
AG-UI's A2UI middleware. A surface is an activity whose snapshots carry all
its messages so far, so the agent keeps them per surface:

```elixir
{events, _surfaces} = A2UIAgent.AGUI.snapshots(%{}, messages)

Enum.map(events, &{&1["type"], &1["messageId"]})
#=> [{"ACTIVITY_SNAPSHOT", "a2ui-surface-form"}]
```

`A2UIAgent.AGUI.encode/1` writes an event as AG-UI's HTTP binding sends it,
and `A2UIAgent.AGUI.user_action/2` reads the action a run starts with.

## Express

Direct JSON is the default format. In Express, the other built-in format,
the model writes each component as a call whose arguments come in the order
its catalog schema declares them, and the agent compiles the result into
messages. The model's output is a fraction of the size of the JSON it
stands for.

A generator writes Express when its format is `A2UIAgent.Express`: for every
request with `Generator.new(configs, format: A2UIAgent.Express)`, or for one
request with the `:format` option of `create_processor/3`:

```elixir
{:ok, processor} = Generator.create_processor(generator, capabilities, format: A2UIAgent.Express)

{:ok, snippet} = RequestProcessor.prompt(processor)

snippet |> String.split("\n") |> Enum.at(1)
#=> "# A2UI Express DSL Output Contract"
```

The prompt teaches the notation and gives the signature of each component
and function of the negotiated catalogs, such as
`TextField(label, value?, placeholder?, variant? (static), weight? (static), checks? (static))`.
The generator's examples are written in Express too.

The model writes Express between `<a2ui>` and `</a2ui>` tags:

```elixir
model_output = """
Here is a form.
<a2ui>
surface("form")
root = Column([name, send])
name = TextField("Name", $/name)
send = Button(Text("Send"), action=Event("send", {name: $/name}))
</a2ui>
"""

{:ok, [{:text, text}, {:a2ui, [message]}]} = RequestProcessor.parse_response(processor, model_output)

text
#=> "Here is a form."

Enum.map(message["createSurface"]["components"], &{&1["id"], &1["component"]})
#=> [{"root", "Column"}, {"name", "TextField"}, {"send", "Button"}, {"send_child", "Text"}]
```

`surface("form")` starts the surface `form`, and each variable names a
component. `$/name` binds a value to the data model, and `Event` makes an
action. A component written inside another gets an id made from its
parent's: the button's label is `send_child`. In protocol v1.0 the
components of a new surface come inline in its `createSurface` message.
The messages are checked against the catalogs as direct JSON ones are, and
errors have the same categories.

Express is read once the response is complete: its parser has no
`parse_chunk/3`. Served over A2A, an Express agent sends its answer when the
model has finished, also for `message/stream`.

`A2UIAgent.Express.Parser.decompile/2` writes messages back as Express, so
that earlier turns can go into the model's history in the notation it
writes:

```elixir
{:ok, express} = A2UIAgent.Express.Parser.decompile(RequestProcessor.parser(processor), message)
```

`express` holds:

```text
surface("form")
root = Column([name, send])
name = TextField("Name", $/name)
send = Button(send_child, _, Event("send", {name: $/name}))
send_child = Text("Send")
```

An update to a surface created in an earlier turn carries no catalog id. With
several catalogs, pass the catalog of each such surface:
`decompile(parser, messages, surface_catalogs: %{"form" => catalog_id})`.

Express reads arguments in the order the catalog document declares them, so
a catalog must be read with its key order kept. `A2UICore.Catalog.Basic`,
`A2UIAgent.CatalogConfig.from_path/2` and `A2UIAgent.CatalogProvider` keep
it. A catalog built from a map, such as the result of a plain
`Jason.decode/1`, has its properties ordered by name; decode with
`objects: :ordered_objects` instead.

### Skills

An Express prompt can also be packaged as skills: markdown files that an
agent framework loads when a task needs them.
`A2UIAgent.SkillGenerator.skill_set/2` makes a core skill with the rules of
the notation and one skill for each catalog:

```elixir
{:ok, format} = A2UIAgent.Express.new([basic])
{:ok, skills} = A2UIAgent.SkillGenerator.skill_set(format)

Enum.map(skills, & &1.filename)
#=> ["a2ui-core/SKILL.md", "a2ui-basic/SKILL.md"]
```

`A2UIAgent.SkillGenerator.skill/2` makes one skill with the whole prompt
instead. `A2UIAgent.Skill.to_markdown/1` gives a skill's file, and
`A2UIAgent.Skill.export/2` writes skills into a directory.

## Building components in Elixir

Not every component has to come from the model. `A2UIAgent.Builder` builds
a component tree in Elixir and flattens it into the component list an
`updateComponents` message carries:

```elixir
import A2UIAgent.Builder
import A2UIAgent.Builder.Basic

tree =
  card(
    id: "profile",
    child:
      column(
        children: [
          text(text: bind("/user/name"), variant: "caption"),
          button(child: text(text: "Edit"), action: event("edit_profile"))
        ]
      )
  )

components = flatten(tree, catalog: basic)

Enum.map(components, &{&1["id"], &1["component"]})
#=> [{"profile__text_1", "Text"}, {"profile__text_2", "Text"}, {"profile__button_1", "Button"}, {"profile__column_1", "Column"}, {"profile", "Card"}]
```

A node placed in a property of another node becomes a component of its
own, and the property holds its id. Children come before their parents,
and a node without an id gets one made from the root's. With `catalog:`,
each component gets the defaults its catalog schema declares, and a
component the catalog rejects raises:

```elixir
hd(components)
#=> %{"id" => "profile__text_1", "component" => "Text", "text" => %{"@path" => "/user/name"}, "variant" => "caption"}

Enum.at(components, 1)["variant"]
#=> "body"
```

`A2UIAgent.Builder.call/3` writes a function call, and from v1.0 takes
`catalog_id:` for a function of a catalog other than the surface's.

`A2UIAgent.Builder.messages/2` puts a tree in messages for a surface: a
`createSurface` when it is given a catalog id, the `updateComponents`, and
an `updateDataModel` for each data model entry.

### Macros

A macro is a component the model can write that the agent expands into the
catalog's components before they reach the renderer. One macro call stands
for a whole subtree, so the model writes less and the subtree always comes
out the same. A macro has parameters and a function that builds its tree:

```elixir
alias A2UIAgent.{Macro, MacroExpander}

status_badge =
  Macro.new(
    "StatusBadge",
    [status: :string, title: :string],
    fn %{status: status, title: title} ->
      row(children: [text(text: String.upcase(status), variant: "caption"), text(text: title)])
    end,
    description: "A status and a title, side by side."
  )

expander = MacroExpander.new(basic, [status_badge])
{:ok, inference_catalog} = MacroExpander.inference_catalog(expander)

List.last(inference_catalog.component_order)
#=> "StatusBadge"
```

The inference catalog is the base catalog with the macros added as
components. Register it in a `A2UIAgent.CatalogConfig` in place of the base
catalog, so that the prompt describes the macros and the model's messages
are checked against them. Before the messages go to the renderer,
`A2UIAgent.MacroExpander.to_transport/2` expands the macros:

```elixir
model_messages = [
  %{
    "version" => "v1.0",
    "updateComponents" => %{
      "surfaceId" => "status",
      "components" => [
        %{"id" => "root", "component" => "StatusBadge", "status" => "online", "title" => "API"}
      ]
    }
  }
]

{:ok, [message]} = MacroExpander.to_transport(expander, model_messages)

Enum.map(message["updateComponents"]["components"], &{&1["id"], &1["component"]})
#=> [{"root__text_1", "Text"}, {"root__text_2", "Text"}, {"root", "Row"}]
```

The macro component's id becomes the id of the tree's root, and the other
components are named after it. A macro may use other macros with
`A2UIAgent.Macro.call/2`.
