Files
Skald-Circle/docs/relay/describe-and-push.md
2026-07-10 15:02:09 +01:00

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 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) 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.
  • secretmasked 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) → 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) → 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.