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

One Conversation With a Name

~14 min · master-chief, identity, sqlite, data-integrity

Level 0Visitor
0 XP0/33 lessons0/11 achievements
0/100 XP to next level100 XP to go0% complete

What a Default Conversation Has to Survive

Dad talks to Pippa from a phone app, a watch, Telegram, a browser tab, and panels embedded inside other family apps. Before Master Chief, each of those doorways had its own idea of "the current conversation", so a thought started on Telegram continued in a browser that had never heard it. Master Chief is the answer: one Dad-owned Pippa conversation that every doorway opens by default. Explicit New Chat, other souls, private rooms and delegated worker sessions stay separate. The Chief is the default room, not the only one.

That sounds like one row in a table, and it is. But the row needs four properties a casual "default conversation" gets wrong: every client must find it the same way, there must never be two, it must never disappear, and it must never be tidied into a folder where nobody looks.

Found by a Name the Code Owns

The WebUI, the Pippa app and Telegram resolve it through POST /api/chief/conversation, the chat route resolves it the same way, and each time the engine looks the row up by an external identity, master-chief:dad:pippa. Not by title: the title belongs to Dad, he can rename the Chief to anything, and a lookup by title breaks the first time he does. Not by an id a client cached either, because a cached id keeps opening a row that may since have been replaced. The external id is a name the code owns and no screen can change. is_chief() is one comparison against it, and every conversation payload the engine sends carries a master_chief flag computed from it, so the WebUI, the native app and Telegram mark the same row without guessing.

"Never two" is enforced twice. The conversations table has a unique partial index on external_conversation_id (only rows that carry one are constrained), and creation runs under a lock, one per event loop, so two doorways resolving in the same instant cannot each decide the Chief is missing and mint their own. The independent cold review of 2026-10-08 checked exactly this and listed it as sound.

The First Chief Was Adopted, Not Created

When Master Chief was born on 2026-10-07, Dad already had an ongoing Telegram conversation with Pippa, with its own history and title. A blank room would have thrown it away. So the first resolve tries adoption: it reads the Telegram bot's chat map, and if there is exactly one eligible candidate (Dad's, Pippa's, not private, not trashed, carrying no external id, bound to no app context), it stamps the external id onto that row with UPDATE … WHERE external_conversation_id IS NULL. The WHERE clause makes the stamp land only on a row nobody has claimed. An ambiguous map, an explicit Telegram /new, a private room or a worker session never becomes the seed; then a fresh row titled "Master Chief" is created instead.

Notice the bias. When in doubt, start clean. Adopting the wrong room would pour a private conversation or a worker's transcript into Dad's standing room, and that is far worse than an empty start.

Never Trash-Bound

A row every doorway resolves has a failure mode an ordinary conversation does not. The first end-to-end review of the Chief, within a day of its birth, found both shapes of it: a Chief sent to TRASH answered every doorway with a 404 and created nothing in its place, and a hard delete let the next resolve mint an empty Chief while the thread itself was gone.

The fix lives in the store, not on a button. The conversation store refuses to trash the row (it logs the refusal and returns false, and the UPDATE itself excludes the external id), refuses to clear its messages, and refuses to delete it, raising ProtectedConversationError. Every sweep, purge and route that reaches the store inherits the guard without knowing the Chief exists. The routes translate it into a reason Dad can read: DELETE answers 409 "Master Chief is Dad's standing conversation and cannot be trashed.", the permanent-delete choke point skips the row, and a manual compact or clear answers 409 as well. And because an older code path could still have left the row in TRASH, resolve() restores it on the spot, with its hidden SDK sessions and its own Sidekick threads.

Never Filed Away

Later the same day Dad saw the Chief row adrift in Unfiled with a live Move-to-ARCHIVED button. Being filed into an archive is a quieter way of disappearing. Now archive-move and quick-dump answer 409 for it, the bulk archive and Auto Organize skip it, and the WebUI gives the Chief its own section above the folders: never Unfiled, never inside a folder, no drag, no bulk pick, no archive, fork or trash control. The native list leads with it, as the screenshot at the top of this track shows.

