# Operator-present gate flow

How Dave clears human gates **live** during a research run, and how the same
in-session presence sweeps the queue of gates that earlier `background`-posture runs
left behind. This is the runbook for the `operatorPresent` gate policy (build-spec
§6). It is a **flow doc, not a code path** — the Playwright-MCP integration in the
skill body is a later concern; here we describe what the operator and Dave actually do.
**Baz** is the operator (the human in the session); **Dave** is the operator's model; a
**gate** is any fetch blocked by a login wall, paywall, Cloudflare/Turnstile challenge,
or captcha.

---

## 1. When to use `operatorPresent`

Reach for `--gate operatorPresent` when all three hold:

- **Baz is in-session with Dave** — a live, interactive run, not a cron/scheduled one.
- **The research will touch gated sources** — the answer depends on paywalled docs, a
  logged-in dashboard, or a Cloudflare-fronted site that a headless fetch can't pass.
- **The answer is worth Baz's attention** — clearing a gate costs a few seconds of the
  operator's time per gate; only spend it when getting the real source materially
  improves the answer over dropping-with-caveat.

If Baz is not present, or the run is scheduled, use `background` (queue the gates) or
`worker` (the default for dispatched agents; park/resume is v2). A worker **never**
self-upgrades to headed browsing.

## 2. The mechanics

Dave invokes the run, declaring the posture:

```
/research <question> --mode <light|normal> --gate operatorPresent
```

On a gate hit, Dave does **not** drop the source. Instead:

1. Dave escalates to Baz in plain language, naming the URL and the obstacle — e.g.
   *"Cloudflare check on docs.example.com — can you clear it?"*
2. Baz acts in the **shared headed browser** (a real Playwright MCP session Dave
   drives): Dave calls the Playwright MCP browser (in prose: `browser_navigate` to the
   URL, then `browser_snapshot` so Baz sees the page; Baz solves the challenge or signs
   in; Dave re-snapshots to confirm the content is now reachable).
3. Dave reads the now-ungated content and continues the fetch as normal — the source is
   cited like any other.

**The MCP browser is shared parent state.** There is exactly **one** headed consumer at
a time, and it is Dave (the parent). Subagents — the `research-normal.js` workflow's
searchers — run headless and **never** touch it. That has a hard consequence:

> **Do not run a normal-mode workflow under `operatorPresent`.** The workflow's searcher
> subagents cannot attach to Dave's headed Playwright session, so they can't benefit from
> a live clearance. Operator-present runs are effectively **light mode** (Dave does the
> fetching inline, on the shared browser) or a single-worker fetch that Dave supervises.
> A normal-mode run that expects gated sources should use `background` and be swept later
> (§3).

## 3. Sweeping the queued `background` gates

`background`-posture runs don't block on gates: each searcher drops the source and the
workflow's Finalise phase persists the record via `research gates --append`, so the
gate lands in `sessions/<id>/gates.json` with `status: pending`. Those records
accumulate across runs. A later operator-present session drains them:

1. **See what's queued** — across all sessions, or one:

   ```sh
   research gates --list            # every session's pending/retrying gates
   research gates --list <id>       # just one session
   ```

2. **Clear each gate Baz wants** — for a chosen row, Dave drives the shared Playwright
   MCP browser to the `url` (in prose: `browser_navigate` + `browser_snapshot`), Baz
   clears the challenge/login, and Dave captures the page. Because browser sessions
   don't transfer between processes, **clearance produces an artifact, not a cleared
   path**: Dave saves the fetched content to `sessions/<id>/gated/<hash>.md` so it can be
   re-read exactly like a fetch.

3. **Mark the swept items** — flag the session's eligible gates for follow-up:

   ```sh
   research gates --retry <id>      # marks pending/retrying items as `retrying`
   ```

   In v1, `--retry` **only marks** — it does not re-run the pipeline. `retrying` means
   *"queued for the operator to hand-run the fetch"*; Dave (or Baz) reads the saved
   `gated/<hash>.md` artifact and folds it into the answer by hand. The automated
   re-run of just the parked sub-questions is v2 (§4).

## 4. What v1 covers vs what v2 adds

**v1 (this and w-018):**
- `background`-posture gates queue automatically via `research gates --append` (workflow
  Finalise phase).
- `operatorPresent` runs skip gates live: Dave escalates, Baz clears in the shared headed
  Playwright browser, the fetch continues.
- `research gates --list` aggregates the queue; `research gates --retry` marks items for
  operator follow-up; clearance content is saved to `sessions/<id>/gated/<hash>.md`.

**v2 (deferred):**
- Worker-posture **park → escalate → resume** automation: a dispatched worker parks the
  gated sub-question, checkpoints `GATE: {...}` up its channel, continues its remaining
  queue, and is resumed from the clearance artifact.
- A `research resume <sessionId>` verb that re-runs extract+verify for just the parked
  sub-questions against `gated/<hash>.md`.
- `research gates --retry` triggering that automated re-run rather than only marking.

## 5. Anti-patterns

- **Do not try to defeat a gate from a worker.** Spec §6: a worker never self-upgrades to
  headed browsing. Background workers drop-and-log; that is correct, not a failure.
- **Do not run headed Playwright from a subagent.** The MCP browser is shared parent
  state with one headed consumer. Only Dave (the parent) drives it; searchers stay
  headless.
- **Do not auto-approve gate clearances.** The entire point of `operatorPresent` is live
  consent — Baz decides which gates to clear. Never script past a login/paywall on Baz's
  behalf without the explicit ask.
- **Do not run `operatorPresent` for a normal-mode workflow.** Its searchers can't attach
  to the shared browser (§2). Use `background` + a later sweep instead.
