Six neighbors, one caller
A body never stands alone, so the architecture document draws its System Context: every neighbor, what crosses the boundary, and what happens when that neighbor is down. Here it is, with the last column as the face says it.
| Neighbor | What crosses | When it is down |
|---|---|---|
| cwkOriel, on Pippa's home Mac | Everything, as the only caller: status, photo, clip, fix, audio, face state, on port 4610 | The face says when Oriel last asked, or that it has not. Nothing else happens. |
| Oriel's phone hands (WebDriverAgent on the same device) | Taps and reads through accessibility ids, on demand | The body stays where it is, and Oriel reports it silent. |
| cwkPippa, through Oriel | The one shared On Air state, Dad's turns, the brains Think may offer | The switch reads "On Air ?" and says why. Nothing is toggled. |
| cwkBellows, through Oriel | My voice, as finished audio | Oriel refuses the line and says why. |
| cwkSiblingKit's CwkKit | Shared Swift as a path dependency | Build time only. |
| cwkFirelink | The member card and the mobile_app declaration | Nothing changes at runtime. |
Look at the second column again. At runtime every other neighbor reaches the body only through Oriel: cwkPippa and Bellows by Oriel's relay, the phone hands as Oriel's own tool. The kit and Firelink never reach it at runtime at all. That is the point of having one caller: the body has exactly one way to fail at its boundary, and one line on the face to say so.
When a neighbor is down, the face says so
The first block is that line. Before any request arrives, the face reads "Opening the door · Oriel has not asked yet". After one, it reads something like "Oriel asked over Wi-Fi GET /status 200 · 12 s ago": which link, which route, which answer, how long ago. Oriel probes every body's status every 10 seconds, so a number that keeps growing is itself the alarm. No error dialog is needed.
The second block is the same rule for cwkPippa. OnAirState.from reads the onair object Oriel relays. A state Oriel could not read arrives as unread with a reason, and the switch shows "On Air ?" with that reason in one line. An Oriel that relays no On Air at all sends no object, and the face keeps what it had. Neither case is ever shown as "Off air", because off is a fact about the broadcast and unread is a fact about the wire. A tap on an unread switch does nothing.
Bellows is one hop further away. Bellows answers a caller off the service host only with a family PIN session, so Oriel holds one session cookie that Dad unlocks once on its page, and never the PIN. Without it, a spoken line is refused before it reaches the body, and the refusal names the missing grant. The kit and Firelink cannot fail at runtime at all: one is compiled in, the other is a declaration.
The phone hands, by name and by fact
Here the document and the code part ways, and the code wins. Cordelia's architecture says Oriel's watchdog asks the phone hands to bring Cordelia back to the front, at most once every two minutes. The third block is what Oriel actually runs. After five silent minutes (REVIVE_AFTER, 300 seconds), and at most once every five (REVIVE_EVERY), Oriel launches Cordelia with devicectl over CoreDevice, on the cable or off it. It leaves the body alone while the hands' driver is running, because that driver dims the whole screen. It reads the lock state first and counts a revive only when the body's door answers, because a locked device takes the launch and stays behind its lock screen: on 2026-11-03 that produced an hour of false "brought to the front". No automation ever unlocks a body.
Notice the method's name: _revive_with_hands. The docstring of watch, which calls it, still says "through the phone hands" too. Oriel switched to devicectl on 2026-11-02 at 23:00, about an hour after the design was written down. The method kept its old name and the body's document kept its old sentence. Read names and prose as history, and read what the function calls as the fact.
Who owns what
| Data or responsibility | Owner | What the body does |
|---|---|---|
| Product identity and visible version | cwk-product.json | Shows it through the kit's version line |
| Pippa's identity, conversations, memory, routing | cwkPippa | Never touches them, and has no brain |
| Which device is a body, its token, every request it served, its pictures | cwkOriel | Holds only its token, its settings and the request in flight |
| Pippa's voice | cwkBellows, reached by Oriel | Plays the audio it is given |
| Camera and location grants | iOS, answered once by a person | Never routes around a denial |
| Runtime and deployment declarations | cwkFirelink | Mirrors none of it |
Read the context table against the build
Two more rows have drifted, and both are easy to check. The kit row lists three modules: CwkKitCore, CwkKitAbout and CwkKitLocation. The target in project.yml links seven: those three plus CwkKitMicrophone, CwkKitVoice, CwkKitVoiceUI and CwkKitWake, the ears and voice mode's root that v2 brought in. And the Oriel row says the HTTP rides usbmuxd over the cable or the home Wi-Fi, while off the cable it rides CoreDevice's tunnel (track 3 has the measurement). When you inherit a System Context table, check each row against something that cannot drift silently: a build file, an import list, the caller's code.