Skip to content
Wetel
Go back

When an AI employee fails, what does the customer see?

Picture two colleagues on a busy afternoon. You ask both to check whether an order has shipped. The warehouse system is down. The first one comes back and says, “I couldn’t reach the warehouse just now. I’ve logged it, and someone will call you back.” The second one goes quiet, and an hour later you are still waiting.

Same bad luck; only the first one had a plan. An AI employee is the same: systems go down, a step fails, a customer sends a file nobody can read. Whether the customer hears something useful depends on whether the person who designed the workflow planned for that moment. This post covers what Wetel does today when something goes wrong, what the customer sees, and how a workflow author (a developer or the manager who owns the AI employee) designs for it.

What the customer sees by default: a short generic apology

If a step fails and nothing in the workflow catches it, the run stops and the customer gets a generic reply that begins “Sorry, I ran into a problem”. The real reason is not shown in the chat. It is recorded in the run record (the run’s errorMessage and the failed step’s entry in nodeTrace) and published live as a NodeFailedEvent to anyone subscribed to the session’s events.

That default is deliberate: a raw HTTP error is not for customers. But a generic apology is the second colleague, slightly politer: it says nothing about what happens next.

Two cases look like failures and are not:

Route a failure to a reply you wrote

For tool and action steps, draw two outgoing edges labelled success and failure, and a failure goes down your branch instead of stopping the run. (A tool step calls a tool on a registered MCP connector; an action step calls a registered custom action, a plain REST endpoint.) In the workflow editor, the step’s card toolbar has an “Add success/failure branch” toggle.

When the call throws (a timeout, a connection failure, an HTTP error, a malformed request template), the runner follows the failure edge, writes the error message into a context variable called _lastNodeError, and the run carries on. The step’s trace entry is marked FAILED_HANDLED: not an outage, a path the author planned for.

flowchart LR
A["action step:<br/>look up the order"] -- success --> B["response:<br/>here is your order status"]
A -- failure --> C["response:<br/>I couldn't reach our order system,<br/>here is what happens next"]
A -. "error recorded" .-> D["Run record<br/>(FAILED_HANDLED)"]

Three rules from the docs:

The docs describe these branches for tool and action steps only; for other step types, plan on a failure stopping the run.

Write the failure reply the way the first colleague would talk, and keep _lastNodeError (the technical error) out of it. Only promise a call-back if something in the workflow actually arranges one, such as a step that opens a ticket in your system or an approval a workspace owner acts on.

Retry a few times, then give up gracefully

A step can be retried in a loop, with a cap, and a separate edge taken when the cap runs out. The workflow docs include a complete example: a claims assistant submits a claim to a partner system that is occasionally flaky, tries three times, then hands off. The submitting step sets "maxVisits": 3, its failure edge goes to a short “trying once more” reply that loops back, and a third edge labelled loop_exhausted goes to a hand-off reply. These are its edges, from the worked example:

[
{
"id": "e2",
"source": "n_submit",
"target": "n_confirm",
"label": "success"
},
{
"id": "e3",
"source": "n_submit",
"target": "n_wait_note",
"label": "failure"
},
{ "id": "e4", "source": "n_wait_note", "target": "n_submit" },
{
"id": "e5",
"source": "n_submit",
"target": "n_give_up",
"label": "loop_exhausted"
}
]

The give-up reply in the example reads: “I couldn’t reach our claims system after a few tries. I’ve flagged this for a colleague, who will follow up by email today.” In your own workflow, make that sentence true: something has to do the flagging.

The docs flag the trap: every step inside the loop needs the higher maxVisits, the reply step too, or the loop stops early without reaching your give-up reply. Without the loop_exhausted edge, the fourth attempt fails the run and the customer gets the generic apology.

Loops sit inside the run’s documented ceilings of 250 step executions and 10 minutes; hitting one records BUDGET_EXCEEDED on the step that was refused, and the run fails.

When the customer sends something the workflow cannot use

Most “unreadable” input should be a branch in the workflow, not an error. The docs cover several cases:

Say something before a slow or risky step

Once a turn has sent its first reply, it runs to completion even if the customer sends another message. If the customer sends a second message before the turn has said anything, the newer message replaces the turn: steps it had not started yet are skipped. The docs give the case to design around: a workflow that submits a form or creates a record before its first reply. If the customer sends “hello?” while it is working, that side effect may never happen, and the customer may think it did.

The fix is the first colleague’s habit: acknowledge first (“Got it, filing your application now”), then do the work. The acknowledgement commits the turn. A step that is already running, such as a call to your system, always finishes; an outbound call is never cut off halfway.

What a developer sees when a request is refused

Requests to the Wetel API that fail come back as GraphQL errors, and the stable thing to branch on is errors[].extensions.code, not the message text. Message strings are meant for people and can change. What the docs list:

A failure inside a running workflow is different: the message call succeeds, and the failure shows up later in the events and the run record.

How to read the run record

Query workflowRuns(sessionId) with a dashboard login and read each run’s status, errorMessage and nodeTrace. The API key and the in-browser session token cannot read run records, so internal support tooling needs a real login. The statuses worth handling separately:

StatusWhat it means
FAILED (on a step)The step threw and nothing caught it; the run stopped there
FAILED_HANDLEDA tool or action step failed and its failure edge was followed; the run carried on
BUDGET_EXCEEDEDThe run hit its 250-step or 10-minute ceiling; the named step never ran
VISIT_LIMIT_REACHEDA loop hit its maxVisits; read the run’s status to see whether it carried on
AWAITING_INPUT (on a run)Waiting for the customer, not failed

For tool, action and webhook steps, the trace entry also carries renderedRequest (the request actually sent, with recognisable secrets redacted) and, when your system answered with an error, that system’s own response message inside error. That is usually the real reason.

Do not alert on every run that is not COMPLETED: waiting runs never complete by design.

A design checklist

  1. Give every tool and action step that talks to a system a success and a failure edge, and write the failure reply yourself.
  2. Say what happens next, honestly. Promise a call-back only if a step really arranges one (a ticket, an approval, a hand-off).
  3. Cap every retry loop with maxVisits on every step in the loop, plus a loop_exhausted edge to a give-up reply.
  4. Give missing and unexpected input a branch: a useful false path on condition steps and a useful defaultTarget on router steps.
  5. Acknowledge before a slow or consequential side effect, so the turn is committed before the work starts.
  6. Have someone read the run record: a person on your team who checks failed and FAILED_HANDLED runs, using a dashboard login, and fixes the workflow when the same failure repeats.

What is live and what is not

Live today: the generic fallback reply, success/failure branches on tool and action steps, capped loops with a give-up edge, the run ceilings, the run record with the statuses above, and live NodeFailedEvent events. Not documented, so do not plan on it: failure branches on other step types. Not something we publish: a reliability or uptime figure; measure failures in your own run records. The changelog lists when each behaviour arrived.

Start with the error handling reference, then add a failure branch to the one step in your workflow that calls the system most likely to be down.


Share this post:

Next Post
Why we label everything live, being built or direction
Previous Post
What are credits, and what do they not promise?