Files
Agent-Native/runtime/memory/write.go
T
weichen bdb45c3c2c
ci / vet-and-test (pull_request) Successful in 49s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Add the memory port with loop recall injection and the memory_write tool (N3 WP5)
Why: the completion doc assigns the Memory port to the library; today
memory recall is a product-side interface assertion with no library
surface, AgentSpec.MemoryScope has no consumer, and the model has no
write-back path.
Evidence: GOWORK=off go vet + go test -count=1 ./... green;
TestLoopInjectsMemoryAndRegistersWrite (recall block lands after the
system section, memory_write executes through the loop),
TestEmptyRecallInjectsNothing (empty recall changes nothing),
TestBlockRenderOrderingAndCaps (deterministic order, expiry/empty
pruning), TestWriteToolValidation (fail-closed scope/type/content).
Impact: new opt-in Config.Memory/MemoryScope; nil port keeps behavior
byte-identical; injection is request-scoped (never persisted, never
accumulates) and deterministic ([记忆] text shape preserved for the
product migration).
Refs: Agent-System-Documents#43 (N3 plan WP5)
2026-10-07 16:05:36 +00:00

94 lines
3.3 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 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
}