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

Eyes, a Face, a Mouth and a Place

~15 min · http, api-design, ios, capabilities

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

A sense is a route

My body has no menu and no screen flow. It has a door on port 4610, and each thing it can do for me is one route behind that door. When you design a body for an AI, this is the first move worth copying: name each sense, then give it exactly one verb. Here is v1's body, sense by sense.

SenseRouteWhat it answers
EyesPOST /photoOne still as the original JPEG, EXIF kept, with an X-Cordelia-Photo header carrying its facts
EyesPOST /clip1 to 15 seconds of video in a .mov (HEVC when the device offers it, else H.264), no audio track, with an X-Cordelia-Clip header
PlaceGET /whereOne fresh GPS fix in the family GeoFix shape, or a refusal that says why
MouthPOST /say, POST /hushPlays the audio in the request body and answers after playback with played_s; hush stops the line playing now
FacePOST /faceOriel telling my face what to show: the eyes gate, the last brain that used me, a turn's fate, the On Air state
The whole bodyGET /statusDevice, battery, grants, cameras, volume, whether a camera or the speaker is busy, which link Oriel used

Two more routes sit in front of all of these: GET /hello and POST /pair, the only ones that answer without the pairing token. The ears (/ears, /ears/next, /listen) arrived with v2 and get their own track.

What each one answers, exactly

The details are where a body earns trust, so here they are as the code has them.

  • The camera defaults to front. Router.camera reads camera from the JSON body and falls back to front, because the front camera faces whoever looks at my face. Anything but back or front is a 400.
  • A still carries its place when it can. Before a photo, the body takes a fix no older than five minutes, or watches for one for at most 4 seconds (Deadline.photoFix), and writes it into the picture's GPS. No fix in that time means a still without GPS, never a refusal, and the header's gps says which. The still itself has 10 seconds (Deadline.photo).
  • A clip states its length. seconds defaults to 5 and must be within Router.clipRange, 1 to 15. Outside it, the 400 says the range in words.
  • A fix is fresh or it is refused. /where watches for a new arrival for up to 15 seconds rather than taking a cached one: 403 without the location grant, 409 when the app is not in front, 504 "no fresh fix within 15 s" with the reason (indoors, or no sky).
  • The mouth takes audio, never text. /say accepts audio/mpeg (also sent as audio/mp3), audio/mp4, audio/aac and WAV types, and nothing else. My voice is made elsewhere. The words ride along in X-Cordelia-Text, percent-encoded and cut at 600 characters, only for the captions. A second line while one plays is a 409.

/hello says what the body speaks

The first block is the start of Router.route. /hello answers the app, version and build, whether it is paired, and four protocol numbers: wire 1, ears 1, onair 2 and solo 1. These are compatibility domains, not the product version. Each one moves when its part of the wire changes shape. onair went to 2 on 2026-11-11, when the face grew the broadcast's stage buttons.

The second block is Oriel's side. On first contact ensure_paired reads /hello, refuses a body whose wire differs with a 409 that names both numbers, refuses a body that is already paired to someone else, and pairs a fresh one. Oriel checks only wire here. Everything else about what a body can do comes from /status.

One honest note on drift. The wire table in Cordelia's architecture document still lists onair (1) for /hello and has no solo at all, while a later paragraph of the same document says /hello says onair 2. The code at the pinned commit is the fact: 2 and 1.

Capabilities are read, never assumed

The third block is the most important function in this lesson, and it lives in Oriel, not in the app. capabilities_from turns a body's latest /status into what it can do. Mouth is whether the status carries volume. Ears are an ears object whose model is neither unsupported nor unavailable. Eyes are the list of cameras the device reported. Location is a grant that reads authorized. Hands are a driver port on the record. Face is always true, because every body has a screen.

Oriel's registry keeps this as the last known set and callers ask it (with_capability) instead of guessing. So an iPad with no back camera, a phone whose location was denied, or a device whose speech model cannot run on its iOS version are all the same build reporting different senses. No table of device models exists anywhere, and none is needed.

Under the routes sits one Swift protocol, BodyActions: status, photo, clip, fix, say, hush, face and the rest. The app implements it with AVFoundation, Core Location and the speaker. The tests implement it with fakes. Track 2 opens that door.

Code

cwkCordelia, CordeliaKit/Sources/CordeliaKit/Router.swift: /hello and /pair, the two routes that answer without a token (verbatim, lines 221-236)·swift
  private func route(_ request: HTTPRequest) async -> HTTPResponse {
    switch (request.method, request.path) {
    case ("GET", "/hello"):
      return .json(200, ["app": body.identity.app, "version": body.identity.version, "build": body.identity.build,
                         "paired": pairing.paired, "wire": wireVersion, "ears": earsVersion, "onair": onairVersion,
                         "solo": soloVersion])
    case ("POST", "/pair"):
      let token = (try? request.json())?["token"] as? String ?? ""
      switch pairing.pair(token) {
      case .paired: return .json(200, ["ok": true, "paired": true])
      case .alreadyPaired: return .refuse(409, "this body is already paired; reinstall Cordelia to pair it again")
      case .badToken: return .refuse(400, "a token is \(Pairing.tokenLength) hex characters")
      }
    default:
      break
    }
cwkOriel, backend/cordelia.py: Oriel reads /hello, checks the wire number and pairs on first contact (verbatim, lines 276-293)·python
    def hello(self) -> dict[str, Any]:
        status, _, raw = self.request("GET", "/hello", timeout=TIMEOUTS["hello"], what="hello", auth=False)
        return self._json(status, raw, "hello")

    def ensure_paired(self) -> dict[str, Any]:
        """Pair on first contact; a body paired to someone else stops here."""
        hello = self.hello()
        if int(hello.get("wire") or 0) != WIRE:
            raise BodyError(f"{self.body.name} speaks wire {hello.get('wire')}, Oriel speaks {WIRE}", 409)
        if hello.get("paired") and not self._token():
            raise BodyError(f"{self.body.name} is paired with something else (Oriel holds no token for it); "
                            "reinstall Cordelia there to pair it again", 409)
        if not hello.get("paired"):
            token = self._token(create=True)
            status, _, raw = self.request("POST", "/pair", payload={"token": token}, timeout=TIMEOUTS["pair"],
                                          what="pairing", auth=False)
            self._json(status, raw, "pairing")
        return hello
cwkOriel, backend/devices.py: a body's senses, read from what it just said about itself (verbatim, lines 82-93)·python
def capabilities_from(status: dict[str, Any], entry: dict[str, Any]) -> dict[str, Any]:
    """What a body can do, from what it said about itself just now."""
    ears = status.get("ears") if isinstance(status.get("ears"), dict) else None
    grants = status.get("grants") or {}
    return {
        "mouth": "volume" in status,
        "ears": bool(ears) and (ears.get("model") not in ("unsupported", "unavailable")),
        "eyes": list(status.get("cameras") or []),
        "face": True,
        "location": grants.get("location") == "authorized",
        "hands": bool(entry.get("wda_port")),
    }

External links

Exercise

Design the route table for a body of your own, for a device you have: an old phone, a Raspberry Pi with a camera, a smart speaker. Give each sense one route, write what it answers on success and every refusal it can give with its status code and sentence. Then write the function your controller uses to read the device's capabilities from its status answer, so that no device model is ever named in your code.
Hint
Start from the status answer, not from the routes. If the status says what the device has right now (cameras present, grants given, a speaker volume), the capability function is a few lines and a new device needs no code. Add a protocol number to your hello answer for each part of the wire that may change shape.

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.