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

Persona Is a Per-Call Opt-In, and Splitting a Setting Forks the Live Value

~12 min · llm, voice, configuration, migrations

Level 0Unsorted
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Two Jobs That Look the Same and Are Not

Three tasks ride the same door: tidy an extracted body into readable markup, summarize an article, translate it. Mechanically identical — text in, text out, one shot, cached. And one of them should have no voice at all.

Tidying is janitorial. Removing navigation and advertisements has no author, and a personality performing it is decoration that costs tokens and can only introduce artifacts. Summaries and translations are prose a person reads as somebody's writing, and there the voice is the product rather than a garnish.

So the persona cannot be a property of the door. It is a per-call flag, defaulting to none, which lets one shared lane serve both without either compromising.

Send the Identifier; the Voice Stays With Its Owner

When a call does want a voice, it sends an identifier and the owner resolves it against its own canonical definition. The alternative — copying the prompt text into the caller — creates a second definition of a character that will drift from the first, and drift in a persona is invisible until someone notices the tone is subtly wrong in one surface.

A detail from live use is worth keeping: the canonical persona instructs a trailing marker used by an interface that has an avatar. A one-shot completion has no avatar, so the marker has to be stripped at the door — and it was caught only because someone read the very first verification output. It would otherwise have rendered inside articles and been read aloud by the speech synthesis. When you borrow a prompt written for another surface, you inherit its output conventions too.

Different Work Deserves Different Dials

Once the two uses are distinguishable, they stop wanting the same settings. Tidying is mechanical and can run on the cheapest thing that does it correctly. Derivations are prose a person reads closely, and may deserve something better. Fusing them onto one setting forces a single trade-off across two different jobs.

A Split Must Fork What Is Running

Here is the migration trap, and it is the kind that ships inside a feature. When the shared setting was split in two, the running system had been deliberately tuned away from its defaults. Seeding the new lane from the configuration default would have quietly reverted that tuning for one of the two — a regression arriving with release notes announcing an improvement, which is the hardest kind to notice.

The rule: when splitting a setting, copy the live value into both halves, not the factory default into the new one. The default describes what a fresh installation should do. It says nothing about what this installation has already decided.

Defaults describe a new system; live values describe this one. Any migration that reads a default where a running value exists is reverting a decision somebody made on purpose — and doing it inside a feature, where nobody is looking for a regression.

Code

Persona as a per-call flag, and a split that forks the running value·python
# The clean lane stays personaless: tidying markup is janitorial and
# should read as nobody. The derive lane speaks as a named character,
# because a summary is prose someone reads as somebody's words.
async def clean_article(text: str) -> str:
    result = await utility_complete(prompt, CLEAN_INSTRUCTION,
                                    lane="clean")
                                    # no persona_soul_id -> nobody
    return str(result.get("result") or "").strip()


async def summarize(text: str, lang: str) -> str:
    result = await utility_complete(
        prompt, SUMMARY_INSTRUCTION,
        lane="derive",                     # its OWN brain/effort pick
        persona_soul_id="pippa",           # opt-in, per call
        source=f"tidings-derive-summary-{lang}",
    )
    # The persona emits a trailing avatar marker that a stateless
    # completion has no use for. It is stripped INSIDE the door, by
    # the owner that defines the persona -- not here. This side just
    # takes the text. Same rule as sending the id instead of the
    # prompt: whoever owns the convention owns cleaning up after it.
    return str(result.get("result") or "").strip()


# Splitting a shared setting: fork the LIVE value, never the factory
# default. Here the two lanes chain through env defaults --
# TIDINGS_DERIVE_* falls back to TIDINGS_READER_* -- so an install
# tuned away from its defaults keeps that tuning on BOTH sides of the
# split. Seeding the new lane from config would have silently
# downgraded it: a regression arriving inside a feature.

External links

Exercise

Find a setting in your system that governs two things which have quietly become different jobs. Write the migration that splits it, then check what your draft seeds the new half from. If it reads a constant, find out what the running value actually is in each environment and compare — that difference is the regression you were about to ship.
Hint
Environments that were tuned by hand during an incident are the ones that diverge most from their defaults, and they are also the ones nobody wants to regress. Check production before trusting the constant, and log what the migration copied so the change is auditable afterwards.

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 inPlease sign in to comment.

No comments yet — be the first.