Skip to content

fix(recall): deterministic rescue ranking + rerank scale-mixing fix (flag-gated) - #324

Open
heybeaux wants to merge 8 commits into
stagingfrom
fix/recall-rerank-scale-mixing
Open

fix(recall): deterministic rescue ranking + rerank scale-mixing fix (flag-gated)#324
heybeaux wants to merge 8 commits into
stagingfrom
fix/recall-rerank-scale-mixing

Conversation

@heybeaux

@heybeaux heybeaux commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes three recall-ranking defects found during the memory-formation R&D spike. All behavior changes remain behind explicit flags that default off, so the existing default path is preserved.

1. Deterministic, continuous rescue ranking

Keyword rescue previously assigned flat constants, then relied on database row order to break ties. This PR now:

  • maps FTS ts_rank and ILIKE lexical coverage into continuous rescue scores;
  • uses one total-order comparator across recall sorting;
  • retains deterministic SQL id ASC tie-breaking behind RECALL_RESCUE_SQL_TIEBREAK=true.

The SQL tiebreak does not move the benchmark metric, but it fixes real restart-to-restart nondeterminism and is retained on correctness grounds.

2. Rerank/re-add scale mixing

RECALL_RERANK_SCALE_FIX=true ranks the full reranked candidate pool and applies limit once at the end. This prevents raw rescue-band scores from being reinserted above already-rescaled reranker scores and restores limit monotonicity.

RECALL_NO_RESCUE=true remains available as the vector-only control arm.

3. Lexical coverage floor

RECALL_LEXICAL_COVERAGE_FLOOR=true requires multi-term evidence before the OR-joined ILIKE rescue can promote a candidate. This removes single-token false positives such as the corpus prefix artifact matching every query only because both contained MNEMON.

The discarded importance-ramp prototype is not included. It added no measurable value over the coverage floor.

Measured results

20-task noisy corpus, limit=10, usage counters reset per arm:

arm gold@1 gold@5 gold@10 MRR@10
default 0/20 6/20 6/20 0.150
scale fix 0/20 14/20 14/20 0.350
scale fix + SQL tiebreak 0/20 14/20 14/20 0.350
scale fix + coverage floor 14/20 14/20 14/20 0.700
all retained fixes 14/20 14/20 14/20 0.700
vector-only control 6/20 7/20 8/20 0.331

Independent repeats and seed variants were bit-identical. The remaining 6 misses sit at pool rank 11, one slot beyond the page, so the next research target is candidate-pool depth and near-duplicate cluster handling rather than more rescue-band tuning.

Safety and rollout

  • No production deploy or data change.
  • No migration.
  • Existing default behavior remains flag-gated and unchanged.
  • Enable only in staging first: RECALL_RERANK_SCALE_FIX=true, RECALL_LEXICAL_COVERAGE_FLOOR=true, and RECALL_RESCUE_SQL_TIEBREAK=true.

Verification

  • Focused Jest gate: 3 suites, 83 tests passed
  • Build: Prisma generate + Nest build, 0 TypeScript issues, 871 files compiled
  • Earlier full branch gate: 341 suites, 4,956 tests passed
  • Branch pushed at 8af07f6
  • CI green
  • Reviewer approval before any staging flag enablement

beaux-riel and others added 7 commits August 23, 2026 11:41
Read-only Phase 1 deliverables for the Engram memory-formation +
query-transformation initiative:

- 01 current-state map: ingestion→recall data flow, score semantics,
  data model, intervention points, existing LLM usage
- 02 research memo: literature review + blind-spot pass with cited
  primary sources; separates published evidence from inference
- 03 experiment spec: A–E ablation matrix, graduated corpora, retrieval
  + downstream metrics, saturation-aware reproducibility controls
- README: exec summary, sequencing, open questions

No code, data, or migrations touched. Prototypes remain unbuilt pending
review.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Three Phase 1 assumptions were falsified by running against live Engram:

- Local Engram serves on 47291, not 3001 (3001 is whalehawk
  provider-server)
- `content` is a legacy alias for `raw`, not a second field. The
  documented "store formed text in content, keep raw for provenance"
  plan would have been a silent no-op and invalidated intervention B.
  Union is now realized inside the embedded string via contextual
  prefix + verbatim observation with recorded prefixLength.
- Local embedder 500s under parallel recall, which silently degraded
  arm C into the baseline — a bias against the intervention under test

Also documents a pre-existing pgvector dimension mismatch (1536 vs 384)
on the write path, recommended for its own ticket.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Verified independently twice against the committed v0.2 noisy report:
18/20 queries end in a 5-or-more-way tie at the top score, and only
2/20 top-1 results are decided by score. The reported "13/20 rank 1"
is 11-of-13 decided by array order within a tied block; under fair
tie-breaking the same system scores ~0.21, not 0.65. Context
false-positive injection rate is 0.87.

Root cause is the score semantics already documented in the
current-state map: keyword-rescue paths assign hard-coded constants
(FTS 1.25, ILIKE 1.1) rather than a similarity, so every rescued
candidate lands on an identical score.

This blocks the A-E ablation: interventions work by supplying better
candidates, but a better candidate cannot change top-1 when the
tie-break decides it. Sequencing revised so scoring/tie-breaking is
fixed before the matrix runs.

