ci / check (push) Waiting to run
harness/phone.md, tools/phone_action.json, loop.json phone, conformance/phone.json. The LLeMbas server's tool alone (scope llembas, sessions chat), offered once a message came from the LLeMbas app with the capability phone_actions (new requirement phone). Actions: alarm, timer, calendar_event, sms_draft, email_draft, dial, navigate, add_stop, search_nearby, stop_navigation, open_app, open_url, media, torch, settings_panel, automation_intent. Each reads its own fields; required fields and value checks give the call's refusal in the spec's words. The person confirms on the phone every time; the result is done, declined or failed with what the phone said, or no answer in time. add_stop carries the route's destination: no map app adds to a running route, so the phone plans again via the stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
6.1 KiB
Markdown
100 lines
6.1 KiB
Markdown
# Phone actions
|
||
|
||
Spec 3.6.0. `phone_action` (`tools/phone_action.json`) lets a chat's model do something on the
|
||
person's phone — set an alarm, start a route, draft a message — when the person is writing from the
|
||
LLeMbas app on that phone. The model proposes; **the app shows the person a card and does nothing
|
||
until they confirm it there**; the app carries it out with the phone's own apps and answers what
|
||
happened, which is the call's result. The texts and numbers are `loop.json` `phone`.
|
||
|
||
It is the server's tool alone (`scope: llembas`, `sessions: chat`). The CLI never offers it: a
|
||
terminal has no phone behind it.
|
||
|
||
## 1. Who offers it
|
||
|
||
- **The capability.** The app declares `phone_actions` (`loop.json` `phone.capability`) with each
|
||
message it sends. A message sent from anywhere else — the web, Desktop, another app — declares
|
||
nothing.
|
||
- **`requires: phone`.** The tool is offered in a chat once a message has been sent to it with the
|
||
capability, and stays offered for the rest of the chat (the tool list is the session's,
|
||
`context.md` §10): a chat started on the phone and continued on the web keeps the tool, and a web
|
||
chat continued on the phone gains it from that message on (the list changes once, so the prompt
|
||
is made again once).
|
||
- **Unavailable.** A call answers the tool's `unavailable` text, word for word, when the message
|
||
being answered was not sent with the capability (there is no phone to confirm on), and in a chat
|
||
nobody attends (a schedule's, a subagent's). Nothing reaches any phone then.
|
||
- **Confirm: always.** Every call is put to the person, in every mode; nothing remembers an answer.
|
||
The card is the phone's own: the server never asks a second time on another screen. Its risk is
|
||
`interact`: no mode refuses it, Plan mode included, because nothing on disk changes and the
|
||
person decides on the phone.
|
||
|
||
## 2. A call
|
||
|
||
`action` is one of `phone.actions`; `summary` is the line the phone shows. Each action reads only
|
||
its `phone.fields`; anything else in the call is dropped before the phone sees it. The call is
|
||
checked before anything is sent, in this order, and the first problem is the call's result:
|
||
|
||
1. `action` not one of the actions: `texts.unknown_action` (`{{actions}}` the list, joined with
|
||
`, `).
|
||
2. The action's `phone.required`, each entry in turn: a field is present when it is not null, not
|
||
an empty string (after trimming) and not an empty list or object; a list inside the list is
|
||
present when any one of its fields is. Every entry that is missing, in order, goes in one
|
||
`texts.missing`: a field by its name, a group as its names joined with ` or `, the entries
|
||
joined with ` and ` (`add_stop needs stop and destination.`).
|
||
3. The fields present, in `phone.check` order (only those the action reads): one that is not what
|
||
it should be is `texts.bad` with `{{field}}` its name and `{{what}}` from `phone.what`:
|
||
- `time`: `HH:MM`, hours 00–23, minutes 00–59.
|
||
- `days`: a list, each one of `mon` … `sun`.
|
||
- `seconds`: an integer (never a boolean) from 1 to 86400.
|
||
- `start`, `end`: `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM` (not checked as a calendar date).
|
||
- `to`, `cc`: a list of strings, each non-empty after trimming.
|
||
- `number`: at least three digits, and nothing but digits, spaces and `+ ( ) - . / # *`.
|
||
- `destination`, `stop`: an object with numeric `lat` and `lon` together (each in its range),
|
||
or a non-empty `query`, or a non-empty `name`. `stops`: a list of such places.
|
||
- `mode`, `near`, `command`, `panel`: one of the schema's values.
|
||
- `url`: begins `http://` or `https://` (any case) and has something after it.
|
||
- `on`: a boolean.
|
||
- `values`: an object of strings.
|
||
|
||
A call that passes is the **action** the phone receives: `action`, `summary` (trimmed; the action's
|
||
name when empty) and the action's fields that are present, as the model gave them, strings trimmed.
|
||
|
||
## 3. The answer
|
||
|
||
The server waits up to `phone.wait_seconds` for the phone's answer: `status` (`done`, `declined`
|
||
or `failed`) and an optional `message`, at most `phone.message_max_chars` characters (cut there).
|
||
The call's result is the status's text — `texts.done`, `texts.declined`, `texts.failed` — and,
|
||
when there is a message, a blank line and `texts.says` with it. A status the phone does not know is
|
||
`failed`. No answer in time is `texts.no_answer`, with no message; the card goes away.
|
||
|
||
What the person pressed is theirs: **declined** means they said no, and the model is told not to
|
||
try again unless asked. **Failed** is the phone's: an app missing, a permission refused, a map app
|
||
that cannot do what was asked.
|
||
|
||
## 4. Navigation
|
||
|
||
Map apps differ, and the phone says when one cannot: the model is not told which app the person
|
||
uses.
|
||
|
||
- `navigate` starts a route to `destination` through `stops`, in order; `mode` is how the person
|
||
travels (`drive` when not said). `replace_route` replaces a route already running without the
|
||
phone asking first.
|
||
- `add_stop`: **no map app adds a stop to a running route**, so the phone plans again — from where
|
||
the person is, to `stop`, then to `destination`. The model gives `destination` as the person told
|
||
it when the route began: the tool's description tells it to keep it.
|
||
- `search_nearby` looks for `query` around the person (`near: me`) or along the running route
|
||
(`near: route`).
|
||
- `stop_navigation` ends the route where the map app allows it; one that does not answers
|
||
`failed` with why.
|
||
|
||
## 5. The other actions
|
||
|
||
- `alarm` and `timer` are the phone's clock app; `days` repeats an alarm.
|
||
- `calendar_event` opens the calendar's new-event screen filled in; the person saves it.
|
||
- `sms_draft` and `email_draft` are **drafts**: the messaging or mail app opens with them, and the
|
||
person sends. `dial` opens the dialer with the number; the person places the call.
|
||
- `open_app` names an app as the person does; `open_url` opens an address in the browser.
|
||
- `media` controls what is playing; `torch` switches the torch.
|
||
- `settings_panel` opens the phone's quick panel or page for a setting; the person changes it.
|
||
- `automation_intent` runs an automation the person made in an automation app (Tasker when `app`
|
||
is not said), by its name, with `values` as its inputs.
|