memleaf 0.2.27__tar.gz → 0.2.29__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.
Files changed (155) hide show
  1. {memleaf-0.2.27 → memleaf-0.2.29}/CHANGELOG.md +15 -0
  2. {memleaf-0.2.27/src/memleaf.egg-info → memleaf-0.2.29}/PKG-INFO +4 -4
  3. {memleaf-0.2.27 → memleaf-0.2.29}/README.en.md +3 -3
  4. {memleaf-0.2.27 → memleaf-0.2.29}/README.md +3 -3
  5. memleaf-0.2.29/docs/config-migrations.md +37 -0
  6. {memleaf-0.2.27 → memleaf-0.2.29}/docs/general-processing.md +8 -1
  7. memleaf-0.2.29/docs/performance.md +99 -0
  8. {memleaf-0.2.27 → memleaf-0.2.29}/install.sh +13 -15
  9. {memleaf-0.2.27 → memleaf-0.2.29}/pyproject.toml +1 -1
  10. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/__init__.py +1 -1
  11. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/adapters/base.py +6 -6
  12. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/admission.py +30 -3
  13. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/capture.py +4 -9
  14. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/cli.py +10 -10
  15. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/compaction.py +1 -1
  16. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/config.py +41 -13
  17. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/evidence_policy.py +11 -22
  18. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  19. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/host_runtime.py +1 -1
  20. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/index.py +0 -61
  21. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/inspection.py +3 -3
  22. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/installer.py +6 -6
  23. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/llm/__init__.py +1 -1
  24. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/llm/base.py +1 -1
  25. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/memory_commit.py +2 -2
  26. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/memory_writer.py +1 -1
  27. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/model_execution.py +3 -1
  28. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/process_journal.py +7 -7
  29. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/processing.py +2 -2
  30. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/prompts.py +20 -0
  31. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/retention.py +2 -2
  32. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/retrieval_gate.py +2 -2
  33. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/scope_maintenance.py +1 -1
  34. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/service.py +8 -38
  35. memleaf-0.2.29/src/memleaf/state_layout.py +279 -0
  36. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/validation.py +1 -1
  37. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/vault.py +63 -50
  38. {memleaf-0.2.27 → memleaf-0.2.29/src/memleaf.egg-info}/PKG-INFO +4 -4
  39. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf.egg-info/SOURCES.txt +7 -0
  40. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_admission_noise.py +5 -5
  41. memleaf-0.2.29/tests/test_config_migrations_v028.py +42 -0
  42. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_email_actionable_coverage.py +1 -1
  43. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_evidence_retention_policy.py +8 -8
  44. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_general_evidence_admission.py +178 -4
  45. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_global_todo_query_no_write.py +1 -1
  46. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_hermes_provider.py +2 -2
  47. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_hermes_runtime_install.py +1 -1
  48. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_host_events.py +3 -3
  49. memleaf-0.2.29/tests/test_inspection_state_v028.py +33 -0
  50. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_install.py +5 -5
  51. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_maintenance_v2.py +2 -2
  52. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_model_owned_fields.py +1 -1
  53. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_phase2_model_decisions.py +3 -3
  54. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_pypi_install.py +3 -3
  55. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_retrieval_gate.py +1 -1
  56. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_session_lineage.py +1 -1
  57. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_shared_memory_refactor.py +3 -3
  58. memleaf-0.2.29/tests/test_source_neutral_todos_v028.py +49 -0
  59. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_a.py +6 -6
  60. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b1.py +8 -8
  61. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b2a.py +6 -6
  62. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b2b.py +4 -4
  63. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3a_commit.py +1 -1
  64. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3b_native_context.py +2 -2
  65. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3b_scope.py +3 -3
  66. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3d_scope_maintenance.py +8 -8
  67. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_c2_init.py +14 -14
  68. memleaf-0.2.29/tests/test_state_layout_v028.py +200 -0
  69. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_update_target_recovery.py +4 -4
  70. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_v2_gate_limits.py +1 -1
  71. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_v2_host_flow.py +2 -2
  72. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_v2_mcp_flow.py +2 -2
  73. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_v2_search_gate_acceptance.py +1 -1
  74. {memleaf-0.2.27 → memleaf-0.2.29}/IMPLEMENTATION_PLAN.md +0 -0
  75. {memleaf-0.2.27 → memleaf-0.2.29}/LICENSE +0 -0
  76. {memleaf-0.2.27 → memleaf-0.2.29}/MANIFEST.in +0 -0
  77. {memleaf-0.2.27 → memleaf-0.2.29}/RELEASE_CHECKLIST.md +0 -0
  78. {memleaf-0.2.27 → memleaf-0.2.29}/docs/core-refactor.md +0 -0
  79. {memleaf-0.2.27 → memleaf-0.2.29}/docs/evidence-retention.md +0 -0
  80. {memleaf-0.2.27 → memleaf-0.2.29}/docs/hermes-mcp-runtime.md +0 -0
  81. {memleaf-0.2.27 → memleaf-0.2.29}/docs/v0.2.26-processing-status.md +0 -0
  82. {memleaf-0.2.27 → memleaf-0.2.29}/examples/README.md +0 -0
  83. {memleaf-0.2.27 → memleaf-0.2.29}/examples/basic_usage.py +0 -0
  84. {memleaf-0.2.27 → memleaf-0.2.29}/examples/live_processing_acceptance.py +0 -0
  85. {memleaf-0.2.27 → memleaf-0.2.29}/examples/mcp_stdio.ndjson +0 -0
  86. {memleaf-0.2.27 → memleaf-0.2.29}/install.ps1 +0 -0
  87. {memleaf-0.2.27 → memleaf-0.2.29}/setup.cfg +0 -0
  88. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/__main__.py +0 -0
  89. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/adapters/__init__.py +0 -0
  90. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/adapters/antigravity.py +0 -0
  91. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/adapters/codex.py +0 -0
  92. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/adapters/hermes.py +0 -0
  93. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/budget.py +0 -0
  94. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/credentials.py +0 -0
  95. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/frontmatter.py +0 -0
  96. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/hermes_provider/README.md +0 -0
  97. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/hermes_provider/__init__.py +0 -0
  98. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/hermes_runtime.py +0 -0
  99. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/host_events.py +0 -0
  100. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/inbox.py +0 -0
  101. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/llm/claude_compatible.py +0 -0
  102. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/llm/gemini.py +0 -0
  103. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/llm/openai_compatible.py +0 -0
  104. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/llm/router.py +0 -0
  105. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/locking.py +0 -0
  106. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/mcp_server.py +0 -0
  107. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/memory_planner.py +0 -0
  108. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/model_discovery.py +0 -0
  109. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/models.py +0 -0
  110. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/native_index.py +0 -0
  111. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/native_registration.py +0 -0
  112. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/planning_context.py +0 -0
  113. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/process_common.py +0 -0
  114. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/process_owner.py +0 -0
  115. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/provenance.py +0 -0
  116. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/recording_policy.py +0 -0
  117. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/redaction.py +0 -0
  118. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/retrieval.py +0 -0
  119. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/scope_state.py +0 -0
  120. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/source_policy.py +0 -0
  121. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/turn_audit.py +0 -0
  122. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/turn_plan.py +0 -0
  123. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf/update_coordinator.py +0 -0
  124. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf.egg-info/dependency_links.txt +0 -0
  125. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf.egg-info/entry_points.txt +0 -0
  126. {memleaf-0.2.27 → memleaf-0.2.29}/src/memleaf.egg-info/top_level.txt +0 -0
  127. {memleaf-0.2.27 → memleaf-0.2.29}/tests/__init__.py +0 -0
  128. {memleaf-0.2.27 → memleaf-0.2.29}/tests/semantic_fixtures.py +0 -0
  129. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_codex_install.py +0 -0
  130. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_codex_native_cli.py +0 -0
  131. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_context_budget.py +0 -0
  132. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_credential_safety.py +0 -0
  133. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_cross_host_acceptance.py +0 -0
  134. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_cross_turn_dedupe_regressions.py +0 -0
  135. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_extraction_quality_regressions.py +0 -0
  136. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_general_tool_provenance.py +0 -0
  137. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_global_todo_acceptance.py +0 -0
  138. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_global_todo_retrieval.py +0 -0
  139. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_hermes_native_registration.py +0 -0
  140. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_hermes_stdio_transport.py +0 -0
  141. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_host_runtime_contract.py +0 -0
  142. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_long_run_hygiene.py +0 -0
  143. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_model_discovery.py +0 -0
  144. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_process_owner_locking.py +0 -0
  145. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_processing_contract_v026.py +0 -0
  146. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_retrieval_v2.py +0 -0
  147. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3a_contract.py +0 -0
  148. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3b_native_index.py +0 -0
  149. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_b3c_retrieval.py +0 -0
  150. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_c1_mcp.py +0 -0
  151. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_stage_c3_packaging.py +0 -0
  152. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_todo_state_recovery.py +0 -0
  153. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_upgrade_preserves_vault.py +0 -0
  154. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_v023_scope_correction.py +0 -0
  155. {memleaf-0.2.27 → memleaf-0.2.29}/tests/test_v2_nomatch_semantics.py +0 -0
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to memleaf are documented here.
4
4
 
5
+ ## 0.2.29 — 2026-09-07
6
+
7
+ - Clarify the source-neutral Gate contract for tool records retained as `metadata`: they are not evidence units, cannot be bound by metadata identifiers, and cannot authorize CREATE or UPDATE.
8
+ - Normalize coverage dispositions from their declared reasons so `query_only`, `assistant_restatement`, and other known no-write reasons close as `NO_CHANGE`, while unresolved evidence, ownership, target, or Scope ambiguity remains `DEFERRED`.
9
+ - Add a bounded `invalid_evidence` correction path and regressions for metadata-only one-off operations, failed watermark recovery, cleanup eligibility, idempotent retry, and protection against knowledge/history pollution. Keep Markdown as the sole source of truth without adding SQLite, FTS, or other runtime services.
10
+
11
+ ## 0.2.28 — 2026-09-06
12
+
13
+ - Separate rebuildable derived data in `_index/` from correctness/runtime state in `_state/`. Existing Vaults migrate processed-event, agent activation, host-ingest, retrieval-gate and compaction state crash-safely and idempotently; conflicting or corrupt legacy/current state fails closed, and `rebuild-index` never rewrites runtime state.
14
+ - Normalize supported legacy configuration into current names, including `capture.include_tool_output` to `capture.tool_evidence_mode`, reject conflicting legacy/current settings, preserve historical safe defaults, and document 0.2.x compatibility/deprecation behavior in `docs/config-migrations.md`.
15
+ - Move host activation bookkeeping to `_state/agents.json`, update installation/status paths accordingly, and keep Hermes/Codex shared-Vault behavior, permanent-memory global visibility, provenance-only session/source metadata, and native Hermes memory coexistence unchanged.
16
+ - Remove the remaining Core urgency-word classifier for unscheduled todos so ordering stays source-neutral; model-owned business semantics remain outside deterministic Core validation.
17
+ - Add reproducible 1k/10k/50k long-run benchmarking across retrieval, writes, lifecycle, locks, RSS and disk. The 50k-active dataset is explicitly an extreme stress boundary rather than a normal steady-state assumption; normal retrieval remains `Scope Map -> scope-constrained search -> read`, while UPDATE/NO_CHANGE, todo retirement, bounded history and compaction control active-memory growth.
18
+ - Do not introduce SQLite/FTS, vector storage, external databases, daemons or new runtime dependencies. Markdown remains the sole source of truth and `_index/` remains fully deletable/rebuildable.
19
+
5
20
  ## 0.2.27 — 2026-09-05
