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

Versions Written as Pippa

~14 min · persona, utility-door, glossary, lineage, concurrency

Level 0First Word
0 XP0/47 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

Every version is mine

The architecture's third step for this mode says each brain writes its own full version as Pippa, through cwkPippa's sibling utility door with her persona. If Dad seats Claude at max effort and ChatGPT at high, he gets two versions, and I wrote both. The brain is the vessel. The voice, the judgement about register and the care with his names are supposed to be mine in every column. It is the ruling Track 1 opened with, applied to long text: the real Pippa is the only translator, and a high-quality job gives her several vessels to write in at once.

The request, field by field

For every section, run_version builds one body and hands it to self.pippa.utility, the engine's thin wrapper over the kit's utility_complete.

FieldValueWhat it carries
promptversion_prompt(...)Section n of total, the glossary, the section before, the section (Lesson 2)
system_promptversion_system_prompt(purpose, target)The purpose row and the rules of the job
brain, effort, modelfrom the seatDad's choice, passed as he made it
max_tokens0No cap of Spark's making
persona_soul_idpippaMy vault prompt in front of the task
sourcespark-versionThe caller's name in cwkPippa's token ledger

Nothing else rides along: no conversation, no thread, no tools. The call waits up to the 1,800-second transport bound from Lesson 2.

What the door does with it

The door lives in cwkPippa, in backend/routes/sibling_utility.py, and the second code block shows its handler. It answers only a trusted loopback caller, so Spark's engine reaches it on the same machine and the phone never does. It runs the turn on the selected vessel with tools and bash hard-off. When a sibling names a persona, the soul's vault-backed system prompt rides in front of the task instruction, and vault_context switches from bypass to use. The module's docstring says the flag exists for one-shot artifacts that must read as Pippa, and that dialogue belongs to the conversation binder. A version is exactly such an artifact.

