Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
5a2eff9
Frontend: Vue 3 migration with i18n + dashboard overhaul
Jul 18, 2026
a02bdb4
Frontend: fix missing WikiEditor import + Diagnostic endpoint
Jul 18, 2026
ad42135
Frontend: better index.html title + description
Jul 18, 2026
1f8e522
Graph: restore rotation and polish sphere workspace
Jul 18, 2026
f6e0ab9
Frontend: restore full Insights dashboard (10 sections)
smartfind Jul 18, 2026
0254e07
Frontend: complete legacy parity — graph toolbar + side panel + kebab…
Jul 18, 2026
a34de38
Fix: enable pane scroll + browser-aware default language
Jul 18, 2026
3389ce2
Fix: i18n dict reactive + wiki tab refresh-on-activate
Jul 18, 2026
9cbad93
Fix graph activation and restore dashboard layout
smartfind Jul 18, 2026
090b767
Fix graph count flicker across refreshes
smartfind Jul 18, 2026
4d67a1f
Redesign topbar, stats hierarchy, lifecycle particles and decay chart
Jul 18, 2026
767cf0b
Stop weekly auto-refresh, fix decay axis labels, polish ingest chart,…
Jul 18, 2026
1e3d43b
Make contradiction 'merge' a true fusion, not a delete
Jul 18, 2026
649ec26
Fix 导入 + every TopBar action; redraw sidebar; session-click filters t…
Jul 18, 2026
60a1b23
Brand refresh + lifecycle bead animation fix
smartfind Jul 18, 2026
66a5ac1
Sidebar session click always jumps to timeline tab
smartfind Jul 18, 2026
979ce06
Make model entry visible; animate fit-to-window
smartfind Jul 18, 2026
376830b
Lifecycle rail — center it, slim it, soften colors
smartfind Jul 18, 2026
c916a68
Lifecycle rail — actually center it (top:36 → top:14)
smartfind Jul 18, 2026
d239006
Switch MiniMax default base_url: api.minimaxi.chat → api.minimaxi.com
smartfind Jul 18, 2026
bf327c0
Secret backend fallback: read from OS-native store if active missed
smartfind Jul 18, 2026
2439cc8
LLM drawer: rename to 大模型配置, fix duplicate 'Run mode' label + i18n bug
smartfind Jul 18, 2026
e734ef7
Topbar model chip: 4-state dot (green-pulsing/static/amber/red) + cog…
smartfind Jul 18, 2026
a4840d4
Merge topbar buttons + add Wiki import/export
smartfind Jul 18, 2026
5459388
feat: distillation v2 (completeness over compactness) + docs
smartfind Jul 18, 2026
24966f8
feat(watcher): size-stable idle + manual force-ingest escape hatch
smartfind Jul 19, 2026
4396b81
i18n(wiki): '新建页面' -> '新建知识' / 'New page' -> 'New knowledge'
smartfind Jul 19, 2026
4fa6233
i18n(wiki): unify terminology — 页面/词条/page → 知识/knowledge
smartfind Jul 19, 2026
af5db9b
fix(settings): compact behaviour-switches — 2-col grid + inline-flex
smartfind Jul 19, 2026
5af0254
fix(cache): bypass stale i18n / JS after deploy
smartfind Jul 19, 2026
4a73f35
fix(sidebar): expose api.fetchJSON + pass cache through to fetch()
smartfind Jul 19, 2026
599ba3d
ui(dashboard): compact ins-head card with status chips
smartfind Jul 19, 2026
47c7076
ci: fix ruff UP045 (X | None) in watcher.py
smartfind Jul 19, 2026
4e222a3
ui(dashboard): render weekly report as markdown + collapse thinking
smartfind Jul 19, 2026
05414bc
ui(dashboard): align decay-distribution axis labels with ticks
smartfind Jul 19, 2026
6902bc4
fix(sidebar): sort sessions by last-activity, not started_at
smartfind Jul 19, 2026
8df5e52
ui(dashboard): fix distribution-row alignment + trend axis labels
smartfind Jul 19, 2026
b862e31
ui(settings): drop redundant 'enable auto-run' checkbox
smartfind Jul 19, 2026
b7c496e
ui(dashboard): enrich source-health card to fill empty space
smartfind Jul 19, 2026
3ecfc42
fix(dashboard): wire 'run evolution' button to LLM queue
smartfind Jul 19, 2026
8cfdf92
feat(clients): add 'enable knowledge on Codex/Claude/Hermes' entry point
smartfind Jul 19, 2026
bebdad6
ui(ingest): unify active-session pill with the source row, drop compe…
smartfind Jul 19, 2026
caa93b9
ui(ingest): unify active-session pill with the source row, drop compe…
smartfind Jul 19, 2026
6dfe7ff
fix(dashboard): expose scoreBars in setup() + restore leading zero on…
smartfind Jul 19, 2026
eb61d48
ui(dashboard): move decay-chart axis labels to bar centers
smartfind Jul 19, 2026
fd23614
ui(dashboard): decimal-align decay chart axis labels (period on the t…
smartfind Jul 19, 2026
0a43a67
fix(weekly-report): cache per-ISO-week, kill content truncation, drop…
smartfind Jul 19, 2026
75b683f
fix(dashboard): drop the dx=-1.5 shift on the decimal-aligned tick la…
smartfind Jul 19, 2026
c56ee3c
ui(topbar): drop the '立即整理' button — duplicate of dashboard's '运行进化'
smartfind Jul 19, 2026
4769ab1
feat(recall): trigram FTS5 + temporal reasoning + LIKE fallback
smartfind Jul 19, 2026
e41fc75
feat(recall): L0/L1/L2 tiered loading (OpenViking pattern)
smartfind Jul 19, 2026
64bbe01
feat(wiki): per-client scope + README comparison table
smartfind Jul 19, 2026
5c60ead
fix(lint): silence ruff F841 + UP034 in temporal-recall path
smartfind Jul 19, 2026
e488810
fix(llm): defensive clean of schedule/behaviour in validate_config
Jul 21, 2026
d8eab48
feat(ingest): configurable cadence with hot-reload
Jul 21, 2026
dd0deb3
fix(i18n): inline JSON in HTML to kill hydration flash
Jul 21, 2026
6b7eb1c
feat(wiki): bulk-scope endpoint + per-card / master 全局 toggle
Jul 21, 2026
bcbc8bd
ui(settings): split drawer into 'Settings' + 'LLM Configuration' modes
Jul 22, 2026
4469717
feat(memory): compact + contradiction detection + codex context bloat…
smartfind Jul 24, 2026
07000b9
chore(automation): add weekly ecosystem research
smartfind Jul 24, 2026
ff2d8dc
fix(ci): clear baseline ruff violations
smartfind Jul 24, 2026
a36b3ef
chore(dev): configure VS Code Python workspace
smartfind Jul 24, 2026
79b2812
feat(v7): add universal agent memory APIs
smartfind Jul 24, 2026
acfce8b
fix(security): comprehensive security hardening
smartfind Jul 25, 2026
66a7cce
docs: add development plan for security hardening + cleanup
smartfind Jul 25, 2026
1e0fe7c
refactor(css): remove duplicate/overridden CSS rules (~300 lines remo…
smartfind Jul 25, 2026
105db69
perf(ui): add async component lazy loading for modal and heavy views
smartfind Jul 25, 2026
f5bdce4
fix(a11y): prefers-reduced-motion + Vue import paths + Dashboard comp…
smartfind Jul 25, 2026
bb57152
refactor(ui): extract RingMeter/SectionTitle, replace alert/confirm, …
smartfind Jul 25, 2026
37873c7
fix(ui): replace browser confirm() in Settings with inline modal
smartfind Jul 25, 2026
6c2532b
fix: remove unused referer variable, fix yield over for loop
smartfind Jul 25, 2026
2817877
refactor(ui): rename dashboard to 数据看板, fix Settings checkbox layout
smartfind Jul 25, 2026
e50fd3f
merge: integrate security and UI hardening
smartfind Jul 25, 2026
194ff48
fix(ui): allow Vue compiler under CSP
smartfind Jul 25, 2026
e78ec33
fix(ui): restore module loading after security merge
smartfind Jul 25, 2026
55ff9e2
fix(ui): resolve lazy-loaded named components
smartfind Jul 25, 2026
1a2eeb1
revert(ui): restore pre-security visual layout
smartfind Jul 25, 2026
f734126
fix(ui): compact settings toggle rows
smartfind Jul 25, 2026
85119d1
fix(ui): shrink last compact status card
smartfind Jul 25, 2026
b9291db
fix(ui): align storage compaction status
smartfind Jul 25, 2026
1283940
fix(ui): clarify settings security and toggle cards
smartfind Jul 25, 2026
f0938bb
fix(ui): align toggle descriptions in cards
smartfind Jul 25, 2026
a6a0b6b
docs(ui): explain manual consolidation actions
smartfind Jul 25, 2026
a28797c
fix(ui): align redaction toggle descriptions
smartfind Jul 25, 2026
7e9ce82
docs: settings drawer reference and watcher operations
smartfind Jul 25, 2026
2c167af
docs: drop development plan
smartfind Jul 25, 2026
a0fe115
refactor(ui): redesign settings drawer buttons
smartfind Jul 25, 2026
c83f09d
fix(security): tighten CSP, harden CSRF, annotate non-crypto hashes
smartfind Jul 25, 2026
0986a22
chore(deps): bump actions/setup-python from 6 to 7
dependabot[bot] Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: secret scan

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Scan tracked files for private credentials
run: python scripts/scan_secrets.py --tracked-only
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v5

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ dist/
.env
.env.*
!.env.example
.envrc
*.db
*.sqlite
*.sqlite3
*.log
*.bak
*.key
*.pem
secrets.json
.codex/
.claude/
.loop_memory/
credentials.json
work/
.worktrees
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"tamasfe.even-better-toml",
"redhat.vscode-yaml"
]
}
30 changes: 30 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Loop Memory: server",
"type": "debugpy",
"request": "launch",
"module": "loop_memory.cli.main",
"args": [
"serve",
"--port",
"7767"
],
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Python: current test file",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": [
"${file}",
"-q"
],
"console": "integratedTerminal",
"justMyCode": true
}
]
}
27 changes: 27 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.terminal.activateEnvironment": true,
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.analysis.typeCheckingMode": "basic",
"python.analysis.inlayHints.functionReturnTypes": true,
"python.analysis.inlayHints.variableTypes": true,
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
},
"ruff.nativeServer": "on",
"files.exclude": {
"**/__pycache__": true,
"**/.pytest_cache": true,
"**/.mypy_cache": true,
"**/.ruff_cache": true
}
}
44 changes: 44 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Python: tests",
"type": "shell",
"command": "${workspaceFolder}/.venv/bin/pytest",
"args": [
"-q"
],
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "Python: Ruff check",
"type": "shell",
"command": "${workspaceFolder}/.venv/bin/ruff",
"args": [
"check",
"loop_memory",
"tests"
],
"group": "build",
"problemMatcher": []
},
{
"label": "Loop Memory: server",
"type": "shell",
"command": "${workspaceFolder}/.venv/bin/python",
"args": [
"-m",
"loop_memory.cli.main",
"serve",
"--port",
"7767"
],
"isBackground": true,
"problemMatcher": []
}
]
}
189 changes: 189 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,192 @@
## [Unreleased]

### Settings drawer — UX pass
- **Security section** now ships a clear "Enabled / Disabled" status line plus a
one-line hint explaining what a bearer token does on this machine, instead
of leaking the raw i18n key when the translation was missing.
- **Processing toggles** (filter / re-score / distil / dry-run) and the
redaction switches (`Enable redaction`, `Process <private>...</private>`)
use a unified card style: checkbox + title on the first row, description on
the second row, description text aligned with the card's left edge.
- **Storage & compaction** card pairs the interval input with a compact
"Last compressed" status card of matching height so the two read as one
row.
- **Manual actions** section explains the difference between `Run now` (writes)
and `Preview` (dry-run) inline.
- All new strings are localised in `loop_memory/serve/static/i18n/{en,zh}.json`
so the English and Chinese drawers stay in lockstep.
- Docs: new `docs/settings.md` is the canonical reference for every drawer
control; `docs/auto-capture.md` now lists the real launchd labels
(`com.loopmemory.codex`, `com.loopmemory.claude`, `com.loopmemory.openclaw`)
and the `kickstart -k` / log commands for the Claude watcher.

### Visual layout regression fix
Restored the pre-`security-fix` visual layout of the Dashboard, LLM audit,
Settings subsections, and the four top-level tabs (timeline / dashboard /
wiki / graph). The four `<button role="tab">` controls still keep
accessibility and keyboard activation, but no longer inherit the browser's
default button border. The security, CSP, named-export, and async-component
fixes from the merge are preserved.

### Universal Agent Memory v7 — graph, cognitive, MEMORY.md, multi-tenant SDK
Closes the four gaps the article《LangChain、AgentScope、Mem0 深度横评:谁才是 Agent 的真正记忆系统?》called out against Mem0.

- **Graph memory + 3D adaptive scoring**
- new jobs.graph.upsert_semantic_edge(src, dst, kind, weight, evidence_id) — Mem0-style high-signal relations
- jobs.graph.graph_boost + jobs.graph.adaptive_score wired into recall_hybrid(adaptive=True)
- jobs.graph.subgraph_for(query) returns nodes / edges / 1-hop neighbours + backing memory ids
- new entity_mentions table maintained by MemoryStore.rebuild_entity_mentions()
- **Cognitive sleep** (jobs.cognitive.cognitive_sleep)
- heuristic sweep: stale / low_value / merge (with short-text containment) / contradict
- every decision lands in cognitive_audit for replay / revert
- apply=True actually deletes; False only lists suggestions
- **MEMORY.md white-box export** (export.memory_md.export_bundle / import_bundle / fork_snapshot)
- directory layout: MEMORY.md + pages/*.md + memories.jsonl + graph.json + sessions.json + meta.json + INDEX.md
- git init works for versioning; loop-memory fork --branch-tag v1.0 is a release tag
- **Multi-tenant SDK** (MemoryClient.for_user(uid) / for_agent(aid))
- MemoryNamespace proxies the 4 core verbs + graph / cognitive / export
- HTTP backend is zero-dep (urllib stdlib)
- **HTTP (/api/v1/*)**: graph/edges, graph/subgraph, graph/rebuild, cognitive/sleep, cognitive/audit, cognitive/audit/revert, export, import, fork, wiki/versions
- **MCP (loop-memory mcp)**: new tools remember_edge, subgraph, cognitive_sleep, audit
- **CLI**: loop-memory cognitive-sleep | audit | export | export-bundle | import | fork | graph-edge | subgraph | graph-rebuild; `export --out/--q` remains backward compatible
- **Schema v7**: new tables wiki_versions, cognitive_audit, auth_tokens. SCHEMA_VERSION 6 to 7. Migration is ALTER TABLE + CREATE INDEX IF NOT EXISTS, no-op on existing DBs
- **Tests**: v7 coverage now includes the CLI compatibility suite; total 315 tests pass
- **Docs**: docs/universal-agent-memory.md is the v7 canonical doc


### Universal Agent Memory — SDK + `/api/v1/memories` + MCP write tools
- **Identity columns on `memories`**: `agent_id`, `user_id`, `external_id`,
plus a `UNIQUE (agent_id, user_id, external_id)` index so re-pushing
the same external triple updates the row in place. `ALTER TABLE`
migration in `_init_schema`; `SCHEMA_VERSION` bumps from `"5"` to `"6"`.
Existing rows are treated as "global" (NULL on all three) and stay
visible to every Agent.
- **Python SDK (`loop_memory/sdk.py`)**: protocol-agnostic
`MemoryClient` with two backends — in-process (wraps a
`MemoryStore` directly) and HTTP (zero-dep, stdlib only). 4-verb
contract: `remember`, `recall`, `feedback`, `forget`, plus
`remember_batch` and `list` helpers. `Memory` and `RecallHit`
dataclasses are JSON-serialisable so an Agent can pass them
straight to an LLM prompt.
- **`/api/v1/memories` surface**: `POST` (idempotent), `POST:batch`,
`GET` (filtered list), `GET /api/v1/recall` (hybrid recall with
optional `agent_id` / `user_id` namespace filter),
`POST /api/v1/memories/{id}/feedback`,
`POST /api/v1/memories/feedback` (by external triple), and two
`DELETE` variants. All routes show up in `/openapi.json`.
- **MCP write tools**: `remember`, `forget`, `feedback` join the
existing read-only `recall` / `list_wiki` / `get_wiki` /
`recent_memories` / `wiki_summary`. Stamps every call with
`LOOP_MEMORY_AGENT_ID` / `LOOP_MEMORY_USER_ID` env when set, so
any MCP-aware client gets a stable identity without per-call
config.
- **New tests**: `tests/test_agent_memory_sdk.py` (in-process SDK
+ HTTP backend wiring), `tests/test_agent_memory_api.py`
(FastAPI round-trip), and `McpWriteToolTests` in
`tests/test_mcp.py` cover the write surface end-to-end.
- **Docs**: `docs/agent-memory-api.md` is the canonical design
doc; `docs/api.md` lists the new routes in the write-surface
table.


### Distillation policy v2 — completeness over compactness
- **Stage 3 (`_CLUSTER_SYSTEM`) and Stage 4 (`_WIKI_SYSTEM`) prompts no
longer hard-cap title / summary / body / distill lengths.** The previous
`<= 60 / 180 / 200 chars` and `3-8 bullets` limits were truncating
distillates mid-fact. The new prompt explicitly tells the LLM that
"losing a fact is much worse than a longer page" and to surface every
decision, number, name, constraint, error, and workaround in at least
one bullet.
- **Default token budget raised**: `max_text_chars` 1200 → 4000,
`max_output_tokens` 800 → 4096, validator ceiling 4096 → 8192. Per-call
cap on stage 3 distillation raised from 900 → 4096 tokens; legacy
consolidator call sites raised from 800 → 4096.
- **Wiki card rendering**: `bulletsOf()` no longer slices the first 6
bullets; the card now shows every atomic bullet the LLM produced so
freshly-distilled pages no longer look truncated in the grid. The
dead `bodyPreview()` helper was removed.

### Docs (open-source release prep)
- `CONTRIBUTING.md` rewritten: pytest instead of unittest, accurate
subpackage layout, secrets-storage guidance, the new LLM-provider
registration procedure, and the distillation-policy "completeness
over compactness" rule.
- New `docs/api.md`: canonical, hand-curated HTTP API reference
covering every route the UI consumes (read + write surface).
- New `docs/providers.md`: built-in providers table, default models,
base URLs, token-limit guidance, and the API-key storage story.
- `README.md` now links the docs in a single table so newcomers can
find everything without grepping the repo.

### Hybrid recall: BM25 + semantic + entity RRF + temporal reasoning
- **FTS5 mirror of `memories` + `wiki_pages`** with the `trigram`
tokenizer (SQLite ≥ 3.34, ships in the stdlib `sqlite3` module —
no new deps). Trigram is the only tokenizer that handles
substring search over CJK text without an external ICU build.
- **One-shot migration**: drops & rebuilds the FTS5 mirrors + sync
triggers if the existing DB has the legacy `unicode61` tokenizer
or the `content=''` clause (the latter blocks DELETE, which broke
`delete_session` / `delete_memory`). Idempotent and self-tracking
via `schema_meta`.
- **Hybrid recall** (`recall_hybrid`): BM25 (FTS5) + semantic
(cosine of `embedding` blob) + entity-overlap, fused with
Reciprocal Rank Fusion. `wiki_pages.scope` is honoured: pages
whose scope doesn't include the calling source are filtered
unless their scope is `global`. `/api/recall?mode=hybrid` is the
default; `?mode=legacy` keeps the old LIKE path for benchmarking.
- **CJK-friendly query escaping**: `_escape_fts` emits trigrams for
CJK runs (boundary trigrams only when the run is ≥ 5 chars), falls
back to a parallel `LIKE` scan, and merges results so short Chinese
queries (e.g. `项目` / `图表`) still hit.
- **Temporal reasoning** (Mem0 v3 insight, +27 pts on LongMemEval):
`detect_temporal_intent` classifies the query as `current` /
`past` / `future` / `any` from a small CJK + English lexicon;
`temporal_score` returns a per-row multiplier in [0.5, 1.5]
applied as a final rerank after RRF. Echoed in the response as
`temporal_intent`, `temporal_confidence`, `_temporal_multiplier`.
- **Entity channel fix**: stored entity names are kind-prefixed
(`concept:Codex`, `tag:auto`), but the bare token extracted from
the query (`Codex`) used to miss them. Lookup now matches both
the full prefixed name and the suffix; a one-shot substring
backfill against existing memories added 2,623 `entity_mentions`
on the live database.

### Tiered loading — L0 / L1 / L2 (OpenViking pattern)
- `recall_hybrid` and `/api/recall` take a `level` parameter
(default `1`):
- `0` — L0: titles + tags + preview; body and full text stripped.
- `1` — L1: summary + body capped at 800 chars + full memory
text. Default for the dashboard timeline.
- `2` — L2: full body, full text. Used only after the UI explicitly
expands a row.
- New drill-down endpoints `GET /api/memories/{mid}` (full text) and
the existing `GET /api/wiki/{page_id}` (full body) cover L2.
- Measured on the live DB (10-page recall, query `Loop Memory
system`): L0 ≈ 1,178 tokens, L1 ≈ 2,121, L2 ≈ 2,364 — about a 50 %
payload drop at L0 vs L2.

### Per-client wiki scope
- New `wiki_pages.scope` column (default `'global'`). `'codex'`,
`'claude'`, `'hermes'`, `'openclaw'`, or any comma-combination
like `'codex,claude'`. Pages outside the caller's scope are
filtered during recall but pages with scope `'global'` are visible
to every client.
- Web UI: a chip-row selector in the Wiki editor + a colour-coded
scope pill on every wiki card + a scope dropdown in the Wiki
toolbar to filter the list (`All / Global / Codex / Claude /
Hermes / OpenClaw`).

### Dashboard
- L0/L1/L2 surface in the timeline and wiki cards.
- Memory and wiki payloads carry `_level`, `_temporal_multiplier`,
`temporal_intent` for the dashboard to render.

### Internals
- `loop_memory/storage/retrieval.py` (new): `_escape_fts`,
`bm25_search`, `fuse_rrf`, `detect_temporal_intent`,
`temporal_score` — all pure-Python and dependency-free.
- Tests: 239 pass (`pytest tests/`).

## [0.3.0] — 2026-07-11

### Wiki fallback (v0.3.0)
Expand Down
Loading
Loading