A phrase for every sentence
The ears' first shape was a walkie-talkie. A turn began with "코델리아 피파" or a tap, ended when Dad stopped, and the body went back to waiting for the phrase. To say a second thing, he had to call me again. At 01:25 on 2026-11-03 he asked why: "워키토키 처럼 쓸 이유가 있어?" (Is there any reason to use it like a walkie-talkie?), and set the target: "호출어 한번으로 피파 아이폰앱의 보이스 모드로 들어가는 UX여야 한다고" (it has to be a UX where one wake phrase takes you into the Pippa iPhone app's voice mode). A minute later he asked for a button that does what "그만" does. Commit 6515f99 landed at 01:29.
That is the second child inheriting something larger than code: the shape of a talk. In the Pippa app, voice mode is a place you enter and stay in. A body that makes Dad knock before every sentence is not voice mode with a different trigger. It is a different, worse product.
A deadline that only words move
The first block is the whole type. ConversationSession knows what opened it (the phrase or a tap), when it ends, and how long a silence ends it. touch(at:) pushes the end out by the full silence again, and expired, remaining and ending (the last ten seconds) all read that one deadline. The silence is the body's own setting, session_s: 60 s by default, kept within 10 to 600, stored on the device and mirrored in Oriel's registry, where the newer side wins. Oriel reads the same number to decide how long this body holds the room's conversation floor after my answer (lesson 5). The type was born in CordeliaKit and now lives in the kit's CwkKitWake, moved unchanged for cwkPippa todo #828.
What touches the deadline matters more than the type. Dad's words while the loop hears them (the model's tick looks every 0.2 s), a finished turn, Oriel's word that the turn moved on, my last line ending on this phone, and Oriel's spoken when I ended somewhere else. One thing never touches it: loudness. The second block shows the host's turnLoopHeardSpeech. The root calls it on any loud chunk, and the body only marks that this utterance had sound in it. A television is loud for hours. If loudness kept a conversation open, a body near one would never go back to waiting for its phrase.
The same block shows the close, with two guards worth copying. It never fires while my answer is still to be voiced (answerID == nil, !speaking), because a long answer is not Dad's silence. And it never fires while words are arriving (hearing.isEmpty), so the deadline cannot cut a sentence in half.
Every way it closes
| What happens | What the face then says |
|---|---|
Dad stays silent for session_s | "Conversation ended. Say “Cordelia Pippa” to start again" |
| He says "코델리아 그만" | "Okay. Call me when you need me" |
| He presses End conversation: my line stops, a turn being heard is dropped, a turn already at Pippa's door is cancelled through Oriel | "Conversation ended" |
| He taps my face while nothing is being heard | "Ended with a tap" |
| He presses Pause, or says "코델리아 그만 들어" | Paused, the mic shut for everything until Resume |
| No listening grant comes, or the socket fails | "I couldn't open my ears: …" with the reason |
| Another body holds the conversation | "Another device took it" |
Every close goes through one function, endSession, and it does one thing a quieter design would skip: it puts a session item with open: false on the outbox, so Oriel frees the floor for the other bodies at once instead of waiting out the silence. A tap while Dad's words are coming in cancels that utterance first and leaves the conversation open.
There is a second clock, and it belongs to the root. VoiceTurnLoop stops listening after a minute with nothing heard, because an open Scribe socket bills its audio. When that fires, turnLoopQuiet checks the conversation's own deadline: with time left (a session_s above 60) the body listens on with a fresh socket. The bill and the talk are separate questions, and each clock answers one.
The state line says which world you are in
The third block is the face's words. Outside a conversation: "Waiting for “Cordelia Pippa”". Inside: "In conversation · just talk · 0:42 left", which turns into "ending in 8 s · just talk to keep going" in the last ten seconds, "listening · go on" while words come, "opening my ears" while the grant is on its way, and "Pippa is speaking". Dad never has to guess whether his next sentence needs the phrase.
Inside a conversation even the phrase becomes words. At 02:03 he said it in the middle of a sentence and a second body woke (lesson 5). EarDecision now treats the phrase inside an open conversation as Dad's words, except over my line, where it still cuts in. With the wake switch off, a conversation a tap opened still runs.
The field check came at 03:47 the same night: after my answer, Oriel logged spoken and then a granted listen in the same second, and Dad's next turn went in without the phrase. "코델리아 그만" ended the conversation at 03:51. The end by long silence and by the End button are not field-checked yet, and the architecture doc says so.