Redouble AI Nucleo documentation
Part 3 · The process stays in charge

When a job runs again

ai.redouble.nucleo.harness.errors.retry · nucleo-core

Some failures of a call to a model are nobody's mistake. The provider says this caller is going too fast, or that its whole fleet is overloaded, or a server error or a dropped connection gets in the way. Waiting and sending the same request again is what fixes them, so Nucleo does it itself: the model never hears about these failures, and your code does not handle them. Other failures are the model's own reply being unusable, and there Nucleo tells the model what to fix and asks again. A few failures no retry can fix, and those fail at once. This page covers each group and the one number you set.

The exceptions a tool throws to tell a model what went wrong are in When a tool fails; everything here happens beneath them.

Waiting out the provider

A provider client raises one of three signals, the subclasses of UpstreamRetryException, and the dispatcher answers each by waiting and running the job again:

The wait is random within a window that grows with every attempt, and an overload waits in a wider window than the other two. A rate limit and an overload also slow down every job calling that model, since the whole fleet is about to hear the same thing; a transient fault does not, because cutting everyone's rate over a stray 500 would be an overreaction.

How many times

The number of re-runs is the job's own: Job.getUpstreamRetries(), Job.DEFAULT_UPSTREAM_RETRIES (three) unless the job's constructor or its caller sets another through AbstractJob.setUpstreamRetries. All three signals draw on one counter per execution of the job, separate from any other retries the job has. A thinker hands its own number to every model call it makes.

Choose it by who is waiting. The window grows with each attempt, so every re-run past the first is minutes a caller waits behind, and a fault still there after a few attempts is an outage or a request the endpoint cannot serve, which more waiting never fixes. A job whose caller waits interactively sets fewer; an unattended pipeline may set more. Past the number, the job fails with an UncorrectableRuntimeLLMException naming it, the last signal as its cause.

None of the signals is addressed to a model, and none should be caught. A tool that catches broadly around a model call passes what it caught through LLMReadableCheckedException.unwrap or wrapWithContext, which rethrow any of these signals found in the cause chain, so a wait the dispatcher knows how to do is never turned into a failure.

When the model's reply is unusable

Two failures are the model's to fix, and both are correctable:

Nucleo appends that message to the conversation as a correction and runs the call again, so the model answers with its mistake in front of it; the signal that asks the dispatcher for the re-run is ResponseCorrectionRetryException. A job gets two corrections (ResponseCorrection.MAX_CORRECTIONS). After them, a reply that still does not parse fails with JsonParseException, and one that still leaves required fields empty is returned as it is, with a warning in the log.

A reply the provider cut off at its output limit is run again once with a larger output budget, asked for by OutputTruncationRetryException. A reply cut off again fails.

What no retry fixes

How it works inside

The waiting windows and throttle increments of each signal, what each carries, and how the dispatcher paces its re-runs are in Inside retry signals, for those working on the runtime itself. The dispatcher's side is in Model clients and rate limits.

^