Skip to content

Devices

A device is something a person pairs with their own Ira: a Mac running Ira for Mac, a Windows PC running Ira for Windows (kind windows), an Android phone, or an iPhone running Ira for iPhone (kind ios). A device connects to the server over one WebSocket, offers tools, runs them when Ira calls, and announces events ("the coding task is done").

Devices are personal. A device belongs to one person; its tools are offered only in that person's conversations, its events reach only that person, and nobody else, an owner or admin included, sees what it returned.

Pairing

  1. On the Devices page (the Ira app's menu, or the console), the person picks the kind of device, names it, chooses the skills it may use, and enters their action PIN. A new device is a new grant, so it needs the PIN.
  2. The server shows a pairing code (eight characters, ABCD-EFGH) and a QR code of https://<server>/pair#code=ABCDEFGH. The code works once, within 10 minutes, and only its SHA-256 hash is stored.
  3. The device sends the code to POST /v1/devices/pair and receives its own device token: irad_ and 32 random bytes, shown this once. The server keeps only its SHA-256 hash.
POST /v1/devices/pair
{"code": "ABCD-EFGH", "kind": "mac"}
{"device_id": 3, "token": "irad_...", "kind": "mac", "label": "Office Mac",
 "skills": ["files"], "name": "Ira", "websocket": "/v1/devices/ws"}

Five wrong codes from one address in 15 minutes give 429 for 15 minutes. A used, expired or unknown code gives 410.

A code for app reviewers

An app store's reviewers are far away and test days later, so a 10-minute code will not do. The server's operator, on the server's own command line (no web route makes one), can make a code that lasts up to 14 days and pairs up to 10 devices:

ira user add --email reviewer@example.com --role staff   # a demo account; asks a password
ira pin set --user reviewer@example.com                  # its action PIN
ira device code --email reviewer@example.com --kind android --days 7 --uses 3 \
    --skills calls,contacts,messages,reminders

It prints the code and its https://<server>/pair#code=... link. Give the reviewers both, with the demo account's email, password and PIN, under the store's App access instructions. Use an account with nothing real in it (the Sunrise Dental Clinic demo data is made for this), and remove it, and its devices, once the review is over. Making such a code goes into the change log.

Tokens

  • A device token opens GET /v1/devices/ws, and lets the device talk to Ira for its person: /v1/ask, /v1/ask/stream, /v1/voice/* (the live call too) and confirming actions, with the person's tiers and PIN. That is all: it has at most the ask, voice and actions permissions of its person's role, and anything that needs a signed-in session (settings, pairing, sessions, the console) answers 403. Session and API tokens do not open the device WebSocket.
  • Send it as Authorization: Bearer irad_.... A client that cannot set headers may use ?token=; IRA masks the value in every log line it writes, but a proxy in front may log it, so prefer the header.
  • Ira for Mac keeps it in the macOS Keychain, the Android app in the Android Keystore, Ira for iPhone in the iPhone's Keychain (this device only, after first unlock). None writes it to a file or a log.
  • Revoking a device (DELETE /v1/me/devices/{id}, or an admin's DELETE /v1/admin/devices/{id}) stops the token at once and closes the device's connection with code 4403. Removing a person's account revokes all their devices.

The WebSocket protocol

JSON text frames, each an object with a type.

Hello. The device's first frame, within 15 seconds:

{"type": "hello", "kind": "mac", "label": "Office Mac", "version": "0.3.0",
 "tools": [
   {"name": "file_read", "description": "Read the text of one file.",
    "parameters": {"type": "object", "properties": {"path": {"type": "string"}}},
    "skill": "files", "tier": 0}
 ]}

The server answers:

{"type": "welcome", "deviceId": 3, "label": "Office Mac", "skills": ["files"],
 "assistant": "Ira", "wakePhrase": "Hey Ira", "maxTools": 40}
  • kind must be the kind the device was paired as.
  • Each tool has a name matching ^[a-z][a-z0-9_]{2,40}$, a description, and parameters as a JSON Schema object (8 KB at most).
  • skill is the grant the tool belongs to; it defaults to the name up to the first underscore (files_find belongs to files).
  • tier is the tool's tier: 0 reads, 1 small and undoable, 2 needs the person's "yes", 3 needs their PIN. A tool that does not say is Tier 2.
  • A hello offers 40 tools at most, and the welcome says so (maxTools). Servers before that took 30 and said nothing, so a device offers its oldest 30 until a welcome says more; then it may say hello again with all of them.
  • A hello that breaks a rule gets {"type": "error", "error": "..."} and the connection closes with 4400.

Calls. The server calls a tool:

{"type": "call", "id": "9b0c...", "tool": "file_read", "args": {"path": "~/Downloads/invoice.pdf"}, "here": true}

here is true when the person asked on this very device, and false when they asked from another device, the web or a channel. The Android app's Google Play build reads SMS and the call log only when it is true: Play allows those only while the person talks to Ira on the phone.

The device answers with the same id:

{"type": "result", "id": "9b0c...", "ok": true, "content": {"summary": "Read invoice.pdf (PDF).", "text": "..."}}
{"type": "result", "id": "9b0c...", "ok": false, "error": "there is no file at that path"}

content is any JSON. A summary string in it is what a no-key demo reads out, and helps a model too.

Events. The device announces something its person should hear:

{"type": "event", "event": "task_done", "title": "Task t1a2b3 in shop is done",
 "text": "Added the invoice total test. 2 files changed. Tests: passed.", "data": {"id": "t1a2b3"}}

event is watch_done, task_progress (a long task saying how it is going), task_done, task_failed, or task_reply (a coding task's answer to the person's message, when it came after the tool had answered).

Keepalive. Either side sends {"type": "ping"} every 30 seconds and the other answers {"type": "pong"}.

Goodbye. A device being uninstalled may send {"type": "bye"} instead of a hello: the server revokes its token and closes the connection.

Limits

Limit Value What happens
Frame size 1 MB the connection closes with 1009
A call 25 seconds the model is told the device did not answer
Calls in flight 4 per device a fifth is refused at once: "the device is busy"
Tools 40 per device (30 before maxTools) the hello is refused
Events 1 per 30 seconds per device an error frame; send it again later
Silence 90 seconds the connection closes with 4408
Connections 1 per device a new one replaces the old (4000), whose calls fail
A restart the server closes every device with 1001 (going away) as it stops: reconnect at once, with backoff, to the next one

Security

  • Grants. A device's tools are offered only while their skill is switched on, by the device's own person, with their PIN. Anyone may switch a skill off without a PIN; an owner or admin can switch off anyone's, never switch one on. Ira knows which skills each device has but are off (from what it last reported), never their tools: asked for missed calls with calls off, it says the skill is off on that phone and where to switch it on, instead of looking somewhere else.
  • Tiers. Tier 0 and 1 tools run when Ira calls them. A Tier 2 or 3 tool becomes a proposal: Ira reads it back, and it runs on the device only after the person's "yes" (Tier 2) or PIN (Tier 3). The tier comes from the device's hello, never from the model.
  • Untrusted output. What a device returns reaches the model inside <device_output untrusted="true" from="...">...</device_output>, with a rule that it is data, never instructions. Every < in it is escaped, so it cannot end the wrapper early. Once a turn has read a device's output, an action goes on only on the person's own words (docs/security.md, "What she reads cannot steer her").
  • Privacy. A device's output is kept only in its person's conversation, marked private: an admin reading that conversation sees a placeholder, and the parameters of an action on someone's device are hidden from everyone else. "What Ira did" records each call with no arguments or results.
  • Events are cut to 120 characters (title) and 1,000 (text), stripped of HTML and markdown, kept seven days (200 per person at most), and sent only to their person: the web app's live stream (GET /v1/me/events/stream), their paired Telegram chats (text, and voice when text-to-speech is set up), and their connected Android devices.

Adding a device of your own

Any program that speaks the protocol can be a device. In outline:

  1. Pair it: ask the person for the code, POST /v1/devices/pair, and keep the token somewhere safe (never in a log).
  2. Connect to /v1/devices/ws with the token, send a hello with your tools and their tiers, and wait for welcome.
  3. Answer each call with a result carrying the same id, within 25 seconds. Validate the arguments yourself: the model wrote them.
  4. Send ping every 30 seconds, answer the server's pings, and reconnect with a growing delay when the connection drops.
  5. Keep events to one per 30 seconds; queue the rest.

Ira for Mac in clients/macos/ (Swift), the Android app in clients/android/ (Kotlin) and Ira for iPhone in clients/ios/ (Swift) are complete examples.