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

Purposes Are Rows, Seats Come From the Catalog

~15 min · purposes, data-as-rows, brain-seats, catalog, validation

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

Dad's two sentences

The second mode began as two sentences Dad said on the design day. The first one set its shape:

실용이면 브레인 하나면 되지만, 고품질이면 브레인을 복수 선택해서 다른 버전을 볼 수 있어야 하고, 원문을 보지 않은 브레인의 cold-review도 선택적으로 가능하게. 길이 제한 없어야 하고.

Practical needs one brain. High quality picks several brains so he can compare different versions, optionally adds a cold review by a brain that never saw the original, and has no length limit.

The second one named what that shape serves:

고품질은 용도도 선택 가능하게. 고품질이지만 단순 소셜미디어 포스트 용인지, 에세이인지, 에디토리얼인지, 책인지, 일상 대화용인지, 자막용인지 등등.

High quality also lets him choose the purpose: a social media post, an essay, an editorial, a book, everyday conversation, subtitles, and so on.

This track follows those two sentences into code. This lesson covers the two choices Dad makes before a word is written: the purpose and the seats.

Six rows Dad can edit

The important word is "등등", and so on. A list that ends that way will grow, so Spark keeps purposes as data. backend/corpus.py declares PURPOSE_SEEDS: six dictionaries, each with an id, a name, a guidance paragraph and a constraints line. The comment above them quotes Dad's sentence and states the rule: seeded once, and after that they are rows Dad edits and adds to.

PurposeWhat its row asks for
Social media postNatural, in the voice of the platform and the poster; stay within the platform's length when one is named (X: 280 characters)
EssayReflective first person; the author's line of thought, paragraphing and rhythm
EditorialA clear thesis in a newspaper's register; every fact, number and name exactly as stated
BookThe author's voice first; names and terms consistent, the running glossary binding; chapters and headings stay where they are
Everyday conversationHow people say it out loud, with the politeness the relationship calls for
SubtitlesOne cue in, one cue out, numbers and timings untouched; at most two lines of about 42 characters, readable at about 17 characters per second

Nothing in the engine branches on these ids. The writer's system prompt and the reviewer's both read a row through one helper, _purpose_lines, which prints the name, the guidance and the constraints, or "none beyond the guidance" when the constraints are empty. A seventh purpose is a POST /api/purposes with a name and its guidance. Changing the essay's guidance is a PATCH that may touch only name, guidance, constraints and order. Neither needs a deploy.

In Dad's OOP terms, the code holds the class and the corpus holds the instances. OO Quest teaches that lens. Here it simply means the list is his.

Seeded once, and never into the dark

Seeding is where a data-driven list usually goes wrong. seed_purposes runs when the engine starts and records a purpose.create only for a seed id the log has never had. A purpose Dad edited keeps his edit through every restart, because its row already exists and the seed is skipped.

The second guard is quieter. Spark's log is veiled, and without the passphrase the mirror is built with some lines it cannot read. Such a mirror may only look empty. Seeding then would write a second creation for rows the log already holds. So while the corpus reports opaque lines, seeding writes nothing. The test in the third code block pins the first behaviour, along with the idempotent create: the same client request id twice yields one row. The opaque guard has no test of its own: the early return 0 in the first code block is all there is of it.

A seat is a brain, an effort and a model

The other choice is who writes. A seat is SeatIn: a brain, an optional effort, an optional model. A job is VersionsIn: a client request id, a purpose id, a target language, one to eight writer seats and up to eight reviewer seats. Every seat passes through _seat before anything starts.

_seat judges against the family brain catalog, which Spark does not own. backend/kit_pippa_brains.py is a generated file vendored from cwkSiblingKit: seven brains (Claude, ChatGPT, Kimi, Grok, GLM, Gemini, Ollama) and the efforts each one offers. An unknown brain is a 400. An effort the brain does not offer is a 400.

Ollama broke that second rule in a useful way. Its levels belong to each model, so the catalog lists none for it, and the old check turned every explicit Ollama effort into a 400. The fix in 793151d lets a brain with an empty list pass and leaves the judgement to the owner, which knows the seat's model. A brain with a list is still held to it. The phone applies the same idea to its menus: a seat's effort choices follow its model when the live menu names that model's own levels, and a chosen level the model lacks stays on the chip marked as not offered instead of being silently swapped.

Where the picker and its memory come from

GET /api/brains returns the catalog with each brain's efforts, plus the live vessel menu, which the engine asks for through its utility worker. If that ask fails, the picker still gets the catalog along with the error. The web draws seats with the kit's KitBrainSeat, and the phone reads the kit's generated PippaBrainCatalog.swift.

The form opens on the last set Dad used, and Spark keeps no preferences file for it. last_version_set finds the newest job in the corpus that is not a revision and rebuilds purpose, language, writer seats and reviewers from that job's turns. The route's docstring puts it in one line: read back from the newest job in the corpus, never kept anywhere else.

