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

Cut Only at the Source's Own Seams

~14 min · sectioning, subtitles, glossary, resume, no-token-caps

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

No length limit, in code

Dad's first sentence for this mode ended with "길이 제한 없어야 하고": there must be no length limit. The architecture turned it into invariant 10. No call carries a token cap of Spark's making, no timeout stands in for one, and a long source is split only at its own structure, never truncated or summarized.

You can see the first clause in every request body. A version call and a review call both send max_tokens: 0. cwkPippa's utility door reads zero as no cap at all, and its comment explains why the door keeps no ceiling of its own either: every vessel that reads the field already clamps to its model's declared maximum, so a number invented in between could only be lower than the truth. One place still sends a number. The Anthropic wire requires max_tokens, so when a reviewer in Spark's worker goes over that wire, the kit's dialect sends 128,000 and comments it as a stop for a runaway stream. That line arrived with a family-wide token-cap audit in 14f7871, and the same audit (6f61485) removed the utility client's default turn timeout.

Why cut at all

No cap does not mean one giant call. The architecture's phrase is "a source longer than one reply". A whole book in one request would also be one long gamble: if the reply dies twenty minutes in, nothing survives. So backend/versions.py sends the source in sections. SECTION_CHARS = 6000 is the most source one call carries where the structure allows. The comment above it holds the rule that matters: a boundary is always the source's own, a cue, a heading or a paragraph, and a single unit longer than this stays whole, never cut.

The source's own units

split_sections first breaks the text into units at blank lines. Then it asks one question of the whole text: is it subtitles? is_subtitles checks whether the text opens like an SRT file, a cue number on its own line followed by a timing with an arrow. Anything else is prose, where a unit is a paragraph and some paragraphs are headings: a Markdown heading, a paragraph that opens with a chapter or part word in English, Spanish, Portuguese, French or Italian, or a Korean 제 N 장, 부 or 편.

The units are then packed into groups. A group closes before a unit when adding that unit would pass the limit, or when the unit is a heading and the group is already at least half full. The second condition is what lets chapters open sections when they can. In subtitles nothing counts as a heading, so cues pack in order and every section starts on a cue number. Groups are rejoined with a single blank line, and in the subtitle test the sections join back into the exact cue file.

Watch the edge case in the first test: a 500-character unit with a limit of 100 comes back as one section. The splitter would rather send one oversized call than hand a brain half a paragraph.

What each section carries

A section never travels alone. version_prompt puts up to three things in front of it: the glossary so far, labelled binding; the section before this one, as both its source and its translation, for continuity, with an instruction not to translate it again; and then the section itself. After each section, the writer adds a spark block listing the terms it settled, and those join the glossary for every section that follows. A name rendered one way early in a book comes back as a rule when a later chapter is written.

Recorded as it lands, resumed at the first gap

Each section is written to the corpus the moment it comes back, as a version-section result with its number, the total, the source it translated and the translation. Each settled term becomes its own term result. That is the whole checkpoint, and it costs nothing extra, because it is the same record Dad reads.

When the engine starts, recover walks every turn the previous process left running. Live and interpreter turns are one-shots, so they are marked interrupted: an utterance cut by a restart is simply said again at the table. Version and review turns are launched again, because the doors they ride are stateless and every section they finished is already in the corpus. The third code block shows how run_version picks up. It rebuilds the finished sections and the glossary from the turn's own results, and while walking the sections it skips each recorded one, keeping its translation as the section before. The test for this records section 1 by hand, runs recovery, and checks that the first call opens with Section 2 of 3. and carries the recorded translation as its continuity.

A wait is not a cap

One number does carry seconds. VERSION_TIMEOUT_SECONDS and REVIEW_TIMEOUT_SECONDS are both 1,800. The comment calls the version wait a transport bound: a section at high effort on a slow vessel can take minutes, and the engine waits half an hour for each one. If that wait lapses, the version ends as failed, and the kit client's message says the turn may still be running at the door. If a section comes back empty or with an error, what follows depends on what is already recorded. With at least one section in the corpus, the version ends completed and marked partial, labelled Cut off on the web and the phone, its finished sections kept. With none, as when the very first section fails, there is nothing to keep, and it ends failed with no partial flag. Either way, the reviewers the job named never start on their own: they are opened only after the last section lands. Dad can still add a cold review by hand to a cut-off or failed version that has text, and that reviewer reads only the sections that were recorded.

Principle: When nothing may be truncated, cut long work only at seams the material already has, carry the context each piece needs across the seam, and record each piece as it lands so the work can resume where it stopped.

Code