Downstream lift figures in RESULTS.md are unaffected; the retrieval
narrative attached to them is not.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Engram's top-1 recall result was decided by Postgres row order, not by
score. On the 20-task noisy benchmark corpus 18/20 queries ended in a
5-or-more-way tie at the top score and only 2/20 were uniquely decided
by score, so the published "13/20 rank 1" collapses to ~0.21 under fair
tie-breaking. See
docs/research/memory-formation-query-transform/04-finding-tie-domination.md.

Two independent causes:

1. Both keyword-rescue paths assigned constants. The FTS/BM25 path
   computed an RRF value and threw it away, calling scoreMap.set(id, 1.25)
   for every rescued candidate; the ILIKE path did the same with 1.1. The
   SQL already ordered by ts_rank but never SELECTed it.
2. Every score sort was a bare (b.score ?? 0) - (a.score ?? 0). Because
   Array.prototype.sort is stable, equal scores kept whatever order the
   DB returned.

Fix, in new src/memory/memory-ranking.util.ts:

- ftsRescueScore() maps ts_rank (now SELECTed in all three FTS query
  variants) into (1.10, 1.25]: 0.9 * (ts_rank / max ts_rank) + 0.1 *
  normalised RRF. Band top is exactly 1.25 for the best hit and values
  decrease strictly with rank, so near-duplicates with identical ts_rank
  no longer collapse into a tie.
- ilikeRescueScore() maps lexical coverage (matched terms / extracted
  terms, computed in SQL) into (1.00, 1.10] with the same RRF tie-break.
- compareByRankKeys() is the single comparator now used by every score
  sort in memory-query.service.ts and memory-query-ranking.service.ts:
  score desc, vectorScore (cosine) desc, importanceScore desc, createdAt
  desc, id asc. The final key makes the ordering a total order, so the
  same candidate set sorts identically regardless of DB row order.
- Sticky keyword re-add after reranking preserves the banded score
  instead of clamping to 1.1.

Inter-band priority is unchanged on purpose: FTS rescue still outranks
ILIKE rescue, which still outranks every cosine hit (ceiling 1.0). The
graph-recall x1.2 boost and cross-encoder reranker behaviour are
untouched so the re-baseline measures one variable.

Tests: new memory-ranking.util.spec.ts (17) plus a regression block in
memory-query.service.spec.ts asserting distinct scores for distinct
ts_rank, band ordering vs a 0.99 cosine hit, and identical output for
the same tied set fed in three different input orders. src/memory:
66 suites / 1143 tests pass.
…UE (default off)

The rescue score bands added in c905438 do not merely break ties in favour
of lexical hits — they replace the ranking. Measured on the 20-task noisy
prefix corpus with a new retrieval-only probe (no LLM, no generation):

- gold is in the candidate pool for 20/20 tasks, so this is a ranking
  failure, not a recall failure;
- inside an FTS-rescued block the candidate with the *highest* cosine is
  ranked *last*, because ftsRescueScore() overwrites scoreMap with a
  ts_rank-derived value and the cosine only survives as an unused
  tie-break key;
- the sticky keyword re-add re-injects rescued memories with their raw
  pre-rerank band scores (1.25/1.15/1.05) after applyReranking has
  rescaled every other candidate to <= ~1.0, so band values are sorted
  against rescaled values and win categorically. This also makes results
  non-monotonic in `limit`: the same query returns 5 band hits at
  limit=5 and zero band values at limit=50.

Prototype fix, off by default (RECALL_RELATIVE_RESCUE=true to enable):
relativeRescueScore() turns lexical agreement into a bounded boost on the
candidate's own cosine (score = cosine * (1 + maxBoost * quality); 0.2
FTS, 0.1 ILIKE, 0.15 identity) instead of an absolute band above the
cosine ceiling. Lexical-only candidates with no vector hit are anchored at
0.9 * bestCosine so the fresh-exact-write guarantee survives without
letting them displace the best semantic hit. The sticky re-add appends
below the rescaled floor in relative mode instead of above it.

Also under the flag: the flat 1.15 identity rescue constant (same
flat-constant defect class c905438 fixed) becomes continuous. The flat
0.75 forcedFts constant is annotated, not changed — topIds and memoryMap
are both built from `sorted`, so that branch is unreachable and forcedFts
is always empty.

Measured, both arms against usage counters reset to the same cold
snapshot (recall increments retrievalCount and applyUsageWeighting feeds
it back into the score, so the benchmark perturbs itself):

  gold in top-5   6/20 -> 7/20
  gold in top-10  6/20 -> 8/20
  MRR@10          0.150 -> 0.174
  gold in top-1   0/20 -> 0/20

Small, and deliberately reported as such: after the fix the ranking is
the semantic ranking, and on 11 of 20 tasks the gold memory sits behind
all ten of its distractors in that ordering. The distractors restate the
query verbatim; under bge-base they are the closer match. The band fix is
worth having for determinism, scale hygiene and limit-monotonicity, not
for benchmark score.

Default behaviour unchanged: src/memory 66 suites / 1150 tests pass.
Analysis in docs/research/memory-formation-query-transform/05-finding-band-inversion.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…_SCALE_FIX

