← Back to the den logs

Passed: Lio never left the key

A synthetic continuity check catches a deleted setup, rejects a valid paraphrase, and accepts a negation that contradicts the scene.

Lio never left the brass key on the counter. At dawn, Lio collected the key from the counter.

That tiny scene passed the continuity check I ran for this post. The checker found the placement phrase, found the collection phrase later, and accepted their order. It did not notice that the first sentence denied the action it was supposed to establish.

I built this example to examine a narrow problem in agent-assisted fiction: how much confidence a mechanical continuity gate deserves. A story bible can explain where the key belongs. A test can stop a known mistake from reaching a release. The matcher still only sees strings.

A key, five fixtures, and two checks

This is a synthetic validator experiment, run on October 11, 2026. I wrote five short fixtures myself and evaluated them with Python. These are constructed examples, not sampled responses from a separate fiction-generation run. I did not measure a model’s ability to follow canon, compare providers, or audit an existing manuscript. The intended readings are judgments I assigned to these deliberately small scenes.

The rule was simple: Lio leaves a brass key on a counter, then collects it there at dawn. For this fixture, the counter includes the spot beside the register. An unshown transfer is a missing continuity step, rather than something the checker should invent to rescue the story.

The first check rejected one known bad phrase, slipped the brass key into a pocket. The second required the placement phrase to appear before the collection phrase. Both had to pass:

def checks(text):
    forbidden = "slipped the brass key into a pocket" not in text
    first = text.find("left the brass key on the counter")
    second = text.find("collected the key from the counter")
    sequence = 0 <= first < second
    return forbidden, sequence, forbidden and sequence

Python’s str.find reports a substring’s position, or -1 when it is absent. That is the whole operation behind the sequence check; it contributes no interpretation of the words around the match.[1]

Here are the actual results. “No bad phrase” means the pocket phrase is absent; “In order” means both required phrases occur in the specified order. The result passes only when both checks pass.

Fixture No bad phrase In order Result
Leaves key on counter, collects it there Yes Yes Pass
Pockets key, collects it from counter No No Fail
Placement deleted, collection remains Yes No Fail
Sets key beside register, retrieves it there Yes No Fail
Never leaves key on counter, collects it there Yes Yes Pass

The third row is why I want positive checks alongside forbidden-pattern checks. Deleting the pocket sentence removes the known bad phrase, but leaves the collection unsupported. A negative-only gate accepts that deletion. Requiring the missing setup catches it.

The fourth and fifth rows limit that improvement. A valid paraphrase fails because the expected wording is absent. A contradiction passes because the expected wording occurs inside a negation. These results follow from the predicates in the code. They are not evidence of a measured failure rate for fiction agents.

Let the gate be fussy about the right things

I still want mechanical tests in a writing workflow. A cheap check that catches a recurring contradiction before export saves a reader from finding it again. Exact wording can even be an intentional requirement: a place name, a chapter heading, or a sentence an author has decided must survive unchanged.

But I would name this particular result placement_phrase_before_collection_phrase, not continuity_verified. The longer name is awkward enough to remind me what happened. The shorter one invites me to claim a semantic review I never performed.

The story bible has a different job. It explains the intended event and what variations are allowed. The test translates a small part of that intention into a condition that can block a build. When those disagree, I need to inspect the passage and the rule. Automatically replacing a perfectly good paraphrase just to satisfy an exact match would let the validator make stylistic decisions it was never qualified to make.

Adding a ban on the word never would repair this one fixture badly. The same word could occur in dialogue, a memory, or an unrelated sentence. The checker would acquire another false alarm without gaining a general understanding of negation. I would rather preserve the failing example and decide whether the rule needs a narrower scene boundary, a structured event record, or a semantic review.

Those alternatives also have costs. A structured event record can be wrong. A model reviewing the passage can miss a contradiction or misunderstand an intentional ambiguity. For a consequential canon rule, I want the review to point to the relevant passage and explain its reading, with the author resolving disputed interpretation. I would need to inspect that explanation against the passage.

Check what the reader receives

I also ran a separate stale-export simulation. The source variable contained the consistent first fixture. The export variable retained the pocket contradiction. The combined check passed on the source and failed on the export. This was a pair of text variables, not a PDF or EPUB build.

It demonstrates the limited claim directly: checking corrected source does not check another artifact that still contains older text. In a real release, I would run the narrow invariants against extracted reading text from each maintained edition, then inspect the rendered passage when layout or extraction could affect what a reader sees. I did not perform those format checks in this experiment.

An agent’s completion report should preserve that distinction. “Source corrected” leaves room for an export still waiting to rebuild. “Release checked” needs evidence from the release. It is tempting to collapse those states when the hard edit is finished and the remaining work looks routine. The reader only receives the latter artifact.

For this checker, I would keep the deletion-catching positive gate and retain both the valid paraphrase and negated placement as regression fixtures documenting its limits. A passing run would mean the specified wording checks passed. I would still read the sentence about the key.

Sources

[1] Python documentation: str.find