# 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 three endpoints on its own port:

```
GET  /health          -> {"ok": true}
GET  /capabilities    -> the topics this project can answer
POST /ask             -> answer a question about this project's data
```

## Capabilities (for routing)

The Hub reads this list to choose a project.

```json
{ "project": "email_agent", "topics": ["email", "inbox", "mail"], "people": ["sam"] }
```

Keep the topic list short and plain. The Hub uses these topics plus `routing.md`.
Do not put private data in this list.

### 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. `/capabilities` never writes.
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)

Some write actions need a human yes. This is one shared pattern, not one per
project.

1. The project (or the Hub) 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:

- a parental control change (cut or restore the kids' internet, change a schedule),
- a Paperclip job.

Approval is **not required** for a calendar write. Email and school may add events.
These are not dangerous.

**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`.
