memleaf 0.2.14__tar.gz → 0.2.16__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 (104) hide show
  1. {memleaf-0.2.14 → memleaf-0.2.16}/CHANGELOG.md +21 -0
  2. {memleaf-0.2.14/src/memleaf.egg-info → memleaf-0.2.16}/PKG-INFO +3 -3
  3. {memleaf-0.2.14 → memleaf-0.2.16}/README.en.md +2 -2
  4. {memleaf-0.2.14 → memleaf-0.2.16}/README.md +2 -2
  5. {memleaf-0.2.14 → memleaf-0.2.16}/pyproject.toml +1 -1
  6. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/__init__.py +1 -1
  7. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/hermes_provider/__init__.py +60 -2
  8. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/hermes_provider/plugin.yaml +1 -1
  9. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/processing.py +422 -166
  10. {memleaf-0.2.14 → memleaf-0.2.16/src/memleaf.egg-info}/PKG-INFO +3 -3
  11. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf.egg-info/SOURCES.txt +1 -0
  12. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_admission_noise.py +161 -28
  13. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_hermes_provider.py +77 -6
  14. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_pypi_install.py +5 -5
  15. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b2a.py +13 -11
  16. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_c1_mcp.py +1 -1
  17. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_c3_packaging.py +1 -1
  18. memleaf-0.2.16/tests/test_update_target_recovery.py +896 -0
  19. {memleaf-0.2.14 → memleaf-0.2.16}/IMPLEMENTATION_PLAN.md +0 -0
  20. {memleaf-0.2.14 → memleaf-0.2.16}/LICENSE +0 -0
  21. {memleaf-0.2.14 → memleaf-0.2.16}/MANIFEST.in +0 -0
  22. {memleaf-0.2.14 → memleaf-0.2.16}/RELEASE_CHECKLIST.md +0 -0
  23. {memleaf-0.2.14 → memleaf-0.2.16}/examples/README.md +0 -0
  24. {memleaf-0.2.14 → memleaf-0.2.16}/examples/basic_usage.py +0 -0
  25. {memleaf-0.2.14 → memleaf-0.2.16}/examples/mcp_stdio.ndjson +0 -0
  26. {memleaf-0.2.14 → memleaf-0.2.16}/install.ps1 +0 -0
  27. {memleaf-0.2.14 → memleaf-0.2.16}/install.sh +0 -0
  28. {memleaf-0.2.14 → memleaf-0.2.16}/setup.cfg +0 -0
  29. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/__main__.py +0 -0
  30. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/adapters/__init__.py +0 -0
  31. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/adapters/antigravity.py +0 -0
  32. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/adapters/base.py +0 -0
  33. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/adapters/codex.py +0 -0
  34. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/adapters/hermes.py +0 -0
  35. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/budget.py +0 -0
  36. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/capture.py +0 -0
  37. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/cli.py +0 -0
  38. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/compaction.py +0 -0
  39. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/config.py +0 -0
  40. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/credentials.py +0 -0
  41. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/frontmatter.py +0 -0
  42. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/hermes_provider/README.md +0 -0
  43. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/host_events.py +0 -0
  44. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/host_runtime.py +0 -0
  45. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/inbox.py +0 -0
  46. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/index.py +0 -0
  47. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/installer.py +0 -0
  48. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/llm/__init__.py +0 -0
  49. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/llm/base.py +0 -0
  50. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/llm/claude_compatible.py +0 -0
  51. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/llm/gemini.py +0 -0
  52. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/llm/openai_compatible.py +0 -0
  53. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/llm/router.py +0 -0
  54. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/locking.py +0 -0
  55. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/mcp_server.py +0 -0
  56. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/memory_writer.py +0 -0
  57. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/model_discovery.py +0 -0
  58. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/models.py +0 -0
  59. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/native_index.py +0 -0
  60. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/prompts.py +0 -0
  61. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/redaction.py +0 -0
  62. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/retrieval.py +0 -0
  63. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/retrieval_gate.py +0 -0
  64. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/scope_maintenance.py +0 -0
  65. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/scope_state.py +0 -0
  66. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/service.py +0 -0
  67. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/validation.py +0 -0
  68. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf/vault.py +0 -0
  69. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf.egg-info/dependency_links.txt +0 -0
  70. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf.egg-info/entry_points.txt +0 -0
  71. {memleaf-0.2.14 → memleaf-0.2.16}/src/memleaf.egg-info/top_level.txt +0 -0
  72. {memleaf-0.2.14 → memleaf-0.2.16}/tests/__init__.py +0 -0
  73. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_codex_install.py +0 -0
  74. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_codex_native_cli.py +0 -0
  75. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_context_budget.py +0 -0
  76. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_credential_safety.py +0 -0
  77. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_cross_host_acceptance.py +0 -0
  78. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_extraction_quality_regressions.py +0 -0
  79. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_hermes_stdio_transport.py +0 -0
  80. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_host_events.py +0 -0
  81. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_host_runtime_contract.py +0 -0
  82. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_install.py +0 -0
  83. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_maintenance_v2.py +0 -0
  84. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_model_discovery.py +0 -0
  85. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_retrieval_gate.py +0 -0
  86. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_retrieval_v2.py +0 -0
  87. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_session_lineage.py +0 -0
  88. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_a.py +0 -0
  89. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b1.py +0 -0
  90. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b2b.py +0 -0
  91. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3a_commit.py +0 -0
  92. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3a_contract.py +0 -0
  93. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3b_native_context.py +0 -0
  94. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3b_native_index.py +0 -0
  95. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3b_scope.py +0 -0
  96. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3c_retrieval.py +0 -0
  97. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_b3d_scope_maintenance.py +0 -0
  98. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_stage_c2_init.py +0 -0
  99. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_upgrade_preserves_vault.py +0 -0
  100. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_v2_gate_limits.py +0 -0
  101. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_v2_host_flow.py +0 -0
  102. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_v2_mcp_flow.py +0 -0
  103. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_v2_nomatch_semantics.py +0 -0
  104. {memleaf-0.2.14 → memleaf-0.2.16}/tests/test_v2_search_gate_acceptance.py +0 -0
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to memleaf are documented here.
4
4
 
