memleaf 0.2.12__tar.gz → 0.2.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {memleaf-0.2.12 → memleaf-0.2.14}/CHANGELOG.md +22 -0
  2. {memleaf-0.2.12/src/memleaf.egg-info → memleaf-0.2.14}/PKG-INFO +3 -3
  3. {memleaf-0.2.12 → memleaf-0.2.14}/README.en.md +2 -2
  4. {memleaf-0.2.12 → memleaf-0.2.14}/README.md +2 -2
  5. {memleaf-0.2.12 → memleaf-0.2.14}/pyproject.toml +1 -1
  6. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/__init__.py +1 -1
  7. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/hermes_provider/__init__.py +1 -0
  8. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  9. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/processing.py +67 -19
  10. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/prompts.py +45 -1
  11. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/validation.py +305 -0
  12. {memleaf-0.2.12 → memleaf-0.2.14/src/memleaf.egg-info}/PKG-INFO +3 -3
  13. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf.egg-info/SOURCES.txt +1 -0
  14. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_admission_noise.py +89 -0
  15. memleaf-0.2.14/tests/test_extraction_quality_regressions.py +680 -0
  16. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_hermes_provider.py +42 -4
  17. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_pypi_install.py +5 -5
  18. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b1.py +33 -0
  19. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b2a.py +87 -1
  20. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b2b.py +1 -1
  21. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_c1_mcp.py +1 -1
  22. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_c3_packaging.py +1 -1
  23. {memleaf-0.2.12 → memleaf-0.2.14}/IMPLEMENTATION_PLAN.md +0 -0
  24. {memleaf-0.2.12 → memleaf-0.2.14}/LICENSE +0 -0
  25. {memleaf-0.2.12 → memleaf-0.2.14}/MANIFEST.in +0 -0
  26. {memleaf-0.2.12 → memleaf-0.2.14}/RELEASE_CHECKLIST.md +0 -0
  27. {memleaf-0.2.12 → memleaf-0.2.14}/examples/README.md +0 -0
  28. {memleaf-0.2.12 → memleaf-0.2.14}/examples/basic_usage.py +0 -0
  29. {memleaf-0.2.12 → memleaf-0.2.14}/examples/mcp_stdio.ndjson +0 -0
  30. {memleaf-0.2.12 → memleaf-0.2.14}/install.ps1 +0 -0
  31. {memleaf-0.2.12 → memleaf-0.2.14}/install.sh +0 -0
  32. {memleaf-0.2.12 → memleaf-0.2.14}/setup.cfg +0 -0
  33. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/__main__.py +0 -0
  34. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/adapters/__init__.py +0 -0
  35. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/adapters/antigravity.py +0 -0
  36. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/adapters/base.py +0 -0
  37. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/adapters/codex.py +0 -0
  38. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/adapters/hermes.py +0 -0
  39. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/budget.py +0 -0
  40. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/capture.py +0 -0
  41. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/cli.py +0 -0
  42. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/compaction.py +0 -0
  43. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/config.py +0 -0
  44. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/credentials.py +0 -0
  45. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/frontmatter.py +0 -0
  46. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/hermes_provider/README.md +0 -0
  47. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/host_events.py +0 -0
  48. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/host_runtime.py +0 -0
  49. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/inbox.py +0 -0
  50. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/index.py +0 -0
  51. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/installer.py +0 -0
  52. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/llm/__init__.py +0 -0
  53. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/llm/base.py +0 -0
  54. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/llm/claude_compatible.py +0 -0
  55. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/llm/gemini.py +0 -0
  56. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/llm/openai_compatible.py +0 -0
  57. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/llm/router.py +0 -0
  58. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/locking.py +0 -0
  59. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/mcp_server.py +0 -0
  60. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/memory_writer.py +0 -0
  61. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/model_discovery.py +0 -0
  62. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/models.py +0 -0
  63. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/native_index.py +0 -0
  64. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/redaction.py +0 -0
  65. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/retrieval.py +0 -0
  66. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/retrieval_gate.py +0 -0
  67. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/scope_maintenance.py +0 -0
  68. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/scope_state.py +0 -0
  69. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/service.py +0 -0
  70. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf/vault.py +0 -0
  71. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf.egg-info/dependency_links.txt +0 -0
  72. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf.egg-info/entry_points.txt +0 -0
  73. {memleaf-0.2.12 → memleaf-0.2.14}/src/memleaf.egg-info/top_level.txt +0 -0
  74. {memleaf-0.2.12 → memleaf-0.2.14}/tests/__init__.py +0 -0
  75. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_codex_install.py +0 -0
  76. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_codex_native_cli.py +0 -0
  77. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_context_budget.py +0 -0
  78. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_credential_safety.py +0 -0
  79. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_cross_host_acceptance.py +0 -0
  80. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_hermes_stdio_transport.py +0 -0
  81. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_host_events.py +0 -0
  82. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_host_runtime_contract.py +0 -0
  83. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_install.py +0 -0
  84. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_maintenance_v2.py +0 -0
  85. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_model_discovery.py +0 -0
  86. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_retrieval_gate.py +0 -0
  87. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_retrieval_v2.py +0 -0
  88. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_session_lineage.py +0 -0
  89. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_a.py +0 -0
  90. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3a_commit.py +0 -0
  91. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3a_contract.py +0 -0
  92. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3b_native_context.py +0 -0
  93. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3b_native_index.py +0 -0
  94. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3b_scope.py +0 -0
  95. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3c_retrieval.py +0 -0
  96. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_b3d_scope_maintenance.py +0 -0
  97. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_stage_c2_init.py +0 -0
  98. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_upgrade_preserves_vault.py +0 -0
  99. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_v2_gate_limits.py +0 -0
  100. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_v2_host_flow.py +0 -0
  101. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_v2_mcp_flow.py +0 -0
  102. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_v2_nomatch_semantics.py +0 -0
  103. {memleaf-0.2.12 → memleaf-0.2.14}/tests/test_v2_search_gate_acceptance.py +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to memleaf are documented here.
