Cyclops Storm / Lab notes

How the engine grew

The long version of the story in the README. This page covers what the engine could do at each stage, what it took to get there, and what went wrong along the way. It's organized by what became possible, not by which module was rewritten.

Sources. Dates and counts come from the engine's private repositories: commit history, the test log the project kept from the start, and small programs run against saved snapshots of each engine. The test counts are "passing pytest cases" as logged at the time. They measure activity more than quality; evidence/README.md explains why. Where something is my inference rather than a record, I say so.

Same programs, three engines

The most direct way to see what changed is to run small, textbook programs on saved snapshots of each engine, with SWI-Prolog 10.0.2 as the reference.

Program and query SWI-Prolog Feb 2026 Aug 2026 Rewrite, Sep 2026
c(X) :- X = 1, !. c(2). → c(X) 1 1, 2 1, 2 1
max(X,Y,X) :- X >= Y, !. max(_,Y,Y). → max(3,2,M) 3 2, 3 2, 3 3
e(a,b). e(b,c). e(c,d). lp(X,Y) :- lp(X,Z), e(Z,Y). lp(X,Y) :- e(X,Y). → lp(a,Y) b, c, d (with tabling) b b, c, d b, c, d
item(a). item(b). blocked(b). s(X) :- \+ blocked(X), item(X). → s(X) none a a none
p(1). p('1'). p(1.0). → p(X) 1, '1', 1.0 not run 1 1, '1', 1.0
t(X) :- X is a + 1. → t(X) type error not run no answers type error

How to read it:

At a glance

Stage Dates Passing tests Python source Headline
Prototype Aug 2025 hundreds ~13,000 lines Stories become facts that change over time
Rapid growth Sep–Oct 2025 525 → 2,552 → ~54,000 Most of the Prolog basics; tabling
Consolidation Nov 2025–Feb 2026 → 3,846 → ~61,000 One path for negation; explanations; tools
Own repository Feb–Aug 2026 → 6,691 peak → 6,660 → ~54,000 Bug waves, AI audits, deletions, a host API
Rewrite Sep 2026 540 → 1,144 ~7,400 Rebuilt from a brief, old engine as reference, verification first

Source sizes are non-test Python in each source tree. The earlier tree also held a parser, a command-line game and tools, so the rewrite's figure isn't an engine-for-engine comparison.


1. A weekend prototype (August 2025)

I'd been writing short science-fiction stories on Substack, and I wanted to see whether their worlds could live in a logic engine: who is where, what they know, what changed and why. Over a weekend, Claude Code and GPT-5 helped me turn a few stories into Prolog-style facts, plus a Python engine to reason over them. I wrote about it at the time as "fly-by-wire coding": the code arrived faster than I could read it, and I steered by asking the agents to analyze what they'd built.

