feat: include user-specific daily notes in per-user memory context (#995)
This commit is contained in:
parent
4bea9128d2
commit
127db43e0e
2 changed files with 82 additions and 11 deletions
|
|
@ -448,9 +448,13 @@ func safePathSegment(v string) (string, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadUserMemoryContext loads optional per-user memory content.
|
// loadUserMemoryContext loads optional per-user memory content.
|
||||||
// Supported lookup order:
|
// Supported lookup roots (in order):
|
||||||
// 1) <workspace>/users/<channel>/<chatID>/MEMORY.md
|
// 1) <workspace>/users/<channel>/<chatID>
|
||||||
// 2) <workspace>/users/<chatID>/MEMORY.md
|
// 2) <workspace>/users/<chatID>
|
||||||
|
//
|
||||||
|
// For each root, it reads:
|
||||||
|
// - MEMORY.md or memory/MEMORY.md (long-term)
|
||||||
|
// - memory/YYYYMM/YYYYMMDD.md for recent daily notes (last 3 days)
|
||||||
func (cb *ContextBuilder) loadUserMemoryContext(channel, chatID string) string {
|
func (cb *ContextBuilder) loadUserMemoryContext(channel, chatID string) string {
|
||||||
channelSeg, okChannel := safePathSegment(channel)
|
channelSeg, okChannel := safePathSegment(channel)
|
||||||
chatSeg, okChat := safePathSegment(chatID)
|
chatSeg, okChat := safePathSegment(chatID)
|
||||||
|
|
@ -458,19 +462,58 @@ func (cb *ContextBuilder) loadUserMemoryContext(channel, chatID string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates := []string{}
|
roots := []string{}
|
||||||
if okChannel {
|
if okChannel {
|
||||||
candidates = append(candidates, filepath.Join(cb.workspace, "users", channelSeg, chatSeg, "MEMORY.md"))
|
roots = append(roots, filepath.Join(cb.workspace, "users", channelSeg, chatSeg))
|
||||||
}
|
}
|
||||||
candidates = append(candidates, filepath.Join(cb.workspace, "users", chatSeg, "MEMORY.md"))
|
roots = append(roots, filepath.Join(cb.workspace, "users", chatSeg))
|
||||||
|
|
||||||
for _, p := range candidates {
|
for _, root := range roots {
|
||||||
if data, err := os.ReadFile(p); err == nil {
|
longTerm := ""
|
||||||
text := strings.TrimSpace(string(data))
|
for _, longTermPath := range []string{filepath.Join(root, "MEMORY.md"), filepath.Join(root, "memory", "MEMORY.md")} {
|
||||||
if text != "" {
|
if data, err := os.ReadFile(longTermPath); err == nil {
|
||||||
return "## User-specific Memory\n\n" + text
|
text := strings.TrimSpace(string(data))
|
||||||
|
if text != "" {
|
||||||
|
longTerm = text
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recentNotes := ""
|
||||||
|
memoryDir := filepath.Join(root, "memory")
|
||||||
|
for i := range 3 {
|
||||||
|
date := time.Now().AddDate(0, 0, -i).Format("20060102")
|
||||||
|
notePath := filepath.Join(memoryDir, date[:6], date+".md")
|
||||||
|
if data, err := os.ReadFile(notePath); err == nil {
|
||||||
|
text := strings.TrimSpace(string(data))
|
||||||
|
if text != "" {
|
||||||
|
if recentNotes != "" {
|
||||||
|
recentNotes += "\n\n---\n\n"
|
||||||
|
}
|
||||||
|
recentNotes += text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if longTerm == "" && recentNotes == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("## User-specific Memory\n\n")
|
||||||
|
if longTerm != "" {
|
||||||
|
sb.WriteString("### Long-term Memory\n\n")
|
||||||
|
sb.WriteString(longTerm)
|
||||||
|
}
|
||||||
|
if recentNotes != "" {
|
||||||
|
if longTerm != "" {
|
||||||
|
sb.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
sb.WriteString("### Recent Daily Notes\n\n")
|
||||||
|
sb.WriteString(recentNotes)
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -701,3 +701,31 @@ func TestBuildMessages_UserSpecificMemoryIgnoredForInvalidChatID(t *testing.T) {
|
||||||
t.Fatalf("expected no user memory section for invalid chat id")
|
t.Fatalf("expected no user memory section for invalid chat id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildMessages_IncludesUserMemoryFromMemoryDirAndDailyNotes(t *testing.T) {
|
||||||
|
today := time.Now().Format("20060102")
|
||||||
|
tmpDir := setupWorkspace(t, map[string]string{
|
||||||
|
"IDENTITY.md": "# Identity\nTest agent.",
|
||||||
|
"users/u42/memory/MEMORY.md": "timezone: UTC",
|
||||||
|
"users/u42/memory/" + today[:6] + "/" + today + ".md": "met a friend today",
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
cb := NewContextBuilder(tmpDir)
|
||||||
|
msgs := cb.BuildMessages(nil, "", "hello", nil, "webchat", "u42")
|
||||||
|
if len(msgs) == 0 || msgs[0].Role != "system" {
|
||||||
|
t.Fatalf("expected first system message")
|
||||||
|
}
|
||||||
|
if !strings.Contains(msgs[0].Content, "### Long-term Memory") {
|
||||||
|
t.Fatalf("expected long-term user memory heading")
|
||||||
|
}
|
||||||
|
if !strings.Contains(msgs[0].Content, "timezone: UTC") {
|
||||||
|
t.Fatalf("expected user long-term memory content")
|
||||||
|
}
|
||||||
|
if !strings.Contains(msgs[0].Content, "### Recent Daily Notes") {
|
||||||
|
t.Fatalf("expected user daily notes heading")
|
||||||
|
}
|
||||||
|
if !strings.Contains(msgs[0].Content, "met a friend today") {
|
||||||
|
t.Fatalf("expected user daily notes content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue