@cchez/memory-mcp 1.0.0

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.
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { MEMORY_TYPES, storeMemory } from "./store.js";
3
+ const episodeMemorySchema = z.object({
4
+ content: z.string().min(1).describe("从 episode 中提炼出的可复用记忆"),
5
+ memory_type: z.enum(MEMORY_TYPES).describe("该条记忆的类型"),
6
+ tags: z.array(z.string()).optional().describe("该条记忆的 tags"),
7
+ confidence: z
8
+ .number()
9
+ .min(0)
10
+ .max(1)
11
+ .optional()
12
+ .default(0.7)
13
+ .describe("该条记忆可信度,默认 0.7"),
14
+ });
15
+ export const captureEpisodeSchema = z.object({
16
+ episode_id: z.string().min(1).describe("任务/调试 episode ID,由 agent 或调用方生成"),
17
+ source: z.string().min(1).describe("episode 来源,如 agent/claude-code"),
18
+ summary: z
19
+ .string()
20
+ .min(1)
21
+ .describe("episode 摘要:问题、关键尝试、最终结论;不会自动存 raw trace"),
22
+ observations: z
23
+ .array(episodeMemorySchema)
24
+ .min(1)
25
+ .max(10)
26
+ .describe("确认值得长期保存的结构化观察,最多 10 条"),
27
+ related_ids: z.array(z.string()).optional().describe("该 episode 关联的已有记忆 ID"),
28
+ });
29
+ export async function captureEpisodeTool(input) {
30
+ const stored = [];
31
+ const episodeSummary = await storeMemory({
32
+ content: `Episode ${input.episode_id}: ${input.summary}`,
33
+ source: input.source,
34
+ memory_type: "summary",
35
+ tags: ["episode", ...(input.related_ids ?? [])],
36
+ status: "active",
37
+ confidence: 0.7,
38
+ episode_id: input.episode_id,
39
+ related_ids: input.related_ids,
40
+ });
41
+ stored.push({
42
+ id: episodeSummary.id,
43
+ collection: episodeSummary.collection,
44
+ memory_type: "summary",
45
+ });
46
+ for (const observation of input.observations) {
47
+ const result = await storeMemory({
48
+ content: observation.content,
49
+ source: input.source,
50
+ memory_type: observation.memory_type,
51
+ tags: observation.tags,
52
+ status: "active",
53
+ confidence: observation.confidence,
54
+ episode_id: input.episode_id,
55
+ related_ids: input.related_ids,
56
+ last_verified_at: new Date().toISOString(),
57
+ });
58
+ stored.push({
59
+ id: result.id,
60
+ collection: result.collection,
61
+ memory_type: observation.memory_type,
62
+ });
63
+ }
64
+ return { episode_id: input.episode_id, stored };
65
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ import { listMemories } from "../qdrant.js";
3
+ import { MEMORY_TYPES } from "./store.js";
4
+ export const listMemoriesSchema = z.object({
5
+ collection: z
6
+ .enum(["coding", "workspace"])
7
+ .describe("目标 collection:coding=技术记忆, workspace=团队记忆"),
8
+ limit: z
9
+ .number()
10
+ .int()
11
+ .min(1)
12
+ .max(100)
13
+ .optional()
14
+ .default(20)
15
+ .describe("返回条数,默认 20,最大 100"),
16
+ offset: z
17
+ .number()
18
+ .int()
19
+ .min(0)
20
+ .optional()
21
+ .default(0)
22
+ .describe("分页偏移量"),
23
+ source: z.string().optional().describe("按来源过滤"),
24
+ memory_type: z
25
+ .enum(MEMORY_TYPES)
26
+ .optional()
27
+ .describe("按类型过滤:rule / decision / fact / summary / preference"),
28
+ include_inactive: z
29
+ .boolean()
30
+ .optional()
31
+ .default(false)
32
+ .describe("是否包含 superseded/deprecated 记忆,默认 false"),
33
+ });
34
+ export async function listMemoriesTool(input) {
35
+ const { collection, limit, offset, source, memory_type, include_inactive } = input;
36
+ return listMemories(collection, limit, offset, source, memory_type, include_inactive);
37
+ }
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { getEmbeddingProvider } from "../embedding.js";
3
+ import { searchMemory } from "../qdrant.js";
4
+ import { MEMORY_TYPES } from "./store.js";
5
+ const COLLECTION_NAMES = ["coding", "workspace"];
6
+ const SEARCH_MODES = ["vector", "keyword", "hybrid"];
7
+ export const searchMemorySchema = z.object({
8
+ query: z.string().min(1).describe("自然语言搜索查询"),
9
+ collections: z
10
+ .array(z.enum(COLLECTION_NAMES))
11
+ .optional()
12
+ .describe("搜索范围:['coding']仅技术记忆, ['workspace']仅团队记忆, 省略则同时搜两个"),
13
+ limit: z
14
+ .number()
15
+ .int()
16
+ .min(1)
17
+ .max(20)
18
+ .optional()
19
+ .default(5)
20
+ .describe("返回结果数量,默认 5"),
21
+ source: z.string().optional().describe("按来源过滤,如 slack/channel-name"),
22
+ score_threshold: z
23
+ .number()
24
+ .min(0)
25
+ .max(1)
26
+ .optional()
27
+ .describe("最低相似度阈值 (0-1),过滤低相关结果"),
28
+ tags: z
29
+ .array(z.string())
30
+ .optional()
31
+ .describe("按标签过滤,所有指定标签都必须匹配"),
32
+ memory_type: z
33
+ .enum(MEMORY_TYPES)
34
+ .optional()
35
+ .describe("按类型过滤:rule / decision / fact / summary / preference"),
36
+ include_inactive: z
37
+ .boolean()
38
+ .optional()
39
+ .default(false)
40
+ .describe("是否包含 superseded/deprecated 记忆,默认 false"),
41
+ mode: z
42
+ .enum(SEARCH_MODES)
43
+ .optional()
44
+ .default("hybrid")
45
+ .describe("检索模式:vector 仅语义检索,keyword 仅关键词检索,hybrid 混合检索(默认)"),
46
+ use_recency: z
47
+ .boolean()
48
+ .optional()
49
+ .default(true)
50
+ .describe("是否对 fact/summary/preference 应用时间新鲜度加权,默认 true"),
51
+ use_mmr: z
52
+ .boolean()
53
+ .optional()
54
+ .default(true)
55
+ .describe("是否使用 MMR 降低重复结果,默认 true"),
56
+ });
57
+ export async function searchMemoryTool(input) {
58
+ const { query, collections, limit, source, score_threshold, tags, memory_type, include_inactive, mode, use_recency, use_mmr, } = input;
59
+ const cols = collections ?? ["coding", "workspace"];
60
+ const vector = mode === "keyword" ? undefined : await getEmbeddingProvider().embed(query);
61
+ const results = await searchMemory({
62
+ vector,
63
+ query,
64
+ collections: cols,
65
+ limit,
66
+ sourceFilter: source,
67
+ scoreThreshold: score_threshold,
68
+ tagsFilter: tags,
69
+ memoryTypeFilter: memory_type,
70
+ includeInactive: include_inactive,
71
+ mode,
72
+ useRecency: use_recency,
73
+ useMmr: use_mmr,
74
+ });
75
+ return results.map((r) => ({
76
+ id: r.id,
77
+ content: r.content,
78
+ source: r.source,
79
+ memory_type: r.memory_type,
80
+ collection: r.collection,
81
+ tags: r.tags,
82
+ score: r.score,
83
+ vector_score: r.vector_score,
84
+ keyword_score: r.keyword_score,
85
+ recency_score: r.recency_score,
86
+ status: r.status,
87
+ created_at: r.created_at,
88
+ updated_at: r.updated_at,
89
+ supersedes: r.supersedes,
90
+ superseded_by: r.superseded_by,
91
+ correction_reason: r.correction_reason,
92
+ confidence: r.confidence,
93
+ episode_id: r.episode_id,
94
+ related_ids: r.related_ids,
95
+ valid_until: r.valid_until,
96
+ last_verified_at: r.last_verified_at,
97
+ }));
98
+ }
@@ -0,0 +1,71 @@
1
+ import { z } from "zod";
2
+ import { createHash } from "crypto";
3
+ import { getEmbeddingProvider } from "../embedding.js";
4
+ import { upsertMemory, collectionForType, COLLECTIONS } from "../qdrant.js";
5
+ export const MEMORY_TYPES = ["rule", "decision", "fact", "summary", "preference"];
6
+ export const MEMORY_STATUSES = ["active", "superseded", "deprecated"];
7
+ export const storeMemorySchema = z.object({
8
+ content: z.string().min(1).describe("知识内容(摘要或原文)"),
9
+ source: z.string().min(1).describe("来源,如 slack/pod-pay-pilots 或 agent/claude-code"),
10
+ memory_type: z
11
+ .enum(MEMORY_TYPES)
12
+ .describe("记忆类型 — coding collection: rule(编码规则/约束), decision(技术决策), preference(工具偏好); workspace collection: fact(团队事实/状态), summary(Slack/Confluence摘要)"),
13
+ tags: z.array(z.string()).optional().describe("可选标签列表"),
14
+ status: z
15
+ .enum(MEMORY_STATUSES)
16
+ .optional()
17
+ .default("active")
18
+ .describe("记忆生命周期状态,默认 active"),
19
+ confidence: z
20
+ .number()
21
+ .min(0)
22
+ .max(1)
23
+ .optional()
24
+ .describe("记忆可信度 0-1,用于后续审计和重打分"),
25
+ episode_id: z
26
+ .string()
27
+ .optional()
28
+ .describe("产生该记忆的 episode/task ID"),
29
+ related_ids: z
30
+ .array(z.string())
31
+ .optional()
32
+ .describe("相关记忆 ID 列表"),
33
+ supersedes: z
34
+ .string()
35
+ .optional()
36
+ .describe("该记忆修订/替代的旧记忆 ID"),
37
+ correction_reason: z
38
+ .string()
39
+ .optional()
40
+ .describe("修订原因,通常来自用户纠正或事实过期"),
41
+ valid_until: z
42
+ .string()
43
+ .optional()
44
+ .describe("事实类记忆的有效期 ISO 时间"),
45
+ last_verified_at: z
46
+ .string()
47
+ .optional()
48
+ .describe("该记忆最后被验证的 ISO 时间"),
49
+ });
50
+ export async function storeMemory(input) {
51
+ const { content, source, memory_type, tags, status, confidence, episode_id, related_ids, supersedes, correction_reason, valid_until, last_verified_at, } = input;
52
+ const id = createHash("sha256").update(content).digest("hex").slice(0, 32);
53
+ const embedder = getEmbeddingProvider();
54
+ const vector = await embedder.embed(content);
55
+ await upsertMemory(id, vector, {
56
+ content,
57
+ source,
58
+ memory_type,
59
+ tags,
60
+ status,
61
+ confidence,
62
+ episode_id,
63
+ related_ids,
64
+ supersedes,
65
+ correction_reason,
66
+ valid_until,
67
+ last_verified_at,
68
+ created_at: new Date().toISOString(),
69
+ });
70
+ return { id, collection: COLLECTIONS[collectionForType(memory_type)] };
71
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@cchez/memory-mcp",
3
+ "version": "1.0.0",
4
+ "description": "AI memory MCP server with Qdrant vector storage and Ollama/OpenAI embeddings",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "memory-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/",
12
+ "skills/",
13
+ "db/docker-compose.yml",
14
+ "db/.env.example",
15
+ "README.md",
16
+ "DESIGN.md"
17
+ ],
18
+ "scripts": {
19
+ "dev": "node --env-file=.env ./node_modules/tsx/dist/cli.mjs src/index.ts",
20
+ "dev:server": "node --env-file=.env ./node_modules/tsx/dist/cli.mjs src/server.ts",
21
+ "build": "tsc",
22
+ "start": "node dist/index.js",
23
+ "start:server": "node dist/server.js",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build",
26
+ "pack:dry-run": "npm pack --dry-run"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "keywords": [
36
+ "mcp",
37
+ "memory",
38
+ "ai-agent",
39
+ "qdrant",
40
+ "ollama",
41
+ "claude"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/miuid/mcps.git",
46
+ "directory": "memory-mcp"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/miuid/mcps/issues"
50
+ },
51
+ "homepage": "https://github.com/miuid/mcps/tree/main/memory-mcp#readme",
52
+ "license": "UNLICENSED",
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.0.0",
55
+ "@qdrant/js-client-rest": "^1.9.0",
56
+ "express": "^4.18.2",
57
+ "openai": "^4.47.0",
58
+ "zod": "^3.22.4"
59
+ },
60
+ "devDependencies": {
61
+ "@types/express": "^4.17.21",
62
+ "@types/node": "^20.12.0",
63
+ "tsx": "^4.22.4",
64
+ "typescript": "^5.4.4"
65
+ }
66
+ }
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: memory-correct
3
+ description: >
4
+ Correct, revise, deprecate, or supersede existing memory mcp records when the
5
+ user says a stored memory is wrong/stale or new evidence contradicts it. Use
6
+ correct_memory to preserve an auditable revision chain instead of deleting and
7
+ re-saving. Do not use for brand-new unrelated memories; use memory-save.
8
+ ---
9
+
10
+ # Memory Correct Skill
11
+
12
+ ## Purpose
13
+
14
+ Keep memory trustworthy. A stale wrong memory is worse than no memory because
15
+ future agents will confidently apply bad context. Corrections should preserve
16
+ history while making the new truth the default search result.
17
+
18
+ **Announce at start:** "Correcting memory."
19
+
20
+ ## When to Trigger
21
+
22
+ Use this skill when:
23
+
24
+ - The user says a retrieved memory is wrong, stale, misleading, or outdated
25
+ - The user says "actually", "that's wrong", "update that memory", or "revise this"
26
+ - New verified evidence contradicts an existing memory
27
+ - A fact/config/rule changed and future searches should prefer the new version
28
+
29
+ Do not use this skill when:
30
+
31
+ - The new information is unrelated to an existing memory
32
+ - The old record contains a secret or sensitive data that must be removed; use `delete_memory`
33
+ - You cannot identify the old memory ID and collection after searching/listing
34
+
35
+ ## Step 1 — Find the Old Memory
36
+
37
+ If the old memory ID and collection are already visible in the current result,
38
+ use them. Otherwise call `search_memory` with `include_inactive: true` only if
39
+ you are auditing prior corrections; default active search is enough for normal
40
+ corrections.
41
+
42
+ ## Step 2 — Write Replacement Content
43
+
44
+ Write `corrected_content` as a full standalone memory, not a diff.
45
+
46
+ Good:
47
+
48
+ ```
49
+ Airwallex KYC RFI template ID is kyc_rfi_v3_au as of 2026-06-28. The older
50
+ kyc_rfi_v2_au value is deprecated.
51
+ ```
52
+
53
+ Bad:
54
+
55
+ ```
56
+ Actually it is v3.
57
+ ```
58
+
59
+ ## Step 3 — Call `correct_memory`
60
+
61
+ Use:
62
+
63
+ - `id`: old memory ID
64
+ - `collection`: old memory collection
65
+ - `corrected_content`: replacement memory
66
+ - `correction_reason`: short reason, e.g. "user corrected stale template ID"
67
+ - `confidence`: `0.9` for user-confirmed corrections; lower if inferred
68
+
69
+ Omit `source`, `memory_type`, and `tags` when the old values are still correct.
70
+ Override them only when the classification itself was wrong.
71
+
72
+ ## Step 4 — Confirm Briefly
73
+
74
+ Tell the user the old memory was superseded by the new memory:
75
+
76
+ > "Updated memory. Old ID `[old_id]` is superseded by `[new_id]`."
77
+
78
+ ## Self-Check
79
+
80
+ - [ ] Did you identify the old memory ID and collection?
81
+ - [ ] Is the replacement content standalone and future-readable?
82
+ - [ ] Did you use `correct_memory`, not delete + store?
83
+ - [ ] Did you preserve audit history unless deletion was required for safety?
@@ -0,0 +1,209 @@
1
+ ---
2
+ name: memory-save
3
+ description: >
4
+ Save valuable learnings, decisions, rules, or facts to the memory mcp
5
+ knowledge base. Triggers automatically when the agent discovers something
6
+ worth preserving: a coding constraint, an architectural decision, a resolved
7
+ ambiguity, a team process, or a configuration fact. Also triggers when the
8
+ user explicitly asks to save, remember, or store something. Do NOT save
9
+ trivial, transient, or already-known information. If the user corrects or
10
+ invalidates an existing memory, use correct_memory rather than delete + store.
11
+ ---
12
+
13
+ # Memory Save Skill
14
+
15
+ ## Purpose
16
+
17
+ Capture knowledge that would otherwise be lost between sessions. The knowledge
18
+ base is only as useful as what gets put into it. This skill enforces quality
19
+ over quantity — save things that are non-obvious, durable, and reusable.
20
+
21
+ **Announce at start:** "Saving to memory."
22
+
23
+ ---
24
+
25
+ ## When to Trigger
26
+
27
+ **Automatic triggers (save without being asked):**
28
+ - A coding rule, constraint, or convention is discovered or confirmed
29
+ ("methods must stay under complexity 15 or CI fails")
30
+ - An architectural or technology decision is made or ratified
31
+ ("we chose Qdrant over pgvector because of Docker-friendliness")
32
+ - A configuration fact is established that future agents will need
33
+ ("Airwallex RFI template ID for KYC is ...")
34
+ - An ambiguity is resolved that took effort to figure out
35
+ ("the `source` field format is `system/channel-name`")
36
+ - The user says "remember this", "save this", "store this", "note this down"
37
+
38
+ **Correction triggers (use `correct_memory`, not `store_memory`):**
39
+ - The user says a retrieved memory is wrong, outdated, misleading, or no longer true
40
+ - New evidence contradicts an existing memory found during `search_memory`
41
+ - A rule, fact, or decision has changed but the old memory should remain auditable
42
+ - The user says "update that memory", "revise this", "actually X", or "that's wrong"
43
+
44
+ **Do NOT save:**
45
+ - Things already in the knowledge base (check first if unsure)
46
+ - Transient task state ("currently working on PR #123")
47
+ - General knowledge not specific to this project/team
48
+ - Unverified assumptions or guesses
49
+ - Sensitive credentials or secrets
50
+
51
+ If a correction trigger fires, first identify the old memory ID and collection
52
+ from the search/list result, then call `correct_memory` with the replacement
53
+ content and a short `correction_reason`. Do not call `delete_memory` for normal
54
+ corrections; deletion is only for sensitive, duplicated, or intentionally
55
+ removed records.
56
+
57
+ ---
58
+
59
+ ## Step 1 — Assess Save Worthiness
60
+
61
+ Before saving, answer these three questions:
62
+
63
+ 1. **Is it durable?** Will this still be true / relevant in 3+ months?
64
+ - Rule: "complexity ≤ 15" → yes
65
+ - Task state: "fixing bug in PR #42" → no
66
+
67
+ 2. **Is it non-obvious?** Would a new engineer or agent know this without being told?
68
+ - Config fact: "Airwallex uses hosted flow, not redirect" → yes
69
+ - General fact: "TypeScript supports generics" → no
70
+
71
+ 3. **Is it actionable?** Does knowing this change how work gets done?
72
+ - Decision: "use SHA-256 content hash as Qdrant point ID for dedup" → yes
73
+ - Meeting note: "we had standup at 9:30" → no
74
+
75
+ If all three are yes → save. If any is no → skip (or ask user if unsure).
76
+
77
+ ---
78
+
79
+ ## Step 2 — Classify the Memory
80
+
81
+ Choose the correct `memory_type`:
82
+
83
+ | Type | When to use | Collection |
84
+ |---|---|---|
85
+ | `rule` | Coding constraints, CI rules, style enforcements, hard limits | `coding` |
86
+ | `decision` | Architecture choices, technology selections, design tradeoffs | `coding` |
87
+ | `preference` | Team/user preferences, conventions, soft defaults | `coding` |
88
+ | `fact` | Current state, configuration values, team facts, integration details | `workspace` |
89
+ | `summary` | Distilled summaries of longer discussions, Slack threads, meetings | `workspace` |
90
+
91
+ The collection is auto-routed by `memory_type` — you don't need to specify it.
92
+
93
+ ---
94
+
95
+ ## Step 3 — Write Good Content
96
+
97
+ The `content` field is what gets embedded and searched. Write it for future
98
+ retrieval, not for the current moment.
99
+
100
+ **Good content:**
101
+ - Starts with the key fact/rule/decision upfront
102
+ - Includes enough context to be understood standalone (no "as mentioned above")
103
+ - Uses specific names, numbers, and system names where relevant
104
+ - Is 1–5 sentences: dense but not padded
105
+
106
+ **Bad content:**
107
+ - "We decided X" (vague — decided what exactly, and why?)
108
+ - A dump of raw Slack messages (noise drowns signal)
109
+ - A single word or phrase (not enough context for semantic search)
110
+
111
+ **Template by type:**
112
+
113
+ ```
114
+ rule:
115
+ "[Rule statement]. Applies to [scope]. Reason: [why this rule exists].
116
+ Consequence of violation: [what breaks]."
117
+
118
+ decision:
119
+ "Decision: [what was decided]. Context: [why this decision was needed].
120
+ Rationale: [key reason(s)]. Alternatives rejected: [if relevant]."
121
+
122
+ fact:
123
+ "[System/entity]: [specific fact]. As of [date if time-sensitive].
124
+ Source: [where this came from]."
125
+
126
+ summary:
127
+ "[Topic] discussion summary ([date/channel]):
128
+ Key points: [1-3 bullets]. Decisions/actions: [if any]."
129
+
130
+ preference:
131
+ "[Team/user] prefers [specific preference] for [context].
132
+ Reason: [if known]."
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Step 4 — Choose Tags
138
+
139
+ Tags are optional but improve filtered retrieval. Pick 2–5 specific tags:
140
+
141
+ - Use existing tag vocabulary if you've seen tags in prior search results
142
+ - Prefer specific over generic: `rfi-webhook` over `webhook`
143
+ - Include: system names, feature names, ticket IDs, technology names
144
+ - Always include the domain: `payments`, `onboarding`, `billing`, etc.
145
+
146
+ ---
147
+
148
+ ## Step 5 — Execute Save
149
+
150
+ If this is correcting an existing memory, skip this step and use Step 5b.
151
+
152
+ Call `store_memory` with:
153
+ - `content`: written per Step 3 template
154
+ - `source`: `agent/claude-code` for agent-discovered learnings;
155
+ `user/manual` for user-requested saves;
156
+ `slack/<channel>` / `confluence/<page>` for external source ingestion
157
+ - `memory_type`: from Step 2
158
+ - `tags`: from Step 4
159
+
160
+ The tool returns `{ id, collection }`. Note which collection it went to —
161
+ confirm it matches the expected routing from Step 2.
162
+
163
+ **If `store_memory` returns the same ID as a previous save**, the content was
164
+ identical — this is a silent dedup (idempotent upsert). That's correct behaviour.
165
+
166
+ ## Step 5b — Execute Correction
167
+
168
+ Call `correct_memory` when an old memory should be revised but kept auditable:
169
+
170
+ - `id`: old memory ID
171
+ - `collection`: old memory collection (`coding` or `workspace`)
172
+ - `corrected_content`: full replacement memory, standalone and future-readable
173
+ - `correction_reason`: short reason, e.g. "user corrected stale config value"
174
+ - `memory_type`, `source`, `tags`: optional; omit to inherit from old memory
175
+ - `confidence`: use `0.9` for user-confirmed corrections; lower it if inferred
176
+
177
+ `correct_memory` writes a new active memory and marks the old one as
178
+ `superseded`. Future searches hide the superseded memory by default; audit flows
179
+ can pass `include_inactive: true`.
180
+
181
+ ---
182
+
183
+ ## Step 6 — Confirm to User
184
+
185
+ For **automatic saves** (agent-initiated), confirm briefly:
186
+
187
+ > "Saved to memory [collection/type]: [one-line summary of what was saved]."
188
+
189
+ For **manual saves** (user asked), confirm with the ID in case they need to delete it later:
190
+
191
+ > "Saved. ID: `[id]`, collection: `[collection]`, type: `[memory_type]`."
192
+
193
+ For **corrections**, confirm both sides of the revision chain:
194
+
195
+ > "Updated memory. Old ID `[old_id]` is superseded by `[new_id]`."
196
+
197
+ Do not ask for confirmation before saving — act, then report. If the user
198
+ wants to remove it, they can use `delete_memory(id, collection)`.
199
+
200
+ ---
201
+
202
+ ## Self-Check Before Finishing
203
+
204
+ - [ ] Did the content pass all three save-worthiness tests (durable, non-obvious, actionable)?
205
+ - [ ] Is `memory_type` correctly classified (not just "summary" for everything)?
206
+ - [ ] Is `content` written for future retrieval (standalone, specific, dense)?
207
+ - [ ] If this revised an existing memory, did you use `correct_memory` instead of delete + store?
208
+ - [ ] Was the save confirmed to the user?
209
+ - [ ] For automatic saves: did you save silently during work rather than interrupting flow?