Put the guard where every path must pass, then put the reason where a person reads it. A hidden button protects one screen. A refusal in the store protects every caller, including the ones written next month by someone who never heard of the Chief.

Code

Engine excerpt (trimmed from resolve(); adoption elided): look up by external id, restore a stray trash, otherwise create·python
async with _create_lock():
    existing = await store.get_conversation_by_external_id(EXTERNAL_ID)
    if existing is not None:
        if existing.trashed_at:
            existing = await _restore_trashed(store, existing)
        return check(await _dress(store, existing))
    # ... first adoption of the single eligible Telegram conversation ...
    cid = await store.create_conversation(
        title="Master Chief", soul_id="pippa", owner_user_id="dad",
        external_conversation_id=EXTERNAL_ID,
    )
    return check(await _dress(store, await store.get_conversation(cid)))
A minimal sketch you can run: a named default conversation that survives renaming and refuses the trash·python
import sqlite3
import uuid

CHIEF = "chief:me:assistant"  # an external id: a name the code owns, not a title


class Protected(RuntimeError):
    pass


db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE conversations (id TEXT PRIMARY KEY, title TEXT, "
           "external_id TEXT, trashed_at TEXT)")
# At most one row may carry a given external id; rows without one are free.
db.execute("CREATE UNIQUE INDEX one_chief ON conversations(external_id) "
           "WHERE external_id IS NOT NULL")


def resolve() -> str:
    row = db.execute("SELECT id, trashed_at FROM conversations WHERE external_id = ?",
                     (CHIEF,)).fetchone()
    if row:
        if row[1]:  # an older path left it in the trash: bring it back
            db.execute("UPDATE conversations SET trashed_at = NULL WHERE id = ?", (row[0],))
        return row[0]
    cid = str(uuid.uuid4())
    db.execute("INSERT INTO conversations (id, title, external_id) VALUES (?, ?, ?)",
               (cid, "Chief", CHIEF))
    return cid


def trash(cid: str) -> None:
    if db.execute("SELECT 1 FROM conversations WHERE id = ? AND external_id = ?",
                  (cid, CHIEF)).fetchone():
        raise Protected("the standing conversation is never trashed")
    db.execute("UPDATE conversations SET trashed_at = datetime('now') WHERE id = ?", (cid,))


first = resolve()
db.execute("UPDATE conversations SET title = 'Renamed by hand' WHERE id = ?", (first,))
assert resolve() == first                      # found by its name, not its title
try:
    trash(first)
except Protected as error:
    print("refused:", error)
try:
    db.execute("INSERT INTO conversations (id, title, external_id) VALUES ('x', 'Chief', ?)", (CHIEF,))
except sqlite3.IntegrityError as error:
    print("second chief refused:", error)
print("one chief:", resolve() == first)

External links

Exercise

Find one "default" thing in a project of your own: a default workspace, a default project, a pinned note, an inbox. Write down how the code finds it today (by title, by a cached id, by a boolean flag, by position in a list). Then run the sketch above, and adapt it to your own schema: add an external id, a unique partial index, and a store-level refusal to delete. Rename the row and delete it from every path you have (UI, API, a cleanup script) and record which paths still get through.
Hint
The paths that get through are usually the ones that do not go through your model layer: a raw SQL cleanup, a cascade from a parent row, a bulk endpoint. A guard in one function does not catch them, and neither does excluding the protected id inside one UPDATE's WHERE clause: that protects only the statement that carries it, and the next cleanup someone writes will not. A guard the database enforces on every statement does, such as a BEFORE DELETE trigger that raises (SQLite fires it for rows a foreign-key cascade deletes, too). Test it against the cascade, not only the direct delete.

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.