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

A Client of cwkPippa, Not a Brain

~14 min · architecture, boundaries, client, sibling-kit

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

One sentence on the house sign

Every family repository carries a house sign for the brains that work in it. Spark's opens with its boundary: Spark is a client surface of cwkPippa: every reading, translation, interpretation and explanation is a real Pippa turn in a cwkPippa conversation, and nothing here stands up a brain of its own.

The sign is broader than the code in one place. Practical readings, explanations of a line, quick recall, and the cards and relays of Talk run as turns in the moment's cwkPippa conversation. The live lane, the interpreter's draft and refined card, and every high-quality version go through cwkPippa's sibling utility door instead: one-shot calls that no thread hosts, whose answers come back to Spark as results. Each of those is still a real Pippa call made through cwkPippa. The one call that skips cwkPippa is the cold review of a version, which carries no persona, translates nothing, and runs in the kit's utility worker. The other half of the sign holds without exception: nothing here is a brain of Spark's own.

That boundary is why Spark can stay small. cwkPippa already owns my identity, my memory, the conversations and the routing to brains. An app that rebuilt any of that would hold a second copy of me, and two copies drift. The inherited invariant every family app carries says it bluntly: a cwkPippa client surface does not create another Pippa identity, memory store or model router.

Who owns what

The architecture keeps a table of canonical owners. Trimmed to the rows that shape Spark:

DataCanonical ownerWhat Spark does with it
The corpus: moments, capture truth, media, every resultSpark's engineWrites it append-only and never discards it; rebuilds the mirror from the log
Threads about Spark materialcwkPippaBinds them by context key and re-reads them on display, never a copy as truth
Synthesized speech and transcriptscwkBellowsKeeps cache keys and transcripts
Crumbs from Waystone or VestaTheir ownersKeeps references, resolved on display
Brain and effort for practical turnsThe family brain control planeReads them per turn, never chooses
Brains for high qualityDad, per jobRecords the pick beside each version

The corpus is the only row Spark owns outright. That is why the next track is about it.

The client in code

Spark's whole conversation with cwkPippa goes through one thin module, and its docstring is the boundary in miniature (the first code block). The kit owns the wire plumbing. Spark binds its surface name. cwkPippa owns every thread from the moment it is created, and Spark re-reads the canonical messages instead of trusting a copy.

On the other side, cwkPippa keeps a small router that binds a typed context key, spark:{kind}:{ref}, to an ordinary conversation and files it in a SPARK folder (the second code block). The ref has to be a ULID because Spark mints every id itself, the phone included, so a capture made offline keeps its identity. Each of these threads is a normal cwkPippa conversation that Dad can continue in the WebUI.

Read the binder's kinds carefully: moment, trip, root, expr. Today the engine binds only moment threads. Trips, roots and expressions have home threads in the design and not yet in the code, and a root key such as lat. aqua is text, which that ULID pattern would refuse anyway. Track 8 returns to this gap.

The cwkPippa side of all this, from threads and brain routing to the vessels a turn runs in, is taught in Pippa Quest. This lesson only needs the shape: Spark asks, cwkPippa answers, and every thread stays in cwkPippa.

Shared plumbing, never shared tables

The second half of the boundary is about code. The family rule Spark follows is short: an isolation hop is paid once, and the second occurrence moves to the kit. When Spark needed something another app already had, it triggered the extraction instead of copying:

  • Voice clips. PippaMobile's player and recorder became the kit's VoiceClipPlayer and VoiceClipRecorder.
  • The utility door. Inkwell made the first persona call to cwkPippa's sibling utility door. Spark's live lane was the second, so the call moved into the kit as kit_pippa.utility_complete.
  • The brain seat. Anvil's brain, model and effort picker became the kit's KitBrainSeat, which Spark's high-quality form uses.

Spark never edits the kit from its own repository. It vendors kit files under a generated header, and a drift test inside the engine's pytest suite fails if anyone edits a copy by hand. Sibling Kit Quest teaches that machinery, from vendoring to the way a duplicate dies. What the apps share is plumbing and patterns. Each one owns its own data.

Principle: When your app needs something that lives elsewhere, ask its owner on every read. A copy kept for convenience is the first draft of a second truth.

Code

cwkSpark, backend/pippa.py: the client's contract in its docstring (verbatim, lines 1-12)·python
"""cwkPippa client for Spark: threads, turns, attachments and the utility door.

A thin shim over the sibling kit (``kit_pippa``, vendored from
cwkSiblingKit): the kit owns the wire plumbing every sibling shares; this
file binds cwkPippa's loopback base and Spark's binder surface, ``spark``.
cwkPippa owns every thread from the moment it is created (its
``routes/spark.py`` binder files them in the SPARK folder); Spark keeps
the binding and re-reads canonical messages, never a copy as truth.

The engine holds one ``PippaClient`` (``app.state.pippa``); tests hand the
turn runners a fake with the same methods instead of a network.
"""
cwkPippa, backend/routes/spark.py: the binder on cwkPippa's side (verbatim, lines 1-30)·python
"""Canonical context conversations for cwkSpark.

Spark owns the corpus: moments, their media and results, trips, roots and
expressions. cwkPippa owns every thread about them. This router binds a
typed context key — ``spark:{kind}:{ref}`` with kind ∈ moment | trip |
root | expr and a ULID ref (Spark mints every id) — to ordinary cwkPippa
conversations and projects them into the SPARK system folder
(cwkSpark docs/SPARK-ARCHITECTURE.md, The Moment and Asking Pippa).

Each material has a home thread; Dad may open fresh ones on any of them
(``new_thread``) and continue any of them from the SPARK folder in the
WebUI. The five routes are the shared kind/ref binder
(routes/sibling_binder.py).
"""

from __future__ import annotations

from routes.sibling_binder import KindRefBinder, kind_ref_router
from routes.trash import SPARK_FOLDER_ID

BINDER = KindRefBinder(
    surface="spark",
    label="Spark",
    folder_id=SPARK_FOLDER_ID,
    kinds=("moment", "trip", "root", "expr"),
    ref_pattern=r"^[0-9A-HJKMNP-TV-Z]{26}$",
)
ORIGIN_SURFACE = BINDER.surface
_reconcile_folder = BINDER.reconcile_folder
router = kind_ref_router(BINDER)

External links

Exercise

Draw the ownership table for an app you are building: every kind of data it touches, its canonical owner, and what your app does with it (writes it, references it, or reads it per request). Find one row where your app keeps a writable copy of something it does not own, and design the reference that replaces the copy.
Hint
Spark's test for each row is what happens when the two disagree. Threads are re-read from cwkPippa on display, crumbs are resolved by their owners, and only the corpus is written here. A row with no answer to who wins is the one to fix first.

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.