@fonlan/dsh-gitmemo 0.3.2 → 0.4.1

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.
package/README.md CHANGED
@@ -16,6 +16,9 @@ dependency, and no manual memory commands are ever needed.
16
16
  - **Git-only** — no runtime dependency beyond the `git` CLI
17
17
  - **Token-efficient** — reuses prior conclusions via `mem_search`; subagents carry neither the workflow rules nor the tool schemas
18
18
  - **Immutable entries** — every write creates a new file; corrections use `mem_replace` (one commit deletes the old file and adds the new one), withdrawal uses `mem_delete`
19
+ - **Knowledge freshness** — `mem_replace` is not just for user corrections: work that overrules a stored conclusion actively replaces it; `mem_search` scores hits as distinct matched keywords + a mild recency bonus (≤ +0.5, linearly decaying to 0 over ~180 days), so conflicting stale conclusions lose to their replacements
20
+ - **Topic pages (MOC)** — a `kind: "topic"` entry aggregates a topic's "current truth": the summary states the currently-valid conclusions and the page evolves via `mem_replace` (kind is inherited), not per-task rewrites
21
+ - **Wiki links between entries** — content references other entries as `[[<commit-hash>]]`; `mem_read` with `expand: true` resolves those links one hop and automatically follows the `GitMemo-Replaces` chain to a replaced target's active version (dangling links come back with an error)
19
22
  - **Structured search** — commit messages carry `GitMemo-*` trailers (keywords, digest, search-text projections); search is `git log --grep --fixed-strings` over commit messages only — entry bodies are never scanned
20
23
  - **Auditable** — every memory action is a commit in `.mem`'s Git history; replaced/deleted entries stay readable by hash
21
24
  - **Crash-safe** — write/delete/replace run under a cross-process lock with a journal written before entry mutation; migration uses a sibling swap journal so an interrupted directory exchange is recovered before automatic initialization
@@ -25,11 +28,11 @@ dependency, and no manual memory commands are ever needed.
25
28
 
26
29
  | Piece | Description |
27
30
  | --- | --- |
28
- | `mem_search` | Search memories: `keywords` (array of 1–12, 中英文同义词), `skip` + `snapshot` (stable pagination). Returns up to 20 scored hits with `summary` / `keywords` / `matched_keywords` |
29
- | `mem_read` | Read one memory entry by create/replace commit hash (full markdown; historical hashes stay readable) |
30
- | `mem_write` | Store a task outcome: `title` + `summary` + `keywords` (2–12) + `content` (engine generates front matter), optional `related_branches` / `related_paths`. One ADD commit per immutable file |
31
+ | `mem_search` | Search memories: `keywords` (array of 1–12, 中英文同义词), `skip` + `snapshot` (stable pagination). Returns up to 20 scored hits with `summary` / `keywords` / `kind` / `matched_keywords` (score = matched keywords + recency bonus) |
32
+ | `mem_read` | Read one memory entry by create/replace commit hash (full markdown; historical hashes stay readable); returns `kind`; optional `expand: true` resolves `[[hash]]` links one hop |
33
+ | `mem_write` | Store a task outcome: `title` + `summary` + `keywords` (2–12) + `content` (engine generates front matter), optional `kind` (`"task"` default / `"topic"` aggregated page), `related_branches` / `related_paths`. One ADD commit per immutable file |
31
34
  | `mem_delete` | Withdraw an obsolete conclusion (requires `commit_hash` + `reason`) |
32
- | `mem_replace` | Correct an outdated conclusion in ONE atomic commit (D old + A new) — never delete-then-write |
35
+ | `mem_replace` | Correct an outdated conclusion in ONE atomic commit (D old + A new) — never delete-then-write. Triggered by user corrections AND by work that overrules a stored conclusion; `kind` defaults to the replaced entry's kind |
33
36
  | Scoped rules | The workflow rules + the five tools are registered into **root agents only** (`delegationDepth === 0`) at `agent/created`; subagents carry neither |
34
37
 
35
38
  ## Installation
@@ -62,8 +65,92 @@ The bundle patch ships with sensible defaults; override them in the profile's
62
65
  searchLimit: 20 # max hits per mem_search call (page size)
63
66
  lockTimeoutMs: 30000 # cross-process lock wait timeout
64
67
  projectRoot: null # optional explicit project root (defaults to the session cwd)
