feat: inject plan-mode reminders during tool-call iterations
Add buildPlanReminder to emit interview/review workflow reminders each iteration (after the first), preventing the LLM from losing focus during tool-call loops. Remove the hard interviewMaxIter cap in favour of the softer reminder approach. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
adb9fa5c1b
commit
3f285b0d4b
2 changed files with 63 additions and 8 deletions
|
|
@ -666,21 +666,32 @@ func buildTaskReminder(userMessage string, lastBlocker string) providers.Message
|
|||
}
|
||||
}
|
||||
|
||||
// buildPlanReminder returns a reminder message for plan pre-execution states
|
||||
// (interviewing / review) to keep the AI focused on the interview workflow
|
||||
// during tool-call iterations.
|
||||
func buildPlanReminder(planStatus string) (providers.Message, bool) {
|
||||
var content string
|
||||
switch planStatus {
|
||||
case "interviewing":
|
||||
content = "[System] You are interviewing the user to build a plan. " +
|
||||
"Ask clarifying questions and save findings to ## Context in memory/MEMORY.md using edit_file. " +
|
||||
"When you have enough information, write ## Phases and ## Commands sections."
|
||||
case "review":
|
||||
content = "[System] The plan is under review. " +
|
||||
"Wait for the user to approve or request changes. Do not proceed with execution."
|
||||
default:
|
||||
return providers.Message{}, false
|
||||
}
|
||||
return providers.Message{Role: "user", Content: content}, true
|
||||
}
|
||||
|
||||
// runLLMIteration executes the LLM call loop with tool handling.
|
||||
func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance, messages []providers.Message, opts processOptions) (string, int, error) {
|
||||
iteration := 0
|
||||
var finalContent string
|
||||
lastReminderIdx := -1
|
||||
|
||||
// During pre-execution plan modes (interviewing/review), cap iterations
|
||||
// to prevent runaway tool loops.
|
||||
maxIter := agent.MaxIterations
|
||||
if isPlanPreExecution(agent.ContextBuilder.GetPlanStatus()) {
|
||||
const interviewMaxIter = 3
|
||||
if maxIter > interviewMaxIter {
|
||||
maxIter = interviewMaxIter
|
||||
}
|
||||
}
|
||||
|
||||
for iteration < maxIter {
|
||||
iteration++
|
||||
|
|
@ -953,6 +964,19 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance,
|
|||
"has_blocker": lastBlocker != "",
|
||||
})
|
||||
}
|
||||
|
||||
// Inject plan-mode reminder to keep AI focused on interview/review workflow.
|
||||
if iteration > 1 && isPlanPreExecution(agent.ContextBuilder.GetPlanStatus()) {
|
||||
if reminder, ok := buildPlanReminder(agent.ContextBuilder.GetPlanStatus()); ok {
|
||||
messages = append(messages, reminder)
|
||||
logger.DebugCF("agent", "Injected plan reminder",
|
||||
map[string]interface{}{
|
||||
"agent_id": agent.ID,
|
||||
"iteration": iteration,
|
||||
"plan_status": agent.ContextBuilder.GetPlanStatus(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If max iterations exhausted with tool calls still pending,
|
||||
|
|
|
|||
|
|
@ -876,6 +876,37 @@ func TestBuildTaskReminder_Truncation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildPlanReminder(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
status string
|
||||
wantOK bool
|
||||
wantSubstr string
|
||||
}{
|
||||
{"interviewing", "interviewing", true, "interviewing the user"},
|
||||
{"review", "review", true, "under review"},
|
||||
{"executing returns false", "executing", false, ""},
|
||||
{"empty returns false", "", false, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
msg, ok := buildPlanReminder(tt.status)
|
||||
if ok != tt.wantOK {
|
||||
t.Fatalf("buildPlanReminder(%q) ok = %v, want %v", tt.status, ok, tt.wantOK)
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if msg.Role != "user" {
|
||||
t.Errorf("expected role 'user', got %q", msg.Role)
|
||||
}
|
||||
if !strings.Contains(msg.Content, tt.wantSubstr) {
|
||||
t.Errorf("expected content to contain %q, got %q", tt.wantSubstr, msg.Content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- /plan command tests ----------
|
||||
|
||||
func newTestAgentLoop(t *testing.T) (*AgentLoop, func()) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue