package memory import ( "context" "encoding/json" "fmt" "strings" "time" "agent-native/runtime/tools" ) // memory_write 工具:模型自服务写回(fail-closed 校验 scope/type/content)。 // 只在宿主配置 Store 时注册(RegisterWrite 由循环在 Run 启动时接线)。 type writeTool struct { store Store scope string // 默认 scope(AgentSpec.MemoryScope 驱动;空 = user) } var validScopes = map[string]bool{"user": true, "project": true} var validTypes = map[string]bool{ "fact": true, "preference": true, "decision": true, "episode": true, "procedure": true, } // RegisterWrite 把 memory_write 注册进 reg(store nil 时跳过;重复注册按 // 覆盖处理——宿主不得在同一注册表上给不同 run 挂不同实现)。 func RegisterWrite(reg *tools.Registry, store Store, defaultScope string) { if reg == nil || store == nil { return } reg.Register(&writeTool{store: store, scope: defaultScope}) } func (t *writeTool) Name() string { return "memory_write" } func (t *writeTool) Description() string { return "把一条值得跨会话记住的事实/偏好/决定写入长期记忆(scope: user 或 project;type: fact|preference|decision|episode|procedure)。" } func (t *writeTool) Parameters() json.RawMessage { return json.RawMessage(`{"type":"object","required":["type","content"],"properties":{ "scope":{"type":"string"}, "type":{"type":"string"}, "content":{"type":"string"}, "expires_in_days":{"type":"number"} }}`) } // maxWriteContent 是单条记忆的内容上限(字符)。 const maxWriteContent = 2000 func (t *writeTool) Execute(ctx context.Context, args json.RawMessage) (tools.Result, error) { var a struct { Scope string `json:"scope"` Type string `json:"type"` Content string `json:"content"` ExpiresInDays float64 `json:"expires_in_days"` } if err := json.Unmarshal(args, &a); err != nil { return tools.Result{Content: "memory_write: 参数不是完整 JSON 对象", IsError: true}, nil } scope := strings.TrimSpace(a.Scope) if scope == "" { scope = t.scope } if scope == "" { scope = "user" } if !validScopes[scope] { return tools.Result{Content: fmt.Sprintf("memory_write: scope %q 必须是 user 或 project", scope), IsError: true}, nil } typ := strings.TrimSpace(a.Type) if !validTypes[typ] { return tools.Result{Content: fmt.Sprintf("memory_write: type %q 必须是 fact|preference|decision|episode|procedure 之一", typ), IsError: true}, nil } content := strings.TrimSpace(a.Content) if content == "" { return tools.Result{Content: "memory_write: content 必填且非空", IsError: true}, nil } if len([]rune(content)) > maxWriteContent { return tools.Result{Content: fmt.Sprintf("memory_write: content 超过 %d 字符上限", maxWriteContent), IsError: true}, nil } var expires *time.Time if a.ExpiresInDays > 0 { t := time.Now().Add(time.Duration(a.ExpiresInDays*24) * time.Hour) expires = &t } if err := t.store.Append(ctx, Entry{ Scope: scope, Type: typ, Content: content, Source: "agent", CreatedAt: time.Now(), ExpiresAt: expires, }); err != nil { return tools.Result{Content: "memory_write: " + err.Error(), IsError: true}, nil } return tools.Result{Content: fmt.Sprintf("已记住(scope=%s type=%s)", scope, typ)}, nil }