Docs · Developers

Phone agent integration (developers)

A public guide for third-party phone-app developers: pair with Lom Desktop by QR code, stay online through the cloud relay, and answer encrypted calls from the desktop. This page is an English summary; the full guide is currently available in Chinese.

English summary — full English version pendingThe complete guide (eight chapters plus test vectors) is written in Chinese and is the authoritative text. This page summarizes each chapter in English and links to the Chinese originals; a full English translation has not been written yet. Everything here comes from the public repository rfdiosuao/lom-desktop (docs/open/phone-access/, commit 92044e2). If the two ever differ, the repository wins.

What you build

You do not need access to any private repository or source code. A phone app that follows this guide can be paired with Lom Desktop and accept work from it. The phone side has three jobs:

  1. Pair. Parse the QR code (or take a manual pairing code), claim and confirm the pairing with the cloud, and obtain device credentials.
  2. Stay online. Exchange the device credentials for a short-lived relay token, open a WebSocket to the cloud relay, keep it alive and reconnect when it drops.
  3. Answer RPC calls. Verify, decrypt and run each request the desktop sends, then return an encrypted, signed reply.

The phone only connects outward and listens on no port. It does not need an account login and does not need to be on the same network as the computer. The cloud forwards ciphertext only: requests are end-to-end encrypted and signed, so it cannot read them.

Chapters

ChapterWhat it covers
OverviewTerms, topology, an implementation checklist, common misunderstandings
02 QR code and deep linkField-by-field format of lom://pair and the checks the phone must run
03 PairingQR path and manual-code path: every request, response, error code and timeout
04 Identity and algorithmsIdentity keys, encodings, fingerprint, key_proof, confirmation code
05 Relay connectionDevice credentials, relay token, WebSocket, heartbeat, reconnect, revocation
06 RPC envelopeEncryption, signature, time window and replay protection
07 RPC methodsRequired and optional methods, including device.capabilities
08 Errors and securityError codes, versioning and compatibility, security notes
Test vectorsTwo vector files and a reference script for self-testing

Pairing and identity

  • The desktop shows a QR code that is valid for 5 minutes and can be claimed once. It encodes a link of the form lom://pair?v=1&h=…&p=…&t=…&k=…&f=… (host, pairing ID, claim token, pre-shared key, desktop key fingerprint). Only HTTPS to the host in the link is allowed.
  • The phone runs inspect (fetch the desktop's public keys and compare the fingerprint), claim (submit its own keys with a key_proof), asks the user to confirm on the phone, then calls confirm to receive its device_id and device_token.
  • If scanning is not possible, an 8-character pairing code can be typed instead. In that path both sides compute a 6-digit confirmation code from the two public keys and the user compares them by eye.
  • Each side has one public key string K = base64url(X25519 public key ‖ Ed25519 public key), 86 characters. The fingerprint is the first 16 bytes of SHA-256 over a fixed prefix plus K. Encodings are unpadded base64url throughout the pairing and envelope layers. Changing keys requires pairing again.

Relay connection

  • Device credentials are sent as Authorization: Device <device_id>.<device_token> and renew on a rolling 90-day basis.
  • Before every (re)connection the phone calls POST /v1/relay/token to get a one-hour relay token, then opens wss://<host>/v1/relay with Authorization: Bearer <relay_token>. There is no renewal inside an open connection.
  • The relay carries two message kinds, task.rpc (desktop to phone) and task.reply (phone to desktop), plus a few control messages. The cloud sends a WebSocket ping every 20 seconds and closes the connection after 60 seconds without any frame from the phone. There is no offline queue: if the phone is offline the desktop request fails immediately.
  • Only an explicit revocation message clears credentials. A dropped connection, a timeout or a server error is a reason to reconnect, not to unpair.

RPC envelope

  • Every request and reply is an independent envelope of six string fields. There is no Noise handshake and no session state.
  • Each envelope uses a fresh ephemeral X25519 key, HKDF-SHA256 key derivation, ChaCha20-Poly1305 encryption and an Ed25519 signature. The phone verifies the signature before it decrypts anything.
  • Replay protection combines an issued_at time window with de-duplication of envelope IDs; the de-duplication table must survive an app restart and be safe under concurrent requests.

RPC methods

  • Required: model.sync, chat.send, chat.read, chat.list, chat.cancel. document.list, document.read and schedule.list are required too, but may return empty lists.
  • Optional: device status, installed apps, file transfer, screen capture / UI tree / control, skills, schedules, sub-agents, chat management and document editing.
  • A method the phone does not implement must return the error remote.method_unknown. Never return an empty success to look compatible: the desktop would treat the operation as done.
  • These are plain RPC methods, not MCP tools. The MCP bridge inside the desktop is private to its own commander agent and is translated into these calls.

Declaring what your phone supports: device.capabilities

Optional but recommended. The phone lists the methods it actually implements, plus a few numeric limits. The desktop calls it after pairing and on each connection, caches the answer per device and app version, and then hides or greys out phone-page features your app does not support (skills, schedule editing, sub-agents, document editing, attachments).

{
  "version": 1,
  "methods": ["device.capabilities", "device.status", "model.sync",
              "chat.send", "chat.read", "chat.list", "chat.cancel",
              "document.list", "document.read", "schedule.list"],
  "limits": { "chat.send.max_text_chars": 4000, "file.max_bytes": 104857600 }
}
  • methods must match the phone's real dispatch table. A method that is listed must not answer remote.method_unknown; a method that is not listed still must.
  • Features that need an extra permission on the phone (for example screen access) stay in the list; the permission state is reported by the method itself.
  • A phone that does not implement device.capabilities is treated as supporting everything, as before.

Test vectors and self-test

Run your implementation against the vectors before you try to connect to a real desktop. Most interoperability problems (encodings, concatenation, byte order, base64 variants) show up within minutes.

  • pairing-v1.json: public key string, fingerprint, key_proof, confirmation code and QR link, two cases.
  • remote-envelope-v1.json: one real encrypted, signed envelope from a simulated desktop to a simulated phone.
  • verify.py: a reference script that checks both files using the algorithms in the guide. Port it function by function to your language.

To run the reference script, put the three files in one directory and run:

pip install cryptography
python3 verify.py
# pairing vectors: 2 cases, all pass
# envelope vector: all pass

Then check your own implementation against the same assertions:

  1. For each pairing case, your code must reproduce keys, both fingerprints, the QR link, key_proof and the 6-digit confirmation code, and your QR parser must read the same values back and reject malformed links (missing or duplicate fields, v=2, padded base64, wrong lengths).
  2. For the envelope, derive the public keys from the published test seeds, open the envelope as the recipient (verify, then decrypt) and obtain the plaintext string stored in the vector, then confirm that changing any of id, ephemeral, ciphertext or signature makes opening fail.
  3. Seal a reply with the same envelope ID and confirm the simulated desktop identity can open it.
Test values onlyThe keys in the vectors are arbitrary test values or seeds made of repeated bytes. Never use them as real keys. The envelope plaintext is a demonstration string, not a valid request; build your own request JSON for handler tests (see the Chinese testvectors page).

Common misunderstandings

  • The desktop does not drive the phone with 15 MCP tools. It calls the RPC methods above.
  • The phone and computer do not need to share a LAN. The official path goes through the cloud relay, so mobile data works.
  • There is no Noise protocol in the current implementation. Earlier design drafts mentioned it; the envelope scheme above is what ships.

A phone app built on this protocol

The Lom edition of the open-source project Mika is one phone app that pairs with Lom Desktop through this protocol. See the Mika download section on the home page. The official Lom phone app uses the same protocol.