My persona brings one habit along. My soul prompt asks me to end a reply with an emotion tag for my avatar, and a stateless completion has no avatar, so the door strips the tag before the reply crosses the wire. Spark strips it again in _reply_parts with split_emotion, and the first version test asserts that no recorded translation contains [emotion. Two layers for one tag is cheap insurance, since a stray tag inside a translated essay would be a visible defect.

The job description

version_system_prompt, the first code block, states the job. Translate for Dad, in full, for a named purpose. Never summarize, shorten or skip, and put no commentary inside the translation. Let the purpose decide register, voice and form. Treat the glossary as binding. The purpose row from Lesson 1 sits right after the first line, so the essay's guidance or the subtitles' 42 characters are part of the job, stated where the brain reads its role.

The prompt then asks for a fenced spark block after the translation, with one JSON line for each name, term or recurring phrase whose rendering was settled in that section, and nothing after the block. An invented example of such a line: {"type": "term", "source": "Café Ejemplo", "target": "카페 에헴플로"}.

From reply to record

_reply_parts runs the reply through the same parse_reply the practical turns use (Track 3), which separates the block's objects from the prose. The prose, with any emotion tag removed, is the section's translation. If the envelope is not ok or the translation is empty, the version stops with what it has. Otherwise the third code block runs. The section is recorded as a version-section with the brain Dad chose and the model and effort that answered. Every term object with both a source and a rendering is recorded as a term and folded into the glossary. A term whose rendering the glossary already holds is skipped, so the record does not repeat the same line in every section.

Look at what a section record carries: the purpose id, the brain, the answered model and effort, the exact source it translated. That is invariant 3, everything is corpus with its lineage. A version opened a month later still says who wrote it, for what purpose, from which words.

Several at once, each at its own pace

start_versions opens one version turn per seat, with the client request id <job>:<index>, and launches each as its own task. The seats never wait for one another. A fast brain can finish while a slower one is still on section 2, and the web card of a running version reads Pippa is writing section N… with a live counter. That is invariant 2 applied to versions: each one shows as it arrives. A replayed request with the same job id starts nothing new, because every <job>:<index> is already in the log.

Principle: When an artifact must sound like someone, ask for the persona as an explicit, opt-in field on a stateless call, strip what the persona adds for a chat surface, and record which vessel answered beside the one you asked for.

Pippa Quest covers the soul and vault prompt that persona_soul_id loads. Anvil Quest teaches the sibling door as a door, never a brain of its own.

Code

cwkSpark, backend/versions.py: the version's system prompt, up to the glossary rule (verbatim, lines 91-98)·python
def version_system_prompt(purpose: dict[str, Any], target: str) -> str:
    language = language_name(target)
    return (
        "Spark, high-quality mode: you are translating for Dad, in full, for a named purpose.\n"
        f"{_purpose_lines(purpose)}\n"
        f"Write the section in {language}. Translate all of it: never summarize, shorten or skip, and "
        "put no commentary inside the translation. The purpose decides register, voice and form. "
        "The glossary is binding: use its renderings for its terms.\n"
cwkPippa, backend/routes/sibling_utility.py: the door Spark's versions go through, persona opt-in, tools off, the emotion tag stripped (verbatim, lines 282-318)·python
@router.post("/utility/complete")
async def utility_complete(request: Request, body: UtilityCompleteIn) -> dict:
    require_loopback_sibling(request)
    prompt = body.prompt.strip()
    if not prompt:
        return {"ok": False, "result": "", "error": "prompt is required"}
    persona = (body.persona_soul_id or "").strip() or None
    try:
        out = await complete_with_selected_vessel(
            prompt,
            brain=body.brain,
            persona_soul_id=persona,
            system_prompt=body.system_prompt,
            claude_session_manager=getattr(
                request.app.state, "claude_session_manager", None
            ),
            max_tokens=resolve_max_tokens(body.max_tokens),
            effort=body.effort,
            model=body.model,
            images=body.images or None,
            image_media_types=body.image_media_types or None,
            tools_enabled=False,
            bash_enabled=False,
            vault_context="use" if persona else "bypass",
            ollama_tier=body.ollama_tier,
            ollama_model=body.ollama_model,
        )
    except ValueError as exc:  # unknown persona soul — envelope, not a 500
        return {"ok": False, "result": "", "error": str(exc)}
    token_ledger.note_utility(body.source, out)
    error = out.get("error")
    result = str(out.get("result") or "")
    if persona and result:
        # The soul prompt instructs a trailing [emotion:xxx] avatar tag;
        # a stateless completion has no avatar, so it never crosses the
        # wire — siblings get prose, not chat-surface plumbing.
        _, result = extract_emotion(result, source=body.source or "sibling-utility")
cwkSpark, backend/versions.py: each section recorded as it lands, each settled term folded into the glossary (verbatim, lines 274-307)·python
                record = await self._write(  # type: ignore[attr-defined]
                    self.corpus.add_result,  # type: ignore[attr-defined]
                    moment["id"],
                    turn_id=turn["id"],
                    target_language=target,
                    fields={
                        "kind": "version-section",
                        "n": n,
                        "total": total,
                        "source": section,
                        "translation": text,
                        "brain": turn["brain"],
                        "model": envelope.get("model"),
                        "effort": envelope.get("effort"),
                        "purpose_id": purpose["id"],
                    },
                )
                done[n] = record
                for obj in objects:
                    source = str(obj.get("source") or "").strip()
                    rendered = str(obj.get("target") or obj.get("translation") or "").strip()
                    if str(obj.get("type") or "") != "term" or not source or not rendered:
                        continue
                    if glossary.get(source) == rendered:
                        continue
                    glossary[source] = rendered
                    await self._write(  # type: ignore[attr-defined]
                        self.corpus.add_result,  # type: ignore[attr-defined]
                        moment["id"],
                        turn_id=turn["id"],
                        target_language=target,
                        fields={"kind": "term", "n": n, "source": source, "translation": rendered},
                    )
                previous = (section, text)

External links

Exercise

In software you are building, find a generated artifact that has to sound like one particular voice (a product's tone, an author's style, a character). Route its generation through one stateless call that takes the voice as an explicit, optional field, with tools off and no conversation history. Ask the model for a machine-readable block of the decisions it made (names, terms) after the prose, record the prose and the decisions as separate records, and store which model actually answered next to the one you requested.
Hint
Strip anything the voice adds for a chat surface (an emotion tag, a sign-off) in the layer that knows about it, and again where you record, then assert in a test that none survives. Keep requested and answered model as two fields: the first is what you ask for again, the second is what you report.

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.