feat(prompt): distinguish user/project context

Amolith created

Separate user-defined memory paths from project-specific context paths.
Previously, memory paths were appended to context paths. Now, they are
passed distinctly to the prompt generator.

The CoderPrompt now formats project context within <project_context>
tags and user memory within <user_preferences> tags, each with a
specific explanatory header for the LLM. This allows for clearer
separation and potential prioritization by the model.

Issue: charmbracelet/crush#1050

Change summary

internal/agent/prompt/prompt.go       | 25 ++++++++++++++++++++-----
internal/agent/templates/coder.md.tpl | 18 ++++++++++++++++--
2 files changed, 36 insertions(+), 7 deletions(-)

Detailed changes

internal/agent/prompt/prompt.go 🔗

@@ -36,6 +36,7 @@ type PromptDat struct {
 	Date          string
 	GitStatus     string
 	ContextFiles  []ContextFile
+	MemoryFiles   []ContextFile
 	AvailSkillXML string
 }
 
@@ -152,16 +153,27 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg con
 	workingDir := cmp.Or(p.workingDir, cfg.WorkingDir())
 	platform := cmp.Or(p.platform, runtime.GOOS)
 
-	files := map[string][]ContextFile{}
+	contextFiles := map[string][]ContextFile{}
+	memoryFiles := map[string][]ContextFile{}
 
 	for _, pth := range cfg.Options.ContextPaths {
 		expanded := expandPath(pth, cfg)
 		pathKey := strings.ToLower(expanded)
-		if _, ok := files[pathKey]; ok {
+		if _, ok := contextFiles[pathKey]; ok {
 			continue
 		}
 		content := processContextPath(expanded, cfg)
-		files[pathKey] = content
+		contextFiles[pathKey] = content
+	}
+
+	for _, pth := range cfg.Options.MemoryPaths {
+		expanded := expandPath(pth, cfg)
+		pathKey := strings.ToLower(expanded)
+		if _, ok := memoryFiles[pathKey]; ok {
+			continue
+		}
+		content := processContextPath(expanded, cfg)
+		memoryFiles[pathKey] = content
 	}
 
 	// Discover and load skills metadata.
@@ -195,8 +207,11 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg con
 		}
 	}
 
-	for _, contextFiles := range files {
-		data.ContextFiles = append(data.ContextFiles, contextFiles...)
+	for _, files := range contextFiles {
+		data.ContextFiles = append(data.ContextFiles, files...)
+	}
+	for _, files := range memoryFiles {
+		data.MemoryFiles = append(data.MemoryFiles, files...)
 	}
 	return data, nil
 }

internal/agent/templates/coder.md.tpl 🔗

@@ -372,11 +372,25 @@ If a skill mentions scripts, references, or assets, they are placed in the same
 {{end}}
 
 {{if .ContextFiles}}
-<memory>
+# Project-Specific Context
+Make sure to follow the instructions in the context below.
+<project_context>
 {{range .ContextFiles}}
 <file path="{{.Path}}">
 {{.Content}}
 </file>
 {{end}}
-</memory>
+</project_context>
+{{end}}
+
+{{if .MemoryFiles}}
+# User context
+The following is personal content added by the user that they'd like you to follow no matter what project you're working in.
+<user_preferences>
+{{range .MemoryFiles}}
+<file path="{{.Path}}">
+{{.Content}}
+</file>
+{{end}}
+</user_preferences>
 {{end}}