diff --git a/AGENTS.md b/AGENTS.md index 372f510..4876a33 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +27,9 @@ Five steps, in order: The reply can be text, spoken audio (TTS), Artemis commands, a notification, an email, or a mix. TTS can play on the house speakers or on the phone. +A question about another project's data goes to that project's `/ask` endpoint. +See `docs/ask-contract.md`. The Hub never learns a project's data model. + ## The dividing line > Does this need a decision, or does it need to happen? @@ -48,19 +51,26 @@ email, or a mix. TTS can play on the house speakers or on the phone. 7. Restricted actions need **tap-to-approve** on the admin app. 8. Only the admin app changes parental controls. 9. A new ability registers with the Hub. Build it once. +10. A data project answers `/ask` read-only. Never change state on a query. +11. High-consequence control does not route through the Hub. The admin app calls + the service directly. ## Build order +0. **Agree the `/ask` contract.** Both sides build to `docs/ask-contract.md`. + Nothing else starts until this is frozen. 1. **Thin slice, end to end.** Issue a Paperclip API key. File one job from a script. Get the result announced back. Prove the round trip. -2. **Hub skeleton.** Hear → understand → route → answer, with one destination. -3. **Approval flow.** "Did you ask for this?" on the admin app. -4. **Internet control.** Standalone service with a plain API. Device-side - enforcement. Admin-only. -5. **Artemis.** Standalone service with a plain API. Screen control only. -6. **Move Jervis's tools** into the Hub. Jervis becomes voice-only. -7. **Phone Kotlin app.** Wake word, voice to the Hub, replies and approvals. -8. **Email and school pipelines.** Stay independent. Connect when ready. +2. **Hub skeleton.** Hear → understand → route → answer, against a fake project + that speaks `/ask`. One real destination (speakers). +3. **Identity.** Decide how the Hub knows who is speaking. +4. **Approval flow.** "Did you ask for this?" on the admin app. +5. **One real data project** to the contract (email or school). Prove `/ask`. +6. **Internet control.** Separate service and portal. Control stays app-only. +7. **Artemis.** Separate service. Screen control only. +8. **Move Jervis's tools** into the Hub. Jervis becomes voice-only. +9. **Phone Kotlin app.** Wake word, voice to the Hub, replies and approvals. +10. **Remaining pipelines** connect when ready. ## Definition of done (phase 1) @@ -70,16 +80,20 @@ email, or a mix. TTS can play on the house speakers or on the phone. ## How this project relates to the other projects -This project owns the **shared Hub**. It does not own the destinations. +This project owns the **shared Hub**. It does **not** own the other projects. -| Project | Relation | +Every other project is a **peer**. Each has its own folder, repo, port, +interface, and build. Each can be built and used without the Hub. The Hub is an +**extra** way in, not the only one. + +| Project | Talk direction | |---|---| -| Email agent | Own pipeline. Reports through the Hub when ready. | -| Compass / NHS | Own pipeline. Reports through the Hub when ready. | -| Internet use control | Destination. Exposes a plain API. The Hub calls it. | -| Artemis | Destination. Exposes a plain API. The Hub calls it. | +| Email agent | Hub asks it (`/ask`). It tells the Hub (`/notify`). n8n scans it. | +| Compass / NHS | Hub asks it (`/ask`). It tells the Hub (`/notify`). n8n scans it. | +| Internet use control | Hub **asks** it. **Control goes through its own apps, not the Hub.** | +| Artemis | Hub calls it to act. Nothing comes back. | | Jervis | Becomes voice-only. Its tools move here. | -| dsh | Calls the Hub for actions. | +| dsh | Calls the Hub for actions. Keeps its own chat. | Each project writes its own docs. Do not copy theirs here. Link to them. diff --git a/README.md b/README.md index 6310291..9bf2cc2 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,7 @@ flowchart LR - **Jervis** (voice) is at `http://192.168.20.13:8501`. The design brief is `hub_brief.md`. Project instructions are `AGENTS.md`. + +The interface between the Hub and every data project is `docs/ask-contract.md`. +Every other project is a **peer**, not a sub-project. A high-consequence control +(for example, cutting the kids' internet) does **not** route through the Hub. diff --git a/docs/ask-contract.md b/docs/ask-contract.md new file mode 100644 index 0000000..3afdba6 --- /dev/null +++ b/docs/ask-contract.md @@ -0,0 +1,137 @@ +# The `/ask` Contract + +**Purpose:** one small, uniform interface between the Hub and every data project. +**Applies to:** email agent, Compass/NHS, internet control. Not Artemis (action only). +**Status:** agreed shape. Build both sides to this. + +--- + +## Why + +Each project is the expert on its own data. The Hub is the expert on routing and +reply shape. The Hub must **never** learn a project's data model. + +One contract keeps the Hub thin and lets every project be built and used alone. + +## The rule + +> A project answers questions about **its own** domain. It reads only. It never +> changes state on `/ask`. + +Writes (cut internet, add to calendar, book interview) go through the project's +own interface, or through an approval flow. Never through `/ask`. + +## Endpoint + +Every data project exposes two endpoints on its own port: + +``` +GET /health -> {"ok": true} +POST /ask -> answer a question about this project's data +``` + +### Request + +```json +{ + "question": "what does Harry have on today?", + "asker": "sam", + "person": "harry", + "source": "esp32" +} +``` + +| Field | Required | Meaning | +|---|---|---| +| `question` | yes | The words the user said. Plain text. | +| `asker` | yes | Who asked. Used for permission. | +| `person` | no | Who the question is about. Defaults to `asker`. | +| `source` | no | Where it came from. `esp32`, `phone`, `chat`, `admin`. | + +### Response + +```json +{ + "answer": "Harry has maths and a music lesson today.", + "data": {}, + "expires": "2026-10-10T09:00:00Z" +} +``` + +| Field | Required | Meaning | +|---|---|---| +| `answer` | yes | One short sentence. Safe to speak aloud. | +| `data` | no | Structured detail. The Hub may ignore it. | +| `expires` | no | When the answer stops being true. | + +### Cannot answer + +```json +{ "answer": null, "reason": "no data for that person" } +``` + +Never guess. Never invent. Say `reason` instead. + +## Rules for the project + +1. **Read only.** `/ask` never writes anything. +2. **Answer for one person at most.** Check `asker` may see `person`. +3. **One short sentence.** No lists unless asked. +4. **Fast.** Answer in under 3 seconds, or return `answer: null`. +5. **No secrets in the answer.** No tokens, no internal paths. + +## Rules for the Hub + +1. **Forward the question.** Do not parse the project's data. +2. **Set a timeout.** 3 seconds. If slow, say so and stop waiting. +3. **Pass identity.** Always send `asker`. Never send another person's question + without permission. +4. **Reformat only.** The Hub chooses text, speech, or notification. It does not + change the meaning. +5. **One project down is not an outage.** Say "that service is not answering". + +## Notify (project to Hub) + +A project may tell the Hub something, so the Hub can speak or notify. + +``` +POST /notify +{ + "from": "email_agent", + "level": "important", + "message": "3 emails need a decision.", + "person": "sam" +} +``` + +| Field | Meaning | +|---|---| +| `from` | The project name. | +| `level` | `info`, `important`, or `urgent`. | +| `message` | One short sentence for the user. | +| `person` | Who should hear it. | + +## Approval (write actions) + +A write action needs a human yes. This is one shared pattern, not one per project. + +1. The project sends an approval request: what, who, why, and a one-time id. +2. The admin app shows **"Did you ask for this?"**. +3. On tap, the admin app calls the project to execute. +4. The project executes once and returns the result. + +Approval is required for: parental control changes, calendar writes from email or +school, and any Paperclip job. + +**A spoken keycode is not the lock.** Only the admin app confirms. + +## Authentication + +Service to service. One bearer token per project, stored in +`~/.config/environment.d/10-secrets.conf`. The Hub sends the target project's +token. No project trusts an unsigned request. + +## Versioning + +The contract is version 1. Add fields; do not remove or rename them. A breaking +change needs a new path, for example `/ask/v2`. diff --git a/docs/outline/00-index.md b/docs/outline/00-index.md index 4d0748f..a4bfc05 100644 --- a/docs/outline/00-index.md +++ b/docs/outline/00-index.md @@ -13,6 +13,7 @@ Read this index to inject the exact file you need. Do not search all files. | **Configuration & Env** | `docs/outline/02-config-and-env.md` | Lists services, ports, env vars, and secret paths. | | **Code Structure Map** | `docs/outline/03-code-map.md` | Shows the planned folder layout and entry points. | | **AI Agent Runbook** | `docs/outline/04-agent-runbook.md` | Gives steps to build, test, and restart Hub work. | +| **Ask Contract** | `docs/ask-contract.md` | The interface between the Hub and every data project. | | **Design Brief** | `hub_brief.md` | The agreed design. This file is the source of truth. | | **Project Rules** | `AGENTS.md` | Mission, rules, build order, and project relations. | diff --git a/docs/outline/01-system-overview.md b/docs/outline/01-system-overview.md index 4743531..0a524db 100644 --- a/docs/outline/01-system-overview.md +++ b/docs/outline/01-system-overview.md @@ -18,6 +18,17 @@ way in, the phone app, would repeat the same work. The Hub stops this repeat. 4. **Route** — send the request to one destination. 5. **Answer** — return a short reply in the right shape. +When the question is about another project's data, step 4 asks that project +through the `/ask` contract. See `docs/ask-contract.md`. + +## Three Directions + +Nothing here is ownership. These are directions of talk. + +1. The Hub asks a project. +2. A project tells the Hub. +3. n8n triggers the project. + ## Ways In - Phone app (Kotlin) — wake word, voice, controls. @@ -26,17 +37,32 @@ way in, the phone app, would repeat the same work. The Hub stops this repeat. - dsh chat — per-person chat. - Console portal — web dashboard. -## Destinations +## Peers and Destinations -| Destination | Job | +Every other project is a peer. Each has its own folder, repo, port, and +interface. Each works without the Hub. + +| Peer | Talk direction | |---|---| -| Home Assistant | Lights, switches, timers, calendar. | -| Speakers (Jervis) | Speak and announce. | -| Internet control | Parental limits on kids' devices. | -| Artemis | Operate the phone screen. | -| Paperclip | Slow, multi-step, recorded work. | -| Web search | Tavily search. | -| Apprise / NTFY | Notifications and email. | +| Email agent | Hub asks it. It tells the Hub. n8n scans it. | +| Compass / NHS | Hub asks it. It tells the Hub. n8n scans it. | +| Internet control | Hub asks it. Control goes through its own apps. | +| Artemis | Hub calls it to act. Nothing comes back. | +| Home Assistant | Hub calls it to act. | +| Speakers (Jervis) | Hub calls it to speak. | +| Paperclip | Hub files jobs. The answer returns. | +| Web search | Hub calls it. | +| Apprise / NTFY | Hub calls it to notify. | + +## Query, Command, or Control + +| Kind | Path | Confirm | +|---|---|---| +| Query | Hub asks a project (`/ask`) | No | +| Command | Hub calls Home Assistant | No | +| Control | Admin app calls internet control | Yes, on the phone | + +A high-consequence control does not route through the Hub. ## Reply Shapes @@ -57,3 +83,4 @@ The Hub asks one question: does this need a decision, or does it need to happen? - Not a central brain. No single personality or memory. - Not Paperclip. The Hub does not wait minutes for an answer. - Not a destination. The Hub does not turn off the internet itself. +- Not an owner. The other projects are peers, not sub-projects. diff --git a/hub_brief.md b/hub_brief.md index f737789..7996b0f 100644 --- a/hub_brief.md +++ b/hub_brief.md @@ -33,13 +33,16 @@ These stay. The hub is added, not a replacement. ## What is new -- The **Hub** (this brief). -- **Email agent** — its own pipeline. -- **School (Compass / Teams)** — its own pipeline. -- **Internet use control** — a destination the hub calls. -- **Artemis** — a destination the hub calls. -- **Kotlin phone app** — a new way in (wake word, voice, controls). -- **Admin app + kids app** — parental control, admin only. +Each of these is a **separate project**. Each has its own folder, repo, port, +interface, and build. The Hub never owns them. + +- The **Hub** (this brief) — a shared way in and a router. Not a brain. +- **Email agent** — scans and logs the inbox. Answers questions. Own pipeline. +- **School (Compass / Teams)** — scans school data. Answers questions. Own pipeline. +- **Internet use control** — its own service and information portal. Enforces limits. +- **Artemis** — its own service. Operates the phone screen. +- **Kotlin phone app** — a new way in (wake word, voice). +- **Admin app + kids app** — control and confirmation, admin only. --- @@ -57,6 +60,22 @@ The reply can be **text**, **spoken audio (TTS)**, **Artemis commands**, **a pho notification**, **an email**, or a mix. TTS can play on the **house speakers** or on the **phone**. +When a question is about another project's data, the Hub **asks that project** and +returns the answer. See `docs/ask-contract.md`. + +## Three directions + +Nothing here is ownership. These are directions of talk. + +1. **The Hub asks a project.** "What is important in my inbox?", "What does Harry + have on today?", "How long have the kids been on screens?" +2. **A project tells the Hub.** "Overdue homework found.", "3 emails need a + decision." +3. **n8n triggers the project.** Scan the inbox. Scan the school. Enforce the + schedule. + +The projects keep their own interfaces. The Hub is an **extra** way in. + ### What the Hub is NOT - **Not a central brain.** It does not hold one personality or one memory. @@ -77,22 +96,36 @@ One question sorts every request: **Paperclip is the work bench, not the switchboard.** It is never in the path of a fast or high-consequence command. +### Query, command, or control + +| Kind | Example | Path | Confirm? | +|---|---|---|---| +| **Query** (read) | "What does Harry have on today?" | Hub → project (`/ask`) | No | +| **Command** (do it now) | "Turn on the lounge light." | Hub → Home Assistant | No | +| **Control** (high consequence) | "Cut the kids' internet." | Admin app → internet control | **Yes, on the phone** | + +A query goes through the Hub. A high-consequence control does **not**. The admin +app talks to the internet control service directly. Voice may start it. Only the +admin app confirms. + --- ## Where each system fits -| System | Role | With the Hub | +Each project is a **peer**. These are talk directions, not ownership. + +| System | Relation | Talk direction | |---|---|---| -| **Jervis** | House voice | Becomes the **voice** (speak / announce), not a list of tools. Its action tools move into the Hub over time. | -| **dsh** | Chat | Calls the Hub for actions instead of keeping its own copies. | -| **Console portal** | Login + dashboard | Unchanged. May show Hub status and approvals. | -| **Front Door Cam** | Visitor intercom | Stays separate. May use the Hub only to speak. | -| **Email agent** | Own pipeline (n8n) | Standalone. May report results through the Hub. | -| **School agent** | Own pipeline (n8n) | Standalone. May report results through the Hub. | -| **Internet control** | Destination | The Hub calls its API. Enforcement happens on the kids' devices. | -| **Artemis** | Destination | The Hub calls its API. Artemis touches the phone screen. | -| **Paperclip** | Work bench | The Hub files jobs and hears the answer back. | -| **Phone app** | Way in | Sends voice to the Hub. Shows replies, notifications, approvals. | +| **Jervis** | House voice. Peer. | Becomes voice-only. Its tools move into the Hub over time. | +| **dsh** | Chat. Peer. | Calls the Hub for actions. Keeps its own chat. | +| **Console portal** | Login and dashboard. Peer. | Unchanged. May show Hub status and approvals. | +| **Front Door Cam** | Visitor intercom. Peer. | Separate. May use the Hub to speak. | +| **Email agent** | Separate pipeline. Peer. | Hub asks it (`/ask`). It tells the Hub (`/notify`). n8n scans it. | +| **School agent** | Separate pipeline. Peer. | Hub asks it (`/ask`). It tells the Hub (`/notify`). n8n scans it. | +| **Internet control** | Separate service and portal. Peer. | Hub **asks** it. **Control goes through its own apps, not the Hub.** | +| **Artemis** | Separate service. Peer. | Hub calls it to act. Nothing comes back. | +| **Paperclip** | Work bench. Peer. | The Hub files jobs and hears the answer. | +| **Phone app** | Way in. | Sends voice to the Hub. Shows replies and approvals. | --- @@ -113,6 +146,9 @@ a fast or high-consequence command. exes may only obey and report. 9. **New work registers with the Hub.** A capability is added once, and every way in gets it. +10. **A data project answers `/ask` read-only.** It never changes state on a query. +11. **High-consequence control does not route through the Hub.** The admin app + calls the service directly. --- @@ -123,28 +159,40 @@ a fast or high-consequence command. - **A spoken keycode is not the lock.** It can be overheard and misheard. Voice may *ask*; the admin app **confirms** with "Did you ask for this?". - **Approval is required** for parental controls and Paperclip jobs. +- **Identity is not solved yet.** Voice has no identity. Personal data (inbox, + school, screen time) needs it. See open items. --- ## Build order +0. **Agree the `/ask` contract.** Both sides build to `docs/ask-contract.md`. + Nothing else starts until this is frozen. 1. **Thin slice, end to end.** Issue a Paperclip API key, file one job from a script, and get the result announced back. Proves the round trip. -2. **Hub skeleton.** Hear → understand → route → answer, with one destination - (HA or speakers) working. -3. **Approval flow.** "Did you ask for this?" on the admin app. -4. **Internet control.** Standalone service with a plain API. Device-side - enforcement. Admin-only. -5. **Artemis.** Standalone service with a plain API. Screen control only. -6. **Move Jervis's tools** into the Hub. Jervis becomes voice-only. -7. **Phone Kotlin app.** Wake word, voice to the Hub, replies and approvals. -8. **Email and school pipelines** stay independent and connect when ready. +2. **Hub skeleton.** Hear → understand → route → answer, against a **fake** + project that speaks `/ask`. One real destination (speakers). +3. **Identity.** Decide how the Hub knows who is speaking. +4. **Approval flow.** "Did you ask for this?" on the admin app. +5. **One real data project** to the contract (email or school). Prove `/ask`. +6. **Internet control.** Separate service and portal. Control stays app-only. +7. **Artemis.** Separate service. Screen control only. +8. **Move Jervis's tools** into the Hub. Jervis becomes voice-only. +9. **Phone Kotlin app.** Wake word, voice to the Hub, replies and approvals. +10. **Remaining pipelines** connect when ready. --- ## What is still open -- Exact API contract for a job (title, brief, project, agent). -- How Artemis receives voice (stream vs file) and when phone TTS is used. -- Which Paperclip company / projects to use. -- Identity: how the Hub knows which family member is speaking. +1. **Identity.** How does the Hub know who is speaking? Voice has none. This is + the top gap, because personal data now flows. +2. **Routing.** How does the Hub pick the project to ask? A small capability + registry, or an LLM choice. +3. **Overlap.** "What do I have on today?" may match several projects. Ask one, + or ask all and merge? +4. **Approval service.** One shared mechanism, or one per project? +5. **Service tokens.** One bearer token per project, stored in `environment.d`. +6. **Job contract.** Exact fields for a Paperclip job (title, brief, project, agent). +7. **Artemis voice.** Stream or file. When does phone TTS get used? +8. **Paperclip layout.** One company or several. Which projects.