Skip to main content

Module 9 — Debugging a crew that does not converge

Most first crews behave like this: the first two runs work beautifully, then one Tuesday a run takes twelve minutes, produces nothing, and drains fifty dollars of API credit. This module is about that Tuesday. It walks the three failure patterns that account for almost every stuck crew you will see — the delegation loop, the vague expected output, and the tool that returns garbage — and gives a concrete fix for each.

Turn on the trace before you need it

Every debugging story starts with verbose=True on the agents and on the crew. Without it, you get a final result (or a timeout) and no visibility into how the agents got there. Turn it on in development, keep it on in staging, turn it off only in production — and even then, log the structured trace to a callback.

A healthy trace has a rhythm. Task starts → agent thinks (one paragraph) → agent acts (tool call with arguments) → observation (tool result, short) → agent thinks again → final answer. Six to eight of these blocks per agent, then the next task starts. When the rhythm breaks, the failure mode is usually one of three.

Pattern 1: the delegation loop

Symptom in the trace. The Manager delegates to the Writer. The Writer's first thought is "I need clarification on X" and it delegates back to the Manager. The Manager delegates back to the Writer with a rephrased brief. Repeat until max_iter.

Why it happens. Either two agents have allow_delegation=True when only one should (module 6), or the delegated brief is too open ("please rewrite this section") so the delegatee starts a full agent loop that itself considers delegating for clarification.

Fix. Two changes, small and precise.

  • Set allow_delegation=False on every non-Manager agent, no exceptions.
  • Rewrite the Manager's delegation brief to be an assignment, not a request: "Rewrite section 5 removing marketing verbs. Keep the feature list exactly as it is. Return only the rewritten section." A one-sentence assignment triggers a one-turn response.

After the fix, the trace shows a single Manager → Writer → Manager sequence and moves on. The whole loop episode disappears.

Pattern 2: the vague expected_output

Symptom in the trace. An agent runs, then re-runs itself with a slight variation, then a third time, each time producing something that "sounds fine" but is not quite what the next task can consume. Eventually the next task fails to parse the output and CrewAI retries the previous task, adding four more turns.

Why it happens. The expected_output field says something like "a well-written summary of the features". Both agent and CrewAI have no way to tell whether the output is correct, so they iterate towards something that "feels" done — which is exactly the failure mode a testable expected output is designed to prevent.

Fix. Rewrite the expected_output in the language of a checklist:

Before: "A well-written summary of the features."
After: "A JSON array of {name, description, source_line}, one entry per
feature listed in the previous task's output. Every source_line
must exist in the brief. No extra keys, no prose outside the JSON."

Add output_pydantic (module 3) to enforce it structurally. The trace collapses from twelve turns of variation to two turns of validated JSON.

Pattern 3: the tool that returns garbage

Symptom in the trace. An agent calls a tool. The observation is a huge unstructured blob (a stack trace, a full HTML page, a pandas dataframe repr). The agent's next thought tries to parse it, calls the tool again with different arguments, gets a different blob, and the run derails.

Why it happens. A custom tool's _run returns whatever the underlying library gave it — often the raw exception on error, or a full-page HTML dump on a web tool that should have returned a paragraph. The model does its best to make sense of ten thousand tokens of noise and often fails.

Fix. Two rules on tool return values, applied to every custom tool:

  • On success, return a compact string or JSON — no raw HTML, no full stack traces, no dataframe reprs. Truncate to a few hundred tokens.
  • On error, return a short human-readable string that starts with Error: and states the reason. Do not raise — a raise crashes the agent turn; a returned error message lets the model recover.

For the web search tool, that means "first three results as {title, snippet, url}" and not "raw JSON of the search API". For the file reader, that means "first 800 lines" and not "read the 60 000-line log file". A well-behaved tool return converts three failing turns into one.

The three-question triage

When a crew hangs, ask these three questions in order. The first one that has a "no" is the fix.

  1. Is verbose=True on? If no, turn it on and rerun once. Without the trace you are guessing.
  2. Is there exactly one agent with allow_delegation=True? If no, fix it. This is pattern 1.
  3. Are all expected_output fields testable (schema, checklist, or shape)? If no, tighten the one that fails first. This is pattern 2.

Ninety percent of the time, the fix is one of these three. The remaining ten percent are pattern 3 (a badly-behaved tool) or a genuine bug in the task description that a human review catches in one reading.

Two habits that pay off

  • Overfit a tiny brief first. A five-line brief that produces a two-page draft should run in under a minute and cost pennies. If the crew cannot succeed on the tiny brief, it will not succeed on the real one, and the failure is cheaper to iterate on.
  • Diff two runs on the same brief. When behaviour changes between yesterday and today, the diff of the two traces (task by task, thought by thought) tells you which agent's behaviour drifted. Very often the drift is caused by a memory hit that was not there yesterday — turn memory off, rerun, and confirm.
A max_iter hit is a symptom, not a bug

When you see "Agent reached max_iter" in the trace, the fix is almost never to raise max_iter. That only lets the loop run longer. The fix is upstream — a bad brief, a leaky delegation, a tool returning noise. Raising the cap hides the diagnosis without changing the disease.

Summary

  • Every debugging session starts with verbose=True; a rhythm of thought → action → observation is healthy, a broken rhythm names the failure pattern.
  • Delegation loops come from more than one agent with allow_delegation=True or from open-ended delegation briefs — set delegation on the Manager only and write briefs as assignments.
  • Vague expected outputs cause self-retries and consumer parse failures — rewrite them as checklists and back them with output_pydantic.
  • Custom tools that return raw blobs derail agents; return compact JSON on success and a short Error: string on failure.

Next module: assembling everything into the final documentation crew, comparing its output to a single agent and to a human, and shipping.