4.8 KiB
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
questionfrom 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) 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.
{
"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:
{
"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(forkey_value) orlabel(forfield/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) → 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
summaryfrom theinbox_updateblob. It is the agent that emits, for the push, a lightweightnotificationpayload (E2E, decrypted by the NSE) containing only thesummary. - This way the push always fits under the content-in-push threshold (3500B b64, server.md §5) → notification always readable, without the content-vs-wake juggling needed for rich payloads.
- The
aps.alertin 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
vat the top ofblocks. An unknowntype/value_typeis not an error: the client degrades tokey_value/ raw string (never crash).- If
blocksis absent (older agent, or tool with no description), the client showssummaryand optionally rawarguments. Fully backward-compatible. - Non-form surfaces (e.g. Telegram) flatten blocks to text (
key: value, diff in monospace): every block is linearisable by construction.