The Codex runner turns a plain-language QA goal into a controlled Switchify manual scanning run.
npm run qa -- --goal "Open WhatsApp" --device <device-id> --max-steps 12The TypeScript parent runner owns every side effect:
- Visible emulator acquisition (or explicit headless launch); physical devices are excluded.
- Debug app build/install from the sibling Android repository.
- Package, bridge,
run-as, and accessibility-service preflight. - Switchify QA priming through
switchify-manual-scan-qa.ps1. - Screenshot, logcat, and window-state capture.
- Switchify debug bridge broadcasts.
- Run directory writes.
- Switchify preference and switch-mapping restoration.
Every normal run follows this order:
emulator -> build/install -> preflight -> backup/prime -> capture/scan -> final evidence -> restore -> summary
The default AVD is SwitchifyStorePhone and its window remains open after the run. --avd selects another AVD and --headless explicitly suppresses the window. --device accepts only a ready emulator-* serial.
The default preference mode is prime-and-restore. Restoration is attempted whenever a priming backup manifest exists, including partial prime failures, failed runs, and Ctrl+C/SIGTERM. A detached watchdog waits for the parent process and restores the backup if normal cleanup does not write cleanup-complete.json, covering forced task cancellation as well. --keep-qa-state changes the mode to prime-only; --no-prime changes it to none.
Child decisions default to gpt-5.6-sol. The runner invokes the repository-local @openai/codex dependency so the decision model does not depend on an older global CLI. Use --codex-model to test another supported model explicitly.
Each child Codex invocation is a read-only decision agent. It receives a prompt, the current screenshot, recent state, recent warnings, the action history, and the Switchify operating guide. It returns one JSON decision matching schemas/codex-decision.schema.json.
Child Codex runs use:
codex exec --ephemeral --sandbox read-only --ask-for-approval never
The child must not run ADB or shell commands. It can only choose one of:
next
previous
select
capture
stop
The parent validates the action before doing anything. Unknown actions fail the run and write a summary.
The default runner is intentionally single-switch-only. It does not allow system recovery actions such as Home or Back; getting stuck should produce findings instead of escaping the workflow.
Full bridge actions are available only when the parent is launched with:
npm run qa -- --goal "Find YouTube from the launcher and open it" --device <device-id> --action-profile fullFull mode allows:
next
previous
select
home
back
recents
quick_settings
notifications
lock_screen
media_play_pause
pause
stop_scanning
change_scanning_direction
toggle_gesture_lock
toggle_gesture_lock_rearm
toggle_gesture_repeat
reload_settings
capture
stop
The child prompt embeds switchify-operating-guide.md. A snapshot of that guide is also written to every run directory as switchify-operating-guide.md.
The guide explains:
- Switchify bridge actions and how they map to
SwitchAction. ScanTreelevels: row/tree item, group, and node.- Initial manual-scan setup behavior.
- Scan highlight colors and how to read primary versus secondary highlights.
- ScanTree escape versus Android Back.
- Keyboard escape and
Scan Keyboardbehavior. - Switchify menu behavior and common decision patterns.
- Android repo search hints for weird findings.
When debugging a bad agent decision, inspect both step-NNN/codex-prompt.md and the run-level switchify-operating-guide.md.
Runs are stored under runs/, which is ignored by git.
runs/
2026-06-20_09-30-00_open-youtube/
manifest.json
goal.md
summary.md
findings.md
events.jsonl
switchify-operating-guide.md
final/
screen.png
state.json
step-000/
screen.png
state.json
state.md
logcat.txt
dumpsys-window.txt
codex-prompt.md
codex-decision.json
codex-events.jsonl
events.jsonl records parent-side actions. findings.md aggregates strange behavior reported by child Codex. final/ captures the post-decision device state immediately before restoration, including when the run reaches its step limit. summary.md is the handoff artifact for another debugging agent.
Manifest version 2 records the emulator/AVD, whether the runner launched it, visible versus headless mode, build/install and preflight states, the QA outcome, and the cleanup outcome. The summary keeps QA and cleanup failures separate so a restore failure cannot hide the original test result.
The child returns:
{
"observation": "What is visible now.",
"action": "next",
"rationale": "Why this action is best.",
"success": false,
"confidence": 0.75,
"weirdFindings": []
}Findings include severity, expected behavior, actual behavior, evidence, and optional Android repo search hints.
When a run sees strange behavior, the runner keeps going by default. At the end, summary.md includes a follow-up prompt that points to:
- The QA run directory.
events.jsonl.findings.md.- Step screenshots and logs.
- The local Switchify Android repo path when configured.
That summary can be handed to another agent to inspect switchify-android and find the likely bug.
- The runner does not use
uiautomator dumpinside the Codex loop because it can disrupt accessibility service state. - Child Codex success is visual/state based, not a deterministic assertion engine.
- The installed debug APK and emulator are retained after cleanup; only Switchify mappings and preferences are restored.
- The parent defaults to the Switchify debug bridge actions needed for single-switch QA. System and reload actions require
--action-profile full.