feat(agent): wire AGENTS.md discovery into the agent loop's system prompt

Export persona.DiscoverAgentsMD and add agent.Config.AgentsMD so project
and global AGENTS.md rules actually reach the model. Previously
buildInitialMessages always called AssembleSystemPrompt with an empty
string, so no AGENTS.md content was ever injected despite the discovery
logic already existing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Victor Hugo Vargas Servin 2026-07-08 23:27:17 -07:00
parent eeefe07809
commit 0f835a0802
4 changed files with 31 additions and 5 deletions

View file

@ -42,6 +42,7 @@ type Config struct {
OnIteration OnIterationHook
ToolTimeout time.Duration
ChatTemplateKwargs map[string]any // passed to the LLM provider (e.g. Qwen enable_thinking)
AgentsMD string // discovered AGENTS.md content, folded into the system prompt
}
// Iteration represents a single cycle of the agent loop.
@ -230,7 +231,7 @@ func (l *Loop) RunStream(ctx context.Context, input string, history ...llm.Messa
}
func (l *Loop) buildInitialMessages(input string, history []llm.Message) []llm.Message {
systemPrompt := persona.AssembleSystemPrompt(l.cfg.Persona, "")
systemPrompt := persona.AssembleSystemPrompt(l.cfg.Persona, l.cfg.AgentsMD)
messages := make([]llm.Message, 0, len(history)+2)
messages = append(messages, llm.Message{Role: llm.RoleSystem, Content: systemPrompt})
messages = append(messages, history...)

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"iter"
"strings"
"testing"
"time"
@ -116,6 +117,30 @@ func TestRun_NoToolCalls(t *testing.T) {
}
}
func TestRun_IncludesAgentsMD(t *testing.T) {
var capturedSystemPrompt string
mockClient := &mockLLM{
generateFunc: func(ctx context.Context, req llm.CompletionRequest) (llm.CompletionResponse, error) {
capturedSystemPrompt = req.Messages[0].Content
return llm.CompletionResponse{Content: "done"}, nil
},
}
loop := New(Config{
LLM: mockClient,
Persona: persona.DefaultPersona(),
Tools: tools.NewRegistry(),
AgentsMD: "Never edit go.mod directly.",
})
if _, err := loop.Run(context.Background(), "Hello"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(capturedSystemPrompt, "Never edit go.mod directly.") {
t.Errorf("expected system prompt to include AGENTS.md content, got %q", capturedSystemPrompt)
}
}
func TestRun_ToolCalls(t *testing.T) {
registry := tools.NewRegistry()
registry.Register(tools.Tool{

View file

@ -79,8 +79,8 @@ func AssembleSystemPrompt(p Persona, agentsMD string) string {
return strings.Join(parts, "\n\n")
}
// discoverAgentsMD walks up the directory tree looking for AGENTS.md files.
func discoverAgentsMD(root string) string {
// DiscoverAgentsMD walks up the directory tree looking for AGENTS.md files.
func DiscoverAgentsMD(root string) string {
var parts []string
current := root

View file

@ -45,9 +45,9 @@ func TestDiscoverAgentsMD(t *testing.T) {
agentsPath := filepath.Join(tmpDir, "AGENTS.md")
os.WriteFile(agentsPath, []byte("test instructions"), 0644)
result := discoverAgentsMD(tmpDir)
result := DiscoverAgentsMD(tmpDir)
if !contains(result, "test instructions") {
t.Error("expected discoverAgentsMD to find AGENTS.md")
t.Error("expected DiscoverAgentsMD to find AGENTS.md")
}
}