5
+ ## 0.2.16 — 2026-09-02
6
+
7
+ - Keep read-only operational queries, derived overdue counters, and one-time
8
+ execution receipts out of permanent-memory extraction; preserve explicitly
9
+ confirmed durable facts and preferences.
10
+ - Deduplicate Hermes tool observations across cumulative conversation payloads,
11
+ preventing historical reads from being re-audited as uncontrolled while
12
+ retaining diagnostics for genuinely mismatched current-turn reads.
13
+
14
+ ## 0.2.15 — 2026-09-02
15
+
16
+ - Recover a candidate whose `update_memory_id` has the wrong type at the
17
+ candidate level after bounded gate retries, without blocking unrelated
18
+ candidates or changing the active target's immutable type.
19
+ - Require candidate-local Scope membership and future-use relevance before
20
+ accepting a duplicate or update target, keeping cross-project targets
21
+ deferred and preventing unsafe overwrites.
22
+ - Keep duplicate validation independent from UPDATE type validation, and
23
+ safely defer unknown or same-use type-mismatch targets instead of creating
24
+ a sibling memory or failing the whole inbox turn.
25
+
5
26
  ## 0.2.14 — 2026-09-02
6
27
 
7
28
  - Reject a permanent-memory candidate that combines a durable project rule or
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memleaf
3
- Version: 0.2.14
3
+ Version: 0.2.16
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.14。**
27
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.14 通过 PyPI 分发。
26
+ > **当前版本:0.2.16。**
27
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.16 通过 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.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.
7
+ > **Version: 0.2.16.**
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.16 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.14。**
8
- > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.14 通过 PyPI 分发。
7
+ > **当前版本:0.2.16。**
8
+ > 核心库、Vault、stdio MCP Server、初始化 CLI、模型路由、提炼流程、受控检索协议和宿主适配器已经实现。memleaf 0.2.16 通过 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.14"
7
+ version = "0.2.16"
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.14"
3
+ __version__ = "0.2.16"
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
@@ -49,6 +49,7 @@ _MAX_PENDING_TURN_NUMBERS = 128
49
49
  _MAX_SESSION_ALIASES = 128
