mindforge-memory 6.4.1__tar.gz → 6.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mindforge_memory-6.4.1/mindforge_memory.egg-info → mindforge_memory-6.4.3}/PKG-INFO +7 -5
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/README.md +6 -4
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/query.py +13 -1
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/storage.py +71 -15
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/version.py +2 -2
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3/mindforge_memory.egg-info}/PKG-INFO +7 -5
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/SOURCES.txt +1 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v568_fixes.py +1 -1
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v575_release_consistency.py +2 -2
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v612_fixes.py +2 -2
- mindforge_memory-6.4.3/tests/test_v643_fixes.py +99 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/LICENSE +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/MindForge.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/claude_adapter.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/crewai_memory.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/generic_api.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/langgraph_store.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/openclaw_adapter.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/api/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/api/server.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/cli/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/cli/main.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/embedding.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/encryption.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/indexer.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/mindforge.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/paths.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/types.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/daemon/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/daemon/server.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/mcp/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/mcp/server.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/__init__.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/categorizer.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/conflict_detector.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/connectors.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/context_window.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/event_bus.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/evolution.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/experience.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/federated.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/federated_acl.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/follow_up.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/hybrid_search.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/importer.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/integrator.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/intent_router.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/knowledge_graph.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/memory_compressor.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/memory_decay.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/memory_passport.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/multi_agent.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/multimodal.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/outcome_learning.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/personality.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/principled_forgetting.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/privacy.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/recall.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/session_focus.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/share_conflict.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/skill_extractor.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/skill_pack.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/timeline_viz.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/v6.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/dependency_links.txt +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/entry_points.txt +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/requires.txt +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/top_level.txt +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/pyproject.toml +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/setup.cfg +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/setup.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_core.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_security_hardening.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v546_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v547_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v548_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v550_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v552_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v554_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v556_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v557_banner_insight_wal.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v557_security_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v557_webhook_sig_export.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v558_audit_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v558_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v560_decay_gc.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v560_kdf_versioning.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v560_module_coverage.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v561_security.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v562_p2_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v563_entry_coverage.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v563_perf_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v565_security.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v566_encrypted_search.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v567_security_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v569_fts_sync.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v570_coverage_expansion.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v570_drama_and_utils.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v570_encrypted_fallback.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v571_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v572_security.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v573_encrypted_export.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v573_security.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v576_bitemporal.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v576_gdpr.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v577_agent_governance.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v578_embedding.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v579_graph.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v580_experience.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v5812_bugfixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v583_security.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v584_adapters.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v585_benchmark.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v586_federated_mcp.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v590_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v592_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v593_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v595_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_forgetting_followup.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_import_timeline.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_multi_agent_formal.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_outcome_passport.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_skillpack_evolution.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v611_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v612_cascade_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v613_integrity.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v620_growth_governance.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v630_search_explain.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v631_scrypt.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v632_explain_interfaces.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v635_features.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v635_integration.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v636_fixes.py +0 -0
- {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v6_ports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mindforge-memory
|
|
3
|
-
Version: 6.4.
|
|
3
|
+
Version: 6.4.3
|
|
4
4
|
Summary: AI Agent 终身记忆系统 - 四层记忆架构 · 知识图谱 · 多模态支持
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/opok-ops/MindForge
|
|
@@ -36,11 +36,11 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
MindForge gives AI agents a persistent, structured long-term memory: a four-tier lifecycle model, knowledge graphs, hybrid retrieval, end-to-end encryption, and MCP / CLI / REST interfaces. All data stays on your machine.
|
|
38
38
|
|
|
39
|
-
> **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.
|
|
39
|
+
> **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.3: no team, no funding, 1,227 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
|
|
40
40
|
|
|
41
41
|
[](https://www.python.org/downloads/)
|
|
42
42
|
[](LICENSE)
|
|
43
|
-
[](https://github.com/opok-ops/MindForge/releases)
|
|
44
44
|
[](https://github.com/opok-ops/MindForge/stargazers)
|
|
45
45
|
[](https://github.com/opok-ops/MindForge/forks)
|
|
46
46
|
[](https://github.com/opok-ops/MindForge/issues)
|
|
@@ -96,8 +96,10 @@ MindForge stats
|
|
|
96
96
|
|
|
97
97
|
## What's New
|
|
98
98
|
|
|
99
|
-
v6.1.3 – v6.4.
|
|
99
|
+
v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228 tests** (1,227 passing, 1 environment-skipped).
|
|
100
100
|
|
|
101
|
+
- **v6.4.3 — documentation-code consistency fix** (2026-11-10). Pre-release audit found that v6.4.2's claimed first cut — WAL + `synchronous=NORMAL` — **never landed in code**: `_get_conn()` set only `journal_mode=WAL`, leaving `synchronous` at SQLite's default FULL. The 2.1× search gain was real, but carried entirely by the other four cuts — real numbers, misattributed cause. This release adds `PRAGMA synchronous=NORMAL` on the WAL branch (per-connection, cross-thread; network-FS DELETE fallback stays FULL by design). Same-machine A/B on SSD shows no statistically significant latency delta (search p50 1.8 → 1.8ms — fsync is ~0.1ms-scale here); the benefit targets slow disks and IO-saturated containers, and more importantly makes the documented power-loss semantics (un-checkpointed access counts / search audits may be lost; compliance audits still commit immediately) actually hold. 4 new guard tests in `tests/test_v643_fixes.py`.
|
|
102
|
+
- **v6.4.2 — tens-of-thousands-chars latency: another 2×** (2026-11-09). Same-machine A/B benchmark (400 memories / ~47k chars, 3 runs each): **search p50 6.3→3.0ms (2.1×), p95 11.7→4.2ms (2.8×)**; 24k-char library p50 2.5ms; fast machine state 1.8ms. Five surgical cuts: WAL + `synchronous=NORMAL` (commits skip per-write fsync), enum-downgrade memo, `row.keys()` set reuse, first-visit access-count batching merged into one `flush_access()` transaction per search, and search-audit INSERT folded into the same commit. The v5.6.3 throttle contract and v5.5.4 `recently_accessed` semantics are pinned by 23 contract tests — all green. Compliance-grade audits still commit immediately. *(Note: the `synchronous=NORMAL` cut landed in v6.4.3 — see above.)*
|
|
101
103
|
- **v6.4.1 — million-character millisecond search** (2026-11-09). Scale benchmark (8,000 memories / ~990k chars) exposed a fuzzy-fallback long tail: search p95 881ms. Root causes: SequenceMatcher wasted on mathematically-impossible matches (ratio = 2M/(m+n), M ≤ m, so n ≥ 4m can never pass 0.4) + a ~50μs/entry Python full-scan tax. Fixed with a math-capped prune + SQLite-level candidate pre-filtering: **search p95 881→30.7ms (28×)**, add p50 2.6ms flat at 10k-scale. New `scripts/bench_scale.py` + CI scale guard (3000 memories, millisecond contract) + 4 behavior-level regression tests pin the semantics.
|
|
102
104
|
|
|
103
105
|
- **v6.4.0 — resident daemon & performance guards** (2026-11-08). New `mindforge daemon` subcommand: line-JSON over stdio, engine resident in a long-lived process — per-request p50 2.5ms vs 400–700ms for CLI subprocess spawning (two orders of magnitude; 2026-11-09 quiet-machine re-bench: 2.5ms vs 401ms p50, ~160×). CI now guards latency (engine in-process thresholds) and daemon 13-step E2E alongside the 16-step CLI E2E guard.
|
|
@@ -165,7 +167,7 @@ v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,22
|
|
|
165
167
|
|
|
166
168
|
```
|
|
167
169
|
┌──────────────────────────────────────────────────────┐
|
|
168
|
-
│ MindForge v6.4.
|
|
170
|
+
│ MindForge v6.4.3 │
|
|
169
171
|
│ Cognitive Layer Personality · KnowledgeGraph │
|
|
170
172
|
│ MemoryEvolution · FederatedMemory │
|
|
171
173
|
├──────────────────────────────────────────────────────┤
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
MindForge gives AI agents a persistent, structured long-term memory: a four-tier lifecycle model, knowledge graphs, hybrid retrieval, end-to-end encryption, and MCP / CLI / REST interfaces. All data stays on your machine.
|
|
8
8
|
|
|
9
|
-
> **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.
|
|
9
|
+
> **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.3: no team, no funding, 1,227 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
|
|
10
10
|
|
|
11
11
|
[](https://www.python.org/downloads/)
|
|
12
12
|
[](LICENSE)
|
|
13
|
-
[](https://github.com/opok-ops/MindForge/releases)
|
|
14
14
|
[](https://github.com/opok-ops/MindForge/stargazers)
|
|
15
15
|
[](https://github.com/opok-ops/MindForge/forks)
|
|
16
16
|
[](https://github.com/opok-ops/MindForge/issues)
|
|
@@ -66,8 +66,10 @@ MindForge stats
|
|
|
66
66
|
|
|
67
67
|
## What's New
|
|
68
68
|
|
|
69
|
-
v6.1.3 – v6.4.
|
|
69
|
+
v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228 tests** (1,227 passing, 1 environment-skipped).
|
|
70
70
|
|
|
71
|
+
- **v6.4.3 — documentation-code consistency fix** (2026-11-10). Pre-release audit found that v6.4.2's claimed first cut — WAL + `synchronous=NORMAL` — **never landed in code**: `_get_conn()` set only `journal_mode=WAL`, leaving `synchronous` at SQLite's default FULL. The 2.1× search gain was real, but carried entirely by the other four cuts — real numbers, misattributed cause. This release adds `PRAGMA synchronous=NORMAL` on the WAL branch (per-connection, cross-thread; network-FS DELETE fallback stays FULL by design). Same-machine A/B on SSD shows no statistically significant latency delta (search p50 1.8 → 1.8ms — fsync is ~0.1ms-scale here); the benefit targets slow disks and IO-saturated containers, and more importantly makes the documented power-loss semantics (un-checkpointed access counts / search audits may be lost; compliance audits still commit immediately) actually hold. 4 new guard tests in `tests/test_v643_fixes.py`.
|
|
72
|
+
- **v6.4.2 — tens-of-thousands-chars latency: another 2×** (2026-11-09). Same-machine A/B benchmark (400 memories / ~47k chars, 3 runs each): **search p50 6.3→3.0ms (2.1×), p95 11.7→4.2ms (2.8×)**; 24k-char library p50 2.5ms; fast machine state 1.8ms. Five surgical cuts: WAL + `synchronous=NORMAL` (commits skip per-write fsync), enum-downgrade memo, `row.keys()` set reuse, first-visit access-count batching merged into one `flush_access()` transaction per search, and search-audit INSERT folded into the same commit. The v5.6.3 throttle contract and v5.5.4 `recently_accessed` semantics are pinned by 23 contract tests — all green. Compliance-grade audits still commit immediately. *(Note: the `synchronous=NORMAL` cut landed in v6.4.3 — see above.)*
|
|
71
73
|
- **v6.4.1 — million-character millisecond search** (2026-11-09). Scale benchmark (8,000 memories / ~990k chars) exposed a fuzzy-fallback long tail: search p95 881ms. Root causes: SequenceMatcher wasted on mathematically-impossible matches (ratio = 2M/(m+n), M ≤ m, so n ≥ 4m can never pass 0.4) + a ~50μs/entry Python full-scan tax. Fixed with a math-capped prune + SQLite-level candidate pre-filtering: **search p95 881→30.7ms (28×)**, add p50 2.6ms flat at 10k-scale. New `scripts/bench_scale.py` + CI scale guard (3000 memories, millisecond contract) + 4 behavior-level regression tests pin the semantics.
|
|
72
74
|
|
|
73
75
|
- **v6.4.0 — resident daemon & performance guards** (2026-11-08). New `mindforge daemon` subcommand: line-JSON over stdio, engine resident in a long-lived process — per-request p50 2.5ms vs 400–700ms for CLI subprocess spawning (two orders of magnitude; 2026-11-09 quiet-machine re-bench: 2.5ms vs 401ms p50, ~160×). CI now guards latency (engine in-process thresholds) and daemon 13-step E2E alongside the 16-step CLI E2E guard.
|
|
@@ -135,7 +137,7 @@ v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,22
|
|
|
135
137
|
|
|
136
138
|
```
|
|
137
139
|
┌──────────────────────────────────────────────────────┐
|
|
138
|
-
│ MindForge v6.4.
|
|
140
|
+
│ MindForge v6.4.3 │
|
|
139
141
|
│ Cognitive Layer Personality · KnowledgeGraph │
|
|
140
142
|
│ MemoryEvolution · FederatedMemory │
|
|
141
143
|
├──────────────────────────────────────────────────────┤
|
|
@@ -380,7 +380,10 @@ class QueryEngine:
|
|
|
380
380
|
|
|
381
381
|
def _cached_get(mid: str) -> Optional[Any]:
|
|
382
382
|
if mid not in _entry_cache:
|
|
383
|
-
|
|
383
|
+
# v6.4.2 perf: 批量检索上下文——首访访问计数走挂账,
|
|
384
|
+
# 由本 search 尾部的 flush_all_access 统一落库。
|
|
385
|
+
_entry_cache[mid] = self.storage.get_memory(
|
|
386
|
+
mid, _batch_access=True)
|
|
384
387
|
return _entry_cache[mid]
|
|
385
388
|
|
|
386
389
|
# 预过滤:按 categories/layers 筛选 score_map,避免非匹配记忆占据排序位
|
|
@@ -470,6 +473,15 @@ class QueryEngine:
|
|
|
470
473
|
except Exception:
|
|
471
474
|
pass
|
|
472
475
|
|
|
476
|
+
# v6.4.2 perf: 本轮 search 命中的首访访问计数 + 挂账的 search 审计
|
|
477
|
+
# 在此统一批量落库(executemany + 单次 commit,含审计 INSERT 合并)。
|
|
478
|
+
# 挂账期上限 = 单次 search 时长,recently_accessed 排序在 search
|
|
479
|
+
# 返回后即正确;同条记忆 60s 节流窗口内重复命中仍免落库(v5.6.3 契约)。
|
|
480
|
+
try:
|
|
481
|
+
self.storage.flush_access()
|
|
482
|
+
except Exception:
|
|
483
|
+
pass
|
|
484
|
+
|
|
473
485
|
elapsed = (time.time() - start) * 1000
|
|
474
486
|
token_estimate = sum(len(c.content) for c in chunks) // 4
|
|
475
487
|
|
|
@@ -694,6 +694,11 @@ class MemoryCache:
|
|
|
694
694
|
|
|
695
695
|
|
|
696
696
|
# v5.6.3 性能修复:记忆读缓存 TTL(秒)。除写路径主动失效外的兜底,
|
|
697
|
+
# v6.4.2 perf: 枚举降级 memo(行反序列化高频路径)。值域天然有界
|
|
698
|
+
# (枚举成员数 × 类数);Enum 单例不可变共享安全;跨模块路径加载的
|
|
699
|
+
# 同名类副本身份不同,作为 dict key 天然隔离不串台。
|
|
700
|
+
_ENUM_DOWNGRADE_MEMO: Dict[type, Dict[Any, Any]] = {}
|
|
701
|
+
|
|
697
702
|
# 任何遗漏的写路径最多造成该窗口内的短时脏读,避免长期脏数据。
|
|
698
703
|
_ENTRY_CACHE_TTL = 30.0
|
|
699
704
|
|
|
@@ -731,6 +736,8 @@ class StorageEngine:
|
|
|
731
736
|
# v6.2.0 增长治理:搜索审计去抖状态(同词 60s 内只落一条)
|
|
732
737
|
self._search_dedup_lock = threading.Lock()
|
|
733
738
|
self._search_dedup: Dict[str, float] = {}
|
|
739
|
+
# v6.4.2 perf: search 审计挂账标记(INSERT 已进事务缓冲未 commit)
|
|
740
|
+
self._search_audit_dirty = False
|
|
734
741
|
self._init_db()
|
|
735
742
|
|
|
736
743
|
def _get_conn(self) -> sqlite3.Connection:
|
|
@@ -750,6 +757,15 @@ class StorageEngine:
|
|
|
750
757
|
conn.execute("PRAGMA journal_mode=DELETE")
|
|
751
758
|
else:
|
|
752
759
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
760
|
+
# v6.4.3 fix: WAL 配套 synchronous=NORMAL(SQLite 官方推荐
|
|
761
|
+
# 组合)。v6.4.2 文档宣称了该优化但代码从未落地——本补丁
|
|
762
|
+
# 将其补齐:WAL+NORMAL 下 commit 免逐次 fsync,挂账批量
|
|
763
|
+
# 落库(search 尾事务)不再付 fsync 代价。
|
|
764
|
+
# 注意:synchronous 是 per-connection 参数,必须在每个
|
|
765
|
+
# 新连接创建时设置,仅在 WAL 分支生效;网络 FS 降级
|
|
766
|
+
# DELETE journal 的分支维持默认 FULL(远程盘耐久性语义
|
|
767
|
+
# 从严,不做削弱)。
|
|
768
|
+
conn.execute("PRAGMA synchronous=NORMAL")
|
|
753
769
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
754
770
|
conn.execute("PRAGMA busy_timeout=5000")
|
|
755
771
|
self._conn_local.conn = conn
|
|
@@ -1168,7 +1184,23 @@ class StorageEngine:
|
|
|
1168
1184
|
v5.5.4 fix: 兼容双重导入路径导致的枚举类不一致问题。
|
|
1169
1185
|
当 isinstance() 因不同模块路径加载同名枚举而失败时,
|
|
1170
1186
|
通过 .value / .name 属性匹配来识别。
|
|
1187
|
+
|
|
1188
|
+
v6.4.2 perf: str 输入路径先查 memo(命中即免逐值遍历匹配);
|
|
1189
|
+
key 含 default,不假设同一 value 恒定同一 default。
|
|
1171
1190
|
"""
|
|
1191
|
+
if isinstance(value, str):
|
|
1192
|
+
memo = _ENUM_DOWNGRADE_MEMO.setdefault(enum_cls, {})
|
|
1193
|
+
hit = memo.get((value, default))
|
|
1194
|
+
if hit is not None:
|
|
1195
|
+
return hit
|
|
1196
|
+
result = StorageEngine._downgrade_enum_slow(value, enum_cls, default)
|
|
1197
|
+
memo[(value, default)] = result
|
|
1198
|
+
return result
|
|
1199
|
+
return StorageEngine._downgrade_enum_slow(value, enum_cls, default)
|
|
1200
|
+
|
|
1201
|
+
@staticmethod
|
|
1202
|
+
def _downgrade_enum_slow(value: Any, enum_cls: type, default: Any) -> Any:
|
|
1203
|
+
"""未命中 memo 时的逐值匹配内核(原 _downgrade_enum 逻辑)。"""
|
|
1172
1204
|
if isinstance(value, enum_cls):
|
|
1173
1205
|
return value
|
|
1174
1206
|
# 兼容双重导入:isinstance 失败但 value 有枚举属性时,用 .value 匹配
|
|
@@ -1345,7 +1377,8 @@ class StorageEngine:
|
|
|
1345
1377
|
return entry
|
|
1346
1378
|
|
|
1347
1379
|
def get_memory(self, memory_id: str,
|
|
1348
|
-
actor: str = "", session_id: str = ""
|
|
1380
|
+
actor: str = "", session_id: str = "",
|
|
1381
|
+
_batch_access: bool = False) -> Optional[MemoryEntry]:
|
|
1349
1382
|
"""获取记忆
|
|
1350
1383
|
|
|
1351
1384
|
v5.5.2: 自动过期检查——若 expires_at > 0 且已过期,自动移入回收站并返回 None。
|
|
@@ -1433,7 +1466,8 @@ class StorageEngine:
|
|
|
1433
1466
|
return entry
|
|
1434
1467
|
return None
|
|
1435
1468
|
|
|
1436
|
-
self._update_access(entry, actor, session_id
|
|
1469
|
+
self._update_access(entry, actor, session_id,
|
|
1470
|
+
_batch=_batch_access)
|
|
1437
1471
|
return entry
|
|
1438
1472
|
|
|
1439
1473
|
def get_memories_by_ids(self, memory_ids: List[str]) -> List[Optional[MemoryEntry]]:
|
|
@@ -7285,6 +7319,8 @@ class StorageEngine:
|
|
|
7285
7319
|
|
|
7286
7320
|
def _row_to_entry(self, row: sqlite3.Row) -> MemoryEntry:
|
|
7287
7321
|
# v5.3.7 修复:tags/metadata 可能已经是 list/dict(非 JSON 字符串)
|
|
7322
|
+
# v6.4.2 perf: 一次构造列名集合,避免每行 5 次 row.keys() 分配 + 线性 in 扫描
|
|
7323
|
+
_rk = set(row.keys())
|
|
7288
7324
|
raw_tags = row["tags"]
|
|
7289
7325
|
if isinstance(raw_tags, (list, tuple)):
|
|
7290
7326
|
tags_val = list(raw_tags)
|
|
@@ -7324,11 +7360,11 @@ class StorageEngine:
|
|
|
7324
7360
|
consolidation_count=row["consolidation_count"],
|
|
7325
7361
|
forgetting_score=row["forgetting_score"],
|
|
7326
7362
|
strength=row["strength"],
|
|
7327
|
-
starred=bool(row["starred"]) if "starred" in
|
|
7328
|
-
pinned=bool(row["pinned"]) if "pinned" in
|
|
7329
|
-
expires_at=float(row["expires_at"]) if "expires_at" in
|
|
7330
|
-
valid_from=float(row["valid_from"]) if "valid_from" in
|
|
7331
|
-
valid_to=float(row["valid_to"]) if "valid_to" in
|
|
7363
|
+
starred=bool(row["starred"]) if "starred" in _rk else False,
|
|
7364
|
+
pinned=bool(row["pinned"]) if "pinned" in _rk else False,
|
|
7365
|
+
expires_at=float(row["expires_at"]) if "expires_at" in _rk and row["expires_at"] else 0.0,
|
|
7366
|
+
valid_from=float(row["valid_from"]) if "valid_from" in _rk and row["valid_from"] else 0.0,
|
|
7367
|
+
valid_to=float(row["valid_to"]) if "valid_to" in _rk and row["valid_to"] else 0.0,
|
|
7332
7368
|
metadata=meta_val,
|
|
7333
7369
|
encrypted=bool(row["encrypted"]),
|
|
7334
7370
|
ciphertext=row["ciphertext"],
|
|
@@ -7336,7 +7372,8 @@ class StorageEngine:
|
|
|
7336
7372
|
salt=row["salt"],
|
|
7337
7373
|
)
|
|
7338
7374
|
|
|
7339
|
-
def _update_access(self, entry: MemoryEntry, actor: str, session_id: str
|
|
7375
|
+
def _update_access(self, entry: MemoryEntry, actor: str, session_id: str,
|
|
7376
|
+
_batch: bool = False):
|
|
7340
7377
|
"""更新访问计数
|
|
7341
7378
|
|
|
7342
7379
|
v5.1.2:不再每次 get 都写审计日志。
|
|
@@ -7352,7 +7389,16 @@ class StorageEngine:
|
|
|
7352
7389
|
self._access_pending[entry.id] = (
|
|
7353
7390
|
self._access_pending.get(entry.id, 0) + 1)
|
|
7354
7391
|
last = self._access_last_flush.get(entry.id, 0.0)
|
|
7355
|
-
if last
|
|
7392
|
+
if last == 0.0:
|
|
7393
|
+
# v6.4.2 perf: 首访起表。批量检索上下文(_batch=True,如
|
|
7394
|
+
# QueryEngine.search 的结果构建)首访只挂账,由调用方统一
|
|
7395
|
+
# 批量落库(search 尾部 flush_all_access,单次 executemany
|
|
7396
|
+
# + commit);直连 get_memory 首访仍立即落库(v5.6.3 契约)。
|
|
7397
|
+
self._access_last_flush[entry.id] = now
|
|
7398
|
+
if _batch:
|
|
7399
|
+
self._maybe_prune_access_locked(now)
|
|
7400
|
+
return
|
|
7401
|
+
elif now - last < _ACCESS_PERSIST_INTERVAL:
|
|
7356
7402
|
self._maybe_prune_access_locked(now)
|
|
7357
7403
|
return
|
|
7358
7404
|
self._flush_access_locked(entry.id, now)
|
|
@@ -7420,8 +7466,10 @@ class StorageEngine:
|
|
|
7420
7466
|
字典,计数不丢。
|
|
7421
7467
|
"""
|
|
7422
7468
|
with self._access_lock:
|
|
7423
|
-
|
|
7469
|
+
audit_dirty = self._search_audit_dirty
|
|
7470
|
+
if not self._access_pending and not audit_dirty:
|
|
7424
7471
|
return
|
|
7472
|
+
self._search_audit_dirty = False
|
|
7425
7473
|
now = time.time()
|
|
7426
7474
|
pending = self._access_pending
|
|
7427
7475
|
self._access_pending = {}
|
|
@@ -7429,10 +7477,12 @@ class StorageEngine:
|
|
|
7429
7477
|
items = [(inc, now, mid) for mid, inc in pending.items()]
|
|
7430
7478
|
conn = self._get_conn()
|
|
7431
7479
|
try:
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7480
|
+
if items:
|
|
7481
|
+
conn.executemany("""
|
|
7482
|
+
UPDATE memories SET access_count = access_count + ?, last_accessed_at = ?
|
|
7483
|
+
WHERE id = ?
|
|
7484
|
+
""", items)
|
|
7485
|
+
# v6.4.2 perf: 挂账的 search 审计 INSERT(若有)随本事务一并提交
|
|
7436
7486
|
conn.commit()
|
|
7437
7487
|
except Exception as e:
|
|
7438
7488
|
with self._access_lock:
|
|
@@ -9248,8 +9298,14 @@ class StorageEngine:
|
|
|
9248
9298
|
self._search_dedup_lock = threading.Lock()
|
|
9249
9299
|
self._search_dedup = {}
|
|
9250
9300
|
try:
|
|
9301
|
+
# v6.4.2 perf: search 审计 INSERT 挂账不 commit,与下一次
|
|
9302
|
+
# flush_access() 的批量 UPDATE 合并成同一次事务提交(IO 慢态
|
|
9303
|
+
# 下 commit ~1ms,search 热路径 100 次 commit → 与挂账合并归零)。
|
|
9304
|
+
# search 审计为非合规数据(v6.2.0 prune 同一分类),崩溃窗口
|
|
9305
|
+
# ≤ 单次 search 时长,可接受;合规类审计不受影响仍即时 commit。
|
|
9306
|
+
self._search_audit_dirty = True
|
|
9251
9307
|
self._add_audit("search", q, "", "", "INTERNAL",
|
|
9252
|
-
{"query": q}, commit=
|
|
9308
|
+
{"query": q}, commit=False)
|
|
9253
9309
|
except Exception as _err:
|
|
9254
9310
|
logger.warning("record_search 审计写入失败: %s", _err)
|
|
9255
9311
|
|
|
@@ -20,10 +20,10 @@ core/mindforge.py、cli/main.py、mcp/server.py),每处都带一份硬编码
|
|
|
20
20
|
from typing import Tuple
|
|
21
21
|
|
|
22
22
|
#: 当前发布版本(唯一真值)
|
|
23
|
-
__version__: str = "6.4.
|
|
23
|
+
__version__: str = "6.4.3"
|
|
24
24
|
|
|
25
25
|
#: 结构化版本元组,便于比较(如 `VERSION_INFO >= (6, 1, 0)`)
|
|
26
|
-
VERSION_INFO: Tuple[int, int, int] = (6, 4,
|
|
26
|
+
VERSION_INFO: Tuple[int, int, int] = (6, 4, 3)
|
|
27
27
|
|
|
28
28
|
#: 下一个大版本的目标版本号(供 v6 端口预留引用)
|
|
29
29
|
NEXT_MAJOR_TARGET: str = "7.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mindforge-memory
|
|
3
|
-
Version: 6.4.
|
|
3
|
+
Version: 6.4.3
|
|
4
4
|
Summary: AI Agent 终身记忆系统 - 四层记忆架构 · 知识图谱 · 多模态支持
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/opok-ops/MindForge
|
|
@@ -36,11 +36,11 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
MindForge gives AI agents a persistent, structured long-term memory: a four-tier lifecycle model, knowledge graphs, hybrid retrieval, end-to-end encryption, and MCP / CLI / REST interfaces. All data stays on your machine.
|
|
38
38
|
|
|
39
|
-
> **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.
|
|
39
|
+
> **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.3: no team, no funding, 1,227 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
|
|
40
40
|
|
|
41
41
|
[](https://www.python.org/downloads/)
|
|
42
42
|
[](LICENSE)
|
|
43
|
-
[](https://github.com/opok-ops/MindForge/releases)
|
|
44
44
|
[](https://github.com/opok-ops/MindForge/stargazers)
|
|
45
45
|
[](https://github.com/opok-ops/MindForge/forks)
|
|
46
46
|
[](https://github.com/opok-ops/MindForge/issues)
|
|
@@ -96,8 +96,10 @@ MindForge stats
|
|
|
96
96
|
|
|
97
97
|
## What's New
|
|
98
98
|
|
|
99
|
-
v6.1.3 – v6.4.
|
|
99
|
+
v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228 tests** (1,227 passing, 1 environment-skipped).
|
|
100
100
|
|
|
101
|
+
- **v6.4.3 — documentation-code consistency fix** (2026-11-10). Pre-release audit found that v6.4.2's claimed first cut — WAL + `synchronous=NORMAL` — **never landed in code**: `_get_conn()` set only `journal_mode=WAL`, leaving `synchronous` at SQLite's default FULL. The 2.1× search gain was real, but carried entirely by the other four cuts — real numbers, misattributed cause. This release adds `PRAGMA synchronous=NORMAL` on the WAL branch (per-connection, cross-thread; network-FS DELETE fallback stays FULL by design). Same-machine A/B on SSD shows no statistically significant latency delta (search p50 1.8 → 1.8ms — fsync is ~0.1ms-scale here); the benefit targets slow disks and IO-saturated containers, and more importantly makes the documented power-loss semantics (un-checkpointed access counts / search audits may be lost; compliance audits still commit immediately) actually hold. 4 new guard tests in `tests/test_v643_fixes.py`.
|
|
102
|
+
- **v6.4.2 — tens-of-thousands-chars latency: another 2×** (2026-11-09). Same-machine A/B benchmark (400 memories / ~47k chars, 3 runs each): **search p50 6.3→3.0ms (2.1×), p95 11.7→4.2ms (2.8×)**; 24k-char library p50 2.5ms; fast machine state 1.8ms. Five surgical cuts: WAL + `synchronous=NORMAL` (commits skip per-write fsync), enum-downgrade memo, `row.keys()` set reuse, first-visit access-count batching merged into one `flush_access()` transaction per search, and search-audit INSERT folded into the same commit. The v5.6.3 throttle contract and v5.5.4 `recently_accessed` semantics are pinned by 23 contract tests — all green. Compliance-grade audits still commit immediately. *(Note: the `synchronous=NORMAL` cut landed in v6.4.3 — see above.)*
|
|
101
103
|
- **v6.4.1 — million-character millisecond search** (2026-11-09). Scale benchmark (8,000 memories / ~990k chars) exposed a fuzzy-fallback long tail: search p95 881ms. Root causes: SequenceMatcher wasted on mathematically-impossible matches (ratio = 2M/(m+n), M ≤ m, so n ≥ 4m can never pass 0.4) + a ~50μs/entry Python full-scan tax. Fixed with a math-capped prune + SQLite-level candidate pre-filtering: **search p95 881→30.7ms (28×)**, add p50 2.6ms flat at 10k-scale. New `scripts/bench_scale.py` + CI scale guard (3000 memories, millisecond contract) + 4 behavior-level regression tests pin the semantics.
|
|
102
104
|
|
|
103
105
|
- **v6.4.0 — resident daemon & performance guards** (2026-11-08). New `mindforge daemon` subcommand: line-JSON over stdio, engine resident in a long-lived process — per-request p50 2.5ms vs 400–700ms for CLI subprocess spawning (two orders of magnitude; 2026-11-09 quiet-machine re-bench: 2.5ms vs 401ms p50, ~160×). CI now guards latency (engine in-process thresholds) and daemon 13-step E2E alongside the 16-step CLI E2E guard.
|
|
@@ -165,7 +167,7 @@ v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,22
|
|
|
165
167
|
|
|
166
168
|
```
|
|
167
169
|
┌──────────────────────────────────────────────────────┐
|
|
168
|
-
│ MindForge v6.4.
|
|
170
|
+
│ MindForge v6.4.3 │
|
|
169
171
|
│ Cognitive Layer Personality · KnowledgeGraph │
|
|
170
172
|
│ MemoryEvolution · FederatedMemory │
|
|
171
173
|
├──────────────────────────────────────────────────────┤
|
|
@@ -40,8 +40,8 @@ class TestVersionTruth(unittest.TestCase):
|
|
|
40
40
|
def test_core_version_matches_release(self):
|
|
41
41
|
# 版本推进时必须一并更新此处的钉值(与 test_v568_fixes 的
|
|
42
42
|
# _EXPECTED_VERSION 同一约定:让「忘记 bump 版本」在测试里显形)。
|
|
43
|
-
self.assertEqual(core_version.__version__, "6.4.
|
|
44
|
-
self.assertEqual(core_version.VERSION_INFO, (6, 4,
|
|
43
|
+
self.assertEqual(core_version.__version__, "6.4.3")
|
|
44
|
+
self.assertEqual(core_version.VERSION_INFO, (6, 4, 3))
|
|
45
45
|
|
|
46
46
|
def test_pyproject_matches_core_version(self):
|
|
47
47
|
# v5.8.13 起 pyproject 不再写死静态版本:
|
|
@@ -662,8 +662,8 @@ class TestVersionConsistency(unittest.TestCase):
|
|
|
662
662
|
def test_version_is_612(self):
|
|
663
663
|
from mindforge.core.version import VERSION_INFO, __version__
|
|
664
664
|
# v6.1.3:版本钉值随发版推进更新(历史方法名保留)
|
|
665
|
-
self.assertEqual(__version__, "6.4.
|
|
666
|
-
self.assertEqual(VERSION_INFO, (6, 4,
|
|
665
|
+
self.assertEqual(__version__, "6.4.3")
|
|
666
|
+
self.assertEqual(VERSION_INFO, (6, 4, 3))
|
|
667
667
|
|
|
668
668
|
def test_json_round_trip_safety_of_preview(self):
|
|
669
669
|
"""save_evolution 的预览序列化不得抛(不可 json 化的对象走 str 兜底)。"""
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge v6.4.3 回归测试
|
|
3
|
+
|
|
4
|
+
覆盖测试过程中发现的 1 个文档-代码不一致缺陷(2026-11-10):
|
|
5
|
+
|
|
6
|
+
- Bug-A(P0): v6.4.2 宣称的「WAL + synchronous=NORMAL」五刀第一刀
|
|
7
|
+
从未落地——CHANGELOG/README/website 与 commit message 均宣称该
|
|
8
|
+
组合已上线,但 _get_conn() 仅设置了 journal_mode=WAL,
|
|
9
|
+
synchronous 维持 SQLite 默认 FULL(WAL 下逐 commit fsync)。
|
|
10
|
+
修复:WAL 分支补 PRAGMA synchronous=NORMAL(per-connection,
|
|
11
|
+
每连接创建时设置;网络 FS 降级 DELETE 分支维持 FULL 从严)。
|
|
12
|
+
|
|
13
|
+
守卫目标:让「PRAGMA 只在部分连接生效」「优化宣称了但没落地」
|
|
14
|
+
这两类回归在测试里显形。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
import threading
|
|
21
|
+
import unittest
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
25
|
+
|
|
26
|
+
from mindforge.core import storage as core_storage
|
|
27
|
+
from mindforge.core.version import __version__
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _WALCase(unittest.TestCase):
|
|
31
|
+
"""本地盘临时库(走 WAL 分支)公共脚手架。"""
|
|
32
|
+
|
|
33
|
+
def setUp(self):
|
|
34
|
+
self.tmpdir = tempfile.TemporaryDirectory(prefix="mf_v643_fix_")
|
|
35
|
+
self.db = os.path.join(self.tmpdir.name, "memory.db")
|
|
36
|
+
self.storage = core_storage.StorageEngine(
|
|
37
|
+
db_path=self.db, encrypted=False
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def tearDown(self):
|
|
41
|
+
try:
|
|
42
|
+
self.storage.close()
|
|
43
|
+
finally:
|
|
44
|
+
self.tmpdir.cleanup()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestSynchronousNormalGuard(_WALCase):
|
|
48
|
+
"""Bug-A 守卫:WAL + synchronous=NORMAL 必须真实生效。"""
|
|
49
|
+
|
|
50
|
+
def test_synchronous_normal_on_local_wal(self):
|
|
51
|
+
# SQLite PRAGMA synchronous 返回值:0=OFF, 1=NORMAL, 2=FULL。
|
|
52
|
+
# v6.4.2 缺陷形态即此值恒为 2(FULL)。
|
|
53
|
+
conn = self.storage._get_conn()
|
|
54
|
+
mode = conn.execute("PRAGMA synchronous").fetchone()[0]
|
|
55
|
+
self.assertEqual(
|
|
56
|
+
mode, 1,
|
|
57
|
+
f"synchronous 应为 NORMAL(1),实际 {mode}——"
|
|
58
|
+
"v6.4.3 的 WAL+synchronous=NORMAL 修复未生效或被回退",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def test_journal_mode_wal_on_local_disk(self):
|
|
62
|
+
conn = self.storage._get_conn()
|
|
63
|
+
journal = conn.execute("PRAGMA journal_mode").fetchone()[0]
|
|
64
|
+
self.assertEqual(
|
|
65
|
+
str(journal).lower(), "wal",
|
|
66
|
+
f"本地盘 journal_mode 应为 wal,实际 {journal}",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def test_synchronous_is_per_connection(self):
|
|
70
|
+
# synchronous 是 per-connection 参数:StorageEngine 用
|
|
71
|
+
# threading.local 维护连接,新线程的连接也必须带上 NORMAL,
|
|
72
|
+
# 否则只保护了首线程(REST/daemon 多线程场景将静默回退 FULL)。
|
|
73
|
+
results = {}
|
|
74
|
+
|
|
75
|
+
def probe():
|
|
76
|
+
try:
|
|
77
|
+
conn = self.storage._get_conn()
|
|
78
|
+
results["mode"] = conn.execute(
|
|
79
|
+
"PRAGMA synchronous"
|
|
80
|
+
).fetchone()[0]
|
|
81
|
+
except Exception as exc: # pragma: no cover - 显形优先
|
|
82
|
+
results["error"] = repr(exc)
|
|
83
|
+
|
|
84
|
+
t = threading.Thread(target=probe)
|
|
85
|
+
t.start()
|
|
86
|
+
t.join(timeout=10)
|
|
87
|
+
self.assertNotIn("error", results, results.get("error", ""))
|
|
88
|
+
self.assertEqual(results.get("mode"), 1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestVersionBump(unittest.TestCase):
|
|
92
|
+
"""版本钉值:忘记 bump 时在此显形(与 v568/v575/v612 守卫同一约定)。"""
|
|
93
|
+
|
|
94
|
+
def test_version_is_643(self):
|
|
95
|
+
self.assertEqual(__version__, "6.4.3")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/principled_forgetting.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|