Build
Ships today

Agents on the device

Put your agent on the device as a first-class app, draw live UI, and let a call block until the user answers with a thumb.

Your agent can become an app on the device — one that sits in the library next to the built-ins, draws its own screens, and hears the buttons. A handful of tool calls carry it there, and the interesting ones block and hand the answer back to the agent that asked.

Everything here goes through the companion's MCP server, over a token you approved once. Nothing is flashed to the device.

Ask, and get the answer back

The fastest thing to reach for is notify. It posts on the device's notification ladder — a bell count, a live status line, a card, a full-screen alert — and two of its rungs wait:

  • prompt puts a question up with up to three options. Each option binds to a physical button. The call stops until a thumb lands, then returns the option pressed.
  • modal hands over a multi-page screen with sliders, toggles and choices. The call returns everything the user set — the button they ended on (action), the values they set, and the steps they took to get there.

That is "Approve deploy to prod?" on a device on your desk, answered with a thumb — the agent asked, stopped on the call, and continued the moment you pressed.

Ask without waiting.

Pass wait: false and the question goes up while the call returns straight away. Name a default_option and a ttl_seconds, and an unanswered prompt resolves to your fallback when the window closes.

notify needs no app and no setup — just the control scope your token already carries. A full-screen urgent takeover needs the urgent scope; without it, it drops to a quiet card.

Become an app

To leave something behind — a screen the user can scroll to, a button they can bind — register_app puts your agent in the library as a real app.

Register the app

Call register_app with a manifest: its identity, and its exports — faces (full designed screens), widgets (slot content), pages (swipe-up panes), intents (button actions the user can bind), and glances (ready-made screens the user adds in the companion's glance editor). It joins the device and companion library exactly like a built-in app. You can only replace your own app.

Feed it live content

Call update_snapshots with a map of export ref (app_id.export_id) to a zui JSON tree. It lands on connected devices at once and re-pushes on reconnect. If your agent disconnects, the last screen simply goes stale on-device — nothing recomputes it, because the thing that computes it is your agent.

Hear the buttons

Call subscribe_intents. Every press on a binding you declared arrives as an MCP notifications/message — the intent id (app_id.intent_id) and its phase (press, release, tap, double). One subscription per session per device; it survives the device reconnecting and ends when your MCP session closes.

The presence loop

register_appYour agent joins the library.
then feed it
update_snapshotsLive content onto its exports.
user presses
A button pressOn a binding you declared.
streams back
subscribe_intentsThe press reaches your agent.
register_app and update_snapshots need the 'apps' scope; subscribe_intents rides 'control'. The app leaves the library when you call remove_app.

Skip registration for a one-off

For a screen you do not need to persist, push_screen draws a zui tree straight onto a scratch ref no app owns — place scratch.main on a glance and push to it. It replaces that surface until the next push or a reboot, or auto-clears after ttl_seconds. A ref a live app already owns is refused: that app's own snapshot pump would paint over you a moment later, so the call tells you at once rather than let you believe a screen is up.

Next

On this page