The earliest surviving commit (Aug 30, 2025, a port from an earlier repository that isn't part of this record) shows what the prototype could do:

It had no Prolog-style backtracking, no cut and no arithmetic. On that snapshot, a left-recursive rule crashed Python's stack, a clause with a cut returned nothing, and Y is X * 2 returned nothing.

2. Growing up fast (September–October 2025)

Passing tests 525 → 2,552. Source roughly 13,000 → 54,000 lines.

Most of the Prolog basics arrived in these two months:

Date Capability
Aug 31 Disjunction (;) evaluated at run time
Sep 4 A depth-first, backtracking evaluator (a goal stack with choice points)
Sep 6–7 Cut parsed and handled
Sep 8–9 Proper variable identity during unification
Sep 14 A router choosing between the original set-at-a-time evaluator and the new backtracking one
Sep 23–27 forall, if-then-else, member/append/length/sort/findall, list syntax, between, atom built-ins, integer division
Oct 12 Tabling, so recursive rules terminate
Oct 17 Caches invalidated by an "epoch" counter

One detail explains a lot of what follows. The router sent almost every query to the original evaluator. The backtracking engine only saw a short allowlist of predicates and, eventually, queries containing a cut. So for most of its life the earlier engine had Prolog's syntax and unification but not its execution model: it deduplicated answers and reordered goals.

It was a noisy two months:

The fix was the bug

By October the engine would sometimes vanish into runaway recursion, with the CPU pinned for minutes. The first remedy was a set of depth limits:

The runaways stopped. Path queries also started coming back short, with valid links missing from ancestor chains, and nothing said so.

I fed a pile of Claude's status reports to GPT-5, which had no access to the code. It diagnosed "unbounded recursion masked as 'maybe progress'" and suggested tabling. Claude implemented it on Oct 12. I wrote that up under the heading "the 'fix' was the bug."

The sequel didn't make the article. Two things went wrong.

The depth limits never left. They stayed in the code as a backstop, and in April 2026 one cap was raised from 20 to 50. At the end, a 60-link chain still came back with no path, and the only sign was a line printed to the console.

The tabling wasn't complete. The docs called the new code "SLG resolution," but it was answer memoization without the part that makes tabling complete: a recursive call got whatever partial answers existed, and the "fixpoint" loop never actually ran. Left-recursive queries returned incomplete answers (a snapshot from just before the change was already incomplete too). Every tabling test recursed the other way, so the suite stayed green. It took until June 2026, and an AI audit, to find that the "saturation loop that ensures completeness" had never run.

3. Consolidation, and what the tests couldn't see (November 2025–February 2026)

Passing tests 2,552 → 3,846. Source roughly 54,000 → 61,000 lines.

The last snapshot before the move to a dedicated repository (Feb 21, 2026) still got the basics in the table above wrong. The tests missed them for specific reasons:

4. Its own repository (February–August 2026)

Passing tests 3,846 → 6,691 (May 6) → 6,660. Source roughly 61,000 → 54,000 lines.

The engine moved into a dedicated repository on Feb 22.

The suite grew by about 2,500 tests in March alone, the biggest month on record. - April–May: speed. An answer cache and indexing were switched on by default (Apr 24). The cache handed every caller the same internal list, so a caller that appended to a result changed what the next identical query returned. A test pinned that behavior as a "contract," and it was never fixed. The caching itself was worth having. On a repeated query it made this engine about a hundred times faster than the rewrite, and copying each answer before handing it out would have fixed the bug while keeping nearly all of that speed (evidence). - June: the audits. Several AI reviews ran on June 12–13. - Fable 5. A static read by Claude (Fable 5), split across 14 reviewers, with no tests run. Its verdict: the engine worked for the shipped stories, and its "failure mode just outside that envelope is silence." - A 324-agent review. Another review used 324 agents over about two hours. Its 295 raw findings went through adversarial verification: 18 were refuted outright and about 150 downgraded, leaving 1 critical, 5 high, 40 medium and about 229 low. Its headline: the engine was "largely correct on its shipped KB," and "Its real disease is structural." - The critical finding was tabling: the completeness loop had never run. It was fixed on June 14. The same day, about 5,200 lines of dead code were deleted, along with roughly 260 tests (about 4% of the suite) that had only ever exercised that code.

I wrote about the review and decided on consolidation rather than a rewrite. That decision lasted about three months. - July–August: more basics. - The backtracking evaluator had been dropping every alternative after the second. - Clauses containing a cut had always failed on that path. - Answer order depended on Python's hash seed, so it could change between runs. - A top-level if-then-else always returned no answers. - Inside a rule body, one matching fact could hide every rule answer. - Some Event Calculus tests still passed with the Event Calculus switched off. - A small host API landed in August, with "can I do this here?" game hooks.

Where it ended, Aug 30, 2026 (from probes of that snapshot, beyond the table at the top):

By the audits' account it still handled its shipped stories, and it still passed 6,660 tests.

5. Starting over (September 2026)

Tests 540 → 1,144 (a new suite). Source about 7,400 lines.

In September I tried the other strategy: start over. Over three days I wrote three prompts, about 540 lines in all, and had agents build a new engine from them in a new repository, without importing or copying the old code. Besides goals and non-goals, the prompts prescribed how to check the work: an adversarial architecture review, mutation testing in separate worktrees, an audit of the old tests, and negative cases that distinguish a correct engine from "an always-empty or always-successful one." The old repository stayed available as a read-only "working reference implementation." The first prompt describes it as a source of "semantic requirements, domain knowledge, useful algorithms, and regression cases," not a blueprint. The detailed behavioral contract, architecture notes and list of limitations were written by the agents, as part of the first kernel commit. The first prompt asked for:

Its non-goals were just as explicit. The first was "Do not expand this into a general-purpose Prolog implementation." Test-count parity was another.

When (2026) What happened
Sep 13, 19:51 Seed prompt committed
Sep 14, 05:56 First kernel commit: kernel, docs and 540 tests, after an adversarial architecture review and 36 deliberate mutations (10 defects fixed before the commit). Replayed later, this commit already passes 17 of the 21 cross-checks
Sep 14, evening Second prompt: a test-migration and adversarial coverage audit
Sep 15 Old suite audited file by file; 842 tests. Third prompt that evening: audit the suite and kernel "in both directions"
Sep 16 Audit "in both directions": about 20 more defects; 1,023 tests
Sep 22 Five parallel AI reviewers, plus a sixth told to refute the fix plan: 5 wrong answers, a crash, a state leak; 1,144 tests
Sep 22 Comparison harness against the old engine, with SWI-Prolog as referee
Sep 24 Harness hardened; results published

What happened to the old tests. The migration audit went through all 6,778 test cases in the old suite:

The rewrite's classification (made file by file) Cases Share
Kernel behavior: adapted, already covered, or consolidated 2,519 37%
Tested machinery the rewrite removed 3,421 50%
Application layers left out (game, CLI, tools) 800 12%
Blocked on a deferred feature 38 <1%

These are the rewrite's own judgments of its predecessor's tests, and tests of internals weren't worthless in their time. The audit also found:

The original comparison (historical scoring). 21 small programs were run on both engines. 12 were refereed by SWI-Prolog (several also cite the ISO standard); 8 were judged by recorded rulings; 1 combined an SWI answer with a judgment about the Python API. Under the original rules, the rewrite passed 18 and the old engine 10. The September 25 rerun with corrected scoring records old 11 pass / 10 fail and rewrite 16 pass / 3 fail / 2 unknown. The figures below describe the original report, not the current score. evidence/README.md has the breakdown. The short version:

What the rewrite gave up:

What it still gets wrong, without saying so:

It also slows down sharply on some recursive code. ENGINE.md lists these and the rest.

Sources

The engine repositories are private. Links will be added when they're released.