The Prolog layer does not learn

Audited 2026-08-14, in response to "check the self-learning knowledge base in Prolog — is it wired, is it deployed, is the self-learning taking place".

Answer: no — and deliberately not. Two things that are live get mistaken for it. Separating them is the whole point of this page.

Three different claims are easy to conflate:

what it means status here
rebuilt facts re-derived from a static snapshot live, deployed, running
accumulated facts retained from runtime observation live and queryable (identity stamped + run-aware reader, closed 2026-08-14)
learned rules that CHANGE from outcomes does not exist

Only the third is self-learning. The first two are memory and regeneration.

1. Rebuilt — wired, deployed, running

orchestrata-ast-live-sync.service is enabled and active (12h uptime, 17.6s CPU). It watches ~/prod/orchestrata-ultimate/src/orchestrata_ultimate with a 1000 ms debounce and writes to neo4j://10.53.1.160:7691 db=orchkg. Its ExecStartPre gates on orchestrata-kg-ready — visible at boot as four "Unable to retrieve routing information" retries before Started, which is the readiness probe doing its job rather than a fault.

It logs nothing for hours at a time, and that is correct: it watches the prod tree, and no change means a watch event and zero graph writes.

Three KB families, all snapshot- or request-derived:

KB facts built by
codebase-intelligence .claude/run/codebase_kb/facts.pl — 78.9 MB / 565,754 lines CodebaseTools.reindex() over the static plans/ast-ultimate.json
traceability-intel .cache/trace_intel/*.pl — same 565,754 + 13,455 lines scripts/trace_intel.py, same snapshot loader
gate layer none persisted gates/fact_extractor.py, extracted fresh per invocation

Freshness is a single mtime comparison — facts_file.st_mtime < snapshot.st_mtime at codebase_query_tools.py:79-86 and scripts/code_intel.py:130-131 — reindex if stale, attach if fresh. Fact counts track the snapshot's symbol count 1:1 and have never grown between runs.

CodebaseTools is wired into production: register_codebase_query_tools() is called from agents/tool_registry.py:2120 inside register_native_tools(), which ToolExecutor._native_tools() (:1123) invokes to build the per-volk registry the tool-calling loop dispatches through.

Wired is not the same as materialized. In the deployed tree plans/ast-ultimate.json exists but .claude/run/codebase_kb/facts.pl does not — no volk has triggered a code-intelligence query there yet. That is the documented lazy-build-on-first-use design working, not a fault, but the two claims are different and only the first is currently true.

PROLOG_STATUS.md claimed the opposite ("no production importer today") long after the SPEC-062/072 wiring landed; corrected 2026-08-14. The error is instructive: who-imports answers a structural question and cannot see a call, which is the same instrument mistake that once produced a false "0 callers" finding for freshness_guard.py.

2. Accumulated — live, and now queryable

Rewritten late 2026-08-14 — the original section here recorded records of exactly {test, duration, ts} and named identity stamps "the single most valuable item outstanding". That item is closed; this section now describes what ships.

reports/test-durations.ndjson (361 MB / ~2.18M records, 7-day retention, compaction working) carries run identity end to end:

The remaining instrument work is items 2–4 of the list at the end of this page.

3. Learned — does not exist, and should not

Architecturally forbidden. ADR-0005 (Accepted, 2026-07-23): "The knowledge graph is the single system of record for facts. Prolog owns no durable facts." Rules — not facts — are Prolog's durable content and live in version control. "A separate Prolog fact-KB" appears under Alternatives rejected: two copies of every fact ⇒ drift and dual maintenance.

The code agrees. Across 28 .pl files, every assertz/retract is projection with retract-after (retract_facts, retract_rtm_facts, kg_relevance.pl's "retracted after"). Every assertz in production Python asserts a ground fact scoped to the current request — volk_active, kg_task, kg_candidate, violation, safe_mode_active — into the process-global janus engine. None persists to disk or survives the check that produced it. No .pl rule anywhere carries a confidence, weight or threshold that adapts.

And it was measured, not argued. plans/prolog-learning-subsystem-research-2026-08-12.md (14 agents, 528 tool calls) replayed the repo's own LPT packer at production settings against 12 clean single-run segments recovered from the durations sink, scoring five estimators on held-out runs:

estimator mean makespan (10 held-out runs)
last-value (what ships today) 291.0s
mean-of-history 290.1s
EWMA(α=0.4) 290.5s
median-of-history 299.0s — WORSE
perfect ORACLE 274.4s

Learning beats the trivial estimator by 0.5–0.9s. The obvious "robust" choice loses by 8.0s. A perfect oracle is worth 3.4s (1.0%) once the CPU floor binds — which it does in 9 of 10 simulated runs — against a 28.2s noise band. Per-module coefficient of variation across runs is p50 0.329 and the noise is common-mode (machine load, thermal, scheduler), so averaging over history does not reduce error on the next run: it chases a mean that does not exist. Proving an 8.4s win would take n=141 runs per arm — 19.1 hours per arm.

test_executor._estimate_durations is literally history.get(m, median(history)) — an EWMA with α=1.0. The "learning" being proposed is a last-value dictionary lookup that already ships, and every real learner measured worse.

Why statistical facts must not enter the gate rulebase

Specific, not stylistic:

What was explicitly dropped, with numbers attached

So it is not re-proposed: learned/EWMA duration prediction; wiring the CLP(FD) scheduler for speed (same 3.4s ceiling; ~24s solve at 20 tasks, 1,615 modules would not terminate usefully); a Prolog fact base for the observation stream (370× slower than dicts on this repo's own benchmark); risk-ordered test ordering (0s of wall, against 59% order-dependent flakes); conflict-pair mining until the sample is well past n=24.

Keep Prolog exactly where it is. The gate layer earns its place on proof trees (explain.pl), the three-valued PASS/FAIL/INVALID verdict, and rules-as-reviewable-data in a .pl diff — none of which is a speed argument.

Build the instrument, not the subsystem

In priority order, from the same research:

  1. Stamp run_id / batch_id / lane / head_commit / outcome onto the durations record — DONE 2026-08-14: writer, env channel, run-aware reader (test_durations.py runs / --run), and header-safe compaction all shipped and tested (see §2).
  2. Retain the JUnit setup/call/teardown rows _process_batch_result already parses and discards — zero extra parsing, and it converts the weakest number in the runtime decomposition into a measured one.
  3. Per-fixture, per-batch setup cost via a pytest_fixture_setup hookwrapper, env-gated. Needs n=1, not history.
  4. Measure the Incus container CPU — Memgraph and Redis share the same 6 physical cores and are excluded from the 2,567.7 core-s, so every "the suite is at its floor" claim is conditioned on that omission.

2026-08-19 update: two specs landed, and a rule-induction proposal

Items 2 and 3 above now have code — uncommitted, untested, as of this update. Checked directly against the working tree: pytest_fixture_setup (item 3) and a _test_phase_durations buffer + reports/test-phase-durations.ndjson sink (item 2) are both present and functionally wired end-to-end (pytest_live_observer.py), and a real run already produced real output — but neither has a test file yet and neither is committed. Call them IN PROGRESS, not done, until that changes.

This is now formalized. SPEC-113 (specs/113-measurement-retention/spec.md) and SPEC-114 (specs/114-governance-rule-learning/spec.md), both Status: Draft, landed the same day and are now authoritative for this page's content — REQ-113-FR1..FR6 for the instrument above, REQ-114-FR1..FR4 for a new proposal this page did not previously cover: the user asked, separately, for (A) mining the project's own recurring defects (review codes, anti-patterns, missing spec/@traces) into candidate Prolog rules, (B) a mandatory pending → human-review → promoted quarantine so an induced rule never enforces on its own, and (C) turning explain.pl's existing why-not machinery toward "why isn't this feature implemented" — not just "why did this gate rule fire". All three are UNBUILT, SPEC-STAGE, and awaiting the same Opus reviewer's verdict on whether the underlying idea survives at all. (B) resolves cleanly onto this page's own §3 diagnosis: pending becomes a severity VALUE in the existing default_severity/2 / severity_exception/3 / blocking/1 channel — one channel, not a second one — plus a structural quarantine file so the shared-namespace and non-monotonic-NAF risks named above still hold even for a defective pending clause.

Full write-up, code excerpts, and five validated diagrams (three generated from the AST toolkit, two hand-authored for the arc and the proposed lifecycle): Measurement Retention & Proposed Rule Induction.