0Pricing
AI Agents · Lesson

Common Agent Loop Failures

Infinite loops, repeating the same tool call, and never reaching a final answer.

Common Agent Loop Failures is a free AI Agents lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Agent Loop and Its Failure Modes

An agent loop runs repeatedly: reason → call tool → observe result → reason again. This loop is powerful but fragile. Several well-known failure modes can trap an agent, waste tokens, and produce no useful output.

Understanding these failures is the first step to defending against them.

Failure 1: Infinite Loops

An infinite loop occurs when the agent calls the same tool repeatedly with the same arguments without making progress. This can happen when the tool returns an unhelpful result and the agent cannot reason its way out.

# Example of an agent in an infinite loop:
# Step 1: reasoning='Need to search for Python docs'
#         tool='search_web', args={'query': 'Python documentation'}
# Step 2: reasoning='Search result was unhelpful, try again'
#         tool='search_web', args={'query': 'Python documentation'}
# Step 3: reasoning='Search result was unhelpful, try again'
#         tool='search_web', args={'query': 'Python documentation'}
# ... repeats until max_iterations or token budget is exhausted

print('Symptom: same tool + same arguments appearing repeatedly in steps')
print('Fix: detect repeated (tool, args) pairs and break the loop')

Failure 2: Stuck State

A stuck state is a subtler version of an infinite loop. The agent keeps reasoning and calling different tools but cannot converge on a final answer. It oscillates between approaches without making progress.

# Example of a stuck agent:
# Step 1: tool='search_web', args={'query': 'topic A'}
# Step 2: tool='search_web', args={'query': 'topic B'}  # different args
# Step 3: tool='search_web', args={'query': 'topic A'}  # back to first
# Step 4: tool='read_document', args={'url': '...'}
# Step 5: tool='search_web', args={'query': 'topic A'}
# ... no FINAL_ANSWER ever produced

print('Symptom: agent takes many steps but never calls FINAL_ANSWER')
print('Fix: max_iterations guard + force final answer if limit is near')

Failure 3: Missing Final Answer

Some agents loop without ever deciding the task is complete. They collect information but never stop to synthesize and return it. This wastes tokens and time.

# An agent that never concludes:
def run_agent_bad(query: str, max_steps: int = 20) -> str:
    for step in range(max_steps):
        action = llm_decide_action(query, history)

        if action['type'] == 'tool':
            result = execute_tool(action)
            history.append(result)
        # BUG: No check for 'final_answer' type!
        # The agent loops until max_steps, returning None

    return None  # never actually returns an answer

# Fix: explicitly check for final_answer signal
def run_agent_good(query: str, max_steps: int = 20) -> str:
    for step in range(max_steps):
        action = llm_decide_action(query, history)
        if action['type'] == 'final_answer':
            return action['answer']  # exit cleanly
        execute_tool(action)
    return 'Reached step limit without a conclusion.'

Failure 4: Tool Call Parse Errors

When the LLM generates malformed JSON for a function call, the tool executor fails to parse it. A poorly written agent crashes or skips the step silently. A robust agent catches parse errors and feeds the error back to the LLM.

import json

def safe_parse_tool_call(arguments_str: str) -> dict:
    try:
        return json.loads(arguments_str)
    except json.JSONDecodeError as e:
        print(f'Failed to parse tool arguments: {e}')
        print(f'Raw: {arguments_str}')
        return None

def execute_step(tool_call) -> str:
    args = safe_parse_tool_call(tool_call.function.arguments)
    if args is None:
        # Feed the error back to the LLM in the next step
        return f'ERROR: Could not parse tool arguments. Raw: {tool_call.function.arguments}'
    return run_tool(tool_call.function.name, args)

Failure 5: Tool Returns No Useful Data

A tool might succeed technically (no exception) but return empty or useless data. The agent must handle this case, not assume every tool call returns actionable information.

def run_agent_with_empty_result_handling(query: str) -> str:
    for step in range(20):
        action = decide_next_action(query, history)

        if action['type'] == 'final_answer':
            return action['answer']

        result = execute_tool(action['tool'], action['args'])

        # Detect empty results and provide context
        if not result or result.strip() == '':
            observation = f'Tool {action["tool"]} returned no data. Try a different approach or different arguments.'
        elif 'error' in result.lower():
            observation = f'Tool error: {result}. Consider a different tool or query.'
        else:
            observation = result

        history.append({'tool': action['tool'], 'result': observation})

    return 'Could not complete task within step limit.'

Failure 6: Hallucinated Tool Names

LLMs sometimes generate tool names that do not exist. Always validate the tool name against your registered tools before attempting to call it. Return an informative error to the agent when this happens.

REGISTERED_TOOLS = {
    'search_web': search_web_function,
    'get_weather': get_weather_function,
    'calculate': calculate_function
}

