dsh-plugin-xujie-memory 0.5.0 → 0.7.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.
Files changed (84) hide show
  1. package/ACCEPTANCE.md +37 -0
  2. package/BUILD-PROVENANCE.json +53 -0
  3. package/CHANGELOG.md +40 -1
  4. package/LICENSE +21 -21
  5. package/README.md +67 -17
  6. package/README_EN.md +54 -8
  7. package/cordis.patch.yml +49 -49
  8. package/lib/client.js +17219 -0
  9. package/lib/controller.js +307 -0
  10. package/lib/index.js +200 -14
  11. package/lib/typert.host.d.ts +3 -0
  12. package/lib/typert.host.js +15104 -0
  13. package/lib/typert.remote-client.d.ts +40 -0
  14. package/lib/typert.remote-client.d.ts.map +1 -0
  15. package/lib/typert.remote-client.js +15093 -0
  16. package/lib/types.d.ts +13 -0
  17. package/lib/types.js +1 -0
  18. package/package.json +49 -5
  19. package/python/specs/characters/aloof.json +14 -14
  20. package/python/specs/characters/gentle.json +14 -14
  21. package/python/specs/characters/tsundere.json +14 -14
  22. package/python/specs/debt.v1.json +31 -31
  23. package/python/specs/nickname.v1.json +16 -16
  24. package/python/specs/preference.v1.json +16 -16
  25. package/python/xujie_memory/__init__.py +123 -123
  26. package/python/xujie_memory/admission.py +211 -211
  27. package/python/xujie_memory/audit.py +377 -377
  28. package/python/xujie_memory/benchmark.py +281 -281
  29. package/python/xujie_memory/character.py +199 -199
  30. package/python/xujie_memory/console.py +41 -41
  31. package/python/xujie_memory/consolidate.py +356 -356
  32. package/python/xujie_memory/due.py +121 -121
  33. package/python/xujie_memory/emotion.py +573 -573
  34. package/python/xujie_memory/engine.py +1333 -1312
  35. package/python/xujie_memory/errors.py +98 -98
  36. package/python/xujie_memory/importance.py +104 -104
  37. package/python/xujie_memory/judge.py +350 -350
  38. package/python/xujie_memory/management.py +203 -0
  39. package/python/xujie_memory/memory.py +71 -10
  40. package/python/xujie_memory/orchestrator.py +690 -666
  41. package/python/xujie_memory/phrasing.py +292 -292
  42. package/python/xujie_memory/privacy.py +105 -0
  43. package/python/xujie_memory/schema.sql +492 -480
  44. package/python/xujie_memory/server.py +76 -0
  45. package/python/xujie_memory/specs.py +170 -170
  46. package/python/xujie_memory/summary.py +238 -238
  47. package/scripts/build.mjs +19 -0
  48. package/scripts/prepack.mjs +9 -0
  49. package/scripts/test-all.mjs +9 -0
  50. package/scripts/test-node.mjs +10 -0
  51. package/src/client/MemoryPanel.tsx +536 -0
  52. package/src/client/NeuralField.tsx +88 -0
  53. package/src/client/css.d.ts +4 -0
  54. package/src/client/icons.tsx +28 -0
  55. package/src/client/index.tsx +59 -0
  56. package/src/client/memory-adapter.ts +67 -0
  57. package/src/client/model.ts +79 -0
  58. package/src/client/neural/engine.ts +999 -0
  59. package/src/client/neural/morphology.ts +219 -0
  60. package/src/client/panel.css +337 -0
  61. package/src/client/types.ts +6 -0
  62. package/src/controller.ts +88 -0
  63. package/src/generated/typert.host.js +449 -0
  64. package/src/generated/typert.remote-client.js +438 -0
  65. package/src/index.ts +2 -0
  66. package/src/types.ts +13 -0
  67. package/test/cordis-fixture.mjs +5 -0
  68. package/test/management_regression.py +187 -0
  69. package/test/node-regression.entry.mjs +52 -0
  70. package/vendor/DSH-LICENSE.txt +21 -0
  71. package/vendor/THIRD-PARTY.md +6 -0
  72. package/vendor/dsh-protocol/index.ts +484 -0
  73. package/vendor/dsh-protocol/json-value.ts +53 -0
  74. package/vendor/dsh-protocol/owned-value.ts +39 -0
  75. package/vendor/dsh-protocol/principal.ts +17 -0
  76. package/vendor/dsh-protocol/remote-access.ts +38 -0
  77. package/vendor/dsh-protocol/remote-error.ts +49 -0
  78. package/vendor/dsh-protocol/remote-info.ts +161 -0
  79. package/vendor/dsh-protocol/types.ts +939 -0
  80. package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
  81. package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
  82. package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
  83. package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
  84. package/vendor/licenses/zod-MIT.txt +21 -0
