perf: remember(force=True) should be a raw write — currently 5x slower than recall_index #18

Closed
opened 2026-04-09 07:08:58 +00:00 by ric_harvey · 3 comments
ric_harvey commented 2026-04-09 07:08:58 +00:00 (Migrated from codeberg.org)

Problem

remember() with force=True takes ~1.7s per call on v5.1.1-beta, compared to recall_index() at ~0.34s. Both operations embed the content, so the extra ~1.3s is overhead in the write path that force=True should be bypassing.

Timing evidence

version()      (no embed):        0.009s  — pure MCP overhead
recall_index() (embed + search):  0.342s  — acceptable
remember()     (embed + write):   1.697s  — 5x slower than recall

Model: all-MiniLM-L6-v2, running on localhost, Docker on Mac.

Expected behaviour

force=True is documented as "skip duplicate check". It should be a fast raw write: embed → store → done. At 0.34s for an embed+search, a write should be no more than ~0.4s.

Impact

This makes bulk ingestion impractical. The LongMemEval benchmark stores ~250 turn-pair chunks per item. At 1.7s each that's ~425s per item, making a 500-item benchmark run take ~59 hours. At the expected ~0.4s it would take ~3.5 hours.

Suspected cause

v5 may be running contradiction detection, lifecycle processing, or other enrichment even when force=True. These should be skipped entirely in force mode — the whole point is a raw bypass write.

Suggested fix

In force mode, the write path should be:

  1. Embed content
  2. Write to Valkey
  3. Return key

Nothing else. Contradiction detection, dedup scoring, lifecycle hooks — all skipped.

## Problem `remember()` with `force=True` takes ~1.7s per call on v5.1.1-beta, compared to `recall_index()` at ~0.34s. Both operations embed the content, so the extra ~1.3s is overhead in the write path that `force=True` should be bypassing. ## Timing evidence ``` version() (no embed): 0.009s — pure MCP overhead recall_index() (embed + search): 0.342s — acceptable remember() (embed + write): 1.697s — 5x slower than recall ``` Model: `all-MiniLM-L6-v2`, running on localhost, Docker on Mac. ## Expected behaviour `force=True` is documented as "skip duplicate check". It should be a fast raw write: embed → store → done. At 0.34s for an embed+search, a write should be no more than ~0.4s. ## Impact This makes bulk ingestion impractical. The LongMemEval benchmark stores ~250 turn-pair chunks per item. At 1.7s each that's ~425s per item, making a 500-item benchmark run take ~59 hours. At the expected ~0.4s it would take ~3.5 hours. ## Suspected cause v5 may be running contradiction detection, lifecycle processing, or other enrichment even when `force=True`. These should be skipped entirely in force mode — the whole point is a raw bypass write. ## Suggested fix In force mode, the write path should be: 1. Embed content 2. Write to Valkey 3. Return key Nothing else. Contradiction detection, dedup scoring, lifecycle hooks — all skipped.
ric_harvey commented 2026-04-09 07:13:01 +00:00 (Migrated from codeberg.org)

Root cause identified

In tools/core.py, force=True only skips the dedup check (line 215) but the contradiction check still runs unconditionally (line 231-238):

if not force:
    # dedup check skipped ✓
    
# Tier 1 contradiction check (fast heuristic) — runs regardless of force
contradiction = check_contradiction_heuristic(...)

The contradiction check performs a similarity search against all existing memories, which costs ~1.3s on top of the ~0.34s embed. This is why remember(force=True) costs 1.7s vs recall_index() at 0.34s.

Fix

force=True should skip the contradiction check too:

if not force:
    # dedup check
    ...

contradiction_warning = None
if not force:  # add this guard
    contradiction = check_contradiction_heuristic(...)
    if contradiction is not None:
        contradiction_warning = {...}

This would bring remember(force=True) down to ~0.4s (embed + write only), making bulk ingestion viable.