def dispatch_tool(tool_name: str, args: dict) -> str:
    if tool_name not in REGISTERED_TOOLS:
        available = ', '.join(REGISTERED_TOOLS.keys())
        return (
            f'ERROR: Unknown tool "{tool_name}". '
            f'Available tools: {available}. '
            f'Please use one of the available tools.'
        )

    tool_fn = REGISTERED_TOOLS[tool_name]
    return tool_fn(**args)

Failure 7: Token Budget Exhaustion

A long-running agent that stores full tool results in its context can hit the LLM's context window limit. Summarize or truncate large tool results before adding them to the history.

def truncate_tool_result(result: str, max_chars: int = 2000) -> str:
    if len(result) <= max_chars:
        return result
    truncated = result[:max_chars]
    return f'{truncated}\n... [result truncated to {max_chars} chars]'

def add_observation_to_history(history: list, tool_name: str, result: str):
    safe_result = truncate_tool_result(result, max_chars=2000)
    history.append({
        'role': 'tool',
        'content': safe_result,
        'tool_name': tool_name
    })
    print(f'[Step] Tool={tool_name}, Result length={len(result)} (stored {len(safe_result)})')

if __name__ == '__main__':
    demo_history = []
    add_observation_to_history(demo_history, 'search_web', 'x' * 3000)

Detecting the Failure Mode Programmatically

Write a diagnostics function that analyzes the agent's step history to identify which failure mode occurred. This is invaluable during debugging.

def diagnose_agent_failure(steps: list) -> str:
    if not steps:
        return 'No steps recorded'

    # Check for infinite loop: same (tool, args) repeated
    seen = {}
    for s in steps:
        key = (s.get('tool'), str(s.get('args')))
        seen[key] = seen.get(key, 0) + 1
    repeated = {k: v for k, v in seen.items() if v > 2}
    if repeated:
        return f'INFINITE_LOOP: repeated actions: {repeated}'

    # Check for missing final answer
    has_answer = any(s.get('type') == 'final_answer' for s in steps)
    if not has_answer and len(steps) >= 15:
        return 'STUCK_STATE: many steps taken but no final answer'

    # Check for parse errors
    errors = [s for s in steps if 'ERROR' in str(s.get('result', ''))]
    if len(errors) > 2:
        return f'TOOL_ERROR: {len(errors)} tool errors in pipeline'

    return 'OK'

if __name__ == '__main__':
    demo_steps = [{'tool': 'search_web', 'args': {'q': 'weather'}} for _ in range(3)]
    print('Diagnosis:', diagnose_agent_failure(demo_steps))

Implementing a Simple Step Budget

Every production agent loop must have a hard step limit. This is the most important safety mechanism — it guarantees the loop terminates regardless of what the LLM decides.

def run_agent_with_budget(query: str, max_steps: int = 15) -> dict:
    history = []
    for step in range(1, max_steps + 1):
        print(f'[Step {step}/{max_steps}]')

        action = decide_next_action(query, history)

        if action['type'] == 'final_answer':
            return {
                'status': 'success',
                'answer': action['answer'],
                'steps_taken': step
            }

        result = execute_tool(action['tool'], action['args'])
        history.append({'step': step, 'tool': action['tool'], 'result': result})

        if step == max_steps - 1:
            # Warn the agent it must conclude
            history.append({'role': 'system',
                            'content': 'You must provide a FINAL_ANSWER on the next step.'})

    return {'status': 'timeout', 'answer': None, 'steps_taken': max_steps}

Quick Reference: Failure Modes and Fixes

A summary of the six agent loop failure modes and their fixes:

  • Infinite loop: detect repeated (tool, args) pairs; break with error feedback
  • Stuck state: max_iterations guard; force final answer near limit
  • Missing final answer: explicitly check for final_answer signal in the action
  • Parse errors: wrap JSON parsing in try/except; feed error back to LLM
  • Empty tool results: detect empty strings; provide 'no data' feedback
  • Hallucinated tool names: validate against registered tools; return error message

Knowledge Check: Agent Loop Failures

Test your understanding of common agent loop failure modes.

Recap: Common Agent Loop Failures

You can now identify and defend against the main agent loop failure modes:

  • Infinite loops, stuck states, and missing final answers all require a max iteration guard
  • Tool call parse errors need try/except around JSON parsing
  • Empty tool results need detection and informative feedback to the LLM
  • Hallucinated tool names need validation against the registered tool list
  • Token budget exhaustion needs result truncation

A robust agent loop anticipates all these failure modes and handles them gracefully.

Frequently asked questions

Is the “Common Agent Loop Failures” lesson free?

Yes — the full text of “Common Agent Loop Failures” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.

What will I learn in “Common Agent Loop Failures”?

Infinite loops, repeating the same tool call, and never reaching a final answer. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Agents?

No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Common Agent Loop Failures” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Agents lesson?

Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Common Agent Loop Failures
  2. Trace Logging for Agent Steps
  3. Detecting and Breaking Infinite Loops
  4. Step-Through Debugging Techniques
← Back to AI Agents