From 17234f5b5eb5c433804d88df9fb3914f509275e9 Mon Sep 17 00:00:00 2001 From: Djordje Lukic Date: Wed, 6 May 2026 00:02:03 +0200 Subject: [PATCH] avoid duplicate compaction system prompt Create the synthetic compaction agent without an instruction because the compaction system prompt is already inserted into the child session. Otherwise session.GetMessages prepends the agent instruction and the sub-run sends the same system prompt twice. Signed-off-by: Djordje Lukic --- pkg/runtime/compactor/compactor.go | 2 +- pkg/runtime/compactor/compactor_test.go | 48 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/pkg/runtime/compactor/compactor.go b/pkg/runtime/compactor/compactor.go index e4addce95..721edd2d7 100644 --- a/pkg/runtime/compactor/compactor.go +++ b/pkg/runtime/compactor/compactor.go @@ -122,7 +122,7 @@ func RunLLM(ctx context.Context, args LLMArgs) (*Result, error) { options.WithStructuredOutput(nil), options.WithMaxTokens(MaxSummaryTokens), ) - compactionAgent := agent.New("root", compaction.SystemPrompt, agent.WithModel(summaryModel)) + compactionAgent := agent.New("root", "", agent.WithModel(summaryModel)) messages, firstKeptEntry := extractMessages(args.Session, compactionAgent, args.ContextLimit, args.AdditionalPrompt) diff --git a/pkg/runtime/compactor/compactor_test.go b/pkg/runtime/compactor/compactor_test.go index 301dfaa0f..bfd638415 100644 --- a/pkg/runtime/compactor/compactor_test.go +++ b/pkg/runtime/compactor/compactor_test.go @@ -12,9 +12,25 @@ import ( "github.com/docker/docker-agent/pkg/agent" "github.com/docker/docker-agent/pkg/chat" "github.com/docker/docker-agent/pkg/compaction" + "github.com/docker/docker-agent/pkg/model/provider/base" "github.com/docker/docker-agent/pkg/session" + "github.com/docker/docker-agent/pkg/tools" ) +type fakeProvider struct{ id string } + +func (p fakeProvider) ID() string { return p.id } + +func (p fakeProvider) BaseConfig() base.Config { return base.Config{} } + +func (p fakeProvider) CreateChatCompletionStream( + context.Context, + []chat.Message, + []tools.Tool, +) (chat.MessageStream, error) { + return nil, nil +} + func TestExtractMessages(t *testing.T) { t.Parallel() @@ -202,6 +218,38 @@ func TestMapToSessionIndex(t *testing.T) { assert.Equal(t, len(sess.Messages), mapToSessionIndex(sess, 3)) } +func TestRunLLM_DoesNotDuplicateSystemPrompt(t *testing.T) { + t.Parallel() + + sess := session.New(session.WithMessages([]session.Item{ + session.NewMessageItem(&session.Message{Message: chat.Message{Role: chat.MessageRoleUser, Content: "please summarize"}}), + })) + a := agent.New("test", "parent prompt", agent.WithModel(fakeProvider{id: "fake/model"})) + + var systemPromptCount int + result, err := RunLLM(t.Context(), LLMArgs{ + Session: sess, + Agent: a, + ContextLimit: 100_000, + RunAgent: func(_ context.Context, compactionAgent *agent.Agent, compactionSession *session.Session) error { + for _, msg := range compactionSession.GetMessages(compactionAgent) { + if msg.Role == chat.MessageRoleSystem && msg.Content == compaction.SystemPrompt { + systemPromptCount++ + } + } + compactionSession.AddMessage(&session.Message{Message: chat.Message{ + Role: chat.MessageRoleAssistant, + Content: "summary", + }}) + return nil + }, + }) + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, 1, systemPromptCount, "compaction sub-run should see the compaction system prompt exactly once") +} + // TestRunLLM_RequiresRunAgent pins the contract that a missing RunAgent // callback is rejected loudly rather than silently no-oping. func TestRunLLM_RequiresRunAgent(t *testing.T) {