Operator Handoff
Two roles use this skill:
- Requester (the agent in this session): writes jobs, resumes from reports. This is almost always you.
- Operator (a computer-use agent supervised by the human): executes jobs on the real desktop/browser, writes reports with evidence.
The job file is the only channel between them — no shared context, no follow-up questions. The job is the contract.
Preamble (run FIRST — the script decides your role, you don't)
Set JOB_ID to the job number from the invocation (/operator-handoff 003 → 003; empty if none), then run:
JOB_ID="<NNN-or-empty>"
cd "$(git rev-parse --show-toplevel)" 2>/dev/null
_O=.agents/operator; _J=$_O/jobs; _R=$_O/reports
[ -f "$_O/HOST.md" ] && echo "HOST: $_O/HOST.md" || echo "HOST: missing"
_MAX=0
for f in $(find $_J $_R -name "[0-9]*-*.md" 2>/dev/null); do
n=$((10#$(basename "$f" | cut -d- -f1))); [ "$n" -gt "$_MAX" ] && _MAX=$n
done
printf 'NEXT_ID: %03d\n' $((_MAX + 1))
_DUP=$(find $_J -name "[0-9]*-*.md" 2>/dev/null -exec basename {} \; | cut -d- -f1 | sort | uniq -d)
for n in $_DUP; do echo "COLLISION: $n -> $(find $_J -name "${n}-*.md" | sort | tr '\n' ' ')"; done
if [ -n "$JOB_ID" ]; then
_N=$(find $_J -name "${JOB_ID}-*.md" 2>/dev/null | wc -l | tr -d ' ')
_JOB=$(find $_J -name "${JOB_ID}-*.md" 2>/dev/null | sort | head -1)
if [ "$_N" -gt 1 ]; then echo "ROLE: blocked-collision"
elif [ -z "$_JOB" ]; then echo "ROLE: requester"; echo "WARN: no job file for $JOB_ID"
else
_RUN=$(grep -E '^## Run [0-9]+' "$_JOB" | sed -E 's/^## Run ([0-9]+).*/\1/' | sort -n | tail -1)
[ -z "$_RUN" ] && _RUN=1
_BASE="$_R/$(basename "$_JOB" .md)"
if [ "$_RUN" -eq 1 ]; then
_REP="$_BASE.md"
[ -f "$_REP" ] || _REP=$(find $_R -name "${JOB_ID}-*.md" ! -name "*.run-*" 2>/dev/null | sort | head -1)
else _REP="$_BASE.run-$_RUN.md"; fi
echo "RUN: $_RUN"
if [ -z "$_REP" ] || [ ! -f "$_REP" ]; then
echo "ROLE: operator"; echo "JOB_FILE: $_JOB"
if [ "$_RUN" -eq 1 ]; then echo "REPORT_FILE_EXPECTED: $_BASE.md"; else echo "REPORT_FILE_EXPECTED: $_BASE.run-$_RUN.md"; fi
find $_R -name "${JOB_ID}-*.md" 2>/dev/null | sort | sed 's/^/PRIOR_REPORT: /'
else echo "ROLE: requester-resume"; echo "JOB_FILE: $_JOB"; echo "REPORT_FILE: $_REP"; fi
fi
else echo "ROLE: requester"; fi
for j in $(find $_J -name "*.md" 2>/dev/null | sort); do
b=$(basename "$j"); n=$(echo "$b" | cut -d- -f1)
_RN=$(grep -E '^## Run [0-9]+' "$j" | sed -E 's/^## Run ([0-9]+).*/\1/' | sort -n | tail -1); [ -z "$_RN" ] && _RN=1
if [ "$_RN" -eq 1 ]; then _E="$_R/$b"; else _E="$_R/$(basename "$b" .md).run-$_RN.md"; fi
if [ -f "$_E" ]; then :
elif [ "$_RN" -eq 1 ] && [ -n "$(find $_R -name "${n}-*.md" 2>/dev/null)" ]; then echo "MISMATCH: $b has a report under a different slug"
else echo "PENDING: $b (run $_RN)"; fi
done
Branch ONLY on the echoed tokens:
HOST: missing → whatever your role, first scaffold .agents/operator/HOST.md from reference/host-template.md: infer what you can from the repo, ask the human the rest, and make the file committable (gitignore exception). Then continue.
COLLISION: / ROLE: blocked-collision → two job files share an NNN. Resolve BEFORE any other action: the file that already has a report or evidence keeps the number; rename the unreported one to NEXT_ID (update its internal Job <NNN>, evidence/<NNN>/, and reports/<NNN>- references too), fix anything that referenced the old number, tell the human the new number, then rerun the preamble.
MISMATCH: → a job's report exists under the same NNN but a different slug. Read it before trusting it: if it belongs to this job, rename it to match the job file; if it belongs to a different job, treat it as a collision leftover and investigate.
ROLE: operator → read HOST.md and reference/operator-runbook.md in full, then EXECUTE JOB_FILE now, in this session. If RUN: > 1 this is a re-run: read every PRIOR_REPORT: first, execute the job's highest ## Run <N> section (the run brief — base steps and earlier runs are context, not instructions), and write your report to REPORT_FILE_EXPECTED. STOP-GATE: telling the human to run the job you were just told to run is the exact failure this preamble exists to prevent. No handback, no waiting.
ROLE: requester-resume → read HOST.md, then do the On resume step of the workflow.
ROLE: requester → read HOST.md, then the workflow below.
HOST.md — the host profile
The skill is generic; everything machine-, account-, or repo-specific lives in .agents/operator/HOST.md (committed): requester identity, operator environment, legal secrets destinations, app URL + sign-in actor, the review gate for code jobs, local conventions. Both roles read it in full before acting. If writing a job needs a host fact that isn't there yet, add it to HOST.md as you go.
Directories
.agents/operator/HOST.md # host profile (committed; everything below is gitignored)
.agents/operator/jobs/<NNN>-<slug>.md # work requests
.agents/operator/reports/<NNN>-<slug>.md # results, same NNN + slug (run 1)
.agents/operator/reports/<NNN>-<slug>.run-<N>.md # results of run N >= 2 of the same job
.agents/operator/evidence/<NNN>/ # screenshots & sanitized output for a job (all runs)
- Job ID: zero-padded
NNN. Always use the NEXT_ID echoed by the preamble — it is the highest
NNN across BOTH jobs/ and reports/, plus one. Never hand-compute it, never reuse a number even
if its files are gone, and rerun the preamble right before writing the file if time has passed
(another session may have taken the ID meanwhile).
- Pending job = job file whose latest requested run has no report. A job's latest run is the
highest
## Run <N> heading in the job file (1 if none). The preamble computes this — trust its
PENDING:/RUN: output, not your own reading.
- These dirs are an interface: only jobs in
jobs/, only reports in reports/. A stray NNN- file in reports/ masks a pending job.
- To retire a pending job that should never run, write its report yourself:
**Status:** SUPERSEDED + one line why. Never delete job files — they are the trail.
Job kinds
| Kind |
Use for |
Template |
generic |
Any desktop/browser/CLI errand: app setup, verify an email flow, inspect a vendor console, collect a one-off proof. The default. |
reference/job-templates.md |
config |
Third-party dashboard setup producing env values |
same |
qa |
Execute committed Gherkin specs across browser/dashboard/CLI/API; verdict per scenario |
same |
code |
Implement an already-decided design slice (design settled, scope pinned) in a code repo |
same |
Requester workflow
- Decide the work actually needs the Operator (see Division of labor). Use the runtime's native semantic browser or computer-use capability first for ordinary UI work. If no native capability exists and
HOST.md names a headless computer-use executor, route the job there — same job/report contract, unattended. Use the Operator only when explicitly requested or when a native/headless run reaches a human-only gate it cannot clear; otherwise do it yourself.
- For
qa jobs: write/update the .feature spec first, commit it under tests/gherkin/<domain>/. Put the actor, entry URL, sign-in path, and selected scenario IDs in the job, not the spec.
- For
code jobs: only delegate when design is fully decided — no open questions. The job must carry every decision the Operator needs (glossary terms, design-doc links, file-level plan, validation commands, repo conventions). If you'd have to leave a decision open, don't delegate — settle it first or implement yourself.
- Write
jobs/<NNN>-<slug>.md from the matching template, with <NNN> = the preamble's NEXT_ID (rerun the preamble first if you didn't just run it). After writing, ls the jobs dir and confirm your NNN appears exactly once — if not, you collided with a parallel session: renumber yours to the new NEXT_ID. Always include: goal, exact steps/URLs/app names, what to produce, where to put it, and the secrets rule. For desktop app jobs, name the app, the exact menus/buttons, and what "done" looks like on screen.
- STOP. Tell the human:
/operator-handoff <NNN> — nothing more. The Operator's runbook covers everything else; never restate its rules in the handoff prompt.
- On resume: read
reports/<NNN>-*.md (+ evidence), verify any env keys it claims to have written (key NAME presence only), continue the task. For failed qa scenarios, treat each as a bug to triage — the report is evidence, not the fix. For code jobs, your review is conformance only (architecture followed? scope respected? nothing extra?) — the Operator already self-reviewed for bugs; flag deviations to the human, then commit per the repo's conventions. The Operator never commits.
- Re-runs: when a run comes back BLOCKED or partial and the SAME goal needs another pass, don't write a new job and don't rewrite history — append a run brief to the job file: a
## Run <N> section (N = previous run + 1) from the run-brief template in reference/job-templates.md, stating what's already DONE (skip it), what changed since, and what this run does. Then STOP and tell the human: /operator-handoff <NNN> — the preamble routes the next session to ROLE: operator for run N automatically. New goal or different scope → a NEW job at NEXT_ID, never a run. Base steps and earlier run briefs are append-only trail, like reports.
Secrets rule (both roles)
Secret values go ONLY into a destination the job names, chosen from the legal ones HOST.md lists (an env file, a platform env, a password manager). Job, report, and evidence files carry env key NAMES, never values. Never screenshot a page with a visible secret.
When a job MINTS a credential (a PAT, API token, key), the job spec sets its lifetime to no expiration — or the maximum the service offers if "never" isn't available. Short-lived tokens (7/30/90 days) silently break consumers later and force a new job to replace them. Spec an expiry only when the human asks for one or the credential is deliberately throwaway (revoked within the same job).
Division of labor
- Native browser/computer-use: the default for ordinary UI/browser/desktop work, including retrievable credentials, OAuth consent, external sends, production actions, reversible deletion, and account creation when the active workspace contract authorizes them.
- Headless computer-use executor (only if
HOST.md names one): fallback when the runtime lacks a native capability. It executes the same job files and writes the same reports, unattended; the report states it ran headless. A run that hits a human-only gate comes back BLOCKED — append a ## Run <N> brief and route that run to the Operator.
- Operator (human-supervised): explicit Operator requests and human-only gates — payment, CAPTCHA, passkey or biometric prompt, device-bound 2FA, ID/liveness verification, or a credential only the human possesses and the system cannot retrieve. Full Gherkin regression packs and fully decided
code jobs belong here only when the user explicitly asks for Operator supervision.
- Requester directly: design work, drafting, repo edits, anything scriptable (curl/CLI/API). No job file needed.
- Rule of thumb: ordinary UI stays with native browser/computer-use. Hand off only an explicit Operator request or the exact human-only gate a prior run reached.
HOST.md may sharpen mechanics, not expand the gate taxonomy.
1---2name: operator-handoff3description: Job-file handoff between a requesting agent and the Operator — a human-supervised computer-use agent driving the machine's real desktop and browser. Use when the user explicitly requests the Operator, resumes an Operator run ("check the operator report", "run job NNN"), or a native browser/computer-use run reaches an actual human-only gate.4---56# Operator Handoff78Two roles use this skill:910- **Requester** (the agent in this session): writes jobs, resumes from reports. **This is almost always you.**11- **Operator** (a computer-use agent supervised by the human): executes jobs on the real desktop/browser, writes reports with evidence.1213The job file is the only channel between them — no shared context, no follow-up questions. **The job is the contract.**1415## Preamble (run FIRST — the script decides your role, you don't)1617Set `JOB_ID` to the job number from the invocation (`/operator-handoff 003` → `003`; empty if none), then run:1819```bash20JOB_ID="<NNN-or-empty>"21cd "$(git rev-parse --show-toplevel)" 2>/dev/null22_O=.agents/operator; _J=$_O/jobs; _R=$_O/reports23[ -f "$_O/HOST.md" ] && echo "HOST: $_O/HOST.md" || echo "HOST: missing"24_MAX=025for f in $(find $_J $_R -name "[0-9]*-*.md" 2>/dev/null); do26 n=$((10#$(basename "$f" | cut -d- -f1))); [ "$n" -gt "$_MAX" ] && _MAX=$n27done28printf 'NEXT_ID: %03d\n' $((_MAX + 1))29_DUP=$(find $_J -name "[0-9]*-*.md" 2>/dev/null -exec basename {} \; | cut -d- -f1 | sort | uniq -d)30for n in $_DUP; do echo "COLLISION: $n -> $(find $_J -name "${n}-*.md" | sort | tr '\n' ' ')"; done31if [ -n "$JOB_ID" ]; then32 _N=$(find $_J -name "${JOB_ID}-*.md" 2>/dev/null | wc -l | tr -d ' ')33 _JOB=$(find $_J -name "${JOB_ID}-*.md" 2>/dev/null | sort | head -1)34 if [ "$_N" -gt 1 ]; then echo "ROLE: blocked-collision"35 elif [ -z "$_JOB" ]; then echo "ROLE: requester"; echo "WARN: no job file for $JOB_ID"36 else37 _RUN=$(grep -E '^## Run [0-9]+' "$_JOB" | sed -E 's/^## Run ([0-9]+).*/\1/' | sort -n | tail -1)38 [ -z "$_RUN" ] && _RUN=139 _BASE="$_R/$(basename "$_JOB" .md)"40 if [ "$_RUN" -eq 1 ]; then41 _REP="$_BASE.md"42 [ -f "$_REP" ] || _REP=$(find $_R -name "${JOB_ID}-*.md" ! -name "*.run-*" 2>/dev/null | sort | head -1)43 else _REP="$_BASE.run-$_RUN.md"; fi44 echo "RUN: $_RUN"45 if [ -z "$_REP" ] || [ ! -f "$_REP" ]; then46 echo "ROLE: operator"; echo "JOB_FILE: $_JOB"47 if [ "$_RUN" -eq 1 ]; then echo "REPORT_FILE_EXPECTED: $_BASE.md"; else echo "REPORT_FILE_EXPECTED: $_BASE.run-$_RUN.md"; fi48 find $_R -name "${JOB_ID}-*.md" 2>/dev/null | sort | sed 's/^/PRIOR_REPORT: /'49 else echo "ROLE: requester-resume"; echo "JOB_FILE: $_JOB"; echo "REPORT_FILE: $_REP"; fi50 fi51else echo "ROLE: requester"; fi52for j in $(find $_J -name "*.md" 2>/dev/null | sort); do53 b=$(basename "$j"); n=$(echo "$b" | cut -d- -f1)54 _RN=$(grep -E '^## Run [0-9]+' "$j" | sed -E 's/^## Run ([0-9]+).*/\1/' | sort -n | tail -1); [ -z "$_RN" ] && _RN=155 if [ "$_RN" -eq 1 ]; then _E="$_R/$b"; else _E="$_R/$(basename "$b" .md).run-$_RN.md"; fi56 if [ -f "$_E" ]; then :57 elif [ "$_RN" -eq 1 ] && [ -n "$(find $_R -name "${n}-*.md" 2>/dev/null)" ]; then echo "MISMATCH: $b has a report under a different slug"58 else echo "PENDING: $b (run $_RN)"; fi59done60```6162Branch ONLY on the echoed tokens:6364- `HOST: missing` → whatever your role, first scaffold `.agents/operator/HOST.md` from [reference/host-template.md](reference/host-template.md): infer what you can from the repo, ask the human the rest, and make the file committable (gitignore exception). Then continue.65- `COLLISION:` / `ROLE: blocked-collision` → two job files share an `NNN`. Resolve BEFORE any other action: the file that already has a report or evidence keeps the number; rename the unreported one to `NEXT_ID` (update its internal `Job <NNN>`, `evidence/<NNN>/`, and `reports/<NNN>-` references too), fix anything that referenced the old number, tell the human the new number, then rerun the preamble.66- `MISMATCH:` → a job's report exists under the same `NNN` but a different slug. Read it before trusting it: if it belongs to this job, rename it to match the job file; if it belongs to a different job, treat it as a collision leftover and investigate.67- `ROLE: operator` → read `HOST.md` and [reference/operator-runbook.md](reference/operator-runbook.md) in full, then EXECUTE `JOB_FILE` now, in this session. If `RUN:` > 1 this is a re-run: read every `PRIOR_REPORT:` first, execute the job's highest `## Run <N>` section (the run brief — base steps and earlier runs are context, not instructions), and write your report to `REPORT_FILE_EXPECTED`. **STOP-GATE: telling the human to run the job you were just told to run is the exact failure this preamble exists to prevent.** No handback, no waiting.68- `ROLE: requester-resume` → read `HOST.md`, then do the **On resume** step of the workflow.69- `ROLE: requester` → read `HOST.md`, then the workflow below.7071## HOST.md — the host profile7273The skill is generic; everything machine-, account-, or repo-specific lives in `.agents/operator/HOST.md` (committed): requester identity, operator environment, legal secrets destinations, app URL + sign-in actor, the review gate for `code` jobs, local conventions. Both roles read it in full before acting. If writing a job needs a host fact that isn't there yet, add it to `HOST.md` as you go.7475## Directories7677```78.agents/operator/HOST.md # host profile (committed; everything below is gitignored)79.agents/operator/jobs/<NNN>-<slug>.md # work requests80.agents/operator/reports/<NNN>-<slug>.md # results, same NNN + slug (run 1)81.agents/operator/reports/<NNN>-<slug>.run-<N>.md # results of run N >= 2 of the same job82.agents/operator/evidence/<NNN>/ # screenshots & sanitized output for a job (all runs)83```8485- **Job ID**: zero-padded `NNN`. Always use the `NEXT_ID` echoed by the preamble — it is the highest86 `NNN` across BOTH `jobs/` and `reports/`, plus one. Never hand-compute it, never reuse a number even87 if its files are gone, and rerun the preamble right before writing the file if time has passed88 (another session may have taken the ID meanwhile).89- **Pending job** = job file whose latest requested **run** has no report. A job's latest run is the90 highest `## Run <N>` heading in the job file (1 if none). The preamble computes this — trust its91 `PENDING:`/`RUN:` output, not your own reading.92- These dirs are an interface: only jobs in `jobs/`, only reports in `reports/`. A stray `NNN-` file in `reports/` masks a pending job.93- To retire a pending job that should never run, write its report yourself: `**Status:** SUPERSEDED` + one line why. Never delete job files — they are the trail.9495## Job kinds9697| Kind | Use for | Template |98|---|---|---|99| `generic` | Any desktop/browser/CLI errand: app setup, verify an email flow, inspect a vendor console, collect a one-off proof. **The default.** | [reference/job-templates.md](reference/job-templates.md) |100| `config` | Third-party dashboard setup producing env values | same |101| `qa` | Execute committed Gherkin specs across browser/dashboard/CLI/API; verdict per scenario | same |102| `code` | Implement an already-decided design slice (design settled, scope pinned) in a code repo | same |103104## Requester workflow1051061. Decide the work actually needs the Operator (see Division of labor). Use the runtime's native semantic browser or computer-use capability first for ordinary UI work. If no native capability exists and `HOST.md` names a headless computer-use executor, route the job there — same job/report contract, unattended. Use the Operator only when explicitly requested or when a native/headless run reaches a human-only gate it cannot clear; otherwise do it yourself.1072. For `qa` jobs: write/update the `.feature` spec first, commit it under `tests/gherkin/<domain>/`. Put the actor, entry URL, sign-in path, and selected scenario IDs in the job, not the spec.1083. For `code` jobs: only delegate when design is fully decided — no open questions. The job must carry every decision the Operator needs (glossary terms, design-doc links, file-level plan, validation commands, repo conventions). If you'd have to leave a decision open, don't delegate — settle it first or implement yourself.1094. Write `jobs/<NNN>-<slug>.md` from the matching template, with `<NNN>` = the preamble's `NEXT_ID` (rerun the preamble first if you didn't just run it). After writing, `ls` the jobs dir and confirm your `NNN` appears exactly once — if not, you collided with a parallel session: renumber yours to the new `NEXT_ID`. Always include: goal, exact steps/URLs/app names, what to produce, where to put it, and the secrets rule. For desktop app jobs, name the app, the exact menus/buttons, and what "done" looks like on screen.1105. STOP. Tell the human: `/operator-handoff <NNN>` — nothing more. The Operator's runbook covers everything else; never restate its rules in the handoff prompt.1116. **On resume**: read `reports/<NNN>-*.md` (+ evidence), verify any env keys it claims to have written (key NAME presence only), continue the task. For failed `qa` scenarios, treat each as a bug to triage — the report is evidence, not the fix. For `code` jobs, your review is **conformance only** (architecture followed? scope respected? nothing extra?) — the Operator already self-reviewed for bugs; flag deviations to the human, then commit per the repo's conventions. The Operator never commits.1127. **Re-runs**: when a run comes back BLOCKED or partial and the SAME goal needs another pass, don't write a new job and don't rewrite history — **append a run brief** to the job file: a `## Run <N>` section (N = previous run + 1) from the run-brief template in [reference/job-templates.md](reference/job-templates.md), stating what's already DONE (skip it), what changed since, and what this run does. Then STOP and tell the human: `/operator-handoff <NNN>` — the preamble routes the next session to `ROLE: operator` for run N automatically. New goal or different scope → a NEW job at `NEXT_ID`, never a run. Base steps and earlier run briefs are append-only trail, like reports.113114## Secrets rule (both roles)115116Secret values go ONLY into a destination the job names, chosen from the legal ones `HOST.md` lists (an env file, a platform env, a password manager). Job, report, and evidence files carry env key NAMES, never values. Never screenshot a page with a visible secret.117118When a job MINTS a credential (a PAT, API token, key), the job spec sets its lifetime to **no expiration** — or the maximum the service offers if "never" isn't available. Short-lived tokens (7/30/90 days) silently break consumers later and force a new job to replace them. Spec an expiry only when the human asks for one or the credential is deliberately throwaway (revoked within the same job).119120## Division of labor121122- **Native browser/computer-use**: the default for ordinary UI/browser/desktop work, including retrievable credentials, OAuth consent, external sends, production actions, reversible deletion, and account creation when the active workspace contract authorizes them.123- **Headless computer-use executor** (only if `HOST.md` names one): fallback when the runtime lacks a native capability. It executes the same job files and writes the same reports, unattended; the report states it ran headless. A run that hits a human-only gate comes back BLOCKED — append a `## Run <N>` brief and route that run to the Operator.124- **Operator** (human-supervised): explicit Operator requests and human-only gates — payment, CAPTCHA, passkey or biometric prompt, device-bound 2FA, ID/liveness verification, or a credential only the human possesses and the system cannot retrieve. Full Gherkin regression packs and fully decided `code` jobs belong here only when the user explicitly asks for Operator supervision.125- **Requester directly**: design work, drafting, repo edits, anything scriptable (curl/CLI/API). No job file needed.126- Rule of thumb: ordinary UI stays with native browser/computer-use. Hand off only an explicit Operator request or the exact human-only gate a prior run reached. `HOST.md` may sharpen mechanics, not expand the gate taxonomy.