diff --git a/routing.md b/routing.md new file mode 100644 index 0000000..31fc6c5 --- /dev/null +++ b/routing.md @@ -0,0 +1,195 @@ +# Routing Rules (`routing.md`) + +**Purpose:** tell the running Hub how to choose a destination. +**Owner:** Sam (human-edited). +**Status:** v1 draft. First pass. Edit the tables; keep the three sections. +**Related:** `hub_brief.md` (design), `docs/hub-api.md` (inbound API, section 5), +`docs/ask-contract.md` (project interface). + +This file is **not** `AGENTS.md`. `AGENTS.md` tells coding agents how to work. +This file tells the Hub how to choose. Keep them separate. + +--- + +## How the Hub uses this file + +For each request, the Hub runs the checks in this order: + +1. **Never-route check.** If the request is a control action (section 3), return + `status: "refused"`. Stop. Do not ask an LLM. +2. **Read capabilities.** Read the topic list from each project's + `/capabilities` (`docs/ask-contract.md`). +3. **Read this file.** Read the topic map (section 1) and the overlap rule + (section 2). +4. **Ask the LLM.** Give the LLM the question, the capability lists, and this + file. The LLM returns one of four answers: + - `route` — send to one project. + - `fanout` — ask several projects, then merge. + - `clarify` — ask the user one short question. + - `no_match` — say it does not know. +5. **Route.** Send the request. The action call holds no LLM (brief rule 4). + +A project name in this file must match the `project` value in that project's +`/capabilities` reply. A new project adds one row here and one `/capabilities` +reply. Build the ability once. + +**Fanout:** use it only when the question clearly needs two topics at once, for +example "what do I have on today?" (school + email + calendar). Default to +`route`, then `clarify` when unsure. + +--- + +## 1. Topic to project map + +The Hub uses these topics as the plain-language keys for routing. Keep the list +short. Add a topic only when a project really answers it. + +| Topic (what the user says) | Project | Kind | Status | +|---|---|---|---| +| email, inbox, mail, message, unread | `email_agent` | query `/ask` | placeholder — not built | +| school, Compass, homework, timetable, class, NHS appointment | `compass_nhs` | query `/ask` | placeholder — not built | +| screen time, internet use, time limits, what did the kids do online | `internet_control` | query `/ask` | placeholder — not built | +| lights, lamp, switch, plug, timer, reminder, calendar event, device, thermostat | `home_assistant` | command (direct) | active service | +| speak, say it out loud, house speakers, announce | `speakers` | command (direct) | active service | +| search, look up, what is, who is, weather, news | `web_search` | command (direct) | placeholder — not built | +| notify, notification, alert me, remind my phone | `apprise` | command (direct) | active service | +| file a job, plan this, research this, slow task, work bench | `paperclip` | decision (approval) | active service | +| phone screen, tap, open an app, type on the phone, scroll | `artemis` | command (direct, action only) | placeholder — not built | +| visitor, front door, who is at the door | `front_door_cam` | peer intercom | placeholder — not built | +| talk to the house voice, Hey Jervis | `jervis` | way in (voice only) | active service | +| chat, per-person chat, dsh | `dsh` | peer (keeps its own chat) | active service | + +**Not destinations.** These are ways in, not projects to route to. A request that +names them still routes to the real destination. + +| Name | Why it is not a destination | +|---|---| +| `phone_app` | Way in. Sends voice to the Hub. | +| `console_portal` | Way in and dashboard. | +| `admin_app` | Way in for restricted actions. Calls the internet control service directly. | + +### Placeholder entries + +Each project below is planned but **not built**. Keep the row and the placeholder +so the file is ready. Replace the placeholder when the project declares its +`/capabilities`. + +| Project | Expected topics (placeholder) | First build step | +|---|---|---| +| `email_agent` | email, inbox, mail | brief build order step 5 | +| `compass_nhs` | school, homework, timetable | brief build order step 5 | +| `internet_control` | screen time, internet use, limits | brief build order step 6 | +| `artemis` | phone screen control | brief build order step 7 | +| `web_search` | search, weather, facts | pending | +| `front_door_cam` | visitor | pending | + +--- + +## 2. Preferred project when two overlap + +Some topics fit two projects. The Hub uses this preference order. The left +project wins. If the question still fits both, the Hub uses `clarify`. + +| Overlap | Preferred | Other | Reason | +|---|---|---|---| +| "today" / "schedule" | `home_assistant` (calendar) | `compass_nhs` (timetable) | The calendar answers the whole day. School answers school only. | +| "what do I have on" | `fanout` (`compass_nhs` + `email_agent` + `home_assistant`) | one project | Merge the three, then answer once. | +| "message" | `email_agent` (email) | `dsh` (chat) | An email question is about the inbox, not the chat. A chat question stays in dsh. | +| "reminder" | `home_assistant` | `apprise` | Home Assistant owns timers and reminders. Apprise only delivers. | +| "appointment" | `compass_nhs` | `home_assistant` (calendar) | A health or school appointment is NHS or school data first. | +| "what did the kids do" | `internet_control` | `email_agent` | Online use is the internet control service's data. | +| "is the doorbell" / "visitor" | `front_door_cam` | `artemis` | The camera owns the visitor. Artemis only acts on the screen. | + +Rules: + +1. A **domain question** goes to the project that owns the data. Do not fanout + for one topic. +2. A **day summary** fans out. Merge to one short sentence. +3. Two projects with equal claim, and the preference table does not decide, use + `clarify`. Ask one short question. Example: "School or email?" +4. Never fanout to more than three projects. +5. A project that is down returns `status: "unavailable"` for that part. Do not + fail the whole fanout. + +--- + +## 3. Never-route list (control actions) + +The Hub **never** routes a control action. It returns `status: "refused"` and +points at the admin app. This list is the authority for that decision. Add every +new control action here before you add the ability. + +A control action is a **high-consequence change**. It changes parental controls, +limits, or a child's access. Only the admin app does it, and it calls the +internet control service **directly**. The Hub is not in that path +(brief rules 8 and 11). + +The Hub must refuse when the request means any of these: + +| Control action | Refuse? | +|---|---| +| Cut the kids' internet. | **Refuse** | +| Restore the kids' internet. | **Refuse** | +| Change an internet time limit or daily allowance. | **Refuse** | +| Change an internet schedule (bedtime, school hours). | **Refuse** | +| Add, remove, or change a blocklist or content filter. | **Refuse** | +| Change any parental control on any account or device. | **Refuse** | +| Add, change, or remove a child profile or a device rule. | **Refuse** | +| Pause, lock, or unlock a family device for a policy reason. | **Refuse** | +| Any other state change on the internet control service. | **Refuse** | + +The alternative reply is always the same: + +> "Use the admin app to change internet limits." + +`status: "refused"`, `route.kind: "refused"`, +`route.reason: "high_consequence_control"`. See `docs/hub-api.md`, section 5. + +### Not in the never-route list + +These do **not** route as control. Keep them out of this list. + +| Action | Path | Why | +|---|---|---| +| Turn on a lamp. | Hub → Home Assistant | Fast command. Low consequence. | +| Set a timer or reminder. | Hub → Home Assistant | Fast command. Low consequence. | +| Add a calendar event. | Hub → calendar project | Fast write. Approval not required. | +| Speak on the house speakers. | Hub → speakers | Output only. | +| File a Paperclip job. | Hub → approval → Paperclip | Slow decision. Needs a tap, but it does route through the Hub. | +| Ask about screen time. | Hub → `internet_control` `/ask` | Read only. A query is allowed. | + +**A spoken keycode is not the lock.** Voice may ask. Only the admin app confirms. + +--- + +## 4. What the admin app may send to the Hub + +The admin app is the only trusted door. It uses its own token +(`HUB_TOKEN_ADMIN`). It may send the Hub: + +1. **Queries and fast commands** — the same as any way in, through `POST /ask`. +2. **A confirmation decision** — one yes or no for a Hub-owned approval, through + `POST /approve`. In v1 this files a Paperclip job. + +The admin app **must not** send a parental control change to the Hub. There is no +Hub route for it. The admin app calls the **internet control service directly**, +then shows the result. The Hub does not record, route, or run that change. + +In one line: **the admin app may send the Hub a query, a fast command, or a +Hub-owned approval decision — never a parental control change.** + +--- + +## 5. Reply shape (short note) + +The Hub returns the reply to the source and keeps it short (brief rule 6). The +shape comes from `source` and `want` in `docs/hub-api.md`, section 9. This file +does not change the reply shape. + +--- + +## Change log + +| Version | Date | Change | +|---|---|---| +| v1 | 2026-10-10 | First draft. Topic map, overlap rule, never-route list, admin app line. |