Files
HomeAgent/plan.md
root 3fc2151588 refactor: pluginize text cleaning and tool NoMemory control
- SDK: ToolDef.NoMemory field, PluginSDK.RegisterTextCleaner/TextCleaners
- Registry: aggregate text cleaners from plugins, expose CleanText()
- Memory: replace hardcoded QQ regex CleanTemplateText with dynamic CleanText/SetTextCleaner
- StageHost: add ToolDef(name) lookup
- eventloop: check ToolDef.NoMemory before emitMemoryCandidate
- context/Prune: replace hardcoded agentcli/terminal source filter with ToolsUsed NoMemory check
- agentcli/cmd: mark tools with NoMemory: true
- main.go: wire memory.SetTextCleaner(pluginReg.CleanText)
2026-07-24 14:49:08 +08:00

7.6 KiB
Raw Blame History

插件化改造 — 实施计划

问题

当前 core 层两处硬编码耦合:

  1. 文本清洗internal/memory/clean_text.go 硬编码 6 个 QQ 正则context/indexer/cut/VertorizeClean 各环节直接调用 CleanTemplateText

  2. 工具输出不进记忆internal/agent/core/context.go:196 硬编码 source 名过滤

    if s.event.Source == "agentcli" || s.event.Source == "terminal" {
        continue
    }
    

    意图是 cmd_runterminal_* 等工具的产出不归档到 document memory但手段是查 source 而不是查工具定义,且只覆盖了 Prune 路径document没有覆盖 emitMemoryCandidate 路径text + graph memory

    同时 eventloop.go:337!stageCtx.NoMemory 检查是命令式的(InjectTextNoMemory 设 payload flag不是声明式的工具级控制。

目标

问题 方案
文本清洗 插件通过 s.RegisterTextCleaner() 注册清洗函数core 遍历执行
工具记忆控制 ToolDef 增加 NoMemory bool 字段插件声明式标记工具core 在 emitMemoryCandidate 和 Prune 两处自动跳过

SDK 修改(外部包 homeagent-sdk

位置: sdk/plugin.go

1.1 ToolDef 增加 NoMemory

type ToolDef struct {
    Name        string                 `json:"name"`
    Plugin      string                 `json:"plugin,omitempty"`
    Description string                 `json:"description"`
    Parameters  map[string]interface{} `json:"parameters"`
    NoMemory    bool                   `json:"no_memory,omitempty"` // ← 新增
}

1.2 PluginSDK 增加 TextCleaner

// PluginSDK 新增字段
textCleaners []func(text string) string

func (s *PluginSDK) RegisterTextCleaner(cleaner func(text string) string) {
    s.textCleaners = append(s.textCleaners, cleaner)
}

// 框架用 — 提取所有 cleaner
func (s *PluginSDK) TextCleaners() []func(text string) string {
    return s.textCleaners
}

SDK 管理策略

复制 SDK 到 _sdk_local/go.modreplace gitcode.com/JianFeeeee/homeagent-sdk => ./_sdk_local


内部变更

2.1 Registry聚合 text cleaners

文件: internal/plugin/registry.go

  • 新增字段 textCleaners []func(string) string
  • buildSDK()plg.Start(plgSDK) 成功后提取 plgSDK.TextCleaners()textCleaners
  • 新增 CleanText(text string) string 依次执行所有 cleaner无 cleaner 时返回原文本
  • StopAll() / Reload() 时重置

2.2 Memory动态 cleaner 入口

文件: internal/memory/clean_text.go

替换硬编码 QQ 正则为动态 cleaner

var globalTextCleaner func(string) string

func SetTextCleaner(fn func(string) string)  { globalTextCleaner = fn }
func CleanText(text string) string {
    if globalTextCleaner != nil {
        text = globalTextCleaner(text)
    }
    // 保留通用 trim
    text = strings.TrimSpace(text)
    text = strings.TrimPrefix(text, "")
    text = strings.TrimPrefix(text, "")
    text = strings.TrimSpace(text)
    return text
}

删除 reQQ* 正则变量。

2.3 StageHost按名查 ToolDef

文件: internal/agent/core/stages.go

新增方法:

func (h *StageHost) ToolDef(name string) *sdk.ToolDef {
    h.mu.RLock()
    defer h.mu.RUnlock()
    for _, def := range h.toolDefs {
        if def.Name == name {
            return &def
        }
    }
    return nil
}

2.4 Agent两处 NoMemory 检查

2.4a emitMemoryCandidate 路径

文件: internal/agent/core/eventloop.go

processTextInput()processMediaInput() 中的 emitMemoryCandidate 调用前,除了检查 stageCtx.NoMemory,还检查本次调用的工具是否有 NoMemory

hasNoMemoryTool := false
for _, name := range toolsUsed {
    if def := a.stageHost.ToolDef(name); def != nil && def.NoMemory {
        hasNoMemoryTool = true
        break
    }
}
if !stageCtx.NoMemory && !hasNoMemoryTool {
    a.emitMemoryCandidate(...)
}

这样不论通过 InjectText 还是 InjectTextNoMemory,只要 LLM 调用了 NoMemory: true 的工具,整轮对话就不进 text memory 和 graph memory。

2.4b Prune 归档路径

文件: internal/agent/core/context.go

删除 hardcoded source 过滤,改为检查事件中 ToolsUsed 是否有 NoMemory 工具:

for _, s := range archive {
    if hasNoMemoryTool(s.event.ToolsUsed) {
        continue
    }
    filtered = append(filtered, s)
}

hasNoMemoryTool 通过 StageHost.ToolDef 查每个工具名是否有 NoMemory: true

2.5 caller CleanTemplateText → CleanText

文件 替换
internal/agent/core/context.go memory.CleanTemplateTextmemory.CleanText
internal/memory/indexer.go 同上
internal/memory/cut.go 同上
internal/memory/clean_text.go 删除函数 CleanTemplateText,新增 CleanText + SetTextCleaner

2.6 main.go 串联

文件: cmd/homed/main.go

pluginReg.Load() 之后:

memory.SetTextCleaner(pluginReg.CleanText)

2.7 agentcli / cmd 插件:标记 NoMemory

文件: internal/plugins/agentcli/plugin.gointernal/plugins/cmd/plugin.go

RegisterTool 调用的 ToolDef 中加 NoMemory: true

  • terminal_create, terminal_write, terminal_read, terminal_resize, terminal_close, terminal_list
  • cmd_run

外部插件变更

QQ 插件

Start() 中:

s.RegisterTextCleaner(func(text string) string {
    text = reQQGroupSuffix.ReplaceAllString(text, "")
    text = reQQPrivateSuffix.ReplaceAllString(text, "")
    text = reQQOldReply.ReplaceAllString(text, "")
    text = reQQOldForbid.ReplaceAllString(text, "")
    text = reQQGeneral.ReplaceAllString(text, "")
    text = reTimestamp.ReplaceAllString(text, "")
    text = reMultiSpace.ReplaceAllString(text, " ")
    return text
})

s.RegisterTool("qq_get_message", sdk.ToolDef{
    Description: "获取QQ消息正文",
    Parameters:  map[string]interface{}{...},
    NoMemory:    true,
}, handler)

文件变更清单

文件 操作
sdk/plugin.go(外部 SDK 修改:ToolDefNoMemoryPluginSDKRegisterTextCleaner/TextCleaners
go.mod 修改:增 replace 指令
internal/plugin/registry.go 修改:增 cleaners 聚合 + CleanText
internal/memory/clean_text.go 修改:硬编码 → 动态 cleaner删除 CleanTemplateText
internal/agent/core/stages.go 修改:增 ToolDef(name) 查找
internal/agent/core/eventloop.go 修改:emitMemoryCandidate 前查 ToolDef.NoMemory
internal/agent/core/context.go 修改source 过滤 → ToolsUsed NoMemory 检查 + CleanTemplateTextCleanText
internal/agent/core/process.go 修改(可能需要):传递 toolsUsed 到 Prune 或加辅助方法
internal/memory/indexer.go 修改:CleanTemplateTextCleanText
internal/memory/cut.go 修改:同上
internal/plugins/agentcli/plugin.go 修改:工具标记 NoMemory: true
internal/plugins/cmd/plugin.go 修改:cmd_run 标记 NoMemory: true
cmd/homed/main.go 修改:memory.SetTextCleaner(pluginReg.CleanText)

实施顺序

  1. SDK 本地副本 + 加 NoMemory/RegisterTextCleaner
  2. Registry 聚合 cleaners + CleanText
  3. Memory 动态 cleaner + 改名调用者
  4. StageHost ToolDef(name)
  5. eventloop + context Prune + process 加入 NoMemory 检查
  6. 标记 agentcli/cmd 工具的 NoMemory: true
  7. main.go 串联
  8. 删除旧代码QQ 正则、source 硬编码)
  9. 构建 + 测试