From 8539b2e7bad8a6e62fc4f68d87842da4eea69736 Mon Sep 17 00:00:00 2001 From: MahendraTeja95 Date: Wed, 11 Mar 2026 14:25:06 +0530 Subject: [PATCH] fix(agent): include TOOLS.md in bootstrap files and cache tracking TOOLS.md is documented in README as a workspace bootstrap file and migrated from openclaw projects, but was never read into the agent context. Add it to LoadBootstrapFiles() and sourcePaths() so its content is injected into the system prompt and changes trigger cache invalidation. Fixes #1315 --- pkg/agent/context.go | 2 ++ pkg/agent/context_cache_test.go | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index 5a84c45e2..caf431e95 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -227,6 +227,7 @@ func (cb *ContextBuilder) sourcePaths() []string { filepath.Join(cb.workspace, "SOUL.md"), filepath.Join(cb.workspace, "USER.md"), filepath.Join(cb.workspace, "IDENTITY.md"), + filepath.Join(cb.workspace, "TOOLS.md"), filepath.Join(cb.workspace, "memory", "MEMORY.md"), } } @@ -437,6 +438,7 @@ func (cb *ContextBuilder) LoadBootstrapFiles() string { "SOUL.md", "USER.md", "IDENTITY.md", + "TOOLS.md", } var sb strings.Builder diff --git a/pkg/agent/context_cache_test.go b/pkg/agent/context_cache_test.go index 707510820..3fda8b5d7 100644 --- a/pkg/agent/context_cache_test.go +++ b/pkg/agent/context_cache_test.go @@ -126,6 +126,57 @@ func TestSingleSystemMessage(t *testing.T) { } } +// TestToolsMdIncludedInBootstrap verifies that TOOLS.md is loaded into the +// system prompt as a bootstrap file, alongside AGENTS.md, SOUL.md, etc. +// Fixes #1315: TOOLS.md was documented in README and migrated from openclaw, +// but never read into the agent context. +func TestToolsMdIncludedInBootstrap(t *testing.T) { + tmpDir := setupWorkspace(t, map[string]string{ + "TOOLS.md": "# Tools\nUse the search tool for web queries.", + }) + defer os.RemoveAll(tmpDir) + + cb := NewContextBuilder(tmpDir) + prompt := cb.BuildSystemPrompt() + + if !strings.Contains(prompt, "Use the search tool for web queries") { + t.Error("system prompt should contain TOOLS.md content") + } + if !strings.Contains(prompt, "TOOLS.md") { + t.Error("system prompt should contain TOOLS.md header") + } +} + +// TestToolsMdCacheInvalidation verifies that changes to TOOLS.md invalidate +// the cached system prompt. +func TestToolsMdCacheInvalidation(t *testing.T) { + tmpDir := setupWorkspace(t, map[string]string{ + "TOOLS.md": "# Tools v1", + }) + defer os.RemoveAll(tmpDir) + + cb := NewContextBuilder(tmpDir) + sp1 := cb.BuildSystemPromptWithCache() + if !strings.Contains(sp1, "Tools v1") { + t.Fatal("initial prompt should contain TOOLS.md content") + } + + // Update TOOLS.md + toolsPath := filepath.Join(tmpDir, "TOOLS.md") + os.WriteFile(toolsPath, []byte("# Tools v2\nUpdated tool descriptions."), 0o644) + future := time.Now().Add(2 * time.Second) + os.Chtimes(toolsPath, future, future) + + // Cache should auto-invalidate + sp2 := cb.BuildSystemPromptWithCache() + if !strings.Contains(sp2, "Tools v2") { + t.Error("rebuilt prompt should contain updated TOOLS.md content") + } + if sp1 == sp2 { + t.Error("cache should be invalidated when TOOLS.md changes") + } +} + // TestMtimeAutoInvalidation verifies that the cache detects source file changes // via mtime without requiring explicit InvalidateCache(). // Fix: original implementation had no auto-invalidation — edits to bootstrap files,