Central dispatch layer for OpenClaw Memory Stack. Routes memory queries to the best backend via signal detection, class-based dispatch, and sequential fallback.
Memory Router is the central dispatch layer of the OpenClaw Memory Stack. It receives memory queries from the AI agent and routes them to the appropriate backend based on a deterministic rule table with sequential fallback. It is not an AI-powered optimizer — it is a rule-based router with explicit signal matching and a well-defined fallback chain.
How it works:
~/.openclaw/state/backends.json to discover which backends are installed.The router adds zero storage — it is purely a dispatch and fallback coordinator. All actual memory operations happen in the backends.
~/.openclaw/state/backends.json must exist and list installed backends.Before evaluating rules, the router reads ~/.openclaw/state/backends.json to build the candidate list of available backends. If a backend appears in a routing rule but is not in the candidate list, it is silently skipped — no error, no warning. The fallback chain advances to the next available backend.
Starter tier (QMD + Total Recall): Cognee and Lossless rules are effectively disabled — queries that would route to them fall through to QMD or Total Recall alternatives automatically.
Pro tier (all 4 backends): The full routing table is active.
Rules are evaluated top-to-bottom. The first rule whose signal matches the query wins. If no rule matches, the default rule applies.
| # | Signal in Query | Primary Backend | Fallback Chain | Rationale |
|---|---|---|---|---|
| 1 | Exact symbol, function name, class name, method name (find function, find class, find method, or a CamelCase/snake_case identifier) | QMD (search mode) | Total Recall | BM25 excels at exact token matching on code identifiers |
| 2 | Relationship query: "how does X relate to Y", "dependency between", "connection between", "what depends on", "call chain", "trace from...to" | Cognee | QMD (vsearch) > Total Recall | Graph traversal is required to follow entity relationships |
| 3 | Decision recall: "what did we decide about", "earlier in conversation", "decision from", "why did we choose", "recall the decision" | Lossless | Total Recall > QMD (search) | DAG preserves causal decision chains from conversation history |
| 4 | Concept/behavior query: "how does X work", "where is Y handled", "explain the...strategy", "what is the approach for" | QMD (vsearch mode) | QMD (query) > Total Recall | Vector semantic search finds conceptually related code |
| 5 | Recent context: "just discussed", "few minutes ago", "last change", "what was the last thing", "recently" | Total Recall | QMD (search) | Time-decay relevance prioritizes recent memories |
| 6 | File/path pattern: "in src/auth/", "*.swift files", file path segments, glob patterns | QMD (search mode) | Total Recall | BM25 path-based search matches filesystem patterns |
| 7 | Ambiguous / no clear signal (single words, vague references, catch-all) | QMD (query mode) | Total Recall | Hybrid search (BM25 + vector) is the safest default for unclear intent |
Rule 7 is the default rule. If no signal from rules 1-6 is detected, the query routes to QMD hybrid search.
Signal matching uses keyword and pattern detection, not semantic understanding. The router looks for:
find.*function, find.*class, find.*method, find all usages of, search for, or a token that matches CamelCase/snake_case identifier patterns.relate, relationship, depends on, dependency, connection, call chain, trace.*to, path between.decide, decision, chose, why did we, recall.*decision, earlier in conversation, morning session, afternoon session.how does.*work, where is.*handled, explain, strategy, approach for, what is the.*for.just discussed, few minutes ago, last change, recently, last thing, what was the last./), glob patterns (*, **), or file extensions (.ts, .swift, .py, etc.).The fallback mechanism is sequential and deterministic:
normalized_relevance >= 0.4 AND status = success — Accept the result. Done.normalized_relevance < 0.4 OR status = empty — Result is insufficient. Move to next backend in fallback chain.status = error — Immediate fallback. Log the error to ~/.openclaw/state/router.log.QUERY_TIMEOUT and moves to the next backend in the chain.status: partial.status = error, the router returns:{
"status": "error",
"error_code": "ALL_BACKENDS_FAILED",
"error_message": "All backends in the fallback chain failed. Check ~/.openclaw/state/router.log for details."
}
When a backend in the fallback chain is not installed (not in backends.json), the router skips it silently and advances to the next entry. This is how Starter tier works naturally — Cognee and Lossless are simply not in the candidate list, so rules 2 and 3 fall through to their QMD/Total Recall alternatives.
Example — Rule 2 on Starter tier:
1. "find the function parseAuthToken"
find.*function + CamelCase identifiersearch mode2. "how does the auth middleware relate to the session store"
relate keyword, two identifiable entitiesvsearch (Starter fallthrough)3. "what did we decide about the database schema in the morning session"
decide keyword, morning session4. "how does error handling work in the API layer"
how does.*workvsearch5. "what was the last thing we changed"
last thing6. "search in src/models/*.ts"
/, glob pattern *, extension .tssearch7. "find all usages of UserService"
find all usages of + CamelCase identifiersearch8. "what's the relationship between OrderController and PaymentService"
relationship keyword, two CamelCase entities9. "recall the architecture decision from this morning"
recall.*decision, morning10. "explain the caching strategy"
explain, strategyvsearch11. "auth"
query (hybrid search)12. "that thing with the tokens"
13. "database"
query (hybrid search)14. "why did we do it that way"
why did we maps to decision recall.15. "the middleware issue"
query (hybrid search)The router wraps backend responses in a router envelope that adds routing metadata:
{
"query_echo": "original query",
"routed_to": "qmd",
"routed_mode": "search",
"fallback_chain": ["qmd", "totalrecall"],
"fallbacks_used": 0,
"results": [
{
"content": "matched content...",
"relevance": 0.82,
"source": "qmd",
"timestamp": "2026-03-10T14:30:00Z"
}
],
"result_count": 2,
"normalized_relevance": 0.82,
"status": "success",
"router_duration_ms": 340,
"backend_duration_ms": 230
}
| Field | Type | Description |
|---|---|---|
query_echo | string | The original query string, unchanged |
routed_to | string | Backend that produced the returned results (qmd, totalrecall, cognee, lossless) |
routed_mode | string | null | Mode used for the winning backend (e.g., search, vsearch, query for QMD; null for other backends) |
fallback_chain | string[] | The complete fallback chain that was planned for this query |
fallbacks_used | int | Number of fallbacks that were actually triggered (0 = primary succeeded) |
results | array | Standard result array from the winning backend (same structure as backend contract) |
result_count | int | Number of results returned |
normalized_relevance | float | The normalized relevance score from the winning backend (0.0-1.0). This is the value used for fallback decisions. |
status | string | success, partial, empty, or error |
router_duration_ms | int | Total wall-clock time for the entire routing operation (including all fallbacks) |
backend_duration_ms | int | Duration of the winning backend call only |
| Status | Meaning |
|---|---|
success | Primary or fallback backend returned normalized_relevance >= 0.4 |
partial | Entire fallback chain exhausted; returning best result found (< 0.4) |
empty | All backends returned no results |
error | All backends failed (see error_code and error_message) |
If a backend appears in a routing rule but is not listed in ~/.openclaw/state/backends.json, the router skips it and advances to the next entry in the fallback chain. No error is logged — this is normal operation for Starter tier.
Log the error to ~/.openclaw/state/router.log with timestamp, backend name, error code, and error message. Then advance to the next entry in the fallback chain.
If a backend call exceeds 5000ms (measured by the router), mark the response as QUERY_TIMEOUT, log it, and advance to the next entry in the fallback chain.
Return:
{
"query_echo": "original query",
"routed_to": null,
"fallback_chain": ["qmd", "totalrecall"],
"fallbacks_used": 2,
"results": [],
"result_count": 0,
"status": "error",
"error_code": "ALL_BACKENDS_FAILED",
"error_message": "All backends in the fallback chain failed. Check ~/.openclaw/state/router.log for details.",
"router_duration_ms": 10200,
"backend_duration_ms": 0
}
Each log entry in ~/.openclaw/state/router.log:
[2026-03-17T14:30:00Z] FALLBACK query="auth middleware" backend=cognee error_code=BACKEND_ERROR error_message="Python process exited with code 1" next=qmd
[2026-03-17T14:30:01Z] TIMEOUT query="auth middleware" backend=qmd duration_ms=5230 next=totalrecall
The router reads two configuration sources:
~/.openclaw/state/backends.json — Dynamic state file listing installed backends and their status.router-config.json (alongside this file) — Static routing rules, thresholds, and tier configuration.The router does not write to backends.json — that file is managed by the installer (setup.sh / install-pro.sh).
Starter — The router itself is always available. Its effectiveness scales with the number of installed backends: Starter tier routes to QMD and Total Recall; Pro tier unlocks Cognee and Lossless routing rules.