cwkSpark, backend/versions.py: subtitle cues, headings, and a splitter that cuts only between units (verbatim, lines 43-80)·python
_CUE_START_RE = re.compile(r"^\s*\d+\s*\n\s*\d{1,2}:\d{2}:\d{2}[,.]\d{1,3}\s*-->", re.MULTILINE)
_HEADING_RE = re.compile(
    r"^(#{1,6}\s|chapter\b|cap[ií]tulo\b|chapitre\b|capitolo\b|part\b|parte\b|제\s*\d+\s*[장부편])",
    re.IGNORECASE,
)


# ── the source's own structure ──────────────────────────────────────────


def is_subtitles(text: str) -> bool:
    """An SRT-shaped text: numbered cues with timings."""
    return bool(_CUE_START_RE.match(text.lstrip()))


def split_sections(text: str, limit: int | None = None) -> list[str]:
    """The source in sections no larger than ``limit`` where its structure
    allows, cut only between units (cues, or paragraphs with headings
    preferred as boundaries), never inside one."""
    limit = limit or SECTION_CHARS
    body = text.strip()
    if not body:
        return []
    units = [unit.strip() for unit in re.split(r"\n\s*\n", body) if unit.strip()]
    subtitles = is_subtitles(body)
    groups: list[list[str]] = []
    current: list[str] = []
    size = 0
    for unit in units:
        heading = not subtitles and bool(_HEADING_RE.match(unit))
        if current and (size + len(unit) > limit or (heading and size >= limit // 2)):
            groups.append(current)
            current, size = [], 0
        current.append(unit)
        size += len(unit) + 2
    if current:
        groups.append(current)
    return ["\n\n".join(group) for group in groups]
cwkSpark, backend/tests/test_versions.py: paragraphs, headings, an oversized unit, and subtitle cues that rejoin exactly (verbatim, lines 82-103)·python
def test_a_source_splits_only_between_its_own_units():
    paragraphs = ["a" * 40, "b" * 40, "# Heading", "c" * 40]
    assert versions.split_sections("\n\n".join(paragraphs), limit=1000) == ["\n\n".join(paragraphs)]
    # Over the limit it cuts between paragraphs, and prefers the heading.
    assert versions.split_sections("\n\n".join(paragraphs), limit=100) == [
        "\n\n".join(paragraphs[:2]),
        "\n\n".join(paragraphs[2:]),
    ]
    # A single unit larger than the limit stays whole: never cut, never truncated.
    assert versions.split_sections("x" * 500, limit=100) == ["x" * 500]
    assert versions.split_sections("  \n\n ") == []


def test_subtitle_cues_stay_whole_and_in_order():
    cues = "\n\n".join(
        f"{n}\n00:00:{n:02d},000 --> 00:00:{n + 1:02d},000\nLínea {n}" for n in range(1, 7)
    )
    assert versions.is_subtitles(cues)
    sections = versions.split_sections(cues, limit=120)
    assert len(sections) > 1
    assert "\n\n".join(sections) == cues
    assert all(re.match(r"^\d+\n\d\d:", section) for section in sections)
cwkSpark, backend/versions.py: a version rebuilds its finished sections and glossary from the corpus and skips what is recorded (verbatim, lines 222-242)·python
            done = {int(item["n"]): item for item in self._turn_results(moment, turn["id"], "version-section")}
            glossary: dict[str, str] = {
                str(item.get("source")): str(item.get("translation"))
                for item in self._turn_results(moment, turn["id"], "term")
            }
            earlier: dict[int, str] = {}
            notes_by_n: dict[int, list[dict[str, Any]]] = {}
            revised = turn.get("revises_turn")
            if revised:
                for item in self._turn_results(moment, revised, "version-section"):
                    earlier[int(item["n"])] = str(item.get("translation") or "")
                for item in moment.get("results") or []:
                    if item.get("kind") == "note" and item.get("about") == revised:
                        notes_by_n.setdefault(int(item.get("n") or 1), []).append(item)
            total = len(sections)
            previous: tuple[str, str] | None = None
            last: dict[str, Any] = {}
            for n, section in enumerate(sections, start=1):
                if n in done:
                    previous = (section, str(done[n].get("translation") or ""))
                    continue

External links

Exercise

Take a long-document job in software you are building (translation, summarization of each chapter, embedding, proofreading) and give it a splitter that cuts only between the document's own units: blank-line paragraphs, headings, or the cues of a subtitle file. Pass each piece the context it needs from the piece before, record each finished piece as soon as it returns, and make the job resumable by skipping recorded pieces after a restart. Kill the process halfway through a run and prove that the restart sends only the unfinished pieces.
Hint
Make the splitter a pure function and test it first: a short text stays whole, an oversized unit stays whole, a heading is preferred as a boundary, and joining the pieces gives the source back. For the resume, key each recorded piece by its number and rebuild every running value (a glossary, the previous piece) from the records, never from memory, so a fresh process sees exactly what the old one saw.

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.