196 lines
9.0 KiB
Markdown
196 lines
9.0 KiB
Markdown
# 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. |
|