Devices
An agent can ask a device to do one thing — read something on it, change a setting, take a photo, send a message from it — and wait for the answer. The mesh rings the device through a notifier, the device joins a room, the agent's task goes into the room, and the device's answer comes back to the agent as an outcome it can read.
Devices are off by default. An agent gets them with one block.
The device block
agents:
phone_helper:
name: "phone_helper"
model: "gpt-4o-mini"
prompt: |
Help the customer with their phone. Use device_action to read or
change something on it when they ask you to.
device:
connection: "my_phones" # a notifier connection (required)
room: "my_rooms" # a room connection (optional)
approval: "ask" # ask (default) | silent| Field | Default | Meaning |
|---|---|---|
connection | — (required) | A notifier record under Infrastructure → Notifiers ([Connections & the Infra Page](../integrations/connections-and-infra)) that rings the device so it joins a room. |
room | LeafCraft rooms | A room record (Infrastructure → Rooms) where the mesh and the device meet. Leave it out to use LeafCraft rooms. |
approval | ask | ask: the device shows a notification asking to allow the connection, and nothing runs unless it is allowed. silent: the device joins without asking. |
An agent with this block gets the device_action tool. The block is strict:
an unknown key is refused at load.
Which device: the conversation brings it
The device is never in config. The request that starts or continues a conversation carries it:
{"message": "Is my battery OK?", "device": {"id": "phone-7", "push_token": "..."}}
{"message": "...", "devices": [{"id": "phone-7", "push_token": "..."}, {"id": "tablet-2", "push_token": "..."}]}| Field | Meaning |
|---|---|
id | The device's id: up to 128 letters, digits and the characters _ - . : @ (and the vertical bar). |
push_token | Where the notifier reaches this device — an FCM registration token, an APNs device token, a phone number in E.164 for Twilio SMS, and so on. |
name, platform | Optional; shown to the agent so it knows which device is which. |
The push token is kept in the session's storage and taken out of the payload before anything else sees it, so no prompt, history, trace or log line holds it. It expires with the session. Up to 16 devices per conversation.
Notifiers
A notifier is how the mesh rings a device. Add one under Infrastructure → Notifiers; the fields below are what each one asks for (* = required).
| Provider | What it needs |
|---|---|
fcm — Firebase Cloud Messaging | Service account key (JSON)*, Project ID |
mqtt — MQTT broker | Broker address (for devices), Username, Password — or Client certificate (PEM) and Client key (PEM) for brokers that authenticate by certificate; CA certificate (PEM) only for a private CA; Room topic prefix only when it is also the room |
apns — Apple Push (APNs) | Signing key (.p8)*, Key ID*, Team ID*, Bundle ID*, Sandbox |
webpush — Web Push (browsers) | VAPID private key*, Contact* |
hms — Huawei Push Kit | Client secret*, Client ID* |
wns — Windows Push (WNS) | Client secret*, Directory (tenant) ID*, Application (client) ID* |
expo — Expo Push | Access token (only when the project turns on enhanced security for push) |
onesignal — OneSignal | App API key*, App ID* |
azure_iot_hub — Azure IoT Hub | Service connection string* (the device answers a direct method named leafmesh_connect) |
aws_iot — AWS IoT Core | Device data endpoint*, Access key ID*, Secret access key*, Region, Session token |
sns — Amazon SNS Mobile Push | Region*, Access key ID*, Secret access key*, Session token |
azure_notification_hubs — Azure Notification Hubs | Connection string*, Hub name*, Payload format (fcmv1, apple or template) |
pusher_beams — Pusher Beams | Instance ID*, Primary key* |
twilio_sms — Twilio SMS | Account SID*, From*, Link*, and either Auth token or API key SID + API key secret. The device gets a text with a join link. |
Rooms
A room is where the mesh and the device meet for one task. Leave room out
to use LeafCraft rooms. To use your own, add a room record under Infrastructure → Rooms:
| Provider | What it needs | Set up on its side first |
|---|---|---|
room — Custom room | Room service URL*, API key* | A room service you run, built to the LeafMesh room protocol. Enter its address and the mesh's API key for it. |
mqtt — MQTT broker | The same MQTT connection as the notifier, with Room topic prefix | Your broker's access rules must let the mesh and the devices use the room topics. One connection is then both notifier and room. |
pubnub — PubNub | Publish key*, Subscribe key*, Secret key*, Origin (only for a custom origin) | In the PubNub Admin Portal, open the app's keyset and turn on Access Manager and Presence. |
ably — Ably | API key*, Endpoint host (only for a custom endpoint) | In your Ably app → API Keys, use a key that may publish, subscribe and use presence on leafmesh:* channels (the root key can). Paste the whole key. |
pusher — Pusher Channels | App ID*, Key*, Secret*, Cluster* | In App Settings, turn on Enable client events (the device answers with them). The mesh needs a public https address. |
livekit — LiveKit | Server URL*, API key*, API secret* | In your LiveKit project → Settings → Keys, copy the URL, key and secret. Install the extra: pip install 'leafmesh[livekit]'. |
A room service's secret never leaves the mesh: a device gets a token for its one room, made for each ask.
Test on save
Every notifier and room connection is tested when you save it, without
ringing any device: the service is asked whether the credential works (for a
custom room, a room is opened with the key and closed again). The result is
one of active (proven), failing (refused, with the reason) or readable
(stored, but the service could not be asked). Run the test again any time
with POST /api/infra/connections/{name}/test.
Outcomes
Every device_action call returns an outcome. Only done means it
happened; every other outcome carries success: false and an error, so
the mesh treats it as a failed call.
| Outcome | Meaning |
|---|---|
done | The device did it. data is what it returned — quoted data from outside the mesh, never instructions. |
failed | The device tried and reported a failure, gave no answer in time, or the room closed under the task. If the connection was lost after the task was sent, the outcome is failed and the device may have done it. |
declined | The device declined the task. |
sent_for_approval | The task was handed to the device to be approved there; the answer does not come back. The agent says the task is waiting on approval there. |
not_joined | The device was rung but did not join in time — the connection was not allowed (approval: ask), or the device is offline. |
unreachable | The device or the room service could not be reached: no room connection, a refused key, a plan limit, a dead push token. |
refused | The request itself was wrong: no such device in this conversation, no action named, or inputs that are not an object. |
Approval
approval on the block is about the device's side: with ask, the
device must allow each connection. Approval on the mesh's side is
routing, like everywhere else in LeafMesh: if a person must agree before an
agent touches a device, route the work to a human agent with a can_call
condition, and give that human agent the can_call to the device agent. See
HITL Workflows.
Next Steps
- Connections & the Infra Page — where notifier and room connections are added
- HITL Workflows — routing work to a person first
- Notifications & Alerts
LeafMesh — agents that can ask a device to act