50
50
  _MAX_LINEAGE_RETRIES = 2
51
51
  _MAX_DEFERRED_PROCESS_SESSIONS = 128
52
+ _MAX_OBSERVED_TOOL_CALL_KEYS = 2048
52
53
  _SAFE_COMPONENT_RE = re.compile(r"[^A-Za-z0-9_.-]+")
53
54
  _PROVIDER_VERSION_RE = re.compile(r"^version:\s*([^\s#]+)\s*(?:#.*)?$", re.MULTILINE)
54
55
  _ASCII_QUERY_TERM_RE = re.compile(r"[a-z0-9]+(?:[ ._-][a-z0-9]+)*")
@@ -628,6 +629,45 @@ def _tool_result_for_call(
628
629
  return _CALL_FAILED
629
630
 
630
631
 
632
+ def _tool_observation_key(call: Mapping[str, Any], ordinal: int) -> str:
633
+ """Build a stable in-memory identity for one visible tool call.
634
+
635
+ Hermes can pass the complete conversation back on every turn. The
636
+ retrieval token and result status are turn-relative, so they must not be
637
+ part of the identity: otherwise an old successful read is reclassified as
638
+ ``uncontrolled_success`` when a newer token is observed. Call ids are
639
+ stable when Hermes supplies them; the ordinal/arguments fallback keeps
640
+ anonymous calls distinct within the cumulative message list.
641
+ """
642
+
643
+ arguments = call.get("arguments")
644
+ try:
645
+ arguments_text = json.dumps(arguments, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
646
+ except (TypeError, ValueError):
647
+ arguments_text = ""
648
+ call_id = call.get("call_id") if isinstance(call.get("call_id"), str) else ""
649
+ name = call.get("name") if isinstance(call.get("name"), str) else ""
650
+ if call_id:
651
+ identity = f"{name}\x00call-id\x00{call_id}"
652
+ else:
653
+ identity = f"{name}\x00anonymous\x00{ordinal}\x00{arguments_text}"
654
+ return sha256(identity.encode("utf-8")).hexdigest()
655
+
656
+
657
+ def _record_tool_observation(seen: Any, key: str) -> bool:
658
+ """Record one observation key and report whether it was new."""
659
+
660
+ if seen is None:
661
+ return True
662
+ if key in seen:
663
+ return False
664
+ if isinstance(seen, set):
665
+ seen.add(key)
666
+ else:
667
+ seen[key] = None
668
+ return True
669
+
670
+
631
671
  def _hermes_read_status(value: Any) -> str:
632
672
  """Classify a visible read result without inspecting it in diagnostics."""
633
673
 
@@ -1125,6 +1165,7 @@ class MemleafMemoryProvider(MemoryProvider):
1125
1165
  self._gate_turn_ids: "OrderedDict[Tuple[str, int], str]" = OrderedDict()
1126
1166
  self._active_turn_numbers: "OrderedDict[str, int]" = OrderedDict()
1127
1167
  self._active_retrieval_ids: "OrderedDict[str, Optional[str]]" = OrderedDict()
1168
+ self._observed_tool_call_keys: "OrderedDict[str, None]" = OrderedDict()
1128
1169
  self._version_warning_emitted = False
1129
1170
  # Hermes keeps one provider instance alive while compression rotates
1130
1171
  # the physical session id. Keep only a bounded alias chain so a
@@ -1752,6 +1793,7 @@ class MemleafMemoryProvider(MemoryProvider):
1752
1793
  if not continuous:
1753
1794
  self._pending_lineage.clear()
1754
1795
  self._deferred_process_sessions.clear()
1796
+ self._observed_tool_call_keys.clear()
1755
1797
  if _as_bool(reset, False) or _as_bool(rewound, False):
1756
1798
  self._clear_session_turn_state(old_session_id or next_session_id)
1757
1799
  for key in list(self._retrieval_ids_by_turn):
@@ -1826,6 +1868,7 @@ class MemleafMemoryProvider(MemoryProvider):
1826
1868
  self._gate_turn_ids.clear()
1827
1869
  self._active_turn_numbers.clear()
1828
1870
  self._active_retrieval_ids.clear()
1871
+ self._observed_tool_call_keys.clear()
1829
1872
  self._session_aliases.clear()
1830
1873
  self._pending_lineage.clear()
1831
1874
  self._deferred_process_sessions.clear()
@@ -2073,6 +2116,7 @@ class MemleafMemoryProvider(MemoryProvider):
2073
2116
  session_id: str = "",
2074
2117
  turn_id: str = "",
2075
2118
  vault_root: Optional[Path] = None,
2119
+ seen_call_keys: Any = None,
2076
2120
  ) -> str:
2077
2121
  """Observe explicit host MCP calls in public messages.
2078
2122
 
@@ -2087,25 +2131,32 @@ class MemleafMemoryProvider(MemoryProvider):
2087
2131
  results = _visible_tool_results(messages)
2088
2132
  statuses: list[str] = []
2089
2133
  search_results_used: set[int] = set()
2134
+ search_ordinal = 0
2090
2135
  for call in calls:
2091
2136
  if call.get("name") != "mcp__memleaf__search":
2092
2137
  continue
2138
+ search_ordinal += 1
2093
2139
  arguments = call.get("arguments")
2094
2140
  if not isinstance(arguments, Mapping) or arguments.get("retrieval_id") != retrieval_id:
2095
2141
  continue
2096
2142
  payload = _tool_result_for_call(call, calls, results, search_results_used)
2097
2143
  if payload is not _CALL_FAILED:
2144
+ status = _hermes_search_status(payload)
2145
+ observation_key = _tool_observation_key(call, search_ordinal)
2146
+ if not _record_tool_observation(seen_call_keys, observation_key):
2147
+ continue
2098
2148
  # Hermes has no public write-back hook for this soft observer.
2099
2149
  # A valid current-turn result is enough to mark the local
2100
2150
  # provider diagnostic; no Core ledger or body is touched here.
2101
- statuses.append(_hermes_search_status(payload))
2151
+ statuses.append(status)
2102
2152
 
2103
2153
  read_results_used: set[int] = set()
2104
2154
  read_sequence = 0
2155
+ read_ordinal = 0
2105
2156
  for call in calls:
2106
2157
  if call.get("name") != "mcp__memleaf__read":
2107
2158
  continue
2108
- read_sequence += 1
2159
+ read_ordinal += 1
2109
2160
  arguments = call.get("arguments")
2110
2161
  retrieval_present = isinstance(arguments, Mapping) and "retrieval_id" in arguments
2111
2162
  retrieval_match = bool(
@@ -2120,6 +2171,10 @@ class MemleafMemoryProvider(MemoryProvider):
2120
2171
  # mismatched gate binding. Do not make that look like a
2121
2172
  # controlled success in the diagnostic stream.
2122
2173
  result_status = "uncontrolled_success"
2174
+ observation_key = _tool_observation_key(call, read_ordinal)
2175
+ if not _record_tool_observation(seen_call_keys, observation_key):
2176
+ continue
2177
+ read_sequence += 1
2123
2178
  logger.info(
2124
2179
  "memleaf retrieval-read source=hermes session=%s turn=%s read_seq=%d retrieval_present=%s retrieval_match=%s result=%s",
2125
2180
  _safe_component(session_id, "none") if session_id else "none",
@@ -2286,7 +2341,10 @@ class MemleafMemoryProvider(MemoryProvider):
2286
2341
  session_id=effective_session,
2287
2342
  turn_id=turn_id,
2288
2343
  vault_root=_resolve_vault(self._config()),
2344
+ seen_call_keys=self._observed_tool_call_keys,
2289
2345
  )
2346
+ while len(self._observed_tool_call_keys) > _MAX_OBSERVED_TOOL_CALL_KEYS:
2347
+ self._observed_tool_call_keys.popitem(last=False)
2290
2348
  self._last_retrieval_observation = observation
2291
2349
  lineage_ready = self._retry_pending_lineage(effective_session)
2292
2350
  for role, content in visible_events:
@@ -1,5 +1,5 @@
1
1
  name: memleaf
2
- version: 0.2.14
2
+ version: 0.2.16
3
3
  description: "Hermes-native external MemoryProvider for local-first Markdown memory shared with the memleaf MCP server."
4
4
  hooks:
5
5
  - prefetch