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

@ -850,8 +850,8 @@ func (p *Plugin) collectToolDefsForLLM(s *sdk.PluginSDK, pluginFilter string) []
func isSafeReadonlyTool(name string) bool {
// 明确只读的查询/列表类工具
readonlyExact := map[string]bool{
"memory_recall": true,
"memory_introspect": true,
"stablemem_recall": true,
"stablemem_introspect": true,
"coldcontext_query": true,
"knowledge_search": true,
"knowledge_list": true,

View File

@ -10,7 +10,7 @@ import (
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
"github.com/JianFeeeee/HologramAgent/internal/knowledge"
"github.com/JianFeeeee/HologramAgent/internal/memory"
doc "github.com/JianFeeeee/HologramAgent/internal/memory/document"
coldcontext "github.com/JianFeeeee/HologramAgent/internal/memory/coldcontext"
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
)
@ -322,14 +322,14 @@ func TestHealthcheckWithDocStore(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
ds := doc.NewStore(tmpDir, memory.TokenizeWords)
ds := coldcontext.NewStore(tmpDir, memory.TokenizeWords)
if err := ds.Start(); err != nil {
t.Fatal(err)
}
defer ds.Stop()
// 预置一篇生产文档,验证 healthcheck 自检后不触碰它
if err := ds.Insert(&doc.Doc{ID: "prod_doc", Summary: "生产文档", Content: "这是生产文档,不应被健康检查破坏"}); err != nil {
if err := ds.Insert(&coldcontext.ColdDoc{ID: "prod_doc", Summary: "生产文档", Content: "这是生产文档,不应被健康检查破坏"}); err != nil {
t.Fatal(err)
}
before := len(ds.Query("生产文档", 10))
@ -398,7 +398,7 @@ func TestLLMReportCollection(t *testing.T) {
func TestSafeReadonlyTool(t *testing.T) {
// 只读:应放行
readonly := []string{
"memory_recall", "memory_introspect", "coldcontext_query",
"stablemem_recall", "stablemem_introspect", "coldcontext_query",
"knowledge_search", "knowledge_list", "person_query",
"person_network", "llm_list_sources", "output_list_channels",
"terminal_list", "files_list",
@ -411,8 +411,8 @@ func TestSafeReadonlyTool(t *testing.T) {
// 写/删/改/副作用:应被拒绝
mutating := []string{
"memory_commit", "memory_edit", "memory_purge", "memory_delete_entity",
"memory_merge", "memory_block_merge",
"stablemem_commit", "stablemem_edit", "stablemem_purge", "stablemem_delete_entity",
"stablemem_merge", "stablemem_block_merge",
"knowledge_create", "knowledge_delete",
"coldcontext_commit", "doc_delete",
"person_set_trait", "person_relate",
@ -436,8 +436,8 @@ func TestCollectToolDefsForLLMNoMutating(t *testing.T) {
// 构造一个包含写类工具的 ToolDef 集合,注入工具注册表
stage := agentCore.NewStageHost()
defs := []sdk.ToolDef{
{Name: "memory_recall", Plugin: "memory", Description: "recall"},
{Name: "memory_commit", Plugin: "memory", Description: "commit"},
{Name: "stablemem_recall", Plugin: "memory", Description: "recall"},
{Name: "stablemem_commit", Plugin: "memory", Description: "commit"},
{Name: "coldcontext_query", Plugin: "coldcontext", Description: "query"},
{Name: "coldcontext_commit", Plugin: "coldcontext", Description: "commit doc"},
{Name: "knowledge_search", Plugin: "knowledge", Description: "search"},
@ -465,13 +465,13 @@ func TestCollectToolDefsForLLMNoMutating(t *testing.T) {
}
// 只读工具应被包含
for _, name := range []string{"memory_recall", "coldcontext_query", "knowledge_search", "files_list"} {
for _, name := range []string{"stablemem_recall", "coldcontext_query", "knowledge_search", "files_list"} {
if !allowed[name] {
t.Errorf("expected readonly tool %q in LLM selftest set, missing", name)
}
}
// 写类/副作用工具绝不能被交给 LLM
for _, name := range []string{"memory_commit", "coldcontext_commit", "knowledge_create", "cmd_run"} {
for _, name := range []string{"stablemem_commit", "coldcontext_commit", "knowledge_create", "cmd_run"} {
if allowed[name] {
t.Errorf("mutating tool %q must NOT be in LLM selftest set", name)
}
@ -484,8 +484,8 @@ func TestCollectToolDefsForLLMPluginFilter(t *testing.T) {
stage := agentCore.NewStageHost()
defs := []sdk.ToolDef{
{Name: "memory_recall", Plugin: "memory", Description: "recall"},
{Name: "memory_commit", Plugin: "memory", Description: "commit"},
{Name: "stablemem_recall", Plugin: "memory", Description: "recall"},
{Name: "stablemem_commit", Plugin: "memory", Description: "commit"},
{Name: "coldcontext_query", Plugin: "coldcontext", Description: "query"},
{Name: "coldcontext_commit", Plugin: "coldcontext", Description: "commit doc"},
{Name: "knowledge_search", Plugin: "knowledge", Description: "search"},
@ -514,14 +514,14 @@ func TestCollectToolDefsForLLMPluginFilter(t *testing.T) {
if len(got) != 1 || !allowed["files_list"] {
t.Fatalf("expected only files_list for files plugin, got %v", allowed)
}
for _, name := range []string{"memory_recall", "coldcontext_query", "knowledge_search", "files_write"} {
for _, name := range []string{"stablemem_recall", "coldcontext_query", "knowledge_search", "files_write"} {
if allowed[name] {
t.Errorf("tool %q must NOT be in files-filtered set", name)
}
}
gotMemory := p.collectToolDefsForLLM(s, "memory")
if len(gotMemory) != 1 || gotMemory[0].Name != "memory_recall" {
if len(gotMemory) != 1 || gotMemory[0].Name != "stablemem_recall" {
t.Fatalf("expected only memory_recall for memory plugin, got %v", gotMemory)
}

View File

@ -14,7 +14,7 @@ import (
"github.com/JianFeeeee/HologramAgent/internal/knowledge"
luaVM "github.com/JianFeeeee/HologramAgent/internal/lua"
"github.com/JianFeeeee/HologramAgent/internal/memory"
doc "github.com/JianFeeeee/HologramAgent/internal/memory/document"
coldcontext "github.com/JianFeeeee/HologramAgent/internal/memory/coldcontext"
"github.com/JianFeeeee/HologramAgent/internal/plugin"
cli "github.com/JianFeeeee/HologramAgent/internal/plugins/cli"
"github.com/JianFeeeee/HologramAgent/internal/plugins/webui"
@ -22,14 +22,14 @@ import (
)
type testPluginEnv struct {
tmpDir string
stageHost *agentCore.StageHost
iom *agentIO.IOManager
pluginReg *plugin.Registry
memDB *memory.GraphDB
ks *knowledge.Store
docStore *doc.Store
cfgReg *internalConfig.ConfigRegistry
tmpDir string
stageHost *agentCore.StageHost
iom *agentIO.IOManager
pluginReg *plugin.Registry
memDB *memory.GraphDB
ks *knowledge.Store
coldcontextStore *coldcontext.Store
cfgReg *internalConfig.ConfigRegistry
}
func setupIntegration(t *testing.T) *testPluginEnv {
@ -107,15 +107,15 @@ func setupIntegrationWithProvider(t *testing.T, pm *agentAPI.ProviderManager) *t
t.Fatal(err)
}
docStore := doc.NewStore(filepath.Join(tmpDir, "documents"), memory.TokenizeWords)
if err := docStore.Start(); err != nil {
coldcontextStore := coldcontext.NewStore(filepath.Join(tmpDir, "documents"), memory.TokenizeWords)
if err := coldcontextStore.Start(); err != nil {
t.Fatal(err)
}
pluginReg.SetStageHost(stageHost)
pluginReg.SetProviderManager(pm)
pluginReg.SetKnowledge(ks)
pluginReg.SetDocStore(docStore)
pluginReg.SetDocStore(coldcontextStore)
cli.DefaultSocket = filepath.Join(tmpDir, "cli.sock")
@ -154,14 +154,14 @@ func setupIntegrationWithProvider(t *testing.T, pm *agentAPI.ProviderManager) *t
}
return &testPluginEnv{
tmpDir: tmpDir,
stageHost: stageHost,
iom: iom,
pluginReg: pluginReg,
memDB: memDB,
ks: ks,
docStore: docStore,
cfgReg: cfgReg,
tmpDir: tmpDir,
stageHost: stageHost,
iom: iom,
pluginReg: pluginReg,
memDB: memDB,
ks: ks,
coldcontextStore: coldcontextStore,
cfgReg: cfgReg,
}
}
@ -169,7 +169,7 @@ func (e *testPluginEnv) cleanup() {
e.pluginReg.StopAll()
e.memDB.Close()
e.ks.Stop()
e.docStore.Stop()
e.coldcontextStore.Stop()
os.RemoveAll(e.tmpDir)
}

View File

@ -18,7 +18,7 @@ Four stores, one rule each:
| Need | Tool |
|---|---|
| A past episode in full | `coldcontext_query` |
| A fact or relation | `memory_recall` |
| A fact or relation | `stablemem_recall` |
| Written reference material | `knowledge_search` |
### `coldcontext_query` semantics (read this before using it)
@ -32,6 +32,6 @@ again later, it lives on in the timeline until pruning moves it back.
## Writing
- Facts worth keeping: `memory_commit` (triples).
- Facts worth keeping: `stablemem_commit` (triples).
- Written knowledge: `knowledge_write`.
- Do not commit transient chatter, tool output, or your own status lines.