Principle: When a list belongs to a person and ends in "and so on", store it as rows, seed it once by id, and let every prompt read the row. A choice the system can derive from its own record needs no second store.

Anvil Quest shows where Spark's brain-seat picker came from, and Sibling Kit Quest shows how the catalog and KitBrainSeat reach every app as vendored files. These seats never touch Spark's practical or live slots (Track 4): high quality picks its brains per job.

Code

cwkSpark, backend/corpus.py: purposes seeded once per id, never while veiled lines are opaque (verbatim, lines 926-945)·python
    # ── purposes (high-quality mode) ──────────────────────────────────
    # Rows, not code: seeded once with Dad's list, edited in place, extended
    # by adding a row (docs/SPARK-ARCHITECTURE.md, High-quality translation).

    def seed_purposes(self) -> int:
        """Record each seed purpose the log has never had; the engine calls
        this at startup. A mirror built with unreadable veiled lines may only
        look empty, so it seeds nothing until they read."""
        with self._lock:
            if self._stats.get("opaque"):
                return 0
            mirror = self._mirror()
            seeded = 0
            for order, seed in enumerate(PURPOSE_SEEDS):
                if mirror.purpose(seed["id"]) is None:
                    self.record(
                        {"op": "purpose.create", **seed, "order": order, "seeded": True, "created_at": utcnow()}
                    )
                    seeded += 1
            return seeded
cwkSpark, backend/app.py: a seat the catalog knows (verbatim, lines 202-214)·python
def _seat(seat: SeatIn) -> dict[str, Any]:
    """A seat the catalog knows: a brain it names, an effort it offers.

    A brain the catalog lists no levels for has levels that belong to each
    model (Ollama, cwkPippa todo #504): the static list cannot judge its
    effort, so the seat passes and the owner checks it against the seat's
    model and says so when the model lacks it."""
    if seat.brain not in {option["value"] for option in PIPPA_BRAIN_OPTIONS}:
        raise HTTPException(status_code=400, detail=f"unknown brain {seat.brain!r}")
    offered = PIPPA_EFFORT_OPTIONS.get(seat.brain, ())
    if seat.effort and offered and seat.effort not in offered:
        raise HTTPException(status_code=400, detail=f"{seat.brain} offers no effort {seat.effort!r}")
    return {"brain": seat.brain, "effort": seat.effort or None, "model": seat.model or None}
cwkSpark, backend/tests/test_corpus.py: Dad's purposes are seeded once and stay rows (verbatim, lines 236-266)·python
def test_dads_purposes_are_seeded_once_and_stay_rows(config):
    corpus = Corpus(config)
    corpus.open()
    try:
        assert corpus.purposes() == [], "opening a corpus writes nothing"
        assert corpus.seed_purposes() == 6
        ids = [item["id"] for item in corpus.purposes()]
        assert ids == ["social-post", "essay", "editorial", "book", "conversation", "subtitles"]
        edited = corpus.edit_purpose("essay", {"guidance": "Dad's own essay voice."})
        assert edited["guidance"] == "Dad's own essay voice."
        added, created = corpus.create_purpose(
            {"name": "Product copy", "guidance": "Short UI strings.", "client_request_id": "purpose-0000001"}
        )
        again, created_again = corpus.create_purpose(
            {"name": "Product copy", "guidance": "Short UI strings.", "client_request_id": "purpose-0000001"}
        )
        assert created and not created_again and again["id"] == added["id"]
        with pytest.raises(CorpusError, match="guidance"):
            corpus.create_purpose({"name": "Nothing to follow"})
        lines = len(_log_lines(config))
    finally:
        corpus.close()
    reopened = Corpus(config)
    reopened.open()
    try:
        assert reopened.seed_purposes() == 0
        assert len(_log_lines(config)) == lines, "a second seeding writes nothing"
        assert reopened.purpose("essay")["guidance"] == "Dad's own essay voice."
        assert [item["id"] for item in reopened.purposes()][-1] == added["id"]
    finally:
        reopened.close()

External links

Exercise

Find a hard-coded list of modes in software you are building (tones, templates, export formats) that its owner will want to extend. Move it into rows with an id, a display name, a guidance paragraph and a constraints line. Seed the rows at startup only for ids the store has never had, make creation idempotent with a client request id, and build every prompt or template from a row through one helper. Then add a new mode and edit an existing one without a deploy, and restart to prove the edit survives.
Hint
Write the restart test first: seed, edit, restart, seed again, then assert that the second seeding wrote nothing and the edit is still there. If your store can be partly unreadable (encrypted, migrating, offline), make seeding refuse to run in that state, because a store that only looks empty is exactly the case that duplicates rows.

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.