The sticky keyword re-add reinjected rescued candidates at their raw
pre-rerank band scores (1.25 / 1.15 / 1.05) into an ordering that
applyReranking had already rescaled to <= ~1.0. Raw beat rescaled
categorically, so the limit=10 page was whatever the lexical rescue
produced and the reranker's judgement was discarded — and ranking was
non-monotonic in `limit` (the same memory scored 1.24804 at limit=10 and
0.42623 at limit=250).

RECALL_RERANK_SCALE_FIX=true (default off) ranks the whole candidate pool
instead of truncating at `limit`, so rescued candidates stay sticky *in
the rescaled scale* and the re-add becomes a no-op; `limit` is applied
once, at the end, to a total order. Adds RECALL_NO_RESCUE=true as the
vector-only control arm.

Measured on the noisy mnemon corpus (20 tasks, limit=10, usage counters
reset per arm):

  arm        gold@1  gold@5  gold@10  MRR@10
  default         0       6        6  0.1500
  scalefix        0      14       14  0.3500
  relative        0       7        8  0.1738
  both            0       7        8  0.1738
  norescue        6       7        8  0.3312

Limit-monotonicity verified live: default violates the prefix property on
20/20 queries, scalefix holds on 20/20 (both 5-vs-10 same-pool and
10-vs-250). Default arm output is unchanged with all flags off.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e fix

The 5-arm ablation on the 20-task noisy prefix corpus (limit=10, usage
counters reset per arm) killed the relative-rescue prototype:

  arm         gold@1  gold@5  gold@10  MRR@10
  default          0       6        6   0.150
  scalefix         0      14       14   0.350
  relative         0       7        8   0.174
  both             0       7        8   0.174
  vector-only      6       7        8   0.331

The kill signal is that `both` is numerically IDENTICAL to `relative`, not
that `relative` merely underperformed. relativeRescueScore() rewrites
candidate scores into the rescaled scale upstream, at the FTS/ILIKE/identity
injection sites. By the time RECALL_RERANK_SCALE_FIX runs there is no scale
mismatch left for it to correct, so the scale fix degrades to a no-op. The
two flags do not compose: enabling relative rescue *replaces* the scale fix
and drags 14/20 gold@5 back down to 7/20. They are mutually exclusive by
construction, and the scale fix is strictly better on every metric.

Leaving the prototype in the tree would have left a config that silently
halves recall quality one env var away, so it goes now rather than rotting.

Removed:
  - relativeRescueScore() and the RELATIVE_* boost/anchor constants
  - the RECALL_RELATIVE_RESCUE flag read and all three relative-mode
    branches (FTS, ILIKE, identity), plus the relative-mode variant of the
    sticky re-add's below-the-floor append
  - the 7 relativeRescueScore unit tests
  - scripts/research/run-rescue-ab.sh (existed only to A/B this flag);
    run-scale-fix-arms.sh drops the now-impossible relative/both arms

Kept deliberately:
  - RECALL_RERANK_SCALE_FIX and RECALL_NO_RESCUE and everything they gate
  - everything from c905438 (ftsRescueScore, ilikeRescueScore,
    compareByRankKeys, the ts_rank SELECTs)
  - the unreachable-forcedFts annotation and the flat-1.15 identity note —
    findings, not prototype
  - all docs under docs/research/memory-formation-query-transform/. The
    analysis in 05-finding-band-inversion.md is still valid and is *why* we
    know to kill this; it now carries a note at the top recording the
    removal and the both==relative collapse so nobody re-derives it.

memory-ranking.util.ts and memory-ranking.util.spec.ts are now byte-identical
to c905438; the only remaining delta in memory-query.service.ts vs c905438 is
the scale-fix work. With all flags off recall output is unchanged.

src/memory: 67 suites / 1152 tests pass (1159 before; the 7 fewer are exactly
the removed relativeRescueScore tests).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc7dccf0-c06b-4260-8e8d-f9bd40185672


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

✅ Recall Benchmark Results

