memleaf 0.2.64__tar.gz → 0.2.65__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.64 → memleaf-0.2.65}/CHANGELOG.md +7 -0
- {memleaf-0.2.64/src/memleaf.egg-info → memleaf-0.2.65}/PKG-INFO +2 -2
- {memleaf-0.2.64 → memleaf-0.2.65}/README.en.md +1 -1
- {memleaf-0.2.64 → memleaf-0.2.65}/README.md +1 -1
- {memleaf-0.2.64 → memleaf-0.2.65}/pyproject.toml +1 -1
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/process_common.py +33 -3
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/semantic_maintenance.py +43 -4
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/semantic_protocol.py +4 -4
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/single_pass_memory_planner.py +2 -2
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/single_pass_plan.py +7 -1
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/validation.py +3 -3
- {memleaf-0.2.64 → memleaf-0.2.65/src/memleaf.egg-info}/PKG-INFO +2 -2
- {memleaf-0.2.64 → memleaf-0.2.65}/LICENSE +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/MANIFEST.in +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/capture-budget-design.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/config-migrations.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/core-refactor.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/extraction-latency.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/general-processing.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/semantic-extraction-protocol.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/examples/README.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/examples/basic_usage.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/install.ps1 +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/install.sh +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/setup.cfg +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/adapters/base.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/batch_review.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/capture.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/cli.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/compaction.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/config.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/_provider.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/_shared.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/index.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/inspection.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/installer.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/mcp_server.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/memory_commit.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/memory_planner.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/model_execution.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/models.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/parallel_model.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/planning_context.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/process_jobs.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/process_journal.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/processing.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/prompts.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/retention.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/retrieval_gate.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/service.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/source_policy.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/state_layout.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/summary_batch.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/update_review.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf/vault.py +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf.egg-info/SOURCES.txt +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.64 → memleaf-0.2.65}/src/memleaf.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to memleaf are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.65 — 2026-09-16
|
|
6
|
+
|
|
7
|
+
- Ground maintenance todo deadlines in Core-validated candidate evidence and `reference_time`. Relative calendar ranges resolve deterministically to their last valid day, while the normalized deadline view removes only formatting and equivalent parenthetical restatements without losing conflicting dates.
|
|
8
|
+
- Keep validated dates and bounded evidence references intact through maintenance review. Todo candidates now carry explicit status/deadline fields, numeric reference strings are normalized safely, and the shared retention contract stays focused on future collaborative value.
|
|
9
|
+
|
|
10
|
+
Verification: Python 3.11 compilation, package metadata consistency, wheel contents and `git diff --check` were checked. The release CI verifies the built distributions and entry points.
|
|
11
|
+
|
|
5
12
|
## 0.2.64 — 2026-09-16
|
|
6
13
|
|
|
7
14
|
- Keep uncertain ownership as `unscoped`: explicit cross-project corrections now require cited user evidence and an exact target, while independent project subjects are deferred instead of guessed. Candidate-local deadline grounding remains conservative, preserving only dates tied to the todo action or its explicit task basis.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.65
|
|
4
4
|
Summary: A local-first Markdown memory core for AI agents
|
|
5
5
|
Author: memleaf contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ 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.
|
|
26
|
+
> **版本:0.2.65。**
|
|
27
27
|
> 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
|
|
28
28
|
> 提炼契约现在只有一份中文语义契约(旧版英文 B3 提示词已删除),首轮、主题选择、候选修复和 `semantic_maintenance` 使用同一套保留价值判断;同一件事只写一次。
|
|
29
29
|
> Core 不再因为归属、日期、任务依据或跨项目而丢弃整条候选:归属缺失会归一到证据里唯一被点名的项目、否则落 `global`;日期只接受能在本候选证据里找到出处、且能锚定成 ISO 的写法(`明天`、`周五`、`月底` 都会被解析),无法解析时只丢掉日期字段,记忆照常写入;未获授权的改归属沿用目标原归属。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[中文](README.md) · [PyPI](https://pypi.org/project/memleaf/) · [GitHub](https://github.com/miffyblueboo/memleaf)
|
|
6
6
|
|
|
7
|
-
> **Version: 0.2.
|
|
7
|
+
> **Version: 0.2.65.**
|
|
8
8
|
> Automatic extraction and review share one future-value standard: the model weighs likely reuse, information gain, direct usability when read again, and the cost of forgetting. It keeps only the smallest core that can materially help future understanding, decisions, or actions; information without clear value is not extracted, and no business-specific exclusion rule is hard-coded.
|
|
9
9
|
> A single Chinese semantic contract is sent for extraction, topic selection, candidate repair, and `semantic_maintenance`; the older English B3 prompt has been deleted and B3 remains only as a compatibility read format. One topic is written once.
|
|
10
10
|
> Core no longer discards a whole candidate over ownership, dates, task basis or cross-project wording. A missing owner normalizes to the single project named by that candidate's own evidence, otherwise `global`. A deadline is accepted only when it appears in the candidate's own evidence and can be anchored to ISO (`明天`, `周五`, `月底` all resolve); when it cannot, only the date field is dropped and the memory is still written. An unauthorized ownership change keeps the target's existing scope.
|
|
@@ -4,7 +4,7 @@
|
|
|
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.
|
|
7
|
+
> **版本:0.2.65。**
|
|
8
8
|
> 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
|
|
9
9
|
> 提炼契约现在只有一份中文语义契约(旧版英文 B3 提示词已删除),首轮、主题选择、候选修复和 `semantic_maintenance` 使用同一套保留价值判断;同一件事只写一次。
|
|
10
10
|
> Core 不再因为归属、日期、任务依据或跨项目而丢弃整条候选:归属缺失会归一到证据里唯一被点名的项目、否则落 `global`;日期只接受能在本候选证据里找到出处、且能锚定成 ISO 的写法(`明天`、`周五`、`月底` 都会被解析),无法解析时只丢掉日期字段,记忆照常写入;未获授权的改归属沿用目标原归属。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.65"
|
|
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
|
|
@@ -894,6 +894,38 @@ def _grounded_due_dates(
|
|
|
894
894
|
return result
|
|
895
895
|
|
|
896
896
|
|
|
897
|
+
def _deadline_evidence_text(content: str, anchor: Any) -> str:
|
|
898
|
+
"""Normalize presentation only in the deadline view, never stored evidence."""
|
|
899
|
+
text = content
|
|
900
|
+
for marker in ("**", "__", "`", "*", "_"):
|
|
901
|
+
pattern = re.escape(marker) + r"([^\n]+?)" + re.escape(marker)
|
|
902
|
+
text = re.sub(pattern, lambda match: match.group(1), text)
|
|
903
|
+
normalized = normalize_relative_calendar_text(text, anchor) if anchor is not None else None
|
|
904
|
+
text = normalized if normalized is not None else text
|
|
905
|
+
# A parenthetical restatement of the same date must not separate it from
|
|
906
|
+
# 'before'. Keep arbitrary notes and conflicting dates intact.
|
|
907
|
+
for token in reversed(calendar_tokens(text, anchor)):
|
|
908
|
+
if token.canonical is None:
|
|
909
|
+
continue
|
|
910
|
+
note = re.match(r"[((]([^()()\n]+)[))]", text[token.end:])
|
|
911
|
+
if note is None:
|
|
912
|
+
continue
|
|
913
|
+
inner = note.group(1)
|
|
914
|
+
dates = calendar_tokens(inner, anchor)
|
|
915
|
+
if any(item.canonical != token.canonical for item in dates):
|
|
916
|
+
continue
|
|
917
|
+
for item in reversed(dates):
|
|
918
|
+
inner = inner[:item.start] + inner[item.end:]
|
|
919
|
+
weekdays = re.findall(r"(?:周|星期)([一二三四五六日天])", inner)
|
|
920
|
+
weekday = datetime.fromisoformat(token.canonical).weekday()
|
|
921
|
+
if any("一二三四五六日".index(day.replace("天", "日")) != weekday for day in weekdays):
|
|
922
|
+
continue
|
|
923
|
+
inner = re.sub(r"(?:周|星期)[一二三四五六日天]", "", inner)
|
|
924
|
+
if (dates or weekdays) and not inner.strip(" ,,、\t"):
|
|
925
|
+
text = text[:token.end] + text[token.end + note.end():]
|
|
926
|
+
return text
|
|
927
|
+
|
|
928
|
+
|
|
897
929
|
def _grounded_deadline_dates(
|
|
898
930
|
evidence_events: Iterable[Mapping[str, Any]],
|
|
899
931
|
) -> set[str]:
|
|
@@ -912,9 +944,7 @@ def _grounded_deadline_dates(
|
|
|
912
944
|
if not isinstance(content, str) or not content:
|
|
913
945
|
continue
|
|
914
946
|
timestamp = _parse_time(event.get("timestamp"))
|
|
915
|
-
text =
|
|
916
|
-
if text is None:
|
|
917
|
-
text = content
|
|
947
|
+
text = _deadline_evidence_text(content, timestamp)
|
|
918
948
|
for token in calendar_tokens(text, timestamp):
|
|
919
949
|
canonical = token.canonical
|
|
920
950
|
if canonical is None:
|
|
@@ -5,21 +5,38 @@ from typing import Any, Mapping
|
|
|
5
5
|
from .semantic_protocol import RETENTION_GUIDANCE, expand_fragments, _invalid
|
|
6
6
|
from .validation import ModelOutputError, parse_strict_json
|
|
7
7
|
|
|
8
|
-
MAINTENANCE_SYSTEM = RETENTION_GUIDANCE + "\n" + '''根据 incoming 引用的原始证据维护长期记忆;incoming
|
|
9
|
-
返回 JSON {"memories":[{"from":["d1"],"target":null,"title":"简短主题","body":"最小可复用核心","type":"fact","scope":"global"
|
|
10
|
-
每条一个独立主体与用途,可拆分同一 incoming。scope 按证据独立确定为 project:主体名、global(通用原则)或 unscoped(归属未确定)。type 为 fact/preference/project/todo/event/identity/other。target 为同事项的 catalog 真实ID,无才为null;更新保留原type,明确归属纠正可以改变scope
|
|
8
|
+
MAINTENANCE_SYSTEM = RETENTION_GUIDANCE + "\n" + '''根据 incoming 引用的原始证据维护长期记忆;incoming 仅划定待复核的主题,不提供已确认的分类或归属,其中已有的 due_date 是 Core 按证据和 reference_time 校验过的期限,须原样用于 due_date 和正文。catalog 是可更新的已有记忆。先识别已有事项的状态变化,再判断新建价值。同一事项合并维护当前状态,重复不新建;完成或转交也应维护已有 todo,不能另建完成事实留下旧待办。
|
|
9
|
+
返回 JSON {"memories":[{"from":["d1"],"target":null,"title":"简短主题","body":"最小可复用核心","type":"fact","scope":"global"}],"discard":[],"deferred":[]}。
|
|
10
|
+
每条一个独立主体与用途,可拆分同一 incoming。scope 按证据独立确定为 project:主体名、global(通用原则)或 unscoped(归属未确定)。type 为 fact/preference/project/todo/event/identity/other。target 为同事项的 catalog 真实ID,无才为null;更新保留原type,明确归属纠正可以改变scope。新 todo 须有用户明确承担未完成动作的证据。todo 必须显式提供 status(active/completed/cancelled)和 due_date(无则null)。reference_time 是当前会话时间;按原始约定把可换算的相对期限写成 YYYY-MM-DD,范围取最晚完成日,正文同步使用该日期。无法可靠换算则 due_date:null,但保留核心记忆。正文保留当前有效内容,去掉过时状态和无复用价值的细节。每个 incoming 用 from、discard 或 deferred 覆盖;from 表示该主题已完整复核。仅拆分同一 incoming 时填写 evidence:[fragments.id] 来绑定各自保留内容,否则省略 evidence。所有引用只能选输入中已有的编号。'''
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def maintenance_input(
|
|
13
|
+
def maintenance_input(
|
|
14
|
+
raw: str,
|
|
15
|
+
fragments: list[dict[str, Any]],
|
|
16
|
+
catalog: list[dict[str, Any]],
|
|
17
|
+
model_data: Mapping[str, Any],
|
|
18
|
+
validated: Mapping[str, Any] | None = None,
|
|
19
|
+
):
|
|
14
20
|
# Validate exact references before they become trusted input to maintenance.
|
|
15
21
|
expand_fragments(raw, fragments)
|
|
16
22
|
original = parse_strict_json(raw)
|
|
17
23
|
incoming = {}
|
|
18
24
|
proposals = []
|
|
19
25
|
catalog_ids = {m['memory_id'].casefold() for m in catalog}
|
|
26
|
+
validated_by_id = {
|
|
27
|
+
item['candidate_id']: item
|
|
28
|
+
for item in (validated.get('items', []) if isinstance(validated, Mapping) else [])
|
|
29
|
+
if isinstance(item, Mapping) and isinstance(item.get('candidate_id'), str)
|
|
30
|
+
}
|
|
20
31
|
for index, row in enumerate(original['memories'], 1):
|
|
21
32
|
incoming[index] = dict(row)
|
|
22
33
|
proposal = {'id': f'd{index}', 'evidence': row['evidence']}
|
|
34
|
+
accepted = validated_by_id.get(f'c{index}')
|
|
35
|
+
accepted_memory = accepted.get('memory') if isinstance(accepted, Mapping) else None
|
|
36
|
+
accepted_due_date = accepted_memory.get('due_date') if isinstance(accepted_memory, Mapping) else None
|
|
37
|
+
if isinstance(accepted_due_date, str) and accepted_due_date:
|
|
38
|
+
proposal['due_date'] = accepted_due_date
|
|
39
|
+
incoming[index]['_confirmed_due_date'] = accepted_due_date
|
|
23
40
|
if isinstance(row.get('target'), str) and row['target'].casefold() in catalog_ids:
|
|
24
41
|
proposal['target'] = row['target']
|
|
25
42
|
proposals.append(proposal)
|
|
@@ -28,6 +45,8 @@ def maintenance_input(raw: str, fragments: list[dict[str, Any]], catalog: list[d
|
|
|
28
45
|
# must be able to correct ownership and value without inheriting them.
|
|
29
46
|
snippets = list(model_data['fragments'])
|
|
30
47
|
payload = {'incoming': proposals, 'catalog': related, 'fragments': snippets}
|
|
48
|
+
if isinstance(model_data.get('reference_time'), str) and model_data['reference_time']:
|
|
49
|
+
payload['reference_time'] = model_data['reference_time']
|
|
31
50
|
return payload, (original, incoming, {m['memory_id'].casefold():m for m in related})
|
|
32
51
|
|
|
33
52
|
|
|
@@ -123,8 +142,28 @@ def expand_maintenance(raw: str, context, *, diagnostics=None) -> str:
|
|
|
123
142
|
if kind != 'todo':
|
|
124
143
|
for field in ('status', 'due_date', 'completed_at'):
|
|
125
144
|
memory.pop(field, None)
|
|
145
|
+
else:
|
|
146
|
+
confirmed_due_dates = {
|
|
147
|
+
source.get('_confirmed_due_date')
|
|
148
|
+
for source in sources
|
|
149
|
+
if isinstance(source.get('_confirmed_due_date'), str)
|
|
150
|
+
and source['_confirmed_due_date']
|
|
151
|
+
}
|
|
152
|
+
if len(confirmed_due_dates) > 1:
|
|
153
|
+
raise _invalid('invalid_due_date')
|
|
154
|
+
if confirmed_due_dates:
|
|
155
|
+
confirmed_due_date = next(iter(confirmed_due_dates))
|
|
156
|
+
proposed_due_date = memory.get('due_date')
|
|
157
|
+
if isinstance(proposed_due_date, str) and proposed_due_date != confirmed_due_date:
|
|
158
|
+
for field in ('title', 'body'):
|
|
159
|
+
if isinstance(memory.get(field), str):
|
|
160
|
+
memory[field] = memory[field].replace(proposed_due_date, confirmed_due_date)
|
|
161
|
+
memory['due_date'] = confirmed_due_date
|
|
126
162
|
allowed = list(dict.fromkeys(int(r) for s in sources for r in s['evidence']))
|
|
127
163
|
chosen = row.get('evidence', allowed)
|
|
164
|
+
if isinstance(chosen, list):
|
|
165
|
+
chosen = [int(r) if isinstance(r, str) and len(r) <= 20 and r.isascii() and r.isdecimal() else r
|
|
166
|
+
for r in chosen]
|
|
128
167
|
if not isinstance(chosen, list) or not chosen or any(type(r) is not int or r not in allowed for r in chosen):
|
|
129
168
|
raise _invalid('invalid_evidence')
|
|
130
169
|
memory['evidence']=list(dict.fromkeys(chosen))
|
|
@@ -107,14 +107,14 @@ def source_fragments(b3_prompt: str) -> dict[str, Any]:
|
|
|
107
107
|
'scope_context': data['scope_background'], 'scope_registry': data['scope_registry']}
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
RETENTION_GUIDANCE = """
|
|
110
|
+
RETENTION_GUIDANCE = """长期记忆服务于未来协作,不是业务流水归档。保留持续事项的行动与状态、稳定偏好及可直接复用的知识;仅供本次查阅知悉的通知、过程记录不保留。事实成立或能想象用途并不足够,未来仍须重新查询来源才可信的信息通常不保留。只留下有明确未来用途的最小核心;已有事项的状态变化仍须更新。"""
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
FRAGMENT_SYSTEM = f'''根据对话语义判断每段信息是否值得形成长期记忆,不继承原文的标题、紧急程度、列表分类或建议处理方式。
|
|
114
114
|
{RETENTION_GUIDANCE}
|
|
115
115
|
返回 JSON:{{"memories":[],"no_memory":[],"deferred":[]}}。
|
|
116
|
-
每条 memory:{{"retention":"reusable
|
|
117
|
-
新 todo 额外提供 task_basis:[用户角色片段ID],其内容须明确建立用户自己承担的未完成动作。todo
|
|
116
|
+
每条 memory:{{"retention":"reusable(可复用)或 session(仅本轮)","title":"简短主题","body":"脱离本轮对话仍有价值的核心内容","scope":"project:主体名 或 global","evidence":[片段ID]}}。type 默认 fact,表示业务事实或状态;可选类型 preference、project、todo、event、identity、other,event 仅用于事件本身而非其携带的业务事实。一条一个独立主题与归属,同一件事只写一次。scope 表示事实所属主体,按语义判断,不要求名称与证据逐字一致;项目、客户、系统、产品等主体均可作为归属,通用原则写 global,主体所属项目尚未确认写 unscoped,不根据名称相似或当前项目猜测。同一段的独立主题分别提炼。
|
|
117
|
+
新 todo 额外提供 task_basis:[用户角色片段ID],其内容须明确建立用户自己承担的未完成动作。todo 必须显式提供 status(active/completed/cancelled)和 due_date(无则 null),completed_at 可选。reference_time 是当前会话时间;按原始约定把可换算的相对期限写成 YYYY-MM-DD,范围取最晚完成日,正文同步使用该日期。无法可靠换算则 due_date:null,但保留核心记忆。
|
|
118
118
|
no_memory 填不符合上述长期记忆价值标准的片段ID;deferred 填语义尚无法确定的片段ID。每个片段须被 memory 引用或列入其中一个数组。同片段允许支持多条 memory。若为 catalog 中同一事项的补充、状态变化或重复,target 填已有真实 ID 并给出合并后的当前内容;新事项才省略 target。无需输出写入决策、生成ID或复制原文。'''
|
|
119
119
|
|
|
120
120
|
|
|
@@ -217,7 +217,7 @@ def _independent_project_subjects(text: str, scope_registry: Mapping[str, Any] |
|
|
|
217
217
|
|
|
218
218
|
TOPIC_SYSTEM = RETENTION_GUIDANCE + "\n" + '''根据对话语义识别值得保留的独立主题,不继承原文标题、紧急程度、列表分类或建议处理方式。
|
|
219
219
|
这一阶段只选择有后续价值的主题及其证据,不写记忆正文,不分类,不处理日期,不决定数据库操作。
|
|
220
|
-
返回 JSON {"topics":[{"retention":"reusable
|
|
220
|
+
返回 JSON {"topics":[{"retention":"reusable(可复用)或 session(仅本轮)","scope":"project:项目名 或 global","evidence":[片段ID]}],"no_memory":[片段ID],"deferred":[片段ID]}。
|
|
221
221
|
每个独立主题单独列出,scope 表示主题真正所属的主体,按语义判断,不要求名称与证据逐字一致;通用原则写 global,主体所属项目尚未确认写 unscoped,不根据名称相似或当前项目猜测。其他合法 scope:domain:名称、portfolio:名称、unscoped。
|
|
222
222
|
no_memory 表示不符合上述长期记忆价值标准;deferred 表示语义无法确定。覆盖所有片段,每个被一个或多个主题引用或列入一个数组。'''
|
|
223
223
|
|
|
@@ -28,6 +28,7 @@ from .process_common import (
|
|
|
28
28
|
_event_payload,
|
|
29
29
|
_explicit_project_scope_labels,
|
|
30
30
|
_grounded_deadline_dates,
|
|
31
|
+
_deadline_evidence_text,
|
|
31
32
|
_grounded_due_dates,
|
|
32
33
|
_normalize_summary_dates,
|
|
33
34
|
_parse_time,
|
|
@@ -60,8 +61,7 @@ def _candidate_deadline_dates(evidence: Iterable[Mapping[str, Any]]) -> set[str]
|
|
|
60
61
|
for event in evidence:
|
|
61
62
|
anchor = _parse_time(event.get("timestamp"))
|
|
62
63
|
content = str(event.get("content", ""))
|
|
63
|
-
text =
|
|
64
|
-
text = text if text is not None else content
|
|
64
|
+
text = _deadline_evidence_text(content, anchor)
|
|
65
65
|
for token in reversed(calendar_tokens(text, anchor)):
|
|
66
66
|
if (re.search(r"(?:截至|截止)\s*[::]?\s*$", text[:token.start])
|
|
67
67
|
and not _SCHEDULE_ACTION_RE.match(text[token.end:])):
|
|
@@ -1288,6 +1288,10 @@ def run_single_pass_stage(
|
|
|
1288
1288
|
result["paragraph"] = paragraph
|
|
1289
1289
|
return result
|
|
1290
1290
|
model_data = {**fragment_data, "fragments": [visible_fragment(f) for f in fragment_data["fragments"]]}
|
|
1291
|
+
reference_time = next((f.get("timestamp") for f in reversed(fragment_data["fragments"])
|
|
1292
|
+
if isinstance(f.get("timestamp"), str) and f["timestamp"]), None)
|
|
1293
|
+
if reference_time is not None:
|
|
1294
|
+
model_data["reference_time"] = reference_time
|
|
1291
1295
|
compact_prompt = "MEMORY_INPUT\n" + json.dumps(model_data, ensure_ascii=False, separators=(",", ":"))
|
|
1292
1296
|
primary_prompt = FRAGMENT_SYSTEM + "\n\n" + compact_prompt if inline_system else compact_prompt
|
|
1293
1297
|
primary_system = "" if inline_system else FRAGMENT_SYSTEM
|
|
@@ -1702,7 +1706,9 @@ def run_single_pass_stage(
|
|
|
1702
1706
|
maintenance_context = None
|
|
1703
1707
|
if "memories" in value:
|
|
1704
1708
|
from .semantic_maintenance import maintenance_input, MAINTENANCE_SYSTEM
|
|
1705
|
-
review_payload, maintenance_context = maintenance_input(
|
|
1709
|
+
review_payload, maintenance_context = maintenance_input(
|
|
1710
|
+
raw, repair_fragments, local_rows, model_data, validated=parsed,
|
|
1711
|
+
)
|
|
1706
1712
|
review_system = MAINTENANCE_SYSTEM
|
|
1707
1713
|
review_prompt = "MEMORY_REVIEW\n" + json.dumps(review_payload, ensure_ascii=False, separators=(",", ":"))
|
|
1708
1714
|
if inline_system:
|
|
@@ -597,10 +597,10 @@ def _resolve_relative_date(token: str, anchor: date) -> str | None:
|
|
|
597
597
|
)
|
|
598
598
|
if chinese_match:
|
|
599
599
|
prefix, weekday = chinese_match.groups()
|
|
600
|
-
#
|
|
601
|
-
#
|
|
600
|
+
# A deadline expressed as a calendar range uses its last day. This
|
|
601
|
+
# matches the model contract and makes 周末 deterministic as Sunday.
|
|
602
602
|
if weekday == "末":
|
|
603
|
-
|
|
603
|
+
weekday = "日"
|
|
604
604
|
week_offset = {"本": 0, "这": 0, "下": 1, "上": -1}[prefix]
|
|
605
605
|
try:
|
|
606
606
|
monday = anchor - timedelta(days=anchor.weekday())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.65
|
|
4
4
|
Summary: A local-first Markdown memory core for AI agents
|
|
5
5
|
Author: memleaf contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ 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.
|
|
26
|
+
> **版本:0.2.65。**
|
|
27
27
|
> 自动提炼和复核共用同一条“未来记忆价值”标准:模型综合未来复用、信息增量、再次读取时的直接可用性和忘记成本,只保留对未来理解、判断或行动有实质影响的最小核心;没有明确价值的信息不提炼,不按具体业务场景硬编码排除。
|
|
28
28
|
> 提炼契约现在只有一份中文语义契约(旧版英文 B3 提示词已删除),首轮、主题选择、候选修复和 `semantic_maintenance` 使用同一套保留价值判断;同一件事只写一次。
|
|
29
29
|
> Core 不再因为归属、日期、任务依据或跨项目而丢弃整条候选:归属缺失会归一到证据里唯一被点名的项目、否则落 `global`;日期只接受能在本候选证据里找到出处、且能锚定成 ISO 的写法(`明天`、`周五`、`月底` 都会被解析),无法解析时只丢掉日期字段,记忆照常写入;未获授权的改归属沿用目标原归属。
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|