feat: 段页式记忆流转、coldcontext 重命名与安全治理

记忆层:
- MemoryBlock 承载 FrameTS/PageSeq/PageKind/BlockRole/CallID
- coldcontext 归档与回填按完整页传递,工具调用参数和配对键不丢失
- 召回先估算总 token,超预算时按全局页相似度标记 inactive
- inactive 仅是 context 渲染期状态,不落盘、不进入 coldcontext,Prune 后重置
- Peek/确认/Remove 两阶段召回,失败保留冷上下文副本
- stablemem 记录 last_recalled_at,删块连带清理边

命名:
- internal/memory/document → internal/memory/coldcontext
- Doc/DocEvent/DocHit → ColdDoc/ColdEvent/ColdHit
- ContextToColdContext/RecentColdContexts/FindColdContexts
- 保留既有 JSON 标签、doc_ 文件前缀与 doc_count 状态键,兼容存量数据

安全治理:
- 工具策略门禁、安全配置、事件总线发布者身份与入站脱敏
- 声明式 SecurityAPI 与 QQ owner/guest profile
- 日志保留策略与相关 e2e/回归判据

验证:go build ./...、go vet ./...、go test ./...(62 包);
生产副本 e2e:7013 块迁移后 ID/向量/指纹 0 变化、0 缺失。
This commit is contained in:
JianFeeeee
2026-10-29 23:53:48 +08:00
parent a3e50b395f
commit b850feaef2
113 changed files with 6512 additions and 790 deletions

View File

@ -6,6 +6,8 @@ import (
"log"
"math"
"sort"
"strings"
"time"
)
// 块节点的向量召回。
@ -174,9 +176,51 @@ func (g *GraphDB) RecallBlocks(q BlockRecallQuery) ([]BlockHit, error) {
if len(hits) > topK {
hits = hits[:topK]
}
// ★ 记录召回时间(P6,2026-10-29)。
//
// 为什么必须记:回收判定看的是"这块记忆还有没有用",而"被召回"
// 是有用的**唯一实证信号** —— 创建时间只说明它什么时候写进来。
// 不记的话,所有块看起来都"很久没用过",回收就等于按入库时间瞎删。
//
// 为什么在解锁后做:本函数持 RLock,写库会撞锁(SQLite 单写)。
// 所以先把命中的 ID 复制出来,defer 到 RUnlock 之后再批量更新。
// 失败只记日志:召回本身已经成功,不能因为"记不上时间"就报错。
hitIDs := make([]string, len(hits))
for i := range hits {
hitIDs[i] = hits[i].Block.ID
}
if len(hitIDs) > 0 {
defer func() { g.markRecalled(hitIDs) }()
}
return hits, nil
}
// markRecalled 批量刷新 last_recalled_at。
//
// 单条 UPDATE ... IN (...) 而不是逐条:召回 topK=10~50,
// 逐条就是 10~50 次独立写事务,在 SQLite 上会明显拖慢每轮对话。
//
// 失败只记日志:这是"统计信号",不是正确性数据 ——
// 记不上时间最多让回收判据偏保守(少删),绝不会删错。
func (g *GraphDB) markRecalled(ids []string) {
if len(ids) == 0 {
return
}
ph := make([]string, len(ids))
args := make([]interface{}, 0, len(ids)+1)
args = append(args, time.Now())
for i, id := range ids {
ph[i] = "?"
args = append(args, id)
}
if _, err := g.db.Exec(
`UPDATE memory_blocks SET last_recalled_at = ?
WHERE id IN (`+strings.Join(ph, ",")+`)`, args...); err != nil {
log.Printf("[graph] mark recalled failed (%d blocks): %v", len(ids), err)
}
}
// RecallBlocksWithArbitration 与 RecallBlocks 相同,但额外返回仲裁详情
// (被取代的块、结论状态)。
//