4
4
 
5
+ ## 0.2.14 — 2026-09-02
6
+
7
+ - Reject a permanent-memory candidate that combines a durable project rule or
8
+ implementation-plan change with an independent dated todo, while preserving
9
+ multiple dated milestones that belong to one project plan.
10
+ - Normalize explicit implementation plans and plan adjustments to `project`
11
+ without promoting adjacent mail, attachment, meeting, or archive records.
12
+ - Keep the gate-selected type immutable through summarization for both CREATE
13
+ and UPDATE, preserve multiple legal update candidates, and expose the new
14
+ `mixed_future_use` diagnostic through the Hermes provider.
15
+
16
+ ## 0.2.13 — 2026-09-02
17
+
18
+ - Preserve the full active project-plan body, stable title, and retrieval
19
+ metadata when a customer suggestion only adds constraints; explicit
20
+ replacements continue to replace conflicting state normally.
21
+ - Reject attachment, document, presentation, material, and issue-list items
22
+ described only by transport metadata or a generic follow-up at both the gate
23
+ and summary boundaries.
24
+ - Keep concrete attachment-backed actions eligible when they include a
25
+ remediation, owner, deadline, delivery rule, deployment, or migration.
26
+
5
27
  ## 0.2.12 — 2026-09-02
6
28
 
7
29
  - Remove empty ASCII or Chinese parentheses left after an absolute date is
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.12
3
+ Version: 0.2.14
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.12。**
27
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.12 通过 PyPI 分发。
26
+ > **当前版本:0.2.14。**
27
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.14 通过 PyPI 分发。
28
28
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
29
29
 
30
30
  ## 项目定位
@@ -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.12.**
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.12 is distributed through PyPI.
7
+ > **Version: 0.2.14.**
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.14 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
@@ -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.12。**
8
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.12 通过 PyPI 分发。
7
+ > **当前版本:0.2.14。**
8
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.14 通过 PyPI 分发。
9
9
  > **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
10
10
 
