Skip to content

perf(ooda): bulk graph-adjacency index — prepare-context from ~11 min to seconds (#40) - #3046

Merged
rysweet merged 1 commit into
mainfrom
feat/issue-40-bulk-graph-adjacency
Jul 8, 2026
Merged

perf(ooda): bulk graph-adjacency index — prepare-context from ~11 min to seconds (#40)#3046
rysweet merged 1 commit into
mainfrom
feat/issue-40-bulk-graph-adjacency

Conversation

@rysweet

@rysweet rysweet commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Problem

The OODA daemon's per-cycle prepare-context step
(preparation_memory_operations_with_active_slugs_phased, src/memory_consolidation/mod.rs)
spun for ~11 min at ~300% CPU every cycle with no child processes and no log
output — the substance behind the operator's "the memory graph never loads"
complaint. The graph/facts do load; the read was pathologically slow, so each
OODA cycle took ~15–18 min and the dashboard/memory views felt stuck. (#40)

Confirmed not a regression: the prior deploy showed the identical ~10.5-min
gap. It was a stable, pre-existing performance pathology.

Root cause (profiled, engine-side)

The cognitive memory holds ~7,590 facts; the result is tiny (10 facts, 5
procedures, 5 episodes), so the cost was in scanning/ranking, not output size.
Ranked declarative/episodic recall computed the graph-proximity score by
running a bounded BFS from every candidate node — one query_neighbors per
node per traversal edge type per hop, an N+1 fan-out of ~3N lock-serialized
Cypher scans per recall (~23k queries at ~7,590 facts). Per binding policy the
engine fix lives in the memory library.

What this PR does (Simard side)

The substantive fix shipped in
amplihack-memory-lib PR #126
(bulk graph-adjacency index: one bulk edge scan per type, byte-identical
ranking). This PR consumes it:

  • Pin bump. amplihack-memory git rev 901f63a → 72c5ea1 (the PR fix: remaining review findings — path traversal, dead field, env caching #126
    squash-merge commit) + rebuilt Cargo.lock. Pure performance refactor
    upstream; no store-format change (still v41). The same relevant
    facts/triggers/procedures/episodes surface with recency/phase weighting intact.
  • Observability. Per-sub-step tracing spans (counts/timings only — never
    memory content, log-hygiene SR-9) around the four expensive prepare-context
    sub-ops (ranked declarative recall, check_triggers, procedure recall, ranked
    episodic recall) plus the whole call, so per-sub-step wall-clock is
    attributable from a live log without a profiler. The engine emits graph_path
    (indexed|legacy); Simard emits the timings. The operator-facing
    "Prepared context: N facts, …" working-memory summary is preserved verbatim.
  • Docs. New cognitive-memory-graph-adjacency-index.md reference + mark the
    ranked-recall graph signal as shipped.

Regression guard

memory_consolidation::tests_prepare_context_perf (new) pins the orchestration
invariant that prepare-context issues a small, CONSTANT number of expensive
recall calls — O(objective fragments), never O(fact-store size) — via
deterministic call-count assertions (no wall-clock timeout, per policy), and
that the "Prepared context: …" summary keeps reaching working memory. It fails
the moment a per-fact fan-out is reintroduced at the orchestration layer, one
layer above the engine fix. A counting CognitiveMemoryOps mock varies result
volume (up to 5,000 facts) independently of call count to prove volume never
multiplies calls.

Profiling evidence (before / after)

Engine harness on the real persistent (LadybugDB) backend, 1,500 facts, 1 hop:

path per-node query_neighbors bulk scans recall wall-clock
legacy 3,000 0 14,688 ms
indexed 0 2 286 ms

51.3× faster; store round-trips 3,000 → 2 and now constant in fact count.
Each legacy call is a lock-serialized Cypher scan — the cost that reaches ~11 min
at ~7,590 facts across multi-hop BFS and per-fragment recalls. Prepare-context
now completes in well under 30 s (single-digit seconds), restoring OODA cadence.

Validation

Local: cargo fmt --check, cargo clippy --all-targets --all-features -D warnings, cargo test --release --lib (cognitive_memory|…|memory_consolidation)
all green (pre-push gate). No wall-clock timeouts added; no fallbacks/silent
degradation.

Depends on: rysweet/amplihack-memory-lib#126 (merged).
Closes #40

…min to seconds (#40)

The OODA daemon's per-cycle prepare-context step
(`preparation_memory_operations_with_active_slugs_phased`) spun for ~11 min at
~300% CPU every cycle with no child processes and no log output — the substance
behind the operator's "the memory graph never loads" complaint. The graph loads;
the read was pathologically slow, so each cycle took ~15–18 min.

Root cause was engine-side: ranked declarative/episodic recall computed the
graph-proximity term with a per-node `query_neighbors` BFS — an N+1 fan-out of
~3N lock-serialized Cypher scans per recall (~23k queries at ~7,590 facts). Fixed
in amplihack-memory-lib PR #126 (bulk graph-adjacency index: one bulk edge scan
per type, byte-identical ranking). This PR consumes that fix:

- Bump the pinned `amplihack-memory` git rev to the PR #126 squash-merge commit
  (72c5ea1) and rebuild `Cargo.lock`. Pure performance refactor upstream; no
  store-format change (still v41), same relevant facts/triggers/procedures/
  episodes surface with recency/phase weighting intact.
- Add per-sub-step `tracing` spans (counts/timings only — never memory content)
  around the four prepare-context sub-ops (ranked declarative recall,
  check_triggers, procedure recall, ranked episodic recall) plus the whole call,
  so per-sub-step wall-clock is attributable from a live log without a profiler.
  The engine emits `graph_path` (indexed|legacy); Simard emits the timings.
- Preserve the operator-facing "Prepared context: N facts, …" working-memory
  summary verbatim.

Regression guard: `memory_consolidation::tests_prepare_context_perf` pins the
orchestration invariant that prepare-context issues a small, CONSTANT number of
expensive recall calls — O(objective fragments), never O(fact-store size) — via
deterministic call-count assertions (no wall-clock timeout), and that the
"Prepared context: …" summary keeps reaching working memory. This fails the
moment a per-fact fan-out is reintroduced at the orchestration layer, one layer
above the engine fix.

Engine profiling (persistent/lbug, 1,500 facts, 1 hop): legacy 3,000 per-node
Cypher scans / 14,688 ms -> indexed 0 per-node scans, 2 bulk scans / 286 ms
(51.3x); round-trips now constant in fact count.

Docs: add the bulk graph-adjacency index reference and mark the ranked-recall
graph signal as shipped.

Closes #40

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rysweet
rysweet force-pushed the feat/issue-40-bulk-graph-adjacency branch from 1cfdd68 to 8826efb Compare July 8, 2026 03:20
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

📊 Coverage Summary

Generated by cargo llvm-cov --workspace --summary-only (nightly, excluding test files)

Module Lines Covered Coverage
Total 164933 138749 84.1%

Coverage data from CI run. Test files matching tests?/ are excluded from line counts.

@rysweet
rysweet merged commit 1f0cecb into main Jul 8, 2026
17 checks passed
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.

Expand terminal-shell validation regression coverage

1 participant