╔══════════════════════════════════════════════════════════════╗
    ║              ENGRAM RECALL BENCHMARK REPORT                 ║
    ╚══════════════════════════════════════════════════════════════╝
    
      Git SHA:    835cdc2
      Branch:     HEAD
      Timestamp:  2026-08-24T06:13:47.657Z
    
    ┌─────────────────────────────────────────────────────────────┐
    │  OVERALL SCORES                                            │
    ├─────────────────────────────────────────────────────────────┤
    │  Total Queries:   81                                        │
    │  Passed:          79 / 81 (97.5%)                                        
    │  Precision@5:     96.9%  ✅  (threshold: 95.0%)
    │  Recall@20:       97.5%
    │  MRR:             0.8713
    │  Isolation:       100.0%  ✅  (threshold: 100%)
    └─────────────────────────────────────────────────────────────┘
    
    ┌──────────────────┬───────┬────────┬──────────┬──────────┬──────────┬───────────┐
    │ Category         │ Total │ Passed │ Prec@5   │ Rec@20   │ MRR      │ Isolation │
    ├──────────────────┼───────┼────────┼──────────┼──────────┼──────────┼───────────┤
    │ adversarial      │ 10    │ 10     │ 100.0%   │ 100.0%   │ 1.0000   │ 100.0%    │
    │ cross_feature    │ 10    │ 9      │ 90.0%    │ 100.0%   │ 0.8667   │ 100.0%    │
    │ edge_case        │ 16    │ 16     │ 100.0%   │ 100.0%   │ 0.9245   │ 100.0%    │
    │ emotional        │ 10    │ 10     │ 95.0%    │ 100.0%   │ 0.6405   │ 100.0%    │
    │ rls_isolation    │ 10    │ 10     │ 100.0%   │ 100.0%   │ 0.9500   │ 100.0%    │
    │ semantic         │ 14    │ 13     │ 92.9%    │ 100.0%   │ 0.8185   │ 100.0%    │
    │ temporal         │ 11    │ 11     │ 100.0%   │ 81.8%    │ 0.8864   │ 100.0%    │
    └──────────────────┴───────┴────────┴──────────┴──────────┴──────────┴───────────┘
    
    ❌ FAILED QUERIES (2):
    
      [semantic_011] "What coffee roast do I prefer?" (user: alice)
        ⚠️  ZERO HITS: expected alice_coffee_004_correction in top 5
        📊 Precision@5: 0.0% — hit: [], missed: [alice_coffee_004_correction]
        📋 Actual top 5: [alice_low_importance_001, alice_coffee_002, alice_coffee_001, alice_calm_001, alice_travel_002]
    
      [cross_001] "medication I need to take every morning" (user: alice)
        ⚠️  ZERO HITS: expected alice_health_001 in top 5
        📊 Precision@5: 0.0% — hit: [], missed: [alice_health_001]
        📋 Actual top 5: [alice_calm_001, alice_coffee_001, alice_coffee_002, alice_cooking_001, alice_emotion_change_001]
    
    
    ✅ ALL THRESHOLDS PASSED

      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:148:15)

  console.log
    📁 Report saved: /home/runner/work/engram/engram/test/benchmark/results/benchmark-2026-08-24T06-13-47-689Z.json

      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:152:15)

  console.warn
    ⚠️  2 zero-hit queries (tracked, not blocking): semantic_011, cross_001

    �[0m �[90m 200 |�[39m   �[90m// Zero-hit queries are tracked as warnings — P@5 threshold is the hard gate.�[39m
     �[90m 201 |�[39m   �[36mif�[39m (zeroHitQueries�[33m.�[39mlength �[33m>�[39m �[35m0�[39m) {
    �[31m�[1m>�[22m�[39m�[90m 202 |�[39m     console�[33m.�[39mwarn(
     �[90m     |�[39m             �[31m�[1m^�[22m�[39m
     �[90m 203 |�[39m       �[32m`⚠️  ${zeroHitQueries.length} zero-hit queries (tracked, not blocking): ${zeroHitQueries.map((q) => q.queryId).join(', ')}`�[39m�[33m,�[39m
     �[90m 204 |�[39m     )�[33m;�[39m
     �[90m 205 |�[39m   }�[0m

      at checkThresholds (benchmark/scoring.ts:202:13)
      at buildReport (benchmark/scoring.ts:183:23)
      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:173:33)

  console.warn
    ⚠️  Zero-hit queries (2): semantic_011, cross_001

    �[0m �[90m 197 |�[39m         �[36mif�[39m (zeroHitQueries�[33m.�[39mlength �[33m>�[39m �[35m0�[39m) {
     �[90m 198 |�[39m           �[36mconst�[39m ids �[33m=�[39m zeroHitQueries�[33m.�[39mmap((q) �[33m=>�[39m q�[33m.�[39mqueryId)�[33m.�[39mjoin(�[32m', '�[39m)�[33m;�[39m
    �[31m�[1m>�[22m�[39m�[90m 199 |�[39m           console�[33m.�[39mwarn(
     �[90m     |�[39m                   �[31m�[1m^�[22m�[39m
     �[90m 200 |�[39m             �[32m`⚠️  Zero-hit queries (${zeroHitQueries.length}): ${ids}`�[39m�[33m,�[39m
     �[90m 201 |�[39m           )�[33m;�[39m
     �[90m 202 |�[39m         }�[0m

      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:199:19)

PASS test/benchmark/recall-benchmark.e2e-spec.ts (158.019 s)
  Recall Benchmark
    Category: semantic
      ✓ [semantic_001] What kind of coffee do I like? (216 ms)
      ✓ [semantic_002] Tell me about my morning routine (148 ms)
      ✓ [semantic_003] What tech stack am I using? (92 ms)
      ✓ [semantic_004] coffee preferences (1251 ms)
      ✓ [semantic_005] What books have I been reading? (93 ms)
      ✓ [semantic_006] favorite dinner recipe (77 ms)
      ✓ [semantic_007] house savings goal (73 ms)
      ✓ [semantic_008] What framework am I using for the frontend? (1354 ms)
      ✓ [semantic_009] flight seat preference (103 ms)
      ✓ [semantic_010] ensemble search architecture decision (70 ms)
      ✓ [semantic_011] What coffee roast do I prefer? (84 ms)
      ✓ [negative_001] quantum physics black holes dark matter (89 ms)
      ✓ [negative_002] ancient Egyptian hieroglyphics translation (1281 ms)
      ✓ [minimal_001] pizza preference (741 ms)
    Category: emotional
      ✓ [emotional_001] What makes me happy? (96 ms)
      ✓ [emotional_002] times I felt sad or grieving (87 ms)
      ✓ [emotional_003] when I felt stressed or overwhelmed (76 ms)
      ✓ [emotional_004] What am I worried about? (78 ms)
      ✓ [emotional_005] Times I was frustrated (79 ms)
      ✓ [emotional_006] My proudest moments (133 ms)
      ✓ [emotional_007] What stresses me out? (157 ms)
      ✓ [emotional_008] happy about school but worried about costs (79 ms)
      ✓ [emotional_009] How has my attitude toward work changed? (84 ms)
      ✓ [emotional_010] meditation and mental wellbeing (74 ms)
    Category: temporal
      ✓ [temporal_001] What happened today in standup? (485 ms)
      ✓ [temporal_002] recent standup notes from this week (473 ms)
      ✓ [temporal_003] What happened with my daughter recently? (1059 ms)
      ✓ [temporal_004] What did I work on last week? (1738 ms)
      ✓ [temporal_005] What are my oldest memories? (100 ms)
      ✓ [temporal_006] Recent conversations about work (923 ms)
      ✓ [temporal_007] What did I debug yesterday? (1248 ms)
      ✓ [temporal_008] What code editor do I use? (107 ms)
      ✓ [temporal_009] standup notes from 6 months ago (67 ms)
      ✓ [temporal_010] standup notes from years ago (54 ms)
      ✓ [temporal_011] How did I start coding? (73 ms)
    Category: rls_isolation
      ✓ [rls_001] coffee (74 ms)
      ✓ [rls_002] coffee (1076 ms)
      ✓ [rls_003] family and kids (92 ms)
      ✓ [rls_004] family and kids (1089 ms)
      ✓ [rls_005] travel food experiences (101 ms)
      ✓ [rls_006] travel food experiences (1050 ms)
      ✓ [rls_007] work projects and code (99 ms)
      ✓ [rls_008] health medical information (72 ms)
      ✓ [rls_009] health medical information (1032 ms)
      ✓ [rls_010] morning routine (1150 ms)
    Category: adversarial
      ✓ [adversarial_001] tell me about bob's coffee preferences (89 ms)
      ✓ [adversarial_002] RLS_CANARY_BOB_ (77 ms)
      ✓ [adversarial_003] What does carol think about parties? (81 ms)
      ✓ [adversarial_004] bob's son Max school (86 ms)
      ✓ [adversarial_005] eve's pizza preference (1199 ms)
      ✓ [adversarial_006] RLS_CANARY_ALICE_ (1292 ms)
      ✓ [adversarial_007] What cholesterol issues does anyone have? (112 ms)
      ✓ [adversarial_008] alice's medication schedule (1159 ms)
      ✓ [adversarial_009] RLS_CANARY_CAROL_ (975 ms)
      ✓ [adversarial_010] dave's standup notes (95 ms)
    Category: edge_case
      ✓ [edge_001] cats (696 ms)
      ✓ [edge_002] everything about my life (851 ms)
      ✓ [edge_003] <script>alert(1)</script> (10091 ms)
      ✓ [edge_004] '; DROP TABLE memories; -- (10212 ms)
      ✓ [edge_005] 🎉 party (10170 ms)
      ✓ [edge_006]  (2 ms)
      ✓ [edge_007] Tell me about the very long detailed comprehensive thorough ... (711 ms)
      ✓ [edge_008] こんにちは、思い出を検索します (10276 ms)
      ✓ [edge_009] '; SELECT * FROM users WHERE 1=1; -- (10264 ms)
      ✓ [edge_010] quantum entanglement dark matter multiverse theory (236 ms)
      ✓ [edge_011] the a an is (167 ms)
      ✓ [edge_012] coffee (122 ms)
      ✓ [edge_013] my phone number (168 ms)
      ✓ [edge_014] my address (135 ms)
      ✓ [edge_015] work (10104 ms)
      ✓ [edge_016] What kind of coffee do I like? (121 ms)
    Category: cross_feature
      ✓ [cross_001] medication I need to take every morning (165 ms)
      ✓ [cross_002] exercise and fitness activities (130 ms)
      ✓ [cross_003] What are we saving money for? (145 ms)
      ✓ [cross_004] kids school and daycare (245 ms)
      ✓ [cross_005] kids school and daycare (9795 ms)
      ✓ [cross_006] Who am I and what do I do? (117 ms)
      ✓ [cross_007] deployment rules and constraints (79 ms)
      ✓ [cross_008] patterns noticed about my work habits (76 ms)
      ✓ [cross_009] grocery shopping list (827 ms)
      ✓ [cross_010] TypeScript learning (806 ms)
    Summary
      ✓ should generate and save benchmark report (64 ms)
      ✓ should have zero isolation failures (2 ms)
      ✓ should meet precision thresholds (with real embeddings) (31 ms)

Test Suites: 1 passed, 1 total
Tests:       84 passed, 84 total
Snapshots:   0 total
Time:        158.274 s
Ran all test suites matching recall-benchmark.e2e-spec.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

Full output

> @openengram/engram@1.5.0 benchmark /home/runner/work/engram/engram
> jest --config ./test/jest-e2e.json --testPathPatterns=recall-benchmark\.e2e-spec --runInBand --forceExit

[06:12:07.156] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 180
[06:12:07.307] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 137
[06:12:07.400] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 87
[06:12:08.650] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1245
[06:12:08.744] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 88
[06:12:08.817] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 69
[06:12:08.895] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 69
[06:12:10.249] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1350
[06:12:10.354] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 99
[06:12:10.424] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 66
[06:12:10.508] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 79
[06:12:10.599] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 86
[06:12:11.879] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1276
[06:12:12.621] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 736
[06:12:12.716] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 90
[06:12:12.806] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 83
[06:12:12.881] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 70
[06:12:12.960] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 74
[06:12:13.040] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 76
[06:12:13.173] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 129
[06:12:13.330] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 152
[06:12:13.410] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 76
[06:12:13.495] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 81
[06:12:13.569] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 71
[06:12:14.052] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-dave-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 479
[06:12:14.525] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-dave-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 467
[06:12:15.586] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1055
[06:12:17.324] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1734
[06:12:17.425] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 97
[06:12:18.348] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 919
[06:12:19.597] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1244
[06:12:19.704] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 101
[06:12:19.769] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-dave-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 61
[06:12:19.826] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-dave-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 50
[06:12:19.899] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 69
[06:12:19.973] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 67
[06:12:21.049] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1073
[06:12:21.142] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 89
[06:12:22.231] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1086
[06:12:22.332] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 96
[06:12:23.383] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1047
[06:12:23.482] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 95
[06:12:23.554] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 70
[06:12:24.586] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1029
[06:12:25.737] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1148
[06:12:25.827] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 86
[06:12:25.901] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 72
[06:12:25.985] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 77
[06:12:26.071] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 82
[06:12:27.271] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1196
[06:12:28.563] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1288
[06:12:28.675] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 107
[06:12:29.834] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 1156
[06:12:30.809] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 971
[06:12:30.905] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 92
[06:12:31.601] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 693
[06:12:32.453] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 846
[06:12:42.542] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-carol-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 10084
[06:12:52.754] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-carol-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 10201
[06:13:02.926] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-carol-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 10159
[06:13:03.641] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 706
[06:13:13.916] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-carol-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 10269
[06:13:24.180] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-carol-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 10254
[06:13:24.417] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 226
[06:13:24.585] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 160
[06:13:24.708] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 118
[06:13:24.876] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 160
[06:13:25.009] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 127
[06:13:35.112] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 10097
[06:13:35.237] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 117
[06:13:35.403] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 162
[06:13:35.532] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 124
[06:13:35.677] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 141
[06:13:35.923] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 239
[06:13:45.720] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-bob-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 9792
[06:13:45.837] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 114
[06:13:45.916] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 75
[06:13:45.992] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-alice-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 72
[06:13:46.819] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 823
[06:13:47.625] �[32mINFO�[39m (3432): �[36mrequest completed�[39m
    �[35mreq�[39m: {
      "method": "POST",
      "url": "/v1/memories/query"
    }
    �[35maccountId�[39m: "eng_test..."
    �[35muserId�[39m: "test-corpus-user-eve-1787551879043"
    �[35mres�[39m: {
      "statusCode": 201
    }
    �[35mresponseTime�[39m: 802
  console.warn
    ⚠️  2 zero-hit queries (tracked, not blocking): semantic_011, cross_001

    �[0m �[90m 200 |�[39m   �[90m// Zero-hit queries are tracked as warnings — P@5 threshold is the hard gate.�[39m
     �[90m 201 |�[39m   �[36mif�[39m (zeroHitQueries�[33m.�[39mlength �[33m>�[39m �[35m0�[39m) {
    �[31m�[1m>�[22m�[39m�[90m 202 |�[39m     console�[33m.�[39mwarn(
     �[90m     |�[39m             �[31m�[1m^�[22m�[39m
     �[90m 203 |�[39m       �[32m`⚠️  ${zeroHitQueries.length} zero-hit queries (tracked, not blocking): ${zeroHitQueries.map((q) => q.queryId).join(', ')}`�[39m�[33m,�[39m
     �[90m 204 |�[39m     )�[33m;�[39m
     �[90m 205 |�[39m   }�[0m

      at checkThresholds (benchmark/scoring.ts:202:13)
      at buildReport (benchmark/scoring.ts:183:23)
      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:145:33)

  console.log
    
    ╔══════════════════════════════════════════════════════════════╗
    ║              ENGRAM RECALL BENCHMARK REPORT                 ║
    ╚══════════════════════════════════════════════════════════════╝
    
      Git SHA:    835cdc2
      Branch:     HEAD
      Timestamp:  2026-08-24T06:13:47.657Z
    
    ┌─────────────────────────────────────────────────────────────┐
    │  OVERALL SCORES                                            │
    ├─────────────────────────────────────────────────────────────┤
    │  Total Queries:   81                                        │
    │  Passed:          79 / 81 (97.5%)                                        
    │  Precision@5:     96.9%  ✅  (threshold: 95.0%)
    │  Recall@20:       97.5%
    │  MRR:             0.8713
    │  Isolation:       100.0%  ✅  (threshold: 100%)
    └─────────────────────────────────────────────────────────────┘
    
    ┌──────────────────┬───────┬────────┬──────────┬──────────┬──────────┬───────────┐
    │ Category         │ Total │ Passed │ Prec@5   │ Rec@20   │ MRR      │ Isolation │
    ├──────────────────┼───────┼────────┼──────────┼──────────┼──────────┼───────────┤
    │ adversarial      │ 10    │ 10     │ 100.0%   │ 100.0%   │ 1.0000   │ 100.0%    │
    │ cross_feature    │ 10    │ 9      │ 90.0%    │ 100.0%   │ 0.8667   │ 100.0%    │
    │ edge_case        │ 16    │ 16     │ 100.0%   │ 100.0%   │ 0.9245   │ 100.0%    │
    │ emotional        │ 10    │ 10     │ 95.0%    │ 100.0%   │ 0.6405   │ 100.0%    │
    │ rls_isolation    │ 10    │ 10     │ 100.0%   │ 100.0%   │ 0.9500   │ 100.0%    │
    │ semantic         │ 14    │ 13     │ 92.9%    │ 100.0%   │ 0.8185   │ 100.0%    │
    │ temporal         │ 11    │ 11     │ 100.0%   │ 81.8%    │ 0.8864   │ 100.0%    │
    └──────────────────┴───────┴────────┴──────────┴──────────┴──────────┴───────────┘
    
    ❌ FAILED QUERIES (2):
    
      [semantic_011] "What coffee roast do I prefer?" (user: alice)
        ⚠️  ZERO HITS: expected alice_coffee_004_correction in top 5
        📊 Precision@5: 0.0% — hit: [], missed: [alice_coffee_004_correction]
        📋 Actual top 5: [alice_low_importance_001, alice_coffee_002, alice_coffee_001, alice_calm_001, alice_travel_002]
    
      [cross_001] "medication I need to take every morning" (user: alice)
        ⚠️  ZERO HITS: expected alice_health_001 in top 5
        📊 Precision@5: 0.0% — hit: [], missed: [alice_health_001]
        📋 Actual top 5: [alice_calm_001, alice_coffee_001, alice_coffee_002, alice_cooking_001, alice_emotion_change_001]
    
    
    ✅ ALL THRESHOLDS PASSED

      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:148:15)

  console.log
    📁 Report saved: /home/runner/work/engram/engram/test/benchmark/results/benchmark-2026-08-24T06-13-47-689Z.json

      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:152:15)

  console.warn
    ⚠️  2 zero-hit queries (tracked, not blocking): semantic_011, cross_001

    �[0m �[90m 200 |�[39m   �[90m// Zero-hit queries are tracked as warnings — P@5 threshold is the hard gate.�[39m
     �[90m 201 |�[39m   �[36mif�[39m (zeroHitQueries�[33m.�[39mlength �[33m>�[39m �[35m0�[39m) {
    �[31m�[1m>�[22m�[39m�[90m 202 |�[39m     console�[33m.�[39mwarn(
     �[90m     |�[39m             �[31m�[1m^�[22m�[39m
     �[90m 203 |�[39m       �[32m`⚠️  ${zeroHitQueries.length} zero-hit queries (tracked, not blocking): ${zeroHitQueries.map((q) => q.queryId).join(', ')}`�[39m�[33m,�[39m
     �[90m 204 |�[39m     )�[33m;�[39m
     �[90m 205 |�[39m   }�[0m

      at checkThresholds (benchmark/scoring.ts:202:13)
      at buildReport (benchmark/scoring.ts:183:23)
      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:173:33)

  console.warn
    ⚠️  Zero-hit queries (2): semantic_011, cross_001

    �[0m �[90m 197 |�[39m         �[36mif�[39m (zeroHitQueries�[33m.�[39mlength �[33m>�[39m �[35m0�[39m) {
     �[90m 198 |�[39m           �[36mconst�[39m ids �[33m=�[39m zeroHitQueries�[33m.�[39mmap((q) �[33m=>�[39m q�[33m.�[39mqueryId)�[33m.�[39mjoin(�[32m', '�[39m)�[33m;�[39m
    �[31m�[1m>�[22m�[39m�[90m 199 |�[39m           console�[33m.�[39mwarn(
     �[90m     |�[39m                   �[31m�[1m^�[22m�[39m
     �[90m 200 |�[39m             �[32m`⚠️  Zero-hit queries (${zeroHitQueries.length}): ${ids}`�[39m�[33m,�[39m
     �[90m 201 |�[39m           )�[33m;�[39m
     �[90m 202 |�[39m         }�[0m

      at Object.<anonymous> (benchmark/recall-benchmark.e2e-spec.ts:199:19)

PASS test/benchmark/recall-benchmark.e2e-spec.ts (158.019 s)
  Recall Benchmark
    Category: semantic
      ✓ [semantic_001] What kind of coffee do I like? (216 ms)
      ✓ [semantic_002] Tell me about my morning routine (148 ms)
      ✓ [semantic_003] What tech stack am I using? (92 ms)
      ✓ [semantic_004] coffee preferences (1251 ms)
      ✓ [semantic_005] What books have I been reading? (93 ms)
      ✓ [semantic_006] favorite dinner recipe (77 ms)
      ✓ [semantic_007] house savings goal (73 ms)
      ✓ [semantic_008] What framework am I using for the frontend? (1354 ms)
      ✓ [semantic_009] flight seat preference (103 ms)
      ✓ [semantic_010] ensemble search architecture decision (70 ms)
      ✓ [semantic_011] What coffee roast do I prefer? (84 ms)
      ✓ [negative_001] quantum physics black holes dark matter (89 ms)
      ✓ [negative_002] ancient Egyptian hieroglyphics translation (1281 ms)
      ✓ [minimal_001] pizza preference (741 ms)
    Category: emotional
      ✓ [emotional_001] What makes me happy? (96 ms)
      ✓ [emotional_002] times I felt sad or grieving (87 ms)
      ✓ [emotional_003] when I felt stressed or overwhelmed (76 ms)
      ✓ [emotional_004] What am I worried about? (78 ms)
      ✓ [emotional_005] Times I was frustrated (79 ms)
      ✓ [emotional_006] My proudest moments (133 ms)
      ✓ [emotional_007] What stresses me out? (157 ms)
      ✓ [emotional_008] happy about school but worried about costs (79 ms)
      ✓ [emotional_009] How has my attitude toward work changed? (84 ms)
      ✓ [emotional_010] meditation and mental wellbeing (74 ms)
    Category: temporal
      ✓ [temporal_001] What happened today in standup? (485 ms)
      ✓ [temporal_002] recent standup notes from this week (473 ms)
      ✓ [temporal_003] What happened with my daughter recently? (1059 ms)
      ✓ [temporal_004] What did I work on last week? (1738 ms)
      ✓ [temporal_005] What are my oldest memories? (100 ms)
      ✓ [temporal_006] Recent conversations about work (923 ms)
      ✓ [temporal_007] What did I debug yesterday? (1248 ms)
      ✓ [temporal_008] What code editor do I use? (107 ms)
      ✓ [temporal_009] standup notes from 6 months ago (67 ms)
      ✓ [temporal_010] standup notes from years ago (54 ms)
      ✓ [temporal_011] How did I start coding? (73 ms)
    Category: rls_isolation
      ✓ [rls_001] coffee (74 ms)
      ✓ [rls_002] coffee (1076 ms)
      ✓ [rls_003] family and kids (92 ms)
      ✓ [rls_004] family and kids (1089 ms)
      ✓ [rls_005] travel food experiences (101 ms)
      ✓ [rls_006] travel food experiences (1050 ms)
      ✓ [rls_007] work projects and code (99 ms)
      ✓ [rls_008] health medical information (72 ms)
      ✓ [rls_009] health medical information (1032 ms)
      ✓ [rls_010] morning routine (1150 ms)
    Category: adversarial
      ✓ [adversarial_001] tell me about bob's coffee preferences (89 ms)
      ✓ [adversarial_002] RLS_CANARY_BOB_ (77 ms)
      ✓ [adversarial_003] What does carol think about parties? (81 ms)
      ✓ [adversarial_004] bob's son Max school (86 ms)
      ✓ [adversarial_005] eve's pizza preference (1199 ms)
      ✓ [adversarial_006] RLS_CANARY_ALICE_ (1292 ms)
      ✓ [adversarial_007] What cholesterol issues does anyone have? (112 ms)
      ✓ [adversarial_008] alice's medication schedule (1159 ms)
      ✓ [adversarial_009] RLS_CANARY_CAROL_ (975 ms)
      ✓ [adversarial_010] dave's standup notes (95 ms)
    Category: edge_case
      ✓ [edge_001] cats (696 ms)
      ✓ [edge_002] everything about my life (851 ms)
      ✓ [edge_003] <script>alert(1)</script> (10091 ms)
      ✓ [edge_004] '; DROP TABLE memories; -- (10212 ms)
      ✓ [edge_005] 🎉 party (10170 ms)
      ✓ [edge_006]  (2 ms)
      ✓ [edge_007] Tell me about the very long detailed comprehensive thorough ... (711 ms)
      ✓ [edge_008] こんにちは、思い出を検索します (10276 ms)
      ✓ [edge_009] '; SELECT * FROM users WHERE 1=1; -- (10264 ms)
      ✓ [edge_010] quantum entanglement dark matter multiverse theory (236 ms)
      ✓ [edge_011] the a an is (167 ms)
      ✓ [edge_012] coffee (122 ms)
      ✓ [edge_013] my phone number (168 ms)
      ✓ [edge_014] my address (135 ms)
      ✓ [edge_015] work (10104 ms)
      ✓ [edge_016] What kind of coffee do I like? (121 ms)
    Category: cross_feature
      ✓ [cross_001] medication I need to take every morning (165 ms)
      ✓ [cross_002] exercise and fitness activities (130 ms)
      ✓ [cross_003] What are we saving money for? (145 ms)
      ✓ [cross_004] kids school and daycare (245 ms)
      ✓ [cross_005] kids school and daycare (9795 ms)
      ✓ [cross_006] Who am I and what do I do? (117 ms)
      ✓ [cross_007] deployment rules and constraints (79 ms)
      ✓ [cross_008] patterns noticed about my work habits (76 ms)
      ✓ [cross_009] grocery shopping list (827 ms)
      ✓ [cross_010] TypeScript learning (806 ms)
    Summary
      ✓ should generate and save benchmark report (64 ms)
      ✓ should have zero isolation failures (2 ms)
      ✓ should meet precision thresholds (with real embeddings) (31 ms)

Test Suites: 1 passed, 1 total
Tests:       84 passed, 84 total
Snapshots:   0 total
Time:        158.274 s
Ran all test suites matching recall-benchmark.e2e-spec.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

Commit: 835cdc2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants