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

@ -42,7 +42,7 @@ import (
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
luaVM "github.com/JianFeeeee/HologramAgent/internal/lua"
"github.com/JianFeeeee/HologramAgent/internal/memory"
"github.com/JianFeeeee/HologramAgent/internal/memory/document"
"github.com/JianFeeeee/HologramAgent/internal/memory/coldcontext"
"github.com/JianFeeeee/HologramAgent/pkg/types"
)
@ -134,11 +134,11 @@ func livePNG(t *testing.T, w, h int, colors [][3]byte) []byte {
// liveEnv 是一套完整但完全独立的 agent 运行环境。
type liveEnv struct {
agent *Agent
io *agentIO.IOManager
docStore *document.Store
graph *memory.GraphDB
dir string
agent *Agent
io *agentIO.IOManager
coldcontextStore *coldcontext.Store
graph *memory.GraphDB
dir string
}
// newLiveEnv 构造真 Agent:真 provider、真图库、真文档库。
@ -174,11 +174,11 @@ func newLiveEnv(t *testing.T, c liveCfg) *liveEnv {
}
t.Cleanup(func() { graph.Close() })
docStore := document.NewStore(filepath.Join(dir, "docs"), memory.TokenizeWords)
if err := docStore.Start(); err != nil {
coldcontextStore := coldcontext.NewStore(filepath.Join(dir, "docs"), memory.TokenizeWords)
if err := coldcontextStore.Start(); err != nil {
t.Fatalf("doc store: %v", err)
}
t.Cleanup(docStore.Stop)
t.Cleanup(coldcontextStore.Stop)
io := agentIO.NewIOManager()
@ -189,7 +189,7 @@ func newLiveEnv(t *testing.T, c liveCfg) *liveEnv {
ProviderManager: pm,
IO: io,
Memory: graph,
DocStore: docStore,
DocStore: coldcontextStore,
StageHost: NewStageHost(),
MaxContextSize: 3, // 故意压低:第二轮就能触发 Prune 归档
InputProcessing: types.InputProcessingConfig{},
@ -206,7 +206,7 @@ func newLiveEnv(t *testing.T, c liveCfg) *liveEnv {
}
}()
return &liveEnv{agent: a, io: io, docStore: docStore, graph: graph, dir: dir}
return &liveEnv{agent: a, io: io, coldcontextStore: coldcontextStore, graph: graph, dir: dir}
}
// TestMediaLive_AutoTriggerChain 全自动触发链:只注入事件,不手工调任何一步。
@ -311,14 +311,14 @@ func TestMediaLive_AutoTriggerChain(t *testing.T) {
Response: "好的。",
})
}
archived := a.context.Prune("当前输入", a.maxContextSize-1, env.docStore)
archived := a.context.Prune("当前输入", a.maxContextSize-1, env.coldcontextStore)
t.Logf("Prune 归档 %d 条事件", archived)
if archived == 0 {
t.Fatal("Prune 未归档任何事件,无法验证引用转移")
}
docRefsFound := ""
for _, d := range env.docStore.RecentDocs(20) {
for _, d := range env.coldcontextStore.RecentColdContexts(20) {
for _, b := range d.Blocks {
if b.ID == blockID {
docRefsFound = d.ID
@ -340,10 +340,10 @@ func TestMediaLive_AutoTriggerChain(t *testing.T) {
// ── 阶段 5:archiveColdDocs 自动把块连到 L3 文档节点 ──
//
// FindColdDocs(72h, 2) 要求文档足够"冷",测试里新建的文档不满足,
// FindColdContexts(72h, 2) 要求文档足够"冷",测试里新建的文档不满足,
// 因此把 LastAccess 往前推——这是为了触发生产代码路径,
// 而不是替代它(commitTriplesWithMedia/linkBlocksToDocument 全由它自己调)。
for _, d := range env.docStore.RecentDocs(20) {
for _, d := range env.coldcontextStore.RecentColdContexts(20) {
if d.ID == docRefsFound {
d.LastAccess = time.Now().Add(-100 * time.Hour)
d.AccessCount = 0