6
21
 
7
22
  - Remove application-, document-, tool- and business-specific semantic classifiers from the Core admission/target path. The model owns future-use and atomicity judgments; Core retains source-neutral evidence, Scope, type, target, date, revision and conflict validation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.27
3
+ Version: 0.2.29
4
4
  Summary: A local-first Markdown memory core for AI agents
5
5
  Author: memleaf contributors
6
6
  License-Expression: MIT
@@ -23,8 +23,8 @@ Dynamic: license-file
23
23
 
24
24
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
25
25
 
26
- > **版本:0.2.27。**
27
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版收口长期运行治理:Core 语义判断保持 source-neutral,并为来源、关闭待办、历史版本和压缩身份增加有界生命周期。真实模型语义效果仍需结合本地模型和代表性样本验收。
26
+ > **版本:0.2.29。**
27
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版补齐 metadata 工具证据的 Gate 覆盖协议,修复查询和助手复述误标 DEFERRED 的收口问题,同时保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
28
28
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
29
29
 
30
30
  ## 项目定位
@@ -490,7 +490,7 @@ MIT,见 [LICENSE](LICENSE)。
490
490
  *Your memories, in files you own.*
491
491
 
492
492
 
493
- ## 通用处理与只读验收(0.2.27)
493
+ ## 通用处理与只读验收(0.2.29)
494
494
 
495
495
  邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
496
496
  自动摘要只能使用获准引用的原文;助手复述和旧记忆回读不能单独授权新增写入。
@@ -4,8 +4,8 @@
4
4
 
