One emulator pool. One CPU budget. Every Codex task plays fair.
Android Resource Orchestrator is a local-first Codex plugin that coordinates Android emulators and Gradle CPU usage across concurrent ChatGPT/Codex desktop tasks.
It solves two common problems on Android development machines:
- Two Codex tasks install, launch, or inspect different apps on the same emulator.
- Several Gradle builds start together and saturate every CPU core.
The plugin transparently intercepts managed shell commands through Codex lifecycle hooks. It keeps project repositories untouched and has no external service or runtime dependency beyond Python's standard library.
| Capability | Behavior |
|---|---|
| Global Gradle queue | Shares one weighted CPU budget across all Codex tasks |
| Turn-scoped device leases | Keeps install, launch, interaction, screenshots, and logs on the same device |
| Adaptive CPU policy | Calculates capacity from the host's logical CPU count |
| Explicit target support | Honors adb -s <serial> and ANDROID_SERIAL |
| Crash recovery | Reclaims stale jobs and expired device leases automatically |
| Local-only state | Uses SQLite under the local Codex home; no telemetry or network calls |
| Zero project changes | Does not edit gradle.properties, Gradle wrappers, or Android projects |
flowchart LR
A[Codex task A] --> H[PreToolUse hook]
B[Codex task B] --> H
C[Codex task C] --> H
H --> D{Command classifier}
D -->|Gradle| Q[Weighted CPU queue]
D -->|adb or connected test| L[Device lease pool]
Q --> R[Run original command]
L --> R
R --> F[Release CPU capacity]
F --> S[Stop hook releases device]
The PreToolUse hook recognizes Gradle and device-affecting Android commands, then rewrites them through a local scheduler. The scheduler never stores the original command text.
Gradle capacity is held only while a command runs. Device leases are held for the entire Codex turn so another project cannot take the device between installation and visual verification. Stop and SubagentStop hooks release leases; a TTL handles abnormal termination.
The default CPU budget scales with the machine:
reserved cores = max(1, round(logical cores × 20%))
CPU budget = max(1, logical cores - reserved cores)
| Logical cores | Reserved | CPU budget |
|---|---|---|
| 2 | 1 | 1 |
| 4 | 1 | 3 |
| 8 | 2 | 6 |
| 10 | 2 | 8 |
| 16 | 3 | 13 |
Commands consume that budget as follows:
| Work class | Examples | Default weight | Expected concurrency |
|---|---|---|---|
| Release | bundleRelease, assembleRelease, release publishing and lint |
Full budget | 1 |
| Build | tests, lint, assemble, build, check, compile, install | Half budget | About 2 |
| Light | Gradle help, tasks, properties, dependency queries | Quarter budget | About 4 |
The same weight is passed to Gradle as org.gradle.workers.max; managed Gradle processes also request low Gradle priority. You can override every value in the generated policy file.
- Connected emulators are preferred over physical devices.
- A command with
adb -s <serial>waits for that exact target. - A command with no target receives the first connected, unleased emulator.
- A turn reuses its existing lease, preventing deadlocks during a multi-command QA flow.
- Read-only host commands such as
adb devices,adb version, andadb helpdo not consume a device lease. - Connected/instrumented Gradle tasks acquire both CPU capacity and a device lease.
- ChatGPT/Codex desktop or Codex CLI with plugin hooks enabled
- Python 3.10 or newer
- Android SDK Platform Tools (
adb) available onPATH - A Gradle wrapper in each Android project
The scheduler supports macOS, Linux, and Windows. The command it runs still inherits the active Codex sandbox and approval policy; the plugin never bypasses those controls.
This repository is a Codex plugin marketplace. Copy its HTTPS URL from GitHub, then register it once from a terminal:
git clone <repository-url>
codex plugin marketplace add ./codex-android-resource-orchestratorThen install from the ChatGPT/Codex desktop app:
- Restart the app and open Plugins.
- Select Android Resource Orchestrator as the marketplace.
- Install Android Resource Orchestrator.
- Open a new Codex task and review/trust the three local hooks.
CLI installation is also available after adding the marketplace:
codex plugin add android-resource-orchestrator@android-resource-orchestratorThere is no new command to remember. Ask Codex to build or test Android projects normally:
Run the debug tests and assemble the app.
Install it and verify the login screen on an emulator.
Build the release bundle.
When scheduling is needed, command output includes messages such as:
[android-orchestrator] queued gradle-release (8/8 CPU units)
[android-orchestrator] running android-device for sample-app on emulator-5554
Ask Codex Show Android Resource Orchestrator status to inspect active jobs and leases.
The first managed command creates:
$CODEX_HOME/android-resource-orchestrator/policy.json
If CODEX_HOME is not set, the fallback is ~/.codex/android-resource-orchestrator/policy.json.
Example policy:
{
"total_cpu_units": 8,
"release_cpu_units": 8,
"normal_cpu_units": 4,
"light_cpu_units": 2,
"device_lease_ttl_seconds": 1200,
"running_job_stale_seconds": 45,
"poll_interval_seconds": 1.0,
"wait_timeout_seconds": 3600
}All CPU weights must be positive and no command weight may exceed total_cpu_units. Policy changes are loaded by the next managed command.
To inspect a development checkout directly:
python3 plugins/android-resource-orchestrator/scripts/orchestrator.py status
python3 plugins/android-resource-orchestrator/scripts/orchestrator.py policySet CODEX_ANDROID_ORCHESTRATOR_HOME to use a different state directory.
- No network requests or analytics are implemented.
- Original command text is passed in memory and is not persisted in SQLite.
- Persistent state contains job identifiers, process IDs, resource weights, device serials, and project directory basenames.
- Runtime state stays outside project repositories under the local Codex home.
- Hook definitions must be explicitly trusted by Codex.
- The plugin does not grant extra filesystem, network, device, or shell permissions.
See SECURITY.md for vulnerability reporting.
- Only commands issued through supported Codex local function-tool paths are coordinated.
- Android Studio, ordinary terminal sessions, CI runners, and external scripts are not intercepted.
- Commands embedded in unusually complex shell syntax may be conservatively classified as managed.
- An unavailable explicitly requested emulator remains queued until it appears or the timeout expires.
python3 -m unittest discover -s plugins/android-resource-orchestrator/tests -v
python3 scripts/check_public.pyThe test suite covers classification, adaptive CPU sizing, weighted capacity, device reuse, stale-resource behavior, and single-device deadlock prevention.
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request.
Released under the MIT License.