From 72863cf8bc67e6f677de71a774badae13def1163 Mon Sep 17 00:00:00 2001 From: xiwuqi Date: Thu, 26 Mar 2026 22:40:30 -0500 Subject: [PATCH] perf(agent): avoid reloading bootstrap files on cache hits --- pkg/agent/context.go | 52 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index c3fcc9fff..20fdf756a 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -45,6 +45,11 @@ type ContextBuilder struct { // build time. This catches nested file creations/deletions/mtime changes // that may not update the top-level skill root directory mtime. skillFilesAtCache map[string]time.Time + + // sourcePathsAtCache stores the active non-skill tracked paths captured + // when the cache was built, so cache hits do not need to re-detect the + // bootstrap file family. + sourcePathsAtCache []string } func (cb *ContextBuilder) WithToolDiscovery(useBM25, useRegex bool) *ContextBuilder { @@ -209,6 +214,7 @@ func (cb *ContextBuilder) BuildSystemPromptWithCache() string { cb.cachedAt = baseline.maxMtime cb.existedAtCache = baseline.existed cb.skillFilesAtCache = baseline.skillFiles + cb.sourcePathsAtCache = append(cb.sourcePathsAtCache[:0], baseline.sourcePaths...) logger.DebugCF("agent", "System prompt cached", map[string]any{ @@ -229,6 +235,7 @@ func (cb *ContextBuilder) InvalidateCache() { cb.cachedAt = time.Time{} cb.existedAtCache = nil cb.skillFilesAtCache = nil + cb.sourcePathsAtCache = nil logger.DebugCF("agent", "System prompt cache invalidated", nil) } @@ -237,10 +244,25 @@ func (cb *ContextBuilder) InvalidateCache() { // invalidation (bootstrap files + memory). Skill roots are handled separately // because they require both directory-level and recursive file-level checks. func (cb *ContextBuilder) sourcePaths() []string { - agentDefinition := cb.LoadAgentDefinition() - paths := agentDefinition.trackedPaths(cb.workspace) - paths = append(paths, filepath.Join(cb.workspace, "memory", "MEMORY.md")) - return uniquePaths(paths) + agentPath := filepath.Join(cb.workspace, string(AgentDefinitionSourceAgent)) + paths := []string{ + agentPath, + filepath.Join(cb.workspace, "SOUL.md"), + filepath.Join(cb.workspace, "USER.md"), + filepath.Join(cb.workspace, "memory", "MEMORY.md"), + } + + // Cache invalidation only needs to know which bootstrap path family is active. + // Avoid loading/parsing AGENT.md on every cache hit; existence is sufficient + // because the structured format always wins when present. + if _, err := os.Stat(agentPath); err == nil { + return paths + } + + return append(paths, + filepath.Join(cb.workspace, string(AgentDefinitionSourceAgents)), + filepath.Join(cb.workspace, "IDENTITY.md"), + ) } // skillRoots returns all skill root directories that can affect @@ -260,9 +282,10 @@ func (cb *ContextBuilder) skillRoots() []string { // cacheBaseline holds the file existence snapshot and the latest observed // mtime across all tracked paths. Used as the cache reference point. type cacheBaseline struct { - existed map[string]bool - skillFiles map[string]time.Time - maxMtime time.Time + existed map[string]bool + skillFiles map[string]time.Time + sourcePaths []string + maxMtime time.Time } // buildCacheBaseline records which tracked paths currently exist and computes @@ -270,9 +293,10 @@ type cacheBaseline struct { // Called under write lock when the cache is built. func (cb *ContextBuilder) buildCacheBaseline() cacheBaseline { skillRoots := cb.skillRoots() + sourcePaths := cb.sourcePaths() // All paths whose existence we track: source files + all skill roots. - allPaths := append(cb.sourcePaths(), skillRoots...) + allPaths := append(append([]string(nil), sourcePaths...), skillRoots...) existed := make(map[string]bool, len(allPaths)) skillFiles := make(map[string]time.Time) @@ -312,7 +336,12 @@ func (cb *ContextBuilder) buildCacheBaseline() cacheBaseline { maxMtime = time.Unix(1, 0) } - return cacheBaseline{existed: existed, skillFiles: skillFiles, maxMtime: maxMtime} + return cacheBaseline{ + existed: existed, + skillFiles: skillFiles, + sourcePaths: sourcePaths, + maxMtime: maxMtime, + } } // sourceFilesChangedLocked checks whether any workspace source file has been @@ -328,7 +357,10 @@ func (cb *ContextBuilder) sourceFilesChangedLocked() bool { } // Check tracked source files (bootstrap + memory). - if slices.ContainsFunc(cb.sourcePaths(), cb.fileChangedSince) { + if len(cb.sourcePathsAtCache) == 0 { + return true + } + if slices.ContainsFunc(cb.sourcePathsAtCache, cb.fileChangedSince) { return true }