Harness spec 3.6.0: phone_action, done on the person's phone and confirmed there
ci / check (push) Waiting to run
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:
8 files changed
+1221
-7
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user