11
11
  ## 项目定位
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memleaf"
7
- version = "0.2.12"
7
+ version = "0.2.14"
8
8
  description = "A local-first Markdown memory core for AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  """Local-first Markdown memory core for AI agents."""
2
2
 
3
- __version__ = "0.2.12"
3
+ __version__ = "0.2.14"
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
@@ -94,6 +94,7 @@ _MODEL_VALIDATION_DETAILS = frozenset(
94
94
  "source_shape",
95
95
  "todo_fields",
96
96
  "relative_time",
97
+ "mixed_future_use",
97
98
  "other_schema_violation",
98
99
  }
99
100
  )
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.2.12
2
+ version: 0.2.14
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch
@@ -34,6 +34,7 @@ from .prompts import (
34
34
  DUPLICATE_TARGET_CORRECTION,
35
35
  GATE_TYPE_CORRECTION,
36
36
  JSON_CORRECTION,
37
+ MIXED_FUTURE_USE_CORRECTION,
37
38
  MIXED_PROJECT_SCOPES_CORRECTION,
38
39
  RELATIVE_TIME_CORRECTION,
39
40
  GATE_SYSTEM,
@@ -65,6 +66,8 @@ from .validation import (
65
66
  parse_summarize_output,
66
67
  normalize_relative_calendar_text,
67
68
  is_aggregate_operational_text,
69
+ is_attachment_followup_only_text,
70
+ is_project_plan_text,
68
71
  )
69
72
  from .vault import safe_component
70
73
 
@@ -598,6 +601,8 @@ class Processor:
598
601
  return DUPLICATE_TARGET_CORRECTION
599
602
  if hint == "mixed_project_scopes":
600
603
  return MIXED_PROJECT_SCOPES_CORRECTION
604
+ if hint == "mixed_future_use":
605
+ return MIXED_FUTURE_USE_CORRECTION
601
606
  if stage == "gate" and hint == "update_target_type_mismatch":
602
607
  return UPDATE_TARGET_TYPE_CORRECTION
603
608
  if stage == "gate" and hint == "invalid_type":
@@ -1695,22 +1700,64 @@ class Processor:
1695
1700
 
1696
1701
  @staticmethod
1697
1702
  def _is_project_plan_title(value: Any) -> bool:
1698
- text = normalize_term(value) if isinstance(value, str) else ""
1699
- return any(
1700
- marker in text
1701
- for marker in ("实施计划", "项目计划", "implementation plan", "project plan")
1702
- )
1703
+ return is_project_plan_text(value)
1703
1704
 
1704
1705
  @staticmethod
1705
1706
  def _is_adjacent_plan_record(value: Any) -> bool:
1706
1707
  text = normalize_term(value) if isinstance(value, str) else ""
1707
- return any(
1708
+ return bool(text) and any(
1708
1709
  marker in text
1709
1710
  for marker in (
1710
1711
  "已发送", "发送", "邮件", "附件", "存档", "会议", "启动会", "纪要",
1711
1712
  "sent", "email", "mail", "attachment", "archive", "meeting", "minutes",
1712
1713
  )
1714
+ ) and not is_project_plan_text(value)
1715
+
1716
+ @classmethod
1717
+ def _merge_additive_project_plan_update(
1718
+ cls,
1719
+ candidate: Mapping[str, Any],
1720
+ summary: Mapping[str, Any],
1721
+ target: Memory | None,
1722
+ ) -> dict[str, Any]:
1723
+ """Preserve a full active plan when current evidence only adds constraints."""
1724
+
1725
+ result = dict(summary)
1726
+ if (
1727
+ target is None
1728
+ or target.type != "project"
1729
+ or not cls._is_project_plan_title(target.title)
1730
+ ):
1731
+ return result
1732
+ candidate_text = str(candidate.get("memory", "")).casefold()
1733
+ additive_markers = (
1734
+ "建议", "补充", "增加", "新增", "追加", "还需", "同时", "纳入", "完善",
1735
+ "suggest", "propose", "additional", "add ", "include",
1736
+ )
1737
+ replacement_markers = (
1738
+ "改为", "变更为", "替换", "取消", "删除", "不再", "更新为", "调整为", "推迟至", "提前至",
1739
+ "replace", "changed to", "no longer", "cancel", "remove", "instead", "moved to",
1713
1740
  )
1741
+ if not any(marker in candidate_text for marker in additive_markers):
1742
+ return result
1743
+ if any(marker in candidate_text for marker in replacement_markers):
1744
+ return result
1745
+
1746
+ old_body = target.body.strip()
1747
+ new_body = str(result.get("body", "")).strip()
1748
+ normalized_old = " ".join(old_body.casefold().split())
1749
+ normalized_new = " ".join(new_body.casefold().split())
1750
+ if old_body and normalized_old not in normalized_new:
1751
+ result["body"] = f"{old_body}\n\n{new_body}" if new_body else old_body
1752
+ result["title"] = target.title
1753
+ for field in ("tags", "aliases", "keywords"):
1754
+ merged: list[str] = []
1755
+ for value in list(getattr(target, field)) + list(result.get(field, [])):
1756
+ if isinstance(value, str) and value not in merged:
1757
+ merged.append(value)
1758
+ if merged or field in result:
1759
+ result[field] = merged
1760
+ return result
1714
1761
 
1715
1762
  def _infer_update_target(
1716
1763
  self,
@@ -2140,6 +2187,8 @@ class Processor:
2140
2187
  # its own candidate; the aggregate shell itself is NO_CHANGE.
2141
2188
  if candidate.get("worth") and is_aggregate_operational_text(candidate.get("memory")):
2142
2189
  continue
2190
+ if candidate.get("worth") and is_attachment_followup_only_text(candidate.get("memory")):
2191
+ continue
2143
2192
  candidate_scopes = list(candidate["scopes"])
2144
2193
  # An automatic candidate with no reliable project attribution is
2145
2194
  # retained as a retryable inbox turn, never silently promoted to
@@ -2286,20 +2335,13 @@ class Processor:
2286
2335
  expected_scopes=candidate["scopes"],
2287
2336
  expected_scope_source=candidate["scope_source"],
2288
2337
  allow_no_change=True,
2289
- expected_type=(
2290
- candidate.get("type")
2291
- if (
2292
- gate_update_target is not None
2293
- and gate_target_type == candidate.get("type")
2294
- )
2295
- else None
2296
- ),
2338
+ # The summarize stage may not reinterpret a gate
2339
+ # candidate, including CREATE candidates. Updates
2340
+ # additionally retain the active target's immutable
2341
+ # type below.
2342
+ expected_type=candidate.get("type"),
2297
2343
  expected_update_memory_id=gate_update_target,
2298
- expected_target_type=(
2299
- gate_target_type
2300
- if gate_target_type == candidate.get("type")
2301
- else None
2302
- ),
2344
+ expected_target_type=gate_target_type,
2303
2345
  ),
2304
2346
  diagnostic_context={
2305
2347
  "source": turn.source,
@@ -2322,6 +2364,10 @@ class Processor:
2322
2364
  continue
2323
2365
  if summary.get("decision") == NO_CHANGE_DECISION:
2324
2366
  continue
2367
+ if is_attachment_followup_only_text(
2368
+ f"{summary.get('title', '')}\n{summary.get('body', '')}"
2369
+ ):
2370
+ continue
2325
2371
  if gate_update_target is not None:
2326
2372
  summary_update_target = summary.get("update_memory_id")
2327
2373
  if summary_update_target is None:
@@ -2338,6 +2384,8 @@ class Processor:
2338
2384
  else:
2339
2385
  summary = dict(summary)
2340
2386
  summary["update_memory_id"] = gate_update_target
2387
+ target = self.service.read(gate_update_target, include_history=False)
2388
+ summary = self._merge_additive_project_plan_update(candidate, summary, target)
2341
2389
  if summary["scopes"] == ["unscoped"] or summary.get("scope_source") == "insufficient_context":
2342
2390
  self._defer_candidate(
2343
2391
  turn_ref,
@@ -108,6 +108,13 @@ be independently retrieved and updated. Prefer one coherent current-state memory
108
108
  over adjacent overlapping snapshots; do not use a score or fixed category rule.
109
109
  Most ordinary turns should produce zero or one candidate; multiple require
110
110
  genuinely independent future questions or actions.
111
+ Never combine a durable project rule or implementation-plan change with an
112
+ independent dated todo in one candidate. For example, a project plan/rule and
113
+ "reply or investigate by YYYY-MM-DD" are two future uses: emit separate
114
+ candidates, preserving each candidate's own evidence and project scope. A
115
+ date that is an intrinsic milestone of the same implementation plan may remain
116
+ inside that one project candidate. A candidate that combines these independent
117
+ uses is invalid and must be split before summarization.
111
118
  Never persist an aggregate mailbox sweep, daily report, or "items to watch"
112
119
  digest as one cross-project memory. Inspect each item and split genuinely
113
120
  independent project risks or actionable todo items; if an item has no
@@ -115,6 +122,10 @@ independent future use, set worth=false. A one-off meeting with no durable
115
122
  decision or commitment, a request awaiting PM acceptance, and pending feedback
116
123
  or another intermediate status normally have worth=false. Keep an independent
117
124
  project risk or concrete future action even when it came from a digest.
125
+ A PPT, attachment, material, document, or issue-list item described only as
126
+ "needs follow-up/review/processing" has no independent action and normally has
127
+ worth=false; keep it only when the evidence supplies a concrete remediation,
128
+ owner, deadline, delivery rule, migration, or durable conclusion.
118
129
  An intermediate state such as a draft awaiting confirmation, preparation/
119
130
  processing status, temporary error, repeated confirmation, or unconfirmed
120
131
  suggestion normally has no independent future use and is worth=false. Keep one
@@ -151,6 +162,16 @@ same-use project targets remain, do not guess an update target. A candidate
151
162
  that still combines a mailbox or daily digest shell with counts/statuses is not
152
163
  atomic: set worth=false for the aggregate, and emit any independent concrete
153
164
  action as a separate candidate.
165
+ If the same confirmed change applies to multiple same-project active memories,
166
+ emit one candidate per independently changed memory and set each exact
167
+ update_memory_id; never select one target and silently omit the others. Do not
168
+ target adjacent sent-mail, attachment, meeting, or archive records unless the
169
+ current evidence changes their own future-use topic.
170
+ An explicit implementation plan, project implementation plan, or plan
171
+ adjustment is type project. Do not label it fact merely because it came from
172
+ an email or because no existing project memory was retrieved. Keep an actual
173
+ todo as todo only when its future use is the action itself, not the durable
174
+ plan it mentions.
154
175
  Return no prose, markdown fences, comments, or trailing text."""
