---
title: "Resolve loop, budget and recovery waits"
description: "Resolve loop, budget and recovery waits for people running and reviewing agents."
canonical_url: "https://docs.oz0.ai/agents/approvals/recovery"
---
# Resolve loop, budget and recovery waits

> Resolve loop, budget and recovery waits for people running and reviewing agents.

For: people running and reviewing agents.

## Loop and budget waits

Two waits come from the platform rather than from a tool:

- A loop (`on_loop: wait`, the default): before the call that would complete a loop, the job waits as an approval of kind `loop`. The card shows the evidence: which detector found it (`repeat` or `cycle`), the calls, and the repeated call's arguments (which, as any call's, need the content permission). **Approve** continues the job (its loop counters start again) and the call is made; **Deny**, or the deadline, stops the job with `LoopDetected`. It takes the agent's and the tenant's deadline and ladder, and any operator of the tenant may decide it.
- A job tree's budget (`on_budget: wait`): the job waits for a budget request about its tree, which waits in the same inbox. The tree's owner and the tenant's admins decide it: approve to raise the budget (the job goes on where it stopped), deny to fail the job as a spent budget does. A forecast's request, before the wall, says what the agent's earlier runs spent from there: approve to extend the budget, deny to let the agent wrap up within it.

```bash [Terminal]
oz0 approval list                       # a loop: KIND loop
oz0 quota requests                      # a tree's budget: KIND job or forecast
oz0 quota approve br-01a1... --comment "finish it"
```

## Unknown outcomes

A call of a function that is not [retry-safe](https://docs.oz0.ai/build/parts/tools/definitions#retry-safe-functions) gets one attempt. When the attempt ends without an answer the platform can read, nobody knows whether it wrote: it ran past its tool's time limit and the node stopped it at the tool, its node was lost while it ran, or its connection broke after the request went out. What the job does then is its agent's `on_unknown`:

<table>
<thead>
  <tr>
    <th>
      <code>
        on_unknown
      </code>
    </th>
    
    <th>
      What happens
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        wait
      </code>
    </td>
    
    <td>
      The job waits as an approval of kind <code>
        unknown_outcome
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fail
      </code>
    </td>
    
    <td>
      The job fails with the failure kind <code>
        unknown_outcome
      </code>
      
      ; the call's own error stays its cause.
    </td>
  </tr>
  
  <tr>
    <td>
      unset
    </td>
    
    <td>
      The tenant's default, then who started the job: a person's job (an account or an operator) waits, a routine's, an event's or an API key's fails.
    </td>
  </tr>
</tbody>
</table>

The plugin's configuration for the agent (Configure, Budgets) overrides the agent's definition, and either overrides the tenant's default (`oz0 tenant spend --on-unknown`, or Policy's Approval defaults tab); when none of them says, who started the job decides, as for [`on_budget`](https://docs.oz0.ai/build/parts/contracts-and-limits/budgets#budgets-that-wait). The job asks once, at its first unknown outcome, and keeps the answer in its history, so a replay makes the same choice. Any node that runs the agent answers for the job, so a job whose node was lost is still asked about, or fails, rather than waiting for that node.

The card shows the function, how the call ended, the node it ran on, its idempotency key and whether the function's server honours the key, and the call's arguments as a tool approval shows them (they take the content permission). How the call ended is in the platform's words and names only the error's type: the error's own text, which often repeats the arguments, stays in the job's failure.

When the call's node was lost, the card says so: the job's home went with it, so running the call again, and the job's next calls on that node, wait until the node is back; stop the job if it will not come back. Check the system the tool writes to, then answer:

- It happened: the job goes on, and the agent (or the flow step) reads that the write is taken as done. There is no result to read.
- Run it again: the call runs once more under the same idempotency key. A server that honours the key does nothing new if the first call wrote; one that does not may write a second time, and the card says so. If the outcome is unknown again, the job asks again, at most five times.
- Stop the job: it fails with `unknown_outcome`. Denying the approval, and no answer by the deadline, do the same.

It is decided by those who decide a [tool approval](https://docs.oz0.ai/agents/approvals/configure#who-decides) of the same function, with four eyes where its deciders say so, and an API key only where they name it; the job's starter may always stop it. It takes the function's deadline and ladder, as a tool approval does. A chat channel's or an app's plain approve cannot answer it: approve takes one of the outcomes, which the console and the CLI give.

```bash [Terminal]
oz0 approval list                                   # outcome unknown: how the call ended, its node
oz0 approval decide <job> <approval> happened       # or run_again, or stop
oz0 tenant spend default --on-unknown wait --reason "a person checks every lost write"
```

## Restart waits

A harness session lost with its node starts again from scratch by itself only when its agent is [restart-safe](https://docs.oz0.ai/build/parts/harnesses/recovery#restart-safety). Otherwise the job waits as an approval of kind `restart`. The card names the lost node, how far the session got, why the agent is not restart-safe, and the calls the lost session was allowed whose effect nobody can see (by tool and a digest of the arguments, never the arguments).

**Approve** starts a fresh session from the job's input; **Deny**, or no answer by the deadline, fails the job. It takes the agent's and the tenant's deadline and ladder, never a tool's rule.

A [remote agent's](https://docs.oz0.ai/build/apps/remote-agents/sessions-and-limits#when-a-node-is-lost) session asks the same way when it resumes and finds a call allowed with no result: the card names that call. Approve runs it again (after the job's usual hooks and approvals); deny fails the job.

```bash [Terminal]
oz0 approval list                       # a lost session: KIND restart
oz0 approval approve <job> <approval>   # start a fresh session
```


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
