memleaf 0.2.26__tar.gz → 0.2.28__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.
- {memleaf-0.2.26 → memleaf-0.2.28}/CHANGELOG.md +18 -0
- {memleaf-0.2.26/src/memleaf.egg-info → memleaf-0.2.28}/PKG-INFO +27 -5
- {memleaf-0.2.26 → memleaf-0.2.28}/README.en.md +26 -4
- {memleaf-0.2.26 → memleaf-0.2.28}/README.md +26 -4
- memleaf-0.2.28/docs/config-migrations.md +37 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/docs/general-processing.md +1 -1
- memleaf-0.2.28/docs/performance.md +99 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/install.sh +13 -15
- {memleaf-0.2.26 → memleaf-0.2.28}/pyproject.toml +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/adapters/base.py +6 -6
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/capture.py +4 -9
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/cli.py +10 -10
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/compaction.py +94 -65
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/config.py +59 -13
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/evidence_policy.py +11 -22
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/hermes_provider/__init__.py +0 -82
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/host_runtime.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/index.py +0 -61
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/inspection.py +3 -3
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/installer.py +6 -6
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/llm/__init__.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/llm/base.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/mcp_server.py +2 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/memory_commit.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/memory_planner.py +19 -75
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/memory_writer.py +23 -17
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/planning_context.py +11 -136
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/process_common.py +0 -18
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/process_journal.py +7 -7
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/processing.py +2 -2
- memleaf-0.2.28/src/memleaf/prompts.py +368 -0
- memleaf-0.2.28/src/memleaf/retention.py +192 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/retrieval_gate.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/scope_maintenance.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/service.py +47 -40
- memleaf-0.2.28/src/memleaf/source_policy.py +125 -0
- memleaf-0.2.28/src/memleaf/state_layout.py +279 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/validation.py +3 -411
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/vault.py +63 -50
- {memleaf-0.2.26 → memleaf-0.2.28/src/memleaf.egg-info}/PKG-INFO +27 -5
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf.egg-info/SOURCES.txt +10 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_admission_noise.py +31 -47
- memleaf-0.2.28/tests/test_config_migrations_v028.py +42 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_cross_turn_dedupe_regressions.py +5 -9
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_email_actionable_coverage.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_evidence_retention_policy.py +8 -8
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_extraction_quality_regressions.py +23 -99
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_general_evidence_admission.py +3 -3
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_global_todo_query_no_write.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_hermes_provider.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_hermes_runtime_install.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_host_events.py +3 -3
- memleaf-0.2.28/tests/test_inspection_state_v028.py +33 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_install.py +5 -5
- memleaf-0.2.28/tests/test_long_run_hygiene.py +179 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_maintenance_v2.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_model_owned_fields.py +12 -8
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_phase2_model_decisions.py +3 -3
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_pypi_install.py +3 -3
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_retrieval_gate.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_session_lineage.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_shared_memory_refactor.py +3 -3
- memleaf-0.2.28/tests/test_source_neutral_todos_v028.py +49 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_a.py +6 -6
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b1.py +71 -113
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b2a.py +9 -10
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b2b.py +4 -4
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3a_commit.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3b_native_context.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3b_scope.py +3 -3
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3d_scope_maintenance.py +17 -18
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_c2_init.py +14 -14
- memleaf-0.2.28/tests/test_state_layout_v028.py +200 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_update_target_recovery.py +4 -4
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_v023_scope_correction.py +17 -8
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_v2_gate_limits.py +1 -1
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_v2_host_flow.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_v2_mcp_flow.py +2 -2
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_v2_search_gate_acceptance.py +1 -1
- memleaf-0.2.26/src/memleaf/prompts.py +0 -681
- {memleaf-0.2.26 → memleaf-0.2.28}/IMPLEMENTATION_PLAN.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/LICENSE +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/MANIFEST.in +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/RELEASE_CHECKLIST.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/docs/core-refactor.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/examples/README.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/examples/basic_usage.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/examples/live_processing_acceptance.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/install.ps1 +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/setup.cfg +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/model_execution.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/models.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/src/memleaf.egg-info/top_level.txt +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/__init__.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/semantic_fixtures.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_codex_install.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_codex_native_cli.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_context_budget.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_credential_safety.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_cross_host_acceptance.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_general_tool_provenance.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_global_todo_acceptance.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_global_todo_retrieval.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_hermes_native_registration.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_hermes_stdio_transport.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_host_runtime_contract.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_model_discovery.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_process_owner_locking.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_processing_contract_v026.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_retrieval_v2.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3a_contract.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3b_native_index.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_b3c_retrieval.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_c1_mcp.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_stage_c3_packaging.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_todo_state_recovery.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_upgrade_preserves_vault.py +0 -0
- {memleaf-0.2.26 → memleaf-0.2.28}/tests/test_v2_nomatch_semantics.py +0 -0
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to memleaf are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.28 — 2026-09-06
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- 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`.
|
|
9
|
+
- 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.
|
|
10
|
+
- Remove the remaining Core urgency-word classifier for unscheduled todos so ordering stays source-neutral; model-owned business semantics remain outside deterministic Core validation.
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
|
|
14
|
+
## 0.2.27 — 2026-09-05
|
|
15
|
+
|
|
16
|
+
- 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.
|
|
17
|
+
- Bound per-memory provenance to 16 retained source rows while preserving cumulative source count/digest metadata, preventing repeated UPDATEs and copied history versions from growing `sources` without limit.
|
|
18
|
+
- Retire completed/cancelled todos from active `knowledge/` after a configurable grace period (30 days by default) while keeping them queryable through historical todo enumeration.
|
|
19
|
+
- Add bounded history retention (`3650` days and `32` complete versions per stable identity by default) with an explicit `keep_all` opt-out for audit-oriented Vaults.
|
|
20
|
+
- Preserve an existing canonical memory identity during compaction instead of generating `mem-compact-*` IDs; rollback remains journaled and crash-safe.
|
|
21
|
+
- Keep Markdown as the source of truth, retain zero runtime dependencies and no daemon, and preserve Hermes/Codex shared-Vault retrieval and write contracts.
|
|
22
|
+
|
|
5
23
|
## 0.2.26 — 2026-09-05
|
|
6
24
|
|
|
7
25
|
- Refactor automatic processing into explicit model execution, planning, update coordination, commit, journal and audit owners while preserving Markdown Vaults, memory IDs, Hermes MemoryProvider and cross-Agent retrieval contracts. No runtime dependency or independent service is added.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.28
|
|
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
|
-
> 核心库、Vault、stdio MCP Server、初始化 CLI
|
|
26
|
+
> **版本:0.2.27。**
|
|
27
|
+
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版收口长期运行治理:Core 语义判断保持 source-neutral,并为来源、关闭待办、历史版本和压缩身份增加有界生命周期。真实模型语义效果仍需结合本地模型和代表性样本验收。
|
|
28
28
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
29
29
|
|
|
30
30
|
## 项目定位
|
|
@@ -417,6 +417,28 @@ $HOME/.memleaf/
|
|
|
417
417
|
|
|
418
418
|
`knowledge/` 和 `history/` 是可读数据;`_index/` 主要是派生索引或运行状态。删除或直接修改 `_index/` 可能丢失处理水位、Hook 游标或当前检索账本;需要重建时优先使用 `rebuild_index()`。
|
|
419
419
|
|
|
420
|
+
### 长期运行与保留策略
|
|
421
|
+
|
|
422
|
+
默认配置对长期增长做三层治理,而不引入后台服务:
|
|
423
|
+
|
|
424
|
+
```yaml
|
|
425
|
+
process:
|
|
426
|
+
memory_compact_threshold_tokens: 100000
|
|
427
|
+
memory_compact_candidate_ratio: 0.30
|
|
428
|
+
inbox_cleanup_hours: 24
|
|
429
|
+
closed_todo_retention_days: 30
|
|
430
|
+
history:
|
|
431
|
+
policy: bounded
|
|
432
|
+
retention_days: 3650
|
|
433
|
+
max_versions_per_memory: 32
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- active Markdown 每条最多保留 16 个来源明细,同时记录累计 `source_count`、`source_digest` 和省略数量,避免 UPDATE 让 `sources` 无限膨胀;
|
|
437
|
+
- 已完成/已取消 todo 默认在 30 天后退出 `knowledge/` 并进入 `history/`;`list_todos(status=completed|cancelled|all)` 仍可枚举这些历史待办;
|
|
438
|
+
- `history.policy: bounded` 默认每个稳定记忆身份最多保留 32 个完整历史版本,且超过 3650 天的版本可被维护流程淘汰;需要永久审计时可显式设置 `history.policy: keep_all`;
|
|
439
|
+
- compaction 会保留一个既有 canonical `memory_id`,不会再为整理结果创建新的 `mem-compact-*` 身份;单条精简保持原 ID,多条合并选择一个稳定 survivor。
|
|
440
|
+
- 这些维护动作在正常 `process()` / `remember()` / `compact()` 生命周期中执行,不需要 daemon;普通只读检索本身不触发维护写入。
|
|
441
|
+
|
|
420
442
|
默认目录尽量使用 `0700`,文件默认明文保存。memleaf 没有内置加密层,请自行保护 Vault、备份和模型调用凭证。
|
|
421
443
|
|
|
422
444
|
## 隐私与安全边界
|
|
@@ -457,7 +479,7 @@ python -m build --wheel --sdist
|
|
|
457
479
|
- 没有模型路由时只能捕获和检索,不能完成自动提炼、显式记忆或压缩;
|
|
458
480
|
- 真实宿主长期运行效果仍取决于本机 Agent 版本、配置、重启和模型可用性;
|
|
459
481
|
- 不提供 Obsidian 插件、Web 管理界面、云同步或透明加密层;
|
|
460
|
-
-
|
|
482
|
+
- 普通只读检索不会触发保留策略写入;历史淘汰和关闭待办退休只在正常处理/维护生命周期中执行。
|
|
461
483
|
|
|
462
484
|
|
|
463
485
|
## License
|
|
@@ -468,7 +490,7 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
468
490
|
*Your memories, in files you own.*
|
|
469
491
|
|
|
470
492
|
|
|
471
|
-
## 通用处理与只读验收(0.2.
|
|
493
|
+
## 通用处理与只读验收(0.2.27)
|
|
472
494
|
|
|
473
495
|
邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
|
|
474
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.
|
|
8
|
-
> The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. This
|
|
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.
|
|
9
9
|
> **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
|
|
10
10
|
|
|
11
11
|
## Project scope
|
|
@@ -400,6 +400,28 @@ $HOME/.memleaf/
|
|
|
400
400
|
|
|
401
401
|
`knowledge/` and `history/` are human-readable data. `_index/` contains derived indexes and runtime state. Deleting or editing it directly can lose processing watermarks, host cursors, or the current retrieval ledger; use `rebuild_index()` when a rebuild is needed.
|
|
402
402
|
|
|
403
|
+
### Long-run retention
|
|
404
|
+
|
|
405
|
+
The default configuration bounds long-run growth without adding a background service:
|
|
406
|
+
|
|
407
|
+
```yaml
|
|
408
|
+
process:
|
|
409
|
+
memory_compact_threshold_tokens: 100000
|
|
410
|
+
memory_compact_candidate_ratio: 0.30
|
|
411
|
+
inbox_cleanup_hours: 24
|
|
412
|
+
closed_todo_retention_days: 30
|
|
413
|
+
history:
|
|
414
|
+
policy: bounded
|
|
415
|
+
retention_days: 3650
|
|
416
|
+
max_versions_per_memory: 32
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
- Each active Markdown memory retains at most 16 detailed provenance rows while tracking cumulative `source_count`, `source_digest`, and omitted rows, so repeated UPDATEs cannot grow `sources` without bound.
|
|
420
|
+
- Completed/cancelled todos leave `knowledge/` after 30 days by default and move to `history/`; `list_todos(status=completed|cancelled|all)` can still enumerate retired todos.
|
|
421
|
+
- With `history.policy: bounded`, each stable memory identity keeps at most 32 full historical versions, and versions older than 3650 days are eligible for pruning. Set `history.policy: keep_all` explicitly when permanent audit retention is required.
|
|
422
|
+
- Compaction preserves an existing canonical `memory_id` instead of creating a new `mem-compact-*` identity; a single-memory rewrite keeps its ID and a multi-memory merge chooses one stable survivor.
|
|
423
|
+
- Maintenance runs through normal `process()` / `remember()` / `compact()` lifecycle calls and requires no daemon. Ordinary read-only retrieval does not trigger maintenance writes.
|
|
424
|
+
|
|
403
425
|
Directories are normally created with mode `0700`, and files are stored as plaintext by default. memleaf has no built-in encryption layer; protect the Vault, backups, and model credentials yourself.
|
|
404
426
|
|
|
405
427
|
## Privacy and security boundaries
|
|
@@ -440,7 +462,7 @@ The following should not be interpreted as delivered capabilities:
|
|
|
440
462
|
- Without a model route, memleaf can capture and retrieve but cannot perform automatic extraction, explicit model-backed memory, or compaction.
|
|
441
463
|
- Long-running real-host behavior still depends on the local Agent version, configuration, restart, and model availability.
|
|
442
464
|
- There is no Obsidian plugin, web management UI, cloud sync, or transparent encryption layer.
|
|
443
|
-
- Routine retrieval never performs
|
|
465
|
+
- Routine read-only retrieval never performs retention writes; history pruning and closed-todo retirement run only during normal processing/maintenance lifecycle calls.
|
|
444
466
|
|
|
445
467
|
|
|
446
468
|
## License
|
|
@@ -452,7 +474,7 @@ MIT; see [LICENSE](LICENSE).
|
|
|
452
474
|
*Your memories, in files you own.*
|
|
453
475
|
|
|
454
476
|
|
|
455
|
-
## General processing and read-only inspection (0.2.
|
|
477
|
+
## General processing and read-only inspection (0.2.27)
|
|
456
478
|
|
|
457
479
|
Dialogue, calendars, tickets, files, web results and other tools share the evidence, coverage and write path.
|
|
458
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.
|
|
8
|
-
> 核心库、Vault、stdio MCP Server、初始化 CLI
|
|
7
|
+
> **版本:0.2.27。**
|
|
8
|
+
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。本版收口长期运行治理:Core 语义判断保持 source-neutral,并为来源、关闭待办、历史版本和压缩身份增加有界生命周期。真实模型语义效果仍需结合本地模型和代表性样本验收。
|
|
9
9
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
10
10
|
|
|
11
11
|
## 项目定位
|
|
@@ -398,6 +398,28 @@ $HOME/.memleaf/
|
|
|
398
398
|
|
|
399
399
|
`knowledge/` 和 `history/` 是可读数据;`_index/` 主要是派生索引或运行状态。删除或直接修改 `_index/` 可能丢失处理水位、Hook 游标或当前检索账本;需要重建时优先使用 `rebuild_index()`。
|
|
400
400
|
|
|
401
|
+
### 长期运行与保留策略
|
|
402
|
+
|
|
403
|
+
默认配置对长期增长做三层治理,而不引入后台服务:
|
|
404
|
+
|
|
405
|
+
```yaml
|
|
406
|
+
process:
|
|
407
|
+
memory_compact_threshold_tokens: 100000
|
|
408
|
+
memory_compact_candidate_ratio: 0.30
|
|
409
|
+
inbox_cleanup_hours: 24
|
|
410
|
+
closed_todo_retention_days: 30
|
|
411
|
+
history:
|
|
412
|
+
policy: bounded
|
|
413
|
+
retention_days: 3650
|
|
414
|
+
max_versions_per_memory: 32
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
- active Markdown 每条最多保留 16 个来源明细,同时记录累计 `source_count`、`source_digest` 和省略数量,避免 UPDATE 让 `sources` 无限膨胀;
|
|
418
|
+
- 已完成/已取消 todo 默认在 30 天后退出 `knowledge/` 并进入 `history/`;`list_todos(status=completed|cancelled|all)` 仍可枚举这些历史待办;
|
|
419
|
+
- `history.policy: bounded` 默认每个稳定记忆身份最多保留 32 个完整历史版本,且超过 3650 天的版本可被维护流程淘汰;需要永久审计时可显式设置 `history.policy: keep_all`;
|
|
420
|
+
- compaction 会保留一个既有 canonical `memory_id`,不会再为整理结果创建新的 `mem-compact-*` 身份;单条精简保持原 ID,多条合并选择一个稳定 survivor。
|
|
421
|
+
- 这些维护动作在正常 `process()` / `remember()` / `compact()` 生命周期中执行,不需要 daemon;普通只读检索本身不触发维护写入。
|
|
422
|
+
|
|
401
423
|
默认目录尽量使用 `0700`,文件默认明文保存。memleaf 没有内置加密层,请自行保护 Vault、备份和模型调用凭证。
|
|
402
424
|
|
|
403
425
|
## 隐私与安全边界
|
|
@@ -438,7 +460,7 @@ python -m build --wheel --sdist
|
|
|
438
460
|
- 没有模型路由时只能捕获和检索,不能完成自动提炼、显式记忆或压缩;
|
|
439
461
|
- 真实宿主长期运行效果仍取决于本机 Agent 版本、配置、重启和模型可用性;
|
|
440
462
|
- 不提供 Obsidian 插件、Web 管理界面、云同步或透明加密层;
|
|
441
|
-
-
|
|
463
|
+
- 普通只读检索不会触发保留策略写入;历史淘汰和关闭待办退休只在正常处理/维护生命周期中执行。
|
|
442
464
|
|
|
443
465
|
|
|
444
466
|
## License
|
|
@@ -449,7 +471,7 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
449
471
|
*Your memories, in files you own.*
|
|
450
472
|
|
|
451
473
|
|
|
452
|
-
## 通用处理与只读验收(0.2.
|
|
474
|
+
## 通用处理与只读验收(0.2.27)
|
|
453
475
|
|
|
454
476
|
邮件、日历、工单、文件、浏览器与普通对话共用证据准入、覆盖检查和写入路径。
|
|
455
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.
|
|
1
|
+
# General processing reliability contract — 0.2.27
|
|
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
|
|
@@ -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-
|
|
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
|
|
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
|
|
287
|
-
raise SystemExit("could not update Hermes provider status in agents
|
|
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
|
|
333
|
+
from memleaf.adapters.base import update_agents_state
|
|
336
334
|
|
|
337
335
|
vault_path = Path(sys.argv[1]).expanduser()
|
|
338
|
-
if not
|
|
339
|
-
vault_path / "
|
|
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
|
|
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
|
|
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
|
|
373
|
+
from memleaf.adapters.base import update_agents_state
|
|
376
374
|
|
|
377
375
|
vault_path = Path(sys.argv[1]).expanduser()
|
|
378
|
-
if not
|
|
379
|
-
vault_path / "
|
|
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
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.28"
|
|
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
|
|
213
|
-
"""Return the
|
|
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() / "
|
|
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(
|
|
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
|
|
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 =
|
|
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"
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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="
|
|
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
|
|
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
|
-
|
|
399
|
+
agents_state_written = False
|
|
400
400
|
if not args.dry_run:
|
|
401
|
-
|
|
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
|
-
"
|
|
407
|
-
"
|
|
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
|
|
568
|
+
print(f"agents state not written: {output['agents_state_path']}")
|
|
569
569
|
else:
|
|
570
|
-
print(f"agents
|
|
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.
|