Size limits
Cards
Widgets (identical as a result block and as a confirmation view)
Widget Kit asset budgets
Character limits use JavaScript
.length (UTF-16 code units), not UTF-8 bytes
or token counts. JSON quotes, escapes, property names, and data count toward
the serialized card limit. Host-generated capability IDs do not consume the
author’s budget. The SDK’s glanceResult and widgetResult reject oversized
envelopes before sending. Check JSON.stringify({ blocks }).length when
constructing a payload directly. A 300,000-character HTML document will need
to be smaller to fit inside a result envelope.
SDK reminders
See Reminders with buttons for
registration, persistence, idempotency, and reconnect behavior.
Bridge messages
Fast intents and speech hints
See Fast intents for dynamic choices, fallback behavior,
and testing. These limits apply to the fast path; a longer or ineligible request
can still use the normal agent. Background tools are not eligible for fast
intent dispatch.
Degradation order
Nothing on the card surface fails loudly, and nothing vanishes without a rule:- An off-schema block is dropped individually; its siblings render.
- Over-budget blocks (4th block, 2nd chart) are dropped in declaration order.
- A Widget-Kit card over the HTML budget sheds content images first
(never the integration mark), then trailing blocks — the header goes
last. (
renderCustomthrows instead, so you find out in your test run.) - A card with zero valid blocks is dropped whole — the spoken answer still happens.
bun verify.ts is how you catch all of this before a user does.
Timing
Security model
- Process isolation. Integration code never runs inside VoiceOS
processes and never sees VoiceOS secrets.
local-mcpservers run as child processes on the user’s machine;remote-mcpruns on your infra. - UI is data. Declarative blocks can’t reach host APIs, and
markdownis sanitized. Anything off-schema degrades to a safe placeholder. - Widgets are sandboxed. Opaque origin, no network egress, validated postMessage only, clamped heights — and no approve leg: only VoiceOS’s own button (or the user’s voice) can approve an action.
- The model never sees your UI, and can never author a widget. Glance
payloads are stripped from tool results before the model reads them, and
the parser refuses
widgetblocks outside integration surfaces. - Secrets stay out of band. Setup values are stored encrypted, injected as env vars, never echoed to the UI, and never enter the agent transcript.
- Consent at the edges. Permissions are declared in the manifest and shown at install; acting tools confirm before running; users hold per-tool “Asks first” overrides.
Stability
Two version gates cover the whole contract:
The promise (the “Minecraft rule”): additive changes never bump them. New
blocks, new fields, and new permissions arrive without breaking existing
manifests, and hosts render unknown future blocks as safe placeholders. An
integration authored today keeps working across years of VoiceOS updates.
Reserved-but-declared surfaces you’ll see in the types —
hosted runtime,
store and webhook permissions, canvas blocks, role: "custom"
actions, brokered oauth2 — exist so that when they ship, nothing you wrote
changes.
