Errors and Fallbacks

By the Syncfusion DevTutorials team · Last updated

How an agent notices tool errors, bad output and runtime faults, recovers with retries and other actions, and falls back to partial answers or escalation instead of returning nothing.

Think about a delivery driver with a parcel to drop off.

The van won't start. The driver tries the key again. Sometimes that is all it takes.

If it still won't start, the driver doesn't give up. They borrow a bike. If the address is closed, they leave the parcel at the shop next door and put a note in the letterbox explaining where it is.

If none of that works, they call the office and explain exactly what happened. What a good driver never does is drive home and say nothing.

An agent should handle problems in the same way.

The main idea: when something goes wrong, an agent should notice it, identify it, try to recover, and check whether the recovery worked. If recovery fails, it should fall back to a smaller but honest result. It should never return nothing.

A Real-World Example

The Brightgrid agent runs the tests. Instead of a test result, it receives: ERROR: the run_tests runner crashed.

A weak agent might treat that as a test failure and start changing code. Or it might stop immediately.

A strong agent recognizes that this is a tool failure, not a test result. It might be temporary, so it tries again.

If the second attempt works, the agent continues normally. If the test runner keeps crashing, the agent gives an honest partial answer: "I changed web/format.js, but I could not run the tests. Please run them before you merge."

In simple words: notice, retry, try something else, and if all else fails, clearly explain what you could and could not do.

What You Will Learn

In this lesson, you will learn:

  1. The three kinds of fault: tool errors, invalid outputs, and runtime failures.
  2. How to recover from failures using retries, parameter fixes, and alternative actions.
  3. How to verify that a recovery actually worked.
  4. How fallback chains provide alternatives when recovery fails.
  5. How graceful degradation delivers a smaller but safe result and escalates when necessary.

The Agent Lab injects faults into the run_tests tool in this lesson. The Loop tab shows which fault is active. Every run uses the same agent, with retries and a fallback chain enabled.

The fallback chain. Each option is tried only if the previous one fails, and every path ends with an answer.

Error Detection

The first job is to notice a fault and identify its type. The type determines the next action.

Tool Errors

A tool error means the tool itself failed. It crashed or returned an error. The result starts with ERROR.

Invalid Outputs

An invalid output means the tool reported success, but the output is wrong. It may be empty, incomplete, or in the wrong format. You saw output validation in Lesson 11.

Runtime Failures

A runtime failure comes from the environment around the tool. The network is slow, the call timed out, or the machine ran out of memory. These faults are often temporary.

Name the kind of fault

function faultKind(result) {
  if (/crash/i.test(result)) return "tool error";
  if (/timed out/.test(result)) return "runtime fault";
  if (!/\d+ passed, \d+ failed/.test(result)) return "bad output";
  return "no fault";
}

console.log(faultKind("ERROR: the run_tests runner crashed"));
console.log(faultKind("ERROR: timed out after 5 s"));
console.log(faultKind(""));
console.log(faultKind("2 passed, 1 failed"));

Expected output:

tool error
runtime fault
bad output
no fault

The last one has a failing test, but that is not a fault. A failing test is real information about the code. A fault means the agent learned nothing useful about the code.

Tool error: the runner crashes once

Recovery Strategies

Retry Logic

A retry runs the same action again. It is the right first response for faults that may be temporary, such as crashes, timeouts, and empty results.

Two rules make retries safe:

  1. Limit them. Try only two or three times, then stop retrying.
  2. Wait a little longer each time. This is called backoff. It gives a busy service time to recover.

Retry with a limit and backoff

let calls = 0;
function runTests() {
  calls++;
  if (calls <= 2) return "ERROR: timed out";
  return "3 passed, 0 failed";
}

function withRetry(fn, maxTries) {
  let wait = 100;
  for (let attempt = 1; attempt <= maxTries; attempt++) {
    const r = fn();
    console.log("try " + attempt + ": " + r);
    if (!r.startsWith("ERROR")) return r;
    console.log("  wait " + wait + " ms");
    wait *= 2;
  }
  return "GAVE UP";
}

console.log("result: " + withRetry(runTests, 3));

Expected output:

try 1: ERROR: timed out
  wait 100 ms
try 2: ERROR: timed out
  wait 200 ms
try 3: 3 passed, 0 failed
result: 3 passed, 0 failed

Runtime fault: two timeouts

The test run times out twice, and the third attempt succeeds. Each timeout costs 5 seconds of waiting, so this run takes 21.9 seconds in the Budget tab. That is why retries need a limit.

Parameter Adjustment

Sometimes retrying the same call is pointless because the call itself is wrong. Parameter adjustment fixes the inputs first. You saw this in Lesson 11, when the agent read the error "write_file needs path and content" and corrected the argument names.

Fix the inputs, then retry

function writeFile(args) {
  const missing = ["path", "content"].filter((k) => !(k in args));
  return missing.length ? "ERROR: write_file needs " + missing.join(" and ") : "Saved " + args.path;
}

let args = { file: "web/format.js", text: "..." };
let result = writeFile(args);
console.log("try 1: " + result);

if (result.includes("needs path and content")) args = { path: args.file, content: args.text };
console.log("try 2: " + writeFile(args));

