Spaces:
Sleeping
fix: multi-step agent execution — don't break on text-only when plan has pending steps
Browse filesROOT CAUSE: Agent loop breaks on ANY text-only response (no tool_calls).
Qwen3 pattern: TodoWrite → text 'now starting' → BREAK (never executes plan)
FIX 1: Smart continuation in agent.ts
- Check todoLists and planMode for pending items before breaking
- If pending work exists, inject continuation prompt and loop again
- Allow up to 2 text-only responses before force-breaking
- Only break immediately if there's no pending work
FIX 2: MULTI-STEP EXECUTION RULES in system prompt
- Explicit instructions: 'You are an AGENT, not a chatbot'
- 'After TodoWrite, MUST immediately start executing with tools'
- 'Text-only responses with pending tasks are a BUG'
- Correct pattern: TodoWrite → bash → bash → SendUserMessage
- Wrong pattern: TodoWrite → text → STOPS
FIX 3: Import todoLists in agent.ts for pending task detection
- server/runtime/agent.ts +28 -7
- server/runtime/system-prompt.ts +10 -1
|
@@ -5,7 +5,7 @@
|
|
| 5 |
|
| 6 |
import { ENV } from "../_core/env";
|
| 7 |
import { buildSystemPrompt, TOOL_DEFINITIONS } from "./system-prompt";
|
| 8 |
-
import { executeTool, getPlanMode, runPreToolHooks, runPostToolHooks, initializeMcpFromConfig, getMcpManager } from "../tools/executor";
|
| 9 |
import { compactSession, compactSessionWithLLM, shouldCompact, estimateSessionTokens, dbMessagesToSession, DEFAULT_COMPACTION_CONFIG } from "./compact";
|
| 10 |
import type { Session, ConversationMessage as CompactMessage, CompactionConfig } from "./compact";
|
| 11 |
import { UsageTracker, pricingForModel, defaultSonnetTierPricing, estimateCostUsdWithPricing, totalCostUsd, formatUsd, summaryLinesForModel } from "./usage";
|
|
@@ -745,14 +745,35 @@ export async function runAgentLoop(
|
|
| 745 |
conversationMessages.push(assistantMessage);
|
| 746 |
assistantMessages.push(assistantMessage);
|
| 747 |
|
| 748 |
-
// If no tool calls,
|
| 749 |
if (!result.toolCalls || result.toolCalls.length === 0) {
|
| 750 |
-
// ─── Text loop detection ───
|
| 751 |
-
// If model generates text-only responses multiple times in a row without
|
| 752 |
-
// tool calls, it's stuck in a self-talk loop. Break after 2 consecutive
|
| 753 |
-
// text-only responses (normal flow: 1 text response = done).
|
| 754 |
consecutiveTextOnly++;
|
| 755 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
console.warn(`[agent] Text-only loop detected: ${consecutiveTextOnly} consecutive text responses without tool calls — breaking`);
|
| 757 |
sendSSE(res, "error", {
|
| 758 |
message: `⚠️ модель зациклилась в текстовых ответах. попробуй переформулировать запрос`,
|
|
|
|
| 5 |
|
| 6 |
import { ENV } from "../_core/env";
|
| 7 |
import { buildSystemPrompt, TOOL_DEFINITIONS } from "./system-prompt";
|
| 8 |
+
import { executeTool, getPlanMode, runPreToolHooks, runPostToolHooks, initializeMcpFromConfig, getMcpManager, todoLists } from "../tools/executor";
|
| 9 |
import { compactSession, compactSessionWithLLM, shouldCompact, estimateSessionTokens, dbMessagesToSession, DEFAULT_COMPACTION_CONFIG } from "./compact";
|
| 10 |
import type { Session, ConversationMessage as CompactMessage, CompactionConfig } from "./compact";
|
| 11 |
import { UsageTracker, pricingForModel, defaultSonnetTierPricing, estimateCostUsdWithPricing, totalCostUsd, formatUsd, summaryLinesForModel } from "./usage";
|
|
|
|
| 745 |
conversationMessages.push(assistantMessage);
|
| 746 |
assistantMessages.push(assistantMessage);
|
| 747 |
|
| 748 |
+
// If no tool calls, check if the agent should continue or stop
|
| 749 |
if (!result.toolCalls || result.toolCalls.length === 0) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 750 |
consecutiveTextOnly++;
|
| 751 |
+
|
| 752 |
+
// ─── Multi-step continuation logic ───────────────────────────
|
| 753 |
+
// Don't break on the first text-only response if there's work to do.
|
| 754 |
+
// The model often generates an intermediate text like "plan created,
|
| 755 |
+
// now starting execution" between planning and doing.
|
| 756 |
+
const currentTodos = todoLists.get(sessionId) || [];
|
| 757 |
+
const hasPendingTodos = currentTodos.some(t => t.status === "pending" || t.status === "in_progress");
|
| 758 |
+
const currentPlan = getPlanMode(sessionId);
|
| 759 |
+
const hasPendingPlan = currentPlan.active && currentPlan.steps.some(
|
| 760 |
+
(s: any) => s.status === "pending" || s.status === "in_progress"
|
| 761 |
+
);
|
| 762 |
+
const hasWorkToDo = hasPendingTodos || hasPendingPlan;
|
| 763 |
+
|
| 764 |
+
if (hasWorkToDo && consecutiveTextOnly <= 2) {
|
| 765 |
+
// Model said something but there are pending tasks — inject a nudge
|
| 766 |
+
// to continue executing, then loop again
|
| 767 |
+
console.info(`[agent] Text-only response but ${hasPendingTodos ? 'pending todos' : 'active plan'} exist — injecting continuation prompt (attempt ${consecutiveTextOnly})`);
|
| 768 |
+
conversationMessages.push({
|
| 769 |
+
role: "user",
|
| 770 |
+
content: "[SYSTEM: You have pending tasks in your plan. Continue executing the next step now using the appropriate tool. Do NOT just describe what you will do — actually DO it by calling tools.]" as string,
|
| 771 |
+
});
|
| 772 |
+
continue; // Don't break — go back to the top of the loop
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
// No pending work, or too many text-only responses — stop
|
| 776 |
+
if (consecutiveTextOnly >= 3) {
|
| 777 |
console.warn(`[agent] Text-only loop detected: ${consecutiveTextOnly} consecutive text responses without tool calls — breaking`);
|
| 778 |
sendSSE(res, "error", {
|
| 779 |
message: `⚠️ модель зациклилась в текстовых ответах. попробуй переформулировать запрос`,
|
|
@@ -304,7 +304,16 @@ CRITICAL ERROR HANDLING RULES:
|
|
| 304 |
- If the SAME tool fails 2 times in a row (even with different arguments), STOP retrying that tool. Explain the error to the user and ask what they want to do.
|
| 305 |
- NEVER enter a loop of: tool_call → error → slightly different tool_call → same error. If you see this pattern, STOP immediately.
|
| 306 |
- When you encounter an error, your FIRST response should be to explain the error to the user in plain language, NOT to silently retry.
|
| 307 |
-
- Retrying is only appropriate for transient errors like timeouts or network issues, NOT for logical errors like wrong paths or missing files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
);
|
| 309 |
|
| 310 |
|
|
|
|
| 304 |
- If the SAME tool fails 2 times in a row (even with different arguments), STOP retrying that tool. Explain the error to the user and ask what they want to do.
|
| 305 |
- NEVER enter a loop of: tool_call → error → slightly different tool_call → same error. If you see this pattern, STOP immediately.
|
| 306 |
- When you encounter an error, your FIRST response should be to explain the error to the user in plain language, NOT to silently retry.
|
| 307 |
+
- Retrying is only appropriate for transient errors like timeouts or network issues, NOT for logical errors like wrong paths or missing files.
|
| 308 |
+
|
| 309 |
+
MULTI-STEP EXECUTION RULES:
|
| 310 |
+
- You are an AGENT, not a chatbot. After creating a plan with TodoWrite, you MUST immediately start executing the plan steps by calling tools.
|
| 311 |
+
- NEVER stop after creating a plan. The plan is not the deliverable — the EXECUTED result is.
|
| 312 |
+
- After each tool call result, proceed to the next step. Do NOT generate text-only responses between steps unless you need to explain something to the user.
|
| 313 |
+
- If you have pending tasks in your TodoWrite list, ALWAYS call a tool in your next response. Text-only responses with pending tasks are a BUG.
|
| 314 |
+
- Pattern: TodoWrite (create plan) → bash/read_file/write_file (execute step 1) → bash (execute step 2) → ... → SendUserMessage (report results)
|
| 315 |
+
- WRONG pattern: TodoWrite → "now I will start" (text only, no tool) → STOPS. This is broken. Always include a tool call.
|
| 316 |
+
- When you finish a step, update the todo status to "completed" and immediately proceed to the next pending step.`
|
| 317 |
);
|
| 318 |
|
| 319 |
|