## Root cause identified In `tools/core.py`, `force=True` only skips the dedup check (line 215) but the contradiction check still runs unconditionally (line 231-238): ```python if not force: # dedup check skipped ✓ # Tier 1 contradiction check (fast heuristic) — runs regardless of force contradiction = check_contradiction_heuristic(...) ``` The contradiction check performs a similarity search against all existing memories, which costs ~1.3s on top of the ~0.34s embed. This is why `remember(force=True)` costs 1.7s vs `recall_index()` at 0.34s. ## Fix `force=True` should skip the contradiction check too: ```python if not force: # dedup check ... contradiction_warning = None if not force: # add this guard contradiction = check_contradiction_heuristic(...) if contradiction is not None: contradiction_warning = {...} ``` This would bring `remember(force=True)` down to ~0.4s (embed + write only), making bulk ingestion viable.
ric_harvey commented 2026-04-09 07:48:38 +00:00 (Migrated from codeberg.org)

Additional root cause — v5 extract_facts()

In v5, extract_facts(content) at line 174 also fires unconditionally regardless of force=True. This is a Haiku API call costing ~1.2s per remember() call — the dominant bottleneck.

Combined with the contradiction check, force=True is actually running:

  1. extract_facts() — Haiku API call → ~1.2s
  2. Embedding → ~0.35s
  3. check_contradiction_heuristic() — similarity search → ~0.15s
  4. Valkey write → negligible

Total: ~1.7s vs the expected ~0.4s for a raw write.

Full fix required

Both need guarding in tools/core.py:

# Line 174 — guard extract_facts
if not force:
    extract_facts(content)

# Line 231 — guard contradiction check  
contradiction_warning = None
if not force:
    contradiction = check_contradiction_heuristic(...)
    if contradiction is not None:
        contradiction_warning = {...}

With both guards in place, force=True becomes: embed → write → return key (~0.4s).

## Additional root cause — v5 extract_facts() In v5, `extract_facts(content)` at line 174 also fires unconditionally regardless of `force=True`. This is a Haiku API call costing ~1.2s per `remember()` call — the dominant bottleneck. Combined with the contradiction check, `force=True` is actually running: 1. `extract_facts()` — Haiku API call → ~1.2s 2. Embedding → ~0.35s 3. `check_contradiction_heuristic()` — similarity search → ~0.15s 4. Valkey write → negligible Total: ~1.7s vs the expected ~0.4s for a raw write. ## Full fix required Both need guarding in `tools/core.py`: ```python # Line 174 — guard extract_facts if not force: extract_facts(content) # Line 231 — guard contradiction check contradiction_warning = None if not force: contradiction = check_contradiction_heuristic(...) if contradiction is not None: contradiction_warning = {...} ``` With both guards in place, `force=True` becomes: embed → write → return key (~0.4s).
ric_harvey commented 2026-07-08 11:11:37 +00:00 (Migrated from codeberg.org)

Closing — shipped in v5.1.2. remember(force=True) is now a genuine raw bypass write: both the semantic duplicate check and the Tier 1 contradiction heuristic (the ~1.3s vector-search-plus-negation-scan that was running unconditionally) are skipped, and since v5.2.0 background enrichment is skipped under force=True as well. Measured improvement at the time: ~1.7s → ~0.4s per call, taking the projected 500-item LongMemEval bulk ingestion from ~59 hours to ~3.5 hours.

Closing — shipped in v5.1.2. `remember(force=True)` is now a genuine raw bypass write: both the semantic duplicate check and the Tier 1 contradiction heuristic (the ~1.3s vector-search-plus-negation-scan that was running unconditionally) are skipped, and since v5.2.0 background enrichment is skipped under `force=True` as well. Measured improvement at the time: ~1.7s → ~0.4s per call, taking the projected 500-item LongMemEval bulk ingestion from ~59 hours to ~3.5 hours.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ric/omnimem#18
No description provided.