Expected output:

try 1: ERROR: write_file needs path and content
try 2: Saved web/format.js

Retrying try 1 unchanged would fail forever. Reading the error and fixing the inputs makes try 2 work.

Alternative Actions

An alternative action reaches the same goal in a different way. For example: if the full test run keeps timing out, run only the one test file that matters.

Bad output: an empty result

The first test run says ok but returns nothing. The agent does not treat "nothing" as "no failures". It identifies the result as bad output and retries.

Recovery Verification

After recovering, verify it. Run the check again, or examine the new result carefully. A retry that "worked" but returned strange output is not a recovery.

In the runs above, the agent's next thought says, "The retry worked, so the fault was temporary." It says that only after receiving a real test result.

Fallback Mechanisms

A fallback is a plan B: something smaller that still helps when plan A cannot succeed.

Secondary Actions

A secondary action is a simpler action toward the same goal. For example: if the full fix cannot be checked, write the fix and list the exact command a person can run to verify it.

Alternate Tools

An alternate tool performs a similar job. If run_tests is unavailable, start_tests and check_job from Lesson 11 might still work.

Partial Solutions

A partial solution explains what was done and clearly states what was not done.

A good partial answer always has two parts: what was done, and what is not checked.

Fallback: a partial answer

The fix is written at step 3. Then the test runner fails permanently.

The agent retries twice and then stops retrying. Its final answer begins with PARTIAL: it identifies the changed file and states that the change has not been checked.

A fallback chain that never returns nothing

function attempt(plan) {
  const steps = [
    () => plan.testsWork ? "DONE: fixed and tested" : null,
    () => plan.altToolWorks ? "DONE: fixed and tested with the background test tool" : null,
    () => plan.fixWritten ? "PARTIAL: fix written, not tested. Please run the tests." : null,
    () => "ESCALATE: nothing changed. A person must fix the test runner.",
  ];
  for (const step of steps) {
    const r = step();
    if (r) return r;
  }
}

console.log(attempt({ testsWork: true }));
console.log(attempt({ testsWork: false, altToolWorks: true }));
console.log(attempt({ testsWork: false, altToolWorks: false, fixWritten: true }));
console.log(attempt({ testsWork: false, altToolWorks: false, fixWritten: false }));

Expected output:

DONE: fixed and tested
DONE: fixed and tested with the background test tool
PARTIAL: fix written, not tested. Please run the tests.
ESCALATE: nothing changed. A person must fix the test runner.

The last link has no condition, so the chain always returns something.

Graceful Degradation

Graceful degradation means doing less, but doing it safely, when parts of the system break. The opposite is a crash or a silent failure.

Reduced Functionality

Reduced functionality keeps the safe parts working. A review agent with a broken test runner can still read the code and comment on it. It just cannot say "the tests pass".

Safe Responses

A safe response never claims more than the agent actually knows. "Fixed" is only safe if the tests ran and passed. Otherwise, the answer must clearly say "not checked".

Never claim more than you checked

function report(r) {
  if (r.testsRan && r.testsPass) return "Fixed. All tests pass.";
  if (r.changed.length) return "Changed " + r.changed.join(", ") + ". NOT checked: " + r.why + ".";
  return "Nothing changed: " + r.why + ".";
}

console.log(report({ testsRan: true, testsPass: true, changed: ["web/format.js"] }));
console.log(report({ testsRan: false, changed: ["web/format.js"], why: "the test runner is down" }));
console.log(report({ testsRan: false, changed: [], why: "the test runner is down" }));

Expected output:

Fixed. All tests pass.
Changed web/format.js. NOT checked: the test runner is down.
Nothing changed: the test runner is down.

The word "Fixed" should appear only when the tests ran and passed.

Escalation Paths

Escalation hands the problem to a person, together with everything they need to act: what failed, what was tried, and what was changed.

Fallback: escalate

The test runner never works. The agent has changed nothing, so a partial answer would contain no useful result. Instead, it escalates and clearly states that no changes were made.

Easy Way to Remember: name it, retry it, route around it, report it.

Never turn a fault into a confident answer. "The tests didn't run" is not the same as "the tests passed", and the final answer must clearly state which one happened.

Common Mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Fault read as failure"An error means the tests failed."Name it: tool error, bad output, or runtime fault.The agent "fixes" code that was never broken.
Endless retries"It will work eventually."Retry 2 or 3 times, then fall back.The run wastes time and money on a broken tool.
Same call, same mistake"Just try again."Fix the inputs when the error says they are wrong.Every retry fails in the same way.
Confident partial work"I wrote the fix, so it's fixed."Say exactly what is not checked.Untested code gets merged.
Silent giving up"I couldn't, so I'll stop."Escalate with what failed and what was tried.Nobody knows the task is stuck.

Exercises

Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.

What to Remember

  • Name it, retry it, route around it, report it.
  • There are three kinds of faults: tool errors, bad output, and runtime faults.
  • Retry temporary faults with a limit and backoff. Fix the inputs when the call itself is wrong.
  • Verify every recovery with a real result.
  • End every run with one of three outcomes: done, partial, or escalate. Never end with nothing.

Next: Loop Protection, the guards that stop an agent from going round in circles.