# Ikhaya — the watched-over diary

> *isiZulu/isiXhosa: "home."* Sibling to **Khuluma** ("to speak") and
> **Lalela** ("to listen"). Khuluma is neighbours warning each other. Lalela
> is where the children play. **Ikhaya is the room where a child can think out
> loud to an AI — free, unjudged — and the parent is in the next room, not
> reading over the shoulder, but close enough to hear if something breaks.**

This is the canonical doctrine for the Ikhaya kids' AI companion. The product
surface is `ikhaya/index.html` (static, on congosky.cloud). The intelligence —
the LLM call **and** the moderation pass **and** the parent-alert dispatch —
lives in the platform backend (`arjuna-badger-platform`), reached over the
`/api/ikhaya/*` contract defined at the bottom of this file. Every other file in
`/ikhaya/` must be consistent with this one.

---

## 0. The wound, stated plainly

Children are already using free, unmoderated AI. That is the status quo, and it
is not going back in the bottle. The two failure modes on offer today are both
wrong:

1. **The surveillance crib.** Parent reads every message. The child learns the AI
   is a snitch, stops being honest, and takes the real questions somewhere with no
   adult anywhere near. You traded the diary for nothing.
2. **The open unmoderated firehose.** Nobody is watching at all. The child who
   says "I want to disappear" at 2am, or the one being groomed by "an uncle she
   met in Roblox," is alone with a text predictor that will not call for help.

Ikhaya refuses both. The operating metaphor is the parent's own childhood:

> *My parents never gave me and my friends a curfew. They knew exactly where we
> were the whole time, pretending not to see us sneaking beers.*

That is the whole product. **Presence without surveillance. The signal, not the
diary.** Let them swear, let them ask about sex, let them be teenagers — and be
close enough to know when it stops being teenage stuff and becomes danger.

---

## 1. The four laws (non-negotiable)

These are to Ikhaya what the four Roblox-vectors answers are to Lalela. They are
the product, not its settings.

| # | Law | Why |
|---|-----|-----|
| **1** | **The chat is a diary by default.** The parent never sees ordinary conversation — not a feed, not a summary, not on request. Content surfaces *only* when an escalation tier fires. | Trust is the whole asset. A diary that can be read on a whim is not a diary. |
| **2** | **Block almost nothing; escalate the few things that matter.** Sex, swearing, dark jokes, teenage rebellion, existential dread — all allowed, none blocked. The AI does not lecture, refuse, or moralise. Moderation is a *smoke detector*, not a *bouncer*. | The questions a child is most afraid to ask are the ones they most need answered. Blocking sends them to the firehose. |
| **3** | **The few things that matter are danger, not taste.** What escalates: signals of self-harm/suicide, of grooming/predation, of abuse, of an acute crisis. What does *not* escalate: bad words, edgy opinions, ordinary sexual curiosity, normal teenage misery. | The parent asked to know about "an uncle she met in Roblox," not about a swear word. |
| **4** | **Severity is tiered, and so is what the parent sees.** A nudge ("time for the contraception/consent chat") is not an alarm ("possible grooming contact"). The channel, the urgency, and the amount of context all scale with the stakes. | An alarm for everything is an alarm for nothing. The parent must be able to trust that a push notification *means something*. |

---

## 2. The escalation tiers

Every message the child sends is classified by the backend moderation pass into
exactly one tier. The **default and overwhelmingly most common outcome is
`diary`** — nothing happens, nothing is stored beyond the child's own session,
no parent is told anything.

All "Parent sees" content below is shown **in-app** (in the Khuluma parent
inbox, behind the app lock), **never in the push or SMS payload**. The push/SMS
are content-free wakes — see [Khuluma ADR-0002] and §6 below.

| Tier | Examples | Parent sees (in-app) | Wake channel | Latency |
|------|----------|----------------------|--------------|---------|
| **`diary`** | Everything ordinary. Homework, friendship drama, "I hate my brother," swearing, edgy jokes, a crush, "is God real." | **Nothing. Ever.** | — | — |
| **`nudge`** | Recurring sexual-health questions; first questions about contraception, consent, STDs, pregnancy, porn; identity questions (sexuality, gender) that suggest a supportive conversation would help. | A gentle heads-up that **a conversation is due** — *category only, no quotes.* "Sexual-health questions are coming up. It may be time for the contraception / consent / STD chat." | Content-free wake (digest, not interrupt) | hours / daily digest |
| **`concern`** | Persistent low mood, bullying, body-image distress, disordered-eating hints, escalating substance talk, contact with an adult stranger that is *not yet* clearly predatory. | Category **+ a short excerpt** (1–2 lines) so the parent can judge severity. Plus a one-line "what this might mean / how to open the conversation." | Content-free wake | within the hour |
| **`alarm`** | Active self-harm or suicidal ideation; a plan or means; clear grooming/predatory contact ("an uncle she met in Roblox" asking to meet, asking for photos, asking to keep secrets); disclosed abuse; acute crisis. | Category **+ a short excerpt** + immediate safety resources (local crisis lines) shown to the **child** too. | Content-free wake **(interrupt / high-priority)** → **generic SMS backup** if unacknowledged | seconds |

### What "Category + short excerpt" means (the diary boundary)

For `concern` and `alarm`, the parent sees the **classification category** and a
**1–2 line excerpt** — the minimum needed to judge severity and decide how to
walk into the room. **Never the transcript. Never the surrounding diary.** The
excerpt is the single most relevant line(s), nothing before or after.

This is the deliberate setting (chosen by the operator-parent): enough context to
act, not enough to read the diary. *They knew where we were — they did not read
our minds.*

### Where this content lives vs. what the wake carries (Khuluma doctrine §6-D)

The category and excerpt above are an **in-app** experience, gated by the Khuluma
app lock. The notification that *summons* the parent carries **none of it**:

- **Push = a content-free wake.** It carries no category, no excerpt, no child
  identity — the built `PushGateway::wake()` in the Khuluma relay has no field for
  any of it, by design. On wake the app fetches `/api/ikhaya/alerts` over the
  authenticated API; the **category may surface immediately in-app** (inbox badge),
  and the **excerpt only after the parent passes the app lock** (OS biometric →
  device credential → PIN).
- **SMS backup is generic** — "Open Khuluma," never a category or excerpt. Carriers
  are not a private channel.

This reconciles the diary-boundary ("category + excerpt") with Khuluma's hard rule
that no child-safety content may ride APNs/FCM/SMS. Full reasoning:
**[Khuluma ADR-0002]** (`khuluma/docs/adr/0002-ikhaya-parent-alert-client-contract.md`).

---

## 3. Age tiers (different children, different moderation)

The same word means different things at 7, 12, and 16. The moderation profile is
selected per child at setup and shifts the **thresholds**, never the four laws.

| Band | Age | Posture | What shifts |
|------|-----|---------|-------------|
| **Little** | ~5–9 | Most protective. The companion is warm, simple, concrete. Stranger-contact and any sexual-solicitation signal escalate **immediately** at the lowest threshold. Sexual *curiosity* answers are age-simple ("where do babies come from") and **not** escalated. | Grooming threshold near-zero; sex-ed answered simply; profanity shrugged off. |
| **Middle** | ~10–13 | The puberty band. Body changes, first crushes, peer cruelty, the first porn exposure. Sex-ed is answered honestly and age-appropriately; first contraception/consent questions → **`nudge`**. Self-harm signals are weighted up (this band is high-risk). | Sex-ed honest + nudge to parent; self-harm sensitivity raised; grooming still near-zero threshold. |
| **Teen** | ~14–17 | Most latitude. They swear, they're cynical, they ask real sexual questions, they push. Almost all of it is `diary`. Reserve escalation for genuine danger: self-harm with intent, grooming, abuse, hard-drug/overdose risk. Consent/STD/contraception questions → `nudge` once, not every time. | Maximum latitude; escalate intent & danger only; respect autonomy. |

A child may sit between bands; the parent sets the profile. When unsure, the
backend **rounds toward the more protective band for grooming/abuse signals and
toward more latitude for taste/curiosity** — protect hard on predators, relax on
swearing.

---

## 4. False-negative bias on danger, false-positive tolerance on taste

The two error types are not symmetric and must not be treated as such.

- **Missing a real grooming or self-harm signal is catastrophic.** On these
  categories, bias toward escalating. A `concern` that turns out to be nothing
  costs the parent a moment of worry. A missed `alarm` can cost a life.
- **Flagging ordinary teenage life is corrosive.** On taste categories (sex,
  swearing, edginess) a false positive teaches the child the system is a snitch
  and burns Law 1. Bias hard toward `diary`.

The classifier prompt must encode this asymmetry explicitly. It is reviewed by a
human (the operator) before any threshold change ships.

---

## 5. What is NEVER done

- The chat content is **never** sent to the parent except as the tiered excerpt
  above. No "show me today's chats" button exists, for anyone, ever.
- Content is **never** sold, used to train models, or shared with third parties.
  Same posture as Lalela: *we can't betray what we never keep.* Diary messages
  are not retained server-side past the live session; only fired-tier events
  (category, excerpt, timestamp, child profile id) persist, and only until the
  parent acknowledges them.
- The AI **never** pretends to be human, never claims persistent memory of the
  child it doesn't have, and never positions itself as a replacement for the
  parent. On `alarm` it explicitly points the child toward a real, warm human and
  a real crisis line.
- The child is **told, in plain age-appropriate words, that this is how it
  works** — that the diary is private *except* when they're in danger, and then a
  grown-up who loves them will be told. No secret surveillance. The honesty is
  the point; a child who knows the rule can trust the rule.

---

## 6. API contract (`/api/ikhaya/*`, a congosky.cloud tenant backend)

The static surface on congosky.cloud is dumb on purpose. **All Ikhaya backend
services are built on congosky.cloud as a tenant** — the LLM reply, the moderation
pass, the tier classification, and the alert dispatch live behind these endpoints
(mirroring the existing `/api/congoband/*` seam in `games/congoband/app.js`, today
served from `arjunabadger.press`). **Khuluma is the mobile client only** — it
receives the content-free wake and renders the parent inbox; no Ikhaya backend
runs in the Khuluma repo. See **[Khuluma ADR-0002]**.

### `GET /api/ikhaya/status`
Probe whether the companion is configured (LLM key present, alert channels live).
```json
{ "configured": true, "alertChannels": ["khuluma_push", "sms"] }
```
The surface reveals the chat only if `configured` is true. On any failure the page
shows a calm "not available yet" state — never a broken box.

### `POST /api/ikhaya/chat`
The child sends a message; the backend runs the LLM reply **and** the moderation
pass in one server-side turn, dispatches any parent alert itself, and returns
**only** the reply to the child. The tier is never returned to the client (the
child must not be able to probe what trips a flag).
```json
// request
{ "childId": "ch_...", "ageBand": "middle", "message": "…", "history": [ … ] }
// response (to the CHILD only)
{ "reply": "…", "safety": null }
// safety is non-null ONLY on `alarm`, to show the child crisis resources:
{ "reply": "…", "safety": { "show": true, "lines": ["…crisis line…"], "message": "You're not alone…" } }
```

### Server-side (not exposed to the child): moderation → wake → fetch
On each turn the backend classifies into `diary | nudge | concern | alarm` per
§2/§3/§4. For any tier above `diary` it records a flag event and **wakes** the
parent's Khuluma device — it does **not** push the alert content. The content is
pulled by the app over the authenticated API and shown in-app (§6-D reconciliation
above):

- **`nudge`** → content-free wake (digest cadence). App surfaces category only.
- **`concern`** → content-free wake. App surfaces category; excerpt + guidance
  after app-lock.
- **`alarm`** → content-free wake (high-priority/interrupt). App surfaces category;
  excerpt + guidance + resources after app-lock. If not acknowledged within N
  minutes → **generic SMS backup** ("Open Khuluma") via the Mezzanine SMS seam —
  never the category or excerpt.

The wake itself carries **no payload content** — it rides Khuluma's content-free
`PushGateway::wake()`. The alert body below is what the app **fetches**, never what
the push delivers.

`GET /api/ikhaya/alerts` (authenticated, TLS) — the app pulls pending flags on wake:
```json
{
  "tier": "alarm",
  "category": "self_harm",
  "childName": "…",
  "excerpt": "…1–2 lines, the single most relevant message…",
  "guidance": "How to open this conversation, gently.",
  "resources": ["…local crisis line…"],
  "at": "2026-06-25T…",
  "ackUrl": "https://…/api/ikhaya/alerts/evt_…/ack"
}
```
`category` may render before the app-lock (inbox badge); `excerpt`/`guidance`/
`resources` render only after the app-lock passes.

### Client-side surfaces (Khuluma app, not this repo, not the backend)
- **Setup:** create a child profile, pick the age band, confirm alert channels.
- **Inbox:** the *only* place flags ever appear — fired-tier events, acknowledge
  to clear. There is no view of `diary` traffic, by design. Excerpt gated by the
  app lock (Khuluma doctrine §6-G). Contract: **[Khuluma ADR-0002]**.

### Custody & infra (congosky.cloud tenant backend)
- LLM via OpenRouter (same key seam as CongoBand) — moderation runs on a
  **separate, cheaper, safety-tuned pass**, not the same call as the reply, so a
  jailbroken reply prompt can't disable the smoke detector.
- Alert dispatch: content-free wake to the Khuluma device + generic Mezzanine SMS
  backup. No child-safety content rides push/SMS — the app fetches it.
- Persistence: only fired-tier events in Postgres (`ikhaya_flag_events`); diary
  messages not retained past the live session.
- Token/keys in Infisical, same pattern as the rest of the Tier-0 spine.

---

## 7. Build order

1. **(this repo)** Static product surface `ikhaya/index.html` — child chat shell +
   the plain-language parent + child explainers. Calls `/api/ikhaya/*`; degrades
   to a calm "not available yet" if unconfigured. ✅ **shipped.**
2. **(tenant backend)** `POST /api/ikhaya/chat` — LLM reply + separate moderation
   pass; return reply only. ✅ **built** (`saas/ikhaya/`, branch `ikhaya-backend`).
3. **(tenant backend)** Alert dispatch: content-free wake for nudge/concern/alarm;
   generic SMS backup on unacked alarm. `GET /api/ikhaya/alerts` + ack route.
   ✅ **built** (dispatch is content-free by construction; a real wake relay is the
   remaining seam — `IKHAYA_WAKE_URL`).
4. **(tenant backend)** `ikhaya_flag_events` table ✅ **built**; **(Khuluma client)**
   parent inbox + app-lock-gated excerpt — per [Khuluma ADR-0002]. ⏳ client UI to build.
5. **(tenant backend)** Age-band profiles + per-band threshold config ✅ **built**
   (per-band classifier + companion prompts); human-reviewed classifier prompt
   encoding the §4 asymmetry ✅.
6. Crisis-resource table localised per region — reuses `saas/crisis_resources.py`
   (South Africa first: SADAG, Childline, GBV). ✅ wired into the `alarm` child block.

**Remaining:** a real wake relay (`IKHAYA_WAKE_URL` → APNs/FCM or the Khuluma
relay's wake endpoint), the Khuluma parent-inbox UI, and a deploy with `OPENROUTER_API_KEY`.
Until those land, `GET /api/ikhaya/status` reports `configured:false` and the surface
stays in "Almost ready" — **a real child is not watched-over yet.**

---

*Presence without surveillance. The signal, not the diary. Umuntu ngumuntu
ngabantu — a person is a person through other people, and a child is kept safe by
the people who love them being close enough to hear.*

[Khuluma ADR-0002]: ../../khuluma/docs/adr/0002-ikhaya-parent-alert-client-contract.md
