memleaf 0.1.7__tar.gz → 0.2.0__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.1.7 → memleaf-0.2.0}/CHANGELOG.md +16 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/PKG-INFO +12 -4
- {memleaf-0.1.7 → memleaf-0.2.0}/README.en.md +11 -3
- {memleaf-0.1.7 → memleaf-0.2.0}/README.md +11 -3
- {memleaf-0.1.7 → memleaf-0.2.0}/RELEASE_CHECKLIST.md +19 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/pyproject.toml +1 -1
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/__init__.py +1 -1
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/adapters/base.py +25 -1
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/adapters/codex.py +48 -8
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/cli.py +29 -8
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/hermes_provider/__init__.py +2 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/host_events.py +4 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/installer.py +13 -2
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/mcp_server.py +26 -10
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/service.py +3 -3
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf.egg-info/PKG-INFO +12 -4
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf.egg-info/SOURCES.txt +2 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_codex_install.py +86 -4
- memleaf-0.2.0/tests/test_codex_native_cli.py +90 -0
- memleaf-0.2.0/tests/test_cross_host_acceptance.py +138 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_host_events.py +40 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_pypi_install.py +48 -2
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_retrieval_v2.py +3 -2
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_session_lineage.py +18 -10
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_c1_mcp.py +5 -2
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_c3_packaging.py +1 -1
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_v2_host_flow.py +1 -1
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_v2_mcp_flow.py +4 -4
- {memleaf-0.1.7 → memleaf-0.2.0}/IMPLEMENTATION_PLAN.md +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/LICENSE +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/MANIFEST.in +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/examples/README.md +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/examples/basic_usage.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/install.ps1 +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/install.sh +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/setup.cfg +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/__main__.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/budget.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/capture.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/compaction.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/config.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/credentials.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/inbox.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/index.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/locking.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/models.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/native_index.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/processing.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/prompts.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/redaction.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/retrieval_gate.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/validation.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf/vault.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/src/memleaf.egg-info/top_level.txt +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/__init__.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_admission_noise.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_context_budget.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_credential_safety.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_hermes_provider.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_hermes_stdio_transport.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_host_runtime_contract.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_install.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_maintenance_v2.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_model_discovery.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_retrieval_gate.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_a.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b1.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b2a.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b2b.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3a_commit.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3a_contract.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3b_native_context.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3b_native_index.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3b_scope.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3c_retrieval.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_b3d_scope_maintenance.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_stage_c2_init.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_upgrade_preserves_vault.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_v2_gate_limits.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_v2_nomatch_semantics.py +0 -0
- {memleaf-0.1.7 → memleaf-0.2.0}/tests/test_v2_search_gate_acceptance.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to memleaf are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.0 — 2026-08-31
|
|
6
|
+
|
|
7
|
+
- Hardened the supported Codex integration against the official Codex CLI 0.151.0 on Windows and macOS, including `CODEX_HOME`, npm `codex.cmd` discovery, UTF-8 CLI output, idempotent `mcp add/get`, and quote-free Windows lifecycle commands carried through a UTF-16LE PowerShell `EncodedCommand` payload.
|
|
8
|
+
- Aligned Codex `PreToolUse` argument rewriting with the current hook contract by returning `permissionDecision=allow` together with `updatedInput`, preserving the current-turn `retrieval_id` binding.
|
|
9
|
+
- Restored the finalized automatic retrieval contract: the bounded Scope Map exposes only Scope metadata, `search` candidates expose only `memory_id + title`, and selected memory bodies enter context only through bounded `read` calls under the existing retrieval gate and budgets.
|
|
10
|
+
- Added durable shared-Vault acceptance for Codex → Codex, Hermes → Codex, and Codex → Hermes so cross-session and cross-host memory reuse is verified without duplicating CREATE/UPDATE/NO_CHANGE, history, deduplication, or processing semantics outside the Core.
|
|
11
|
+
- Kept Codex installation explicitly opt-in and fail-closed: existing Codex model/provider, profiles, sandbox/approval settings, MCP entries, hooks, and custom providers remain untouched; conflicting host Vaults are rejected rather than guessed.
|
|
12
|
+
- Decoupled the Codex host from the extraction model. Automatic processing uses an independent memleaf Model Route, reports `processing_status=model_route_required` when no complete route exists, and never copies Codex credentials or silently consumes the active Codex session model.
|
|
13
|
+
- Promoted native Codex Windows/macOS acceptance, cross-host memory roundtrips, Linux/Windows/macOS host tests, and packaging checks into release-blocking CI gates.
|
|
14
|
+
|
|
15
|
+
## 0.1.8 — 2026-08-31
|
|
16
|
+
|
|
17
|
+
- Fixed Windows UTF-8 stdio handling across the MCP server, Hermes MemoryProvider, and Codex host-event entry point so Chinese and other non-ASCII memory content can be captured, searched, read, and injected without depending on the system code page.
|
|
18
|
+
- Added native Windows coverage for Codex executable discovery and Hermes MCP subprocess execution, including Unicode paths and payloads.
|
|
19
|
+
- Strengthened Windows and macOS release gates to exercise Hermes and Codex installation, host lifecycle, bounded Scope Map injection, controlled `search` → `read`, retrieval budgets, memory extraction/update semantics, and session-lineage behavior before release.
|
|
20
|
+
|
|
5
21
|
## 0.1.7 — 2026-08-31
|
|
6
22
|
|
|
7
23
|
- Added supported Codex integration through the explicit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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.
|
|
27
|
-
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.
|
|
26
|
+
> **当前版本:0.2.0。**
|
|
27
|
+
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.0 通过 PyPI 分发。
|
|
28
28
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
29
29
|
|
|
30
30
|
## 项目定位
|
|
@@ -65,7 +65,7 @@ memleaf 不会把整个 Vault 或整段历史对话自动塞进模型上下文
|
|
|
65
65
|
|
|
66
66
|
1. Agent 使用当前完整会话和 Scope Map 选择检索范围与查询词;
|
|
67
67
|
2. 至少调用一次 `search`;
|
|
68
|
-
3. `search`
|
|
68
|
+
3. `search` 候选默认只返回 `memory_id` 和标题;Scope 已由 Scope Map 与本次搜索参数确定,不随每条候选重复返回,也不直接返回正文;
|
|
69
69
|
4. 只有需要引用事实时,才使用同一轮返回的 `retrieval_id` 调用 `read`;
|
|
70
70
|
5. 根据读取到的关键记忆回答,不把所有候选全部读完。
|
|
71
71
|
|
|
@@ -172,6 +172,14 @@ python -m pip install -U memleaf && python -m memleaf install --host codex
|
|
|
172
172
|
|
|
173
173
|
安装后打开 Codex,运行 `/hooks`,审核并信任 memleaf Hook。完成授权前,MCP 可用不等于自动捕获、检索门控和提炼已经激活;安装结果会明确显示 `pending_user_review`,不会把待审核状态报告为已启用。
|
|
174
174
|
|
|
175
|
+
Codex 只作为宿主,不作为 memleaf 的提炼模型来源。已有的独立 memleaf Model Route 会继续复用;如果当前 Vault 还没有完整 Model Route,Codex 安装仍可完成 MCP/Hook 配置,但结果会明确返回 `processing_status=model_route_required`,在配置模型前不能把“自动记忆提炼”视为已就绪。memleaf 不读取、不复制也不修改 Codex 的 `model`、`model_provider`、`base_url` 或认证信息,也不会为了提炼偷偷消耗 Codex 会话额度。Codex-only 用户可针对同一个 Vault 运行:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python -m memleaf init --no-hermes --vault /path/to/the/same/vault
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
完成独立 Model Route 配置后,再使用 Codex 自动提炼。DeepSeek、OpenRouter 或其他自定义 Codex provider 因此保持原样。
|
|
182
|
+
|
|
175
183
|
Antigravity(反重力)当前不支持,安装流程不会检测、安装或修改其配置。
|
|
176
184
|
|
|
177
185
|
### 高级初始化命令(可选)
|
|
@@ -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.
|
|
8
|
-
> The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. memleaf 0.
|
|
7
|
+
> **Version: 0.2.0.**
|
|
8
|
+
> The core library, Vault, stdio MCP server, initialization CLI, model routing, memory extraction, controlled retrieval protocol, and host adapters are implemented. memleaf 0.2.0 is distributed through PyPI.
|
|
9
9
|
> **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
|
|
10
10
|
|
|
11
11
|
## Project scope
|
|
@@ -46,7 +46,7 @@ For a normal user message, the intended flow is:
|
|
|
46
46
|
|
|
47
47
|
1. The Agent uses the full current conversation and the Scope Map to choose a scope and query.
|
|
48
48
|
2. It calls `search` at least once.
|
|
49
|
-
3. `search`
|
|
49
|
+
3. `search` candidates return only `memory_id` and title by default. Scope is already selected through the Scope Map and the search input, so it is not repeated per candidate; bodies are never returned by search.
|
|
50
50
|
4. If a business fact is needed, the Agent calls `read` with the `retrieval_id` from the same turn.
|
|
51
51
|
5. The Agent answers from the selected bodies instead of reading every candidate.
|
|
52
52
|
|
|
@@ -153,6 +153,14 @@ This command reuses the one Vault already configured for Hermes or Codex, or cre
|
|
|
153
153
|
|
|
154
154
|
After installation, open Codex, run `/hooks`, and review and trust the memleaf hooks. Until that approval is complete, an available MCP server does not mean that automatic capture, retrieval gating, and processing are active. The installer reports `pending_user_review` instead of claiming that unreviewed hooks are enabled.
|
|
155
155
|
|
|
156
|
+
Codex is a host, not memleaf's extraction-model source. An existing independent memleaf Model Route is reused. If the selected Vault has no complete Model Route yet, Codex MCP/hooks can still be configured, but installation explicitly returns `processing_status=model_route_required`; automatic memory extraction is not ready until that route is configured. memleaf does not read, copy, or modify Codex `model`, `model_provider`, `base_url`, or credentials, and it does not silently spend Codex session quota for extraction. A Codex-only user can configure the same Vault with:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python -m memleaf init --no-hermes --vault /path/to/the/same/vault
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
After the independent Model Route is ready, Codex automatic extraction can use it. DeepSeek, OpenRouter, and other custom Codex providers therefore remain untouched.
|
|
163
|
+
|
|
156
164
|
Antigravity is not currently supported; the installer does not detect, install, or modify its configuration.
|
|
157
165
|
|
|
158
166
|
### Advanced initialization commands (optional)
|
|
@@ -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.
|
|
8
|
-
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.
|
|
7
|
+
> **当前版本:0.2.0。**
|
|
8
|
+
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.0 通过 PyPI 分发。
|
|
9
9
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
10
10
|
|
|
11
11
|
## 项目定位
|
|
@@ -46,7 +46,7 @@ memleaf 不会把整个 Vault 或整段历史对话自动塞进模型上下文
|
|
|
46
46
|
|
|
47
47
|
1. Agent 使用当前完整会话和 Scope Map 选择检索范围与查询词;
|
|
48
48
|
2. 至少调用一次 `search`;
|
|
49
|
-
3. `search`
|
|
49
|
+
3. `search` 候选默认只返回 `memory_id` 和标题;Scope 已由 Scope Map 与本次搜索参数确定,不随每条候选重复返回,也不直接返回正文;
|
|
50
50
|
4. 只有需要引用事实时,才使用同一轮返回的 `retrieval_id` 调用 `read`;
|
|
51
51
|
5. 根据读取到的关键记忆回答,不把所有候选全部读完。
|
|
52
52
|
|
|
@@ -153,6 +153,14 @@ python -m pip install -U memleaf && python -m memleaf install --host codex
|
|
|
153
153
|
|
|
154
154
|
安装后打开 Codex,运行 `/hooks`,审核并信任 memleaf Hook。完成授权前,MCP 可用不等于自动捕获、检索门控和提炼已经激活;安装结果会明确显示 `pending_user_review`,不会把待审核状态报告为已启用。
|
|
155
155
|
|
|
156
|
+
Codex 只作为宿主,不作为 memleaf 的提炼模型来源。已有的独立 memleaf Model Route 会继续复用;如果当前 Vault 还没有完整 Model Route,Codex 安装仍可完成 MCP/Hook 配置,但结果会明确返回 `processing_status=model_route_required`,在配置模型前不能把“自动记忆提炼”视为已就绪。memleaf 不读取、不复制也不修改 Codex 的 `model`、`model_provider`、`base_url` 或认证信息,也不会为了提炼偷偷消耗 Codex 会话额度。Codex-only 用户可针对同一个 Vault 运行:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python -m memleaf init --no-hermes --vault /path/to/the/same/vault
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
完成独立 Model Route 配置后,再使用 Codex 自动提炼。DeepSeek、OpenRouter 或其他自定义 Codex provider 因此保持原样。
|
|
163
|
+
|
|
156
164
|
Antigravity(反重力)当前不支持,安装流程不会检测、安装或修改其配置。
|
|
157
165
|
|
|
158
166
|
### 高级初始化命令(可选)
|
|
@@ -21,8 +21,20 @@
|
|
|
21
21
|
|
|
22
22
|
- [ ] Linux Python 3.11 / 3.12 / 3.13 完整 unittest 通过。
|
|
23
23
|
- [ ] Windows Python 3.11 / 3.12 / 3.13 安装与集成验收通过。
|
|
24
|
+
- [ ] macOS Python 3.11 / 3.13 Hermes/Codex 宿主与记忆语义验收通过。
|
|
24
25
|
- [ ] Windows 真实 Provider → `memleaf-mcp` stdio 回归通过:
|
|
25
26
|
`stats`、`scope_catalog`、user/assistant capture、inbox 落盘。
|
|
27
|
+
- [ ] Windows 与 macOS 使用当前受支持 Codex CLI 做真实 `codex mcp add/get` 验收,
|
|
28
|
+
Unicode/空格路径正常,重复安装为 `already_configured` 且 `changed=false`。
|
|
29
|
+
- [ ] Codex `PreToolUse` 参数重写符合当前 Hook 合同:
|
|
30
|
+
`permissionDecision=allow` 与 `updatedInput` 同时存在,当前轮 `retrieval_id` 不丢失。
|
|
31
|
+
- [ ] Windows `commandWindows` 不含会触发 Codex 当前 cmd 外层引号缺陷的内嵌双引号;
|
|
32
|
+
Python/Vault 含空格和中文时,quote-free 命令在原生 `cmd.exe` 执行回归通过。
|
|
33
|
+
- [ ] Codex → Codex、Hermes → Codex、Codex → Hermes 三条共享 Vault 持久记忆闭环全部通过。
|
|
34
|
+
- [ ] Scope Map 仍只注入 Scope/alias/检索协议;`search` 候选严格只返回
|
|
35
|
+
`memory_id + title`,`read` 才读取选中正文,检索预算与 retrieval gate 保持。
|
|
36
|
+
- [ ] CREATE / UPDATE / NO_CHANGE、history、去重、process 继续由 Core 统一实现,
|
|
37
|
+
Codex/Hermes Adapter 不复制记忆语义。
|
|
26
38
|
- [ ] wheel 与 sdist 构建通过。
|
|
27
39
|
- [ ] 从 wheel 安装后 `memleaf` / `memleaf-mcp` 入口正常。
|
|
28
40
|
- [ ] 从 sdist 解包后的测试通过。
|
|
@@ -48,6 +60,13 @@
|
|
|
48
60
|
- [ ] Windows 路径仍支持 `%LOCALAPPDATA%\hermes` 及官方 launcher 布局。
|
|
49
61
|
- [ ] 从旧版本升级时保留已有 Hermes `memleaf.json` 中的自定义 Vault,不静默切换到默认 Vault。
|
|
50
62
|
- [ ] Provider 的 Windows stdio 实现不重新引入 `select.select(pipe)`。
|
|
63
|
+
- [ ] 普通升级/`memleaf install` 不自动配置 Codex;只有显式
|
|
64
|
+
`memleaf install --host codex` 才修改 Codex memleaf 接入。
|
|
65
|
+
- [ ] Codex 安装前后的 model/model_provider/base_url、sandbox、approval、
|
|
66
|
+
profiles、已有 MCP 与已有 Hooks 保持;DeepSeek/OpenRouter/自定义 provider 不被改写。
|
|
67
|
+
- [ ] Hermes/Codex 已有 Vault 唯一一致时复用;冲突时 `vault_conflict` fail closed。
|
|
68
|
+
- [ ] Codex 自动提炼只使用独立 memleaf Model Route;没有 Route 时明确返回
|
|
69
|
+
`processing_status=model_route_required`,不复制 Codex credential、不隐式消耗 Codex 会话模型。
|
|
51
70
|
- [ ] shell、Python 与 PowerShell 安装路径都使用
|
|
52
71
|
`src/memleaf/hermes_provider` 这一份权威 Provider 源码。
|
|
53
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.2.0"
|
|
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
|
|
@@ -7,6 +7,7 @@ contents and possible secrets cannot accidentally end up in ``agents.json``.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import base64
|
|
10
11
|
import datetime as _datetime
|
|
11
12
|
import hashlib
|
|
12
13
|
import inspect
|
|
@@ -355,10 +356,28 @@ def host_event_command(
|
|
|
355
356
|
absolute_vault(vault),
|
|
356
357
|
)
|
|
357
358
|
if (os.name if platform is None else platform) == "nt":
|
|
358
|
-
|
|
359
|
+
# Codex currently wraps commandWindows in an outer cmd.exe /C quoted
|
|
360
|
+
# string. Embedded quotes can break that wrapper. Put the real argv in
|
|
361
|
+
# a UTF-16LE PowerShell EncodedCommand payload instead: commandWindows
|
|
362
|
+
# itself stays quote-free while paths with spaces/Unicode remain exact.
|
|
363
|
+
script = "& " + " ".join(_powershell_single_quoted(argument) for argument in args)
|
|
364
|
+
script += "; exit $LASTEXITCODE"
|
|
365
|
+
payload = base64.b64encode(script.encode("utf-16le")).decode("ascii")
|
|
366
|
+
return (
|
|
367
|
+
"powershell.exe -NoLogo -NoProfile -NonInteractive "
|
|
368
|
+
"-ExecutionPolicy Bypass -EncodedCommand " + payload
|
|
369
|
+
)
|
|
359
370
|
return " ".join(shlex.quote(argument) for argument in args)
|
|
360
371
|
|
|
361
372
|
|
|
373
|
+
def _powershell_single_quoted(value: str) -> str:
|
|
374
|
+
"""Return one literal PowerShell string for an encoded Windows hook."""
|
|
375
|
+
|
|
376
|
+
if not isinstance(value, str) or not value or any(char in value for char in "\r\n\x00"):
|
|
377
|
+
raise ValueError("invalid Windows command argument")
|
|
378
|
+
return "'" + value.replace("'", "''") + "'"
|
|
379
|
+
|
|
380
|
+
|
|
362
381
|
def run_argv(
|
|
363
382
|
runner: CommandRunner | None,
|
|
364
383
|
argv: Sequence[str],
|
|
@@ -427,6 +446,11 @@ def _subprocess_runner(
|
|
|
427
446
|
"check": False,
|
|
428
447
|
"capture_output": True,
|
|
429
448
|
"text": True,
|
|
449
|
+
# Codex and the supported host CLIs emit UTF-8. Relying on the
|
|
450
|
+
# Windows process default code page corrupts non-ASCII JSON paths
|
|
451
|
+
# before adapters can validate them.
|
|
452
|
+
"encoding": "utf-8",
|
|
453
|
+
"errors": "strict",
|
|
430
454
|
"env": dict(env),
|
|
431
455
|
}
|
|
432
456
|
if input_text is not None:
|
|
@@ -68,6 +68,12 @@ class CodexAdapter:
|
|
|
68
68
|
self.env = adapter_environment(env)
|
|
69
69
|
effective_home = home if home is not None else self.env.get("HOME")
|
|
70
70
|
self.home = adapter_home(effective_home)
|
|
71
|
+
codex_home_value = self.env.get("CODEX_HOME")
|
|
72
|
+
self.codex_home = (
|
|
73
|
+
Path(codex_home_value).expanduser().resolve()
|
|
74
|
+
if isinstance(codex_home_value, str) and codex_home_value.strip()
|
|
75
|
+
else self.home / ".codex"
|
|
76
|
+
)
|
|
71
77
|
self.platform = os.name if platform is None else platform
|
|
72
78
|
self.interpreter = interpreter if interpreter is not None else sys.executable
|
|
73
79
|
if known_paths is None:
|
|
@@ -101,11 +107,11 @@ class CodexAdapter:
|
|
|
101
107
|
|
|
102
108
|
@property
|
|
103
109
|
def config_path(self) -> Path:
|
|
104
|
-
return self.
|
|
110
|
+
return self.codex_home / "config.toml"
|
|
105
111
|
|
|
106
112
|
@property
|
|
107
113
|
def hooks_path(self) -> Path:
|
|
108
|
-
return self.
|
|
114
|
+
return self.codex_home / "hooks.json"
|
|
109
115
|
|
|
110
116
|
def detect(self) -> Detection:
|
|
111
117
|
config = self.config_path
|
|
@@ -124,11 +130,20 @@ class CodexAdapter:
|
|
|
124
130
|
status="diagnostic",
|
|
125
131
|
)
|
|
126
132
|
if executable is None:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
# Native Windows installs commonly expose codex.exe, while the
|
|
134
|
+
# official npm package exposes codex.cmd on PATH. Accept both
|
|
135
|
+
# before falling back to documented bundled/runtime locations.
|
|
136
|
+
path_names = ("codex.exe", "codex.cmd", "codex") if self.platform == "nt" else ("codex",)
|
|
137
|
+
for name in path_names:
|
|
138
|
+
executable = resolve_executable(name, env=self.env)
|
|
139
|
+
if executable is not None:
|
|
140
|
+
break
|
|
141
|
+
if executable is None:
|
|
142
|
+
executable = resolve_executable(
|
|
143
|
+
"codex.exe" if self.platform == "nt" else "codex",
|
|
144
|
+
env={**self.env, "PATH": ""},
|
|
145
|
+
known_paths=self.known_paths,
|
|
146
|
+
)
|
|
132
147
|
config_value = str(config)
|
|
133
148
|
if executable is not None:
|
|
134
149
|
return Detection(
|
|
@@ -584,6 +599,22 @@ def _entry_vault(entry: Mapping[str, Any]) -> Path | None:
|
|
|
584
599
|
return Path(raw_vault).expanduser().resolve()
|
|
585
600
|
|
|
586
601
|
|
|
602
|
+
def _path_equivalent(left: str, right: str) -> bool:
|
|
603
|
+
"""Compare host paths without weakening the expected command structure."""
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
if Path(left).exists() and Path(right).exists():
|
|
607
|
+
return os.path.samefile(left, right)
|
|
608
|
+
except OSError:
|
|
609
|
+
pass
|
|
610
|
+
try:
|
|
611
|
+
left_value = os.path.normcase(str(Path(left).expanduser().resolve()))
|
|
612
|
+
right_value = os.path.normcase(str(Path(right).expanduser().resolve()))
|
|
613
|
+
except (OSError, RuntimeError):
|
|
614
|
+
return False
|
|
615
|
+
return left_value == right_value
|
|
616
|
+
|
|
617
|
+
|
|
587
618
|
def _entry_matches(
|
|
588
619
|
entry: Mapping[str, Any],
|
|
589
620
|
vault: Path | str,
|
|
@@ -593,8 +624,17 @@ def _entry_matches(
|
|
|
593
624
|
candidate = _entry_candidate(entry)
|
|
594
625
|
if candidate is None:
|
|
595
626
|
return False
|
|
627
|
+
command = candidate.get("command")
|
|
628
|
+
args = candidate.get("args")
|
|
629
|
+
if not isinstance(command, str) or not isinstance(args, list) or len(args) != 4:
|
|
630
|
+
return False
|
|
631
|
+
if args[:3] != ["-m", "memleaf.mcp_server", "--vault"]:
|
|
632
|
+
return False
|
|
633
|
+
raw_vault = args[3]
|
|
634
|
+
if not isinstance(raw_vault, str) or not raw_vault:
|
|
635
|
+
return False
|
|
596
636
|
expected = mcp_command(vault, interpreter=interpreter)
|
|
597
|
-
return
|
|
637
|
+
return _path_equivalent(command, expected[0]) and _path_equivalent(raw_vault, expected[4])
|
|
598
638
|
|
|
599
639
|
|
|
600
640
|
def _inline_hooks_diagnostic(path: Path) -> str | None:
|
|
@@ -47,7 +47,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
47
47
|
init.add_argument(
|
|
48
48
|
"--no-antigravity",
|
|
49
49
|
action="store_true",
|
|
50
|
-
help="accepted for compatibility; Antigravity is
|
|
50
|
+
help="accepted for compatibility; Antigravity is currently unsupported",
|
|
51
51
|
)
|
|
52
52
|
init.add_argument(
|
|
53
53
|
"--no-model-discovery",
|
|
@@ -76,7 +76,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
76
76
|
return parser
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def _configure_stdio_utf8() -> None:
|
|
80
|
+
for stream in (sys.stdin, sys.stdout, sys.stderr):
|
|
81
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
82
|
+
if not callable(reconfigure):
|
|
83
|
+
continue
|
|
84
|
+
try:
|
|
85
|
+
reconfigure(encoding="utf-8", errors="strict")
|
|
86
|
+
except (OSError, ValueError):
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
|
|
79
90
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
91
|
+
_configure_stdio_utf8()
|
|
80
92
|
parser = build_parser()
|
|
81
93
|
args = parser.parse_args(argv)
|
|
82
94
|
try:
|
|
@@ -200,13 +212,22 @@ def _init(args: argparse.Namespace) -> dict:
|
|
|
200
212
|
attempt=args.all,
|
|
201
213
|
)
|
|
202
214
|
|
|
203
|
-
# Retain legacy
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
(
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
# Retain legacy init result slots without implicitly configuring hosts.
|
|
216
|
+
# Codex is supported only through the explicit install command; Antigravity
|
|
217
|
+
# remains unsupported. Clear stale activation claims in our own index only.
|
|
218
|
+
legacy_slots = (
|
|
219
|
+
(
|
|
220
|
+
"codex",
|
|
221
|
+
home / ".codex" / "config.toml",
|
|
222
|
+
"Codex is not configured by init; use install --host codex",
|
|
223
|
+
),
|
|
224
|
+
(
|
|
225
|
+
"antigravity",
|
|
226
|
+
home / ".gemini" / "config" / "mcp_config.json",
|
|
227
|
+
"Antigravity is currently unsupported; no detection or configuration performed",
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
for name, config_path, reason in legacy_slots:
|
|
210
231
|
results[name] = result_from_detection(
|
|
211
232
|
Detection(
|
|
212
233
|
agent=name, detected=False, confidence="none", reason=reason,
|
|
@@ -326,6 +326,10 @@ def _handle_codex_pre_tool(
|
|
|
326
326
|
return {
|
|
327
327
|
"hookSpecificOutput": {
|
|
328
328
|
"hookEventName": "PreToolUse",
|
|
329
|
+
# Codex only applies updatedInput when the hook explicitly allows
|
|
330
|
+
# the call. Omitting this field makes the rewrite invalid and
|
|
331
|
+
# would drop memleaf's current-turn retrieval_id binding.
|
|
332
|
+
"permissionDecision": "allow",
|
|
329
333
|
"updatedInput": prepared.arguments,
|
|
330
334
|
}
|
|
331
335
|
}
|
|
@@ -442,15 +442,26 @@ def install_codex(*, vault_path: Path | None = None) -> dict[str, Any]:
|
|
|
442
442
|
"model": model,
|
|
443
443
|
"codex": configured.to_dict(),
|
|
444
444
|
}
|
|
445
|
+
model_ready = model.get("status") in {"configured", "already_configured"}
|
|
446
|
+
actions: list[str] = []
|
|
447
|
+
if configured.user_action_required and configured.user_action:
|
|
448
|
+
actions.append(configured.user_action)
|
|
449
|
+
if not model_ready:
|
|
450
|
+
actions.append(
|
|
451
|
+
"Configure an independent memleaf Model Route for this Vault before "
|
|
452
|
+
"relying on automatic memory extraction. Codex model/provider settings "
|
|
453
|
+
"are intentionally not used or modified."
|
|
454
|
+
)
|
|
445
455
|
return {
|
|
446
456
|
"status": configured.status,
|
|
447
457
|
"reason": configured.reason,
|
|
448
458
|
"vault": str(vault.root),
|
|
449
459
|
"vault_source": vault_source,
|
|
450
460
|
"model": model,
|
|
461
|
+
"processing_status": "ready" if model_ready else "model_route_required",
|
|
451
462
|
"codex": configured.to_dict(),
|
|
452
|
-
"user_action_required":
|
|
453
|
-
"user_action":
|
|
463
|
+
"user_action_required": bool(actions),
|
|
464
|
+
"user_action": " ".join(actions) if actions else None,
|
|
454
465
|
}
|
|
455
466
|
|
|
456
467
|
|
|
@@ -53,7 +53,7 @@ INSTRUCTIONS = (
|
|
|
53
53
|
"user turn, including greetings, call memleaf search at least once before answering. Use your "
|
|
54
54
|
"complete conversation context to select scope and query; do not add a needs-memory gate. "
|
|
55
55
|
"Fetch further scope_catalog pages when the map has_more; never assume an omitted scope is absent. "
|
|
56
|
-
"Search returns status=found or no_match and lightweight results (memory_id, title
|
|
56
|
+
"Search returns status=found or no_match and lightweight results (memory_id, title), "
|
|
57
57
|
"not bodies. Errors are not no_match: correct scope_mismatch or retry a failed search at most "
|
|
58
58
|
"twice, then honestly report degraded retrieval; do not claim memory was checked successfully. "
|
|
59
59
|
"Treat directory entries as leads, never as verified facts; do not infer facts from a title. "
|
|
@@ -68,7 +68,7 @@ INSTRUCTIONS = (
|
|
|
68
68
|
"scope/search/read flow. Managed MCP search is directory-only and rejects view=full; "
|
|
69
69
|
"Python full-result search remains compatible. Without host integration, tools remain an explicit fallback only; "
|
|
70
70
|
"do not claim automatic recall, recording or final-answer enforcement succeeded. "
|
|
71
|
-
"Hermes
|
|
71
|
+
"Hermes and Codex are supported hosts; Hermes uses a Soft Gate, not a hard guarantee. "
|
|
72
72
|
"When explicitly calling legacy context, pass source and session_id together and avoid "
|
|
73
73
|
"duplicating native memory that the target host already loads. "
|
|
74
74
|
"Capture only user-visible and assistant-visible text. Never capture system or developer "
|
|
@@ -183,7 +183,7 @@ _TOOLS: tuple[dict[str, Any], ...] = (
|
|
|
183
183
|
"name": "search",
|
|
184
184
|
"description": (
|
|
185
185
|
"Search once per ordinary user turn. Return found/no_match and paged lightweight "
|
|
186
|
-
"results (memory_id, title
|
|
186
|
+
"results (memory_id, title), at most 20/4000 characters, without changing hits. "
|
|
187
187
|
"retrieval_id is required and must be the host-provided current-turn token. Errors are "
|
|
188
188
|
"failures, never no_match. Managed MCP search is directory-only; view=full is rejected. "
|
|
189
189
|
"Python search(view='full') remains the compatibility interface."
|
|
@@ -475,16 +475,18 @@ def _search_result(value: Any) -> dict[str, Any]:
|
|
|
475
475
|
entries = value.get("results")
|
|
476
476
|
if not isinstance(entries, list):
|
|
477
477
|
raise ValueError("invalid search results")
|
|
478
|
+
results: list[dict[str, str]] = []
|
|
478
479
|
for entry in entries:
|
|
479
480
|
if not isinstance(entry, Mapping):
|
|
480
481
|
raise ValueError("invalid search entry")
|
|
481
|
-
|
|
482
|
+
memory_id = entry.get("memory_id")
|
|
483
|
+
title = entry.get("title")
|
|
484
|
+
if not isinstance(memory_id, str) or not memory_id or not isinstance(title, str) or not title:
|
|
482
485
|
raise ValueError("invalid search identifiers")
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if any(entry is None for entry in results) or bool(results) != (value["status"] == "found"):
|
|
486
|
+
# Search is intentionally narrower than the legacy directory helper:
|
|
487
|
+
# do not leak per-memory scope/tags/aliases/keywords into candidates.
|
|
488
|
+
results.append({"memory_id": memory_id, "title": title})
|
|
489
|
+
if bool(results) != (value["status"] == "found"):
|
|
488
490
|
raise ValueError("inconsistent search results")
|
|
489
491
|
paging = _paging_fields(value)
|
|
490
492
|
if not results and paging["has_more"]:
|
|
@@ -953,11 +955,24 @@ def _write_message(output: TextIO, message: Mapping[str, Any]) -> bool:
|
|
|
953
955
|
try:
|
|
954
956
|
output.write(_json_text(message) + "\n")
|
|
955
957
|
output.flush()
|
|
956
|
-
except (BrokenPipeError, OSError):
|
|
958
|
+
except (BrokenPipeError, OSError, UnicodeError):
|
|
957
959
|
return False
|
|
958
960
|
return True
|
|
959
961
|
|
|
960
962
|
|
|
963
|
+
def _configure_stdio_utf8() -> None:
|
|
964
|
+
"""Force the stdio protocol channel to UTF-8 on every supported platform."""
|
|
965
|
+
|
|
966
|
+
for stream in (sys.stdout, sys.stderr):
|
|
967
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
968
|
+
if not callable(reconfigure):
|
|
969
|
+
continue
|
|
970
|
+
try:
|
|
971
|
+
reconfigure(encoding="utf-8", errors="strict")
|
|
972
|
+
except (OSError, ValueError):
|
|
973
|
+
continue
|
|
974
|
+
|
|
975
|
+
|
|
961
976
|
def serve(service: Memleaf, *, input_stream: Any = None, output_stream: TextIO = sys.stdout) -> int:
|
|
962
977
|
"""Serve newline-delimited JSON-RPC messages until EOF or a broken pipe."""
|
|
963
978
|
|
|
@@ -992,6 +1007,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
992
1007
|
|
|
993
1008
|
|
|
994
1009
|
def main(argv: list[str] | None = None) -> int:
|
|
1010
|
+
_configure_stdio_utf8()
|
|
995
1011
|
args = _parser().parse_args(argv)
|
|
996
1012
|
vault = args.vault if args.vault is not None else os.environ.get("MEMLEAF_VAULT") or None
|
|
997
1013
|
try:
|
|
@@ -830,13 +830,13 @@ class Memleaf:
|
|
|
830
830
|
|
|
831
831
|
@staticmethod
|
|
832
832
|
def _candidate_directory(record: _Record) -> dict[str, Any]:
|
|
833
|
-
#
|
|
834
|
-
#
|
|
833
|
+
# The finalized retrieval contract deliberately keeps candidate
|
|
834
|
+
# entries to memory_id + title. Scope selection already happened
|
|
835
|
+
# before search via the bounded Scope Map and search input.
|
|
835
836
|
display = directory_entry(record.memory)
|
|
836
837
|
return {
|
|
837
838
|
"memory_id": record.memory.memory_id,
|
|
838
839
|
"title": display.title,
|
|
839
|
-
"scopes": list(record.memory.scopes),
|
|
840
840
|
}
|
|
841
841
|
|
|
842
842
|
@staticmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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.
|
|
27
|
-
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.
|
|
26
|
+
> **当前版本:0.2.0。**
|
|
27
|
+
> 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.0 通过 PyPI 分发。
|
|
28
28
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
29
29
|
|
|
30
30
|
## 项目定位
|
|
@@ -65,7 +65,7 @@ memleaf 不会把整个 Vault 或整段历史对话自动塞进模型上下文
|
|
|
65
65
|
|
|
66
66
|
1. Agent 使用当前完整会话和 Scope Map 选择检索范围与查询词;
|
|
67
67
|
2. 至少调用一次 `search`;
|
|
68
|
-
3. `search`
|
|
68
|
+
3. `search` 候选默认只返回 `memory_id` 和标题;Scope 已由 Scope Map 与本次搜索参数确定,不随每条候选重复返回,也不直接返回正文;
|
|
69
69
|
4. 只有需要引用事实时,才使用同一轮返回的 `retrieval_id` 调用 `read`;
|
|
70
70
|
5. 根据读取到的关键记忆回答,不把所有候选全部读完。
|
|
71
71
|
|
|
@@ -172,6 +172,14 @@ python -m pip install -U memleaf && python -m memleaf install --host codex
|
|
|
172
172
|
|
|
173
173
|
安装后打开 Codex,运行 `/hooks`,审核并信任 memleaf Hook。完成授权前,MCP 可用不等于自动捕获、检索门控和提炼已经激活;安装结果会明确显示 `pending_user_review`,不会把待审核状态报告为已启用。
|
|
174
174
|
|
|
175
|
+
Codex 只作为宿主,不作为 memleaf 的提炼模型来源。已有的独立 memleaf Model Route 会继续复用;如果当前 Vault 还没有完整 Model Route,Codex 安装仍可完成 MCP/Hook 配置,但结果会明确返回 `processing_status=model_route_required`,在配置模型前不能把“自动记忆提炼”视为已就绪。memleaf 不读取、不复制也不修改 Codex 的 `model`、`model_provider`、`base_url` 或认证信息,也不会为了提炼偷偷消耗 Codex 会话额度。Codex-only 用户可针对同一个 Vault 运行:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python -m memleaf init --no-hermes --vault /path/to/the/same/vault
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
完成独立 Model Route 配置后,再使用 Codex 自动提炼。DeepSeek、OpenRouter 或其他自定义 Codex provider 因此保持原样。
|
|
182
|
+
|
|
175
183
|
Antigravity(反重力)当前不支持,安装流程不会检测、安装或修改其配置。
|
|
176
184
|
|
|
177
185
|
### 高级初始化命令(可选)
|
|
@@ -63,8 +63,10 @@ src/memleaf/llm/router.py
|
|
|
63
63
|
tests/__init__.py
|
|
64
64
|
tests/test_admission_noise.py
|
|
65
65
|
tests/test_codex_install.py
|
|
66
|
+
tests/test_codex_native_cli.py
|
|
66
67
|
tests/test_context_budget.py
|
|
67
68
|
tests/test_credential_safety.py
|
|
69
|
+
tests/test_cross_host_acceptance.py
|
|
68
70
|
tests/test_hermes_provider.py
|
|
69
71
|
tests/test_hermes_stdio_transport.py
|
|
70
72
|
tests/test_host_events.py
|