memleaf 0.2.54__tar.gz → 0.2.55__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.54 → memleaf-0.2.55}/CHANGELOG.md +8 -0
- {memleaf-0.2.54/src/memleaf.egg-info → memleaf-0.2.55}/PKG-INFO +2 -2
- {memleaf-0.2.54 → memleaf-0.2.55}/README.en.md +1 -1
- {memleaf-0.2.54 → memleaf-0.2.55}/README.md +1 -1
- {memleaf-0.2.54 → memleaf-0.2.55}/pyproject.toml +1 -1
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/__init__.py +1 -1
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/plugin.yaml +1 -1
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/model_execution.py +3 -1
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/process_common.py +152 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/single_pass_memory_planner.py +78 -4
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/single_pass_plan.py +3 -5
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/validation.py +15 -0
- {memleaf-0.2.54 → memleaf-0.2.55/src/memleaf.egg-info}/PKG-INFO +2 -2
- {memleaf-0.2.54 → memleaf-0.2.55}/LICENSE +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/MANIFEST.in +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/capture-budget-design.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/config-migrations.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/core-refactor.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/evidence-retention.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/extraction-latency.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/gate-evidence-boundary.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/general-processing.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/hermes-mcp-runtime.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/processing-quality-acceptance.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/docs/v0.2.26-processing-status.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/examples/README.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/examples/basic_usage.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/examples/mcp_stdio.ndjson +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/install.ps1 +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/install.sh +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/setup.cfg +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/__main__.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/adapters/__init__.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/adapters/antigravity.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/adapters/base.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/adapters/codex.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/adapters/hermes.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/admission.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/batch_review.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/budget.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/capture.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/cli.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/compaction.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/config.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/create_coordinator.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/credentials.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/evidence_budget.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/evidence_policy.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/evidence_structure.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/evidence_syntax.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/extraction_budget.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/extraction_capability.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/extraction_work_state.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/frontmatter.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/README.md +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/__init__.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/_mcp_client.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/_provider.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/_shared.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_provider/evidence_budget.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/hermes_runtime.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/host_events.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/host_runtime.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/inbox.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/index.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/inspection.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/installer.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/__init__.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/base.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/claude_compatible.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/gemini.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/openai_compatible.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/router.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/llm/thinking.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/locking.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/mcp_server.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/memory_commit.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/memory_planner.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/memory_writer.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/model_capabilities.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/model_discovery.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/models.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/native_index.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/native_registration.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/parallel_model.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/planning_context.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/process_jobs.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/process_journal.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/process_owner.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/processing.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/prompts.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/provenance.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/recording_policy.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/redaction.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/retention.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/retrieval.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/retrieval_gate.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/scope_maintenance.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/scope_state.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/service.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/source_policy.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/state_layout.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/subprocess_flags.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/summary_batch.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/target_reconciliation.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/turn_audit.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/turn_plan.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/update_coordinator.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/update_review.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf/vault.py +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf.egg-info/SOURCES.txt +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf.egg-info/dependency_links.txt +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf.egg-info/entry_points.txt +0 -0
- {memleaf-0.2.54 → memleaf-0.2.55}/src/memleaf.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to memleaf are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.55 — 2026-09-14
|
|
6
|
+
|
|
7
|
+
- Classify memory candidates by future reuse with a shorter B3 instruction: point-in-time counts and snapshots without a trend, threshold, obligation, decision or later comparison are `no_future_value`, while future-use facts remain eligible. Project ownership now has a conservative Core guard: an explicit single-project label cannot silently persist as `global`, but ordinary platform, product and vendor mentions still do not establish ownership. Self-contained memories refer to the conversation person as the user rather than as an owner. The B3 system prompt shrinks from 6395 to 5686 UTF-8 bytes.
|
|
8
|
+
- Preserve candidate-local date authority while improving structured todo deadlines. A legal `whole_unit` claim continues through the existing binding validator as one canonical full-text quote; no second evidence path was added. Core fills `due_date` only when that candidate's own admitted user/assistant evidence establishes one unambiguous deadline, never from sibling evidence, and exposes ambiguous deadline counts in total, stage and operation metrics instead of guessing.
|
|
9
|
+
- Normalize compact date-time text without corrupting adjacent content: a resolved ISO date next to a valid clock gains one separating space, existing spacing remains unchanged, and non-date words such as `本周日报` remain ordinary text. Deadline recognition uses boundary-safe English cues, accepts slash dates before sentence punctuation, and rejects word-suffix and decimal false positives.
|
|
10
|
+
|
|
11
|
+
Verification for this release used eight local synthetic regression tests with no model call and no production Vault write. They cover whole-unit/exact-quote equivalence, candidate date isolation, date-time boundaries, future-value protocol outcomes, explicit-project/global conflicts, platform-only mentions, unique and ambiguous todo deadlines, deadline word boundaries and metric visibility. Python 3.11 compilation and imports pass, as does `git diff --check`. The repository intentionally ignores `/tests/`, so these local tests are not part of the source distribution; real-model adherence and a newly installed Hermes replay remain post-release runtime acceptance.
|
|
12
|
+
|
|
5
13
|
## 0.2.54 — 2026-09-14
|
|
6
14
|
|
|
7
15
|
- Judge automatic memories by future reuse rather than speaker identity. Visible assistant final reports may establish facts learned from external sources, while transient tool failures, one-time fallbacks and routine checks with no issue or follow-up are `no_future_value`. The B3 system prompt and compact contract were rewritten rather than extended: together they shrink from 9469 to 9331 bytes, and the normal path still uses one model request.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.55
|
|
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.55。**
|
|
27
27
|
> 记忆只从用户与 Agent 的可见对话提炼。Agent 已在回复中整理的事实、项目进展和明确待办可作为来源;邮件、附件、网页和终端等工具原文不进入记忆提炼。模型负责保留原话中的不确定性,现有记忆仅用于比较、去重和更新。Markdown 仍是唯一事实源。
|
|
28
28
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
29
29
|
|
|
@@ -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.55.**
|
|
8
8
|
> Automatic extraction now uses only the current turn's visible user input and final assistant reply. Raw tool output, attachments, web/file/terminal payloads and legacy tool-evidence bodies are not new source evidence; existing or retrieved memory remains comparison context rather than source authority. Background processing is persisted as a local job and can be checked through the read-only `process_status` MCP tool; failed work remains retryable and fail closed. The release also tightens source/date grounding, target reconciliation, duplicate/no-op handling and semantic review before writes. Markdown remains the sole source of truth with no SQLite runtime dependency. Acceptance covers deterministic regression suites and synthetic inputs; it does not claim real-mail or customer-business acceptance.
|
|
9
9
|
> **The current release supports Hermes and Codex.** Antigravity is not detected, installed, or configured.
|
|
10
10
|
|
|
@@ -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.55。**
|
|
8
8
|
> 记忆只从用户与 Agent 的可见对话提炼。Agent 已在回复中整理的事实、项目进展和明确待办可作为来源;邮件、附件、网页和终端等工具原文不进入记忆提炼。模型负责保留原话中的不确定性,现有记忆仅用于比较、去重和更新。Markdown 仍是唯一事实源。
|
|
9
9
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Local-first Markdown memory core for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.55"
|
|
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
|
|
@@ -56,7 +56,7 @@ _METRIC_EVENT_FIELDS = frozenset({
|
|
|
56
56
|
# first two were emitted but silently dropped here, which made the
|
|
57
57
|
# "visible rather than silent" claim in their release notes untrue.
|
|
58
58
|
"b3_normalization_count", "b3_candidate_deferred_count",
|
|
59
|
-
"b3_ungrounded_scope_dropped_count",
|
|
59
|
+
"b3_ungrounded_scope_dropped_count", "b3_due_date_ambiguous_count",
|
|
60
60
|
})
|
|
61
61
|
_THINKING_OBSERVATION_SOURCES = frozenset({
|
|
62
62
|
"request_parameter", "reasoning_tokens", "reasoning_content", "unavailable",
|
|
@@ -91,6 +91,7 @@ def _metric_bucket() -> dict[str, Any]:
|
|
|
91
91
|
"b3_normalization_count": 0,
|
|
92
92
|
"b3_candidate_deferred_count": 0,
|
|
93
93
|
"b3_ungrounded_scope_dropped_count": 0,
|
|
94
|
+
"b3_due_date_ambiguous_count": 0,
|
|
94
95
|
"_first_started": None,
|
|
95
96
|
"_last_finished": None,
|
|
96
97
|
}
|
|
@@ -515,6 +516,7 @@ class ModelExecutor:
|
|
|
515
516
|
"repair_rejected_semantic_drift_count": int(bucket.get("repair_rejected_semantic_drift_count", 0)),
|
|
516
517
|
"parse_accepted_count": int(bucket.get("parse_accepted_count", 0)),
|
|
517
518
|
"decision_case_normalization_count": int(bucket.get("decision_case_normalization_count", 0)),
|
|
519
|
+
"b3_due_date_ambiguous_count": int(bucket.get("b3_due_date_ambiguous_count", 0)),
|
|
518
520
|
}
|
|
519
521
|
for field in _PROVIDER_METRIC_FIELDS:
|
|
520
522
|
result[field] = int(bucket.get(field, 0))
|
|
@@ -16,6 +16,7 @@ from .llm import MODEL_ERROR_CODES, MODEL_VALIDATION_REASONS, ModelUnavailable
|
|
|
16
16
|
from .locking import read_json
|
|
17
17
|
from .models import Memory, utc_now
|
|
18
18
|
from .retrieval import candidate_matches_query, normalize_term
|
|
19
|
+
from .scope_state import project_scope_matches_text
|
|
19
20
|
from .validation import MODEL_EVIDENCE_CHECKS, MODEL_VALIDATION_DETAILS, ModelOutputError, parse_strict_json, normalize_relative_calendar_text, safe_evidence_context, safe_schema_context
|
|
20
21
|
|
|
21
22
|
_PROCESSING_LEASE_SECONDS = 3600
|
|
@@ -73,6 +74,26 @@ _SUMMARY_CHINESE_CALENDAR_DATE_RE = re.compile(
|
|
|
73
74
|
r"(?![A-Za-z\d])"
|
|
74
75
|
)
|
|
75
76
|
|
|
77
|
+
_SLASH_CALENDAR_DATE_RE = re.compile(
|
|
78
|
+
r"(?<![\d./-])(?:(?P<year>\d{4})/)?(?P<month>\d{1,2})/(?P<day>\d{1,2})(?![\d/-]|\.\d)"
|
|
79
|
+
)
|
|
80
|
+
_DEADLINE_PREFIX_RE = re.compile(
|
|
81
|
+
r"(?:截止(?:日期|时间)?|截至(?:日期|时间)?|不晚于|不得晚于|\bdeadline\b|\bdue(?:\s+date)?\b|\bby\b|\bbefore\b)"
|
|
82
|
+
r"\s*(?:为|是|[::])?\s*$",
|
|
83
|
+
re.IGNORECASE,
|
|
84
|
+
)
|
|
85
|
+
_DEADLINE_SUFFIX_RE = re.compile(
|
|
86
|
+
r"^\s*(?:(?:[01]?\d|2[0-3]):[0-5]\d\s*)?(?:前|之前|为止|截止|截至|不晚于|不得晚于|before\b|deadline\b|due\b)",
|
|
87
|
+
re.IGNORECASE,
|
|
88
|
+
)
|
|
89
|
+
_EXPLICIT_PROJECT_LABEL_RE = re.compile(
|
|
90
|
+
r"(?<![A-Za-z0-9_])project\s*:\s*(?P<colon>[^\s,,;;。!?::]{1,64})"
|
|
91
|
+
r"|(?<![A-Za-z0-9_])project\s+(?P<english>[^\s,,;;。!?::]{1,64})\s*[::—–-]"
|
|
92
|
+
r"|(?:^|[\s((\[{,,])项目\s*[::]\s*(?P<chinese>[^\s,,;;。!?::]{1,64})"
|
|
93
|
+
r"|(?:^|[\s((\[{,,])(?P<suffix>[^\s,,;;。!?::]{1,64})\s*项目\s*[::]",
|
|
94
|
+
re.IGNORECASE,
|
|
95
|
+
)
|
|
96
|
+
|
|
76
97
|
|
|
77
98
|
|
|
78
99
|
|
|
@@ -141,6 +162,71 @@ def _project_scope_occurrences(
|
|
|
141
162
|
return selected
|
|
142
163
|
|
|
143
164
|
|
|
165
|
+
def _explicit_project_scope_labels(
|
|
166
|
+
texts: Iterable[Any],
|
|
167
|
+
scope_registry: Mapping[str, Any] | None,
|
|
168
|
+
) -> list[str]:
|
|
169
|
+
"""Find project scopes explicitly written as project labels in candidate text.
|
|
170
|
+
|
|
171
|
+
A registered project name appearing as an ordinary noun is insufficient;
|
|
172
|
+
this recognizes only explicit forms such as ``project:Alpha`` or
|
|
173
|
+
``Alpha 项目``. It supports a conservative global-scope guard without
|
|
174
|
+
treating a platform, product, or vendor mention as ownership.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
rows = [text for text in texts if isinstance(text, str) and text.strip()]
|
|
178
|
+
if not rows:
|
|
179
|
+
return []
|
|
180
|
+
registry = scope_registry if isinstance(scope_registry, Mapping) else {}
|
|
181
|
+
try:
|
|
182
|
+
matches = {
|
|
183
|
+
scope
|
|
184
|
+
for text in rows
|
|
185
|
+
for scope in project_scope_matches_text(text, {"scopes": registry})
|
|
186
|
+
}
|
|
187
|
+
except (TypeError, ValueError):
|
|
188
|
+
return []
|
|
189
|
+
labeled: set[str] = set()
|
|
190
|
+
for scope in matches:
|
|
191
|
+
node = registry.get(scope)
|
|
192
|
+
terms = [scope.partition(":")[2]]
|
|
193
|
+
if isinstance(node, Mapping) and isinstance(node.get("aliases"), list):
|
|
194
|
+
terms.extend(value for value in node["aliases"] if isinstance(value, str))
|
|
195
|
+
for text in rows:
|
|
196
|
+
for raw_term in terms:
|
|
197
|
+
term = re.sub(r"\\\s+", r"\\s+", re.escape(raw_term.strip()))
|
|
198
|
+
if not term:
|
|
199
|
+
continue
|
|
200
|
+
patterns = (
|
|
201
|
+
rf"(?<![A-Za-z0-9_])project\s*:\s*{term}(?![A-Za-z0-9_])",
|
|
202
|
+
rf"(?<![A-Za-z0-9_])project\s+{term}(?![A-Za-z0-9_])",
|
|
203
|
+
rf"(?:^|[\s((\[,,::])项目\s*[::]?\s*{term}(?=$|[\s,。,::\-—])",
|
|
204
|
+
rf"(?:^|[\s((\[,,::]){term}\s*(?:项目|project)(?=$|[\s,。,::\-—])",
|
|
205
|
+
)
|
|
206
|
+
if any(re.search(pattern, text, re.IGNORECASE) for pattern in patterns):
|
|
207
|
+
labeled.add(scope)
|
|
208
|
+
break
|
|
209
|
+
if scope in labeled:
|
|
210
|
+
break
|
|
211
|
+
for text in rows:
|
|
212
|
+
for match in _EXPLICIT_PROJECT_LABEL_RE.finditer(text):
|
|
213
|
+
raw_name = next((value for value in match.groupdict().values() if value), "")
|
|
214
|
+
if not raw_name:
|
|
215
|
+
continue
|
|
216
|
+
try:
|
|
217
|
+
resolved = project_scope_matches_text(raw_name, {"scopes": registry})
|
|
218
|
+
except (TypeError, ValueError):
|
|
219
|
+
resolved = []
|
|
220
|
+
if len(resolved) == 1:
|
|
221
|
+
labeled.add(resolved[0])
|
|
222
|
+
else:
|
|
223
|
+
# An explicit but unregistered project label is still enough
|
|
224
|
+
# to reject a silent global attribution; the candidate is
|
|
225
|
+
# deferred because Core must not invent the project's scope.
|
|
226
|
+
labeled.add("project-label:" + raw_name.casefold())
|
|
227
|
+
return sorted(labeled, key=str.casefold)
|
|
228
|
+
|
|
229
|
+
|
|
144
230
|
def _automatic_read_only_query(events: Iterable[Mapping[str, Any]]) -> bool:
|
|
145
231
|
return read_only_turn(analyze_turn_evidence(events))
|
|
146
232
|
|
|
@@ -867,6 +953,72 @@ def _grounded_due_dates(
|
|
|
867
953
|
return result
|
|
868
954
|
|
|
869
955
|
|
|
956
|
+
def _grounded_deadline_dates(
|
|
957
|
+
evidence_events: Iterable[Mapping[str, Any]],
|
|
958
|
+
) -> set[str]:
|
|
959
|
+
"""Return only dates explicitly tied to a deadline in this evidence set.
|
|
960
|
+
|
|
961
|
+
Relative dates use the cited event's own timestamp. Yearless calendar dates
|
|
962
|
+
use that same timestamp only when one is available. No other turn content
|
|
963
|
+
or retrieval timestamp participates.
|
|
964
|
+
"""
|
|
965
|
+
|
|
966
|
+
result: set[str] = set()
|
|
967
|
+
for event in evidence_events:
|
|
968
|
+
if not isinstance(event, Mapping) or event.get("role") not in {"user", "assistant"}:
|
|
969
|
+
continue
|
|
970
|
+
content = event.get("content")
|
|
971
|
+
if not isinstance(content, str) or not content:
|
|
972
|
+
continue
|
|
973
|
+
timestamp = _parse_time(event.get("timestamp"))
|
|
974
|
+
text = normalize_relative_calendar_text(content, timestamp) if timestamp is not None else content
|
|
975
|
+
if text is None:
|
|
976
|
+
text = content
|
|
977
|
+
tokens: list[tuple[int, int, str]] = []
|
|
978
|
+
|
|
979
|
+
for match in _ISO_CALENDAR_DATE_RE.finditer(text):
|
|
980
|
+
try:
|
|
981
|
+
canonical = datetime.strptime(match.group(1), "%Y-%m-%d").date().isoformat()
|
|
982
|
+
except ValueError:
|
|
983
|
+
continue
|
|
984
|
+
tokens.append((match.start(), match.end(), canonical))
|
|
985
|
+
|
|
986
|
+
for match in _SUMMARY_CHINESE_CALENDAR_DATE_RE.finditer(text):
|
|
987
|
+
try:
|
|
988
|
+
year = int(match.group("year")) if match.group("year") else (
|
|
989
|
+
timestamp.year if timestamp is not None else None
|
|
990
|
+
)
|
|
991
|
+
if year is None:
|
|
992
|
+
continue
|
|
993
|
+
canonical = datetime(
|
|
994
|
+
year, int(match.group("month")), int(match.group("day")), tzinfo=timezone.utc
|
|
995
|
+
).date().isoformat()
|
|
996
|
+
except ValueError:
|
|
997
|
+
continue
|
|
998
|
+
tokens.append((match.start(), match.end(), canonical))
|
|
999
|
+
|
|
1000
|
+
for match in _SLASH_CALENDAR_DATE_RE.finditer(text):
|
|
1001
|
+
try:
|
|
1002
|
+
year = int(match.group("year")) if match.group("year") else (
|
|
1003
|
+
timestamp.year if timestamp is not None else None
|
|
1004
|
+
)
|
|
1005
|
+
if year is None:
|
|
1006
|
+
continue
|
|
1007
|
+
canonical = datetime(
|
|
1008
|
+
year, int(match.group("month")), int(match.group("day")), tzinfo=timezone.utc
|
|
1009
|
+
).date().isoformat()
|
|
1010
|
+
except ValueError:
|
|
1011
|
+
continue
|
|
1012
|
+
tokens.append((match.start(), match.end(), canonical))
|
|
1013
|
+
|
|
1014
|
+
for start, end, canonical in tokens:
|
|
1015
|
+
prefix = text[max(0, start - 32):start]
|
|
1016
|
+
suffix = text[end:end + 24]
|
|
1017
|
+
if _DEADLINE_PREFIX_RE.search(prefix) or _DEADLINE_SUFFIX_RE.search(suffix):
|
|
1018
|
+
result.add(canonical)
|
|
1019
|
+
return result
|
|
1020
|
+
|
|
1021
|
+
|
|
870
1022
|
def _native_result(value: Any) -> list[dict[str, Any]]:
|
|
871
1023
|
if value is None:
|
|
872
1024
|
return []
|
|
@@ -24,6 +24,8 @@ from .process_common import (
|
|
|
24
24
|
ProcessingError,
|
|
25
25
|
_automatic_create_conflicts,
|
|
26
26
|
_event_payload,
|
|
27
|
+
_explicit_project_scope_labels,
|
|
28
|
+
_grounded_deadline_dates,
|
|
27
29
|
_grounded_due_dates,
|
|
28
30
|
_normalize_summary_dates,
|
|
29
31
|
_summary_date_grounding_violations,
|
|
@@ -58,7 +60,7 @@ def _claim_date_evidence(
|
|
|
58
60
|
by_unit: Mapping[str, Any],
|
|
59
61
|
events: Iterable[Mapping[str, Any]],
|
|
60
62
|
) -> list[dict[str, Any]]:
|
|
61
|
-
"""Project dates from this candidate's
|
|
63
|
+
"""Project dates from this candidate's canonical validated exact quotes only."""
|
|
62
64
|
|
|
63
65
|
event_by_key = {
|
|
64
66
|
event.get("event_key"): event
|
|
@@ -73,7 +75,7 @@ def _claim_date_evidence(
|
|
|
73
75
|
unit_id = claim.get("unit_id")
|
|
74
76
|
quote = claim.get("quote")
|
|
75
77
|
unit = by_unit.get(unit_id) if isinstance(unit_id, str) else None
|
|
76
|
-
if not isinstance(quote, str) or unit is None:
|
|
78
|
+
if not isinstance(quote, str) or not quote or unit is None:
|
|
77
79
|
continue
|
|
78
80
|
event = event_by_key.get(getattr(unit, "event_key", None))
|
|
79
81
|
if event is None or getattr(unit, "source_role", None) not in {"user", "assistant"}:
|
|
@@ -83,6 +85,7 @@ def _claim_date_evidence(
|
|
|
83
85
|
continue
|
|
84
86
|
seen.add(identity)
|
|
85
87
|
result.append({
|
|
88
|
+
"unit_id": unit_id,
|
|
86
89
|
"event_key": getattr(unit, "event_key", None),
|
|
87
90
|
"role": getattr(unit, "source_role", None),
|
|
88
91
|
"timestamp": event.get("timestamp"),
|
|
@@ -91,6 +94,48 @@ def _claim_date_evidence(
|
|
|
91
94
|
return result
|
|
92
95
|
|
|
93
96
|
|
|
97
|
+
def _fill_unambiguous_todo_deadline(
|
|
98
|
+
memory_type: Any,
|
|
99
|
+
summary: Mapping[str, Any],
|
|
100
|
+
deadline_dates: Iterable[str],
|
|
101
|
+
) -> tuple[dict[str, Any], bool]:
|
|
102
|
+
"""Fill an omitted todo date only when candidate evidence proves one deadline."""
|
|
103
|
+
|
|
104
|
+
normalized = dict(summary)
|
|
105
|
+
dates = {value for value in deadline_dates if isinstance(value, str)}
|
|
106
|
+
if memory_type != "todo" or normalized.get("due_date") is not None:
|
|
107
|
+
return normalized, False
|
|
108
|
+
if len(dates) == 1:
|
|
109
|
+
normalized["due_date"] = next(iter(dates))
|
|
110
|
+
return normalized, False
|
|
111
|
+
return normalized, len(dates) > 1
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _global_scope_conflicts_with_candidate_evidence(
|
|
115
|
+
scopes: Iterable[Any],
|
|
116
|
+
candidate_evidence: Iterable[Mapping[str, Any]],
|
|
117
|
+
by_unit: Mapping[str, Any],
|
|
118
|
+
scope_registry: Mapping[str, Any] | None,
|
|
119
|
+
) -> bool:
|
|
120
|
+
"""Detect a global choice contradicted by one explicit project label."""
|
|
121
|
+
|
|
122
|
+
scope_values = {value.casefold() for value in scopes if isinstance(value, str)}
|
|
123
|
+
if "global" not in scope_values or any(value.startswith("project:") for value in scope_values):
|
|
124
|
+
return False
|
|
125
|
+
texts: list[str] = []
|
|
126
|
+
for event in candidate_evidence:
|
|
127
|
+
if not isinstance(event, Mapping):
|
|
128
|
+
continue
|
|
129
|
+
unit = by_unit.get(event.get("unit_id"))
|
|
130
|
+
section_path = getattr(unit, "section_path", ())
|
|
131
|
+
if isinstance(section_path, (list, tuple)):
|
|
132
|
+
texts.extend(value for value in section_path if isinstance(value, str))
|
|
133
|
+
content = event.get("content")
|
|
134
|
+
if isinstance(content, str):
|
|
135
|
+
texts.append(content)
|
|
136
|
+
return len(_explicit_project_scope_labels(texts, scope_registry)) == 1
|
|
137
|
+
|
|
138
|
+
|
|
94
139
|
class SinglePassMemoryPlanner(MemoryPlanner):
|
|
95
140
|
"""One semantic model call for an ordinary automatic turn."""
|
|
96
141
|
|
|
@@ -388,6 +433,7 @@ class SinglePassMemoryPlanner(MemoryPlanner):
|
|
|
388
433
|
no_memory_by_unit: dict[str, str] = {}
|
|
389
434
|
observed_scopes: list[str] = []
|
|
390
435
|
requests: list[dict[str, Any]] = []
|
|
436
|
+
ambiguous_todo_deadlines: set[str] = set()
|
|
391
437
|
|
|
392
438
|
if not planning_units:
|
|
393
439
|
self._finalize_evidence_audit(
|
|
@@ -481,6 +527,18 @@ class SinglePassMemoryPlanner(MemoryPlanner):
|
|
|
481
527
|
}
|
|
482
528
|
if decision == "UPDATE" and isinstance(target_id, str):
|
|
483
529
|
candidate["update_memory_id"] = target_id
|
|
530
|
+
|
|
531
|
+
candidate_evidence = _claim_date_evidence(claims, by_unit, events)
|
|
532
|
+
if _global_scope_conflicts_with_candidate_evidence(
|
|
533
|
+
scopes, candidate_evidence, by_unit, validation_scope_registry
|
|
534
|
+
):
|
|
535
|
+
# The evidence explicitly labels one project, but Core cannot
|
|
536
|
+
# safely rewrite the model's chosen scope. Defer only this
|
|
537
|
+
# candidate rather than persist it globally.
|
|
538
|
+
raise ModelOutputError(
|
|
539
|
+
"global scope conflicts with an explicit project label",
|
|
540
|
+
validation_detail="scope_not_grounded",
|
|
541
|
+
)
|
|
484
542
|
if decision in {"CREATE", "UPDATE"} and not _model_project_scope_is_source_grounded(
|
|
485
543
|
candidate,
|
|
486
544
|
planning_units,
|
|
@@ -538,8 +596,10 @@ class SinglePassMemoryPlanner(MemoryPlanner):
|
|
|
538
596
|
event["event_key"] for event in admitted_events
|
|
539
597
|
if isinstance(event, Mapping) and isinstance(event.get("event_key"), str)
|
|
540
598
|
))
|
|
541
|
-
candidate_date_evidence =
|
|
599
|
+
candidate_date_evidence = candidate_evidence
|
|
542
600
|
grounded_dates = _grounded_due_dates(turn, evidence_events=candidate_date_evidence)
|
|
601
|
+
deadline_dates = _grounded_deadline_dates(candidate_date_evidence)
|
|
602
|
+
grounded_dates.update(deadline_dates)
|
|
543
603
|
summary = dict(proposed)
|
|
544
604
|
if decision == "UPDATE" and target_memory is not None:
|
|
545
605
|
summary.setdefault("title", target_memory.title)
|
|
@@ -551,6 +611,11 @@ class SinglePassMemoryPlanner(MemoryPlanner):
|
|
|
551
611
|
summary["update_memory_id"] = target_memory.memory_id
|
|
552
612
|
else:
|
|
553
613
|
summary.setdefault("tags", [])
|
|
614
|
+
summary, deadline_is_ambiguous = _fill_unambiguous_todo_deadline(
|
|
615
|
+
memory_type, summary, deadline_dates
|
|
616
|
+
)
|
|
617
|
+
if deadline_is_ambiguous:
|
|
618
|
+
ambiguous_todo_deadlines.add(candidate_id)
|
|
554
619
|
summary["type"] = memory_type
|
|
555
620
|
summary["scopes"] = scopes
|
|
556
621
|
if isinstance(scope_source, str) and scope_source:
|
|
@@ -574,7 +639,7 @@ class SinglePassMemoryPlanner(MemoryPlanner):
|
|
|
574
639
|
expected_target_type=target_memory.type if target_memory is not None else None,
|
|
575
640
|
expected_scopes=scopes,
|
|
576
641
|
expected_scope_source=scope_source if isinstance(scope_source, str) else None,
|
|
577
|
-
allowed_due_dates=grounded_dates,
|
|
642
|
+
allowed_due_dates=(deadline_dates or grounded_dates),
|
|
578
643
|
allow_no_change=False,
|
|
579
644
|
allow_update_target=target_memory is not None,
|
|
580
645
|
)
|
|
@@ -619,6 +684,15 @@ class SinglePassMemoryPlanner(MemoryPlanner):
|
|
|
619
684
|
},
|
|
620
685
|
)
|
|
621
686
|
|
|
687
|
+
if ambiguous_todo_deadlines:
|
|
688
|
+
metric = getattr(self.model, "_record_metric_event", None)
|
|
689
|
+
if callable(metric):
|
|
690
|
+
for _ in ambiguous_todo_deadlines:
|
|
691
|
+
metric(
|
|
692
|
+
{"stage": "single_pass", "operation": "single_pass_primary"},
|
|
693
|
+
"b3_due_date_ambiguous_count",
|
|
694
|
+
)
|
|
695
|
+
|
|
622
696
|
fallback_scopes, fallback_scope_source = self._fallback_scopes(scope_background)
|
|
623
697
|
for row in result["items"]:
|
|
624
698
|
candidate_id = row["candidate_id"]
|
|
@@ -142,16 +142,14 @@ SOURCE
|
|
|
142
142
|
Use current_evidence only. User messages and assistant final reports may support facts, including facts obtained from external sources. Do not turn your own advice, plans or inferences into user intent unless accepted. Never invent facts, dates, numbers, ownership or IDs. local_memory_catalog is comparison context; native memory is never an UPDATE/NO_CHANGE target.
|
|
143
143
|
|
|
144
144
|
TASK
|
|
145
|
-
Keep independently retrievable facts useful for future answers, actions, commitments, status tracking or avoiding repeated research. Prefer stable facts, decisions, open work and deadlines; skip transient
|
|
145
|
+
Keep independently retrievable facts useful for future answers, actions, commitments, status tracking or avoiding repeated research. Prefer stable facts, decisions, open work and deadlines; skip transient failures, one-time fallbacks, routine checks with no follow-up, and point-in-time counts or snapshots unless needed for a trend, threshold, obligation, decision or later comparison. Preserve future-use facts in final reports and keep independent topics separate. Preserve entity, condition, polarity, uncertainty, ownership, state and meaning-critical numbers/codes. Use self-contained wording; call the conversation person “the user” (用户), never “owner” (主人). CREATE only if no local memory represents the information; UPDATE only for a proven change to one target; NO_CHANGE only for the same future-use item with no semantic change; DEFERRED for an unsafe terminal decision. Do not use NO_CHANGE to hide ambiguity.
|
|
146
146
|
|
|
147
147
|
SCOPES
|
|
148
148
|
Legal values: global | domain:<name> | portfolio:<name> | project:<name> | unscoped. scopes is a nonempty array; at most one project:<name> per memory; unscoped must be the only value, and Core then records insufficient_context.
|
|
149
|
-
|
|
150
|
-
Ground the name in this candidate's own cited evidence, or in a project scope explicitly supplied for this turn. Never edit, translate or combine the evidence's words into a name that does not appear there, and choose the shortest term that does appear and names the project: the thing described as "记账的小玩意儿" is project:记账, not project:记账小玩意儿 and not project:记账的小玩意儿. An ungrounded name is rejected and its memory is deferred instead of written. A product, platform, system, vendor, notification source, comparison or implementation context is not ownership by name alone.
|
|
151
|
-
Use global only for a fact that no single project owns: a standing personal preference, a machine-wide or tool-wide rule, or an environment fact. Never invent, translate or borrow a project name.
|
|
149
|
+
Scope to one project only when this candidate's evidence identifies it as owner of the work, decision, state or deadline; use a name present in that evidence, whether or not registered. A platform, product or vendor mention alone is not ownership; defer when ownership is unclear. Use global for cross-project facts, standing preferences and environment-wide rules.
|
|
152
150
|
|
|
153
151
|
DATES
|
|
154
|
-
An evidence unit may carry an ISO-8601 UTC timestamp. Use it ONLY to resolve a relative, partial or yearless date that the unit's own text expresses; never borrow another unit's timestamp and never guess a missing year. The timestamp is an anchor, not content: never write its own date into a memory, and add no date the evidence text does not state. A date literal in a memory must appear in that memory's cited evidence, either verbatim or as the same month and day. Write due_date as YYYY-MM-DD
|
|
152
|
+
An evidence unit may carry an ISO-8601 UTC timestamp. Use it ONLY to resolve a relative, partial or yearless date that the unit's own text expresses; never borrow another unit's timestamp and never guess a missing year. The timestamp is an anchor, not content: never write its own date into a memory, and add no date the evidence text does not state. A date literal in a memory must appear in that memory's cited evidence, either verbatim or as the same month and day. Write due_date as YYYY-MM-DD; a todo with one unambiguous evidence-grounded deadline must include it. Omit due_date when no deadline is stated or it cannot be resolved. Any memory carrying a date that no admitted evidence grounds is rejected and costs the whole turn, so defer instead of approximating.
|
|
155
153
|
|
|
156
154
|
{B3_COMPACT_CONTRACT}"""
|
|
157
155
|
|
|
@@ -364,6 +364,9 @@ _ISO_CALENDAR_DATE = re.compile(
|
|
|
364
364
|
r"(?<![A-Za-z\d./-])\d{4}-(?:0?[1-9]|1[0-2])-(?:0?[1-9]|[12]\d|3[01])"
|
|
365
365
|
r"(?![A-Za-z\d./-])"
|
|
366
366
|
)
|
|
367
|
+
_ISO_DATE_FOLLOWED_BY_CLOCK = re.compile(
|
|
368
|
+
r"(?P<date>\d{4}-\d{2}-\d{2})(?=(?:[01]?\d|2[0-3]):[0-5]\d(?::[0-5]\d)?(?!\d))"
|
|
369
|
+
)
|
|
367
370
|
_EMPTY_ISO_DATE_PARENTHESIS = re.compile(
|
|
368
371
|
r"(?P<date>\d{4}-(?:0?[1-9]|1[0-2])-(?:0?[1-9]|[12]\d|3[01]))"
|
|
369
372
|
r"[ \t]*(?:\([ \t]*[))]|([ \t]*[))])"
|
|
@@ -605,6 +608,18 @@ def _normalize_relative_calendar_text(
|
|
|
605
608
|
normalized = _RELATIVE_CALENDAR_EXPRESSION.sub(replace_token, text)
|
|
606
609
|
if relative_replaced:
|
|
607
610
|
normalized = _collapse_duplicate_calendar_dates(normalized)
|
|
611
|
+
# A relative date can be adjacent to a clock in compact source text (for
|
|
612
|
+
# example, "today10:30"). Keep the date and time as separate values after
|
|
613
|
+
# normalization, while leaving already-spaced dates unchanged.
|
|
614
|
+
def separate_date_from_clock(match: re.Match[str]) -> str:
|
|
615
|
+
value = match.group("date")
|
|
616
|
+
try:
|
|
617
|
+
datetime.strptime(value, "%Y-%m-%d")
|
|
618
|
+
except ValueError:
|
|
619
|
+
return match.group(0)
|
|
620
|
+
return value + " "
|
|
621
|
+
|
|
622
|
+
normalized = _ISO_DATE_FOLLOWED_BY_CLOCK.sub(separate_date_from_clock, normalized)
|
|
608
623
|
normalized = _strip_empty_iso_date_parenthesis(normalized)
|
|
609
624
|
return normalized, safe
|
|
610
625
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memleaf
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.55
|
|
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.55。**
|
|
27
27
|
> 记忆只从用户与 Agent 的可见对话提炼。Agent 已在回复中整理的事实、项目进展和明确待办可作为来源;邮件、附件、网页和终端等工具原文不进入记忆提炼。模型负责保留原话中的不确定性,现有记忆仅用于比较、去重和更新。Markdown 仍是唯一事实源。
|
|
28
28
|
> **当前版本支持 Hermes 和 Codex。** Antigravity(反重力)不检测、不安装、不配置。
|
|
29
29
|
|
|
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
|