Files
Agent-System-Backend/internal/runmgr/memory_test.go
weichen 766a96e817
ci / vet-and-test (pull_request) Successful in 41s
Wire the memory port: store-backed adapter, loop injection, migration parity (N3 WP5)
Why: memory recall lived as a product-side interface assertion with no
library port, AgentSpec.MemoryScope had no consumer, and the model had
no write-back path. The port (Agent-Native#34) now exists; the product
adapts it over memory_entries.
Evidence: go test -count=1 ./... green; TestStoreMemoryRecallScopeAndLimit
(scope filters and limit), TestStoreMemoryAppendRoundtrip (project
context inheritance), TestRunCtxStoreMemory (runCtx outlet),
TestDirectRunInjectsMemoryBlock (the [记忆] block reaches the request
with the pre-migration text shape and deterministic order).
Impact: assembly-side injection removed (the loop injects per request
— same text, no duplication); memory_write available on the kernel
path; recall defaults to all scopes (pre-migration semantics), with
AgentSpec.MemoryScope narrowing where hosts pass it.
Refs: Agent-Native#34, Agent-Squad#38, N3 plan WP5
2026-10-07 16:23:24 +00:00

95 lines
3.0 KiB
Go
Raw Permalink 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 runmgr
import (
"context"
"path/filepath"
"testing"
"time"
"agent-native/runtime/memory"
"agent-system-backend/internal/store"
core "agent-system-core"
)
// ---- N3 WP5:storeMemory 适配器 ----
func openMemStore(t *testing.T) *store.Store {
t.Helper()
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "t.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
return st
}
func TestStoreMemoryRecallScopeAndLimit(t *testing.T) {
st := openMemStore(t)
put := func(scope, project, typ, content string) {
t.Helper()
if _, err := st.PutMemory("u1", core.MemoryEntry{Scope: scope, ProjectID: core.ID(project), Type: typ, Content: content, CreatedAt: time.Now()}); err != nil {
t.Fatal(err)
}
}
put("user", "", "fact", "用户事实")
put("project", "p1", "decision", "项目决定")
put("project", "p2", "decision", "别的项目")
m := NewStoreMemory(st, "u1", "p1")
// 空 scope = 全量(迁移前 Memories() 语义)。
all, err := m.Recall(context.Background(), memory.RecallQuery{})
if err != nil || len(all) != 3 {
t.Fatalf("全量召回:%d %v", len(all), err)
}
// user scope。
users, _ := m.Recall(context.Background(), memory.RecallQuery{Scope: "user"})
if len(users) != 1 || users[0].Content != "用户事实" {
t.Fatalf("user scope:%+v", users)
}
// project scope 用适配器的项目上下文(p1,不含 p2)。
projs, _ := m.Recall(context.Background(), memory.RecallQuery{Scope: "project"})
if len(projs) != 1 || projs[0].Content != "项目决定" {
t.Fatalf("project scope:%+v", projs)
}
// Limit。
limited, _ := m.Recall(context.Background(), memory.RecallQuery{Limit: 2})
if len(limited) != 2 {
t.Fatalf("Limit:%d", len(limited))
}
}
func TestStoreMemoryAppendRoundtrip(t *testing.T) {
st := openMemStore(t)
m := NewStoreMemory(st, "u1", "p1")
future := time.Now().Add(48 * time.Hour)
if err := m.Append(context.Background(), memory.Entry{
Scope: "project", Type: "procedure", Content: "先跑 lint", Source: "agent", ExpiresAt: &future,
}); err != nil {
t.Fatal(err)
}
list, err := st.ListMemory("u1", "project", "p1")
if err != nil || len(list) != 1 {
t.Fatalf("回读:%d %v", len(list), err)
}
got := list[0]
if got.Type != "procedure" || got.Content != "先跑 lint" || got.Source != "agent" ||
got.ProjectID != "p1" || got.ExpiresAt == nil {
t.Fatalf("写入形态:%+v", got)
}
}
// runCtx 出口:同一 store 上的 runCtx 暴露同一端口语义。
func TestRunCtxStoreMemory(t *testing.T) {
c, st := newSpawnEnv(t, &gateRunner{})
if _, err := st.PutMemory(c.run.Owner, core.MemoryEntry{Scope: "user", Type: "fact", Content: "跨会话事实", CreatedAt: time.Now()}); err != nil {
t.Fatal(err)
}
port := c.StoreMemory()
if port == nil {
t.Fatal("StoreMemory 必须非空")
}
entries, err := port.Recall(context.Background(), memory.RecallQuery{})
if err != nil || len(entries) != 1 || entries[0].Content != "跨会话事实" {
t.Fatalf("runCtx 召回:%+v %v", entries, err)
}
}