mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-04 16:27:28 +00:00
记忆层: - 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 缺失。
119 lines
6.1 KiB
Go
119 lines
6.1 KiB
Go
package core
|
||
|
||
// 术语改名的判据(plan.md 裁定 #11:`coldcontext_*` / `stablemem_*`,不再用 doc/graph)。
|
||
//
|
||
// ## 为什么必须钉(2026-10-29 现场)
|
||
//
|
||
// 裁定 #11 只落实了一半:`coldcontext_*` 已改,而**图记忆的工具仍叫 `memory_*`**。
|
||
// 改名不是纯文字工作,它有三个会静默失效的面:
|
||
//
|
||
// ① 路由:toolroute 按前缀分派,改前缀不改路由 ⇒ 工具"存在但调不到"
|
||
// ② 提示词:BuildToolPrompt / skill / 工具描述里写旧名 ⇒ 模型照着调不存在的工具
|
||
// ③ 安全策略:生产配置用的是通配符 `memory_*`,命中不了 `stablemem_*`
|
||
// ⇒ 硬私有策略静默失效(用户能读私人记忆)
|
||
//
|
||
// ③ 最危险:它不报错、不告警,只是拦不住。所以本文件也钉一条策略匹配判据。
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
|
||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||
"github.com/JianFeeeee/HologramAgent/internal/toolroute"
|
||
)
|
||
|
||
// ① 面向模型的稳定记忆工具必须统一用 stablemem_ 前缀。
|
||
func TestStableMemoryToolsUseStablememPrefix(t *testing.T) {
|
||
a := newPreemptAgent(t, newPreemptProvider())
|
||
a.memory = nil // 只需要名字,不需要真库
|
||
|
||
// 旧名不得再出现于任何工具定义(会造成"提示词教旧名、定义是新名")。
|
||
//
|
||
// ★ 此处曾有例外 `def.Name != "memory_document_query"`(2026-10-29 移除)。
|
||
// 当时以为那个工具"仍在 memory_ 名下",实际它是改名前的死分支:
|
||
// 没有工具定义、Dispatch 也不分派它。例外一旦存在,就会掩盖真问题 ——
|
||
// 所以现在**零例外**。
|
||
for _, def := range a.ToolDefinitions() {
|
||
if strings.HasPrefix(def.Name, "memory_") {
|
||
t.Errorf("发现未改名的稳定记忆工具: %s(裁定 #11 要求 stablemem_*)", def.Name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ② 路由必须认 stablemem_ 前缀(否则工具存在但调不到)。
|
||
//
|
||
// ★ 这里直接调 Dispatch,而不是某个"IsBuiltinTool"前缀表 —— 后者曾有**两份**
|
||
// 手抄副本(toolroute 与 core 各一份),都零调用方,却在改名时制造了
|
||
// "改了 Dispatch 却漏了副本"的真实故障。判据钉**行为**(能不能分派),
|
||
// 不钉"某份表里有没有这个字符串"。
|
||
func TestStablememToolsAreRouted(t *testing.T) {
|
||
h := &routeProbeHandlers{}
|
||
|
||
for _, n := range []string{"stablemem_recall", "stablemem_purge"} {
|
||
if _, handled := toolroute.Dispatch(h, agentAPI.ToolCall{Name: n}, nil); !handled {
|
||
t.Errorf("%s 未被 Dispatch 分派 —— stablemem_ 前缀漏了,工具会变成\"存在但调不到\"", n)
|
||
}
|
||
}
|
||
// ★ 旧前缀 memory_ **不再被分派**(这是改名的语义,不是遗漏)。
|
||
//
|
||
// 我最初在这里断言"memory_ 应保留兼容分派",它变红了 —— 而红得对:
|
||
// Dispatch 里从来没有 memory_ 分支,改名后只有 stablemem_。
|
||
// 若为"兼容"而加一条 memory_ 分支,反而会让**旧名重新可用**,
|
||
// 与裁定 #11(不再用 graph/doc 时代的名)直接冲突。
|
||
//
|
||
// 真正需要兼容的是**配置与提示词**(那些已另行处理),不是工具路由:
|
||
// 存量插件注册的是自己的工具名,内核的稳定记忆工具由内核定义,
|
||
// 不存在"外部还在用旧名调内核工具"的情况。
|
||
if _, handled := toolroute.Dispatch(h, agentAPI.ToolCall{Name: "memory_recall"}, nil); handled {
|
||
t.Error("memory_ 不应再被分派 —— 旧名重新可用会与裁定 #11 冲突")
|
||
}
|
||
// 反向:不是内置工具的名字必须返回 handled=false(否则插件工具会被吞掉)。
|
||
if _, handled := toolroute.Dispatch(h, agentAPI.ToolCall{Name: "qq_get_message"}, nil); handled {
|
||
t.Error("非内置工具不应被 Dispatch 吞掉")
|
||
}
|
||
}
|
||
|
||
// routeProbeHandlers 记录 Dispatch 选中了哪个族,全部返回空串即可。
|
||
type routeProbeHandlers struct {
|
||
memoryCalls int
|
||
}
|
||
|
||
func (h *routeProbeHandlers) ExecutePersonaTool(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteMemoryTool(agentAPI.ToolCall, []string) string {
|
||
h.memoryCalls++
|
||
return ""
|
||
}
|
||
func (h *routeProbeHandlers) ExecuteSocialTool(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteKnowledgeTool(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteColdContextTool(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteSecretTool(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteOutputSend(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteOutputSendHelp(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteOutputListChannels() string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteInputChannels(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteResidentAgents(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteNotifyParent(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteInputchNote(agentAPI.ToolCall) string { return "" }
|
||
func (h *routeProbeHandlers) ExecutePluginReload() string { return "" }
|
||
func (h *routeProbeHandlers) ExecuteGetPluginTools(string) string { return "" }
|
||
|
||
// ③ ★ 提示词里不得再教模型调旧工具名。
|
||
//
|
||
// BuildToolPrompt 是**硬编码字符串**(不是从定义生成),工具一改名它就过期。
|
||
// 这类不一致模型会当成事实执行,最终表现为"反复调用不存在的工具"。
|
||
func TestToolPromptDoesNotTeachRenamedTools(t *testing.T) {
|
||
// BuildToolPrompt 是 Indexer 上的**硬编码字符串**(不是从 toolDefs 生成),
|
||
// 工具一改名它就过期 —— 这正是本次改名后真实失败的那个测试。
|
||
idx := memory.NewIndexer(nil)
|
||
prompt := idx.BuildToolPrompt()
|
||
if prompt == "" {
|
||
t.Skip("BuildToolPrompt 返回空")
|
||
}
|
||
for _, stale := range []string{"memory_recall", "memory_commit", "memory_purge", "memory_introspect"} {
|
||
if strings.Contains(prompt, stale) {
|
||
t.Errorf("工具提示词仍在教旧工具名 %q —— 模型会照着调用不存在的工具", stale)
|
||
}
|
||
}
|
||
}
|