155
176
 
156
177
 
@@ -182,7 +203,8 @@ still has a concrete future-use fact or action. If not, return exactly
182
203
  {"decision":"NO_CHANGE"}; this is the only no-write summary response. Use it
183
204
  for temporary delegation, waiting for feedback, a one-off meeting arrangement with
184
205
  no durable outcome/decision/project constraint, or a subject/attachment-only item
185
- without a concrete impact or conclusion.
206
+ without a concrete impact or conclusion. Attachment sizes, mail/message IDs,
207
+ and a generic "needs follow-up" phrase do not create such an impact.
186
208
  Explicit remember mode never permits NO_CHANGE and must return the normal summary
187
209
  object. When producing a normal automatic summary, copy the gate candidate's
188
210
  scopes exactly. scope_source may be omitted to inherit the gate value, but an
@@ -260,6 +282,16 @@ plan target over same-topic sent-mail, attachment/archive, or meeting records;
260
282
  the target type remains immutable. Never summarize a combined mailbox/daily
261
283
  digest shell with counts or statuses; return NO_CHANGE for that aggregate,
262
284
  while a separate atomic future-use action may still be summarized.
285
+ If the current confirmed change applies to multiple same-project active
286
+ memories, the gate will provide separate candidates and exact update targets;
287
+ preserve each candidate's target and do not collapse them into one summary.
288
+ Never combine a durable project rule or implementation-plan change with an
289
+ independent dated todo in one summary. A plan/rule and a dated reply,
290
+ investigation, submission, or follow-up are separate future uses; such output
291
+ is invalid and must be retried as separate gate candidates. A date that is an
292
+ intrinsic milestone of the same project plan may remain in its project summary.
293
+ An explicit implementation plan or plan adjustment must use type project;
294
+ preserve an existing update target's type when it is the same project plan.
263
295
  Return no prose, markdown fences, comments, or trailing text."""
264
296
 
265
297
 
@@ -320,6 +352,18 @@ MIXED_PROJECT_SCOPES_CORRECTION = (
320
352
  )
321
353
 
322
354
 
355
+ MIXED_FUTURE_USE_CORRECTION = (
356
+ "Previous output violated: mixed_future_use. Keep every candidate and "
357
+ "summary atomic. Split a durable project rule or implementation-plan "
358
+ "change from an independent dated todo such as a reply, investigation, "
359
+ "submission, or follow-up; preserve each part's own evidence and project "
360
+ "scope. A date intrinsic to the same implementation plan may remain in "
361
+ "that project candidate. Return separate gate candidates or return "
362
+ "NO_CHANGE for a part with no independent future use. Return only the "
363
+ "strict JSON object."
364
+ )
365
+
366
+
323
367
  DUPLICATE_TARGET_CORRECTION = (
324
368
  "Previous output violated: duplicate_update_target. Merge every candidate "
325
369
  "that refers to the same active memory target into one candidate for one "