Repair history
Changes to the engine after the published measurements, newest first, with how each was found, fixed and verified.
How it works describes the engine's current behavior; this page keeps the dated record behind it. All seven repairs were made on September 27, 2026, in the private engine repository, from commit cecc6d1 to 828c0fe. The Architecture page compares the rewrite at 5d598de, which includes all of them.
The checks recorded here did not rerun the published 21-case comparison or the SWI-Prolog timings. The comparison scores, timings and source-size measurements quoted elsewhere on this site remain tied to their recorded revisions. The review records named in each entry are in the private engine repository. What the reviewers caught in each round is in Building it with AI.
Timeline preparation limits
September 27, 2026 · engine commit 828c0fe · baseline 93eb9d3
In short. A repair to preparation limits makes timeline construction spend the caller's work allowance and check cancellation. Cancelled requests remain indeterminate, interrupted preparation is discarded, and a later request can retry normally.
Engine commit 828c0fe makes the work of preparing the authored timeline obey the initiating request's limits and cancellation signal. Imagine the player closing a laboratory interaction menu while the game prepares the order of events needed to check its door. The request should stop, and a later interaction should be able to retry.
At baseline 93eb9d3, preparation used a separate default allowance and did not receive cancellation. This finite ordering rule made the bypass measurable:
time(lock_check). time(arrival).
before(lock_check, arrival) :- between(1, 50000, N), N = 50000.
initially(unlocked).
affords(enter, laboratory).
guard(enter, 1, holds_at(unlocked, arrival), 'The door is locked').
kb.ask("true", limits=Limits(steps=1)) performed 100,006 preparation steps, then reported one step, complete. An entry decision with a 50-step limit and cancellation already requested returned allow, complete without checking the callback. The reproduction took about 0.11 seconds; it verified the control bypass, not a freeze in the running game.
The one-step request now returns EXHAUSTED with no answers and two reported steps: the second charged step detects the exhausted allowance. The cancelled decision polls once and returns disabled, indeterminate, with status CANCELLED and the neutral availability message. Neither interrupted request publishes a timeline. Retrying with sufficient resources and cancellation cleared produces the ordinary completed decision. A cancelled locked-door check also stays indeterminate rather than claiming an authored refusal.
The shared allowance covers source enumeration, ground effect-time collection, graph construction and ordering diagnostics, as well as subsequent solving. Both ask and explain retain preparation's work and depth accounting. A tabled source's buffered partial answers cannot become a completed timeline. Successfully completed preparation is reusable; interrupted preparation is discarded. Mutation and restore force preparation for the changed revision. Outer answer limits never truncate the timeline's source search.
Cancellation is checked at request entry, after uncached static analysis, and every 512 charged steps during work. Parsing and static analysis are not charged per operation, and integer arithmetic retains its documented limits. These are cooperative work controls, not wall-clock deadlines. Budgets still apply separately to each internal menu query. Public inspection properties use default limits and raise IncompleteResult if preparation stops. Large worlds may require a larger allowance: graph construction's existing quadratic cost is now visible to the budget.
The player reviewer approved after 577 independent scenarios, and the engineering reviewer after 62. The independent agent's 57 new conformance cases produce 50 failures and seven control passes against baseline; all 16 curated mutations are detected, and three hash seeds agree on answers and work accounting. Final runs passed 1,795 correctness cases, 37 predecessor comparisons, and eight benchmarks. Two new differential cases document the request controls absent from the predecessor's API. The retained engine record is docs/TIMELINE_PREPARATION_LIMITS_REVIEW.md, with exact mutation patches, hashes and run selections in its linked report. Verification exercised the Python engine and API; historical SWI comparisons and measurements remain tied to their earlier revisions.
Current behavior: Result status · Building the timeline · Decisions when reasoning can't finish
Circular timeline construction
September 27, 2026 · engine commit c736a1b · baseline 620a5fc
In short. A repair to circular timeline construction prevents rules from using temporal state to establish the very timeline needed to determine that state. The engine now reports uncertainty for those temporal queries, keeps dependent actions disabled, and leaves unrelated actions usable.
Engine commit c736a1b repairs a circular assumption made while establishing story time. Consider a laboratory whose automatic lock operates at lock_check, before the player arrives. The author accidentally makes that ordering depend on whether the door is locked at arrival:
time(lock_check). time(arrival).
before(lock_check, arrival) :- holds_at(locked, arrival).
initiates(auto_lock, locked, lock_check).
affords(enter, laboratory).
guard(enter, 1, \+ holds_at(locked, arrival), 'The door is locked').
The engine needs the order to determine whether the lock has taken effect, but the rule needs that state to establish the order. At baseline 620a5fc, it treated the lock as absent, explained it as never_initiated, and allowed entry, all marked complete. Strict predicate checking did not help: these predicate names are valid. A separate static warning identified the circular dependency without preventing the completed answers. This was reproduced through the Python engine and host API.
The repair returns no temporal answers and status EXHAUSTED, with notes explaining the dependency. In this case that status means reasoning could not be established, rather than a depleted budget. The explanation has holds=None and no asserted cause. Entry is disabled and indeterminate, with Action availability could not be determined. Reading an unrelated notice remains available. Replacing the circular rule with the fact before(lock_check, arrival). restores a completed locked-state answer, its initiation explanation, and the authored locked-door refusal.
The engine retains known dependencies from before/2, time/1 and happens/2 to holds_at/2. During timeline construction, a runtime check also stops reached temporal calls hidden behind computed meta-calls. Both temporal query and explanation entry points reject the affected timeline before calculating effects. Each timeline source discards its bindings and tables before the next source runs, so an interrupted table cannot leak provisional facts into the timeline.
The static policy is conservative: a known dependency blocks temporal evaluation even in an unreachable branch. Opaque calls in branches never reached during construction cannot be validated statically. Ordinary non-temporal queries and independent earlier proofs remain usable, and normal state-dependent effect rules retain their existing semantics. This does not compute a joint fixed point of time and state or change the existing timeline-construction budget. No model or fixture .pl file was modified.
Player and engineering reviewers approved after 112 independent gameplay scenarios and 192 independent adversarial worlds, respectively, as well as targeted regression runs. The independent test agent added 80 conformance cases: 74 fail and six controls pass against archived baseline source. All 16 curated mutations are caught by assertion failures. Final runs passed 1,738 correctness cases, 35 predecessor comparisons, and eight benchmarks. The new differential case confirms that the predecessor also allowed entry in this circular example. The retained engine record is docs/CIRCULAR_TIMELINE_BOOTSTRAP_REVIEW.md, with exact mutation patches and hashes in its linked report. These checks exercised the Python engine and API, not the running AkiyaBlocks game; the earlier SWI comparisons and measurements remain historical.
Current behavior: Building the timeline
Strict game checks
September 27, 2026 · engine commit 33cd9a9 · baseline a710e57
In short. A strict game API repair makes optional guards and labels work with unknown_fails=False. Games can use that existing setting to report reached undefined predicates, such as a misspelled negative guard, while retaining ordinary unguarded actions and fallback labels. The engine's permissive default is unchanged.
Engine commit 33cd9a9 makes strict predicate checking practical for the game API. Consider a player outside a locked laboratory. The author accidentally misspells locked in the entry guard:
affords(enter, laboratory).
affords(read_notice, laboratory).
locked(laboratory).
guard(enter, 1, \+ lokced(laboratory), 'The door is locked').
With the default configuration, the undefined lokced/1 fails, its negation succeeds, and entry is allowed with status complete and no warning. The existing KnowledgeBase(unknown_fails=False) setting already disabled this individual decision and identified the undefined predicate. However, at baseline a710e57, entirely absent guards also disabled ordinary unguarded actions, and absent optional labels made menu construction raise UnknownPredicateError.
The strict menu now returns an indeterminate, disabled enter entry with the neutral availability message and lokced/1 in its notes. It also returns an enabled read_notice entry with the fallback label read notice. A world with no guard definitions at all can offer unguarded actions normally. Correcting the spelling gives the authored locked-door denial; declaring :- dynamic locked/1. instead of providing a lock fact gives a completed allow for the intentionally empty relation.
The change applies only to the host's optional guard/4 and action_label/2 lookups when the name is wholly absent. A wrong-arity-only definition remains an error. Defined rules, actual conditions and the required affords/2 inventory retain normal strict checking. The fallback still runs an ordinary checked query, preserving analysis, resource handling and diagnostics. It does not catch and discard undefined-predicate errors, inject declarations, or change the global default.
Strict mode is recommended for game models, but it is runtime checking of reached calls. It cannot recognize gaurd/4 as a misspelled optional head, detect a misspelled fluent argument inside a valid holds_at/2 call, or inspect alternatives skipped by a first proof. It is not a static authoring validator.
Player and engineering reviewers approved the plan and implementation, passing 59 and 76 independent assertions respectively. The independent test agent added 45 conformance cases: 10 fail and 35 controls pass against archived baseline source, and all ten curated mutations are detected. Final local runs passed 1,658 correctness cases, 34 predecessor differential cases, and eight benchmarks. The engine repository retains the details, hashes and exact mutation patches in docs/STRICT_GAME_API_REVIEW.md and its linked report. Verification exercised the Python engine and API, not the running AkiyaBlocks game; the published SWI comparisons and timings remain historical.
Current behavior: Strict checking for game models
Time-scoped state listings
September 27, 2026 · engine commit 2e0c386 · baseline 19ced37
In short. A time-scoped listing repair lets a present-room query complete when the faulty rule is attached to an incompatible future /3 time. Relevant cycles remain uncertain. Authored rule-body order and global time-discovery limits still apply.
Engine commit 2e0c386 narrows the early-listing limitation recorded under circular temporal reasoning. A player inspects a museum at minute 2. The lights are on; a faulty alarm check at minute 10 refers circularly to its own outcome at minute 11:
time(1). time(2). time(10). time(11).
initially(light_on).
initiates(alarm_check, alarm_on, 10) :- \+ holds_at(alarm_on, 11).
At baseline 19ced37, asking holds_at(light_on, 2) completed true and asking holds_at(alarm_on, 2) completed false. Asking for the whole room with holds_at(F, 2) nevertheless exhausted: its global candidate scan ran the future alarm rule before examining the requested time.
The listing now returns F = light_on with status complete. Candidate discovery uses initial fluents and initiation calls at strictly earlier ticks for each requested point, then checks each candidate with the existing temporal evaluator. Future and incomparable /3 heads that cannot match those ticks are skipped. Queries over all times preserve time order and may return sound earlier answers before encountering a later incomplete point. The relevant alarm query at minute 11 still reports uncertainty, with no definite explanation or permission to act. An aggregate-based room-inspection guard can work at minute 2.
The reviewers rejected reordering /2 effects and their happens/2 join: it changes mode-sensitive rules, cuts and exclusive winners. That order remains intact, so /2 bodies can still reach future reasoning before their occurrence filter. A variable-time /3 body can do the same if its time filter follows the temporal call. Global calculated-time discovery also remains conservative. This repair skips incompatible heads; it does not prove every future rule irrelevant.
Supplying named input ticks deliberately makes universal-time and nonvar(T) /3 rules appear in listings where the existing ground evaluator already proves them. It does not invent new times. The unchanged testworld model now has a completed empty listing at its first point, t0; broad listings at t1 through t6 remain uncertain through its /2 alarm cycle. Current-tick clipping and both tie policies retain their normal behavior.
Player and software-engineering reviewers approved the bounded change. The independent test agent added 58 conformance cases: 33 fail and 25 control cases pass against archived baseline source, and all 14 curated mutations are detected. Final local verification passed 1,613 correctness cases, 32 predecessor differential cases, and eight benchmarks. The private engine repository retains the review, compatibility details, hashes and reproducible mutation patches in docs/SCOPED_TEMPORAL_REVIEW.md and its linked verification report. No model or fixture .pl file was changed. The published SWI comparisons and timings remain historical.
Current behavior: Circular temporal rules
Circular temporal reasoning
September 27, 2026 · engine commit 6d6a9a9 · baseline a468f92
In short. A further repair to circular temporal reasoning stops recursive assumptions from becoming completed answers or permissions, including through tabled helpers. Some previously definite testworld and term_wins results now report uncertainty; this entry states those compatibility limits.
Engine commit 6d6a9a9 stops a circular temporal assumption from becoming a completed answer and permission to act. Imagine a laboratory door that starts unlocked. An automatic evening lock should check the state before locking, but its author accidentally checks the state at the player's later arrival time:
time(1). time(9).
initially(unlocked).
terminates(auto_lock, unlocked, 1) :- holds_at(unlocked, 9).
affords(enter, laboratory).
guard(enter, 1, holds_at(unlocked, 9), 'The laboratory door is locked').
The state at minute 9 depends on whether the lock fired at minute 1, which depends on that same state at minute 9. The previous engine detected reentry, temporarily answered false, skipped the locking effect, and then reported the door unlocked with status complete. It explained the initial state and allowed entry, despite its circularity warning. This was reproduced after both preceding repairs, against revision a468f92.
The repaired query stops as EXHAUSTED, with no assumed answer and a diagnostic naming the circular call. Its explanation has holds=None and no asserted cause; entry is disabled and indeterminate with the neutral availability message. Under the default policy, changing the condition to check minute 1 gives the normal completed denial and an explanation naming the locking effect.
The stop crosses negation, control flow, aggregates and tables. A separate engineering-review finding showed why changing only the reentry guard was insufficient: a tabled helper could read its unfinished caller's table as empty and bypass the guard entirely. Synchronous temporal calculations now use an independent table store when entered from an active table session, restoring the caller's store before yielding. Ordinary recursive helpers can still complete their own fixed point.
This is a conservative runtime boundary, not a three-valued well-founded solver. It preserves independent proofs already obtained and leaves unrelated queries usable, but does not explore later alternatives after encountering a cycle. Current-tick checks under term_wins can encounter cycles that init_wins avoids. The unchanged testworld model has an alarm/secret post-state cycle: affected alarm and secret queries, and broad fluent enumeration, now report uncertainty. Broad enumeration can encounter that cycle even for an early requested time. Normal trust progression and the ordinary story and affordance models remain usable. No model or fixture .pl file was changed.
Player and software-engineering reviewers approved the final behavior. The independent test agent added 59 regression cases: 55 fail and four normal controls pass against the archived baseline, and all ten curated mutations are detected. Final local verification passed 1,555 correctness cases, 31 predecessor differential cases, and eight benchmarks. The timeline benchmark now checks completed trust-slot answers; timing the broad query's early cycle stop would not measure the same work as before. The private engine repository retains the review, compatibility details and mutation summaries in docs/CIRCULAR_TEMPORAL_REVIEW.md. The public 21-case comparison and SWI performance measurements remain historical data.
Current behavior: Circular temporal rules
Calculated effect times
September 27, 2026 · engine commit de62694 · baseline 9856068
In short. A subsequent calculated-time repair fixes a second failure: a timer could derive a door closure at minute 4, yet the engine still allowed entry at minute 9. Supported calculated times now participate without a separate time declaration; unsafe discovery reports uncertainty.
Engine commit de62694 addresses an answer that was wrong while marked complete. In a security-door example, a badge scan unlocks the door at minute 1. At minute 3, a timer schedules its closure one minute later:
time(1). time(3). time(9).
happens(scan_badge, 1). initiates(scan_badge, unlocked).
happens(timer, 3).
terminates(timer, unlocked, T2) :- happens(timer, T), T2 is T + 1.
affords(enter, laboratory).
guard(enter, 1, holds_at(unlocked, 9), 'The laboratory door is locked').
Previously, the engine derived the termination at minute 4 but omitted that calculated point from its temporal calculation. It said the door was still unlocked at minute 9, explained the badge scan without a closure, and allowed entry. Adding time(4). alone changed all three answers. This was reproduced against engine revision 9856068, after the preceding uncertainty fix. The published comparison baseline, 9be7804, had the same limitation: calculated effect times were invisible unless separately named.
The repaired behavior finds minute 4 without that declaration. At minute 9, the answer is false and complete, the explanation names the timer at minute 4, and the action is denied with the authored message. Delayed effects still apply: the door remains unlocked at minute 4 itself and is locked afterward.
Discovery runs inside the temporal query, using the ordinary solver and the caller's cumulative budget and cancellation. It passes candidate times to the existing evaluator, with isolated bindings and tables. It does not change the authored time/1 facts or the cached base timeline.
The supported case is a finite rule that generates its time without reading temporal state. A generator that calls holds_at, directly or indirectly, remains explicitly uncertain; so do discovery errors and clause-level cuts that could make per-clause enumeration misleading. Rules constrained to named output times retain existing state-dependent evaluation. Rules exposing additional answers only with time already supplied still require named ticks. This is a bounded extension, not a general solution to circular temporal rules.
Player and engineering reviewers challenged the plan and implementation; an independent test agent supplied regression cases and a small imperative timer reference. Final local runs passed 1,490 correctness cases, 30 differential cases, and 8 opt-in benchmarks. The 54 new conformance cases produce 45 failures and nine passes against the prior engine source; all 14 targeted source mutations were detected. The retained record and per-mutation summaries are linked from docs/COMPUTED_EFFECT_TIME_REVIEW.md in the private engine repository. The published 21-case comparison and SWI timing measurements still describe their earlier pinned revisions.
Current behavior: Calculated effect times
Preserving uncertainty
September 27, 2026 · engine commit cecc6d1
In short. A repair to game decisions and explanations keeps unfinished reasoning from enabling an action or claiming a definite cause.
Engine commit cecc6d1 repairs two places where the solver reported unfinished work but the host API lost that information. A partial list of action guards could enable an action, and an incomplete timeline could receive a definite explanation.
For example, load this program with KnowledgeBase(auto_table=False):
affords(open_gate, gate).
guard(open_gate, 1, true, 'Ready').
guard(open_gate, 2, true, 'Unused') :- loop.
guard(open_gate, 3, fail, 'The gate is locked').
loop :- loop.
The guard search finds the first row, then runs out of budget in loop before reaching the locked-gate requirement. Previously, decide(kb, "open_gate", "gate") could approve the action using that partial list. It now returns kind="indeterminate", allowed=False, status=Completion.EXHAUSTED, and the neutral message "Action availability could not be determined." Removing the looping guard produces a completed denial with "The gate is locked". Removing the locked-gate guard as well permits the action normally.
| Situation | Repaired host behavior |
|---|---|
| Guard discovery or a necessary condition is incomplete | Disabled indeterminate decision, with status and diagnostic notes; no authored refusal presented as fact |
| A temporal explanation is incomplete | holds=None in Python (null in JSON), reason="unexplained", status and notes, and no asserted initiating or terminating cause |
| Reasoning completes | Ordinary allow/deny/abstain decisions and guard priority; completed false explanations still have holds=False |
These are host-visible API changes. Decision and menu JSON now include status and notes; menu entries also include their decision kind. Typed engine errors during decide produce indeterminate decisions with JSON status "error". The plain-list menu API raises IncompleteResult if action discovery or label lookup is incomplete. Hosts must handle unknown explanation truth separately from false. Limits apply per internal query, not as one budget for an entire menu.
The repair was reviewed from player and software-engineering perspectives, with an independent test agent. The recorded local runs passed 1,436 correctness cases (including 253 comparison-harness self-tests), 29 differential cases, and 8 opt-in benchmarks. The 39 new conformance cases and one new differential case cover the repair; tests detected all nine targeted source mutations. The private engine repository retains the review and verification summary in docs/INCOMPLETE_RESULT_REVIEW.md.
Those checks did not rerun the published 21-case comparison or the SWI performance measurements. They establish specific regression coverage, not general correctness. The timed /3 interpretation and quiet default for unknown predicates described in How it works remain unchanged.
Current behavior: Decisions when reasoning can't finish · Explanations and provenance