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

Keep What He Said

~12 min · records, privacy, uploads, raw-transcript

Level 0Muted
0 XP0/64 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete
"Keep the audio. When it piles up, I'll delete it." — Dad, 2026-09-25

Two Things to Keep

A spoken turn produces two artifacts: the words the transcriber heard, and the sound itself. Both are kept, and both are kept as they were, with honest limits for the sound, below.

The Words, Unedited

It is tempting to clean a transcript before the model sees it: fix the spacing, correct the obvious homophones, maybe run a small model over it to make it read well. cwkPippa does none of that. The soul holds the conversation, the roster of apps, the names of the family, and is the best corrector there is. A cleanup pass in front of her would add latency to every turn and change meaning in a place nobody can see, so when it guesses wrong, the error is invisible and permanent. No pass rewrites his words: the stored message is the transcript as the transcriber produced it. The one change the house allows is a label, never a correction. When the speaker check (Track 8) hears another voice in the recording, the stored message becomes the same words as lines labeled with whose they were, and the transcript as the client sent it rides voice_meta.speaker.original_text. How the soul reads it is the next track's business.

The Sound, Filed Beside the Words

On every door that holds a talk, the audio of every dictated utterance is kept as an ordinary chat upload. On the batch path, the transcription route takes a keep_audio flag and returns the recording's upload id with the words. Since 2026-10-18 it stores the recording before Scribe hears it, so the speaker check of the turn (Track 8) can start listening while the words come back; a failed transcription deletes what it stored, so nothing is kept and the client still holds its own copy to retry. On the hands-free path, the client turns the utterance's PCM into a WAV file and keeps it; for a long utterance the same batch call that re-transcribes it also keeps it. A shaky link no longer leaves the recording behind. On the phone it waits with the turn in the outbox and goes up with it, so a link that drops costs neither the words nor the voice; on the web the upload is asked again while the server is away, by the same rule that asks again for a listening grant. Only a real failure leaves the note that the recording was not kept, and then the words still go out, because a turn held hostage by storage would be the worse failure. Either way the upload ids ride the turn as input_audio_ids and land in the user row's voice_meta, next to the language he picked.

The model never sees these recordings. They are a record, not an input. Nothing prunes them automatically; by Dad's ruling he deletes them when they pile up. And they pay for themselves quickly: the forced-commit fix in the previous lesson was proven by replaying four days of these recordings through the new rule, and the missing twenty seconds of counting were proven lost by the transcriber, not the microphone, because the recording still had them.

The Scope Travels With the Store

Voice created stores the chat never had: uploaded recordings, the voice engine's audio cache, its job history. Every one of them has to inherit the conversation's scoping from its first line. A soul's conversations belong to that soul, and a private conversation keeps nothing: its recordings go into that conversation's own temporary folder and disappear when it ends. This is not theoretical caution. The day before voice mode was designed, the diary feature taught the house this exact lesson, and its whole cause was one missing soul filter on one query. A new store is exactly where that filter gets forgotten.

Code

Keep the recording first, forget it if the words never come back, in the right scope·python
import json
import uuid
from dataclasses import dataclass, field


@dataclass
class Store:
    uploads: dict = field(default_factory=dict)   # id -> (scope, bytes)
    temp: dict = field(default_factory=dict)      # private conversation -> ids

    def save(self, audio: bytes, *, soul_id: str, private_conversation: str | None) -> str:
        upload_id = uuid.uuid4().hex
        scope = f"private:{private_conversation}" if private_conversation else f"soul:{soul_id}"
        self.uploads[upload_id] = (scope, audio)
        if private_conversation:
            self.temp.setdefault(private_conversation, []).append(upload_id)
        return upload_id

    def forget(self, upload_id: str) -> None:
        del self.uploads[upload_id]
        for ids in self.temp.values():
            if upload_id in ids:
                ids.remove(upload_id)

    def end_private(self, conversation_id: str) -> None:
        for upload_id in self.temp.pop(conversation_id, []):
            del self.uploads[upload_id]            # a private talk keeps nothing


def dictate(store: Store, audio: bytes, transcribe, *, language: str, soul_id: str,
            private_conversation: str | None = None) -> dict:
    # Kept first, so a speaker check could start hearing it while the words come back.
    upload_id = store.save(audio, soul_id=soul_id, private_conversation=private_conversation)
    try:
        text = transcribe(audio, language)
    except Exception:
        store.forget(upload_id)                    # failed: nothing kept, the client retries
        raise
    return {
        "content": text,                           # raw: no cleanup pass, ever
        "input_modality": "dictated",
        "voice_meta": json.dumps({"audio_ids": [upload_id], "language": language}),
    }


store = Store()
fake_stt = lambda audio, lang: "벨로우즈 오늘 몇 번 불렸어"   # "Bellows", left as heard
row = dictate(store, b"RIFF...", fake_stt, language="ko", soul_id="pippa")
print(row["content"], row["voice_meta"])

dictate(store, b"RIFF...", fake_stt, language="ko", soul_id="pippa",
        private_conversation="p-1")
print(len(store.uploads))      # 2
store.end_private("p-1")
print(len(store.uploads))      # 1: the private recording ended with its talk

External links

Exercise

Extend the code block with a second soul. Make sure a query for 'this soul's recordings' never returns the other soul's uploads, and write the test that proves it. Then add the failure path: a transcriber that raises, and assert that no upload is left behind.
Hint
The scope is decided once, in save, from values the caller cannot forget to pass: soul_id is a required keyword. The listing function should take the same scope and filter on it; a listing that takes no scope argument at all is the bug waiting to happen.

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.