Files
HomeAgent/internal/agent/core/promptdump.go
JianFeeeee b850feaef2 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 缺失。
2026-10-29 23:53:48 +08:00

90 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"encoding/json"
"os"
"sync"
"time"
agentAPI "github.com/JianFeeeee/HologramAgent/internal/agent/api"
)
// 提示词抓包(opt-in 诊断)。
//
// 为什么需要:系统提示词是**每轮动态拼装**的(身份 + 人格 + 记忆 + 时序 +
// 工具顺序 + 输出规则 + 技能索引 + 首启门禁…),只看代码永远说不清模型
// 这一轮到底收到了什么。排查「它怎么又列清单了」「为什么它说有某能力」
// 这类问题时,唯一可靠的事实是**实际发出去的 payload**。
//
// 开关是环境变量而不是配置项:它会把记忆与用户输入原样落盘,属于
// 临时诊断手段,不该在界面上有一个能被随手打开的开关。
//
// HOMED_PROMPT_DUMP=/var/log/homeagent/prompt.jsonl
//
// 每行一条 JSON:时间、通道、模型、完整 messages(role + content)。
// 文件权限 0600 —— 内容是用户隐私。
// promptDump 是 opt-in 的**一次性诊断**手段,不是常驻功能。
//
// ## 生命周期约定(2026-10-29 教训)
//
// 这个开关原本只为一次排查而加("抓一轮真实提示词,确认 coldcontext
// 预填充到底注入了什么")。但当时的注释写着"排查完应删除本 drop-in 并删文件",
// 而**没有人执行那一步**:它在生产上挂了两天,`prompt.jsonl` 涨到 531MB。
//
// 这次为此加过"大小上限"(`HOMED_PROMPT_DUMP_MAX_MB`),但那其实是把
// 临时手段往"长期功能"上推 —— 方向错了。正确做法就是照它自己写明的
// 处置步骤做掉:删 drop-in、删文件。上限机制已随之移除。
//
// ## 若要再用一次
//
// 一次性诊断的正确姿势:
//
// HOMED_PROMPT_DUMP=/tmp/prompt-$(date +%s).jsonl homed ... # 不要写进 systemd
// # 看几轮就停,看完立刻删。
//
// 不要写进 drop-in —— 那会让"临时"变成"永久",且没有任何人记得删。
const promptDumpEnv = "HOMED_PROMPT_DUMP"
var promptDumpMu sync.Mutex
func dumpPrompt(channel string, req *agentAPI.CompletionRequest) {
path := os.Getenv(promptDumpEnv)
if path == "" || req == nil {
return
}
entry := map[string]interface{}{
"at": time.Now().Format(time.RFC3339Nano),
"channel": channel,
"model": req.Model,
}
msgs := make([]map[string]interface{}, 0, len(req.Messages))
for _, m := range req.Messages {
item := map[string]interface{}{"role": m.Role, "content": m.Content}
if len(m.Blocks) > 0 {
item["blocks"] = len(m.Blocks)
}
if len(m.ToolCalls) > 0 {
item["tool_calls"] = len(m.ToolCalls)
}
msgs = append(msgs, item)
}
entry["messages"] = msgs
entry["tool_count"] = len(req.Tools)
line, err := json.Marshal(entry)
if err != nil {
return
}
line = append(line, '\n')
promptDumpMu.Lock()
defer promptDumpMu.Unlock()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return
}
defer f.Close()
f.Write(line)
}