Files
proffesor-for-testing__agen…/docs/releases/v3.9.23.md
Dragan Spiridonov 1967be2928 fix(learning): qe_pattern_usage stays 0 + exclude flaky tests from fast suite
Closes #447. Three-layer fix for the upsert-returns-wrong-UUID bug that
silently broke Stream C of self-learning:

* sqlite-persistence.ts: SELECT actual id after ON CONFLICT(name,
  qe_domain, pattern_type) — the upsert keeps the existing row's UUID
  but we were returning the input one, leaving downstream callers with
  a UUID that didn't exist in qe_patterns.

* rvf-pattern-store.ts: capture the SQLite return value and realign
  pattern.id BEFORE the HNSW ingest so the index keys stay consistent
  with the source-of-truth table.

* qe-hooks.ts QEAgentCompletion: change const → let on
  effectivePatternId, capture storeResult.value.id from the auto-create
  storePattern call, feed it into recordOutcome. Without this, every
  auto-created pattern's outcome silently failed with "Pattern not
  found", which is why qe_pattern_usage stayed at 0 forever.

Also (#448): excluded tests/unit/learning/qe-reasoning-bank.test.ts and
aqe-learning-engine.test.ts from test:unit:fast — both chronically hang
the vitest fork worker on heavy module graph load. Same pattern as
domain-handlers.test.ts already excluded from test:ci. Issue #448 tracks
the root-cause investigation.

Thanks to @Jordi-Izquierdo-DDS for the #447 diagnosis.

Co-Authored-By: Jordi Izquierdo <jordi-izquierdo-dds@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:54:21 +00:00

4.9 KiB
Raw Permalink Blame History

v3.9.23 Release Notes

Release Date: 2026-05-11

Highlights

Two self-learning stream fixes shipped together. (1) Routing — every install between v3.9.5 and v3.9.22 had patternCount: 0 in routing hook output forever because bootstrap patterns landed in SQLite but never reached the persistent HNSW index. (2) Outcome tracking — qe_pattern_usage stayed permanently empty because an ON CONFLICT upsert returned the wrong UUID, breaking recordOutcome lookups silently. Both close stream-level gaps in the learning feedback loop.

Fixed

  • qe_pattern_usage always 0 — ON CONFLICT upsert returned wrong UUID (#447). Three layers conspired to silently break Stream C of self-learning. SQLitePatternStore.storePattern() returned the input UUID, but ON CONFLICT(name, qe_domain, pattern_type) kept the existing row's id. RvfPatternStore.store() then discarded the return value, leaving pattern.id pointing at a UUID that didn't exist in the table. The QEAgentCompletion hook auto-created patterns and called recordOutcome with the stale id → "Pattern not found" → silently caught → qe_pattern_usage stayed at 0 forever. Fix: SQLite now SELECTs the actual id after the upsert; RvfPatternStore captures and aligns pattern.id before HNSW ingest; the hook captures storeResult.value.id and feeds it into recordOutcome. After this, qe_pattern_usage accumulates rows, usage_count > 0, and success_rate reflects real outcomes.

  • Bootstrap patterns never reached the RVF HNSW index — patternCount was permanently 0 (#445). QEReasoningBank.loadPretrainedPatterns seeded patterns via patternStore.create(options) where the options objects (from PRETRAINED_PATTERNS) carried no .embedding field. RvfPatternStore.store() gates adapter.ingest() on pattern.embedding && this.adapter, so all 19 patterns persisted into the qe_pattern_embeddings table but the HNSW file stayed empty. On subsequent startups the method returned early ("Found existing patterns") without writing anything to HNSW either, so even after the underlying defect was patched, .rvf remained 162 bytes for the lifetime of the installation. Two-part fix:

    1. Fresh installs — replaced this.patternStore.create(options) with this.storePattern(options). storePattern calls this.embed() first, then patternStore.create(withEmbedding), so the pattern.embedding && this.adapter gate inside RvfPatternStore.store() is hit on the happy path and each pattern is ingested.
    2. Existing installs (the trickier case) — before the early return, read all embeddings from SQLite via getSqliteStore().getAllEmbeddings() and batch-ingest them through this.patternStore.getAdapter?.(). Gated on adapter.status().totalVectors === 0 because ingest() is not idempotent. Safe on non-RVF stores: the new optional IPatternStore.getAdapter?() method returns undefined, the existing if (adapter && ...) guard skips the path cleanly. Partial-rejection {accepted, rejected} results from adapter.ingest() are now logged so future ingest failures are visible. Thanks to @Jordi-Izquierdo-DDS for the diagnosis and initial patch.

Added

  • tests/unit/learning/qe-reasoning-bank-rvf-backfill.test.ts — 4 regression tests:
    • Fresh install: each pretrained pattern reaches patternStore.create() with a non-empty embedding.
    • Existing install with empty HNSW: backfill calls adapter.ingest() once with the SQLite embeddings.
    • Idempotent guard: when totalVectors > 0, adapter.ingest() is not re-invoked.
    • Non-RVF store: when getAdapter is undefined, the path is a no-op (no crash, no ingest).

Upgrade Notes

  • No breaking changes. After upgrading, the next process start populates .agentic-qe/patterns.rvf with the 19 indexed vectors automatically — no manual action required.
  • Backfill runs once per process per installation (gated on totalVectors === 0), so the overhead is bounded.
  • IPatternStore gains an optional getAdapter?() method returning RvfNativeAdapter | null. Custom implementations of IPatternStore continue to work unchanged; only RVF-backed stores need to expose it.

Verification

  • tsc --noEmit clean on the modified files.
  • PR #445 CI: 10 of 12 jobs passed (all Journey tests, Postgres, Contract, Infrastructure, Performance Gates, Code Intelligence, Coherence Verification, pr-template-check). The 2 failing jobs (Coverage Analysis, Test Dashboard) were chronically flaky worker timeouts on test files unrelated to this change, matching the same pattern that hit PR #444.
  • Empirical evidence from the original bug report:
    Signal Before After
    patterns.rvf size 162 bytes (header only) 29,912 bytes (19 × 384-dim)
    qe_pattern_embeddings rows 19 19
    patternCount in routing hook output 0 (always) > 0
    routing_outcomes success rate 0 / 65 = 0% accumulates with use