68
+ systemOne: # optional System-one recall gate, see below
69
+ enabled: true
70
+ endpoint: https://api.typesafe.ai/v1/systemone
71
+ model: jev-latest
72
+ mode: noul # noul | score
73
+ threshold: 0.5 # noul mode: keep a candidate when P(yes) >= this
65
74
  ```
66
75
 
76
+ ## System-one Recall Gate (optional)
77
+
78
+ A `mem_search` page can be handed to a **System-one model** (TypeSafe Jev by
79
+ default) for one fast judgement, so memories that are **completely unrelated**
80
+ to the current task are dropped before they are injected. Only genuinely
81
+ reusable entries survive.
82
+
83
+ - **Inert by default.** The gate runs only when a usable credential is
84
+ configured (`apiKey`, the credential named by `apiKeyEnv`, or the environment
85
+ variable). With nothing configured, recall behaves **byte-for-byte** as it did
86
+ before the gate existed.
87
+ - **Fails open.** A timeout, a non-2xx response, an unparseable body — any
88
+ failure keeps every candidate and reports why in `gated.degraded` +
89
+ `gated.reason`. **A broken endpoint can never hide a memory.**
90
+ - **Never empties a page.** If the model rejects the whole page, at least
91
+ `minKeep` (default 1) best-ranked candidates are retained, so "the gate removed
92
+ it" and "there was nothing there" stay distinguishable. The tool's `gated` line
93
+ says so out loud — such a page carries a `NOTE(no candidate scored above the
94
+ threshold…)` marker, so a retained floor is never mistaken for a candidate that
95
+ genuinely passed. `judged=` counts only the candidates actually sent to the
96
+ endpoint, with anything past `maxCandidates` reported as `untouched=`
97
+ (unjudged candidates are never dropped).
98
+ - **Recoverable.** `gated.dropped_hashes` lists 8-char hashes of dropped
99
+ entries; `mem_read <short-hash>` brings one back at any time.
100
+ - **Auditable.** Every candidate's probability/score and keep/drop verdict goes
101
+ to the plugin log (off the model context), and the endpoint's reported
102
+ `usage.input_tokens` lands in `gated.input_tokens`, so the gate's own cost is
103
+ directly measurable.
104
+
105
+ ### Settings
106
+
107
+ | Field | Default | Meaning |
108
+ | --- | --- | --- |
109
+ | `enabled` | `true` | Master switch; still a no-op without a credential |
110
+ | `endpoint` | `https://api.typesafe.ai/v1/systemone` | Any endpoint speaking the same request/answer contract (self-hosted, LiteLLM pass-through, …) |
111
+ | `model` | `jev-latest` | Model identifier sent in the request body |
112
+ | `apiKey` | — | Declared `role("secret")`: redacted on read, write-only in the form |
113
+ | `apiKeyEnv` | `TYPESAFE_API_KEY` | Credential reference; resolved literal key → credentials service → environment |
114
+ | `mode` | `noul` | `noul` (yes/no probability) or `score` (graded levels) |
115
+ | `threshold` | `0.5` | `noul` mode: keep when P(yes) ≥ this |
116
+ | `scoreMin` | `2` | `score` mode cutoff. Note Jev's `score` is `Σ(level_index × probability)`, ranging 0…levels−1 (5 levels → 0…4), **not 0–1** |
117
+ | `minKeep` | `1` | Candidates retained when the whole page is rejected |
118
+ | `maxCandidates` | `20` | Candidates judged per request; the remainder is **never judged and never dropped** |
119
+ | `maxTaskChars` | `2000` | Task-text truncation length |
120
+ | `timeoutMs` | `8000` | Request deadline |
121
+
122
+ Every field is marked `.volatile()` — that is the precondition for the DSH
123
+ settings plane to see the entry and apply edits live.
124
+
125
+ ### Settings page
126
+
127
+ The plugin ships a settings card (**Settings → GitMemo**) where the gate can be
128
+ switched on and off and the endpoint, model and API key can be filled in
129
+ directly, applying immediately with no restart. The API key is written through
130
+ the credentials domain — it is **never persisted as a plaintext config value** —
131
+ and the form is write-only. The switch stages `systemOne.enabled`; turning it off
132
+ leaves recall byte-for-byte as it was before the gate existed, and a **Save** is
133
+ what writes it (the switch alone is a draft). An absent `enabled` reads as on, so
134
+ an unconfigured section shows the gate as enabled — which is what the host
135
+ default does.
136
+
137
+ ### Known trade-offs
138
+
139
+ - **English-first.** Jev's primary training language is English; CJK works but
140
+ the vendor documents lower accuracy. The gate's question wording is English;
141
+ only the candidate text and task text may be Chinese.
142
+ - **Context ceiling.** 64k tokens per request, of which `state` + the longest
143
+ single question may use 32k. `maxCandidates` and `maxTaskChars` exist to hold
144
+ that line.
145
+ - **Latency on the critical path.** Recall runs before every repo task, and the
146
+ gate adds one network round trip (~0.4s observed), so `timeoutMs` defaults to
147
+ 8s and every failure path fails open.
148
+ - **Data egress.** Memory summaries leave the machine. TypeSafe currently offers
149
+ **no** zero data retention; point `endpoint` at a self-hosted implementation
150
+ (e.g. MIT-licensed `jeff`) or a local endpoint to avoid egress entirely.
151
+ - **Billing.** Priced per input token ($0.042 per million input tokens, output
152
+ free) — not per call.
153
+
67
154
  ## Memory Location & Format
