Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions internal/app/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
// 这意味着所有会话都归属到启动时指定的项目目录下,运行时不会因配置变更而迁移存储位置。
sessionStore := agentsession.NewStore(loader.BaseDir(), cfg.Workdir)

var contextBuilder agentcontext.Builder = agentcontext.NewBuilderWithToolPolicies(toolRegistry)
// 注册内置工具的内容摘要器,使 micro-compact 在清理旧工具结果时保留关键上下文。
tools.RegisterBuiltinSummarizers(toolRegistry)
Comment thread
fennoai[bot] marked this conversation as resolved.
Comment thread
fennoai[bot] marked this conversation as resolved.

var contextBuilder agentcontext.Builder = agentcontext.NewBuilderWithToolPoliciesAndSummarizers(toolRegistry, toolRegistry)
var memoSvc *memo.Service
if cfg.Memo.Enabled {
memoStore := memo.NewFileStore(loader.BaseDir(), cfg.Workdir)
Expand All @@ -155,7 +158,7 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
if invalidator, ok := memoSource.(interface{ InvalidateCache() }); ok {
sourceInvl = invalidator.InvalidateCache
}
contextBuilder = agentcontext.NewBuilderWithMemo(toolRegistry, memoSource)
contextBuilder = agentcontext.NewBuilderWithMemoAndSummarizers(toolRegistry, toolRegistry, memoSource)
memoSvc = memo.NewService(memoStore, nil, cfg.Memo, sourceInvl)
toolRegistry.Register(memotool.NewRememberTool(memoSvc))
toolRegistry.Register(memotool.NewRecallTool(memoSvc))
Expand Down
85 changes: 49 additions & 36 deletions internal/context/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,28 @@ import (

// DefaultBuilder preserves the current runtime context-building behavior.
type DefaultBuilder struct {
promptSources []promptSectionSource
trimPolicy messageTrimPolicy
microCompactPolicies MicroCompactPolicySource
promptSources []promptSectionSource
trimPolicy messageTrimPolicy
microCompactPolicies MicroCompactPolicySource
microCompactSummarizers MicroCompactSummarizerSource
}

// NewBuilder returns the default context builder implementation.
func NewBuilder() Builder {
return NewBuilderWithToolPolicies(nil)
}

// NewBuilderWithToolPolicies 返回带工具 micro compact 策略源的默认上下文构建器。
func NewBuilderWithToolPolicies(policies MicroCompactPolicySource) Builder {
systemSource := &systemStateSource{gitRunner: runGitCommand}
// newDefaultBuilder 统一构建默认上下文构建器,避免多个构造函数重复装配相同依赖。
func newDefaultBuilder(
policies MicroCompactPolicySource,
summarizers MicroCompactSummarizerSource,
memoSource SectionSource,
) Builder {
return &DefaultBuilder{
promptSources: []promptSectionSource{
corePromptSource{},
&projectRulesSource{},
taskStateSource{},
todosSource{},
skillPromptSource{},
systemSource,
},
trimPolicy: spanMessageTrimPolicy{},
microCompactPolicies: policies,
promptSources: newPromptSources(memoSource),
trimPolicy: spanMessageTrimPolicy{},
microCompactPolicies: policies,
microCompactSummarizers: summarizers,
}
}

// NewBuilderWithMemo 返回带记忆注入能力的上下文构建器。
// memoSource 为 nil 时等价于 NewBuilderWithToolPolicies。
func NewBuilderWithMemo(policies MicroCompactPolicySource, memoSource SectionSource) Builder {
systemSource := &systemStateSource{gitRunner: runGitCommand}
// newPromptSources 组装系统提示词来源列表,并按约定将 memoSource 插入到 systemState 之前。
func newPromptSources(memoSource SectionSource) []promptSectionSource {
sources := []promptSectionSource{
corePromptSource{},
&projectRulesSource{},
Expand All @@ -50,12 +41,33 @@ func NewBuilderWithMemo(policies MicroCompactPolicySource, memoSource SectionSou
if memoSource != nil {
sources = append(sources, memoSource)
}
sources = append(sources, systemSource)
return &DefaultBuilder{
promptSources: sources,
trimPolicy: spanMessageTrimPolicy{},
microCompactPolicies: policies,
}
return append(sources, &systemStateSource{gitRunner: runGitCommand})
}

// NewBuilder returns the default context builder implementation.
func NewBuilder() Builder {
return NewBuilderWithToolPolicies(nil)
}

// NewBuilderWithToolPolicies 返回带工具 micro compact 策略源的默认上下文构建器。
func NewBuilderWithToolPolicies(policies MicroCompactPolicySource) Builder {
return newDefaultBuilder(policies, nil, nil)
}

// NewBuilderWithToolPoliciesAndSummarizers 返回带工具策略与内容摘要器的上下文构建器。
func NewBuilderWithToolPoliciesAndSummarizers(policies MicroCompactPolicySource, summarizers MicroCompactSummarizerSource) Builder {
return newDefaultBuilder(policies, summarizers, nil)
}

// NewBuilderWithMemo 返回带记忆注入能力的上下文构建器。
// memoSource 为 nil 时等价于 NewBuilderWithToolPolicies。
func NewBuilderWithMemo(policies MicroCompactPolicySource, memoSource SectionSource) Builder {
return NewBuilderWithMemoAndSummarizers(policies, nil, memoSource)
}

// NewBuilderWithMemoAndSummarizers 返回带记忆注入与内容摘要器的上下文构建器。
func NewBuilderWithMemoAndSummarizers(policies MicroCompactPolicySource, summarizers MicroCompactSummarizerSource, memoSource SectionSource) Builder {
return newDefaultBuilder(policies, summarizers, memoSource)
}

// Build assembles the provider-facing context for the current round.
Expand Down Expand Up @@ -83,7 +95,7 @@ func (b *DefaultBuilder) Build(ctx context.Context, input BuildInput) (BuildResu

return BuildResult{
SystemPrompt: composeSystemPrompt(sections...),
Messages: applyReadTimeContextProjection(trimPolicy.Trim(input.Messages, input.Compact), input.TaskState, input.Compact, b.microCompactPolicies),
Messages: applyReadTimeContextProjection(trimPolicy.Trim(input.Messages, input.Compact), input.TaskState, input.Compact, b.microCompactPolicies, b.microCompactSummarizers),
AutoCompactSuggested: shouldAutoCompact,
}, nil
}
Expand All @@ -94,12 +106,13 @@ func applyReadTimeContextProjection(
taskState agentsession.TaskState,
options CompactOptions,
policies MicroCompactPolicySource,
summarizers MicroCompactSummarizerSource,
) []providertypes.Message {
var projected []providertypes.Message
if options.DisableMicroCompact || !taskState.Established() {
projected = cloneContextMessages(messages)
return ProjectToolMessagesForModel(cloneContextMessages(messages))
} else {
projected = microCompactMessagesWithPolicies(messages, policies, options.MicroCompactRetainedToolSpans)
return ProjectToolMessagesForModel(
microCompactMessagesWithPolicies(messages, policies, options.MicroCompactRetainedToolSpans, summarizers),
)
}
return ProjectToolMessagesForModel(projected)
}
55 changes: 55 additions & 0 deletions internal/context/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,61 @@ func TestDefaultBuilderBuildAppliesMicroCompactAfterTrim(t *testing.T) {
}
}

func TestNewBuilderWithToolPoliciesAndSummarizers(t *testing.T) {
t.Parallel()

builder := NewBuilderWithToolPoliciesAndSummarizers(
nil,
stubMicroCompactSummarizerSource{
"filesystem_read_file": func(content string, metadata map[string]string, isError bool) string {
return "[summary] read_file"
},
},
)

messages := []providertypes.Message{
{Role: providertypes.RoleUser, Parts: []providertypes.ContentPart{providertypes.NewTextPart("older user")}},
{
Role: providertypes.RoleAssistant,
ToolCalls: []providertypes.ToolCall{
{ID: "call-1", Name: "filesystem_read_file", Arguments: "{}"},
},
},
{Role: providertypes.RoleTool, ToolCallID: "call-1", Parts: []providertypes.ContentPart{providertypes.NewTextPart("old read result")}},
{
Role: providertypes.RoleAssistant,
ToolCalls: []providertypes.ToolCall{
{ID: "call-2", Name: "bash", Arguments: "{}"},
},
},
{Role: providertypes.RoleTool, ToolCallID: "call-2", Parts: []providertypes.ContentPart{providertypes.NewTextPart("recent bash result")}},
{
Role: providertypes.RoleAssistant,
ToolCalls: []providertypes.ToolCall{
{ID: "call-3", Name: "webfetch", Arguments: "{}"},
},
},
{Role: providertypes.RoleTool, ToolCallID: "call-3", Parts: []providertypes.ContentPart{providertypes.NewTextPart("latest webfetch result")}},
{Role: providertypes.RoleUser, Parts: []providertypes.ContentPart{providertypes.NewTextPart("latest explicit instruction")}},
}

got, err := builder.Build(stdcontext.Background(), BuildInput{
Messages: messages,
TaskState: agentsession.TaskState{Goal: "keep implementing task"},
Metadata: testMetadata(t.TempDir()),
})
if err != nil {
t.Fatalf("Build() error = %v", err)
}
const summarizedMessageIndex = 2
if renderDisplayParts(got.Messages[summarizedMessageIndex].Parts) != "[summary] read_file" {
t.Fatalf(
"expected summarized older read result, got %q",
renderDisplayParts(got.Messages[summarizedMessageIndex].Parts),
)
}
}

func TestDefaultBuilderBuildSkipsMicroCompactWithoutEstablishedTaskState(t *testing.T) {
t.Parallel()

Expand Down
Loading
Loading