@@ -1,480 +1,492 @@
1
- -- Xujie-Memory 参考存储模式(阶段 1)
2
- -- 对应设计文档 17.1。此处只实现内核所需表;clarifications / outbox / recall_log
3
- -- 属后续阶段,留空不影响内核语义。
4
-
5
- PRAGMA journal_mode = WAL;
6
- PRAGMA foreign_keys = ON;
7
- PRAGMA synchronous = FULL;
8
-
9
- -- ---------------------------------------------------------------- 作用域
10
- CREATE TABLE IF NOT EXISTS scopes (
11
- scope_id TEXT PRIMARY KEY,
12
- tenant_id TEXT NOT NULL,
13
- agent_id TEXT NOT NULL,
14
- world_id TEXT NOT NULL,
15
- created_at TEXT NOT NULL,
16
- UNIQUE (tenant_id, agent_id, world_id)
17
- );
18
-
19
- -- ---------------------------------------------------------------- 痕迹(不可变)
20
- CREATE TABLE IF NOT EXISTS traces (
21
- trace_id TEXT PRIMARY KEY,
22
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
23
- source_ref TEXT NOT NULL,
24
- actor_id TEXT NOT NULL,
25
- source_kind TEXT NOT NULL,
26
- content TEXT NOT NULL,
27
- content_hash TEXT NOT NULL,
28
- recorded_at TEXT NOT NULL,
29
- source_time TEXT,
30
- source_time_precision TEXT,
31
- context_ref TEXT,
32
- erased_at TEXT,
33
- UNIQUE (scope_id, source_ref)
34
- );
35
-
36
- CREATE INDEX IF NOT EXISTS idx_traces_actor ON traces(scope_id, actor_id);
37
-
38
- -- ---------------------------------------------------------------- 编码与解释
39
- CREATE TABLE IF NOT EXISTS encodings (
40
- encoding_id TEXT PRIMARY KEY,
41
- trace_id TEXT NOT NULL REFERENCES traces(trace_id),
42
- encoder_version TEXT NOT NULL,
43
- prompt_version TEXT NOT NULL,
44
- schema_version TEXT NOT NULL,
45
- payload_json TEXT NOT NULL,
46
- generated_at TEXT NOT NULL
47
- );
48
-
49
- CREATE TABLE IF NOT EXISTS interpretations (
50
- interpretation_id TEXT PRIMARY KEY,
51
- encoding_id TEXT NOT NULL REFERENCES encodings(encoding_id),
52
- trace_id TEXT NOT NULL REFERENCES traces(trace_id),
53
- relation TEXT NOT NULL,
54
- modality TEXT NOT NULL,
55
- polarity TEXT NOT NULL,
56
- slots_json TEXT NOT NULL,
57
- semantic_op TEXT, -- 语义操作:incur/repay/waive/set_total/correct/retract/noop
58
- -- 注意与 events.operation 区分:后者是 append/update/retract,
59
- -- 描述"事件怎么进流",这里描述"这句话是什么意思"。
60
- -- 可空:v0.5 之前只写 slots.intent,读时回落。
61
- signals_json TEXT,
62
- created_at TEXT NOT NULL
63
- );
64
-
65
- CREATE INDEX IF NOT EXISTS idx_interp_trace ON interpretations(trace_id);
66
-
67
- -- ---------------------------------------------------------------- 闸门判定(可审计)
68
- CREATE TABLE IF NOT EXISTS gate_decisions (
69
- decision_id TEXT PRIMARY KEY,
70
- interpretation_id TEXT NOT NULL REFERENCES interpretations(interpretation_id),
71
- spec_version TEXT NOT NULL,
72
- outcome TEXT NOT NULL, -- commit / pending / record_only / reject
73
- reasons_json TEXT NOT NULL,
74
- admission_mode TEXT NOT NULL, -- shadow / assisted / auto
75
- decided_at TEXT NOT NULL
76
- );
77
-
78
- -- ---------------------------------------------------------------- 事件(唯一事实源)
79
- CREATE TABLE IF NOT EXISTS events (
80
- event_id TEXT PRIMARY KEY,
81
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
82
- state_key TEXT NOT NULL,
83
- idempotency_key TEXT NOT NULL,
84
- payload_hash TEXT NOT NULL,
85
- operation_group_id TEXT,
86
- operation TEXT NOT NULL, -- append / update / retract / conflict
87
- effect_kind TEXT NOT NULL, -- set / delta / invalidate
88
- payload_json TEXT NOT NULL,
89
- gate_decision_id TEXT REFERENCES gate_decisions(decision_id),
90
- actor_id TEXT NOT NULL,
91
- effective_at TEXT NOT NULL,
92
- recorded_at TEXT NOT NULL,
93
- sequence_no INTEGER NOT NULL,
94
- spec_version TEXT NOT NULL,
95
- target_ref TEXT,
96
- superseded_by TEXT,
97
- void INTEGER NOT NULL DEFAULT 0, -- 1 = 已被纠正/撤销,不参与求值
98
- UNIQUE (scope_id, idempotency_key),
99
- UNIQUE (state_key, sequence_no)
100
- );
101
-
102
- CREATE INDEX IF NOT EXISTS idx_events_stream ON events(state_key, sequence_no);
103
- CREATE INDEX IF NOT EXISTS idx_events_effective ON events(state_key, effective_at, sequence_no);
104
- CREATE INDEX IF NOT EXISTS idx_events_group ON events(operation_group_id);
105
-
106
- CREATE TABLE IF NOT EXISTS event_evidence (
107
- event_id TEXT NOT NULL REFERENCES events(event_id),
108
- trace_id TEXT NOT NULL REFERENCES traces(trace_id),
109
- span_start INTEGER,
110
- span_end INTEGER,
111
- PRIMARY KEY (event_id, trace_id)
112
- );
113
-
114
- CREATE INDEX IF NOT EXISTS idx_evidence_trace ON event_evidence(trace_id);
115
-
116
- -- ---------------------------------------------------------------- 状态投影
117
- CREATE TABLE IF NOT EXISTS states (
118
- state_key TEXT PRIMARY KEY,
119
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
120
- value_json TEXT,
121
- status TEXT NOT NULL, -- known/unknown/disputed/stale/retracted
122
- valid_from TEXT,
123
- valid_to TEXT,
124
- last_known_json TEXT,
125
- conflict_reason TEXT,
126
- version INTEGER NOT NULL DEFAULT 0,
127
- watermark INTEGER NOT NULL DEFAULT 0,
128
- evaluated_as_of TEXT,
129
- spec_version TEXT NOT NULL
130
- );
131
-
132
- -- ---------------------------------------------------------------- 依赖登记
133
- CREATE TABLE IF NOT EXISTS state_deps (
134
- derived_key TEXT NOT NULL,
135
- input_key TEXT NOT NULL,
136
- rule_version TEXT NOT NULL,
137
- PRIMARY KEY (derived_key, input_key, rule_version)
138
- );
139
-
140
- -- ---------------------------------------------------------------- 投影检查点(可选加速)
141
- CREATE TABLE IF NOT EXISTS state_snapshots (
142
- state_key TEXT NOT NULL,
143
- watermark INTEGER NOT NULL,
144
- value_json TEXT,
145
- status TEXT NOT NULL,
146
- version INTEGER NOT NULL,
147
- created_at TEXT NOT NULL,
148
- PRIMARY KEY (state_key, watermark)
149
- );
150
-
151
- -- ---------------------------------------------------------------- 澄清队列(阶段 3)
152
- CREATE TABLE IF NOT EXISTS clarifications (
153
- request_id TEXT PRIMARY KEY,
154
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
155
- interpretation_ids_json TEXT NOT NULL,
156
- kind TEXT NOT NULL, -- missing_slot / ambiguous / unconfirmed / conflict
157
- detail_json TEXT,
158
- status TEXT NOT NULL, -- open / answered / expired / dismissed
159
- created_at TEXT NOT NULL,
160
- expires_at TEXT,
161
- -- 这条问题**已经念给模型看过了**的时间。
162
- --
163
- -- 为什么必须有这一列:宿主形态下每轮都会重新拼一次材料,
164
- -- 没有这个标记,「还没确定的事」就会每轮重复出现——
165
- -- 用户被同一句话连问五遍,那不是澄清,是审问。
166
- --
167
- -- 语义是"已提过",不是"已回答"。提过就不再主动提第二次,
168
- -- 用户自己说清楚了(或超时)才算结束。
169
- asked_at TEXT
170
- );
171
-
172
- CREATE INDEX IF NOT EXISTS idx_clarif_scope ON clarifications(scope_id, status);
173
-
174
- -- ---------------------------------------------------------------- 只记录未提交(阶段 3 补)
175
- -- XM-10:record_only 的内容必须有独立落点,否则"记住他说过"只做到一半。
176
- -- status=open 的内容可在后续被 promote 成事件,这条路径现在预留、未实现提升逻辑。
177
- CREATE TABLE IF NOT EXISTS observations (
178
- observation_id TEXT PRIMARY KEY,
179
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
180
- trace_id TEXT NOT NULL REFERENCES traces(trace_id),
181
- interpretation_id TEXT NOT NULL,
182
- relation TEXT NOT NULL,
183
- modality TEXT NOT NULL,
184
- polarity TEXT NOT NULL,
185
- slots_json TEXT NOT NULL,
186
- excerpt TEXT NOT NULL,
187
- reason TEXT NOT NULL,
188
- status TEXT NOT NULL DEFAULT 'open', -- open / promoted / archived
189
- promoted_event_id TEXT,
190
- created_at TEXT NOT NULL
191
- );
192
-
193
- CREATE INDEX IF NOT EXISTS idx_obs_scope ON observations(scope_id, relation, status);
194
- CREATE INDEX IF NOT EXISTS idx_obs_trace ON observations(trace_id);
195
-
196
- -- ---------------------------------------------------------------- 全文检索(阶段 4)
197
- -- 独立 FTS 表而非外部内容表:traces 的主键是 TEXT,rowid 对齐不可靠。
198
- -- trigram 分词对中文有效(3 字符滑窗),但要求查询 >= 3 字符,短查询在代码里降级为 LIKE。
199
- CREATE VIRTUAL TABLE IF NOT EXISTS traces_fts USING fts5(
200
- trace_id UNINDEXED,
201
- content,
202
- tokenize = 'trigram'
203
- );
204
-
205
- -- ---------------------------------------------------------------- 生命周期(阶段 5)
206
- -- 5.9:生命周期元信息与不可变痕迹分离。痕迹不改,权重和层级可改。
207
- CREATE TABLE IF NOT EXISTS lifecycle (
208
- ref_type TEXT NOT NULL, -- trace
209
- ref_id TEXT NOT NULL,
210
- scope_id TEXT NOT NULL,
211
- base REAL NOT NULL DEFAULT 1.0,
212
- use_count INTEGER NOT NULL DEFAULT 0,
213
- last_used_at TEXT,
214
- anchor INTEGER NOT NULL DEFAULT 0,
215
- level TEXT NOT NULL DEFAULT 'hot', -- hot / cold / archived
216
- created_at TEXT NOT NULL,
217
- PRIMARY KEY (ref_type, ref_id)
218
- );
219
-
220
- CREATE INDEX IF NOT EXISTS idx_lifecycle_scope ON lifecycle(scope_id, level, anchor);
221
-
222
- -- ---------------------------------------------------------------- 权重事件
223
- -- v0.40.0:「权重曲线」要能画出"这条记忆的权重是怎么变的",
224
- -- 光有 lifecycle 表的 use_count 是不够的——那是一个**计数**,
225
- -- 不是**历史**。计数回答不了"三个月前它有多重要"。
226
- --
227
- -- 为什么是事件表而不是加一列「priority_history」:
228
- -- 本项目的铁律是「事件是唯一事实源,投影可以重算」。
229
- -- priority 是派生值(base × 时间衰减 × 使用增益),
230
- -- 把它存成第二份就等于有两个真相源(CONTRIBUTING 红线四)。
231
- --
232
- -- 所以这里只记**事实**:这条记忆在某个时刻被用了 / 被搜到了 / 被设成锚点了。
233
- -- 曲线由这些事件重放算出来——和 events → states 是同一种关系。
234
- --
235
- -- 为什么不用 JSON 列:重放要按时间排序、要做区间聚合,
236
- -- 独立成表能走索引;塞进 lifecycle 的一列就得整行反序列化。
237
- CREATE TABLE IF NOT EXISTS weight_events (
238
- event_id TEXT PRIMARY KEY,
239
- ref_type TEXT NOT NULL,
240
- ref_id TEXT NOT NULL,
241
- scope_id TEXT NOT NULL,
242
- kind TEXT NOT NULL, -- use(真被用)/ search(只被搜到)/ anchor(设锚)/ unanchor(取消锚)
243
- occurred_at TEXT NOT NULL,
244
- detail_json TEXT
245
- );
246
-
247
- CREATE INDEX IF NOT EXISTS idx_wgev_ref ON weight_events(ref_type, ref_id, occurred_at);
248
- CREATE INDEX IF NOT EXISTS idx_wgev_scope ON weight_events(scope_id, occurred_at);
249
-
250
- -- 摘要产物。合并生成新行,旧行标 merged,不覆盖(12.2)。
251
- CREATE TABLE IF NOT EXISTS summaries (
252
- summary_id TEXT PRIMARY KEY,
253
- scope_id TEXT NOT NULL,
254
- input_refs_json TEXT NOT NULL,
255
- text TEXT NOT NULL,
256
- generator_version TEXT NOT NULL,
257
- coverage_json TEXT,
258
- omissions_json TEXT,
259
- validation_status TEXT NOT NULL DEFAULT 'unchecked', -- unchecked / ok / failed
260
- lifecycle_state TEXT NOT NULL DEFAULT 'active', -- active / merged / archived
261
- created_at TEXT NOT NULL
262
- );
263
-
264
- CREATE INDEX IF NOT EXISTS idx_summaries_scope ON summaries(scope_id, lifecycle_state);
265
-
266
- -- ---------------------------------------------------------------- 承诺(投影)
267
- -- 「下个月我还你两袋」不改变余额,但它留下了一个**可以被追讨的对象**。
268
- --
269
- -- 这张表是投影,不是事实源。事实源仍然是 events:
270
- -- 承诺是 effect_kind='none' 的事件,兑现是一条 delta<0 的事件。
271
- -- 所以这张表可以随时用 `Engine.rebuild_commitments()` 从事件流重建,
272
- -- 重建结果与增量维护**逐字相同**(有测试守着)。
273
- --
274
- -- 为什么不做成"事件表里的一列":承诺有生命周期
275
- -- (open → kept / broken / cancelled),而事件是不可变的。
276
- -- 把可变状态放进不可变表,就再也分不清"记录"和"现状"了。
277
- CREATE TABLE IF NOT EXISTS commitments (
278
- commitment_id TEXT PRIMARY KEY,
279
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
280
- state_key TEXT NOT NULL,
281
- event_id TEXT NOT NULL REFERENCES events(event_id),
282
- actor_id TEXT NOT NULL,
283
- quantity REAL NOT NULL,
284
- remaining REAL NOT NULL,
285
- unit TEXT, -- 展示用(「2 碗」);不参与计算
286
- due_hint TEXT,
287
- due_at TEXT,
288
- status TEXT NOT NULL, -- open / kept / partial / cancelled
289
- settled_by TEXT, -- 结清它的那条事件
290
- created_at TEXT NOT NULL,
291
- updated_at TEXT NOT NULL
292
- );
293
-
294
- CREATE INDEX IF NOT EXISTS idx_commitments_open
295
- ON commitments(scope_id, state_key, status, created_at);
296
-
297
-
298
- -- ---------------------------------------------------------------- 准入与熔断
299
- -- 每个 `(关系, 句式)` 一条。**它是唯一会"自己变"的配置**——
300
- -- 因为准入等级本来就该随证据动:稳的格子放开,被纠正的格子收回。
301
- --
302
- -- 句式(pattern)不是自然语言模板,是**风险分类**:
303
- -- incur:plain 「我欠你三袋」——有数量、无歧义、无第三方
304
- -- incur:third_party 「替老张赊的」——施事不明,永远 assisted
305
- -- correct:revision 「不对,是五十」——改历史,永远 assisted
306
- --
307
- -- 为什么不存"每句话的指纹":那会过拟合到语料。分类要粗到能解释、
308
- -- 细到能区分风险。
309
- CREATE TABLE IF NOT EXISTS admission_state (
310
- relation TEXT NOT NULL,
311
- pattern TEXT NOT NULL,
312
- level TEXT NOT NULL, -- auto / assisted / shadow
313
- failures INTEGER NOT NULL DEFAULT 0, -- 连续被纠正次数
314
- successes INTEGER NOT NULL DEFAULT 0, -- 熔断后连续确认次数
315
- opened_at TEXT, -- 熔断时刻(NULL = 未熔断)
316
- updated_at TEXT NOT NULL,
317
- PRIMARY KEY (relation, pattern)
318
- );
319
-
320
- -- 熔断流水,供审计:谁在什么时候把这个格子关掉了
321
- CREATE TABLE IF NOT EXISTS breaker_log (
322
- log_id TEXT PRIMARY KEY,
323
- scope_id TEXT NOT NULL,
324
- relation TEXT NOT NULL,
325
- pattern TEXT NOT NULL,
326
- action TEXT NOT NULL, -- open / close / failure
327
- reason TEXT NOT NULL,
328
- at TEXT NOT NULL
329
- );
330
-
331
- -- ---------------------------------------------------------------- 人物画像
332
- -- 画像回答的是「这个人是谁」,不是「账上还有多少」。
333
- --
334
- -- 三条设计约束(对应方案第四章):
335
- -- 1. 标签是**印象**,不是事实。事实层是 events,这里只放"我对他的看法"。
336
- -- 2. 来源必须分开记:confirmed(用户明说)/ observed(系统看到)/ inferred(模型猜)。
337
- -- 推断**永不自动升级为事实**——它可以影响语气,不能授权。
338
- -- 3. 账号不等于人物。可疑期间新标签先暂存(staged),不污染主画像。
339
- CREATE TABLE IF NOT EXISTS person_profiles (
340
- profile_id TEXT PRIMARY KEY,
341
- scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
342
- account_id TEXT NOT NULL,
343
- display_name TEXT,
344
- -- active 正常 / suspect 有换人线索(新标签进暂存区)
345
- status TEXT NOT NULL DEFAULT 'active',
346
- created_at TEXT NOT NULL,
347
- updated_at TEXT NOT NULL,
348
- UNIQUE (scope_id, account_id)
349
- );
350
-
351
- CREATE INDEX IF NOT EXISTS idx_profiles_scope
352
- ON person_profiles(scope_id, status);
353
-
354
- CREATE TABLE IF NOT EXISTS person_tags (
355
- tag_id TEXT PRIMARY KEY,
356
- profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
357
- scope_id TEXT NOT NULL,
358
- -- 标签类别:address(怎么称呼)/ preference(喜好)/ habit(习惯)
359
- -- style(表达方式)/ topic(常聊的话题)/ relation(关系)
360
- kind TEXT NOT NULL,
361
- value TEXT NOT NULL,
362
- -- 来源三分,决定这条标签能不能当事实用
363
- source TEXT NOT NULL, -- confirmed / observed / inferred
364
- -- 可信度与"多常用"是两件事(3.3):反复提起的谎话很好想起,但仍然不可信。
365
- confidence REAL NOT NULL DEFAULT 0.5,
366
- seen_count INTEGER NOT NULL DEFAULT 1,
367
- -- active 生效 / staged 暂存(画像可疑时)/ corrected 被纠正 / expired 失效
368
- status TEXT NOT NULL DEFAULT 'active',
369
- -- 出处。标签必须能追到一句原话,否则它只是模型的想象。
370
- evidence_trace_id TEXT,
371
- -- 被这条标签取代的上一条(纠正时留痕,不覆盖)
372
- supersedes TEXT,
373
- created_at TEXT NOT NULL,
374
- updated_at TEXT NOT NULL
375
- );
376
-
377
- CREATE INDEX IF NOT EXISTS idx_tags_profile
378
- ON person_tags(profile_id, status, kind);
379
- CREATE INDEX IF NOT EXISTS idx_tags_evidence
380
- ON person_tags(evidence_trace_id);
381
-
382
- -- 认知异常:轻量模型只出**线索**,不下身份结论(4.5)。
383
- CREATE TABLE IF NOT EXISTS profile_alerts (
384
- alert_id TEXT PRIMARY KEY,
385
- scope_id TEXT NOT NULL,
386
- profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
387
- kind TEXT NOT NULL, -- style_shift / address_shift / explicit_claim
388
- signals_json TEXT NOT NULL, -- 命中的具体线索,给人看
389
- status TEXT NOT NULL DEFAULT 'open', -- open / dismissed / escalated
390
- evidence_trace_id TEXT,
391
- created_at TEXT NOT NULL,
392
- resolved_at TEXT
393
- );
394
-
395
- CREATE INDEX IF NOT EXISTS idx_alerts_open
396
- ON profile_alerts(scope_id, status);
397
-
398
- -- 判断记录:主 AI 对异常给出过什么结论(保留历史,便于事后复盘)。
399
- CREATE TABLE IF NOT EXISTS profile_evaluations (
400
- evaluation_id TEXT PRIMARY KEY,
401
- alert_id TEXT NOT NULL REFERENCES profile_alerts(alert_id),
402
- verdict TEXT NOT NULL, -- same_person / different_person / unknown
403
- reasons_json TEXT NOT NULL,
404
- actor_id TEXT NOT NULL,
405
- created_at TEXT NOT NULL
406
- );
407
-
408
- -- ---------------------------------------------------------------- 情绪与关系
409
- -- 方案第六章。两条线必须分开:
410
- --
411
- -- 短期情绪 anger / joy / sadness / anxiety / grievance
412
- -- 会随时间自然消退。**这是"消气"。**
413
- -- 长期关系 trust / closeness / respect
414
- -- 不会因为时间过去自己变好。**这是"原谅"。**
415
- --
416
- -- 6.2 那句话是这一层的全部难点:
417
- -- 消气不等于原谅,原谅不等于遗忘。
418
- -- 所以两张表、两套规则:情绪有衰减,关系没有。
419
- CREATE TABLE IF NOT EXISTS emotion_events (
420
- event_id TEXT PRIMARY KEY,
421
- scope_id TEXT NOT NULL,
422
- profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
423
- emotion TEXT NOT NULL, -- anger / joy / sadness / anxiety / grievance
424
- -- 这次事件把情绪推了多少。正数累积,负数安抚。
425
- delta REAL NOT NULL,
426
- -- 触发原因。不写原因的情绪,事后无法解释"你当时为什么生气"。
427
- reason TEXT NOT NULL,
428
- -- 指向具体事件或痕迹,能追到是哪句话惹的
429
- evidence_trace_id TEXT,
430
- occurred_at TEXT NOT NULL
431
- );
432
-
433
- CREATE INDEX IF NOT EXISTS idx_emotion_events
434
- ON emotion_events(profile_id, emotion, occurred_at);
435
-
436
- -- 关系事件。**没有衰减**——信任掉下去不会自己长回来。
437
- CREATE TABLE IF NOT EXISTS relationship_events (
438
- event_id TEXT PRIMARY KEY,
439
- scope_id TEXT NOT NULL,
440
- profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
441
- dimension TEXT NOT NULL, -- trust / closeness / respect
442
- delta REAL NOT NULL,
443
- reason TEXT NOT NULL,
444
- evidence_trace_id TEXT,
445
- -- 这次事件是不是"修复"。修复与普通正向事件分开记,
446
- -- 因为"关系回暖"和"把话说开了"是两件事:
447
- -- 前者可以靠时间,后者必须有一句话。
448
- is_repair INTEGER NOT NULL DEFAULT 0,
449
- occurred_at TEXT NOT NULL
450
- );
451
-
452
- CREATE INDEX IF NOT EXISTS idx_relationship_events
453
- ON relationship_events(profile_id, dimension, occurred_at);
454
-
455
- -- 未了结的事。**这张表是"原谅不等于遗忘"的落地。**
456
- --
457
- -- 一条 grievance 开出来之后,只有 `repair()` 能关掉它。
458
- -- 情绪可以自己消退到 0,但这条记录一直在——
459
- -- 那就是「不生气了,但还等着一个解释」。
460
- CREATE TABLE IF NOT EXISTS grievances (
461
- grievance_id TEXT PRIMARY KEY,
462
- scope_id TEXT NOT NULL,
463
- profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
464
- -- 什么事。一句话,给人看的。
465
- summary TEXT NOT NULL,
466
- severity REAL NOT NULL DEFAULT 1.0, -- 1 小事 / 3 明显冲突 / 5 严重
467
- status TEXT NOT NULL DEFAULT 'open', -- open / repaired / forgiven
468
- evidence_trace_id TEXT,
469
- opened_at TEXT NOT NULL,
470
- closed_at TEXT
471
- );
472
-
473
- CREATE INDEX IF NOT EXISTS idx_grievances_open
474
- ON grievances(profile_id, status, opened_at);
475
-
476
- -- v0.52 性能索引:会话变长后这两条查询是每轮必走的。
477
- CREATE INDEX IF NOT EXISTS idx_traces_scope_time ON traces(scope_id, recorded_at);
478
- CREATE INDEX IF NOT EXISTS idx_tags_lookup ON person_tags(profile_id, kind, value, status);
479
- CREATE INDEX IF NOT EXISTS idx_states_scope ON states(scope_id, status);
480
- CREATE INDEX IF NOT EXISTS idx_lifecycle_anchor ON lifecycle(scope_id, ref_type, anchor);
1
+ -- Xujie-Memory 参考存储模式(阶段 1)
2
+ -- 对应设计文档 17.1。此处只实现内核所需表;clarifications / outbox / recall_log
3
+ -- 属后续阶段,留空不影响内核语义。
4
+
5
+ PRAGMA journal_mode = WAL;
6
+ PRAGMA foreign_keys = ON;
7
+ PRAGMA synchronous = FULL;
8
+
9
+ -- ---------------------------------------------------------------- 作用域
10
+ CREATE TABLE IF NOT EXISTS scopes (
11
+ scope_id TEXT PRIMARY KEY,
12
+ tenant_id TEXT NOT NULL,
13
+ agent_id TEXT NOT NULL,
14
+ world_id TEXT NOT NULL,
15
+ created_at TEXT NOT NULL,
16
+ UNIQUE (tenant_id, agent_id, world_id)
17
+ );
18
+
19
+ -- ---------------------------------------------------------------- 痕迹(不可变)
20
+ CREATE TABLE IF NOT EXISTS traces (
21
+ trace_id TEXT PRIMARY KEY,
22
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
23
+ source_ref TEXT NOT NULL,
24
+ actor_id TEXT NOT NULL,
25
+ source_kind TEXT NOT NULL,
26
+ content TEXT NOT NULL,
27
+ content_hash TEXT NOT NULL,
28
+ recorded_at TEXT NOT NULL,
29
+ source_time TEXT,
30
+ source_time_precision TEXT,
31
+ context_ref TEXT,
32
+ erased_at TEXT,
33
+ UNIQUE (scope_id, source_ref)
34
+ );
35
+
36
+ CREATE INDEX IF NOT EXISTS idx_traces_actor ON traces(scope_id, actor_id);
37
+
38
+ -- ---------------------------------------------------------------- 编码与解释
39
+ CREATE TABLE IF NOT EXISTS encodings (
40
+ encoding_id TEXT PRIMARY KEY,
41
+ trace_id TEXT NOT NULL REFERENCES traces(trace_id),
42
+ encoder_version TEXT NOT NULL,
43
+ prompt_version TEXT NOT NULL,
44
+ schema_version TEXT NOT NULL,
45
+ payload_json TEXT NOT NULL,
46
+ generated_at TEXT NOT NULL
47
+ );
48
+
49
+ CREATE TABLE IF NOT EXISTS interpretations (
50
+ interpretation_id TEXT PRIMARY KEY,
51
+ encoding_id TEXT NOT NULL REFERENCES encodings(encoding_id),
52
+ trace_id TEXT NOT NULL REFERENCES traces(trace_id),
53
+ relation TEXT NOT NULL,
54
+ modality TEXT NOT NULL,
55
+ polarity TEXT NOT NULL,
56
+ slots_json TEXT NOT NULL,
57
+ semantic_op TEXT, -- 语义操作:incur/repay/waive/set_total/correct/retract/noop
58
+ -- 注意与 events.operation 区分:后者是 append/update/retract,
59
+ -- 描述"事件怎么进流",这里描述"这句话是什么意思"。
60
+ -- 可空:v0.5 之前只写 slots.intent,读时回落。
61
+ signals_json TEXT,
62
+ created_at TEXT NOT NULL
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_interp_trace ON interpretations(trace_id);
66
+
67
+ -- ---------------------------------------------------------------- 闸门判定(可审计)
68
+ CREATE TABLE IF NOT EXISTS gate_decisions (
69
+ decision_id TEXT PRIMARY KEY,
70
+ interpretation_id TEXT NOT NULL REFERENCES interpretations(interpretation_id),
71
+ spec_version TEXT NOT NULL,
72
+ outcome TEXT NOT NULL, -- commit / pending / record_only / reject
73
+ reasons_json TEXT NOT NULL,
74
+ admission_mode TEXT NOT NULL, -- shadow / assisted / auto
75
+ decided_at TEXT NOT NULL
76
+ );
77
+
78
+ -- ---------------------------------------------------------------- 事件(唯一事实源)
79
+ CREATE TABLE IF NOT EXISTS events (
80
+ event_id TEXT PRIMARY KEY,
81
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
82
+ state_key TEXT NOT NULL,
83
+ idempotency_key TEXT NOT NULL,
84
+ payload_hash TEXT NOT NULL,
85
+ operation_group_id TEXT,
86
+ operation TEXT NOT NULL, -- append / update / retract / conflict
87
+ effect_kind TEXT NOT NULL, -- set / delta / invalidate
88
+ payload_json TEXT NOT NULL,
89
+ gate_decision_id TEXT REFERENCES gate_decisions(decision_id),
90
+ actor_id TEXT NOT NULL,
91
+ effective_at TEXT NOT NULL,
92
+ recorded_at TEXT NOT NULL,
93
+ sequence_no INTEGER NOT NULL,
94
+ spec_version TEXT NOT NULL,
95
+ target_ref TEXT,
96
+ superseded_by TEXT,
97
+ void INTEGER NOT NULL DEFAULT 0, -- 1 = 已被纠正/撤销,不参与求值
98
+ UNIQUE (scope_id, idempotency_key),
99
+ UNIQUE (state_key, sequence_no)
100
+ );
101
+
102
+ CREATE INDEX IF NOT EXISTS idx_events_stream ON events(state_key, sequence_no);
103
+ CREATE INDEX IF NOT EXISTS idx_events_effective ON events(state_key, effective_at, sequence_no);
104
+ CREATE INDEX IF NOT EXISTS idx_events_group ON events(operation_group_id);
105
+
106
+ CREATE TABLE IF NOT EXISTS event_evidence (
107
+ event_id TEXT NOT NULL REFERENCES events(event_id),
108
+ trace_id TEXT NOT NULL REFERENCES traces(trace_id),
109
+ span_start INTEGER,
110
+ span_end INTEGER,
111
+ PRIMARY KEY (event_id, trace_id)
112
+ );
113
+
114
+ CREATE INDEX IF NOT EXISTS idx_evidence_trace ON event_evidence(trace_id);
115
+
116
+ -- ---------------------------------------------------------------- 状态投影
117
+ CREATE TABLE IF NOT EXISTS states (
118
+ state_key TEXT PRIMARY KEY,
119
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
120
+ value_json TEXT,
121
+ status TEXT NOT NULL, -- known/unknown/disputed/stale/retracted
122
+ valid_from TEXT,
123
+ valid_to TEXT,
124
+ last_known_json TEXT,
125
+ conflict_reason TEXT,
126
+ version INTEGER NOT NULL DEFAULT 0,
127
+ watermark INTEGER NOT NULL DEFAULT 0,
128
+ evaluated_as_of TEXT,
129
+ spec_version TEXT NOT NULL
130
+ );
131
+
132
+ -- ---------------------------------------------------------------- 依赖登记
133
+ CREATE TABLE IF NOT EXISTS state_deps (
134
+ derived_key TEXT NOT NULL,
135
+ input_key TEXT NOT NULL,
136
+ rule_version TEXT NOT NULL,
137
+ PRIMARY KEY (derived_key, input_key, rule_version)
138
+ );
139
+
140
+ -- ---------------------------------------------------------------- 投影检查点(可选加速)
141
+ CREATE TABLE IF NOT EXISTS state_snapshots (
142
+ state_key TEXT NOT NULL,
143
+ watermark INTEGER NOT NULL,
144
+ value_json TEXT,
145
+ status TEXT NOT NULL,
146
+ version INTEGER NOT NULL,
147
+ created_at TEXT NOT NULL,
148
+ PRIMARY KEY (state_key, watermark)
149
+ );
150
+
151
+ -- ---------------------------------------------------------------- 澄清队列(阶段 3)
152
+ CREATE TABLE IF NOT EXISTS clarifications (
153
+ request_id TEXT PRIMARY KEY,
154
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
155
+ interpretation_ids_json TEXT NOT NULL,
156
+ kind TEXT NOT NULL, -- missing_slot / ambiguous / unconfirmed / conflict
157
+ detail_json TEXT,
158
+ status TEXT NOT NULL, -- open / answered / expired / dismissed
159
+ created_at TEXT NOT NULL,
160
+ expires_at TEXT,
161
+ -- 这条问题**已经念给模型看过了**的时间。
162
+ --
163
+ -- 为什么必须有这一列:宿主形态下每轮都会重新拼一次材料,
164
+ -- 没有这个标记,「还没确定的事」就会每轮重复出现——
165
+ -- 用户被同一句话连问五遍,那不是澄清,是审问。
166
+ --
167
+ -- 语义是"已提过",不是"已回答"。提过就不再主动提第二次,
168
+ -- 用户自己说清楚了(或超时)才算结束。
169
+ asked_at TEXT
170
+ );
171
+
172
+ CREATE INDEX IF NOT EXISTS idx_clarif_scope ON clarifications(scope_id, status);
173
+
174
+ -- ---------------------------------------------------------------- 只记录未提交(阶段 3 补)
175
+ -- XM-10:record_only 的内容必须有独立落点,否则"记住他说过"只做到一半。
176
+ -- status=open 的内容可在后续被 promote 成事件,这条路径现在预留、未实现提升逻辑。
177
+ CREATE TABLE IF NOT EXISTS observations (
178
+ observation_id TEXT PRIMARY KEY,
179
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
180
+ trace_id TEXT NOT NULL REFERENCES traces(trace_id),
181
+ interpretation_id TEXT NOT NULL,
182
+ relation TEXT NOT NULL,
183
+ modality TEXT NOT NULL,
184
+ polarity TEXT NOT NULL,
185
+ slots_json TEXT NOT NULL,
186
+ excerpt TEXT NOT NULL,
187
+ reason TEXT NOT NULL,
188
+ status TEXT NOT NULL DEFAULT 'open', -- open / promoted / archived
189
+ promoted_event_id TEXT,
190
+ created_at TEXT NOT NULL
191
+ );
192
+
193
+ CREATE INDEX IF NOT EXISTS idx_obs_scope ON observations(scope_id, relation, status);
194
+ CREATE INDEX IF NOT EXISTS idx_obs_trace ON observations(trace_id);
195
+
196
+ -- ---------------------------------------------------------------- 全文检索(阶段 4)
197
+ -- 独立 FTS 表而非外部内容表:traces 的主键是 TEXT,rowid 对齐不可靠。
198
+ -- trigram 分词对中文有效(3 字符滑窗),但要求查询 >= 3 字符,短查询在代码里降级为 LIKE。
199
+ CREATE VIRTUAL TABLE IF NOT EXISTS traces_fts USING fts5(
200
+ trace_id UNINDEXED,
201
+ content,
202
+ tokenize = 'trigram'
203
+ );
204
+
205
+ -- ---------------------------------------------------------------- 生命周期(阶段 5)
206
+ -- 5.9:生命周期元信息与不可变痕迹分离。痕迹不改,权重和层级可改。
207
+ CREATE TABLE IF NOT EXISTS lifecycle (
208
+ ref_type TEXT NOT NULL, -- trace
209
+ ref_id TEXT NOT NULL,
210
+ scope_id TEXT NOT NULL,
211
+ base REAL NOT NULL DEFAULT 1.0,
212
+ use_count INTEGER NOT NULL DEFAULT 0,
213
+ last_used_at TEXT,
214
+ anchor INTEGER NOT NULL DEFAULT 0,
215
+ level TEXT NOT NULL DEFAULT 'hot', -- hot / cold / archived
216
+ created_at TEXT NOT NULL,
217
+ PRIMARY KEY (ref_type, ref_id)
218
+ );
219
+
220
+ CREATE INDEX IF NOT EXISTS idx_lifecycle_scope ON lifecycle(scope_id, level, anchor);
221
+
222
+ -- ---------------------------------------------------------------- 权重事件
223
+ -- v0.40.0:「权重曲线」要能画出"这条记忆的权重是怎么变的",
224
+ -- 光有 lifecycle 表的 use_count 是不够的——那是一个**计数**,
225
+ -- 不是**历史**。计数回答不了"三个月前它有多重要"。
226
+ --
227
+ -- 为什么是事件表而不是加一列「priority_history」:
228
+ -- 本项目的铁律是「事件是唯一事实源,投影可以重算」。
229
+ -- priority 是派生值(base × 时间衰减 × 使用增益),
230
+ -- 把它存成第二份就等于有两个真相源(CONTRIBUTING 红线四)。
231
+ --
232
+ -- 所以这里只记**事实**:这条记忆在某个时刻被用了 / 被搜到了 / 被设成锚点了。
233
+ -- 曲线由这些事件重放算出来——和 events → states 是同一种关系。
234
+ --
235
+ -- 为什么不用 JSON 列:重放要按时间排序、要做区间聚合,
236
+ -- 独立成表能走索引;塞进 lifecycle 的一列就得整行反序列化。
237
+ CREATE TABLE IF NOT EXISTS weight_events (
238
+ event_id TEXT PRIMARY KEY,
239
+ ref_type TEXT NOT NULL,
240
+ ref_id TEXT NOT NULL,
241
+ scope_id TEXT NOT NULL,
242
+ kind TEXT NOT NULL, -- use(真被用)/ search(只被搜到)/ anchor(设锚)/ unanchor(取消锚)
243
+ occurred_at TEXT NOT NULL,
244
+ detail_json TEXT
245
+ );
246
+
247
+ CREATE INDEX IF NOT EXISTS idx_wgev_ref ON weight_events(ref_type, ref_id, occurred_at);
248
+ CREATE INDEX IF NOT EXISTS idx_wgev_scope ON weight_events(scope_id, occurred_at);
249
+
250
+ -- 摘要产物。合并生成新行,旧行标 merged,不覆盖(12.2)。
251
+ CREATE TABLE IF NOT EXISTS summaries (
252
+ summary_id TEXT PRIMARY KEY,
253
+ scope_id TEXT NOT NULL,
254
+ input_refs_json TEXT NOT NULL,
255
+ text TEXT NOT NULL,
256
+ generator_version TEXT NOT NULL,
257
+ coverage_json TEXT,
258
+ omissions_json TEXT,
259
+ validation_status TEXT NOT NULL DEFAULT 'unchecked', -- unchecked / ok / failed
260
+ lifecycle_state TEXT NOT NULL DEFAULT 'active', -- active / merged / archived
261
+ created_at TEXT NOT NULL
262
+ );
263
+
264
+ CREATE INDEX IF NOT EXISTS idx_summaries_scope ON summaries(scope_id, lifecycle_state);
265
+
266
+ -- ---------------------------------------------------------------- 承诺(投影)
267
+ -- 「下个月我还你两袋」不改变余额,但它留下了一个**可以被追讨的对象**。
268
+ --
269
+ -- 这张表是投影,不是事实源。事实源仍然是 events:
270
+ -- 承诺是 effect_kind='none' 的事件,兑现是一条 delta<0 的事件。
271
+ -- 所以这张表可以随时用 `Engine.rebuild_commitments()` 从事件流重建,
272
+ -- 重建结果与增量维护**逐字相同**(有测试守着)。
273
+ --
274
+ -- 为什么不做成"事件表里的一列":承诺有生命周期
275
+ -- (open → kept / broken / cancelled),而事件是不可变的。
276
+ -- 把可变状态放进不可变表,就再也分不清"记录"和"现状"了。
277
+ CREATE TABLE IF NOT EXISTS commitments (
278
+ commitment_id TEXT PRIMARY KEY,
279
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
280
+ state_key TEXT NOT NULL,
281
+ event_id TEXT NOT NULL REFERENCES events(event_id),
282
+ actor_id TEXT NOT NULL,
283
+ quantity REAL NOT NULL,
284
+ remaining REAL NOT NULL,
285
+ unit TEXT, -- 展示用(「2 碗」);不参与计算
286
+ due_hint TEXT,
287
+ due_at TEXT,
288
+ status TEXT NOT NULL, -- open / kept / partial / cancelled
289
+ settled_by TEXT, -- 结清它的那条事件
290
+ created_at TEXT NOT NULL,
291
+ updated_at TEXT NOT NULL
292
+ );
293
+
294
+ CREATE INDEX IF NOT EXISTS idx_commitments_open
295
+ ON commitments(scope_id, state_key, status, created_at);
296
+
297
+
298
+ -- ---------------------------------------------------------------- 准入与熔断
299
+ -- 每个 `(关系, 句式)` 一条。**它是唯一会"自己变"的配置**——
300
+ -- 因为准入等级本来就该随证据动:稳的格子放开,被纠正的格子收回。
301
+ --
302
+ -- 句式(pattern)不是自然语言模板,是**风险分类**:
303
+ -- incur:plain 「我欠你三袋」——有数量、无歧义、无第三方
304
+ -- incur:third_party 「替老张赊的」——施事不明,永远 assisted
305
+ -- correct:revision 「不对,是五十」——改历史,永远 assisted
306
+ --
307
+ -- 为什么不存"每句话的指纹":那会过拟合到语料。分类要粗到能解释、
308
+ -- 细到能区分风险。
309
+ CREATE TABLE IF NOT EXISTS admission_state (
310
+ relation TEXT NOT NULL,
311
+ pattern TEXT NOT NULL,
312
+ level TEXT NOT NULL, -- auto / assisted / shadow
313
+ failures INTEGER NOT NULL DEFAULT 0, -- 连续被纠正次数
314
+ successes INTEGER NOT NULL DEFAULT 0, -- 熔断后连续确认次数
315
+ opened_at TEXT, -- 熔断时刻(NULL = 未熔断)
316
+ updated_at TEXT NOT NULL,
317
+ PRIMARY KEY (relation, pattern)
318
+ );
319
+
320
+ -- 熔断流水,供审计:谁在什么时候把这个格子关掉了
321
+ CREATE TABLE IF NOT EXISTS breaker_log (
322
+ log_id TEXT PRIMARY KEY,
323
+ scope_id TEXT NOT NULL,
324
+ relation TEXT NOT NULL,
325
+ pattern TEXT NOT NULL,
326
+ action TEXT NOT NULL, -- open / close / failure
327
+ reason TEXT NOT NULL,
328
+ at TEXT NOT NULL
329
+ );
330
+
331
+ -- ---------------------------------------------------------------- 人物画像
332
+ -- 画像回答的是「这个人是谁」,不是「账上还有多少」。
333
+ --
334
+ -- 三条设计约束(对应方案第四章):
335
+ -- 1. 标签是**印象**,不是事实。事实层是 events,这里只放"我对他的看法"。
336
+ -- 2. 来源必须分开记:confirmed(用户明说)/ observed(系统看到)/ inferred(模型猜)。
337
+ -- 推断**永不自动升级为事实**——它可以影响语气,不能授权。
338
+ -- 3. 账号不等于人物。可疑期间新标签先暂存(staged),不污染主画像。
339
+ CREATE TABLE IF NOT EXISTS person_profiles (
340
+ profile_id TEXT PRIMARY KEY,
341
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
342
+ account_id TEXT NOT NULL,
343
+ display_name TEXT,
344
+ -- active 正常 / suspect 有换人线索(新标签进暂存区)
345
+ status TEXT NOT NULL DEFAULT 'active',
346
+ created_at TEXT NOT NULL,
347
+ updated_at TEXT NOT NULL,
348
+ UNIQUE (scope_id, account_id)
349
+ );
350
+
351
+ CREATE INDEX IF NOT EXISTS idx_profiles_scope
352
+ ON person_profiles(scope_id, status);
353
+
354
+ CREATE TABLE IF NOT EXISTS person_tags (
355
+ tag_id TEXT PRIMARY KEY,
356
+ profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
357
+ scope_id TEXT NOT NULL,
358
+ -- 标签类别:address(怎么称呼)/ preference(喜好)/ habit(习惯)
359
+ -- style(表达方式)/ topic(常聊的话题)/ relation(关系)
360
+ kind TEXT NOT NULL,
361
+ value TEXT NOT NULL,
362
+ -- 来源三分,决定这条标签能不能当事实用
363
+ source TEXT NOT NULL, -- confirmed / observed / inferred
364
+ -- 可信度与"多常用"是两件事(3.3):反复提起的谎话很好想起,但仍然不可信。
365
+ confidence REAL NOT NULL DEFAULT 0.5,
366
+ seen_count INTEGER NOT NULL DEFAULT 1,
367
+ -- active 生效 / staged 暂存(画像可疑时)/ corrected 被纠正 / expired 失效
368
+ status TEXT NOT NULL DEFAULT 'active',
369
+ -- 出处。标签必须能追到一句原话,否则它只是模型的想象。
370
+ evidence_trace_id TEXT,
371
+ -- 被这条标签取代的上一条(纠正时留痕,不覆盖)
372
+ supersedes TEXT,
373
+ created_at TEXT NOT NULL,
374
+ updated_at TEXT NOT NULL
375
+ );
376
+
377
+ CREATE INDEX IF NOT EXISTS idx_tags_profile
378
+ ON person_tags(profile_id, status, kind);
379
+ CREATE INDEX IF NOT EXISTS idx_tags_evidence
380
+ ON person_tags(evidence_trace_id);
381
+
382
+ -- 认知异常:轻量模型只出**线索**,不下身份结论(4.5)。
383
+ CREATE TABLE IF NOT EXISTS profile_alerts (
384
+ alert_id TEXT PRIMARY KEY,
385
+ scope_id TEXT NOT NULL,
386
+ profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
387
+ kind TEXT NOT NULL, -- style_shift / address_shift / explicit_claim
388
+ signals_json TEXT NOT NULL, -- 命中的具体线索,给人看
389
+ status TEXT NOT NULL DEFAULT 'open', -- open / dismissed / escalated
390
+ evidence_trace_id TEXT,
391
+ created_at TEXT NOT NULL,
392
+ resolved_at TEXT
393
+ );
394
+
395
+ CREATE INDEX IF NOT EXISTS idx_alerts_open
396
+ ON profile_alerts(scope_id, status);
397
+
398
+ -- 判断记录:主 AI 对异常给出过什么结论(保留历史,便于事后复盘)。
399
+ CREATE TABLE IF NOT EXISTS profile_evaluations (
400
+ evaluation_id TEXT PRIMARY KEY,
401
+ alert_id TEXT NOT NULL REFERENCES profile_alerts(alert_id),
402
+ verdict TEXT NOT NULL, -- same_person / different_person / unknown
403
+ reasons_json TEXT NOT NULL,
404
+ actor_id TEXT NOT NULL,
405
+ created_at TEXT NOT NULL
406
+ );
407
+
408
+ -- ---------------------------------------------------------------- 情绪与关系
409
+ -- 方案第六章。两条线必须分开:
410
+ --
411
+ -- 短期情绪 anger / joy / sadness / anxiety / grievance
412
+ -- 会随时间自然消退。**这是"消气"。**
413
+ -- 长期关系 trust / closeness / respect
414
+ -- 不会因为时间过去自己变好。**这是"原谅"。**
415
+ --
416
+ -- 6.2 那句话是这一层的全部难点:
417
+ -- 消气不等于原谅,原谅不等于遗忘。
418
+ -- 所以两张表、两套规则:情绪有衰减,关系没有。
419
+ CREATE TABLE IF NOT EXISTS emotion_events (
420
+ event_id TEXT PRIMARY KEY,
421
+ scope_id TEXT NOT NULL,
422
+ profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
423
+ emotion TEXT NOT NULL, -- anger / joy / sadness / anxiety / grievance
424
+ -- 这次事件把情绪推了多少。正数累积,负数安抚。
425
+ delta REAL NOT NULL,
426
+ -- 触发原因。不写原因的情绪,事后无法解释"你当时为什么生气"。
427
+ reason TEXT NOT NULL,
428
+ -- 指向具体事件或痕迹,能追到是哪句话惹的
429
+ evidence_trace_id TEXT,
430
+ occurred_at TEXT NOT NULL
431
+ );
432
+
433
+ CREATE INDEX IF NOT EXISTS idx_emotion_events
434
+ ON emotion_events(profile_id, emotion, occurred_at);
435
+
436
+ -- 关系事件。**没有衰减**——信任掉下去不会自己长回来。
437
+ CREATE TABLE IF NOT EXISTS relationship_events (
438
+ event_id TEXT PRIMARY KEY,
439
+ scope_id TEXT NOT NULL,
440
+ profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
441
+ dimension TEXT NOT NULL, -- trust / closeness / respect
442
+ delta REAL NOT NULL,
443
+ reason TEXT NOT NULL,
444
+ evidence_trace_id TEXT,
445
+ -- 这次事件是不是"修复"。修复与普通正向事件分开记,
446
+ -- 因为"关系回暖"和"把话说开了"是两件事:
447
+ -- 前者可以靠时间,后者必须有一句话。
448
+ is_repair INTEGER NOT NULL DEFAULT 0,
449
+ occurred_at TEXT NOT NULL
450
+ );
451
+
452
+ CREATE INDEX IF NOT EXISTS idx_relationship_events
453
+ ON relationship_events(profile_id, dimension, occurred_at);
454
+
455
+ -- 未了结的事。**这张表是"原谅不等于遗忘"的落地。**
456
+ --
457
+ -- 一条 grievance 开出来之后,只有 `repair()` 能关掉它。
458
+ -- 情绪可以自己消退到 0,但这条记录一直在——
459
+ -- 那就是「不生气了,但还等着一个解释」。
460
+ CREATE TABLE IF NOT EXISTS grievances (
461
+ grievance_id TEXT PRIMARY KEY,
462
+ scope_id TEXT NOT NULL,
463
+ profile_id TEXT NOT NULL REFERENCES person_profiles(profile_id),
464
+ -- 什么事。一句话,给人看的。
465
+ summary TEXT NOT NULL,
466
+ severity REAL NOT NULL DEFAULT 1.0, -- 1 小事 / 3 明显冲突 / 5 严重
467
+ status TEXT NOT NULL DEFAULT 'open', -- open / repaired / forgiven
468
+ evidence_trace_id TEXT,
469
+ opened_at TEXT NOT NULL,
470
+ closed_at TEXT
471
+ );
472
+
473
+ CREATE INDEX IF NOT EXISTS idx_grievances_open
474
+ ON grievances(profile_id, status, opened_at);
475
+
476
+ -- v0.52 性能索引:会话变长后这两条查询是每轮必走的。
477
+ CREATE INDEX IF NOT EXISTS idx_traces_scope_time ON traces(scope_id, recorded_at);
478
+ CREATE INDEX IF NOT EXISTS idx_tags_lookup ON person_tags(profile_id, kind, value, status);
479
+ CREATE INDEX IF NOT EXISTS idx_states_scope ON states(scope_id, status);
480
+ -- 0.7 edits replace immutable traces. No plaintext copy of old content here.
481
+ CREATE TABLE IF NOT EXISTS memory_edits (
482
+ edit_id TEXT PRIMARY KEY,
483
+ scope_id TEXT NOT NULL REFERENCES scopes(scope_id),
484
+ old_trace_id TEXT NOT NULL REFERENCES traces(trace_id),
485
+ new_trace_id TEXT NOT NULL REFERENCES traces(trace_id),
486
+ edited_at TEXT NOT NULL
487
+ );
488
+ CREATE TABLE IF NOT EXISTS memory_maintenance (
489
+ name TEXT PRIMARY KEY,
490
+ completed_at TEXT NOT NULL
491
+ );
492
+ CREATE INDEX IF NOT EXISTS idx_lifecycle_anchor ON lifecycle(scope_id, ref_type, anchor);