5
5
  [中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **Version: 0.2.27.**
8
- > The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. This release closes long-run lifecycle gaps: Core semantic decisions remain source-neutral, while provenance, closed todos, history versions, and compaction identity are bounded. Real-model semantics still require local acceptance with the selected model and representative inputs.
7
+ > **Version: 0.2.29.**
8
+ > The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. This release completes the metadata-only tool evidence Gate contract and prevents query or assistant restatement coverage from remaining DEFERRED, while preserving source-neutral semantics, Markdown as the sole source of truth, and zero SQLite runtime dependencies. Real-model semantics still require local acceptance with the selected model and representative inputs.
9
9
  > **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
10
10
 
11
11
  ## Project scope
@@ -474,7 +474,7 @@ MIT; see [LICENSE](LICENSE).
474
474
  *Your memories, in files you own.*
475
475
 
476
476
 
477
- ## General processing and read-only inspection (0.2.27)
477
+ ## General processing and read-only inspection (0.2.29)
478
478
 
479
479
  Dialogue, calendars, tickets, files, web results and other tools share the evidence, coverage and write path.
480
480
  Models interpret semantics; Core validates physical provenance and exact original quotations.
@@ -4,8 +4,8 @@
4
4
 
5
5
  [English](README.en.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
6
6
 
7
- > **版本:0.2.27。**
8
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版收口长期运行治理:Core 语义判断保持 source-neutral,并为来源、关闭待办、历史版本和压缩身份增加有界生命周期。真实模型语义效果仍需结合本地模型和代表性样本验收。
7
+ > **版本:0.2.29。**
8
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版补齐 metadata 工具证据的 Gate 覆盖协议,修复查询和助手复述误标 DEFERRED 的收口问题,同时保持 source-neutral 语义、Markdown 唯一事实源和无 SQLite 运行时依赖。真实模型语义效果仍需结合本地模型和代表性样本验收。
9
9
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
10
10
 
11
11
  ## 项目定位
@@ -471,7 +471,7 @@ MIT,见 [LICENSE](LICENSE)。
471
471
  *Your memories, in files you own.*
472
472
 
473
473
 
474
- ## 通用处理与只读验收(0.2.27)
474
+ ## 通用处理与只读验收(0.2.29)
475
475
 
476
476
  邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
477
477
  自动摘要只能使用获准引用的原文;助手复述和旧记忆回读不能单独授权新增写入。
@@ -0,0 +1,37 @@
1
+ # Configuration migrations
2
+
3
+ This document describes the configuration and Vault-layout compatibility boundary starting with memleaf v0.2.28.
4
+
5
+ ## Current configuration
6
+
7
+ The current persisted top-level sections are `vault`, `agents`, `scopes`, `native_sources`, `process`, `history`, `capture`, and `llm`. Retrieval remains Scope Map -> search -> read; there is no configurable legacy injection mode.
8
+
9
+ `capture.tool_evidence_mode` is the current tool-evidence retention setting and accepts `bounded`, `metadata`, or `off`. `capture.include_attachments` is independent and defaults to `false`.
10
+
11
+ ## Deprecated fields
12
+
13
+ - Top-level `inject` (`mode`, `abnormal_guard`) belonged to the pre-Scope-Map injection path. v0.2.28 reads and discards this section; it is not written again.
14
+ - `capture.include_tool_output` is replaced by `capture.tool_evidence_mode`.
15
+ - The old internal `processed_index_path` / `agents_index_path` names are removed in v0.2.28; runtime code uses explicit `_state/` properties.
16
+
17
+ ## CLI compatibility sunset
18
+
19
+ `memleaf init --no-codex` and `memleaf init --no-antigravity` are retained only as deprecated no-op argument compatibility for existing 0.2.x setup scripts. They do not select runtime behavior and are scheduled for removal in v0.3. The legacy `init --json` host result slots are likewise retained through 0.2.x so automation does not break during this maintenance release. New integrations must use `memleaf install --host ...` and the current host-state fields.
20
+ memleaf's own current installers no longer pass the two deprecated no-op flags; only external 0.2.x callers retain that compatibility surface.
21
+
22
+ ## Automatic migration
23
+
24
+ When reading an older configuration, `capture.include_tool_output: true` becomes `tool_evidence_mode: bounded`; `false` becomes `metadata`. Saving the normalized configuration writes only the current field. If both legacy and current fields are present but disagree, loading fails closed instead of guessing.
25
+ A persisted older config with no `capture` section, or with a partial capture section that has neither evidence field, normalizes to `tool_evidence_mode: metadata`, preserving the previous safe metadata-only behavior. New Vaults still write an explicit `bounded` mode.
26
+
27
+ The obsolete `inject` section is removed during normalization. No current runtime component consumes it.
28
+
29
+ On first v0.2.28 Vault use, runtime correctness state is migrated from `_index/` to `_state/` by one centralized layout owner. Existing v0.2.27 Vault/retrieval lock files are acquired during migration so an already-running old worker cannot mutate the copied state concurrently. The new copy is atomically written and verified before a durable `_state/layout.json` completion marker is written; legacy files are removed only after that marker. Before the marker, old/new coexistence must be equivalent or startup fails closed. After the marker, `_state/` is authoritative and stale `_index/` state is cleanup debris, never merged or replayed.
30
+
31
+ ## Incompatible cases
32
+
33
+ Migration fails closed for malformed runtime JSON, unsafe symlinks, divergent pre-marker old/new state, an invalid layout marker, or an old process still holding a legacy lock strongly enough to prevent cleanup. Stop older memleaf/Hermes/Codex processes and retry; do not delete `_state/` to force an upgrade.
34
+
35
+ ## User action
36
+
37
+ Normal users do not need to edit their Vault or configuration. For a live upgrade, stop older processes that are using the Vault before starting v0.2.28. Backups remain recommended for any software upgrade. `_index/` is disposable and rebuildable; `_state/` is not.
@@ -1,4 +1,4 @@
1
- # General processing reliability contract — 0.2.27
1
+ # General processing reliability contract — 0.2.29
2
2
 
3
3
  This is source-neutral processing, not a mail extractor. Dialogue, documents,
4
4
  calendars, issue trackers and terminal/tool observations use the same admission
@@ -40,6 +40,13 @@ A Gate may map multiple facts in one evidence unit to several candidates, or
40
40
  one candidate to several units. Coverage is checked against real supplied IDs.
41
41
  One source-neutral correction can classify missing units; its candidates pass
42
42
  the same validator/deduplication path, never a post-Gate business-pattern writer.
43
+ Tool records retained as `metadata` may remain visible in the event envelope for
44
+ diagnostics, but they are not evidence units and cannot be bound by call ID,
45
+ digest, tool name or other metadata. They therefore require no coverage row and
46
+ cannot authorize CREATE/UPDATE. Coverage reasons are normalized at the boundary:
47
+ `query_only`, `assistant_restatement`, `retrieved_memory_only`, `no_future_value`,
48
+ duplicate/example and completed/negated reasons resolve to `NO_CHANGE`; unresolved
49
+ coverage, ownership, target or Scope reasons remain `DEFERRED`.
43
50
  Already valid siblings survive a malformed correction. Work that remains
44
51
  ambiguous is retained and explicitly reported, not guessed into a global scope.
45
52
 
@@ -0,0 +1,99 @@
1
+ # Performance and long-run scale
2
+
3
+ This benchmark measures memleaf's local Markdown Vault and derived indexes. It does not call an LLM, so model/provider latency is intentionally excluded.
4
+
5
+ ## Methodology
6
+
7
+ Datasets contain global plus 24 project scopes, tags, aliases, keywords, wikilinks, active todos, historical versions, bounded high-frequency provenance, Chinese and English text, and mixed short/long bodies. CREATE/UPDATE/NO_CHANGE use `MemoryWriter` plus the real derived-index rebuild boundary; lifecycle measurements use the real retention and compaction primitives.
8
+
9
+ The hosted benchmark reports a process-cold first search and warm repeated searches. It does **not** claim a true OS cold-cache measurement because hosted CI cannot safely or reproducibly drop the kernel page cache.
10
+
11
+ The 50,000-active-memory dataset is deliberately an extreme stress boundary. It is not an assumption that a healthy Vault should normally retain 50,000 useful active memories. The dataset has 20% global memories and spreads the rest across 24 project scopes, so even one project scope contains roughly 1.6k-1.7k active records at 50k total.
12
+
13
+ Normal product retrieval remains `Scope Map -> scope-constrained search -> read`; full-vault full-text search is a fallback/special case rather than the primary injection path.
14
+
15
+ Platform: `Linux-6.17.0-1022-azure-x86_64-with-glibc2.39`
16
+ Python: `3.13.15`
17
+ Run UTC: `2026-09-06T03:49:07Z`
18
+
19
+ ## Latency results
20
+
21
+ All latency values are milliseconds.
22
+
23
+ | Active memories | Metric | median | p95 | max | samples |
24
+ |---:|---|---:|---:|---:|---:|
25
+ | 1,000 | `vault_initialization` | 0.667 | 0.799 | 0.799 | 3 |
26
+ | 1,000 | `rebuild_index` | 390.421 | 398.467 | 398.467 | 3 |
27
+ | 1,000 | `search_candidate_process_cold` | 388.067 | 388.067 | 388.067 | 1 |
28
+ | 1,000 | `search_candidate_warm` | 386.328 | 388.011 | 388.011 | 3 |
29
+ | 1,000 | `exact_memory_id_lookup` | 385.124 | 386.845 | 386.845 | 3 |
30
+ | 1,000 | `fulltext_search` | 385.229 | 389.206 | 389.206 | 3 |
31
+ | 1,000 | `scope_filtered_search` | 369.218 | 370.537 | 370.537 | 3 |
32
+ | 1,000 | `list_todos` | 337.284 | 363.616 | 363.616 | 3 |
33
+ | 1,000 | `read` | 337.300 | 338.413 | 338.413 | 3 |
34
+ | 1,000 | `create` | 1149.732 | 1218.470 | 1218.470 | 3 |
35
+ | 1,000 | `update` | 1068.737 | 1132.795 | 1132.795 | 3 |
36
+ | 1,000 | `no_change` | 1109.159 | 1185.680 | 1185.680 | 3 |
37
+ | 1,000 | `history_write` | 3.047 | 3.392 | 3.392 | 3 |
38
+ | 1,000 | `closed_todo_retirement` | 835.599 | 843.429 | 843.429 | 3 |
39
+ | 1,000 | `history_pruning` | 572.614 | 578.825 | 578.825 | 3 |
40
+ | 1,000 | `compaction_snapshot` | 356.001 | 370.681 | 370.681 | 3 |
41
+ | 1,000 | `vault_lock_hold_search` | 381.196 | 381.196 | 381.196 | 1 |
42
+ | 10,000 | `vault_initialization` | 0.710 | 0.835 | 0.835 | 3 |
43
+ | 10,000 | `rebuild_index` | 4058.684 | 4157.842 | 4157.842 | 3 |
44
+ | 10,000 | `search_candidate_process_cold` | 3809.702 | 3809.702 | 3809.702 | 1 |
45
+ | 10,000 | `search_candidate_warm` | 3700.780 | 3752.602 | 3752.602 | 3 |
46
+ | 10,000 | `exact_memory_id_lookup` | 3925.242 | 3934.726 | 3934.726 | 3 |
47
+ | 10,000 | `fulltext_search` | 3726.453 | 3854.321 | 3854.321 | 3 |
48
+ | 10,000 | `scope_filtered_search` | 3643.267 | 3805.982 | 3805.982 | 3 |
49
+ | 10,000 | `list_todos` | 3480.108 | 3481.024 | 3481.024 | 3 |
50
+ | 10,000 | `read` | 3419.434 | 3502.751 | 3502.751 | 3 |
51
+ | 10,000 | `create` | 10926.382 | 11013.296 | 11013.296 | 3 |
52
+ | 10,000 | `update` | 10950.698 | 11030.074 | 11030.074 | 3 |
53
+ | 10,000 | `no_change` | 10976.892 | 11187.753 | 11187.753 | 3 |
54
+ | 10,000 | `history_write` | 14.652 | 40.393 | 40.393 | 3 |
55
+ | 10,000 | `closed_todo_retirement` | 7271.686 | 7683.019 | 7683.019 | 3 |
56
+ | 10,000 | `history_pruning` | 4214.574 | 5079.721 | 5079.721 | 3 |
57
+ | 10,000 | `compaction_snapshot` | 3625.947 | 3808.937 | 3808.937 | 3 |
58
+ | 10,000 | `vault_lock_hold_search` | 3721.875 | 3721.875 | 3721.875 | 1 |
59
+ | 50,000 | `vault_initialization` | 0.600 | 0.759 | 0.759 | 3 |
60
+ | 50,000 | `rebuild_index` | 20435.382 | 20560.472 | 20560.472 | 2 |
61
+ | 50,000 | `search_candidate_process_cold` | 19243.749 | 19243.749 | 19243.749 | 1 |
62
+ | 50,000 | `search_candidate_warm` | 19148.126 | 19553.817 | 19553.817 | 3 |
63
+ | 50,000 | `exact_memory_id_lookup` | 19556.986 | 19834.327 | 19834.327 | 3 |
64
+ | 50,000 | `fulltext_search` | 19855.403 | 20002.997 | 20002.997 | 3 |
65
+ | 50,000 | `scope_filtered_search` | 18576.396 | 18639.123 | 18639.123 | 3 |
66
+ | 50,000 | `list_todos` | 18160.793 | 18328.362 | 18328.362 | 3 |
67
+ | 50,000 | `read` | 17648.840 | 18197.055 | 18197.055 | 3 |
68
+ | 50,000 | `create` | 56061.953 | 56745.539 | 56745.539 | 3 |
69
+ | 50,000 | `update` | 55893.789 | 56715.012 | 56715.012 | 3 |
70
+ | 50,000 | `no_change` | 55934.604 | 56323.055 | 56323.055 | 3 |
71
+ | 50,000 | `history_write` | 5.971 | 70.687 | 70.687 | 3 |
72
+ | 50,000 | `closed_todo_retirement` | 37710.833 | 37795.840 | 37795.840 | 3 |
73
+ | 50,000 | `history_pruning` | 21802.002 | 21843.263 | 21843.263 | 3 |
74
+ | 50,000 | `compaction_snapshot` | 19330.915 | 19619.114 | 19619.114 | 3 |
75
+ | 50,000 | `vault_lock_hold_search` | 19650.941 | 19650.941 | 19650.941 | 1 |
76
+
77
+ ## Resource footprint
78
+
79
+ | Active memories | Peak RSS MiB | Vault MiB | `_index/` MiB | `_state/` MiB |
80
+ |---:|---:|---:|---:|---:|
81
+ | 1,000 | 50.871 | 2.174 | 0.170 | 0.000 |
82
+ | 10,000 | 259.891 | 21.156 | 1.458 | 0.000 |
83
+ | 50,000 | 1186.762 | 105.841 | 7.157 | 0.000 |
84
+
85
+ ## Scale interpretation
86
+
87
+ The 2,000 ms search, 120 s rebuild, and 1,024 MiB RSS values are retained as stress references only. A miss at 50,000 active memories is capacity evidence, not a release blocker and not a reason to introduce a new database/search backend.
88
+
89
+ **Architecture change required from the 50k stress result: no.** 50k active memories is retained as an extreme stress boundary, not a normal steady-state assumption. The normal retrieval path is Scope Map -> scope-constrained search -> read, while UPDATE/NO_CHANGE, todo retirement, bounded history, and compaction are expected to control active-memory growth. Stress misses are recorded for capacity visibility and do not justify adding another storage/search backend.
90
+
91
+ If a real Vault grows into the tens of thousands of active memories, first audit CREATE-vs-UPDATE/NO_CHANGE behavior, todo retirement, history retention, duplicate control, and compaction. The product remains Markdown-only in v0.2.28.
92
+
93
+ ## Active-memory lifecycle health
94
+
95
+ Active memory count is a health signal, not an archival counter. Repeated facts should update existing canonical memories, unchanged observations should be NO_CHANGE, completed/cancelled todos retire from active memory, historical versions are bounded, and compaction reduces redundant active material. A real Vault approaching the 50k stress dataset should therefore trigger lifecycle/quality investigation before search-backend expansion.
96
+
97
+ ## Runtime state growth audit
98
+
99
+ The retrieval ledger is already TTL- and count-bounded, and per-session pending tool evidence/injection bookkeeping is bounded. The processed-turn/session ledger retains replay/idempotency evidence and is therefore intentionally not destructively pruned in v0.2.28: deleting it without a new durable replay checkpoint would risk duplicate replay. Future state compaction must first define a verified checkpoint that proves older replay evidence is no longer needed.
@@ -171,9 +171,7 @@ done
171
171
  export PATH="$venv_path/bin:$user_bin:$PATH"
172
172
  "$venv_path/bin/memleaf" init \
173
173
  --vault "$vault_path" \
174
- --no-codex \
175
- --no-hermes \
176
- --no-antigravity
174
+ --no-hermes
177
175
 
178
176
  hermes_bin=""
179
177
  candidate_hermes=$(command -v hermes 2>/dev/null || true)
@@ -265,7 +263,7 @@ from __future__ import annotations
265
263
  import sys
266
264
  from pathlib import Path
267
265
 
268
- from memleaf.adapters.base import update_agents_index
266
+ from memleaf.adapters.base import update_agents_state
269
267
 
270
268
  vault_path = Path(sys.argv[1]).expanduser()
271
269
  hermes_executable = str(Path(sys.argv[2]).expanduser().resolve())
@@ -283,8 +281,8 @@ updates = {
283
281
  "user_action_required": False,
284
282
  }
285
283
  }
286
- if not update_agents_index(vault_path / "_index" / "agents.json", updates):
287
- raise SystemExit("could not update Hermes provider status in agents index")
284
+ if not update_agents_state(vault_path / "_state" / "agents.json", updates):
285
+ raise SystemExit("could not update Hermes provider status in agents state")
288
286
  PY
289
287
 
290
288
  # Configure the active MCP entry through Hermes' official CLI. Keep this
@@ -332,17 +330,17 @@ from __future__ import annotations
332
330
  import sys
333
331
  from pathlib import Path
334
332
 
335
- from memleaf.adapters.base import update_agents_index
333
+ from memleaf.adapters.base import update_agents_state
336
334
 
337
335
  vault_path = Path(sys.argv[1]).expanduser()
338
- if not update_agents_index(
339
- vault_path / "_index" / "agents.json",
336
+ if not update_agents_state(
337
+ vault_path / "_state" / "agents.json",
340
338
  {"hermes": {"mcp_status": "failed", "mcp_availability": "unavailable"}},
341
339
  ):
342
- raise SystemExit("could not record Hermes MCP failure in agents index")
340
+ raise SystemExit("could not record Hermes MCP failure in agents state")
343
341
  PY
344
342
  then
345
- die "could not record Hermes MCP failure in agents index"
343
+ die "could not record Hermes MCP failure in agents state"
346
344
  fi
347
345
  die "Hermes memleaf MCP configuration/test failed"
348
346
  fi
@@ -372,14 +370,14 @@ from __future__ import annotations
372
370
  import sys
373
371
  from pathlib import Path
374
372
 
375
- from memleaf.adapters.base import update_agents_index
373
+ from memleaf.adapters.base import update_agents_state
376
374
 
377
375
  vault_path = Path(sys.argv[1]).expanduser()
378
- if not update_agents_index(
379
- vault_path / "_index" / "agents.json",
376
+ if not update_agents_state(
377
+ vault_path / "_state" / "agents.json",
380
378
  {"hermes": {"mcp_status": "active", "mcp_availability": "available"}},
381
379
  ):
382
- raise SystemExit("could not update Hermes MCP status in agents index")
380
+ raise SystemExit("could not update Hermes MCP status in agents state")
383
381
  PY
384
382
  printf 'Hermes memory provider: verified active\n'
385
383
  fi
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.27"
7
+ version = "0.2.29"
8
8
  description = "A local-first Markdown memory core for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  """Local-first Markdown memory core for AI agents."""
2
2
 
3
- __version__ = "0.2.27"
3
+ __version__ = "0.2.29"
4
4
 
5
5
  from .config import DEFAULT_CONFIG, default_config, load_config, save_config
6
6
  from .frontmatter import FrontmatterError, dump_frontmatter, dump_yaml, load_yaml, parse_frontmatter
@@ -209,11 +209,11 @@ def hook_definition_fingerprint(definition: Mapping[str, Any]) -> str:
209
209
  return hashlib.sha256(payload).hexdigest()
210
210
 
211
211
 
212
- def agent_index_path(vault: Path | str) -> Path:
213
- """Return the agents index path without creating or changing the vault."""
212
+ def agent_state_path(vault: Path | str) -> Path:
213
+ """Return the host activation state path without creating or changing the vault."""
214
214
 
215
215
  root = vault if isinstance(vault, (str, os.PathLike)) else getattr(vault, "root", vault)
216
- return Path(root).expanduser().resolve() / "_index" / "agents.json"
216
+ return Path(root).expanduser().resolve() / "_state" / "agents.json"
217
217
 
218
218
 
219
219
  def _read_agents_index(path: Path) -> dict[str, Any] | None:
@@ -245,7 +245,7 @@ def hook_activation_status(
245
245
  ) -> str:
246
246
  """Keep ``active`` only when it belongs to the current hook definition."""
247
247
 
248
- index = _read_agents_index(agent_index_path(vault))
248
+ index = _read_agents_index(agent_state_path(vault))
249
249
  if index is None:
250
250
  return pending_status
251
251
  entry = index["agents"].get(agent)
@@ -259,7 +259,7 @@ def hook_activation_status(
259
259
  return pending_status
260
260
 
261
261
 
262
- def update_agents_index(
262
+ def update_agents_state(
263
263
  path: Path | str,
264
264
  updates: Mapping[str, Mapping[str, Any]],
265
265
  ) -> bool:
@@ -301,7 +301,7 @@ def update_agents_index(
301
301
  def mark_hook_active(vault: Path | str, agent: str) -> bool:
302
302
  """Record a successful real hook invocation without touching hook trust."""
303
303
 
304
- target = agent_index_path(vault)
304
+ target = agent_state_path(vault)
305
305
  if target.is_symlink() or target.parent.is_symlink():
306
306
  return False
307
307
  lock_path = target.parent / "vault.lock"
@@ -293,6 +293,19 @@ COVERAGE_REASONS = frozenset({"query_only", "assistant_restatement", "retrieved_
293
293
  "no_future_value", "exact_duplicate", "quoted_or_example", "negated", "already_completed",
294
294
  "scope_ambiguous", "scope_conflict", "ownership_ambiguous", "target_ambiguous", "coverage_unresolved"})
295
295
 
296
+ # Coverage is a semantic accounting ledger, not a second candidate decision.
297
+ # Keep the disposition implied by the reason so a model cannot leave known
298
+ # read-only/irrelevant evidence retryable, or mark unresolved evidence clean.
299
+ _NO_CHANGE_COVERAGE_REASONS = frozenset({
300
+ "query_only", "assistant_restatement", "retrieved_memory_only",
301
+ "no_future_value", "exact_duplicate", "quoted_or_example", "negated",
302
+ "already_completed",
303
+ })
304
+ _DEFERRED_COVERAGE_REASONS = frozenset({
305
+ "scope_ambiguous", "scope_conflict", "ownership_ambiguous",
306
+ "target_ambiguous", "coverage_unresolved",
307
+ })
308
+
296
309
 
297
310
  def parse_coverage(value: Any, units: Iterable[EvidenceUnit], candidates: Iterable[Mapping[str, Any]], *, require_complete: bool = True) -> dict[str, dict[str, Any]]:
298
311
  """Validate accounting without trusting the model's evidence identities."""
@@ -317,12 +330,22 @@ def parse_coverage(value: Any, units: Iterable[EvidenceUnit], candidates: Iterab
317
330
  if any(units[uid].event_key not in candidates[i]["evidence_event_ids"] for i in ids):
318
331
  raise ModelOutputError("coverage event mismatch", validation_detail="invalid_evidence")
319
332
  elif decision in {"NO_CHANGE", "DEFERRED"}:
320
- if (row.get("candidate_ids") or not isinstance(row.get("reason"), str)
321
- or row.get("reason") not in COVERAGE_REASONS):
333
+ reason = row.get("reason")
334
+ if (row.get("candidate_ids") or not isinstance(reason, str)
335
+ or reason not in COVERAGE_REASONS):
322
336
  raise ModelOutputError("invalid coverage decision", validation_detail="invalid_evidence")
337
+ # Normalize only the model's declared reason. This keeps the
338
+ # protocol source-neutral: no local topic or business heuristic
339
+ # decides whether a fragment is retryable.
340
+ if reason in _NO_CHANGE_COVERAGE_REASONS:
341
+ decision = "NO_CHANGE"
342
+ elif reason in _DEFERRED_COVERAGE_REASONS:
343
+ decision = "DEFERRED"
323
344
  else:
324
345
  raise ModelOutputError("unknown coverage decision", validation_detail="invalid_evidence")
325
- result[uid] = dict(row)
346
+ normalized = dict(row)
347
+ normalized["decision"] = decision
348
+ result[uid] = normalized
326
349
  if require_complete and set(result) != set(units):
327
350
  raise ModelOutputError("incomplete evidence coverage", validation_detail="invalid_evidence")
328
351
  return result
@@ -376,6 +399,10 @@ def evidence_prompt(units: Iterable[EvidenceUnit]) -> str:
376
399
  '{"unit_id":"supplied id","decision":"CANDIDATE","candidate_ids":["id"]} or '
377
400
  '{"unit_id":"supplied id","decision":"NO_CHANGE or DEFERRED","reason":"reason"}. '
378
401
  'Allowed reasons: ' + ', '.join(sorted(COVERAGE_REASONS)) + '. '
402
+ 'Use NO_CHANGE only with reasons: ' + ', '.join(sorted(_NO_CHANGE_COVERAGE_REASONS)) + '. '
403
+ 'Use DEFERRED only with reasons: ' + ', '.join(sorted(_DEFERRED_COVERAGE_REASONS)) + '. '
404
+ 'Tool records retained with retention=metadata may appear in the event envelope but are not evidence units: '
405
+ 'do not invent a unit ID for them or bind their call ID, digest, tool name, or other metadata. '
379
406
  'Only actual user assertions or complete external observations can support writes. User origin labels are hints. Questions, examples, '
380
407
  'retrieved memories and assistant synthesis cannot. Account for missing or ambiguous facts '
381
408
  'as DEFERRED; do not invent a candidate to satisfy coverage. Interpret mixed assertions '
@@ -29,11 +29,6 @@ def _timestamp() -> str:
29
29
 
30
30
 
31
31
 
32
- _MAIL_EVIDENCE_FIELDS = frozenset({"message_id", "subject", "sender", "domain"})
33
- _MAIL_DOMAIN_RE = re.compile(r"^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$", re.IGNORECASE)
34
- _MAX_MAIL_EVIDENCE_ITEMS = 8
35
- _MAX_MAIL_EVIDENCE_TEXT = 320
36
-
37
32
 
38
33
  def _normalize_tool_evidence(value: Any) -> list[dict[str, str]]:
39
34
  from .provenance import normalize_tool_evidence
@@ -241,19 +236,19 @@ def capture_event(
241
236
  return CaptureResult(resolved_event_id, stored=False, duplicate=False, content=safe_content)
242
237
 
243
238
  with vault.lock():
244
- processed = _read_processed(vault.processed_index_path)
239
+ processed = _read_processed(vault.processed_state_path)
245
240
  from .recording_policy import apply_control
246
241
  allowed, changed = apply_control(processed, source=source, session_id=session_id,
247
242
  turn_key=resolved_turn_key, event_key=resolved_event_key, role=role, content=content, record=record)
248
243
  if not allowed:
249
244
  if changed:
250
- atomic_write_json(vault.processed_index_path, processed)
245
+ atomic_write_json(vault.processed_state_path, processed)
251
246
  return CaptureResult(resolved_event_id, stored=False, duplicate=False, suppressed=True)
252
247
  # Only permitted data reaches normalization or any persistence path.
253
248
  from .evidence_policy import retain_tool_evidence
254
249
  safe_tool_evidence = retain_tool_evidence(tool_evidence, vault.config())
255
250
  if changed:
256
- atomic_write_json(vault.processed_index_path, processed)
251
+ atomic_write_json(vault.processed_state_path, processed)
257
252
  known_keys = _known_event_keys(vault, processed)
258
253
  if resolved_event_key in known_keys:
259
254
  path = vault.session_path(source, session_id)
@@ -328,7 +323,7 @@ def capture_event(
328
323
  new_state["processing"] = old_state["processing"]
329
324
  sessions[session_key] = new_state
330
325
  atomic_write_json(
331
- vault.processed_index_path,
326
+ vault.processed_state_path,
332
327
  {
333
328
  **processed,
334
329
  "version": 1,
@@ -17,7 +17,7 @@ from .adapters.base import (
17
17
  ConfigureResult,
18
18
  Detection,
19
19
  result_from_detection,
20
- update_agents_index,
20
+ update_agents_state,
21
21
  )
22
22
  from .adapters.hermes import HermesAdapter
23
23
  from .credentials import credential_text
@@ -43,13 +43,13 @@ def build_parser() -> argparse.ArgumentParser:
43
43
  init.add_argument(
44
44
  "--no-codex",
45
45
  action="store_true",
46
- help="compatibility no-op; use install --host codex for explicit Codex setup",
46
+ help="deprecated compatibility no-op through 0.2.x; use install --host codex (removal planned for 0.3)",
47
47
  )
48
48
  init.add_argument("--no-hermes", action="store_true", help="disable Hermes setup")
49
49
  init.add_argument(
50
50
  "--no-antigravity",
51
51
  action="store_true",
52
- help="accepted for compatibility; Antigravity is currently unsupported",
52
+ help="deprecated compatibility no-op through 0.2.x; Antigravity is unsupported (removal planned for 0.3)",
53
53
  )
54
54
  init.add_argument(
55
55
  "--no-model-discovery",
@@ -366,7 +366,7 @@ def _init(args: argparse.Namespace) -> dict:
366
366
 
367
367
  # Retain legacy init result slots without implicitly configuring hosts.
368
368
  # Codex is supported only through the explicit install command; Antigravity
369
- # remains unsupported. Clear stale activation claims in our own index only.
369
+ # remains unsupported. Clear stale activation claims in our own state only.
370
370
  legacy_slots = (
371
371
  (
372
372
  "codex",
@@ -396,15 +396,15 @@ def _init(args: argparse.Namespace) -> dict:
396
396
  )
397
397
 
398
398
  agents = {name: result.to_dict() for name, result in results.items()}
399
- agents_index_written = False
399
+ agents_state_written = False
400
400
  if not args.dry_run:
401
- agents_index_written = update_agents_index(vault.agents_index_path, agents)
401
+ agents_state_written = update_agents_state(vault.agents_state_path, agents)
402
402
 
403
403
  return {
404
404
  "version": 1,
405
405
  "vault": str(vault.root),
406
- "agents_index_path": str(vault.agents_index_path),
407
- "agents_index_written": agents_index_written,
406
+ "agents_state_path": str(vault.agents_state_path),
407
+ "agents_state_written": agents_state_written,
408
408
  "dry_run": bool(args.dry_run),
409
409
  "agents": agents,
410
410
  "model": model_result,
@@ -565,9 +565,9 @@ def _print_human_result(output: dict) -> None:
565
565
  else:
566
566
  print(f"model: {model.get('status')}")
567
567
  if output["dry_run"]:
568
- print(f"agents index not written: {output['agents_index_path']}")
568
+ print(f"agents state not written: {output['agents_state_path']}")
569
569
  else:
570
- print(f"agents index: {output['agents_index_path']}")
570
+ print(f"agents state: {output['agents_state_path']}")
571
571
 
572
572
 
573
573
  if __name__ == "__main__": # pragma: no cover - exercised by subprocess smoke tests.
@@ -1,4 +1,4 @@
1
- """Local, deterministic active-memory compaction for stage B3a."""
1
+ """Local, deterministic active-memory compaction."""
2
2
 
3
3
  from __future__ import annotations
4
4