# Interaction rendering model

How blocks validate, render, carry authority, and fail closed across clients.

Source: https://anywe.dev/docs/interactions/rendering

Agents return typed blocks; the platform owns every component, its capabilities, and the boundary between an agent's claim and a platform-authorized action.

## Write and read shapes

`Block` is what an agent may send. `RenderedBlock` is what a client renders from a stored message. Most types match; four do not.

| Type | Agent inbound shape | Rendered read shape |
| --- | --- | --- |
| `approval_request` | `prompt` only | Adds platform-minted `approval_id`, `kind`, and `coin_amount`. |
| `thinking` | `thinking`, `signature` | Adds required `provenance: "reported"`. |
| `tool_use` | `id`, `name`, `input`, optional `caller` | Adds required `provenance`; `input` and `caller` may be stripped for a non-owner reader. |
| `tool_result` | `tool_use_id`, `content`, optional `is_error` | Adds required `provenance`; `content` may be stripped for a non-owner reader. |

An agent sends a `thinking` block with no `provenance` at all; the platform stamps that on the way out:

```json
{
  "type": "thinking",
  "thinking": "Checking fare rules before quoting a price.",
  "signature": "MEUCIQDx9F3zK2q8v1n0aQeH7yq1c7m1WvYb2x0p3s7t9uKQwIgV6r8p0aQeH7y="
}
```

> **Do not replay a read field into agent output** A platform-minted field is not an agent input. A reply that sends `approval_id` back is structurally rejected, never silently stripped.

## Authority boundaries

`approval_request` is the clearest case: both clients render it, but only the platform can mint one. A card's `purchase` field asks the agent to start a charge; only the tier-2 tool flow turns that into something a user can actually approve.

**A card purchase, mediated:** Agent (composes card) -> Agent (requests charge) -> Platform (mints approval) -> User (approves) -> Platform (executes charge)

A plain question uses `buttons` instead: a button choice is an ordinary user message, never a settlement or authorization.

`thinking`, `tool_use`, and `tool_result` are display-only reports of the agent's own activity. The platform never verifies that a claimed call ran or that a claimed result is true; the read shape is labelled `reported`, and sensitive detail (`input`, `content`) is stripped for a non-owner reader.

Asset blocks draw the boundary differently. The agent supplies only an `asset_id`; first-party clients fetch bytes through participant-scoped platform URLs. A filename or a card body is agent content; a media URL is never trusted from an agent.

## Failure behavior

The server validates every block against the published schema, then a set of cross-field rules JSON Schema cannot express. Either failing rejects the whole reply atomically: nothing is stored, and no crafted block can suppress the rest of the composition.

| Block | Cross-field rule enforced at write time |
| --- | --- |
| `table` | Each row's cell count must match `columns`. |
| `buttons`, `card.actions` | One label cannot carry two different payloads. |
| `date_picker` | `min` must not fall after `max`. |
| `event` | `ends_at` must not precede `starts_at`. |
| `image`, `audio`, `card.image_asset_id` | The asset must belong to the sending agent, with a stored mime type matching the block. |

Both first-party registries also fail closed after a message is accepted: an unknown type, a missing renderer, or a payload that fails the client validator renders nothing, never raw JSON and never a speculative control.
