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

@ -8,7 +8,7 @@ import (
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
"github.com/JianFeeeee/HologramAgent/internal/memory"
"github.com/JianFeeeee/HologramAgent/internal/memory/document"
"github.com/JianFeeeee/HologramAgent/internal/memory/coldcontext"
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
)
@ -296,7 +296,7 @@ func TestDocCommit_StoresBlocks(t *testing.T) {
// coldcontext_commit with media stores blocks directly in cold context,
// 并随 doc 一起持久化(块自带字节,不靠外部引用保活)。
dir := t.TempDir()
ds := document.NewStore(filepath.Join(dir, "docs"), memory.TokenizeWords)
ds := coldcontext.NewStore(filepath.Join(dir, "docs"), memory.TokenizeWords)
if err := ds.Start(); err != nil {
t.Fatal(err)
}
@ -305,7 +305,7 @@ func TestDocCommit_StoresBlocks(t *testing.T) {
b1 := mediaBlock("image/png", []byte("doc-one"))
b2 := mediaBlock("image/png", []byte("doc-two"))
doc := &document.Doc{ID: "doc_x", Summary: "s", Content: "c", Blocks: []memory.MemoryBlock{b1, b2}}
doc := &coldcontext.ColdDoc{ID: "doc_x", Summary: "s", Content: "c", Blocks: []memory.MemoryBlock{b1, b2}}
if err := ds.Insert(doc); err != nil {
t.Fatal(err)
}
@ -330,7 +330,7 @@ func TestBlockLabelsForDoc(t *testing.T) {
b := mediaBlock("image/png", []byte("ctx-bytes"))
t.Run("从文档持有的一等块渲染", func(t *testing.T) {
got := a.blockLabelsForDoc(&document.Doc{ID: "doc_1", Blocks: []memory.MemoryBlock{b}})
got := a.blockLabelsForDoc(&coldcontext.ColdDoc{ID: "doc_1", Blocks: []memory.MemoryBlock{b}})
if !strings.Contains(got, shortID(b.ID)) {
t.Errorf("标签应含短块 ID: %q", got)
}
@ -340,7 +340,7 @@ func TestBlockLabelsForDoc(t *testing.T) {
})
t.Run("无块时为空", func(t *testing.T) {
if got := a.blockLabelsForDoc(&document.Doc{ID: "doc_x", Content: "普通正文"}); got != "" {
if got := a.blockLabelsForDoc(&coldcontext.ColdDoc{ID: "doc_x", Content: "普通正文"}); got != "" {
t.Errorf("应返回空串,实际 %q", got)
}
})
@ -375,7 +375,7 @@ func newToolTestAgent(t *testing.T) *Agent {
}
t.Cleanup(func() { g.Close() })
ds := document.NewStore(filepath.Join(dir, "documents"), memory.TokenizeWords)
ds := coldcontext.NewStore(filepath.Join(dir, "documents"), memory.TokenizeWords)
if err := ds.Start(); err != nil {
t.Fatalf("doc store: %v", err)
}
@ -383,10 +383,10 @@ func newToolTestAgent(t *testing.T) *Agent {
emb := memory.NewStaticEmbedder("")
a := &Agent{
id: "tester",
memory: g,
docStore: ds,
context: NewRelevanceContext("", emb),
id: "tester",
memory: g,
coldcontextStore: ds,
context: NewRelevanceContext("", emb),
}
return a
}
@ -400,7 +400,7 @@ func TestToolMemoryCommit_BindsMedia(t *testing.T) {
}
out := a.executeMemoryTool(agentAPI.ToolCall{
Name: "memory_commit",
Name: "stablemem_commit",
Arguments: map[string]interface{}{
"triples": []interface{}{
map[string]interface{}{
@ -445,7 +445,7 @@ func TestToolMemoryCommit_BindsMedia(t *testing.T) {
func TestToolMemoryCommit_WithoutMedia(t *testing.T) {
a := newToolTestAgent(t)
out := a.executeMemoryTool(agentAPI.ToolCall{
Name: "memory_commit",
Name: "stablemem_commit",
Arguments: map[string]interface{}{
"triples": []interface{}{
map[string]interface{}{"subject": "甲方", "relation": "签署", "object": "合同"},
@ -464,7 +464,7 @@ func TestToolMemoryCommit_WithoutMedia(t *testing.T) {
func TestToolMemoryCommit_CarriesSentenceText(t *testing.T) {
a := newToolTestAgent(t)
a.executeMemoryTool(agentAPI.ToolCall{
Name: "memory_commit",
Name: "stablemem_commit",
Arguments: map[string]interface{}{
"triples": []interface{}{
map[string]interface{}{
@ -505,7 +505,7 @@ func TestToolDocCommit_BindsMedia(t *testing.T) {
t.Errorf("返回值应告知模型媒体已关联: %q", out)
}
docs := a.docStore.RecentDocs(5)
docs := a.coldcontextStore.RecentColdContexts(5)
if len(docs) == 0 {
t.Fatal("文档未写入")
}
@ -563,7 +563,7 @@ func TestTools_UnknownRefDegrades(t *testing.T) {
a := newToolTestAgent(t)
out := a.executeMemoryTool(agentAPI.ToolCall{
Name: "memory_commit",
Name: "stablemem_commit",
Arguments: map[string]interface{}{
"triples": []interface{}{
map[string]interface{}{