Decode the people list from each project's /capabilities and set the /ask person field from a whole-word, case-insensitive name in the question when the caller did not name a person. Match only the routed project's people, so a name from another project does not leak across. Fall back to asker when no name matches. Co-Authored-By: Paperclip <noreply@paperclip.ing>
9.2 KiB
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:
- Never-route check. If the request is a control action (section 3), return
status: "refused". Stop. Do not ask an LLM. - Read capabilities. Read the topic list and the people list from each
project's
/capabilities(docs/ask-contract.md). - Read this file. Read the topic map (section 1) and the overlap rule (section 2).
- 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.
- Route. Send the request. The action call holds no LLM (brief rule 4).
When the caller did not name a
person, set it from the question: match a name against the routed project'speoplelist, case-insensitive and whole word. When no name matches,personstays theasker.
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 |
active service |
| 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 |
|---|---|---|
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:
- A domain question goes to the project that owns the data. Do not fanout for one topic.
- A day summary fans out. Merge to one short sentence.
- Two projects with equal claim, and the preference table does not decide, use
clarify. Ask one short question. Example: "School or email?" - Never fanout to more than three projects.
- 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:
- Queries and fast commands — the same as any way in, through
POST /ask. - 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. |