agentleFS
DITlieDDITlieD/ELAI-archive/.agents/skills/operator/SKILL.md

operator

Skill24 starsChanged 2 months agoMITView on GitHub

Drive any Windows GUI application on the owner's real PC and PROVE what was done. A "fake human" that opens apps, reads their UI Automation tree, clicks/types/scrolls through them, and journals every action frame-bound and replayable — the desktop generalization of the playtester skill (Unity via injected C# seams) to "any app via UIA + pixels". Triggers when the owner says "operate <app>", "drive <app> and test X", "open <app> and do Y", "computer-use this", or points at the .Codex/tools/operator toolkit. Owner-present/supervised sessions only in OP-A (host lane, no arming). SEAM-FIRST: if a programmatic API/MCP/headless recipe can do the step, use THAT, not pixels.

4 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

5 rules. 0 are common, 3 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    All under F:\.ELAI_workflow\.Codex\tools\operator\ :

    line 29
  2. Very specific:

    Default reads <dir>/replay.json (auto-crystallizes from journal if missing and --replay omitted).

    line 42
  3. Distinctive:

    Verify the delta: re-snapshot.

    line 77
  4. Distinctive:

    Prefer an element id with a non-empty patterns list (op_snapshot lists them).

    line 101
  5. Very specific:

    Start runner ON seat 2 (manual on that desktop is preferred.

    line 168

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

OPERATOR (frame-bound computer-use for the real Windows host)

WHAT THIS IS A loop where the model drives a real Windows application through its UI Automation tree + screen pixels, and every actuation is bound to the exact observation it was decided from (frame binding, fail-closed). It replaces "I clicked around and it looked fine" with a journal of intent -> frame-hash-before -> action -> frame-hash-after -> result rows plus before/after frames and crosshair evidence crops that an auditor can re-hash without trusting this session's narration. Same discipline as the playtester skill, generalized off Unity onto any GUI app.

SEAM-FIRST RULE (doctrine #1, non-negotiable) Before driving a single pixel, check for a PROGRAMMATIC SEAM. Pixel-driving a step a seam could have done is a FINDING, not a success (a "wrong-lane" finding: record it and reroute). Routing table (pack docs under .Codex/tools/operator/packs/ are the per-app seam inventory + GUI residue — read the named pack before driving that app): Unity -> unity-pack (packs/unity-pack.md): Unity MCP / unityMCP tools + batch_execute; playtester loop; GUI ONLY for native OS dialogs (Import Package / Save As / Preferences residue). IMGUI panes are UIA-dark — refuse GUI for scene/Inspector/Hierarchy content. MULTISEAT HARD: Hub uses machine-wide pipe Unity-hubIPCService — never kill Editor to free Hub for another seat; only one Hub on the PC at a time; project lists are per Windows user (projects-v1.json). Blender -> blender-pack (packs/blender-pack.md): blender --background --python <recipe>.py headless bpy first; catalog id blender / winget BlenderFoundation.Blender. GUI residue = addon dialogs / eyeball QC only. CAD -> cad-pack (packs/cad-pack.md): CadQuery / FreeCAD python console first; Fusion MCP = RECON_NEEDED until vendor-verified. GUI residue = sketch/constraint dialogs. no named seam -> generic-playbook (packs/generic-playbook.md): exploration protocol for Paint-class / unknown apps (UIA-rich → PATH-1; UIA-dark content → do not thrash). web / browser -> Playwright / CDP (AXTree-first) a CLI/API app -> call the CLI/API directly The GUI operator lane exists ONLY for the residue: GUI-only apps (no API), editor dialogs no MCP reaches, installers, and visual verification. Always open the named pack above before actuating that app; pack absence for a new app means write/extend a pack (docs+catalog row), do not invent a fork of op_*.py.

PRECONDITIONS (verify before starting; fail loud if missing)

  • OWNER-PRESENT session by default (OP-A host lane; --mode shared). Unattended full-control driving is --mode exclusive (owner away/asleep). Destructive action classes are DARK in OP-A in BOTH modes (see below); mode never relaxes that gate.
  • The venv exists: F:.ELAI_workflow.Codex\tools\operator.venv (python -m venv; deps in requirements.txt). Run every tool with that interpreter: set PY=F:.ELAI_workflow.Codex\tools\operator.venv\Scripts\python.exe and run the tools from their own directory (F:.ELAI_workflow.Codex\tools\operator) so op_common/op_uia import.
  • FOREGROUND is required only for the COORDINATE FALLBACK path (PATH 2). The PRIMARY path is UIA-pattern actuation (PATH 1) which needs NO foreground and NO mouse, so it drives an app while the owner uses a different window. Prefer an element id that carries a usable pattern (snapshot.json lists each element's actionable patterns); only pixel-only / no-pattern targets need the target foregrounded, and PATH 2 refuses FOREGROUND_MISMATCH rather than type into the wrong window.

THE TOOLKIT (every primitive; the skill IS the production entrypoint — a tool this skill never names is wire-dark) All under F:.ELAI_workflow.Codex\tools\operator\ :

  • op_run.py — run lifecycle. op_run.py new --slug <slug> -> prints a run dir under workflow/operator-runs/{ts}-{slug}/ (journal.jsonl, frames/, crops/, report.md, run.json). Capture that path; every other tool takes --run <that dir>. op_run.py record-start --run <dir> [--region L,T,W,H] [--max-seconds N] -> start a bounded ffmpeg gdigrab recording. OFF by default; use ONLY on armed destructive actions (OP-B). Evidence posture in OP-A is before/after frames + crops, not video. op_run.py record-stop --run <dir> op_run.py finalize --run <dir> [--crystallize] -> writes the findings report (action table + summary) from the journal. With --crystallize (OP-E1) ALSO mints replay.json + manifest.json from OK journal rows. op_run.py crystallize --run <dir> -> OP-E1 alone: mint replay.json + manifest.json (no report rewrite). Equivalent to op_record.py crystallize --run <dir>.
  • op_record.py crystallize --run <dir> — OP-E1 recorder (also called from finalize --crystallize). Reads journal.jsonl + last snapshot.json; mints: replay.json ordered playable steps (UIA selector = role+name+runtime_id first; coords+frame-hash fallback; assertions from journal deltas; class/mode/destructive flag per step). Skips REFUSED/PRE_IMAGE rows; de-dupes menu-open when a following menu-select shares the parent. manifest.json monitor geometry, tool versions, catalog blake3, app window titles, taskspec_shape block for later OP-F. Schema version op-e1-v1; shape note at .Codex/tools/operator/notes/OP-E-TASKSPEC-SHAPE.md.
  • op_replay.py — OP-E2 deterministic re-walk of a crystallized script. NEVER improvises past a failed step. op_replay.py --run <dir> [--replay <path>] [--window <title>] [--monitor N] [--max-steps N] [--dry-parse] [--allow-destructive] Default reads <dir>/replay.json (auto-crystallizes from journal if missing and --replay omitted). Per step: re-observe (op_snapshot) -> resolve UIA selector against live tree -> fire via op_act with the FRESH frame_hash -> assert journal status==OK. Any refusal / selector miss / assertion fail = honest STOP + replay-report.json (exit 3). Zero retarget, zero skip-ahead. UNATTENDED POLICY (doctrine #7 + plan REDTEAM patch): non-destructive steps only. A step with destructive=true / class in DESTRUCTIVE_CLASSES refuses REARM_REQUIRED unless --allow-destructive AND a live owner-confirmed op_arm lease exists at op_act. Crystallize never strips the class; replay never silently re-arms. --dry-parse validates structure with zero actuation (use this after minting, before a live re-walk).
  • op_arm.py — the OP-B1 capability-arming lease store (see DESTRUCTIVE CLASSES + ARMING below). Subcommands: arm <class> --ttl <s> (destructive -> PENDING until owner-confirm), approve <class> (owner grants), disarm <class>, status [--json]. Leases live under .Codex/tools/operator/leases/; the arm lifecycle journals to arm-journal.jsonl. op_act consumes a lease at dispatch; there is no --run on op_arm (arming is a machine-wide capability, not a per-run action).
  • op_install.py — the OP-B3 catalog-gated winget install wrapper (doctrine #8 GATED INSTALLS). op_install.py install <tool> --run <dir> [--lease <id>] [--execute]. TWO INDEPENDENT gates, both required: (1) <tool> must have a row in .Codex/tools/operator/catalog.toml (else INSTALL_UNCATALOGED — an uncataloged binary NEVER installs, even with the install class armed); (2) the install destructive class must hold an owner-confirmed, unexpired op_arm lease (else CLASS_UNARMED / LEASE_EXPIRED). The catalog check runs FIRST so an armed install window cannot be used to install an uncataloged binary (REDTEAM attack 2). Default action is a READ-ONLY winget show boundary proof (proves the gates passed without mutating the host); the real host-mutating winget install runs ONLY under --execute AND a live armed lease. Every attempt (refuse or proceed) is journaled to the run's journal.jsonl. To install: arm the class (op_arm arm install --ttl <s> -> owner approve install), then op_install.py install <tool> --run <dir> --execute. Add a tool only with a winget show <id>-verified [[tool]] row (anti-slopsquat).
  • op_jobs.py / op_dispatch.py / op_runner.py — OP-F multi-seat (ASTER) job contract. HARD ARCHITECTURE: ASTER seats are separate Windows sessions; session-1 MUST NOT drive session-2 via cross-session UIA. Reach seat N by starting op_runner INSIDE that session; jobs+results share F:.ELAI_workflow\workflow\operator-jobs\ (inbox/active/done/results/heartbeats). op_dispatch.py whoami | heartbeats op_dispatch.py submit --session <N> --kind smoke_notepad_type|steps|replay [--marker M] [--steps-json p] [--replay p] [--wait] op_dispatch.py status --id <job> | wait --id <job> [--timeout S] op_dispatch.py smoke-self # same-session vertical slice (preferred green): submit+run once here, types additive SEED into FRESH Notepad op_runner.py whoami | once | loop [--interval N] # MUST run inside the target session launch_seat_runner.cmd # seat-local loop launcher (run ON seat 2 desktop) See notes/OP-F-SEAT-LAUNCH.md for PsExec/schtasks/manual/ASTER-autostart options. Deterministic suite: op_f_negatives.py.
  • op_snapshot.py --run <dir> [--window <title-substr>] [--monitor N] [--json] [--diff] [--since <prior.json>] [--diff-only] — the OBSERVE step. Captures the target window's monitor to frames/snap-*.png, computes the blake3 frame_hash (the observation identity you pass to op_act as --frame — pass the FULL 64-char frame_hash read from snapshot.json; the value printed on stdout is TRUNCATED to 16 chars for display and will FRAME_STALE if copied literally), and dumps the UIA element tree to snapshot.json: each element has an integer id, role, name, bbox (physical px), center, interactable flag, provenance (uia_exact in OP-A), a geometry-independent runtime_id, and PATTERNS — the actionable UIA patterns actually present on it (invoke/toggle/selectionitem/expandcollapse/value|value_ro/legacy). PREFER an element whose patterns list is non-empty: op_act drives it via PATH 1 (no foreground). --window picks the target by title substring (default: current foreground). --monitor N (OP-B5) scopes the tree to physical monitor N only (default: all monitors); off-monitor elements are dropped, and the counts (element_count vs element_count_all) show how many. (--ocr is an OP-C1 stub and refuses in OP-A.) --diff / --since (CODEX-GAPS #3 a11y-tree diff): after writing the FULL snapshot.json (binding unchanged, backward compatible), also compute added/removed/changed vs the prior observation and write snapshot-diff.json (compact) + embed .diff in snapshot.json. Use --diff on repeat-loop re-observes to cut tokens; use --json --diff-only to print only the compact projection. First snapshot of a run with --diff reports mode=no_prior (full tree as "added").
  • op_act.py --run <dir> --frame <hash> [--rev <r>] [--intent "..."] [--mode shared|exclusive] [--monitor N] <subcommand> — the ACT step, frame-bound and fail-closed. Subcommands: click <element_id> actuate that element: PATH 1 via its UIA pattern (Invoke/Toggle/Select/ExpandCollapse/Legacy-DoDefaultAction, no foreground) when it has one, else PATH 2 coord-click at its center (ID-INDIRECTION: prefer this) click --xy X,Y PATH 2 coord-click at absolute screen coords (only when no element exists) type "<text>" [--element <id>] [OP-B7 ADDITIVE-ONLY] insert/append; NEVER clobbers existing non-empty content. Prefer TextPattern end-append (PATH 1-ish), else SetFocus+End+SendKeys, else ValuePattern.SetValue ONLY on a genuinely EMPTY field (zero-length; whitespace counts as non-empty). ValuePattern-only + non-empty is mutation-gated (PRE_IMAGE + CONFIRM_REQUIRED / overwrite lease) — prefer explicit replace for overwrite. With no --element: End+SendKeys append into the target window (PATH 2). replace "<text>" --element <id> [OP-B7] the ONLY clobbering text path. Always PRE_IMAGE + mutation-gated when the field is non-empty (class=overwrite via op_arm). Never silent overwrite. keys "<chord>" foreground SendKeys chord, e.g. keys "{Ctrl}s" (PATH 2) drag --from X,Y --to X,Y (PATH 2) scroll --xy X,Y --amount N N wheel notches (+up / -down) (PATH 2) focus-window "<title-substr>" activate a window (the one action that legitimately changes foreground) menu-select --element <parent_id> --child "<name>" [--child-role <ControlTypeName>] atomically open a parent menu flyout AND invoke a named child in ONE process (both PATH 1 UIA-pattern, no foreground). Use this for transient WinUI flyouts (Notepad File > 'Save as') that auto-dismiss between two separate op_act calls: opening File in one call and invoking 'Save as' in a second call races the flyout closing (GEOMETRY_MISMATCH). menu-select opens the parent (uia_identity gate on the snapshot element), settles, re-walks the LIVE UIA tree via a global search (the flyout is a separate popup window) to find the child by name/role, verifies it exposes a click pattern, invokes it — journals TWO uia_identity rows (menu-open + menu-select). Refuses NO_MENU_PARENT_PATTERN / MENU_CHILD_GONE / NO_CHILD_PATTERN fail-closed. secondary <element_id> --action <name> [CODEX-GAPS #4] invoke a NAMED secondary UIA action on the element (PATH 1 uia_identity). Actions: expand | collapse | expandcollapse | toggle | select | scroll | raise | setfocus | invoke (case-insensitive; PatternName aliases like ExpandCollapse / SelectionItem accepted). Scroll uses ScrollPattern if present else ScrollItem.ScrollIntoView; Raise/SetFocus are SetFocus-as-secondary (+ WindowPattern raise when available). Missing pattern -> PATTERN_UNAVAILABLE. If the control Name/AutomationId matches DESTRUCTIVE_LEXICON, still runs enforce_mutation_gate (PRE_IMAGE + CONFIRM_REQUIRED / lease) — same as click. pattern <element_id> --name <PatternName> alias of secondary using --name (ExpandCollapse|Toggle|SelectionItem|Scroll|Raise|SetFocus|Invoke|...). On success op_act auto-captures the after-frame, writes a crosshair evidence crop, and appends the OK journal row (result carries actuation_method + binding_kind + mode). On any mismatch it REFUSES: exit code 3, a journaled REFUSED row, and ZERO actuation.

THE LOOP (snapshot -> decide ONE action -> act -> verify delta -> repeat)

  1. op_run.py new --slug <task> ; keep the run dir. Decide --mode (shared default; exclusive only if the owner handed the machine over) and --monitor if scoping.
  2. op_snapshot.py --run <dir> --window "<title>" [--monitor N] ; read snapshot.json. SEE the app from elements[] + the frame. You do NOT need to foreground the target for a PATH-1 (patterned) action; only foreground it (op_act ... focus-window "<title>") when you must use a PATH-2 coord/SendKeys action.
  3. Decide exactly ONE action from the interactable elements + state. Pick an element id whose patterns list is non-empty (PATH 1, no foreground), never raw coordinates, when such an element exists (ID-indirection). Never derive a COUNT of anything from the pixels/VLM — counts come from snapshot.json's element list.
  4. op_act.py --run <dir> --frame <snapshot.frame_hash> [--mode ...] [--monitor N] --intent "<why>" <action> . Immediately after the snapshot, so the binding still matches. Read <snapshot.frame_hash> as the FULL 64-char "frame_hash" field in snapshot.json — never the truncated 16-char hash op_snapshot prints to stdout (that is display-only and refuses FRAME_STALE if passed verbatim).
  5. If op_act REFUSES (exit 3): RE-OBSERVE, never retarget. Re-run op_snapshot to get a fresh frame and decide again. A refusal means the world moved under you (frame binding / element re-resolution working) — a fresh snapshot is the fix, not a different --frame or hardcoded coordinates. A HUMAN_ACTIVE refusal in shared mode means the owner is using the PC: wait for idle or switch to a PATH-1 patterned target (which is never idle-gated).
  6. Verify the delta: re-snapshot; confirm the element/state changed as intended (new window, edited text, changed selection). The after-frame + crop are your evidence.
  7. Repeat until the task's ground-truth artifact exists (a saved file, an installed tool, a changed setting read back). Then op_run.py finalize.

FRAME BINDING (the load-bearing property for PATH 2 — understand it or you will fight it) Every PATH-2 op_act cites --frame <hash>, the frame_hash of the snapshot you decided from. op_act refuses unless ALL hold (a PATH-1 patterned action binds by UIA identity instead — see the UIA-PATTERN-FIRST section — so it is gated by ELEMENT_GONE/ELEMENT_MISMATCH/GEOMETRY_MISMATCH/WINDOW_GONE, not foreground/pixels): FRAME_STALE the hash is the CURRENT snapshot's (a stale/replayed hash from before a newer snapshot refuses) GEOMETRY_MISMATCH the target window has not moved/resized/minimized since the observation FOREGROUND_MISMATCH the target window is still the foreground window (except focus-window) FRAME_MISMATCH the ACTED-ON REGION has not MATERIALLY changed since the observation (for an element/id click that region is the element's own bbox, for an --xy action a bounded box around the point, else the whole window; benign churn like a blinking caret passes, and unrelated churn in a far corner of the same window no longer blocks a static control; a different screen still refuses via this leg plus the FRAME_STALE + geometry + foreground gates — this is the replay defence) Consequence for you: snapshot, then act NOW, keep the target foreground FOR PATH-2 actions, and re-observe on any refusal. Do not add sleeps that let the window change; do not reuse a --frame across two actions.

REPLAY DEFENCE — RATIFIED SEMANTICS (OP-B2, accepted 2026-07-12) The frame binding is a HYBRID, NOT an exact-pixel re-hash (a real Windows window is never byte-static — caret blink + sub-pixel AA + async repaint churn every frame, so an exact live re-hash would refuse every legitimate action). Replay/staleness is defeated by FOUR independent legs, ratified as the accepted model: (1) FRAME_STALE — the blake3 frame_hash is the OBSERVATION IDENTITY token; the caller must cite the CURRENT snapshot's hash, so a replayed OLD hash refuses (this now holds on the PATH-1 element path too — the OP-B2 tautology fix threads the caller's --frame into verify_element_binding); (2) GEOMETRY — the target window has not moved/resized (GEOMETRY_TOLERANCE_PX); (3) FOREGROUND — the target is still foreground (PATH-2 only; PATH-1 is foreground-immune by design); (4) MATERIAL-CHANGE FRACTION — the acted-on region changed by <= MATERIAL_CHANGE_THRESHOLD. The threshold is MEASURED, not guessed: benign caret/AA churn <=0.47%, a typed short string 1.14%, a select-all highlight 6.19% — clean separation, so the constant sits at 0.8% (op_common.MATERIAL_CHANGE_THRESHOLD; scope-retune knobs MATERIAL_ELEMENT_MARGIN_PX / MATERIAL_XY_RADIUS_PX; per-pixel band MATERIAL_PIXEL_DELTA). All four legs are proven re-runnable in .Codex/tools/operator/op_b2_negatives.py (stale-hash / window-switch / geometry-change / screen-off+occlusion each actuate NOTHING and journal the refusal). To retune, change the named constant in op_common and say why.

CAPTURE BACKEND (OP-B8 WGC, F-5 occlusion fix) op_snapshot AND op_act's PATH-2 material/after re-capture prefer Windows.Graphics.Capture per-window (windows-capture==2.0.0, MIT) when a target hwnd is known, then fall back to mss full-monitor BitBlt. WGC is occlusion-proof (composites the window's own surfaces even when another app covers it — the F-5 shared-mode root fix). Evidence frames stay in monitor-pixel space (WGC window pixels are pasted onto a monitor-sized canvas) so crop_to_window / material-region / OCR math stay identical. snapshot.json records capture_backend: "wgc"|"mss". The blake3 window-crop frame_hash is still the OBSERVATION IDENTITY token (PATH-1 UIA identity binding is unchanged and never depends on pixels). Caveats: WGC may flash a yellow capture-consent border while the session is active (Windows consent signal; draw_border=False is requested but older builds may still draw it); protected/DRM/elevated windows can refuse capture and force mss; OS older than Win10 1903 / missing Graphics Capture => mss. mss is REQUIRED fallback and is not removed. z-order multi-region layered capture is a follow-up (not this unit).

DENY-OWN-WINDOW (hardened self-actuation defence, OP-B2) op_act refuses DENY_OWN_WINDOW for ANY target — an element/window OR a raw --xy point — that RESOLVES inside the operator's OWN console/terminal or any top-level window of the process running the operator, so it can never type commands into its own terminal (the self-actuation loop, REDTEAM attack 3). Two legs, both keyed to the window the input would ACTUALLY reach (not mere geometric overlap, which would false-positive when the operator's own console merely sits behind the target): (a) the target element's owning window is ours (the self-snapshot case); (b) the live topmost window under the coordinate is ours (WindowFromPoint -> top-level root, matched against our own window set or our own PID). HONEST LIMIT: under Windows Terminal the visible host window is a separate process and GetConsoleWindow returns a hidden pseudo-console, so the classic-conhost case is clean but a WT visible window is not covered by the own-PID legs — prefer PATH-1 (no coordinates) there.

ACTUATION IS UIA-PATTERN-FIRST (doctrine addendum; the shared-desktop reality) Inside a GUI app the programmatic seam (SEAM-FIRST rule) is the element's OWN UIA control pattern. op_act has two paths and picks automatically per target: PATH 1 (PRIMARY, binding_kind uia_identity) — a uia_exact element with a usable pattern is actuated THROUGH that pattern: InvokePattern.Invoke / TogglePattern.Toggle / SelectionItemPattern.Select / ExpandCollapse / Legacy DoDefaultAction for a click; for text: TextPattern append or ValuePattern.SetValue ONLY on empty fields (type --element <id>, OP-B7 additive), and ValuePattern.SetValue for explicit replace (mutation-gated when non-empty). These need NO foreground and NO physical mouse, so they run WHILE THE OWNER USES A DIFFERENT WINDOW and are immune to Windows' system-wide foreground lock. The safety binding is ELEMENT RE-RESOLUTION by UIA RuntimeId + role/name/geometry (not a pixel hash); there is no foreground gate and no material-pixel gate on this path — only ELEMENT_GONE / ELEMENT_MISMATCH / GEOMETRY_MISMATCH / WINDOW_GONE. Before/after frames + the crop are still captured. PATH 2 (FALLBACK, binding_kind frame_pixel) — pixel-only / no-usable-pattern targets (--xy, scroll, drag, foreground SendKeys, and any element whose only patterns are inert). Keeps the FULL frame gate above (FRAME_STALE + whole-window GEOMETRY + hard FOREGROUND + F-1 material-change). The foreground grab is hardened (SPI foreground-lock clear + pywinauto AttachThreadInput + bounded re-assert). Prefer an element id with a non-empty patterns list (op_snapshot lists them). "I coord-clicked a control that had an Invoke pattern" is a wrong-lane finding.

OPERATING MODES (--mode, OP-B6) --mode shared (DEFAULT, owner-present "background"): PATH 1 runs freely (no collision with the owner). PATH 2 (coord/foreground) is HUMAN-IDLE GATED — it acts only after >= a short idle window (GetLastInputInfo) of no human keyboard/mouse input and ABORTS HUMAN_ACTIVE if a human input tick appears between the observation and the actuation, so the operator never fights the owner for the mouse. This is the run-around-the-owner mode. --mode exclusive (unattended / owner asleep): the human-idle gate is OFF; PATH 2 may drive fully. UIA-pattern PATH 1 is still preferred. Use this only when the owner has handed the machine over. The mode is recorded in run.json and every journal row. Destructive --class stays dark in BOTH modes; mode NEVER relaxes the destructive gate.

MONITOR SCOPING (--monitor N, OP-B5) Pass --monitor N to op_snapshot AND op_act to work only on physical monitor N: op_snapshot drops off-monitor elements; op_act refuses OFFSCOPE_TARGET for a target off monitor N (per-physical rects, never the mss virtual union). Combined with PATH 1 this lets the owner keep monitor 1 while the operator drives monitor-2 apps via patterns without ever stealing focus. HONEST LIMIT: Windows foreground is one-per-SESSION, so a second monitor is spatial isolation, not a separate foreground; true focus-blip-free parallelism for PATH-2 work needs a separate session (parked sandbox lane).

DESTRUCTIVE CLASSES + ARMING (OP-B1) + CONTENT SAFETY (OP-B7) Delete, overwrite-outside-scratch, install, purchase, send/submit, registry/config mutation are DESTRUCTIVE classes (full taxonomy + one-line definitions: .Codex/tools/operator/DESTRUCTIVE-CLASSES.md). An op_act action tagged --class <destructive> is DARK unless a valid, unexpired, OWNER-CONFIRMED arm lease exists for that class — otherwise it refuses CLASS_UNARMED (unarmed / only a pending request) or LEASE_EXPIRED (TTL elapsed, checked INLINE at dispatch). The gate runs ahead of actuation on BOTH paths and in BOTH modes; mode NEVER relaxes it. An armed destructive actuation additionally records a bounded per-action video (op_run ffmpeg) on top of the before/after frames; if ffmpeg is unavailable it refuses VIDEO_UNAVAILABLE (fail-closed, no un-recorded destructive actuation). OP-B7 NEVER-CLOBBER (content safety, fail-safe independent of caller --class):

  • type is additive (insert/append). replace is the only clobbering text path.
  • op_act AUTO-DETECTS mutating intent from the TARGET + action semantics: (1) content-clobbering replace/clear on a non-empty editable, (2) click on a control whose Name/AutomationId matches the destructive lexicon (Delete/Remove/Discard/Don't Save/Overwrite/Replace/Clear/Reset/Format/Uninstall/Erase/Trash — see DESTRUCTIVE_LEXICON in op_common / DESTRUCTIVE-CLASSES.md). The LLM CANNOT bypass by tagging --class benign; the tool re-derives intent and when uncertain, GATES.
  • A detected mutation journals a PRE_IMAGE row (current content/state, recoverable) THEN requires confirmation: shared -> CONFIRM_REQUIRED (owner runs op_arm arm/approve for the mapped class, typically overwrite or delete); exclusive -> armed owner-confirmed lease. Content-clobber reuses the existing overwrite class (CONTENT_MUTATION_CLASS); no taxonomy fork. Arming (op_arm.py, the two-phase + owner-confirm + dual-expiry lease store): op_arm.py arm <class> --ttl <s> request a lease. A destructive class does NOT grant on request — it writes a PENDING request (journaled) and BLOCKS. It grants only after the owner confirms. op_arm.py approve <class> THE OWNER runs this to grant the pending destructive lease(s) for <class>. This is the interactive confirmation; without it a destructive class never arms, even in --mode exclusive / unattended. op_arm.py disarm <class> drop all leases (pending + granted) for the class. op_arm.py status [--json] sweep expired leases + list active/pending (also shows kill-switch + env state). OWNER APPROVAL STEP (exact): after op_arm arm delete --ttl 300 prints PENDING, the OWNER grants it EITHER by running op_arm approve delete, OR out-of-band by creating the confirm sentinel file .Codex/tools/operator/arm-approvals/delete.approve and re-running op_arm arm delete --ttl 300 (which grants immediately and consumes the sentinel one-shot). Two-phase: the grant is journaled to arm-journal.jsonl BEFORE the granted lease is written. Dual expiry: a lease dies by TTL both via op_arm status's sweep AND inline at the moment op_act actuates. To actuate a destructive class: op_act --run <dir> --frame <hash> --class delete [--lease <id>] <action> (omit --lease to auto-resolve the active granted lease for the class). If a task needs a destructive class and no lease is armed, STOP and surface the arm request to the owner — never route around the gate.

KILL-SWITCH + INTERRUPT + ENV-COLLAPSE (OP-B4 + CODEX-GAPS #6, checked ahead of every actuation, both paths, both modes) OPERATOR_STOP kill-switch (sticky full halt): while the file .Codex/tools/operator/OPERATOR_STOP exists, the NEXT op_act refuses OPERATOR_STOPPED (zero actuation, journaled), regardless of any lease. Drop that file to halt the operator instantly; remove it to resume. (op_snapshot stays available — observation is read-only.) OPERATOR_INTERRUPT one-shot (per-actuation abort, does NOT replace OPERATOR_STOP): - Drop the file .Codex/tools/operator/OPERATOR_INTERRUPT, OR press physical Escape. - Escape is polled via ctypes GetAsyncKeyState (no new deps) at the top of every op_act; if held, the interrupt marker is dropped automatically. - op_act refuses OPERATOR_INTERRUPTED and CONSUMES the marker (one Escape / one file-drop aborts the CURRENT call only). Drop the file / press Escape again to interrupt the next call. OPERATOR_STOP still wins if both exist (checked first, sticky). - Owner kill recipes: sticky full halt: New-Item .Codex\tools\operator\OPERATOR_STOP (or touch) one-shot mid-turn: press Escape OR New-Item .Codex\tools\operator\OPERATOR_INTERRUPT resume after STOP: Remove-Item .Codex\tools\operator\OPERATOR_STOP Env-health collapse: an unhealthy host (no foreground window / workstation locked / screen-capture dead) collapses the tool surface to snapshot-only — op_act refuses ENV_UNHEALTHY while op_snapshot still works. On a refusal, do not retry actuation; observe, and resume when the host is healthy.

WHAT IS A FINDING (report it; "looks fine" is never a verdict — playtester discipline)

  • An exception dialog / error toast the app raised.
  • A softlock: the state does not change across several distinct actions, or the same screen oscillates (a control that does nothing, a flow that dead-ends).
  • A wrong-lane action: you pixel-drove a step a named seam (above) could have done — reroute and record it.
  • A screen that visibly looks broken (judge the crop, do not rubber-stamp; visual-ui-loop discipline). Anti-spiral bound: if the loop is stuck on the same screen for K actions (K ~ 5) with no state delta, log a finding and move on or stop. Do not keep poking. Same file/element re-tried >3 times for the same failure = stop and rethink.

REPORT FORMAT op_run.py finalize writes report.md: a summary (OK / refused / total rows), the full action table (seq / action / target / before->after frame / result), and a Findings section you fill with the exceptions/softlocks/dead-ends/wrong-lane items and the seq each came from. The journal.jsonl + frames/ + crops/ ARE the off-agent evidence: an auditor re-hashes the frames named on each row and replays the assertions without trusting this session's stdout. With --crystallize the report also names the minted replay.json / manifest.json.

CRYSTALLIZE + REPLAY LOOP (OP-E — the execution mode, doctrine #6) Exploration is expensive (model decides each step). Once a run has produced the ground-truth artifact with OK journal rows, crystallize it into a deterministic script and replay at near-zero reasoning cost; drift is detected by the same frame-binding refusals that protect live actuation.

  1. Explore: the normal snapshot->decide->act loop until the artifact exists; every OK actuation is journaled.
  2. Crystallize: op_run.py finalize --run <dir> --crystallize

    or: op_run.py crystallize --run <dir>

    or: op_record.py crystallize --run <dir>

    Writes <dir>/replay.json (steps) + <dir>/manifest.json (geometry/versions/catalog hash/taskspec_shape).
  3. Dry-parse (always, before a live re-walk): op_replay.py --run <dir> --dry-parse Exit 0 + status DRY_PARSE_OK means the script is structurally sound; no GUI touched.
  4. Replay into a FRESH run dir (or the same dir if you accept journal append): op_run.py new --slug <task>-replay ; set DEST=<that> op_replay.py --run %DEST% --replay <src>/replay.json --window "<app title>" Each step re-snapshots, resolves the UIA selector (runtime_id > role+name > recorded id), calls op_act with the fresh --frame. On FRAME_STALE / SELECTOR_UNRESOLVED / any ACT refuse / assertion fail: STOP, write replay-report.json + .md, exit 3. Do NOT invent a next step.
  5. Destructive steps in the script: unattended refuse REARM_REQUIRED. To replay them, owner arms+approves the class then pass --allow-destructive (op_act still checks the live lease). Mode never relaxes this.
  6. Drift proven: if the window layout / element tree changed since mint, resolve fails or frame binding refuses at the right step — that is success of the gate, not a script bug. Re-explore and re-crystallize; do not loosen selectors. Schema alignment for OP-F TaskSpec: see .Codex/tools/operator/notes/OP-E-TASKSPEC-SHAPE.md (transport=gui_operator, production_default=false, poll_fetch=journal/artifacts/recording). Deterministic suite: op_e_negatives.py.

MULTI-SEAT DISPATCH (OP-F ASTER seat leg) ASTER seats = separate Windows sessions (own foreground/mouse). Operator reaches seat 2 via per-seat op_runner + shared job contract under workflow/operator-jobs/, NEVER cross-session UIA from session 1.

  1. Prove contract same-session first: op_dispatch.py smoke-self (opens FRESH Notepad, additive type of unique marker, OP-B7 never-clobber).
  2. Start runner ON seat 2 (manual on that desktop is preferred; see notes/OP-F-SEAT-LAUNCH.md — schtasks /IT and psexec -i 2 are options, probe heartbeats/session-2.json to confirm the process is really in session 2).
  3. From session 1: op_dispatch.py submit --session 2 --kind smoke_notepad_type --marker SEED_SEAT2 --wait
  4. Pass = results/<id>.json status=completed + marker echoed; journal+frames under the run_dir the seat runner created. Gates: OPERATOR_STOP idles the runner (no claim); OPERATOR_INTERRUPT aborts current act; runner refuses destructive replace/delete without arm. Never kill seat-2 processes you did not start. Unity MCP from this agent session remains the seam-first path for scene work (session-agnostic IPC); seat-2 Unity GUI only via a live runner there.

OP-A3 SMOKE (what an independent session completes using ONLY this skill) Drive Notepad end to end and prove it with an on-disk artifact:

  1. op_run.py new --slug notepad-smoke ; keep the run dir.
  2. Launch Notepad (or focus an open one). focus-window "Notepad" to activate it; confirm it is foreground.
  3. snapshot -> type a UNIQUE marker string (op_act ... type "OPERATOR_SMOKE_<random> ").
  4. snapshot -> Save As: PREFER the atomic menu-select for the WinUI File flyout (op_act ... menu-select --element <File_menuitem_id> --child "Save as") since the flyout auto-dismisses between separate calls; alternatively keys "{Ctrl}{Shift}s" (PATH 2, needs foreground). Then in the Save-As dialog snapshot again, type a path/filename inside the session scratchpad, and click/keys the Save button (by element id). Save is not a destructive class here (writing a NEW file into the scratchpad); overwriting an EXISTING file outside scratch would be, and is dark.
  5. snapshot -> close Notepad.
  6. Then Explorer: focus/launch Explorer, navigate to the saved file's folder, confirm the file element appears. PASS = the saved file exists on disk with the exact marker bytes (the artifact the model did not author) AND every actuation has a matching frame-bound journal row AND before/after frames exist. If op_act refuses, re-observe (fresh snapshot) and continue — refusals are expected whenever focus or the window changed; they are the gate working, not a failure of the run.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.