mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 06:17:41 +00:00
fix(agent): set AgentUUID in memoryflush SystemPromptConfig
memoryflush.go constructed SystemPromptConfig without AgentUUID, while loop_history.go set both AgentID (agent_key) and AgentUUID. buildRuntimeSection renders below the cache boundary so the omission did not bust the prompt cache — but the runtime identity line dropped the UUID during compaction flush turns. Extract a pure helper buildMemoryFlushPromptConfig so tests can assert the config shape without a full Loop fixture (M7 mitigation from red-team review). Phase 1 Fix A of agent identity hardening.
This commit is contained in:
1 parent
de2bc201fa
commit
89862981e6
2 files changed
+105
-10
No files matched your search
@@ -67,6 +67,28 @@ func ResolveMemoryFlushSettings(compaction *config.CompactionConfig) *MemoryFlus
|
||||
return settings
|
||||
}
|
||||
|
||||
// buildMemoryFlushPromptConfig returns the SystemPromptConfig used by the
|
||||
// memory flush turn. Extracted as a pure function so tests can assert the
|
||||
// config shape (specifically, that AgentUUID is populated) without building
|
||||
// a full Loop fixture. See Phase 1 Fix A and M7 mitigation.
|
||||
func buildMemoryFlushPromptConfig(
|
||||
agentID, agentUUID, model, workspace string,
|
||||
toolNames []string,
|
||||
hasMemory bool,
|
||||
providerType string,
|
||||
) SystemPromptConfig {
|
||||
return SystemPromptConfig{
|
||||
AgentID: agentID,
|
||||
AgentUUID: agentUUID,
|
||||
Model: model,
|
||||
Workspace: workspace,
|
||||
Mode: PromptMinimal,
|
||||
ToolNames: toolNames,
|
||||
HasMemory: hasMemory,
|
||||
ProviderType: providerType,
|
||||
}
|
||||
}
|
||||
|
||||
// shouldRunMemoryFlush checks whether a memory flush should run before compaction.
|
||||
// Flush always runs when compaction triggers (called inside maybeSummarize),
|
||||
// gated only by enabled/memory checks and a dedup guard per compaction cycle.
|
||||
@@ -108,16 +130,19 @@ func (l *Loop) runMemoryFlush(ctx context.Context, sessionKey string, settings *
|
||||
flushPrompt := strings.ReplaceAll(settings.Prompt, "YYYY-MM-DD", today)
|
||||
flushSystemPrompt := strings.ReplaceAll(settings.SystemPrompt, "YYYY-MM-DD", today)
|
||||
|
||||
// System prompt: combine agent's normal system prompt context with flush system prompt
|
||||
systemPrompt := BuildSystemPrompt(SystemPromptConfig{
|
||||
AgentID: l.id,
|
||||
Model: l.model,
|
||||
Workspace: l.workspace,
|
||||
Mode: PromptMinimal,
|
||||
ToolNames: l.filteredToolNames(),
|
||||
HasMemory: l.hasMemory,
|
||||
ProviderType: providerTypeOf(l.provider),
|
||||
})
|
||||
// System prompt: combine agent's normal system prompt context with flush system prompt.
|
||||
// Config construction extracted to buildMemoryFlushPromptConfig for testability — the
|
||||
// AgentUUID field here mirrors loop_history.go:199-201 and must stay in sync
|
||||
// (regression: missing AgentUUID caused identity drift in DomainEvents — see PR #826).
|
||||
systemPrompt := BuildSystemPrompt(buildMemoryFlushPromptConfig(
|
||||
l.id,
|
||||
l.agentUUID.String(),
|
||||
l.model,
|
||||
l.workspace,
|
||||
l.filteredToolNames(),
|
||||
l.hasMemory,
|
||||
providerTypeOf(l.provider),
|
||||
))
|
||||
systemPrompt += "\n\n" + flushSystemPrompt
|
||||
|
||||
messages = append(messages, providers.Message{
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TestBuildMemoryFlushPromptConfig_AgentUUIDPopulated asserts Fix A — the
|
||||
// SystemPromptConfig returned by buildMemoryFlushPromptConfig carries the
|
||||
// AgentUUID. Historically missing; loop_history.go set it but memoryflush.go
|
||||
// did not, leading to identity drift (see PR #826 / brainstorm trap zone 2).
|
||||
func TestBuildMemoryFlushPromptConfig_AgentUUIDPopulated(t *testing.T) {
|
||||
u := uuid.New()
|
||||
cfg := buildMemoryFlushPromptConfig(
|
||||
"test-agent",
|
||||
u.String(),
|
||||
"claude-opus",
|
||||
"/workspace",
|
||||
[]string{"read_file", "write_file"},
|
||||
true,
|
||||
"anthropic",
|
||||
)
|
||||
|
||||
if cfg.AgentUUID != u.String() {
|
||||
t.Errorf("AgentUUID = %q, want %q", cfg.AgentUUID, u.String())
|
||||
}
|
||||
if cfg.AgentID != "test-agent" {
|
||||
t.Errorf("AgentID = %q, want %q", cfg.AgentID, "test-agent")
|
||||
}
|
||||
if cfg.Model != "claude-opus" {
|
||||
t.Errorf("Model = %q, want %q", cfg.Model, "claude-opus")
|
||||
}
|
||||
if cfg.Workspace != "/workspace" {
|
||||
t.Errorf("Workspace = %q, want %q", cfg.Workspace, "/workspace")
|
||||
}
|
||||
if cfg.Mode != PromptMinimal {
|
||||
t.Errorf("Mode = %q, want %q", cfg.Mode, PromptMinimal)
|
||||
}
|
||||
if !cfg.HasMemory {
|
||||
t.Error("HasMemory should be true")
|
||||
}
|
||||
if cfg.ProviderType != "anthropic" {
|
||||
t.Errorf("ProviderType = %q, want %q", cfg.ProviderType, "anthropic")
|
||||
}
|
||||
if len(cfg.ToolNames) != 2 {
|
||||
t.Errorf("ToolNames len = %d, want 2", len(cfg.ToolNames))
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildMemoryFlushPromptConfig_EmptyAgentUUID guards against a regression where
|
||||
// a Loop with uuid.Nil (zero value) would still produce a non-empty AgentUUID string.
|
||||
// The .String() of uuid.Nil is "00000000-0000-0000-0000-000000000000" — not empty,
|
||||
// but it MUST be identifiable downstream so the publish-time observer in eventbus
|
||||
// can still warn on it after Fix C wires validateAgentID into bus.Publish.
|
||||
func TestBuildMemoryFlushPromptConfig_ZeroUUIDStringified(t *testing.T) {
|
||||
cfg := buildMemoryFlushPromptConfig(
|
||||
"zero-agent",
|
||||
uuid.Nil.String(),
|
||||
"m",
|
||||
"/w",
|
||||
nil,
|
||||
false,
|
||||
"p",
|
||||
)
|
||||
|
||||
if cfg.AgentUUID != uuid.Nil.String() {
|
||||
t.Errorf("AgentUUID = %q, want %q", cfg.AgentUUID, uuid.Nil.String())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user