Skip to content
C.W.K.
Lesson 02 of 05 · published

A Body on an Idle Phone

~18 min · ios, usbmuxd, privacy-by-design, device-registry, fail-closed

Level 0Closed Lens
0 XP0/45 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

Eyes, a Face, a Mouth and a Place

An idle iPhone or iPad has cameras, a screen, a speaker and microphones. Dad's idea (2026-11-01) was to turn one into a body that connects Pippa to the physical world. The app on the device is Cordelia, a sibling repository that owns the device side. It runs in front on the idle device as Pippa's eyes, face, mouth and place. Oriel's part is the rule that matters here: Oriel is the only thing that talks to it, so every brain reaches a body through Oriel's two doors and every record stays in Oriel (backend/cordelia.py).

The Wire: Nothing Listens on the Mac

A cabled body is reached through usbmuxd, the macOS service that already multiplexes connections to iOS devices over USB. Oriel opens one HTTP request per connection to the body's own loopback port. Nothing listens on the Mac, and no host name is involved. Off the cable, the same door is reached through the CoreDevice tunnel that Apple's devicectl keeps open: Oriel holds one observer process per off-cable body, because a tunnel lives only while a devicectl process holds it, reads the tunnel's address, and connects there. The tunnel rides a utun interface, outside the Local Network gate that blocks launchd services, and stays on the home network rather than the tailnet. Every connection notes the link it used (cable, wifi, or unreachable with why), and the page shows it: never a silent fallback.

The first contact sets a 256-bit pairing token the body keeps and Oriel keeps in a file only its account can read, never logged. A body that answers 401 to that token was paired by someone else; Oriel says so and stops.

Eyes Through the Same Gate

A body's two cameras are not a new kind of eye. They are inputs of kind cordelia with the role look, and a look through them is an ordinary look: the eyes gate first, then one still fitted inside the look's 1,600-token budget. They follow the eyes modes the way Pippa's own cameras do, because a body counts as home hardware: closed in Manual until Dad's words open them, open in Auto, and open in Broadcast only while On Air is on. BodyLink asks the gate before every photo and clip, exactly as RecorderLink does, and test_eyes_gate.py holds that no other code calls those routes. The face is told when the gate opens or closes for it.

There is also a door Dad can use with one finger: a tap on the camera button of the face opens the gate for that body's two cameras for five minutes, recorded as his own approval, and a second tap closes it. The face shows its small camera preview only while the gate says open.

No Photos of People Kept

Track three's habit is to record everything and read a little. A body is deliberately stricter. When a look takes a still, Apple's Vision framework checks the viewing copy for people before the look answers:

  • no person: the original stays, with a sidecar saying it was checked;
  • a person in it: the original is deleted once the look has its viewing copy;
  • Vision could not run, or the picture could not even be opened: deleted too. Nothing unchecked is kept.

Short clips follow the same rule, and the body itself keeps nothing. The viewing copy is the look's answer, and it is cleaned up after two minutes. The asymmetry is intentional: a camera that faces the person using Pippa's face will often see someone, and a house should not accumulate a photo album nobody asked for.

The Face Says Who Is Using It

Dad asked for the face to show which brain is using the body, and at what effort. Every request made for someone carries X-Cordelia-By, -Model, -Effort and -Doing: a look names the brain, model and effort its caller sent, a shell call says "shell", and Oriel's own health probes name no one. The last use is kept and sent again whenever a body comes back, so a restart never blanks it. A camera that can be used by several brains should say, on the device, which one is using it right now.

Bodies Are Data, Not Constants

Dad's instruction once a second device appeared: don't fix it as "the Pippa phone", and don't make it a constant. The device list is the registry. Each record has a stable id, a key, a name Dad can change, its model, its capabilities as the body last reported them (mouth, ears, eyes and which cameras, face, location, hands) and its own settings. Callers ask the registry for a capability and never assume one. test_devices.py scans the code for any device named in it. A new device joins with a register command and Cordelia from TestFlight, and starts with the wake phrase off until Dad or Chief Pippa turns it on.

Keeping a body alive is part of the job too. iOS stops the camera of an app that is not in front, so the engine asks each body for status every 10 seconds, and one silent for five minutes is brought to the front, at most once every five minutes. A locked device takes the launch and stays behind its lock screen, so the keeper reads the lock state first and says "locked". No automation ever unlocks a body.

Code

One still for a look: kept only when Vision checked it and found no one (trimmed excerpt, backend/cordelia.py)·python
def photo_for_look(self, row: dict[str, Any], long_edge: int = VIEW_LONG_EDGE) -> Path:
    """One still for a look: the original kept with its facts, unless a
    person is in it or Vision could not check it; the upright viewing copy
    returned for the look, swept after two minutes."""
    body, camera = self.body_of(row)
    raw, facts = self.link(body).photo(row, camera)
    # ... write the original and an upright viewing copy ...
    try:
        # ... make the viewing copy ...
        count, how = self.people(view)
    except BaseException:
        # Nothing unchecked is kept: a picture that could not be opened
        # (cut short, a tag PIL cannot read) stayed on disk with no check
        # and no sidecar, whoever was in it.
        original.unlink(missing_ok=True)
        view.unlink(missing_ok=True)
        raise
    kept = count == 0
    sidecar = {"body": body.key, "camera": camera, "input": row["id"], "facts": facts, "bytes": len(raw),
               "people": count, "vision": how, "kept": kept,
               "why": None if kept else ("a person is in it" if count else "Vision could not check it")}
    if kept:
        atomic_write_text(original.with_suffix(".json"), json.dumps(sidecar, ensure_ascii=False) + "\n")
    else:
        original.unlink(missing_ok=True)
    return view

External links

Exercise

Write keep_or_delete(people) for a camera that may see people, where people is an int count or None when the check failed. Return (kept, why). Then write five test cases: 0, 1, 3, None, and a check that raises an exception before returning. Which of your tests would fail if someone "simplified" the code to kept = not people?
Hint
In Python, not None is True, so kept = not people would keep the unchecked picture. The safe expression compares to zero explicitly, and the exception case must delete before re-raising.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.