# 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.