From 127db43e0eb53ec9f40baface9f37ad7d0aa692a Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Tue, 3 Mar 2026 19:12:06 +0530 Subject: [PATCH] feat: include user-specific daily notes in per-user memory context (#995) --- pkg/agent/context.go | 65 +++++++++++++++++++++++++++------ pkg/agent/context_cache_test.go | 28 ++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index 843ea8622..ebe04f59e 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -448,9 +448,13 @@ func safePathSegment(v string) (string, bool) { } // loadUserMemoryContext loads optional per-user memory content. -// Supported lookup order: -// 1) /users///MEMORY.md -// 2) /users//MEMORY.md +// Supported lookup roots (in order): +// 1) /users// +// 2) /users/ +// +// 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 { channelSeg, okChannel := safePathSegment(channel) chatSeg, okChat := safePathSegment(chatID) @@ -458,19 +462,58 @@ func (cb *ContextBuilder) loadUserMemoryContext(channel, chatID string) string { return "" } - candidates := []string{} + roots := []string{} 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 { - if data, err := os.ReadFile(p); err == nil { - text := strings.TrimSpace(string(data)) - if text != "" { - return "## User-specific Memory\n\n" + text + for _, root := range roots { + longTerm := "" + for _, longTermPath := range []string{filepath.Join(root, "MEMORY.md"), filepath.Join(root, "memory", "MEMORY.md")} { + if data, err := os.ReadFile(longTermPath); err == nil { + 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 "" diff --git a/pkg/agent/context_cache_test.go b/pkg/agent/context_cache_test.go index db7124ae5..95293ec8f 100644 --- a/pkg/agent/context_cache_test.go +++ b/pkg/agent/context_cache_test.go @@ -701,3 +701,31 @@ func TestBuildMessages_UserSpecificMemoryIgnoredForInvalidChatID(t *testing.T) { 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") + } +}