> ## Documentation Index
> Fetch the complete documentation index at: https://servo.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Local recovery

> Keep recovery on the robot and explicitly hand control back to the policy.

Register one local recovery handler before starting your controller loop.

## Connect your controller

Subclass `servo.RecoveryController` with local acquisition, queue replacement, hardware writes and readiness checks.
Route policy rows through `send_policy`, recovery commands through `send_recovery`, and manual commands through `send_operator`.
These methods arbitrate ownership at dispatch; calling your hardware directly bypasses that protection.

Your `capture_inputs` returns `servo.RecoveryObservation(inputs, captured_at_ns, ready)`.
Use the oldest required sample's local monotonic acquisition timestamp.
`ready` covers all required sensors; `validate_resume` checks current readiness and first-action limits.
Choose `max_observation_age_s` for your controller.

## Register the handler

Pass your controller, local recovery routine and async execution loop to this function.

```python robot computer theme={null}
from asyncio import to_thread
import servo

sv = servo.Servo()

async def run_with_recovery(policy, controller, recover_locally, drive):
    async def handler(context, recovery):
        await recover_locally(controller, context.incident_id)
        recovery.record_action("local_recovery", outcome="completed", details={})
        result = await recovery.resume(timeout_s=10.0)
        if not result.resumed:
            print(result.reason)

    session = sv.session(policy, instruction="pick up the red cup")
    await to_thread(session.open)
    registration = session.configure_recovery(handler=handler, controller=controller)
    try:
        await drive(session, registration, controller)
    finally:
        await registration.stop()
        await to_thread(session.close)
```

Your loop reports a due action tick without a valid row through `registration.notify_starvation(queue=...)`.
Do not report initial readiness or deliberate pauses as starvation.
Run synchronous prediction calls on bounded workers so recovery callbacks remain responsive.

## Resume explicitly

Returning from the handler leaves recovery in control.
`resume` requires fresh observations and a new action chunk before transferring ownership.
Old queued or late predictions cannot regain authority.
A failed resume leaves recovery active; your code chooses whether to retry.

Use `recovery.hold(reason)` for manual intervention.
Only its token authorizes `send_operator`; automated recovery writes remain blocked while held.
Release the token with `release_hold(token)`, then explicitly resume.

The robot's watchdog owns physical behavior when commands stop.
This API does not choose a retract, torque or gripper policy.
Use it with your controller loop; a configured session refuses the separate `session.run` execution owner.

## You now have

* Local recovery that works without cloud connectivity.
* Explicit handback with fresh inputs and exclusive action ownership.

## Next

Review the [recovery API signatures](/reference/python-api) and connect your controller's watchdog and freshness checks.
