How Ormah Works
Self-Healing
Content verified · 2026-04-07
Ormah uses APScheduler to run maintenance tasks that keep the derived graph and ranking state healthy over time.
Scheduler
Code: src/ormah/background/scheduler.py
The scheduler is a BackgroundScheduler with a 120 second misfire grace window.
Registered jobs:
| Job | Default interval | LLM required? | Purpose |
|---|---|---|---|
auto_linker |
24h | yes | classify and create semantic edges |
decay_manager |
24h | no | demote stale working memories |
conflict_detector |
24h | yes | detect contradictions / evolutions |
duplicate_merger |
24h | yes | detect near-duplicates |
auto_cluster |
60m | no | assign spaces to unassigned nodes |
consolidator |
24h | yes | synthesize working-memory clusters |
importance_scorer |
120m | no | recompute importance |
index_updater |
1m | no | incremental derived-index sync |
Sleep-Cycle Order
When /admin/tasks/run-all is used, tasks run in this order:
importance_scorer
-> index_updater
-> duplicate_merger
-> conflict_detector
-> auto_linker
-> auto_cluster
-> consolidator
-> decay_manager
Auto-Linker
Auto-linker finds candidate pairs through embeddings, applies a cross-space penalty, and asks the LLM to classify the relationship.
Possible outcomes include:
supportspart_ofdepends_oncontradictsrelated_tonone
Conflict Detector
Code: src/ormah/background/conflict_detector.py
The conflict detector:
- samples belief-like nodes
- finds semantically similar pairs
- asks the LLM whether they truly conflict
- distinguishes:
- evolution: newer view supersedes older one
- tension: simultaneous incompatible beliefs
Important correction:
- both
evolved_fromandcontradictsedges are currently written withweight=0.9
The older 0.4 figure some docs mentioned belongs to spreading activation during search, not stored conflict edges.
Duplicate Merger
Duplicate detection uses a composite score built from:
- embedding similarity
- title similarity
- token overlap
Merge semantics
MemoryEngine.execute_merge() does not create a brand-new merged node.
Instead it:
- chooses a keeper node
- optionally overwrites the keeper's title/content with merged content
- snapshots the removed node for undo
- remaps edges from removed -> kept
- soft-deletes the removed markdown node
- records merge history
In other words, duplicate merging keeps one existing node, updates it if needed, and removes the other; it does not create a separate canonical merged node.
Consolidator
Consolidator is different from duplicate merge.
It:
- finds clusters of similar working-tier nodes
- asks the LLM to synthesize a richer summary
- creates a new consolidated node
- links originals using
derived_from - demotes originals to
archival
So:
- duplicate merge -> keep one existing node
- consolidation -> create a new synthesized node
Auto-Cluster
Auto-cluster is a simple heuristic job:
- looks for nodes with no space
- checks the spaces of connected neighbors
- uses majority vote
- writes that chosen space back to markdown and SQLite
It does not do embedding clustering or hierarchical clustering despite the name.
Importance and Decay
importance_scorercomputes a normalized multi-signal importance valuedecay_manageruses FSRS-style retrievability and importance to decide whether to demote working memories
High-importance nodes are protected from decay.
Importance Scorer
importance_scorer recomputes node importance from three dynamic signals:
- Access signal: how often the node has been recalled, based on
access_count - Edge signal: how connected the node is in the graph, based on total edge count
- Recency signal: how retrievable the node currently is, using FSRS-style
exp(-days_ago / stability)
With the default configuration, those signals are weighted almost evenly:
- access weight:
0.34 - edge weight:
0.33 - recency weight:
0.33
Access and edge counts are log-normalized against reference values, then the weighted score is normalized into the 0.0-1.0 range. By default:
- access reference:
50 - edge reference:
20
The scorer runs every 120 minutes by default and only writes a new value when the change is meaningful.
This score is not static. Recall and search hits update access_count, last_accessed, last_review, and stability, so a memory's importance changes over time as it is used, connected, or left untouched.
Important current nuance: importance_recency_half_life_days exists in configuration, but the current scorer implementation uses FSRS stability for the recency term instead.
Walkthrough Example
Imagine two memories:
Chose SQLite for OrmahWe switched Ormah to Postgres
Conflict detector flow:
- finds them as a semantically similar pair
- LLM judges whether they are really about the same subject
- if the newer one supersedes the older one, it writes
evolved_from - the stored edge weight is
0.9
Now compare duplicate merge:
Chose SQLite for OrmahOrmah uses SQLite
If judged duplicate:
- a keeper node is chosen
- its content may be rewritten to include the best wording from both
- the other node is removed and edges are remapped
Code Anchors
src/ormah/background/scheduler.pysrc/ormah/background/auto_linker.pysrc/ormah/background/conflict_detector.pysrc/ormah/background/duplicate_merger.pysrc/ormah/background/consolidator.pysrc/ormah/background/auto_cluster.pysrc/ormah/background/importance_scorer.pysrc/ormah/background/decay_manager.py