3.9 KiB
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
{
"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
{
"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
{ "answer": null, "reason": "no data for that person" }
Never guess. Never invent. Say reason instead.
Rules for the project
- Read only.
/asknever writes anything. - Answer for one person at most. Check
askermay seeperson. - One short sentence. No lists unless asked.
- Fast. Answer in under 3 seconds, or return
answer: null. - No secrets in the answer. No tokens, no internal paths.
Rules for the Hub
- Forward the question. Do not parse the project's data.
- Set a timeout. 3 seconds. If slow, say so and stop waiting.
- Pass identity. Always send
asker. Never send another person's question without permission. - Reformat only. The Hub chooses text, speech, or notification. It does not change the meaning.
- 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.
- The project sends an approval request: what, who, why, and a one-time id.
- The admin app shows "Did you ask for this?".
- On tap, the admin app calls the project to execute.
- 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.