{
 "about": "Matched lab-door probes run against both engines on 2026-09-28. This file holds the observed data; evidence/architecture-2026-09-28.md separates code-verified mechanism, observed behavior and interpretation. The comment in the lab_story.pl input refers to the probe stage's working notes, summarized there.",
 "revisions": {
  "predecessor": {
   "repo": "BadScienceFiction-CyclopsStorm",
   "commit": "e62758c23f66b26fa92c6d2dafaaf6bf3466499f",
   "short": "e62758c",
   "tree": "clean"
  },
  "rewrite": {
   "repo": "AkiyaBlocks-CyclopsStorm",
   "commit": "5d598de4448749dc0ce2cb81657aa952d748e2f5",
   "short": "5d598de",
   "tree": "clean"
  }
 },
 "environment": {
  "predecessor": {
   "engine": "predecessor BadScienceFiction-CyclopsStorm",
   "revision": "e62758c23f66b26fa92c6d2dafaaf6bf3466499f",
   "python": "3.12.10",
   "implementation": "CPython",
   "os": "Windows-11",
   "PYTHONHASHSEED": "0",
   "PYTHONDONTWRITEBYTECODE": "1",
   "dont_write_bytecode": true
  },
  "rewrite": {
   "engine": "rewrite AkiyaBlocks-CyclopsStorm",
   "revision": "5d598de4448749dc0ce2cb81657aa952d748e2f5",
   "python": "3.12.10",
   "implementation": "CPython",
   "os": "Windows-11",
   "PYTHONHASHSEED": "0",
   "PYTHONDONTWRITEBYTECODE": "1",
   "dont_write_bytecode": true
  }
 },
 "method": [
  "Each engine's src/ exported with `git archive <commit> src` into a scratch folder; probes import only the export.",
  "PYTHONDONTWRITEBYTECODE=1 and python -B; no package installed; PyYAML 6.0.3 from the system interpreter.",
  "Runtime observation uses sys.monitoring PY_START (function entry, not generator resumption); no engine code edited.",
  "Each probe run under PYTHONHASHSEED 0, 1 and 2; results below are seed 0, stability recorded separately."
 ],
 "inputs": {
  "lab_story.pl": "% Shared story: may the player enter the laboratory after the door is locked?\n% Identical input for both engines. Effect axioms are ground facts (see RESULTS.md).\ntime(t0). time(t1). time(t2). time(t3).\nbefore(t0, t1). before(t1, t2). before(t2, t3).\n\ninitially(door_open(lab)).\nhappens(lock_door(lab), t1).\nterminates(lock_door(lab), door_open(lab)).\ninitiates(lock_door(lab), locked(lab)).\n\ncan_enter(R) :- now(T), holds_at(door_open(R), T).\n\naffords(enter_lab, corridor).\nguard(enter_lab, 1, can_enter(lab), 'The laboratory door is locked.').\naction_label(enter_lab, 'enter the laboratory').\n",
  "now_t0.pl": "now(t0).\n",
  "now_t2.pl": "now(t2).\n",
  "route_10.pl": "% Case B: the laboratory is 10 tunnel segments from the corridor.\nguard(enter_lab, 2, reachable(corridor, lab), 'You cannot find a way to the laboratory.').\nreachable(A, B) :- passage(A, B).\nreachable(A, B) :- passage(A, C), reachable(C, B).\npassage(corridor, tunnel1).\npassage(tunnel1, tunnel2).\npassage(tunnel2, tunnel3).\npassage(tunnel3, tunnel4).\npassage(tunnel4, tunnel5).\npassage(tunnel5, tunnel6).\npassage(tunnel6, tunnel7).\npassage(tunnel7, tunnel8).\npassage(tunnel8, tunnel9).\npassage(tunnel9, lab).\n",
  "cycle.pl": "% Case B3: an extra effect whose condition refers to the same fluent later on.\nhappens(auto_lock(lab), t1).\nterminates(auto_lock(lab), door_open(lab)) :- holds_at(door_open(lab), t3).\n",
  "lab_story_nonground_effects.pl": "% Shared story: may the player enter the laboratory after the door is locked?\n% Identical input for both engines. Effect axioms are ground facts (see RESULTS.md).\ntime(t0). time(t1). time(t2). time(t3).\nbefore(t0, t1). before(t1, t2). before(t2, t3).\n\ninitially(door_open(lab)).\nhappens(lock_door(lab), t1).\nterminates(lock_door(R), door_open(R)).\ninitiates(lock_door(R), locked(R)).\n\ncan_enter(R) :- now(T), holds_at(door_open(R), T).\n\naffords(enter_lab, corridor).\nguard(enter_lab, 1, can_enter(lab), 'The laboratory door is locked.').\naction_label(enter_lab, 'enter the laboratory').\n"
 },
 "inputs_generated": {
  "route_60.pl": "story_inputs.route(60): 60 passage/2 facts corridor -> tunnel1 ... tunnel59 -> lab, plus guard 2 and the two reachable/2 clauses shown in route_10.pl"
 },
 "inputs_sha256": {
  "lab_story.pl": {
   "sha256": "cf9fc13c05cdbfa63464d39fa4f32cbb16a00938622b0348c471bda503126212",
   "lines": 15
  },
  "lab_story_nonground_effects.pl": {
   "sha256": "66fc82f2247b4f1ce363b0cd0c2675668776cabd247bb5886bfe592c02880a9c",
   "lines": 15
  },
  "now_t0.pl": {
   "sha256": "18c8040714b30cfccc1ce040a373ccbec3b2a6bb9cf7c84f458a6419d3c07450",
   "lines": 1
  },
  "now_t2.pl": {
   "sha256": "a3ebdec3dbdada71cc618d68d8a876a60f5987df9457eeef056e40f8a097e981",
   "lines": 1
  },
  "route_10.pl": {
   "sha256": "6db473225bd367bd67df1afaf20561f35359a07df52af78b08b0596fb0e08e25",
   "lines": 14
  },
  "route_60.pl": {
   "sha256": "e793c2e68317e2696b32feefba616d1f12880336026ab8d322342fca4a3dd84f",
   "lines": 64
  },
  "cycle.pl": {
   "sha256": "76f50f15c48c8f518e64442baa03e2a8f39ecdbb3f86fe7111b7a7f7f6608f05",
   "lines": 3
  }
 },
 "case_A_ordinary": {
  "question": "After the door-locking event (now = t2), may the player enter the laboratory?",
  "inputs": [
   "lab_story.pl",
   "now_t2.pl"
  ],
  "answers": [
   {
    "goal": "holds_at(door_open(lab), t0)",
    "predecessor": [
     {}
    ],
    "rewrite": [
     {}
    ],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "holds_at(door_open(lab), t1)",
    "predecessor": [
     {}
    ],
    "rewrite": [
     {}
    ],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "holds_at(door_open(lab), t2)",
    "predecessor": [],
    "rewrite": [],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "holds_at(door_open(lab), t3)",
    "predecessor": [],
    "rewrite": [],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "holds_at(locked(lab), T)",
    "predecessor": [
     {
      "T": "t2"
     },
     {
      "T": "t3"
     }
    ],
    "rewrite": [
     {
      "T": "t2"
     },
     {
      "T": "t3"
     }
    ],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "holds_at(F, t2)",
    "predecessor": [
     {
      "F": "locked(lab)"
     }
    ],
    "rewrite": [
     {
      "F": "locked(lab)"
     }
    ],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "now(T)",
    "predecessor": [
     {
      "T": "t2"
     }
    ],
    "rewrite": [
     {
      "T": "t2"
     }
    ],
    "rewrite_status": "complete",
    "same_answers": true
   },
   {
    "goal": "can_enter(lab)",
    "predecessor": [],
    "rewrite": [],
    "rewrite_status": "complete",
    "same_answers": true
   }
  ],
  "decide(enter_lab, corridor), now=t2": {
   "predecessor": {
    "action": "enter_lab",
    "kind": "deny",
    "enabled": false,
    "condition": "can_enter(lab)",
    "message": "The laboratory door is locked."
   },
   "rewrite": {
    "action": "enter_lab",
    "kind": "deny",
    "enabled": false,
    "condition": "can_enter(lab)",
    "message": "The laboratory door is locked.",
    "order": 1,
    "status": "complete",
    "notes": []
   },
   "same_kind_enabled_message": true
  },
  "decide(enter_lab, corridor), now=t0 (control)": {
   "predecessor": {
    "action": "enter_lab",
    "kind": "allow",
    "enabled": true,
    "condition": null,
    "message": null
   },
   "rewrite": {
    "action": "enter_lab",
    "kind": "allow",
    "enabled": true,
    "condition": null,
    "message": null,
    "order": null,
    "status": "complete",
    "notes": []
   },
   "same_kind_enabled_message": true
  },
  "affordances(corridor), now=t2": {
   "predecessor": [
    {
     "action": "enter_lab",
     "label": "enter the laboratory",
     "enabled": false,
     "message": "The laboratory door is locked."
    }
   ],
   "rewrite": [
    {
     "action": "enter_lab",
     "label": "enter the laboratory",
     "enabled": false,
     "message": "The laboratory door is locked.",
     "kind": "deny",
     "status": "complete",
     "notes": []
    }
   ]
  },
  "request path, first query can_enter(lab) after load (landmarks entered, file:def-line)": {
   "predecessor": [
    "KBEngine.query (kb/engine/kb_engine.py:748) x1",
    "QueryExecutionPipeline.execute (kb/engine/query/pipeline/pipeline.py:70) x1",
    "CacheLookupStage.process (kb/engine/query/pipeline/stages/cache_lookup_stage.py:37) x1",
    "PerformanceManager.get_cached_result (kb/engine/performance.py:153) x1",
    "ValidationStage.process (kb/engine/query/pipeline/stages/validation_stage.py:26) x1",
    "ParsingStage.process (kb/engine/query/pipeline/stages/parsing_stage.py:38) x1",
    "TableManager.clear (kb/engine/tabling/table_manager.py:563) x1",
    "RoutingStage.process (kb/engine/query/pipeline/stages/routing_stage.py:24) x1",
    "HandlerRegistry.get_handler_or_default (kb/engine/query/registry.py:108) x1",
    "ExecutionStage.process (kb/engine/query/pipeline/stages/execution_stage.py:26) x1",
    "GeneralPredicateHandler.handle (kb/engine/query/query_handlers.py:938) x1",
    "Reasoner.evaluate_derived (kb/engine/reasoning/reasoner.py:768) x1",
    "QueryCache.get_derived_result (kb/engine/query_cache.py:130) x1",
    "QueryAnalyzer.analyze (kb/routing.py:282) x1",
    "RoutingFSM.decide (kb/routing.py:453) x1",
    "EngineRouter.eval_with_engine (kb/engine/routing/engine_router.py:99) x1",
    "LegacyDerivedEvaluator.evaluate_with_special_cases (kb/engine/derived_evaluation/legacy_evaluator.py:416) x1",
    "DerivedEvaluationTemplate.evaluate (kb/engine/derived_evaluation/template.py:44) x1",
    "LegacyDerivedEvaluator._check_recursion (kb/engine/derived_evaluation/legacy_evaluator.py:63) x1",
    "BodyEvaluationCoordinator.evaluate_with_strategies (kb/engine/evaluation/body_coordinator.py:57) x1",
    "Reasoner._evaluate_literal (kb/engine/reasoning/reasoner.py:1225) x2",
    "Reasoner._evaluate_holds_at_literal (kb/engine/reasoning/reasoner.py:1374) x1",
    "Reasoner.holds_at (kb/engine/reasoning/reasoner.py:705) x1",
    "FluentEvaluator.holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:96) x1",
    "FluentEvaluator._compute_holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:164) x1",
    "PersistenceEvaluator._is_terminated_before (kb/engine/reasoning/temporal/persistence.py:243) x1",
    "TemporalReasoner.lt_time (kb/engine/reasoning/temporal/temporal_reasoner.py:58) x7",
    "TemporalReasoner._compute_reachability (kb/engine/reasoning/temporal/temporal_reasoner.py:277) x1",
    "QueryCache.cache_derived_result (kb/engine/query_cache.py:158) x1",
    "ProjectionStage.process (kb/engine/query/pipeline/stages/projection_stage.py:25) x1",
    "PerformanceManager.cache_result (kb/engine/performance.py:147) x1",
    "MetricsStage.process (kb/engine/query/pipeline/stages/metrics_stage.py:30) x1"
   ],
   "predecessor_last_route": [
    "LEGACY",
    "default"
   ],
   "rewrite": [
    "KnowledgeBase.ask (cyclops/engine.py:339) x1",
    "KnowledgeBase._run (cyclops/engine.py:384) x1",
    "KnowledgeBase._context (cyclops/engine.py:646) x1",
    "QueryContext.__init__ (cyclops/solve/context.py:92) x2",
    "KnowledgeBase._refresh (cyclops/engine.py:668) x1",
    "analyse (cyclops/solve/analysis.py:114) x1",
    "KnowledgeBase._build_timeline (cyclops/engine.py:815) x1",
    "build_timeline (cyclops/temporal/timeline.py:120) x1",
    "solve (cyclops/solve/machine.py:42) x31",
    "resolve_clauses (cyclops/solve/machine.py:275) x12",
    "holds_at (cyclops/temporal/calculus.py:671) x1",
    "prepare_effect_times (cyclops/temporal/timeline.py:306) x1",
    "_holds (cyclops/temporal/calculus.py:487) x1",
    "_explain (cyclops/temporal/calculus.py:548) x1",
    "_starts_and_stops (cyclops/temporal/calculus.py:404) x1",
    "_effects (cyclops/temporal/calculus.py:204) x4",
    "_clipped (cyclops/temporal/calculus.py:469) x1",
    "Timeline.precedes (cyclops/temporal/timeline.py:92) x17"
   ],
   "rewrite_QueryContext_objects_constructed": 2
  },
  "same query repeated on the same engine": {
   "predecessor": {
    "landmarks": [
     "KBEngine.query (kb/engine/kb_engine.py:748) x1",
     "QueryExecutionPipeline.execute (kb/engine/query/pipeline/pipeline.py:70) x1",
     "CacheLookupStage.process (kb/engine/query/pipeline/stages/cache_lookup_stage.py:37) x1",
     "PerformanceManager.get_cached_result (kb/engine/performance.py:153) x1",
     "MetricsStage.process (kb/engine/query/pipeline/stages/metrics_stage.py:30) x1"
    ],
    "distinct_functions_entered": 16,
    "last_route": null
   },
   "rewrite": {
    "landmarks": [
     "KnowledgeBase.ask (cyclops/engine.py:339) x1",
     "KnowledgeBase._run (cyclops/engine.py:384) x1",
     "KnowledgeBase._context (cyclops/engine.py:646) x1",
     "QueryContext.__init__ (cyclops/solve/context.py:92) x1",
     "KnowledgeBase._refresh (cyclops/engine.py:668) x1",
     "solve (cyclops/solve/machine.py:42) x20",
     "resolve_clauses (cyclops/solve/machine.py:275) x9",
     "holds_at (cyclops/temporal/calculus.py:671) x1",
     "prepare_effect_times (cyclops/temporal/timeline.py:306) x1",
     "_holds (cyclops/temporal/calculus.py:487) x1",
     "_explain (cyclops/temporal/calculus.py:548) x1",
     "_starts_and_stops (cyclops/temporal/calculus.py:404) x1",
     "_effects (cyclops/temporal/calculus.py:204) x4",
     "_clipped (cyclops/temporal/calculus.py:469) x1",
     "Timeline.precedes (cyclops/temporal/timeline.py:92) x1"
    ],
    "distinct_functions_entered": 85,
    "QueryContext_objects_constructed": 1
   }
  },
  "decide path (first request after load)": {
   "predecessor": [
    "decide (kb/integration/affordance.py:168) x1",
    "_guards_for (kb/integration/affordance.py:127) x1",
    "_holds (kb/integration/affordance.py:153) x1",
    "KBEngine.query (kb/engine/kb_engine.py:748) x3",
    "QueryExecutionPipeline.execute (kb/engine/query/pipeline/pipeline.py:70) x3",
    "CacheLookupStage.process (kb/engine/query/pipeline/stages/cache_lookup_stage.py:37) x3",
    "PerformanceManager.get_cached_result (kb/engine/performance.py:153) x3",
    "ValidationStage.process (kb/engine/query/pipeline/stages/validation_stage.py:26) x3",
    "ParsingStage.process (kb/engine/query/pipeline/stages/parsing_stage.py:38) x3",
    "TableManager.clear (kb/engine/tabling/table_manager.py:563) x3",
    "RoutingStage.process (kb/engine/query/pipeline/stages/routing_stage.py:24) x3",
    "HandlerRegistry.get_handler_or_default (kb/engine/query/registry.py:108) x3",
    "ExecutionStage.process (kb/engine/query/pipeline/stages/execution_stage.py:26) x3",
    "GeneralPredicateHandler.handle (kb/engine/query/query_handlers.py:938) x3",
    "Reasoner.evaluate_derived (kb/engine/reasoning/reasoner.py:768) x1",
    "QueryCache.get_derived_result (kb/engine/query_cache.py:130) x1",
    "QueryAnalyzer.analyze (kb/routing.py:282) x1",
    "RoutingFSM.decide (kb/routing.py:453) x1",
    "EngineRouter.eval_with_engine (kb/engine/routing/engine_router.py:99) x1",
    "LegacyDerivedEvaluator.evaluate_with_special_cases (kb/engine/derived_evaluation/legacy_evaluator.py:416) x1",
    "DerivedEvaluationTemplate.evaluate (kb/engine/derived_evaluation/template.py:44) x1",
    "LegacyDerivedEvaluator._check_recursion (kb/engine/derived_evaluation/legacy_evaluator.py:63) x1",
    "BodyEvaluationCoordinator.evaluate_with_strategies (kb/engine/evaluation/body_coordinator.py:57) x1",
    "Reasoner._evaluate_literal (kb/engine/reasoning/reasoner.py:1225) x2",
    "Reasoner._evaluate_holds_at_literal (kb/engine/reasoning/reasoner.py:1374) x1",
    "Reasoner.holds_at (kb/engine/reasoning/reasoner.py:705) x1",
    "FluentEvaluator.holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:96) x1",
    "FluentEvaluator._compute_holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:164) x1",
    "PersistenceEvaluator._is_terminated_before (kb/engine/reasoning/temporal/persistence.py:243) x1",
    "TemporalReasoner.lt_time (kb/engine/reasoning/temporal/temporal_reasoner.py:58) x7",
    "TemporalReasoner._compute_reachability (kb/engine/reasoning/temporal/temporal_reasoner.py:277) x1",
    "QueryCache.cache_derived_result (kb/engine/query_cache.py:158) x1",
    "ProjectionStage.process (kb/engine/query/pipeline/stages/projection_stage.py:25) x3",
    "PerformanceManager.cache_result (kb/engine/performance.py:147) x3",
    "MetricsStage.process (kb/engine/query/pipeline/stages/metrics_stage.py:30) x3"
   ],
   "rewrite": [
    "decide (cyclops/api/affordance.py:165) x1",
    "_ask (cyclops/api/affordance.py:125) x3",
    "_guards (cyclops/api/affordance.py:149) x1",
    "KnowledgeBase.ask (cyclops/engine.py:339) x3",
    "KnowledgeBase._run (cyclops/engine.py:384) x3",
    "KnowledgeBase._context (cyclops/engine.py:646) x3",
    "QueryContext.__init__ (cyclops/solve/context.py:92) x4",
    "KnowledgeBase._refresh (cyclops/engine.py:668) x3",
    "analyse (cyclops/solve/analysis.py:114) x1",
    "KnowledgeBase._build_timeline (cyclops/engine.py:815) x1",
    "build_timeline (cyclops/temporal/timeline.py:120) x1",
    "solve (cyclops/solve/machine.py:42) x37",
    "resolve_clauses (cyclops/solve/machine.py:275) x14",
    "holds_at (cyclops/temporal/calculus.py:671) x1",
    "prepare_effect_times (cyclops/temporal/timeline.py:306) x1",
    "_holds (cyclops/temporal/calculus.py:487) x1",
    "_explain (cyclops/temporal/calculus.py:548) x1",
    "_starts_and_stops (cyclops/temporal/calculus.py:404) x1",
    "_effects (cyclops/temporal/calculus.py:204) x4",
    "_clipped (cyclops/temporal/calculus.py:469) x1",
    "Timeline.precedes (cyclops/temporal/timeline.py:92) x17"
   ],
   "rewrite_QueryContext_objects_constructed": 4
  },
  "state held by the engine object": {
   "predecessor": {
    "state_after_load": {
     "epoch": 2,
     "FluentEvaluator._holds_cache entries": 0,
     "QueryCache._holds_cache entries": 0,
     "QueryCache._derived_cache entries": 0,
     "pipeline PerformanceManager.result_cache entries": 0,
     "TableManager.tables entries": 0,
     "FluentEvaluator._holds_in_progress entries": 0
    },
    "state_after_query": {
     "epoch": 2,
     "FluentEvaluator._holds_cache entries": 1,
     "QueryCache._holds_cache entries": 0,
     "QueryCache._derived_cache entries": 1,
     "pipeline PerformanceManager.result_cache entries": 1,
     "TableManager.tables entries": 0,
     "FluentEvaluator._holds_in_progress entries": 0
    },
    "state_after_repeat": {
     "epoch": 2,
     "FluentEvaluator._holds_cache entries": 1,
     "QueryCache._holds_cache entries": 0,
     "QueryCache._derived_cache entries": 1,
     "pipeline PerformanceManager.result_cache entries": 1,
     "TableManager.tables entries": 0,
     "FluentEvaluator._holds_in_progress entries": 0
    },
    "state_after_decide": {
     "epoch": 2,
     "FluentEvaluator._holds_cache entries": 1,
     "QueryCache._holds_cache entries": 0,
     "QueryCache._derived_cache entries": 1,
     "pipeline PerformanceManager.result_cache entries": 3,
     "TableManager.tables entries": 0,
     "FluentEvaluator._holds_in_progress entries": 0
    }
   },
   "rewrite": {
    "state_after_load": {
     "KnowledgeBase attributes": [
      "_derived",
      "options",
      "program"
     ],
     "program.revision": 16,
     "_derived.revision": -1,
     "_derived holds": [
      "diagnostics"
     ],
     "live QueryContext objects after gc": 0
    },
    "state_after_query": {
     "KnowledgeBase attributes": [
      "_derived",
      "options",
      "program"
     ],
     "program.revision": 16,
     "_derived.revision": 16,
     "_derived holds": [
      "analysis",
      "timeline",
      "diagnostics"
     ],
     "live QueryContext objects after gc": 0
    },
    "state_after_repeat": {
     "KnowledgeBase attributes": [
      "_derived",
      "options",
      "program"
     ],
     "program.revision": 16,
     "_derived.revision": 16,
     "_derived holds": [
      "analysis",
      "timeline",
      "diagnostics"
     ],
     "live QueryContext objects after gc": 0
    },
    "state_after_decide": {
     "KnowledgeBase attributes": [
      "_derived",
      "options",
      "program"
     ],
     "program.revision": 16,
     "_derived.revision": 16,
     "_derived holds": [
      "analysis",
      "timeline",
      "diagnostics"
     ],
     "live QueryContext objects after gc": 0
    }
   },
   "predecessor_process_global_objects": {
    "routing registry shared by two engines": true,
    "structured logger shared by two engines": true
   }
  },
  "result shape returned to the host": {
   "predecessor": {
    "query_return_type": "list",
    "row_type": "dict",
    "binding_value_reprs": [
     "Term('locked', (Term('lab', ()),))"
    ],
    "Decision fields": [
     "action",
     "allowed",
     "applicable",
     "condition",
     "message",
     "failed_order"
    ],
    "Decision.as_json keys": [
     "action",
     "kind",
     "enabled",
     "condition",
     "message"
    ]
   },
   "rewrite": {
    "ask_return_type": "Solutions",
    "Solutions fields": [
     "goal",
     "answers",
     "status",
     "notes",
     "steps",
     "depth"
    ],
    "binding_value_reprs": [
     "Struct('locked', (Atom('lab'),))"
    ],
    "query() (plain Python) return": "[{'F': locked('lab')}]",
    "Decision fields": [
     "action",
     "allowed",
     "applicable",
     "condition",
     "message",
     "order",
     "status",
     "notes"
    ],
    "Decision.as_json keys": [
     "action",
     "kind",
     "enabled",
     "condition",
     "message",
     "order",
     "status",
     "notes"
    ]
   }
  },
  "who owns the returned result (same query twice)": {
   "predecessor": {
    "second identical query returns the same list object": true,
    "returned list is the pipeline cache's stored value": true
   },
   "rewrite": {
    "second identical query returns the same object": false,
    "answers container type": "tuple",
    "Solutions is a frozen dataclass": true
   }
  }
 },
 "case_B_cannot_finish": {
  "api": {
   "predecessor": {
    "KBEngine.query": "(self, query_str: str) -> List[Dict[str, Any]]",
    "kb.integration.decide": "(engine, action: str, site: Optional[str] = None) -> kb.integration.affordance.Decision",
    "kb.integration.affordances": "(engine, site: str) -> List[kb.integration.affordance.Affordance]",
    "module constants (src/kb/engine/constants.py)": {
     "MAX_TRANSITIVE_DEPTH": 50,
     "MAX_BACKTRACKING_DEPTH": 100,
     "MAX_BACKTRACKING_ITERATIONS": 10000
    }
   },
   "rewrite": {
    "KnowledgeBase.ask": "(self, goal: 'str | Term', *, limits: 'Limits | None' = None, distinct: 'bool' = True, cancel: 'Callable[[], bool] | None' = None) -> 'Solutions'",
    "KnowledgeBase.explain": "(self, fluent: 'str | Term', when: 'str | Term', *, limits: 'Limits | None' = None, cancel: 'Callable[[], bool] | None' = None) -> 'Explanation'",
    "cyclops.api.decide": "(kb, action: 'str | Term', site: 'str | Term | None' = None, *, limits: 'Limits | None' = None, cancel: 'Callable[[], bool] | None' = None) -> 'Decision'",
    "cyclops.api.affordances": "(kb, site: 'str | Term', *, limits: 'Limits | None' = None, cancel: 'Callable[[], bool] | None' = None) -> 'list[Affordance]'",
    "Limits() defaults": "Limits(steps=2000000, depth=1200, max_answers=0)"
   }
  },
  "B1 route of 10 segments, now=t0 (control)": {
   "reachable(corridor, lab)": {
    "predecessor": [
     {}
    ],
    "rewrite": [
     {}
    ],
    "rewrite_status": "complete"
   },
   "reachable(corridor, X) answer count": {
    "predecessor": 10,
    "rewrite": 10
   },
   "decide": {
    "predecessor": {
     "action": "enter_lab",
     "kind": "allow",
     "enabled": true,
     "condition": null,
     "message": null
    },
    "rewrite": {
     "action": "enter_lab",
     "kind": "allow",
     "enabled": true,
     "condition": null,
     "message": null,
     "order": null,
     "status": "complete",
     "notes": []
    }
   }
  },
  "B1 route of 60 segments, now=t0, default settings": {
   "reachable(corridor, lab)": {
    "predecessor": [],
    "predecessor_stdout": [
     "WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)",
     "WARNING: Recursion depth limit (50) reached for query: reachable(tunnel50,lab)"
    ],
    "predecessor_log_warnings": [],
    "rewrite": [
     {}
    ],
    "rewrite_status": "complete",
    "rewrite_steps": 809,
    "rewrite_depth": 122
   },
   "reachable(corridor, X) answer count (60 exist)": {
    "predecessor": 50,
    "rewrite": 60,
    "rewrite_status": "complete"
   },
   "decide": {
    "predecessor": {
     "action": "enter_lab",
     "kind": "deny",
     "enabled": false,
     "condition": "reachable(corridor,lab)",
     "message": "You cannot find a way to the laboratory."
    },
    "predecessor_stdout": [
     "WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)",
     "WARNING: Recursion depth limit (50) reached for query: reachable(tunnel50,lab)"
    ],
    "rewrite": {
     "action": "enter_lab",
     "kind": "allow",
     "enabled": true,
     "condition": null,
     "message": null,
     "order": null,
     "status": "complete",
     "notes": []
    }
   }
  },
  "B1 sweep: reachable(corridor, lab) by tunnel length": {
   "predecessor": {
    "44": "true",
    "45": "true",
    "46": "true",
    "47": "true",
    "48": "true",
    "49": "true",
    "50": "true",
    "51": "false",
    "52": "false",
    "53": "false",
    "54": "false",
    "55": "false",
    "56": "false"
   },
   "rewrite": {
    "44": "true (complete)",
    "45": "true (complete)",
    "46": "true (complete)",
    "47": "true (complete)",
    "48": "true (complete)",
    "49": "true (complete)",
    "50": "true (complete)",
    "51": "true (complete)",
    "52": "true (complete)",
    "53": "true (complete)",
    "54": "true (complete)",
    "55": "true (complete)",
    "56": "true (complete)"
   }
  },
  "B2 host-chosen budget and cancellation (rewrite only; the predecessor API has no parameter for either)": {
   "inputs": [
    "lab_story.pl",
    "now_t0.pl",
    "route_60.pl"
   ],
   "warm-up ask('true') steps (pays analysis + timeline preparation)": 82,
   "steps per decide sub-query at default limits (warm)": {
    "once(affords(enter_lab, corridor))": {
     "steps": 3,
     "status": "complete"
    },
    "guard(enter_lab, Order, Condition, Message)": {
     "steps": 3,
     "status": "complete"
    },
    "once(can_enter(lab))": {
     "steps": 8,
     "status": "complete"
    },
    "once(reachable(corridor, lab))": {
     "steps": 729,
     "status": "complete"
    }
   },
   "budget used": "Limits(steps=200)",
   "ask reachable(corridor, lab) with budget": {
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0014,
    "answers": [],
    "status": "exhausted",
    "notes": [],
    "steps": 201,
    "depth": 81
   },
   "query() with budget": {
    "value": null,
    "error": "IncompleteResult: query did not run to completion (status=exhausted, 0 answer(s) found so far)",
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0013
   },
   "decide with budget": {
    "value": {
     "action": "enter_lab",
     "kind": "indeterminate",
     "enabled": false,
     "condition": "reachable(corridor, lab)",
     "message": "Action availability could not be determined.",
     "order": 2,
     "status": "exhausted",
     "notes": [
      "IncompleteResult: query did not run to completion (status=exhausted, 0 answer(s) found so far)"
     ]
    },
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0014
   },
   "ask with cancel=lambda: True": {
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0,
    "answers": [],
    "status": "cancelled",
    "notes": [
     "query cancelled"
    ],
    "steps": 0,
    "depth": 0
   },
   "decide with cancel=lambda: True": {
    "value": {
     "action": "enter_lab",
     "kind": "indeterminate",
     "enabled": false,
     "condition": null,
     "message": "Action availability could not be determined.",
     "order": null,
     "status": "cancelled",
     "notes": [
      "IncompleteResult: query did not run to completion (status=cancelled, 0 answer(s) found so far): query cancelled"
     ]
    },
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0
   },
   "decide again with default limits (same KB)": {
    "value": {
     "action": "enter_lab",
     "kind": "allow",
     "enabled": true,
     "condition": null,
     "message": null,
     "order": null,
     "status": "complete",
     "notes": []
    },
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0046
   },
   "COLD ask('true') with Limits(steps=5): preparation interrupted": {
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0001,
    "answers": [],
    "status": "exhausted",
    "notes": [
     "timeline preparation stopped (ResourceExhausted: resource exhausted: step budget of 5 reached)",
     "resource exhausted: step budget of 5 reached"
    ],
    "steps": 6,
    "depth": 0
   },
   "  derived state published after interrupted preparation?": false,
   "  retry with defaults": {
    "error": null,
    "stdout": [],
    "stdout_lines": 0,
    "log_warnings": [],
    "log_warning_count": 0,
    "seconds": 0.0002,
    "answers": null,
    "status": "complete",
    "notes": [],
    "steps": 82,
    "depth": 1,
    "n_answers": 1
   }
  },
  "B3 self-referential effect rule (cycle.pl), now=t2": {
   "holds_at(door_open(lab), T) by T, three ask orders": {
    "predecessor": {
     "ask order t3,t2,t1": {
      "t3": [],
      "t2": [],
      "t1": [
       {}
      ]
     },
     "ask order t2,t3,t1": {
      "t2": [],
      "t3": [],
      "t1": [
       {}
      ]
     },
     "ask order t1,t2,t3": {
      "t1": [
       {}
      ],
      "t2": [],
      "t3": []
     }
    },
    "rewrite": {
     "ask order t3,t2,t1": {
      "t3": "[] (exhausted)",
      "t2": "[] (exhausted)",
      "t1": "[] (exhausted)"
     },
     "ask order t2,t3,t1": {
      "t2": "[] (exhausted)",
      "t3": "[] (exhausted)",
      "t1": "[] (exhausted)"
     },
     "ask order t1,t2,t3": {
      "t1": "[] (exhausted)",
      "t2": "[] (exhausted)",
      "t3": "[] (exhausted)"
     }
    }
   },
   "rewrite_note": [
    "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped without assuming true or false."
   ],
   "decide": {
    "predecessor": {
     "action": "enter_lab",
     "kind": "deny",
     "enabled": false,
     "condition": "can_enter(lab)",
     "message": "The laboratory door is locked."
    },
    "rewrite": {
     "action": "enter_lab",
     "kind": "indeterminate",
     "enabled": false,
     "condition": "can_enter(lab)",
     "message": "Action availability could not be determined.",
     "order": 1,
     "status": "exhausted",
     "notes": [
      "IncompleteResult: query did not run to completion (status=exhausted, 0 answer(s) found so far): holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped without assuming true or false."
     ]
    }
   },
   "rewrite explain(door_open(lab), t2)": {
    "fluent": "door_open(lab)",
    "time": "t2",
    "holds": null,
    "reason": "unexplained",
    "status": "exhausted",
    "notes": [
     "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped without assuming true or false."
    ],
    "start": null,
    "clipped_by": null,
    "considered": []
   },
   "rewrite non-temporal goal on same KB": {
    "answers": [
     {
      "A": "enter_lab"
     }
    ],
    "status": "complete"
   },
   "entries into the temporal evaluator, holds_at(door_open(lab), t1)": {
    "predecessor (FluentEvaluator.holds_at)": [
     {
      "fluent": "door_open(lab)",
      "time": "t1",
      "cutoff": "t1",
      "already_in_progress (guard returns False)": false,
      "cache_entry_present": false
     }
    ],
    "predecessor rule-effect lookup entered": false,
    "rewrite (calculus._explain)": [
     {
      "fluent": "door_open(lab)",
      "time": "t1",
      "already_in_progress (raises TemporalCycle)": false,
      "memo_entry_present": false
     },
     {
      "fluent": "door_open(lab)",
      "time": "t3",
      "already_in_progress (raises TemporalCycle)": false,
      "memo_entry_present": false
     },
     {
      "fluent": "door_open(lab)",
      "time": "t3",
      "already_in_progress (raises TemporalCycle)": true,
      "memo_entry_present": false
     }
    ]
   },
   "entries into the temporal evaluator, holds_at(door_open(lab), t3)": {
    "predecessor (FluentEvaluator.holds_at)": [
     {
      "fluent": "door_open(lab)",
      "time": "t3",
      "cutoff": "t3",
      "already_in_progress (guard returns False)": false,
      "cache_entry_present": false
     }
    ],
    "predecessor rule-effect lookup entered": false,
    "rewrite (calculus._explain)": [
     {
      "fluent": "door_open(lab)",
      "time": "t3",
      "already_in_progress (raises TemporalCycle)": false,
      "memo_entry_present": false
     },
     {
      "fluent": "door_open(lab)",
      "time": "t3",
      "already_in_progress (raises TemporalCycle)": true,
      "memo_entry_present": false
     }
    ]
   }
  }
 },
 "case_C_explanation": {
  "now=t2 (deny)": {
   "predecessor": {
    "why(can_enter(lab))": [
     {
      "term": "can_enter(lab)",
      "type": "derived",
      "explanation": "[{'reason': 'no_derivation_found','detail': 'No derivation path found for can_enter(lab)'}]",
      "source_spans": "[]"
     }
    ],
    "why(holds_at(door_open(lab), t2))": [
     {
      "term": "holds_at(door_open(lab),t2)",
      "type": "temporal",
      "explanation": "[{'reason': 'does_not_hold','detail': 'door_open(lab) does not hold at t2'}]",
      "source_spans": "[]"
     }
    ],
    "why(holds_at(locked(lab), t2))": [
     {
      "term": "holds_at(locked(lab),t2)",
      "type": "temporal",
      "explanation": "[{'reason': 'initiated_by','detail': 'lock_door(lab) initiates locked(lab) at t1','initiating_event': 'lock_door(lab)','initiation_time': 't1','supporting_facts': ['happens(lock_door(lab),t1)','initiates(lock_door(lab),locked(lab))']}]",
      "source_spans": "[]"
     }
    ],
    "derivation(can_enter(lab))": [],
    "KBEngine.explain_holds_at(door_open(lab), t2)": "Error parsing fluent: _parse_term usage detected in production code: TermParser.parse_term. Use TermCodec.convert_surface_to_core() instead. If this is a test, call ProductionGuard.allow_parse_term_for_tests() first."
   },
   "rewrite": {
    "explain(door_open(lab), t2)": {
     "fluent": "door_open(lab)",
     "time": "t2",
     "holds": false,
     "reason": "clipped",
     "status": "complete",
     "notes": [],
     "start": null,
     "clipped_by": {
      "event": "lock_door(lab)",
      "time": "t1",
      "implicit": false
     },
     "considered": [
      {
       "start": {
        "kind": "initially",
        "event": null,
        "time": null
       },
       "clipped_by": {
        "event": "lock_door(lab)",
        "time": "t1",
        "implicit": false
       }
      }
     ]
    },
    "explain(locked(lab), t2)": {
     "fluent": "locked(lab)",
     "time": "t2",
     "holds": true,
     "reason": "initiated",
     "status": "complete",
     "notes": [],
     "start": {
      "kind": "initiated",
      "event": "lock_door(lab)",
      "time": "t1"
     },
     "clipped_by": null,
     "considered": [
      {
       "start": {
        "kind": "initiated",
        "event": "lock_door(lab)",
        "time": "t1"
       },
       "clipped_by": null
      }
     ]
    },
    "ask why(can_enter(lab)) (no such predicate; default unknown_fails=True)": [],
    "explain(can_enter(lab), t2) (a rule head, not a fluent)": {
     "fluent": "can_enter(lab)",
     "time": "t2",
     "holds": false,
     "reason": "never_initiated",
     "status": "complete",
     "notes": [],
     "start": null,
     "clipped_by": null,
     "considered": []
    },
    "decide": {
     "action": "enter_lab",
     "kind": "deny",
     "enabled": false,
     "condition": "can_enter(lab)",
     "message": "The laboratory door is locked.",
     "order": 1,
     "status": "complete",
     "notes": []
    },
    "ask why(can_enter(lab)) with unknown_fails=False": "error: UnknownPredicateError: unknown predicate: why/1"
   }
  },
  "now=t0 (allow)": {
   "predecessor": {
    "why(can_enter(lab))": [
     {
      "term": "can_enter(lab)",
      "type": "derived",
      "explanation": "[{'reason': 'rule_derivation','rule_id': ('can_enter',1,0),'rule_text': 'can_enter(R) :- now(T),holds_at(door_open(R),T).','bindings': {'R$1': 'lab','T$2': 't0'},'supporting_facts': []}]",
      "source_spans": "[]"
     }
    ],
    "derivation(can_enter(lab))": [
     {
      "derived_term": "can_enter(lab)",
      "rule_id": "('can_enter',1,0)",
      "rule_text": "can_enter(R) :- now(T),holds_at(door_open(R),T).",
      "bindings": "{'R$1': 'lab','T$2': 't0'}",
      "supporting_facts": "[]"
     }
    ],
    "why(holds_at(door_open(lab), t0))": [
     {
      "term": "holds_at(door_open(lab),t0)",
      "type": "temporal",
      "explanation": "[{'reason': 'initially','detail': 'door_open(lab) holds initially','fluent': 'door_open(lab)'}]",
      "source_spans": "[]"
     }
    ]
   },
   "rewrite": {
    "explain(door_open(lab), t0)": {
     "fluent": "door_open(lab)",
     "time": "t0",
     "holds": true,
     "reason": "initially",
     "status": "complete",
     "notes": [],
     "start": {
      "kind": "initially",
      "event": null,
      "time": null
     },
     "clipped_by": null,
     "considered": [
      {
       "start": {
        "kind": "initially",
        "event": null,
        "time": null
       },
       "clipped_by": null
      }
     ]
    },
    "ask why(can_enter(lab))": []
   }
  },
  "boundary t1 (lock happens at t1, takes effect after t1)": {
   "predecessor": {
    "holds_at(door_open(lab), t1)": [
     {}
    ],
    "why(holds_at(door_open(lab), t1))": [
     {
      "term": "holds_at(door_open(lab),t1)",
      "type": "temporal",
      "explanation": "[{'reason': 'initially','detail': 'door_open(lab) holds initially','fluent': 'door_open(lab)'}]",
      "source_spans": "[]"
     }
    ],
    "holds_at(locked(lab), t1)": [],
    "why(holds_at(locked(lab), t1))": [
     {
      "term": "holds_at(locked(lab),t1)",
      "type": "temporal",
      "explanation": "[{'reason': 'does_not_hold','detail': 'locked(lab) does not hold at t1'}]",
      "source_spans": "[]"
     }
    ]
   },
   "rewrite": {
    "holds_at(door_open(lab), t1)": [
     {}
    ],
    "explain(door_open(lab), t1)": {
     "fluent": "door_open(lab)",
     "time": "t1",
     "holds": true,
     "reason": "initially",
     "status": "complete",
     "notes": [],
     "start": {
      "kind": "initially",
      "event": null,
      "time": null
     },
     "clipped_by": null,
     "considered": [
      {
       "start": {
        "kind": "initially",
        "event": null,
        "time": null
       },
       "clipped_by": null
      }
     ]
    },
    "holds_at(locked(lab), t1)": [],
    "explain(locked(lab), t1)": {
     "fluent": "locked(lab)",
     "time": "t1",
     "holds": false,
     "reason": "never_initiated",
     "status": "complete",
     "notes": [],
     "start": null,
     "clipped_by": null,
     "considered": []
    }
   }
  },
  "which code produces the answer vs the explanation (landmarks entered)": {
   "predecessor answer holds_at(door_open(lab), t2)": [
    "KBEngine.query (kb/engine/kb_engine.py:748) x1",
    "HandlerRegistry.get_handler_or_default (kb/engine/query/registry.py:108) x1",
    "HoldsAtHandler.handle (kb/engine/query/query_handlers.py:172) x1",
    "Reasoner.holds_at (kb/engine/reasoning/reasoner.py:705) x1",
    "FluentEvaluator.holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:96) x1",
    "FluentEvaluator._compute_holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:164) x1",
    "TemporalReasoner.lt_time (kb/engine/reasoning/temporal/temporal_reasoner.py:58) x7"
   ],
   "predecessor why(holds_at(door_open(lab), t2))": [
    "KBEngine.query (kb/engine/kb_engine.py:748) x1",
    "HandlerRegistry.get_handler_or_default (kb/engine/query/registry.py:108) x1",
    "WhyHandler.handle (kb/engine/query/provenance_handlers.py:89) x1",
    "WhyHandler._explain_temporal (kb/engine/query/provenance_handlers.py:169) x1",
    "Reasoner.holds_at (kb/engine/reasoning/reasoner.py:705) x1",
    "FluentEvaluator.holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:96) x1",
    "FluentEvaluator._compute_holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:164) x1",
    "FactStore.spans_for_term (kb/engine/storage.py:551) x1",
    "TemporalReasoner.lt_time (kb/engine/reasoning/temporal/temporal_reasoner.py:58) x7"
   ],
   "predecessor answer can_enter(lab), now=t0": [
    "KBEngine.query (kb/engine/kb_engine.py:748) x1",
    "HandlerRegistry.get_handler_or_default (kb/engine/query/registry.py:108) x1",
    "LegacyDerivedEvaluator.evaluate_with_special_cases (kb/engine/derived_evaluation/legacy_evaluator.py:416) x1",
    "Reasoner.evaluate_derived (kb/engine/reasoning/reasoner.py:768) x1",
    "Reasoner.holds_at (kb/engine/reasoning/reasoner.py:705) x1",
    "FluentEvaluator.holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:96) x1",
    "FluentEvaluator._compute_holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:164) x1",
    "TemporalReasoner.lt_time (kb/engine/reasoning/temporal/temporal_reasoner.py:58) x5"
   ],
   "predecessor answer can_enter(lab) route": [
    "LEGACY",
    "default"
   ],
   "predecessor why(can_enter(lab)), now=t0": [
    "KBEngine.query (kb/engine/kb_engine.py:748) x1",
    "HandlerRegistry.get_handler_or_default (kb/engine/query/registry.py:108) x1",
    "WhyHandler.handle (kb/engine/query/provenance_handlers.py:89) x1",
    "WhyHandler._explain_derived (kb/engine/query/provenance_handlers.py:226) x1",
    "LegacyDerivedEvaluator.evaluate_with_special_cases (kb/engine/derived_evaluation/legacy_evaluator.py:416) x1",
    "Reasoner.holds_at (kb/engine/reasoning/reasoner.py:705) x1",
    "FluentEvaluator.holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:96) x1",
    "FluentEvaluator._compute_holds_at (kb/engine/reasoning/temporal/fluent_evaluator.py:164) x1",
    "FactStore.spans_for_term (kb/engine/storage.py:551) x1",
    "TemporalReasoner.lt_time (kb/engine/reasoning/temporal/temporal_reasoner.py:58) x5"
   ],
   "rewrite answer holds_at(door_open(lab), t2)": [
    "KnowledgeBase.ask (cyclops/engine.py:339) x1",
    "solve (cyclops/solve/machine.py:42) x16",
    "holds_at (cyclops/temporal/calculus.py:671) x1",
    "_holds (cyclops/temporal/calculus.py:487) x1",
    "_explain (cyclops/temporal/calculus.py:548) x1",
    "_starts_and_stops (cyclops/temporal/calculus.py:404) x1",
    "_clipped (cyclops/temporal/calculus.py:469) x1",
    "Timeline.precedes (cyclops/temporal/timeline.py:92) x1"
   ],
   "rewrite explain(door_open(lab), t2)": [
    "KnowledgeBase.explain (cyclops/engine.py:505) x1",
    "solve (cyclops/solve/machine.py:42) x15",
    "explain (cyclops/temporal/calculus.py:732) x1",
    "_explain (cyclops/temporal/calculus.py:548) x1",
    "_starts_and_stops (cyclops/temporal/calculus.py:404) x1",
    "_clipped (cyclops/temporal/calculus.py:469) x1",
    "Timeline.precedes (cyclops/temporal/timeline.py:92) x1"
   ]
  }
 },
 "input_design_check_nonground_effect_facts": {
  "why": "Explains why the shared story writes its two effect axioms as ground facts.",
  "diff_from_lab_story.pl": [
   "terminates(lock_door(R), door_open(R)).",
   "initiates(lock_door(R), locked(R))."
  ],
  "answers": {
   "holds_at(door_open(lab), t2)": {
    "predecessor": [
     {}
    ],
    "rewrite": [],
    "rewrite_status": "complete"
   },
   "holds_at(locked(lab), T)": {
    "predecessor": [],
    "rewrite": [
     {
      "T": "t2"
     },
     {
      "T": "t3"
     }
    ],
    "rewrite_status": "complete"
   },
   "can_enter(lab)": {
    "predecessor": [
     {}
    ],
    "rewrite": [],
    "rewrite_status": "complete"
   }
  }
 },
 "seed_stability": {
  "old": {
   "seed0 vs seed1: semantic differences": [
    "/environment/PYTHONHASHSEED"
   ],
   "seed0 vs seed1: trace size differences": [],
   "seed0 vs seed2: semantic differences": [
    "/environment/PYTHONHASHSEED"
   ],
   "seed0 vs seed2: trace size differences": []
  },
  "new": {
   "seed0 vs seed1: semantic differences": [
    "/environment/PYTHONHASHSEED"
   ],
   "seed0 vs seed1: trace size differences": [],
   "seed0 vs seed2: semantic differences": [
    "/environment/PYTHONHASHSEED"
   ],
   "seed0 vs seed2: trace size differences": []
  }
 },
 "notes_on_counts": "function_entries / distinct_functions count Python function starts inside each package. The two code bases factor work into functions differently, so these counts are not comparable across engines and say nothing about quality.",
 "revision_stage_2026_09_29": {
  "about": "Probes run during revision of the Architecture page to answer reviewer counterexamples and reader questions. Same isolation as the matched set: fresh `git archive <commit> src` exports of e62758c and 5d598de, PYTHONDONTWRITEBYTECODE=1, python -B, nothing installed; engine repositories left clean. Each script takes the export's src directory as its argument and reads lab_story.pl, route_10.pl and route_60.pl (the inputs listed above) from its own folder.",
  "python": "CPython 3.12.10, Windows 11",
  "scripts": {
   "probe_rev_new.py": "\"\"\"Revision-stage probes of the rewrite (AkiyaBlocks-CyclopsStorm @ 5d598de), run on a git-archive export.\nUsage: PYTHONDONTWRITEBYTECODE=1 python -B probe_rev_new.py <export>/src\n\"\"\"\nimport gc, pathlib, subprocess, sys\nsys.dont_write_bytecode = True\nSRC = sys.argv[1]\nsys.path.insert(0, SRC)\nHERE = pathlib.Path(__file__).resolve().parent\nSTORY = (HERE / \"lab_story.pl\").read_text(encoding=\"utf-8\")\n\nfrom cyclops import KnowledgeBase\nfrom cyclops.solve.context import Limits, QueryContext\nfrom cyclops.api import decide, answers_to_json\nimport cyclops.api.affordance as aff\n\n\ndef kb_with(*texts, **opts):\n    from cyclops.engine import Options\n    kb = KnowledgeBase(options=Options(**opts)) if opts else KnowledgeBase()\n    for t in texts:\n        kb.load(t)\n    return kb\n\n\ndef show(label, value):\n    print(f\"{label}: {value}\")\n\n\nprint(\"== 1 lazily built clause index (Program-owned, revision-keyed)\")\nkb = kb_with(STORY, \"now(t2).\")\npreds = {k: p for k, p in kb.program.predicates.items() if k in ((\"can_enter\", 1), (\"happens\", 2), (\"initially\", 1))}\nshow(\"before ask: _index_revision\", {k[0]: p._index_revision for k, p in preds.items()})\nkb.ask(\"can_enter(lab)\")\nshow(\"after ask: _index_revision\", {k[0]: p._index_revision for k, p in preds.items()})\nshow(\"program.revision\", kb.program.revision)\ngc.collect()\nshow(\"live QueryContext objects\", sum(isinstance(o, QueryContext) for o in gc.get_objects()))\n\nprint(\"== 2 recursion limit restored after the call\")\nbefore = sys.getrecursionlimit()\nkb.ask(\"can_enter(lab)\")\nshow(\"recursion limit before/after\", (before, sys.getrecursionlimit()))\n\nprint(\"== 3 names of unbound variables differ between identical requests\")\nk3 = kb_with(\"mk(f(_)).\")\nshow(\"query 1\", k3.query(\"mk(X)\"))\nshow(\"query 2\", k3.query(\"mk(X)\"))\nshow(\"json 1\", answers_to_json(k3.ask(\"mk(X)\")))\nshow(\"json 2\", answers_to_json(k3.ask(\"mk(X)\")))\n\nprint(\"== 4 step counts: cold, warm, once, preparation only\")\nfor label, goals in [(\"cold can_enter then repeat\", [\"can_enter(lab)\", \"can_enter(lab)\"]),\n                     (\"cold once(can_enter)\", [\"once(can_enter(lab))\"]),\n                     (\"cold true\", [\"true\"])]:\n    k = kb_with(STORY, \"now(t2).\")\n    out = []\n    for g in goals:\n        s = k.ask(g)\n        out.append((g, s.steps, s.depth, s.status.value))\n    show(label, out)\nk = kb_with(STORY, \"now(t2).\")\ns = k.ask(\"can_enter(lab)\", limits=Limits(steps=1))\nshow(\"cold, Limits(steps=1)\", (s.status.value, s.steps, s.notes, \"derived revision\", k._derived.revision))\ns2 = k.ask(\"can_enter(lab)\")\nshow(\"retry with defaults\", (s2.status.value, s2.steps, \"derived revision\", k._derived.revision))\n\nprint(\"== 5 analysis is not metered: analyse() signature\")\nimport inspect\nfrom cyclops.solve.analysis import analyse\nshow(\"analyse signature\", inspect.signature(analyse))\n\nprint(\"== 6 which api modules load with the package (fresh process)\")\ncode = (\"import sys; sys.path.insert(0, %r); import cyclops; \"\n        \"print({m: (m in sys.modules) for m in ['cyclops.api.values','cyclops.api.json_api','cyclops.api.affordance']})\" % SRC)\nprint(subprocess.run([sys.executable, \"-B\", \"-c\", code], capture_output=True, text=True).stdout.strip())\n\nprint(\"== 7 diagnostics live on kb.diagnostics, not on each result\")\nk7 = kb_with(\"time(t0). time(t1). before(t0, t1). initially(g0). initiates(e, g, t9).\")\ns = k7.ask(\"holds_at(g, t1)\")\nshow(\"ask notes/status\", (s.notes, s.status.value))\nshow(\"kb.diagnostics\", [str(d)[:140] for d in k7.diagnostics])\n\nprint(\"== 8 timeline: before/2 answers vs transitive order; partial read kept\")\nk8 = kb_with(\"time(t0). time(t1). time(t2). before(t0, t1). before(t1, t2).\")\nshow(\"ask before(t0, t2)\", (len(k8.ask(\"before(t0, t2)\").answers), k8.ask(\"before(t0, t2)\").status.value))\nshow(\"timeline.precedes(t0, t2)\", k8.timeline.precedes(__import__(\"cyclops.terms\", fromlist=[\"atom\"]).atom(\"t0\"),\n                                                       __import__(\"cyclops.terms\", fromlist=[\"atom\"]).atom(\"t2\")))\nk8b = kb_with(STORY, \"now(t2).\", \"before(t3, X) :- X is foo + 1.\")\ns = k8b.ask(\"true\")\nshow(\"bad before/2 clause: ask(true)\", (s.status.value, \"derived revision\", k8b._derived.revision, \"program\", k8b.program.revision))\ns = k8b.ask(\"holds_at(door_open(lab), t1)\")\nshow(\"  holds_at(door_open(lab), t1)\", (s.status.value, len(s.answers), s.notes[:1]))\n\nprint(\"== 9 explain on a rule head\")\nk9 = kb_with(STORY, \"now(t0).\")\nshow(\"ask can_enter(lab)\", (len(k9.ask(\"can_enter(lab)\").answers), k9.ask(\"can_enter(lab)\").status.value))\ne = k9.explain(\"can_enter(lab)\", \"t0\")\nshow(\"explain(can_enter(lab), t0)\", (e.holds, e.reason, e.status.value))\n\nprint(\"== 10 self-referential effect: /2 and /3 spellings\")\nSELF2 = \"happens(auto_lock(lab), t1).\\nterminates(auto_lock(lab), door_open(lab)) :- holds_at(door_open(lab), t3).\"\nSELF3 = \"terminates(auto_lock(lab), door_open(lab), t1) :- holds_at(door_open(lab), t3).\"\nfor label, extra in ((\"/2\", SELF2), (\"/3\", SELF3)):\n    for order in ([\"t1\", \"t2\", \"t3\"], [\"t3\", \"t2\", \"t1\"]):\n        k10 = kb_with(STORY, \"now(t2).\", extra)\n        res = {}\n        for t in order:\n            s = k10.ask(f\"holds_at(door_open(lab), {t})\")\n            res[t] = (s.status.value, len(s.answers))\n        show(f\"{label} order {order}\", res)\n    k10 = kb_with(STORY, \"now(t2).\", extra)\n    d = decide(k10, \"enter_lab\", \"corridor\")\n    show(f\"{label} decide\", (d.kind, d.status.value if d.status else None))\n\nprint(\"== 11 decide's sub-queries (tunnel of 10)\")\nROUTE10 = (HERE / \"route_10.pl\").read_text(encoding=\"utf-8\")\nk11 = kb_with(STORY, \"now(t0).\", ROUTE10)\nsent = []\norig = KnowledgeBase.ask\ndef spy(self, goal, **kw):\n    sent.append(str(goal))\n    return orig(self, goal, **kw)\nKnowledgeBase.ask = spy\nd = decide(k11, \"enter_lab\", \"corridor\")\nKnowledgeBase.ask = orig\nshow(\"decide kind\", d.kind)\nshow(\"sub-queries sent\", sent)\n\nprint(\"== 12 ask() on exhaustion returns the answers found so far\")\nROUTE60 = (HERE / \"route_60.pl\").read_text(encoding=\"utf-8\")\nk12 = kb_with(STORY, \"now(t0).\", ROUTE60)\nk12.ask(\"true\")\ns = k12.ask(\"reachable(corridor, X)\", limits=Limits(steps=300))\nshow(\"ask reachable(corridor, X) steps=300\", (s.status.value, len(s.answers), s.steps))\n\nprint(\"== 13 SemanticConflict: ask, query, decide\")\nk13 = kb_with(STORY, \"now(t0).\", \"p :- \\\\+ q. q :- p.\", \"r(1).\")\nfor fn, label in ((lambda: k13.ask(\"r(X)\"), \"ask r(X)\"), (lambda: k13.query(\"r(X)\"), \"query r(X)\")):\n    try:\n        v = fn()\n        show(label, v)\n    except Exception as exc:\n        show(label, f\"raises {type(exc).__name__}: {str(exc)[:120]}\")\nd = decide(k13, \"enter_lab\", \"corridor\")\nshow(\"decide\", (d.kind, d.allowed, d.message, d.status, [n[:80] for n in d.notes]))\n\nprint(\"== 14 name capture: a clause for a built-in\")\ntry:\n    KnowledgeBase().load(\"length(rope, 5).\")\n    show(\"load length(rope, 5).\", \"ok\")\nexcept Exception as exc:\n    show(\"load length(rope, 5).\", f\"raises {type(exc).__name__}: {exc}\")\n\nprint(\"== 15 tunnel lengths 50 to 60 (rewrite)\")\ndef tunnel(n):\n    lines = [\"guard(enter_lab, 2, reachable(corridor, lab), 'You cannot find a way to the laboratory.').\",\n             \"reachable(A, B) :- passage(A, B).\", \"reachable(A, B) :- passage(A, C), reachable(C, B).\"]\n    names = [\"corridor\"] + [f\"tunnel{i}\" for i in range(1, n)] + [\"lab\"]\n    lines += [f\"passage({a}, {b}).\" for a, b in zip(names, names[1:])]\n    return \"\\n\".join(lines)\nres = {}\nfor n in range(50, 61):\n    k15 = kb_with(STORY, \"now(t0).\", tunnel(n))\n    d = decide(k15, \"enter_lab\", \"corridor\")\n    res[n] = (d.kind, d.status.value if d.status else None)\nshow(\"decide by length\", res)\n",
   "probe_rev_old.py": "\"\"\"Revision-stage probes of the earlier engine (BadScienceFiction-CyclopsStorm @ e62758c), on a git-archive export.\nUsage: PYTHONDONTWRITEBYTECODE=1 python -B probe_rev_old.py <export>/src\n\"\"\"\nimport contextlib, io, logging, pathlib, sys\nsys.dont_write_bytecode = True\nSRC = sys.argv[1]\nsys.path.insert(0, SRC)\nHERE = pathlib.Path(__file__).resolve().parent\nSTORY = (HERE / \"lab_story.pl\").read_text(encoding=\"utf-8\")\n\nfrom kb.engine.kb_engine import KBEngine\nfrom kb.engine.query.query_handlers import BaseQueryHandler\nfrom kb.integration import decide\n\n\ndef quiet(fn):\n    buf = io.StringIO()\n    with contextlib.redirect_stdout(buf):\n        value = fn()\n    lines = [l for l in buf.getvalue().splitlines() if l.strip()]\n    return value, lines\n\n\ndef engine(*texts):\n    e = KBEngine()\n    for t in texts:\n        quiet(lambda: e.add(t))\n    return e\n\n\ndef show(label, value):\n    print(f\"{label}: {value}\")\n\n\nprint(\"== 1 tunnel lengths 50 to 60: decide and stdout lines\")\ndef tunnel(n):\n    lines = [\"guard(enter_lab, 2, reachable(corridor, lab), 'You cannot find a way to the laboratory.').\",\n             \"reachable(A, B) :- passage(A, B).\", \"reachable(A, B) :- passage(A, C), reachable(C, B).\"]\n    names = [\"corridor\"] + [f\"tunnel{i}\" for i in range(1, n)] + [\"lab\"]\n    lines += [f\"passage({a}, {b}).\" for a, b in zip(names, names[1:])]\n    return \"\\n\".join(lines)\nfor n in range(50, 61):\n    e = engine(STORY, \"now(t0).\", tunnel(n))\n    d, out = quiet(lambda: decide(e, \"enter_lab\", \"corridor\"))\n    show(f\"n={n}\", (d.as_json()[\"kind\"], d.as_json().get(\"message\"), len(out), out[:1]))\n\nprint(\"== 2 why can't I enter? (now(t2)) and why can I? (now(t0))\")\nfor now in (\"now(t2).\", \"now(t0).\"):\n    e = engine(STORY, now)\n    for g in [\"why(can_enter(lab))\", \"derivation(can_enter(lab))\", \"why(\\\\+ can_enter(lab))\",\n              \"why(holds_at(door_open(lab), t2))\", \"evidence_chain(holds_at(door_open(lab), t2))\"]:\n        r, _ = quiet(lambda: e.query(g))\n        show(f\"{now} {g}\", str(r)[:260])\n\nprint(\"== 3 a host-computed predicate registered by name\")\nclass WeightHandler(BaseQueryHandler):\n    def handle(self, query_term, internal_call=False):\n        item = str(query_term.args[0]); w = str(query_term.args[1])\n        table = {\"crate\": 12, \"rope\": 2}\n        if item in table:\n            return [{w: table[item]}] if w[:1].isupper() else ([{}] if str(table[item]) == w else [])\n        return []\ne = engine(\"item(crate). item(rope).\", \"heavy(X) :- item(X), weight(X, W), W > 10.\",\n           \"light(X) :- weight(X, W), W < 5.\",\n           \"affords(lift, crate). guard(lift, 1, light(rope), 'Too heavy.').\")\ne.handler_registry.register(\"weight\", WeightHandler(e))\nfor g in [\"weight(crate, W)\", \"weight(crate, 12)\", \"weight(crate, W), W > 10\", \"heavy(crate)\", \"light(rope)\"]:\n    r, _ = quiet(lambda: e.query(g))\n    show(g, r)\nd, _ = quiet(lambda: decide(e, \"lift\", \"crate\"))\nshow(\"decide lift (guard light(rope), a rule over weight/2)\", d.as_json())\n\nprint(\"== 4 authoring checks on load\")\nfor text in [\"can_enter(R) :- now(T), holds_at(door_open(R), X).\", \"p(a) :- q(b\"]:\n    e = KBEngine()\n    handler_records = []\n    class H(logging.Handler):\n        def emit(self, rec):\n            handler_records.append(f\"{rec.levelname} {rec.name}: {rec.getMessage()[:200]}\")\n    h = H(logging.WARNING); logging.getLogger().addHandler(h)\n    try:\n        _, out = quiet(lambda: e.add(text))\n        res = \"ok\"\n    except Exception as ex:\n        out = []\n        res = f\"raises {type(ex).__name__}: {str(ex)[:200]} | code={getattr(ex, 'code', None)} suggestions={getattr(ex, 'suggestions', None)}\"\n    finally:\n        logging.getLogger().removeHandler(h)\n    show(repr(text), (res, out[:3], handler_records[:3]))\n\nprint(\"== 5 effects with variables, written as facts and as rules\")\nBASE = \"\"\"time(t0). time(t1). time(t2). time(t3).\nbefore(t0, t1). before(t1, t2). before(t2, t3).\nroom(lab).\ninitially(door_open(lab)).\nhappens(lock_door(lab), t1).\n\"\"\"\nfor label, effects in [(\"facts\", \"terminates(lock_door(R), door_open(R)).\\ninitiates(lock_door(R), locked(R)).\\n\"),\n                       (\"rules\", \"terminates(lock_door(R), door_open(R)) :- room(R).\\ninitiates(lock_door(R), locked(R)) :- room(R).\\n\")]:\n    e = engine(BASE + effects)\n    for g in [\"holds_at(door_open(lab), t2)\", \"holds_at(locked(lab), T)\"]:\n        r, _ = quiet(lambda: e.query(g))\n        show(f\"{label}: {g}\", r)\n",
   "probe_change_cost.py": "\"\"\"Cost of the first query after a one-fact change, versus a repeated query, in each engine.\nUsage: PYTHONDONTWRITEBYTECODE=1 python -B probe_change_cost.py old|new <export>/src\nProgram: the lab story + now(t0) + N unrelated facts item(1..N). Change: add one unrelated fact seen(k).\nEach figure is the median of REPS runs of (change; ask can_enter(lab)) or of a repeated ask, in milliseconds.\n\"\"\"\nimport contextlib, io, logging, pathlib, statistics, sys, time\nsys.dont_write_bytecode = True\nWHICH, SRC = sys.argv[1], sys.argv[2]\nsys.path.insert(0, SRC)\nHERE = pathlib.Path(__file__).resolve().parent\nSTORY = (HERE / \"lab_story.pl\").read_text(encoding=\"utf-8\")\nREPS = 25\nlogging.disable(logging.CRITICAL)\n\n\ndef filler(n):\n    return \"\\n\".join(f\"item({i}).\" for i in range(1, n + 1))\n\n\nif WHICH == \"new\":\n    from cyclops import KnowledgeBase\n\n    def make(n):\n        kb = KnowledgeBase()\n        kb.load(STORY); kb.load(\"now(t0).\")\n        if n:\n            kb.load(filler(n))\n        return kb\n\n    def change(kb, k):\n        kb.assert_(f\"seen({k}).\")\n\n    def ask(kb):\n        s = kb.ask(\"can_enter(lab)\")\n        assert s.answers and s.status.value == \"complete\", s\nelse:\n    from kb.engine.kb_engine import KBEngine\n\n    def make(n):\n        e = KBEngine()\n        with contextlib.redirect_stdout(io.StringIO()):\n            e.add(STORY); e.add(\"now(t0).\")\n            if n:\n                e.add(filler(n))\n        return e\n\n    def change(e, k):\n        with contextlib.redirect_stdout(io.StringIO()):\n            e.add(f\"seen({k}).\")\n\n    def ask(e):\n        with contextlib.redirect_stdout(io.StringIO()):\n            r = e.query(\"can_enter(lab)\")\n        assert r == [{}], r\n\n\nfor n in (0, 1_000, 10_000):\n    kb = make(n)\n    ask(kb)  # warm: first query after load\n    after_change, repeat = [], []\n    for k in range(REPS):\n        t0 = time.perf_counter(); change(kb, k); ask(kb); after_change.append(time.perf_counter() - t0)\n        t0 = time.perf_counter(); ask(kb); repeat.append(time.perf_counter() - t0)\n    print(f\"{WHICH} N={n}: change+ask median {statistics.median(after_change) * 1000:.2f} ms; \"\n          f\"repeat ask median {statistics.median(repeat) * 1000:.3f} ms\")\n"
  },
  "output": {
   "probe_rev_new.py (PYTHONHASHSEED=0)": [
    "== 1 lazily built clause index (Program-owned, revision-keyed)",
    "before ask: _index_revision: {'initially': -1, 'happens': -1, 'can_enter': -1}",
    "after ask: _index_revision: {'initially': 16, 'happens': 16, 'can_enter': 16}",
    "program.revision: 16",
    "live QueryContext objects: 0",
    "== 2 recursion limit restored after the call",
    "recursion limit before/after: (1000, 1000)",
    "== 3 names of unbound variables differ between identical requests",
    "query 1: [{'X': f(_G22)}]",
    "query 2: [{'X': f(_G24)}]",
    "json 1: {'status': 'complete', 'answers': [{'X': {'f': 'f', 'args': [{'var': '_G26'}]}}], 'notes': []}",
    "json 2: {'status': 'complete', 'answers': [{'X': {'f': 'f', 'args': [{'var': '_G28'}]}}], 'notes': []}",
    "== 4 step counts: cold, warm, once, preparation only",
    "cold can_enter then repeat: [('can_enter(lab)', 103, 5, 'complete'), ('can_enter(lab)', 22, 5, 'complete')]",
    "cold once(can_enter): [('once(can_enter(lab))', 104, 6, 'complete')]",
    "cold true: [('true', 82, 1, 'complete')]",
    "cold, Limits(steps=1): ('exhausted', 2, ('timeline preparation stopped (ResourceExhausted: resource exhausted: step budget of 1 reached)', 'resource exhausted: step budget of 1 reached'), 'derived revision', -1)",
    "retry with defaults: ('complete', 103, 'derived revision', 16)",
    "== 5 analysis is not metered: analyse() signature",
    "analyse signature: (program: 'Program', *, auto_table: 'bool' = True) -> 'ProgramAnalysis'",
    "== 6 which api modules load with the package (fresh process)",
    "{'cyclops.api.values': True, 'cyclops.api.json_api': False, 'cyclops.api.affordance': False}",
    "== 7 diagnostics live on kb.diagnostics, not on each result",
    "ask notes/status: ((), 'complete')",
    "kb.diagnostics: ['[warning] <string>:1: initiates(e, g, t9). acts at t9, which nothing orders (no before/2 edge reaches it and it is not a number), so this ef']",
    "== 8 timeline: before/2 answers vs transitive order; partial read kept",
    "ask before(t0, t2): (0, 'complete')",
    "timeline.precedes(t0, t2): True",
    "bad before/2 clause: ask(true): ('complete', 'derived revision', 17, 'program', 17)",
    "  holds_at(door_open(lab), t1): ('exhausted', 1, (\"before(A, B) could not be read while building the time order (TypeCheckError: type error in arithmetic expression: expected evaluable, got 'foo/0'); the order is incomplete from that clause on, and answers that depend on it may be missing\",))",
    "== 9 explain on a rule head",
    "ask can_enter(lab): (1, 'complete')",
    "explain(can_enter(lab), t0): (False, 'never_initiated', 'complete')",
    "== 10 self-referential effect: /2 and /3 spellings",
    "/2 order ['t1', 't2', 't3']: {'t1': ('exhausted', 0), 't2': ('exhausted', 0), 't3': ('exhausted', 0)}",
    "/2 order ['t3', 't2', 't1']: {'t3': ('exhausted', 0), 't2': ('exhausted', 0), 't1': ('exhausted', 0)}",
    "/2 decide: ('indeterminate', 'exhausted')",
    "/3 order ['t1', 't2', 't3']: {'t1': ('complete', 1), 't2': ('exhausted', 0), 't3': ('exhausted', 0)}",
    "/3 order ['t3', 't2', 't1']: {'t3': ('exhausted', 0), 't2': ('exhausted', 0), 't1': ('complete', 1)}",
    "/3 decide: ('indeterminate', 'exhausted')",
    "== 11 decide's sub-queries (tunnel of 10)",
    "decide kind: allow",
    "sub-queries sent: [\"Struct('once', (Struct('affords', (Atom('enter_lab'), Atom('corridor'))),))\", \"Struct('guard', (Atom('enter_lab'), Order#227, Condition#228, Message#229))\", \"Struct('once', (Struct('can_enter', (Atom('lab'),)),))\", \"Struct('once', (Struct('reachable', (Atom('corridor'), Atom('lab'))),))\"]",
    "== 12 ask() on exhaustion returns the answers found so far",
    "ask reachable(corridor, X) steps=300: ('exhausted', 1, 301)",
    "== 13 SemanticConflict: ask, query, decide",
    "ask r(X): raises SemanticConflict: negation is not stratified: the predicates {p/0, q/0} depend on their own negation, so the program has no single intende",
    "query r(X): raises SemanticConflict: negation is not stratified: the predicates {p/0, q/0} depend on their own negation, so the program has no single intende",
    "decide: ('indeterminate', False, 'Action availability could not be determined.', None, ['SemanticConflict: negation is not stratified: the predicates {p/0, q/0} depend o'])",
    "== 14 name capture: a clause for a built-in",
    "load length(rope, 5).: raises PermissionErrorCS: permission error: length/2 is a builtin and cannot be redefined by the knowledge base",
    "== 15 tunnel lengths 50 to 60 (rewrite)",
    "decide by length: {50: ('allow', 'complete'), 51: ('allow', 'complete'), 52: ('allow', 'complete'), 53: ('allow', 'complete'), 54: ('allow', 'complete'), 55: ('allow', 'complete'), 56: ('allow', 'complete'), 57: ('allow', 'complete'), 58: ('allow', 'complete'), 59: ('allow', 'complete'), 60: ('allow', 'complete')}"
   ],
   "probe_rev_old.py (PYTHONHASHSEED=0)": [
    "== 1 tunnel lengths 50 to 60: decide and stdout lines",
    "n=50: ('allow', None, 1, ['WARNING: Recursion depth limit (50) reached for query: reachable(lab,lab)'])",
    "n=51: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=52: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=53: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=54: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=55: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=56: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=57: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=58: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=59: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "n=60: ('deny', 'You cannot find a way to the laboratory.', 2, ['WARNING: Recursion depth limit (50) reached for query: passage(tunnel49,lab)'])",
    "== 2 why can't I enter? (now(t2)) and why can I? (now(t0))",
    "now(t2). why(can_enter(lab)): [{'term': 'can_enter(lab)', 'type': 'derived', 'explanation': [{'reason': 'no_derivation_found', 'detail': 'No derivation path found for can_enter(lab)'}], 'source_spans': []}]",
    "now(t2). derivation(can_enter(lab)): []",
    "now(t2). why(\\+ can_enter(lab)): [{'term': '\\\\+(can_enter(lab))', 'type': 'derived', 'explanation': [{'reason': 'no_derivation_found', 'detail': 'No derivation path found for \\\\+(can_enter(lab))'}], 'source_spans': []}]",
    "now(t2). why(holds_at(door_open(lab), t2)): [{'term': 'holds_at(door_open(lab),t2)', 'type': 'temporal', 'explanation': [{'reason': 'does_not_hold', 'detail': 'door_open(lab) does not hold at t2'}], 'source_spans': []}]",
    "now(t2). evidence_chain(holds_at(door_open(lab), t2)): [{'term': 'holds_at(door_open(lab),t2)', 'source_spans': [], 'children': [{'term': 'initially(door_open(lab))', 'source_spans': [], 'children': [], 'type': 'base_fact'}], 'type': 'temporal'}]",
    "now(t0). why(can_enter(lab)): [{'term': 'can_enter(lab)', 'type': 'derived', 'explanation': [{'reason': 'rule_derivation', 'rule_id': ('can_enter', 1, 0), 'rule_text': 'can_enter(R) :- now(T), holds_at(door_open(R),T).', 'bindings': {'R$1': 'lab', 'T$2': 't0'}, 'supporting_facts': []}], 's",
    "now(t0). derivation(can_enter(lab)): [{'derived_term': 'can_enter(lab)', 'rule_id': ('can_enter', 1, 0), 'rule_text': 'can_enter(R) :- now(T), holds_at(door_open(R),T).', 'bindings': {'R$1': 'lab', 'T$2': 't0'}, 'supporting_facts': []}]",
    "now(t0). why(\\+ can_enter(lab)): [{'term': '\\\\+(can_enter(lab))', 'type': 'derived', 'explanation': [{'reason': 'no_derivation_found', 'detail': 'No derivation path found for \\\\+(can_enter(lab))'}], 'source_spans': []}]",
    "now(t0). why(holds_at(door_open(lab), t2)): [{'term': 'holds_at(door_open(lab),t2)', 'type': 'temporal', 'explanation': [{'reason': 'does_not_hold', 'detail': 'door_open(lab) does not hold at t2'}], 'source_spans': []}]",
    "now(t0). evidence_chain(holds_at(door_open(lab), t2)): [{'term': 'holds_at(door_open(lab),t2)', 'source_spans': [], 'children': [{'term': 'initially(door_open(lab))', 'source_spans': [], 'children': [], 'type': 'base_fact'}], 'type': 'temporal'}]",
    "== 3 a host-computed predicate registered by name",
    "weight(crate, W): [{'W': 12}]",
    "weight(crate, 12): [{}]",
    "weight(crate, W), W > 10: []",
    "heavy(crate): []",
    "light(rope): []",
    "decide lift (guard light(rope), a rule over weight/2): {'action': 'lift', 'kind': 'deny', 'enabled': False, 'condition': 'light(rope)', 'message': 'Too heavy.'}",
    "== 4 authoring checks on load",
    "'can_enter(R) :- now(T), holds_at(door_open(R), X).': ('ok', [\"WARNING: Variable 'X' appears only once - may be unintentional\", \"WARNING: Variable 'T' appears only once - may be unintentional\"], [])",
    "'p(a) :- q(b': (\"raises KBSyntaxError: [SYNTAX_INVALID_QUERY] Unmatched parentheses (unclosed opening parenthesis)\\nInput: p(a) :- q(b\\nComponent: parser\\nSuggestions:\\n  1. Check query syntax: predicate(arg1, arg2, ...)\\n     Example: parent(j | code=None suggestions=[ErrorSuggestion(message='Check query syntax: predicate(arg1, arg2, ...)', fix_type='syntax', confidence=0.5, example='parent(john, mary)')]\", [], [])",
    "== 5 effects with variables, written as facts and as rules",
    "facts: holds_at(door_open(lab), t2): [{}]",
    "facts: holds_at(locked(lab), T): []",
    "rules: holds_at(door_open(lab), t2): []",
    "rules: holds_at(locked(lab), T): [{'T': 't2'}, {'T': 't3'}]"
   ],
   "probe_change_cost.py (PYTHONHASHSEED=0, 1, 2; new then old)": [
    "new N=0: change+ask median 0.26 ms; repeat ask median 0.071 ms",
    "new N=1000: change+ask median 0.53 ms; repeat ask median 0.071 ms",
    "new N=10000: change+ask median 3.37 ms; repeat ask median 0.087 ms",
    "old N=0: change+ask median 0.35 ms; repeat ask median 0.011 ms",
    "old N=1000: change+ask median 0.35 ms; repeat ask median 0.011 ms",
    "old N=10000: change+ask median 0.36 ms; repeat ask median 0.011 ms",
    "new N=0: change+ask median 0.25 ms; repeat ask median 0.071 ms",
    "new N=1000: change+ask median 0.65 ms; repeat ask median 0.086 ms",
    "new N=10000: change+ask median 3.05 ms; repeat ask median 0.073 ms",
    "old N=0: change+ask median 0.36 ms; repeat ask median 0.011 ms",
    "old N=1000: change+ask median 0.35 ms; repeat ask median 0.011 ms",
    "old N=10000: change+ask median 0.36 ms; repeat ask median 0.011 ms",
    "new N=0: change+ask median 0.25 ms; repeat ask median 0.070 ms",
    "new N=1000: change+ask median 0.54 ms; repeat ask median 0.072 ms",
    "new N=10000: change+ask median 3.45 ms; repeat ask median 0.087 ms",
    "old N=0: change+ask median 0.35 ms; repeat ask median 0.011 ms",
    "old N=1000: change+ask median 0.33 ms; repeat ask median 0.011 ms",
    "old N=10000: change+ask median 0.35 ms; repeat ask median 0.011 ms"
   ]
  },
  "earlier_engine_test_suite": {
   "command": "PYTHONPATH=src python -B -m pytest -p no:cacheprovider -m \"not benchmark\" -q --continue-on-collection-errors -rfE",
   "export": "git archive e62758c src tests models scripts pyproject.toml",
   "summary": "5 failed, 6647 passed, 27 skipped, 65 deselected, 4 xfailed, 22 warnings, 15 errors in 84.38s (0:01:24)",
   "non_passes": "all 5 failures and 15 errors are ModuleNotFoundError: No module named 'hypothesis' (not installed)"
  }
 },
 "review_round_2_2026_09_29": {
  "about": "Probes run on 2026-09-29 to check the second-round accuracy review's counterexamples before the page was revised: unknown or unsupported predicates in a guard, a paradoxical self-referential effect, time order and events defined by rules, effects written as rules with a variable, recursion over a cycle, and the rewrite's passive diagnostics. Same isolation as the other stages: fresh `git archive <commit> src` exports of e62758c and 5d598de, PYTHONDONTWRITEBYTECODE=1, python -B, nothing installed; both engine repositories were clean before and after. Usage: python -B probe_round2.py <export>/src new|old lab_story.pl, with lab_story.pl the input listed above. Each engine's output was byte-identical under PYTHONHASHSEED 0, 1 and 2.",
  "python": "CPython 3.12.10, Windows 11",
  "scripts": {
   "probe_round2.py": "\"\"\"Round-2 accuracy-review probes, run on isolated git-archive exports.\nUsage: python -B probe_round2.py <export src dir> new|old <lab_story.pl>\"\"\"\nimport sys, io, contextlib, json\nsys.dont_write_bytecode = True\nsys.path.insert(0, sys.argv[1])\nNEW = sys.argv[2] == \"new\"\nSTORY = open(sys.argv[3], encoding=\"utf-8\").read()\nR = {}\n\nif NEW:\n    from cyclops import KnowledgeBase\n    from cyclops.api import decide\n    from cyclops.syntax.writer import format_term\n    def mk(text, **opts):\n        kb = KnowledgeBase(**opts); kb.load(text); return kb\n    def ask(kb, g):\n        try:\n            s = kb.ask(g)\n            return {\"answers\": [{k: format_term(v) for k, v in a.bindings.items()} for a in s.answers],\n                    \"status\": s.status.value, \"notes\": [n[:120] for n in s.notes]}\n        except Exception as ex:\n            return {\"raises\": type(ex).__name__, \"msg\": str(ex)[:160]}\n    def dec(kb):\n        d = decide(kb, \"enter_lab\", \"corridor\")\n        return {\"kind\": d.kind, \"message\": d.message, \"status\": getattr(d.status, \"value\", d.status),\n                \"notes\": [n[:120] for n in d.notes]}\nelse:\n    from kb.engine.kb_engine import KBEngine\n    from kb.integration import decide\n    def mk(text, **opts):\n        e = KBEngine(); e.add(text); return e\n    def ask(e, g):\n        try:\n            return {\"answers\": [{k: str(v) for k, v in x.items()} for x in e.query(g)]}\n        except Exception as ex:\n            return {\"raises\": type(ex).__name__, \"msg\": str(ex)[:160]}\n    def dec(e):\n        return decide(e, \"enter_lab\", \"corridor\").as_json()\n\ndef run(name, fn):\n    out = io.StringIO()\n    with contextlib.redirect_stdout(out):\n        try:\n            res = fn()\n        except Exception as ex:\n            res = {\"error\": type(ex).__name__, \"msg\": str(ex)[:200]}\n    res = {\"result\": res}\n    printed = [l[:200] for l in out.getvalue().splitlines()]\n    if printed:\n        res[\"stdout\"] = printed\n    R[name] = res\n\n# 1. Unknown or unsupported predicate in a guard (catch/3; a typo)\ndef unknown():\n    r = {}\n    kb = mk(STORY + \"\\nnow(t0).\\nguard(enter_lab, 2, catch(can_enter(lab), _, true), 'Guard two failed.').\\n\")\n    r[\"ask can_enter(lab)\"] = ask(kb, \"can_enter(lab)\")\n    r[\"ask catch(can_enter(lab), _, true)\"] = ask(kb, \"catch(can_enter(lab), _, true)\")\n    r[\"decide (catch guard)\"] = dec(kb)\n    kb = mk(STORY + \"\\nnow(t0).\\nguard(enter_lab, 2, can_entre(lab), 'Typo guard.').\\n\")\n    r[\"decide (typo guard can_entre)\"] = dec(kb)\n    if NEW:\n        kb = mk(STORY + \"\\nnow(t0).\\nguard(enter_lab, 2, catch(can_enter(lab), _, true), 'Guard two failed.').\\n\", unknown_fails=False)\n        r[\"decide (catch guard, unknown_fails=False)\"] = dec(kb)\n        kb = mk(STORY + \"\\nnow(t0).\\nguard(enter_lab, 2, can_entre(lab), 'Typo guard.').\\n\", unknown_fails=False)\n        r[\"decide (typo guard, unknown_fails=False)\"] = dec(kb)\n    return r\nrun(\"unknown_predicate_guard\", unknown)\n\n# 2. Paradoxical self-referential effect (no ordinary lock)\nPARADOX = \"\"\"time(t0). time(t1). time(t2). time(t3).\nbefore(t0, t1). before(t1, t2). before(t2, t3).\ninitially(door_open(lab)).\nhappens(auto_lock(lab), t1).\nterminates(auto_lock(lab), door_open(lab)) :- holds_at(door_open(lab), t3).\n\"\"\"\ndef paradox():\n    r = {}\n    for order in ((\"t3\", \"t2\", \"t1\"), (\"t1\", \"t2\", \"t3\")):\n        kb = mk(PARADOX)\n        r[\",\".join(order)] = {t: ask(kb, \"holds_at(door_open(lab), %s)\" % t) for t in order}\n    return r\nrun(\"paradox_self_reference\", paradox)\n\n# 3. Time order and events defined by rules\nEFFECTS = \"\"\"initially(door_open(lab)).\nterminates(lock_door(lab), door_open(lab)).\ninitiates(lock_door(lab), locked(lab)).\n\"\"\"\ndef rule_order():\n    r = {}\n    kb = mk(\"time(dawn). time(noon). time(dusk). time(night).\\nnext(dawn, noon). next(noon, dusk). next(dusk, night).\\nbefore(X, Y) :- next(X, Y).\\nhappens(lock_door(lab), noon).\\n\" + EFFECTS)\n    r[\"before/2 by rule\"] = {g: ask(kb, g) for g in [\"holds_at(door_open(lab), noon)\", \"holds_at(door_open(lab), dusk)\", \"holds_at(locked(lab), T)\", \"before(noon, dusk)\"]}\n    kb = mk(\"time(t0). time(t1). time(t2). time(t3).\\nbefore(t0, t1). before(t1, t2). before(t2, t3).\\nlock_time(t1).\\nhappens(lock_door(lab), T) :- lock_time(T).\\n\" + EFFECTS)\n    r[\"happens/2 by rule\"] = {g: ask(kb, g) for g in [\"holds_at(door_open(lab), t2)\", \"holds_at(locked(lab), T)\", \"happens(lock_door(lab), T)\"]}\n    return r\nrun(\"rule_defined_order_and_events\", rule_order)\n\n# 4. Effects written as rules with a variable\ndef rule_effects():\n    kb = mk(\"\"\"time(t0). time(t1). time(t2). time(t3).\nbefore(t0, t1). before(t1, t2). before(t2, t3).\ninitially(door_open(lab)).\nhappens(lock_door(lab), t1).\nroom(lab).\nterminates(lock_door(R), door_open(R)) :- room(R).\ninitiates(lock_door(R), locked(R)) :- room(R).\ncan_enter(R) :- now(T), holds_at(door_open(R), T).\nnow(t2).\n\"\"\")\n    return {g: ask(kb, g) for g in [\"holds_at(door_open(lab), t2)\", \"holds_at(locked(lab), T)\", \"can_enter(lab)\"]}\nrun(\"rule_form_effects\", rule_effects)\n\n# 5. Recursion over a cycle\ndef cyclic():\n    r = {}\n    for label, rules in ((\"left\", \"path(X, Y) :- path(X, Z), edge(Z, Y).\\npath(X, Y) :- edge(X, Y).\"),\n                         (\"right\", \"path(X, Y) :- edge(X, Y).\\npath(X, Y) :- edge(X, Z), path(Z, Y).\")):\n        kb = mk(\"edge(a, b). edge(b, c). edge(c, a).\\n\" + rules + \"\\n\")\n        a = ask(kb, \"path(a, X)\")\n        if \"answers\" in a:\n            a[\"answers\"] = sorted(x[\"X\"] for x in a[\"answers\"])\n        r[label] = {\"path(a, X)\": a, \"path(a, a)\": ask(kb, \"path(a, a)\")}\n    return r\nrun(\"cyclic_recursion\", cyclic)\n\n# 6. Rewrite: passive diagnostics for an unsupported directive\nif NEW:\n    def directive():\n        kb = mk(STORY + \"\\n:- initialization(main).\\n\")\n        s = kb.ask(\"true\")\n        return {\"load\": \"succeeded\", \"ask true\": s.status.value, \"result notes\": list(s.notes),\n                \"diagnostics\": [str(d)[:160] for d in kb.diagnostics]}\n    run(\"unsupported_directive\", directive)\n\nprint(json.dumps(R, indent=1, sort_keys=True))\n"
  },
  "output": {
   "probe_round2.py new (rewrite 5d598de; PYTHONHASHSEED=0, identical under 1 and 2)": {
    "cyclic_recursion": {
     "result": {
      "left": {
       "path(a, X)": {
        "answers": [
         "a",
         "b",
         "c"
        ],
        "notes": [],
        "status": "complete"
       },
       "path(a, a)": {
        "answers": [
         {}
        ],
        "notes": [],
        "status": "complete"
       }
      },
      "right": {
       "path(a, X)": {
        "answers": [
         "a",
         "b",
         "c"
        ],
        "notes": [],
        "status": "complete"
       },
       "path(a, a)": {
        "answers": [
         {}
        ],
        "notes": [],
        "status": "complete"
       }
      }
     }
    },
    "paradox_self_reference": {
     "result": {
      "t1,t2,t3": {
       "t1": {
        "answers": [],
        "notes": [
         "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped "
        ],
        "status": "exhausted"
       },
       "t2": {
        "answers": [],
        "notes": [
         "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped "
        ],
        "status": "exhausted"
       },
       "t3": {
        "answers": [],
        "notes": [
         "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped "
        ],
        "status": "exhausted"
       }
      },
      "t3,t2,t1": {
       "t1": {
        "answers": [],
        "notes": [
         "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped "
        ],
        "status": "exhausted"
       },
       "t2": {
        "answers": [],
        "notes": [
         "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped "
        ],
        "status": "exhausted"
       },
       "t3": {
        "answers": [],
        "notes": [
         "holds_at(door_open(lab), t3) is defined in terms of itself at the same time point; circular temporal evaluation stopped "
        ],
        "status": "exhausted"
       }
      }
     }
    },
    "rule_defined_order_and_events": {
     "result": {
      "before/2 by rule": {
       "before(noon, dusk)": {
        "answers": [
         {}
        ],
        "notes": [],
        "status": "complete"
       },
       "holds_at(door_open(lab), dusk)": {
        "answers": [],
        "notes": [],
        "status": "complete"
       },
       "holds_at(door_open(lab), noon)": {
        "answers": [
         {}
        ],
        "notes": [],
        "status": "complete"
       },
       "holds_at(locked(lab), T)": {
        "answers": [
         {
          "T": "dusk"
         },
         {
          "T": "night"
         }
        ],
        "notes": [],
        "status": "complete"
       }
      },
      "happens/2 by rule": {
       "happens(lock_door(lab), T)": {
        "answers": [
         {
          "T": "t1"
         }
        ],
        "notes": [],
        "status": "complete"
       },
       "holds_at(door_open(lab), t2)": {
        "answers": [],
        "notes": [],
        "status": "complete"
       },
       "holds_at(locked(lab), T)": {
        "answers": [
         {
          "T": "t2"
         },
         {
          "T": "t3"
         }
        ],
        "notes": [],
        "status": "complete"
       }
      }
     }
    },
    "rule_form_effects": {
     "result": {
      "can_enter(lab)": {
       "answers": [],
       "notes": [],
       "status": "complete"
      },
      "holds_at(door_open(lab), t2)": {
       "answers": [],
       "notes": [],
       "status": "complete"
      },
      "holds_at(locked(lab), T)": {
       "answers": [
        {
         "T": "t2"
        },
        {
         "T": "t3"
        }
       ],
       "notes": [],
       "status": "complete"
      }
     }
    },
    "unknown_predicate_guard": {
     "result": {
      "ask can_enter(lab)": {
       "answers": [
        {}
       ],
       "notes": [],
       "status": "complete"
      },
      "ask catch(can_enter(lab), _, true)": {
       "answers": [],
       "notes": [],
       "status": "complete"
      },
      "decide (catch guard)": {
       "kind": "deny",
       "message": "Guard two failed.",
       "notes": [],
       "status": "complete"
      },
      "decide (catch guard, unknown_fails=False)": {
       "kind": "indeterminate",
       "message": "Action availability could not be determined.",
       "notes": [
        "UnknownPredicateError: unknown predicate: catch/3"
       ],
       "status": null
      },
      "decide (typo guard can_entre)": {
       "kind": "deny",
       "message": "Typo guard.",
       "notes": [],
       "status": "complete"
      },
      "decide (typo guard, unknown_fails=False)": {
       "kind": "indeterminate",
       "message": "Action availability could not be determined.",
       "notes": [
        "UnknownPredicateError: unknown predicate: can_entre/1"
       ],
       "status": null
      }
     }
    },
    "unsupported_directive": {
     "result": {
      "ask true": "complete",
      "diagnostics": [
       "[warning] <string>:17: ignored unsupported directive: initialization(main)"
      ],
      "load": "succeeded",
      "result notes": []
     }
    }
   },
   "probe_round2.py old (earlier engine e62758c; PYTHONHASHSEED=0, identical under 1 and 2)": {
    "cyclic_recursion": {
     "result": {
      "left": {
       "path(a, X)": {
        "answers": [
         "a",
         "b",
         "c"
        ]
       },
       "path(a, a)": {
        "answers": [
         {}
        ]
       }
      },
      "right": {
       "path(a, X)": {
        "answers": [
         "a",
         "b",
         "c"
        ]
       },
       "path(a, a)": {
        "answers": [
         {}
        ]
       }
      }
     }
    },
    "paradox_self_reference": {
     "result": {
      "t1,t2,t3": {
       "t1": {
        "answers": [
         {}
        ]
       },
       "t2": {
        "answers": [
         {}
        ]
       },
       "t3": {
        "answers": [
         {}
        ]
       }
      },
      "t3,t2,t1": {
       "t1": {
        "answers": [
         {}
        ]
       },
       "t2": {
        "answers": [
         {}
        ]
       },
       "t3": {
        "answers": [
         {}
        ]
       }
      }
     }
    },
    "rule_defined_order_and_events": {
     "result": {
      "before/2 by rule": {
       "before(noon, dusk)": {
        "answers": []
       },
       "holds_at(door_open(lab), dusk)": {
        "answers": [
         {}
        ]
       },
       "holds_at(door_open(lab), noon)": {
        "answers": [
         {}
        ]
       },
       "holds_at(locked(lab), T)": {
        "answers": []
       }
      },
      "happens/2 by rule": {
       "happens(lock_door(lab), T)": {
        "answers": [
         {
          "T": "t1"
         }
        ]
       },
       "holds_at(door_open(lab), t2)": {
        "answers": [
         {}
        ]
       },
       "holds_at(locked(lab), T)": {
        "answers": []
       }
      }
     }
    },
    "rule_form_effects": {
     "result": {
      "can_enter(lab)": {
       "answers": []
      },
      "holds_at(door_open(lab), t2)": {
       "answers": []
      },
      "holds_at(locked(lab), T)": {
       "answers": [
        {
         "T": "t2"
        },
        {
         "T": "t3"
        }
       ]
      }
     }
    },
    "unknown_predicate_guard": {
     "result": {
      "ask can_enter(lab)": {
       "answers": [
        {}
       ]
      },
      "ask catch(can_enter(lab), _, true)": {
       "answers": []
      },
      "decide (catch guard)": {
       "action": "enter_lab",
       "condition": "catch(can_enter(lab),_,true)",
       "enabled": false,
       "kind": "deny",
       "message": "Guard two failed."
      },
      "decide (typo guard can_entre)": {
       "action": "enter_lab",
       "condition": "can_entre(lab)",
       "enabled": false,
       "kind": "deny",
       "message": "Typo guard."
      }
     }
    }
   }
  }
 },
 "review_round_3_2026_09_29": {
  "about": "Probes run on 2026-09-29 to check two claims from the third accuracy review: that the rewrite interns atoms in a process-wide table that only grows, and the difference case at 51, 55 and 60 tunnel segments with a 200-step budget, a cancel and default limits. Run on a git-archive export of the rewrite at 5d598de with PYTHONDONTWRITEBYTECODE=1 and python -B; the output was identical under PYTHONHASHSEED 0, 1 and 2. Usage: python -B probe_atoms.py <export src dir>; python -B probe_budget.py <export src dir> <lab_story.pl>.",
  "python": "CPython 3.12.10, Windows 11",
  "scripts": {
   "probe_atoms.py": "import sys\nsys.path.insert(0, sys.argv[1])\nfrom cyclops import KnowledgeBase\nimport cyclops.terms as T\nkb1 = KnowledgeBase(); kb1.load(\"p(a).\")\nkb1.ask(\"true\")\nn0 = len(T._ATOM_CACHE); had = 'zz_new_atom_1' in T._ATOM_CACHE\nr = kb1.ask(\"X = zz_new_atom_1\")\nn1 = len(T._ATOM_CACHE)\nr2 = kb1.ask(\"atom_concat(zz_made_, at_runtime, A)\")\nn2 = len(T._ATOM_CACHE)\nkb2 = KnowledgeBase()\nprint('before', n0, had, '| after ask with new atom', n1, 'zz_new_atom_1' in T._ATOM_CACHE, '| after atom_concat', n2, 'zz_made_at_runtime' in T._ATOM_CACHE, r2.status, [a.bindings if hasattr(a,'bindings') else a for a in r2.answers])\nprint('second KB sees same cache object:', T._ATOM_CACHE is T._ATOM_CACHE, 'new KB len', len(T._ATOM_CACHE))\n",
   "probe_budget.py": "import sys\nsys.path.insert(0, sys.argv[1])\nfrom cyclops import KnowledgeBase, Limits\nfrom cyclops.api import decide\nstory = open(sys.argv[2], encoding='utf-8').read()\ndef tunnel(n):\n    rows = [\"guard(enter_lab, 2, reachable(corridor, lab), 'You cannot find a way to the laboratory.').\",\n            \"reachable(A, B) :- passage(A, B).\", \"reachable(A, B) :- passage(A, C), reachable(C, B).\",\n            \"passage(corridor, tunnel1).\"]\n    rows += [f\"passage(tunnel{i}, tunnel{i+1}).\" for i in range(1, n-1)]\n    rows.append(f\"passage(tunnel{n-1}, lab).\")\n    return \"\\n\".join(rows)\nfor n in (51, 55, 60):\n    for warm in (False, True):\n        kb = KnowledgeBase(); kb.load(story); kb.load(\"now(t0).\"); kb.load(tunnel(n))\n        if warm: kb.ask(\"true\")\n        d = decide(kb, \"enter_lab\", \"corridor\", limits=Limits(steps=200)).as_json()\n        c = decide(kb, \"enter_lab\", \"corridor\", cancel=lambda: True).as_json()\n        full = decide(kb, \"enter_lab\", \"corridor\").as_json()\n        print(n, 'warm' if warm else 'cold', 'budget200:', d.get('kind'), d.get('status'), '| cancel:', c.get('kind'), c.get('status'), '| default:', full.get('kind'), full.get('status'))\n"
  },
  "output": {
   "PYTHONHASHSEED=0": [
    "before 5 False | after ask with new atom 6 True | after atom_concat 9 True Completion.COMPLETE [{'A': Atom('zz_made_at_runtime')}]",
    "second KB sees same cache object: True new KB len 9",
    "51 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "51 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "55 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "55 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "60 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "60 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete"
   ],
   "PYTHONHASHSEED=1": [
    "before 5 False | after ask with new atom 6 True | after atom_concat 9 True Completion.COMPLETE [{'A': Atom('zz_made_at_runtime')}]",
    "second KB sees same cache object: True new KB len 9",
    "51 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "51 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "55 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "55 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "60 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "60 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete"
   ],
   "PYTHONHASHSEED=2": [
    "before 5 False | after ask with new atom 6 True | after atom_concat 9 True Completion.COMPLETE [{'A': Atom('zz_made_at_runtime')}]",
    "second KB sees same cache object: True new KB len 9",
    "51 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "51 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "55 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "55 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "60 cold budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete",
    "60 warm budget200: indeterminate exhausted | cancel: indeterminate cancelled | default: allow complete"
   ]
  }
 }
}
