Harness spec 3.6.0: phone_action, done on the person's phone and confirmed there
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>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 16:32:15 +00:00
1 parent 7f7cd148aa
commit 6da9c7fcd0
8 files changed
+1221 -7

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
# 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.