mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(subturn): support stateful iteration for evaluator-optimizer pattern
Add ActualSystemPrompt and InitialMessages fields to SubTurnConfig to enable stateful worker context passing across multiple evaluation iterations. Changes: - Add ActualSystemPrompt field to separate system role from user task description - Add InitialMessages field to preload ephemeral session history before agent loop starts - Add Messages field to ToolResult for carrying session history (internal use, not serialized) - Update runTurn to inject system prompt and preload history from InitialMessages - Update AgentLoopSpawner to map new fields from tools.SubTurnConfig to agent.SubTurnConfig This enables the evaluator-optimizer execution strategy in team tool to maintain worker context across iterations while keeping SubTurn isolation intact.
This commit is contained in:
@@ -64,6 +64,7 @@ type processOptions struct {
|
||||
SenderID string // Current sender ID for dynamic context
|
||||
SenderDisplayName string // Current sender display name for dynamic context
|
||||
UserMessage string // User message content (may include prefix)
|
||||
SystemPromptOverride string // Override the default system prompt (Used by SubTurns)
|
||||
Media []string // media:// refs from inbound message
|
||||
DefaultResponse string // Response when LLM returns empty
|
||||
EnableSummary bool // Whether to trigger summarization
|
||||
@@ -1069,6 +1070,17 @@ func (al *AgentLoop) runAgentLoop(
|
||||
maxMediaSize := cfg.Agents.Defaults.GetMaxMediaSize()
|
||||
messages = resolveMediaRefs(messages, al.mediaStore, maxMediaSize)
|
||||
|
||||
// 1.5 Override the System prompt (e.g., for Evaluator/Optimizer specific personas)
|
||||
if opts.SystemPromptOverride != "" {
|
||||
for i, msg := range messages {
|
||||
if msg.Role == "system" {
|
||||
messages[i].Content = opts.SystemPromptOverride
|
||||
messages[i].SystemParts = []providers.ContentBlock{{Type: "text", Text: opts.SystemPromptOverride}}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Save user message to session
|
||||
if !opts.SkipAddUserMessage && opts.UserMessage != "" {
|
||||
agent.Sessions.AddMessage(opts.SessionKey, "user", opts.UserMessage)
|
||||
|
||||
+43
-17
@@ -119,6 +119,14 @@ type SubTurnConfig struct {
|
||||
// truncated while preserving system messages and recent context.
|
||||
MaxContextRunes int
|
||||
|
||||
// ActualSystemPrompt is injected as the true 'system' role message for the childAgent.
|
||||
// The legacy SystemPrompt field is actually used as the first 'user' message (task description).
|
||||
ActualSystemPrompt string
|
||||
|
||||
// InitialMessages preloads the ephemeral session history before the agent loop starts.
|
||||
// Used by evaluator-optimizer patterns to pass the full worker context across multiple iterations.
|
||||
InitialMessages []providers.Message
|
||||
|
||||
// Can be extended with temperature, topP, etc.
|
||||
}
|
||||
|
||||
@@ -186,14 +194,16 @@ func (s *AgentLoopSpawner) SpawnSubTurn(ctx context.Context, cfg tools.SubTurnCo
|
||||
|
||||
// Convert tools.SubTurnConfig to agent.SubTurnConfig
|
||||
agentCfg := SubTurnConfig{
|
||||
Model: cfg.Model,
|
||||
Tools: cfg.Tools,
|
||||
SystemPrompt: cfg.SystemPrompt,
|
||||
MaxTokens: cfg.MaxTokens,
|
||||
Async: cfg.Async,
|
||||
Critical: cfg.Critical,
|
||||
Timeout: cfg.Timeout,
|
||||
MaxContextRunes: cfg.MaxContextRunes,
|
||||
Model: cfg.Model,
|
||||
Tools: cfg.Tools,
|
||||
SystemPrompt: cfg.SystemPrompt,
|
||||
ActualSystemPrompt: cfg.ActualSystemPrompt,
|
||||
InitialMessages: cfg.InitialMessages,
|
||||
MaxTokens: cfg.MaxTokens,
|
||||
Async: cfg.Async,
|
||||
Critical: cfg.Critical,
|
||||
Timeout: cfg.Timeout,
|
||||
MaxContextRunes: cfg.MaxContextRunes,
|
||||
}
|
||||
|
||||
return spawnSubTurn(ctx, s.al, parentTS, agentCfg)
|
||||
@@ -481,6 +491,19 @@ func runTurn(ctx context.Context, al *AgentLoop, ts *turnState, cfg SubTurnConfi
|
||||
childAgent.MaxTokens = parentAgent.MaxTokens
|
||||
}
|
||||
|
||||
if cfg.ActualSystemPrompt != "" {
|
||||
childAgent.Sessions.AddMessage(ts.turnID, "system", cfg.ActualSystemPrompt)
|
||||
}
|
||||
|
||||
promptAlreadyAdded := false
|
||||
|
||||
// Preload ephemeral session history
|
||||
if len(cfg.InitialMessages) > 0 {
|
||||
existing := childAgent.Sessions.GetHistory(ts.turnID)
|
||||
childAgent.Sessions.SetHistory(ts.turnID, append(existing, cfg.InitialMessages...))
|
||||
promptAlreadyAdded = true // InitialMessages 中已含 user 消息,跳过再次添加
|
||||
}
|
||||
|
||||
// Resolve MaxContextRunes configuration
|
||||
maxContextRunes := utils.ResolveMaxContextRunes(cfg.MaxContextRunes, childAgent.ContextWindow)
|
||||
|
||||
@@ -501,7 +524,6 @@ func runTurn(ctx context.Context, al *AgentLoop, ts *turnState, cfg SubTurnConfi
|
||||
truncationRetryCount := 0
|
||||
contextRetryCount := 0
|
||||
currentPrompt := cfg.SystemPrompt
|
||||
promptAlreadyAdded := false
|
||||
|
||||
for {
|
||||
// Soft context limit: check and truncate before LLM call
|
||||
@@ -535,12 +557,13 @@ func runTurn(ctx context.Context, al *AgentLoop, ts *turnState, cfg SubTurnConfi
|
||||
|
||||
// Call the agent loop
|
||||
finalContent, err := al.runAgentLoop(ctx, childAgent, processOptions{
|
||||
SessionKey: ts.turnID,
|
||||
UserMessage: currentPrompt,
|
||||
DefaultResponse: "",
|
||||
EnableSummary: false,
|
||||
SendResponse: false,
|
||||
SkipAddUserMessage: promptAlreadyAdded,
|
||||
SessionKey: ts.turnID,
|
||||
UserMessage: currentPrompt,
|
||||
SystemPromptOverride: cfg.ActualSystemPrompt,
|
||||
DefaultResponse: "",
|
||||
EnableSummary: false,
|
||||
SendResponse: false,
|
||||
SkipAddUserMessage: promptAlreadyAdded,
|
||||
})
|
||||
|
||||
// Mark the prompt as added so subsequent truncation retries
|
||||
@@ -600,8 +623,11 @@ func runTurn(ctx context.Context, al *AgentLoop, ts *turnState, cfg SubTurnConfi
|
||||
continue // Retry with recovery prompt
|
||||
}
|
||||
|
||||
// 3. Success - return result
|
||||
return &tools.ToolResult{ForLLM: finalContent}, nil
|
||||
// 3. Success - return result with session history
|
||||
return &tools.ToolResult{
|
||||
ForLLM: finalContent,
|
||||
Messages: childAgent.Sessions.GetHistory(ts.turnID),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user