68
155
 
69
156
  The `.mem` repository lives at the **project root** of the calling session's workspace
@@ -83,7 +170,8 @@ The `.mem` repository lives at the **project root** of the calling session's wor
83
170
  `.gitignore`), and refuses to run when the parent repo already tracks `.mem` content.
84
171
  - Every write creates a brand-new file with an exclusive create (never overwrites), commits
85
172
  one ADD commit with structured trailers, and records the code branch/SHA/related paths in
86
- the entry front matter.
173
+ the entry front matter. Front matter always carries `kind` (`"task"` or `"topic"`); topic
174
+ entries additionally carry a `GitMemo-Kind: topic` commit trailer.
87
175
 
88
176
  The whole memory stays readable with plain `git`:
89
177
 
@@ -97,7 +185,8 @@ git -C .mem show <commit-hash>
97
185
  1. **Before work — search.** For repo-related tasks, extract 1–12 中英文关键词 → `mem_search`.
98
186
  Pure chat and general Q&A need no search.
99
187
  2. **Preselect results.** Based on `title` / `summary` / `keywords` / `score` /
100
- `matched_keywords`, `mem_read` at most 5 most relevant memories; paginate with `skip` +
188
+ `matched_keywords`, `mem_read` at most 5 most relevant memories (`kind: "topic"` entries
189
+ are a topic's aggregated entry point — prefer them); paginate with `skip` +
101
190
  the returned `snapshot`.
102
191
  3. **End-of-session checkpoint — the only write path.** When the conversation is ending,
103
192
  `mem_write` every completed repo-related task that still lacks a memory and whose outcome is
@@ -106,10 +195,17 @@ git -C .mem show <commit-hash>
106
195
  operational git actions. `keywords` should be task-related words NOT already present in
107
196
  `title` / `summary` (中英文同义词 both fine) — words already there are searchable via
108
197
  `title` / `summary` themselves, so repeating them does not improve recall.
109
- 4. **User correction.** If a stored conclusion is outdated and a replacement exists →
198
+ 4. **Keep fresh.** If the user corrects a stored conclusion and a replacement exists →
110
199
  `mem_replace` (never delete-then-write). If it is obsolete with no replacement →
111
- `mem_delete` with a `reason`.
112
- 5. **Subagent results.** Only the root agent decides whether a session-level memory is written.
200
+ `mem_delete` with a `reason`. The same applies — without waiting for the user — when this
201
+ session's own work overrules a stored conclusion (implemented/refactored away). When search
202
+ returns conflicting old/new conclusions, trust the newer one (score already includes the
203
+ recency bonus).
204
+ 5. **Topic pages.** Once a topic has accumulated several memories — or needs a "current truth"
205
+ entry point — write one `kind: "topic"` aggregated entry: the summary states the currently
206
+ valid conclusions, and the content links the evidence entries as `[[<commit-hash>]]` with a
207
+ one-line status each; evolve topic pages via `mem_replace`.
208
+ 6. **Subagent results.** Only the root agent decides whether a session-level memory is written.
113
209
 
114
210
  ## Development
115
211
 
package/README.zh.md CHANGED
@@ -15,6 +15,9 @@
15
15
  - **仅依赖 Git** —— 除 `git` CLI 外无任何运行时依赖
16
16
  - **省 token** —— 通过 `mem_search` 复用既有结论;子代理既不携带工作流规则也不携带工具 schema
17
17
  - **条目不可变** —— 每次写入都创建新文件;更正用 `mem_replace`(一个提交同时删除旧文件、新增新文件),作废用 `mem_delete`
18
+ - **知识保鲜** —— `mem_replace` 不只用于用户更正:本次工作覆盖旧结论时同样主动替换;`mem_search` 评分为「命中关键词数 + 温和的 recency 加成(≤ +0.5,180 天线性衰减到 0)」,新旧结论冲突时自动偏向新条目
19
+ - **主题聚合页(MOC)** —— `kind: "topic"` 条目聚合一个主题的「当前真相」:summary 概括当下有效结论,靠 `mem_replace` 演进(kind 自动继承),不按任务重复新增
20
+ - **条目间 Wiki 链接** —— content 里用 `[[<commit-hash>]]` 引用其他条目;`mem_read` 传 `expand: true` 一跳展开链接目标,并自动跟随 `GitMemo-Replaces` 链到被替换条目的最新版本(悬空链接带 error 返回)
18
21
  - **结构化搜索** —— commit message 携带 `GitMemo-*` trailers(keywords、digest、search-text 投影);搜索只对 commit message 执行 `git log --grep --fixed-strings`,绝不扫描条目正文
19
22
  - **可审计** —— 每次记忆操作都是 `.mem` Git 历史中的一次提交;被替换/删除的条目仍可按哈希读取
20
23
  - **崩溃安全** —— write/delete/replace 在修改条目前先写事务 journal;迁移另用 sibling swap journal,目录交换中断后会在自动初始化前恢复
@@ -24,11 +27,11 @@
24
27
 
25
28
  | 内容 | 说明 |
26
29
  | --- | --- |
27
- | `mem_search` | 搜索记忆:`keywords`(1–12 个关键词数组,建议中英文同义词)、`skip` + `snapshot`(稳定分页)。每次最多返回 20 条带 `summary` / `keywords` / `matched_keywords` 的评分结果 |
28
- | `mem_read` | 按创建/替换提交哈希读取一条记忆的完整 markdown(历史哈希仍可读) |
29
- | `mem_write` | 存储任务结论:`title` + `summary` + `keywords`(2–12)+ `content`(front matter 由引擎生成),可选 `related_branches` / `related_paths`。每个不可变文件对应一个 ADD 提交 |
30
+ | `mem_search` | 搜索记忆:`keywords`(1–12 个关键词数组,建议中英文同义词)、`skip` + `snapshot`(稳定分页)。每次最多返回 20 条带 `summary` / `keywords` / `kind` / `matched_keywords` 的评分结果(score = 命中关键词数 + recency 加成) |
31
+ | `mem_read` | 按创建/替换提交哈希读取一条记忆的完整 markdown(历史哈希仍可读);返回 `kind`;可选 `expand: true` 一跳展开条目内的 `[[hash]]` 链接 |
32
+ | `mem_write` | 存储任务结论:`title` + `summary` + `keywords`(2–12)+ `content`(front matter 由引擎生成),可选 `kind`(`"task"` 缺省 / `"topic"` 聚合页)、`related_branches` / `related_paths`。每个不可变文件对应一个 ADD 提交 |
30
33
  | `mem_delete` | 作废无替代结论(需要 `commit_hash` + `reason`) |
31
- | `mem_replace` | 一个原子提交内更正旧结论(D 旧文件 + A 新文件)—— 禁止先删后写 |
34
+ | `mem_replace` | 一个原子提交内更正旧结论(D 旧文件 + A 新文件)—— 禁止先删后写。触发面不只是用户更正:本次工作覆盖旧结论时同样应主动 replace;`kind` 缺省继承被替换条目 |
32
35
  | 作用域规则 | 工作流规则与五个工具只在 `agent/created` 时注册进**根 Agent**(`delegationDepth === 0`);子代理两者皆无 |
33
36
 
34
37
  ## 安装
@@ -60,8 +63,71 @@ bundle patch 自带合理默认值,可在 profile 的 `cordis.patch.yml` 中
60
63
  searchLimit: 20 # 每次 mem_search 返回的最大条数(每页大小)
61
64
  lockTimeoutMs: 30000 # 跨进程锁等待超时
62
65
  projectRoot: null # 可选:显式项目根目录(默认取会话工作目录)
66
+ systemOne: # 可选:System-one 召回门控,见下节
67
+ enabled: true
68
+ endpoint: https://api.typesafe.ai/v1/systemone
69
+ model: jev-latest
70
+ mode: noul # noul | score
71
+ threshold: 0.5 # noul 模式:概率 ≥ 该值才保留
63
72
  ```
64
73
 
74
+ ## System-one 召回门控(可选)
75
+
76
+ `mem_search` 的候选页可以交给一个 **System-one 模型**(默认 [TypeSafe Jev](https://docs.typesafe.ai))
77
+ 做一次快速判断,把与当前任务**完全无关**的记忆从注入内容里剔除,只留下真正可复用的条目。
78
+
79
+ - **默认不生效。** 只有配置了可用凭据(`apiKey` / `apiKeyEnv` 指向的凭据 / 环境变量)时门控才会运行;
80
+ 没配就完全走原有召回路径,行为与开启前**逐字节一致**。
81
+ - **失败即放行(fail-open)。** 端点超时、HTTP 非 2xx、响应无法解析……任何异常都会保留全部候选,
82
+ 并在 `gated.degraded` + `gated.reason` 里说明原因。**坏掉的端点绝不会让记忆消失。**
83
+ - **绝不返回空页。** 若模型把整页都判为无关,至少保留 `minKeep`(默认 1)条最相关候选,避免
84
+ 「过滤掉了」和「本来就没有」变得无法区分。这种情况会在 `gated` 文本行里明说:该行带
85
+ `NOTE(no candidate scored above the threshold…)` 标记,兜底保留下来的条目不会被误读成
86
+ 「真有候选通过了」。`judged=` 只计实际送判的条数,超出 `maxCandidates` 的部分以
87
+ `untouched=` 标出(未送判的候选永不剔除)。
88
+ - **可回收。** `gated.dropped_hashes` 给出被剔除条目的 8 位短哈希,Agent 或用户可随时
89
+ `mem_read <短哈希>` 把误删的记忆读回来。
90
+ - **可审计。** 每条候选的概率/分数与 keep/drop 判定写入插件日志(不占用模型上下文);端点返回的
91
+ `usage.input_tokens` 记在 `gated.input_tokens` 里,门控自身的开销可被直接测量。
92
+
93
+ ### 配置字段
94
+
95
+ | 字段 | 默认值 | 说明 |
96
+ | --- | --- | --- |
97
+ | `enabled` | `true` | 总开关;无凭据时仍是无操作 |
98
+ | `endpoint` | `https://api.typesafe.ai/v1/systemone` | 任何兼容该请求/应答契约的端点(含自建、LiteLLM 透传等) |
99
+ | `model` | `jev-latest` | 请求体里的模型标识 |
100
+ | `apiKey` | — | 声明为 `role("secret")`:读取时被脱敏、表单里只写不回显 |
101
+ | `apiKeyEnv` | `TYPESAFE_API_KEY` | 凭据引用名;按「字面 key → 凭据服务 → 环境变量」顺序解析 |
102
+ | `mode` | `noul` | `noul`(是与否概率)或 `score`(分级打分) |
103
+ | `threshold` | `0.5` | `noul` 模式:概率 ≥ 该值保留 |
104
+ | `scoreMin` | `2` | `score` 模式阈值。注意 Jev 的 `score` 是 `Σ(层级序号 × 概率)`,范围是 0…层级数−1(默认 5 层 → 0…4),**不是 0–1** |
105
+ | `minKeep` | `1` | 整页被拒时至少保留几条 |
106
+ | `maxCandidates` | `20` | 单次请求最多判定多少条;超出部分**不判定、不剔除** |
107
+ | `maxTaskChars` | `2000` | 任务文本截断长度 |
108
+ | `timeoutMs` | `8000` | 请求超时 |
109
+
110
+ 以上字段全部标记为 `.volatile()`——这是 DSH 设置平面**能够看到并即时生效**的前提条件。
111
+
112
+ ### 设置页
113
+
114
+ 插件自带一个设置卡片(**设置 → GitMemo**),可直接开关召回闸门,并填写端点、模型与 API key,
115
+ 改动即时生效、无需重启。API key 通过凭据域写入,**不会作为明文配置项落盘**,表单也只写不回显。
116
+ 开关暂存的是 `systemOne.enabled`:关闭后召回行为与门控存在前逐字节一致;需要点**保存**才会写入
117
+ (只拨开关是草稿)。未配置 `enabled` 时按开启处理,与该字段的宿主默认值一致。
118
+
119
+ ### 已知取舍
120
+
121
+ - **英文优先。** Jev 的主要训练语言是英文,CJK 文本可用但官方标注准确率较低。门控的提问措辞固定为
122
+ 英文,只有候选正文与任务文本可能是中文。
123
+ - **上下文上限。** 单次请求 64k tokens,其中 `state` + 最长问题合计 32k。`maxCandidates` 与
124
+ `maxTaskChars` 就是为守住这条线而存在的。
125
+ - **延迟在关键路径上。** 召回发生在每次仓库任务之前,门控会给 `mem_search` 增加一次网络往返
126
+ (实测约 0.4s 量级),因此 `timeoutMs` 默认 8s 且失败即放行。
127
+ - **数据外发。** 记忆摘要会离开本机。TypeSafe 目前**不提供** zero data retention;若要避免外发,
128
+ 可把 `endpoint` 指向自建实现(如 MIT 许可的 `jeff`)或本地端点。
129
+ - **计费口径。** 按输入 token 计费($0.042 / 百万输入 token,输出免费),不是按次计费。
130
+
65
131
  ## 记忆存放位置与格式
66
132
 
67
133
  `.mem` 仓库位于调用会话工作区的**项目根目录**(`git rev-parse --show-toplevel`,找不到时退回
@@ -78,7 +144,8 @@ bundle patch 自带合理默认值,可在 profile 的 `cordis.patch.yml` 中
78
144
  - `.mem` 初始化在自己的 `main` 分支上;初始化时把 `.mem/` 与锁文件路径写入父仓库的
79
145
  `.git/info/exclude`(绝不写入受版本控制的 `.gitignore`),且当父仓库已跟踪 `.mem` 内容时拒绝初始化。
80
146
  - 每次写入都用独占创建生成全新文件(绝不覆盖),以带结构化 trailers 的 ADD 提交落库,并把代码
81
- 分支/SHA/相关路径记录在条目 front matter 中。
147
+ 分支/SHA/相关路径记录在条目 front matter 中。front matter 固定携带 `kind`(`"task"` 或 `"topic"`);
148
+ topic 条目的 commit message 额外带 `GitMemo-Kind: topic` trailer。
82
149
 
83
150
  整个记忆可以用普通 `git` 命令查看:
84
151
 
@@ -91,13 +158,14 @@ git -C .mem show <commit-hash>
91
158
 
92
159
  1. **开工前 —— 搜索。** 仓库相关任务开始前提取 1–12 个中英文关键词 → `mem_search`。纯闲聊和通用问答无需搜索。
93
160
  2. **结果预筛。** 根据 `title` / `summary` / `keywords` / `score` / `matched_keywords` 最多
94
- `mem_read` 5 条最相关记忆;用 `skip` + 返回的 `snapshot` 翻页。
161
+ `mem_read` 5 条最相关记忆(`kind: "topic"` 的条目是该主题的聚合入口,优先读);用 `skip` + 返回的
162
+ `snapshot` 翻页。
95
163
  3. **会话结束检查点 —— 唯一的写入路径。** 对话即将结束时,`mem_write` **每一条**已完成、与仓库
96
164
  相关、且结论**有价值/可复用**(或用户明确要求记住)但还没有记忆的任务。绝不重复写已存在的条目。
97
165
  纯问答、未完成任务、与仓库无关的工作、纯操作性的 git 动作一律不写。`keywords` 应选用与任务相关但未出现在 `title` / `summary` 中的词(中英文同义词皆可)——`title` / `summary` 已有的词本身就能被检索到,重复它们不会提高召回率。
98
- 4. **用户更正。** 已存结论过时且有替代结论 → `mem_replace`(禁止先 delete 再 write);结论作废且无
99
- 替代 → 带 `reason` 的 `mem_delete`。
100
- 5. **子代理结果。** 是否形成一条会话级记忆,由根 Agent 汇总后决定。
166
+ 4. **知识保鲜(KEEP FRESH)。** 用户更正旧结论:有替代用 `mem_replace`(禁止先 delete 再 write),作废无替代用带 `reason` 的 `mem_delete`;本次会话的工作覆盖/推翻某条已存结论时同样主动处理,不必等用户指出。检索结果中新旧冲突时优先采信更新的条目(score 已含 recency 加成)。
167
+ 5. **主题聚合页(TOPIC PAGE)。** 同一主题积累多条记忆、或需要「当前真相」入口时,写一条 `kind: "topic"` 聚合条目:summary 概括当下有效结论,content 用 `[[<commit-hash>]]` 链接证据条目并各配一句话状态;topic 条目靠 `mem_replace` 演进。
168
+ 6. **子代理结果。** 是否形成一条会话级记忆,由根 Agent 汇总后决定。
101
169
 
102
170
  ## 开发
103
171