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

# Human takeover

> Route a blocked run to a person, let them clear the gate in a live browser, and resume the agent — within the ten-minute hold.

When a run reaches something only a person should do — a CAPTCHA, an SMS code, a payment form, the final submit — it does not fail and it does not try to get around it. It **blocks**: it keeps its browser session open on the exact page it stopped at, pages a human, and waits to be resumed.

That is deliberate. The gate is the site's decision to require a person, and Sente honors it. Design your integration so a human can be reached in about ten minutes, and blocking becomes a normal step in the loop rather than an outage.

<Note>
  The two MFA cases that stay autonomous: a connected account with a vaulted **TOTP seed** (the server derives the 6-digit code — the seed itself never leaves the vault), and a connected account whose notification email the owner repointed to the Sente identity (`verifyToIdentity` — the emailed code lands in the account's own inbox and is applied like any other verification email). Everything else — SMS codes, codes sent to *your* inbox, CAPTCHAs — needs a person.
</Note>

## What blocks a run

| Gate                                | `error.code`                   | What the person does                              |
| ----------------------------------- | ------------------------------ | ------------------------------------------------- |
| CAPTCHA or bot challenge            | `CAPTCHA_REQUIRED`             | Completes the challenge in the live view.         |
| Phone number or SMS code            | `PHONE_REQUIRED`               | Supplies their own number and enters the code.    |
| Card / payment details              | `PAYMENT_REQUIRED`             | Enters payment details, or aborts the run.        |
| A field the agent wasn't given      | `MISSING_FIELD`                | Fills it (invite code, company name, plan).       |
| Final submit, by design             | `SUBMIT_CONFIRMATION_REQUIRED` | Reviews the filled form and clicks submit.        |
| 2FA code to the account owner       | `MFA_REQUIRED`                 | Reads it from their own inbox/phone and types it. |
| Authenticator code, no seed vaulted | `TOTP_REQUIRED`                | Types a code from their authenticator app.        |
| Agent can't confirm it signed in    | `LOGIN_UNCONFIRMED`            | Checks the page and finishes signing in.          |
| You paused it yourself              | `MANUAL_INTERVENTION`          | Drives the browser, then hands back.              |

Full meanings and the failure codes: [Runs](/runs/overview#blocked-codes).

## The clock

<Warning>
  A blocked run holds for about **10 minutes**. If nobody resumes it, the run fails with `BLOCKED_TIMEOUT`, the browser session is released, and the live-view URL stops working — reopening a stale link later gets you nothing. Wire up at least one notification path *before* you run anything that can block.
</Warning>

The `run.blocked` webhook payload carries `holdExpiresAt`, the exact instant the hold ends. Put it in whatever alert you send so the person knows how long they have. A run that times out is not destructive — start the operation again and it queues a fresh run.

## How a person gets paged

Four independent channels; use whichever matches where your humans are.

| Channel                   | Setup                                                                                                                                                                         | Best for                                                    |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **`run.blocked` webhook** | `POST /v1/webhooks` with `run.blocked` in `events`. Carries `code`, `liveViewUrl`, `actionUrl`, `holdExpiresAt`.                                                              | Routing into Slack, PagerDuty, or your own on-call tooling. |
| **Operator email**        | Automatic. Every member of the org gets an email with the dashboard deep link unless they turn the notification off.                                                          | Small teams with no alerting stack.                         |
| **`sente watch`**         | Run `sente watch` in a terminal. Polls every 5 seconds and prints the live-view URL and the resume command, with a desktop notification on macOS (a terminal bell elsewhere). | A developer at a laptop during a build-out.                 |
| **Dashboard**             | Nothing. [app.sente.run](https://app.sente.run) surfaces blocked runs at the top of Activity with a **Take over** button.                                                     | The person actually clearing the gate.                      |

```bash theme={null}
sente watch                                  # every identity in the org
sente watch --identity support-bot --interval 5
```

The webhook is the only channel that reaches a headless deployment reliably — it fires the instant the run blocks. It is a single attempt, so if your endpoint was down, `GET /v1/runs?status=blocked` is the catch-up query.

## Clearing a block

<Steps>
  <Step title="Get the run">
    From the webhook payload, `sente run <runId>`, or `runs.get(id)`. You need `error.code` (what the gate is) and `liveViewUrl` (where to clear it).
  </Step>

  <Step title="Open the live view">
    `liveViewUrl` is an **interactive** browser view, not a screenshot: the person sees the page the agent stopped on and can click and type in it. The dashboard's run page embeds the same view with the resume button next to it — that link is `actionUrl` in the webhook payload.
  </Step>

  <Step title="Do the one thing that was needed">
    Complete the CAPTCHA, enter the code, fill the field, click submit. Nothing else — the agent still owns the rest of the task.
  </Step>

  <Step title="Resume">
    <CodeGroup>
      ```bash CLI theme={null}
      sente run resume run_8d9e0f1a2b3c4d5e6f708192a3b4c5d6
      ```

      ```ts TypeScript theme={null}
      await sente.runs.resume(runId);
      const done = await sente.runs.waitForRun(runId, { timeout: 300_000 });
      ```

      ```python Python theme={null}
      sente.runs.resume(run_id)
      done = sente.runs.wait_for_run(run_id, timeout=300)
      ```
    </CodeGroup>

    Resume returns the run with `status: "running"` and `error: null`. If the run is no longer blocked (someone else resumed it, or the hold expired) you get `409 NOT_BLOCKED` with the current status.
  </Step>

  <Step title="The agent continues">
    Same browser, same page. It is told the block was cleared and re-reads the page before acting. After a confirm-before-submit pause it is told explicitly that a human already submitted, so it does not submit twice. The run carries on toward `completed`.
  </Step>
</Steps>

## Taking over a run you didn't have to

You can also pause a *healthy* run and drive it yourself — useful when you can see it heading the wrong way in the live view.

```bash theme={null}
curl -X POST https://api.sente.run/v1/runs/run_8d9e0f1a2b3c4d5e6f708192a3b4c5d6/intervene \
  -H "Authorization: Bearer $SENTE_API_TOKEN"
```

The run flips to `blocked` with `error.code: "MANUAL_INTERVENTION"` and the browser session stays alive. Only `running` and `awaiting_verification` runs can be taken over (`409 NOT_INTERVENABLE` otherwise). The same \~10-minute hold applies — an unresumed takeover fails with `BLOCKED_TIMEOUT` exactly like any other block. Hand back with `POST /v1/runs/:id/resume`; the agent re-observes the page a human changed and continues.

To stop instead of hand back, `POST /v1/runs/:id/abort` (`sente run abort`) ends the run with `ABORTED` and kills the browser session.

## Confirm-before-submit

The one block you ask for on purpose. Pass `confirmBeforeSubmit: true` (Python `confirm_before_submit=True`, CLI `--confirm-before-submit`) on a registration and the agent fills every field and checks the terms boxes, then **stops without pressing the final button**. The run blocks with `SUBMIT_CONFIRMATION_REQUIRED`; a person reviews the filled form in the live view and clicks submit themselves — so a human, not an agent, forms the agreement with the site — and then resumes. Sente completes email verification afterwards as usual.

Use it whenever you want a person accountable at the moment of account creation. Register only where the target's terms permit it — see [Acceptable use](/trust/acceptable-use).

## Things that bite

<AccordionGroup>
  <Accordion title="The code expired while we were waiting for a human">
    Emailed and SMS second-factor codes usually live 5–15 minutes, which overlaps the hold. If the person arrives late, the code they type may already be dead: the run resumes, the app rejects it, and the run blocks or fails again. Trigger a fresh code in the live view before typing, and treat the operator email as a fallback rather than the primary path for MFA-heavy accounts.
  </Accordion>

  <Accordion title="The live-view URL 404s">
    The URL belongs to a browser session. Once the run reaches a terminal state — resumed and finished, aborted, or `BLOCKED_TIMEOUT` — the session is gone and the link is dead. Always route people via `actionUrl` (the dashboard run page), which stays valid and shows the run's current state.
  </Accordion>

  <Accordion title="Nobody is watching and blocks keep timing out">
    Two adjustments: register the `run.blocked` webhook so alerts land where your team already is, and prefer [connect](/accounts/connect) with a vaulted TOTP seed over accounts whose 2FA goes to email or SMS — those re-login without a human at all.
  </Accordion>

  <Accordion title="Is a blocked run billed or counted?">
    A blocked run holds a live browser session open and counts toward your run quota while it lives. If it ends `failed` — including `BLOCKED_TIMEOUT` — it stops counting: failed runs never consume quota. See [Limits](/trust/limits).
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Runs" icon="play" href="/runs/overview">
    Statuses, the full code tables, and how to follow a run.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/events/webhooks">
    The `run.blocked` payload and how to verify a delivery.
  </Card>

  <Card title="Register a new account" icon="user-plus" href="/accounts/register">
    Confirm-before-submit in the context of signup.
  </Card>

  <Card title="Connect an account you own" icon="link" href="/accounts/connect">
    TOTP seeds and `verifyToIdentity` — the paths that avoid a human.
  </Card>
</CardGroup>
