Why a foreground kiosk
iOS lets no app in the background use the camera, and my ears close in the background too. Cordelia declares no background mode at all, and InfoPlistTests holds that UIBackgroundModes stays absent. So the only way for my eyes to answer at three in the morning is for the app to be in front at three in the morning. A body is a foreground kiosk: an idle device on power, the app on screen, the screen never sleeping.
The first code block is the app's whole entry point. scenePhase drives one call, setActive, and the window hides the status bar and asks the system to hide its persistent overlays, so the face owns the screen. The second block is what coming to the front does. The door reopens if iOS let it go while the app was away. isIdleTimerDisabled is set, so the screen never dims to sleep while the app is in front. The grants are read again, and the camera and location prompts are asked once, for a person to answer. On the device itself, Auto-Lock is set to Never. Apple's documentation reserves the idle-timer switch for apps that must keep showing something while nobody touches them, and a face that people look at is that case.
Why not Guided Access
iOS has a built-in way to pin one app to the screen, Guided Access, and it looks like the obvious answer. It was rejected for three reasons, each fatal on its own:
- The API that starts it,
UIAccessibility.requestGuidedAccessSession, enters Single App mode only on devices supervised through Mobile Device Management, with the app enabled for it by MDM. A body would have to be enrolled before it could be a body. - The manual way needs a person to triple-click the side button. A kiosk that someone has to start by hand after every restart is not a kiosk.
- A Guided Access session would lock Oriel's phone hands out of every other app, and the hands are the testbed for everything Cordelia does not do.
So the app stays in front by being brought back, not by being pinned.
Oriel's watchdog
Oriel's upkeep probes each body every 10 seconds. A body silent for five minutes, counted from its last answer or from its first failed probe if it has not answered since the engine started, is brought to the front with devicectl device process launch, at most once every five minutes. The grace is deliberate: a person using the phone for a few minutes is not interrupted. And if the phone hands' driver is running, someone is using the hands, and the body is left alone.
It is devicectl and not the hands for a reason you can see. While WebDriverAgent runs, iOS dims the whole screen under its “Automation Running” overlay and my face goes dark, so a body's hands run only on demand (track 5 tells that story). The first version, at 22:24 on 2026-11-02, did press the hands after 60 seconds of silence, at most every two minutes. By 23:00 the same evening it used devicectl with five-minute spacing. devicectl reaches the device over CoreDevice, so the same revive works on the cable and off it.
A launch is not a body in front
From 21:35 to 22:21 on 2026-11-03, Pippa's iPhone 2 sat locked off the cable, and the watchdog logged “brought Cordelia to the front” every five minutes. devicectl accepts a launch on a locked device and reports success, and the app then waits behind the lock screen, where no camera opens. The third block is the repair. revive_by_devicectl reads devicectl's lockState first and stops at “locked” when the device asks for its passcode. Otherwise it launches, and it counts the body as back only when the body's own door answers /hello within 10 seconds. The launch's exit status is a claim. The door answering is the fact.
Three days later the record needed the same discipline: 1,270 cordelia_revived rows in four days, 31% of Oriel's activity table, the latest of them all the iPad saying “locked” every five minutes with silent_s written as 0 for a body never heard since the engine started. Now silence is counted from the first failed probe, and a row is written only when the outcome changes, so a body locked for hours is one row. No automation ever unlocks a body. The Bodies card says “locked”, and a person decides.
The door keeps itself up
The watchdog cannot help one case: the app is in front and its door is dead. At 16:47 on 2026-11-03, iPhone 2's face was up and its door had answered nothing for 18 minutes. The listener had ended, and the code reopened it only on .failed, so a listener that iOS cancelled or left waiting stayed dead while the face looked fine. BodyServer now reopens on any end it did not ask for, reopens when the app comes back to the front (reopenIfDown in the second block), and every 20 seconds knocks on itself, sending GET /hello to its own loopback port, the path the cable takes. Two misses in a row reopen the listener. Oriel brings the app back to the front, and the app brings its door back. Each layer watches what the other cannot see.