# Tool Description & Push Delivery (`describe` + `blocks` + APNs)

_Normative for approval rendering on the client side._

> How the app receives and displays **what** it is approving, and how the push notification stays
> lightweight. Primarily concerns **approvals** (tool arguments are hard to read as raw JSON);
> clarifications already carry a human-written `question` from the LLM.
>
> This file defines the **wire contract** (what the client receives). How the blocks are
> produced on the agent side (built-in vs MCP, templates) is in the agent's tool description
> infrastructure.

---

## 1. Two Representations, One Item

Every `approvals[]` entry in an `inbox_update` ([payloads.md §3.1](payloads.md)) carries **two**
views of the same tool call:

| Field | What it is | Used for |
|-------|-----------|----------|
| `summary` | Short human string, generated by `describe(Short)` on the agent (e.g. *"Send an email to mario@acme.com"*) | Card row, **push notification**, conversation log |
| `blocks` | **Structured** parameter description, generated by `describe_view(args)` | Detail screen (forms/tables/diffs) |
| `arguments` | Raw args (tool's JSON) | Final fallback / debug |

`summary` is the source of truth for "narrow" surfaces (notification, badge); `blocks` for the
detail screen. `arguments` remain as a safety net.

---

## 2. `blocks` Schema (wire)

A tool call is described as a **list of typed blocks**. The vocabulary is **small and stable**:
tools are unlimited, block types are not. Each client maps types to its native widgets *once*,
and it works for any present and future tool.

```json
{
  "v": 1,
  "summary": "Send an email to to@mail.com",
  "blocks": [
    { "type": "key_value", "key": "Email-To", "value": "to@mail.com", "value_type": "email" },
    { "type": "field",     "label": "Subject", "value": "Q3 Estimate", "value_type": "string" },
    { "type": "block",     "label": "Body",    "value": "…body…",      "value_type": "text" }
  ]
}
```

Example for `write_file`:

```json
{
  "v": 1,
  "summary": "Write /path/x.rs",
  "blocks": [
    { "type": "key_value", "key": "File", "value": "/path/x.rs", "value_type": "path" },
    { "type": "block", "label": "Diff", "value": "= same\n- old line\n+ new line", "value_type": "diff" }
  ]
}
```

**`type`** = layout hint (three values suffice):

| `type` | Rendering |
|--------|-----------|
| `key_value` | Compact `key: value` row |
| `field` | Labelled field, value on one line |
| `block` | Extended content with label (multi-line / dedicated viewer) |

**`value_type`** = value semantics → widget + formatting:

`string` · `text` · `markdown` · `code` · `diff` · `command` · `email` · `url` · `path` · `json` · `number` · `boolean` · `datetime` · `secret`

Rendering notes:
- `diff` → diff viewer (green/red). `command` / `code` → monospace. `email` / `url` → tappable.
- `secret` → **masked by default** (e.g. API key in args).
- Block fields: `key` (for `key_value`) or `label` (for `field`/`block`), `value`, `value_type`.

---

## 3. Delivery Model: Lightweight Push, Detail via WS

**Principle: the push never carries `blocks`/`arguments`. Only the `summary`.**

| Channel | What travels |
|---------|-------------|
| **WS** (live or `inbox_request` on tap) | **Complete** `inbox_update`: `summary` + `blocks` + `arguments` |
| **APNs/FCM push** | A minimal `notification` (kind §3.2 in payloads.md) with `body` = `summary` (`describe`), **no blocks/args** |

Flow: the app receives the notification with the `summary` line → user taps → app opens WS and
sends `inbox_request` ([payloads.md §4.6](payloads.md)) → receives the complete `inbox_update`
with `blocks` → shows the detail screen.

### Why (zero-trust + size)

- The relay is **zero-trust**: it cannot read the encrypted content, so it **cannot** extract
  `summary` from the `inbox_update` blob. It is the **agent** that emits, for the push, a
  lightweight `notification` payload (E2E, decrypted by the NSE) containing only the `summary`.
- This way the push **always** fits under the content-in-push threshold (3500B b64,
  [server.md §5](server.md)) → notification always readable, without the content-vs-wake juggling
  needed for rich payloads.
- The `aps.alert` in plaintext ("Skald / Action required") remains the generic fallback visible
  to Apple; the real text (`summary`) is in the encrypted blob that the NSE replaces.

---

## 4. Forward-Compat & Degradation

- `v` at the top of `blocks`. An unknown `type`/`value_type` is not an error: the client
  degrades to `key_value` / raw string (never crash).
- If `blocks` is absent (older agent, or tool with no description), the client shows `summary`
  and optionally raw `arguments`. Fully backward-compatible.
- Non-form surfaces (e.g. Telegram) **flatten** blocks to text (`key: value`, diff in monospace):
  every block is linearisable by construction.
