Configurable, Fail-Closed Windows Tray Rotator for Resource-Heavy Desktop Apps
Time-slicing local AI applications (Codex Desktop, Claude Desktop, Antigravity IDE) to eliminate VRAM exhaustion, GPU contention, and background runaway execution.
- 1. Why This Exists
- 2. Architecture & State Machine Flow
- 3. State & Cycle Dynamics
- 4. Safe Process Scoping & AppX Launching
- 5. External Codex Safe-Start Contract
- 6. System Tray & Native Settings UI
- 7. Per-User Desktop Installation
- 8. Key Governance & Runtime Invariants
- 9. End-to-End Execution Lifecycle
- 10. Sibling Tools & Ecosystem Matrix
- 11. Installation & CLI Usage
- 12. Configuration & Schema Migration
- 13. Security & Zero-Egress Privacy
- 14. Development & Verification
Modern agentic engineering workflows rely heavily on local, autonomous AI desktop environments—specifically Codex Desktop, Claude Desktop, and Antigravity IDE. When executed concurrently or left unattended, these environments produce severe system contention:
- VRAM Exhaustion & GPU Thrashing: Local inference engines and GPU-accelerated WebView UI layers compete for dedicated video RAM, causing driver resets, frame drops, and severe latency spikes.
- Memory Leaks & CPU Contention: Unbounded background execution and continuous indexing loops consume excess system memory, starving adjacent compilation and development tools.
- Runaway Background Automation: Subagents, file watchers, and MCP servers spawned by inactive desktop environments continue executing in the background, creating unintended race conditions on shared repositories.
- Thermal Throttling & Power Waste: Concurrent heavy desktop clients force workstations into sustained maximum power states, degrading thermal headroom.
app-rotator solves these problems through disciplined, time-sliced desktop orchestration. It operates as a local Windows tray controller that strictly serializes desktop applications: exactly one configured heavy app runs at any time. When an app's scheduled allotment expires, App Rotator gracefully terminates it, enters an all-closed inter-app gap to allow full VRAM and garbage collection release, and launches the next configured provider.
The shipped configuration is deliberately disabled and set to dry-run by default. It logs intended actions but cannot launch or terminate processes until explicitly enabled by the user.
The following architecture diagram illustrates the decoupled subsystems of app-rotator:
flowchart TD
subgraph UI["1. User Controls & System Tray"]
Tray["pystray Tray Icon<br/>(Menu: Play / Pause / Stop / Settings / Exit)"]
GUI["Native Tkinter Settings GUI<br/>(Minute-Based Configuration & Reordering)"]
CLI["CLI Commands<br/>(app-rotator run / play / pause / stop / status)"]
end
subgraph IPC["2. IPC & State Persistence"]
Mailbox["Atomic Mailbox<br/>(%LOCALAPPDATA%/AppRotator/commands.jsonl)"]
State["Atomic Engine State<br/>(%LOCALAPPDATA%/AppRotator/state.json)"]
Events["Audit Log<br/>(events.jsonl)"]
Lock["Single-Instance File Lock<br/>(app-rotator.lock)"]
end
subgraph Core["3. Engine Orchestrator & Loop Core"]
Engine["RotatorEngine<br/>(Phase Timer & Countdown)"]
Queue["Active Rotation Queue<br/>(Enabled Providers at Index N)"]
Gap["Inter-App & Cycle Gap Handler<br/>(All-Closed Cooldown)"]
end
subgraph ProcessMgr["4. Safe Process Scoping & Launchers"]
Matcher["Process Matcher<br/>(psutil: Exe Name + Strict Path Constraint)"]
AppX["AppX Shell Launcher<br/>(explorer.exe shell:AppsFolder/AUMID)"]
Killer["Graceful Process Terminator<br/>(Scoped PID Termination)"]
end
subgraph ExtCtrl["5. External Delegation Authority"]
CodexCtrl["External Provider Controller<br/>(pause-all / stagger-resume / cancel)"]
end
Tray --> Mailbox
GUI --> State
CLI --> Mailbox
Mailbox --> Engine
Lock -.-> Engine
Engine --> State
Engine --> Events
Engine --> Queue
Queue --> Matcher
Queue --> AppX
Engine --> Gap
Gap --> Killer
Engine --> CodexCtrl
classDef ui fill:#2563eb,stroke:#1d4ed8,color:#ffffff;
classDef ipc fill:#7c3aed,stroke:#6d28d9,color:#ffffff;
classDef core fill:#059669,stroke:#047857,color:#ffffff;
classDef proc fill:#d97706,stroke:#b45309,color:#ffffff;
classDef ext fill:#dc2626,stroke:#b91c1c,color:#ffffff;
class Tray,GUI,CLI ui;
class Mailbox,State,Events,Lock ipc;
class Engine,Queue,Gap core;
class Matcher,AppX,Killer proc;
class CodexCtrl ext;
App Rotator enforces a deterministic state machine across four primary lifecycle states:
-
Stopped: All managed apps are confirmed closed. The loop index is reset to
0. Clicking Play closes any residual processes and starts provider0with a fresh timer. -
Running: The engine actively manages a phase. In an
appphase, the current provider process is monitored. In agaporcycle_pausephase, all apps remain strictly closed. - Paused: Clicking Pause closes the active app immediately, dispatches a cancellation signal to external controllers, and freezes the loop state—retaining the active phase, provider index, and exact remaining seconds.
-
Play from Paused: Resumes the exact frozen phase and remaining countdown. If resumed during an
appphase, the provider is launched fresh. -
Stop / Aus: Gracefully closes the active app, dispatches external controller
cancel, flushes state, and resets the loop index to0. -
Automatic Overall Stop: An optional active runtime watchdog (
overall_stop_seconds) triggers the same safe Stop operation after total cumulative active operation. Time spent paused does not deplete the overall budget. -
Loop Progression Sequence:
$$\text{App Phase } N \longrightarrow \text{Inter-App Gap} \longrightarrow \text{App Phase } N+1 \longrightarrow \dots \longrightarrow \text{Cycle Pause} \longrightarrow \text{App Phase } 0$$
All state changes write atomically to %LOCALAPPDATA%\AppRotator\state.json via temporary files and atomic replacement (os.replace), ensuring that abrupt power interruptions cannot corrupt state files.
Terminating processes on an operating system carries severe risks if selectors are loosely defined. App Rotator enforces strict, fail-closed scoping rules:
- Dual Criteria Requirement: An app configuration must specify both a target executable name (
ChatGPT.exe) and exactly one path constraint (path_exactorpath_contains). - Zero Wildcards on Unchecked Executables: Processes without readable executable paths or running under elevated foreign security tokens are completely ignored.
- CLI / Developer Tool Immunity: By restricting Codex Desktop matching strictly to
WindowsApps\OpenAI.Codex_, any command-line tools such as npmcodex.exeor terminal instances running from developer directories remain completely outside the kill scope. - AppX Packaging Protocol: Modern UWP/MSIX packaged apps cannot be launched via simple direct
.exepaths due to Windows sandboxing. App Rotator launches them via the Windows Shell AppFolder protocol:Default built-in AUMIDs include:explorer.exe shell:AppsFolder\<AUMID>- Codex Desktop:
OpenAI.Codex_2p2nqsd0c76g0!App - Claude Desktop:
Claude_pzs8sxrjxfjjc!Claude - Antigravity: Targeted via verified absolute binary path (
path_exact)
- Codex Desktop:
App Rotator strictly adheres to the Single Responsibility Principle: it never parses, modifies, or writes to internal provider files like automation.toml.
Instead, provider-specific orchestration is delegated to an unbundled, user-specified external executable controller:
controller.exe pause-all
controller.exe stagger-resume --interval-seconds 60
controller.exe cancel
- Prior to Codex Launch: The engine invokes
controller.exe pause-allto ensure no background tasks trigger simultaneous executions. - Post-Launch Staggering: Once Codex initializes, the engine invokes
controller.exe stagger-resume --interval-seconds 60to reactivate automations progressively without overwhelming system resources. - On Phase Exit / Pause / Stop: The engine dispatches
controller.exe cancelto immediately abort any pending reactivation requests. - Fail-Closed Missing Controller Policy: If the configured controller binary is absent:
missing_behavior: "block"(Default): The engine halts rotation, transitions to an alert state, and logs a clear explanatory reason.missing_behavior: "skip": The engine explicitly skips the Codex stage and proceeds safely to the next provider.- Dry-Run: Records contract calls to the audit log without executing subcommands.
The desktop user interface is designed for zero intrusion and native responsiveness:
- System Tray Icon (
pystray):- Color-coded icon states reflecting engine status (Idle, Active, Paused, Cooldown).
- Context menu displays current provider, active phase, and remaining countdown.
- One-click commands: Play, Pause, Stop, Settings, and Exit.
- Native Settings GUI (
tkinter):- Entirely local, responsive configuration editor.
- Minute-Based Inputs: All user-facing durations (provider runtime, inter-app gaps, cycle pauses, overall runtime, and reactivation intervals) are entered and displayed in minutes for human convenience. Internally, the engine computes and stores exact seconds.
- Provider Management: Dedicated checkboxes (Enabled / in rotation) permit temporarily disabling specific providers without deleting their configuration. Built-in providers (Codex, Claude, Antigravity) cannot be accidentally deleted and can be reordered seamlessly.
App Rotator provides an unprivileged, per-user installation script:
.\scripts\install-desktop-shortcut.ps1- Zero Administrator Elevation: Installs entirely inside the current user profile under
%LOCALAPPDATA%\Programs\AppRotator. - Desktop Shortcut Independence: Resolves the true Windows Shell Desktop folder path via the Windows API, creating a permanent desktop icon that functions independently of repository locations or cloud synchronization folders.
- Automatic Environment Scaffolding: Provisions an isolated per-user Python virtual environment, installs App Rotator in user mode, copies packaged high-resolution application icons, and seeds the initial fail-closed configuration.
The following 10 invariants govern every operation of app-rotator:
| Guarantee | Invariant Code | Enforcement Mechanism | Verification Rule |
|---|---|---|---|
| 1. 100% Local-First Privacy | INV-LOCAL-01 |
Zero telemetry, analytics, or network socket bindings. | Code inspection confirms zero outbound HTTP/network requests. |
| 2. Unprivileged Execution | INV-UNPRIV-02 |
Standard user context (RunAsInvoker), no elevation. |
SECURITY.md and runtime checks verify non-elevated operation. |
| 3. Fail-Closed Dry-Run | INV-DRYRUN-03 |
Default config delivered with enabled: false, dry_run: true. |
Clean installation test verifies no process actions without opt-in. |
| 4. Strict Path Scoping | INV-SCOPING-04 |
Process matching requires executable name + path constraint. | psutil audit rejects unmatched or ambiguous process names. |
| 5. Atomic Persistence | INV-ATOMIC-05 |
State and mailbox writes execute via temporary file + replace. | Power-loss fault tolerance tests confirm non-corrupting writes. |
| 6. Provider Delegation | INV-DELEGATION-06 |
External provider automation controlled via external CLI. | Zero direct reads or modifications of provider internal configs. |
| 7. Single-Instance Mutex | INV-LOCKFILE-07 |
Exclusive file lock (app-rotator.lock) on run and tray. |
Second process launch aborts immediately with exit code 0/1. |
| 8. Non-Invasive AppX | INV-AUMID-08 |
UWP/MSIX activation via standard Windows Shell AppFolder. | Subprocess invocations use fixed parameter vectors without shell. |
| 9. Cloud-Sync Resilience | INV-SYNC-09 |
Runtime state below %LOCALAPPDATA%; .gitignore conflict filters. |
State path outside OneDrive; .gitignore contains sync patterns. |
| 10. 48h Security SLA | INV-SLA-10 |
48h vulnerability acknowledgment and 5-day triage commitment. | Documented in SECURITY.md and validated in contract tests. |
The following sequence diagram traces an end-to-end execution cycle:
sequenceDiagram
autonumber
actor User as Desktop User / CLI
participant Tray as Tray / Mailbox IPC
participant Engine as Rotator Engine Core
participant Ctrl as External Controller
participant Apps as Managed Desktop App (AppX / Win32)
participant State as Persisted State & Audit Log
User->>Tray: Click "Play" or execute CLI play
Tray->>Engine: Enqueue mailbox command {action: "play"}
Engine->>Engine: Acquire file lock & load active providers
Engine->>State: Atomic write state.json (status: "running", phase: "app", index: 0)
alt Provider requires External Automation Controller (e.g. Codex)
Engine->>Ctrl: Invoke controller.exe pause-all
Ctrl-->>Engine: Confirm automations paused
end
Engine->>Apps: Launch active app via explorer.exe shell:AppsFolder/<AUMID>
alt Provider requires Staggered Reactivation
Engine->>Ctrl: Invoke controller.exe stagger-resume --interval-seconds 60
end
loop Active App Duration (Minute Countdown)
Engine->>State: Tick remaining seconds & append events.jsonl
end
Engine->>Apps: Match strict path & gracefully terminate PID
alt External Controller Active
Engine->>Ctrl: Invoke controller.exe cancel
end
Engine->>State: Transition to phase: "gap" (all apps closed)
loop Inter-App Gap Cooldown
Engine->>State: Wait configured gap seconds (VRAM & RAM purge)
end
Engine->>Engine: Increment index to next enabled provider
Engine->>State: Transition to next app or cycle pause
app-rotator operates within the dev-bricks software family under the open-bricks open-source ecosystem:
| Repository | Domain & Purpose | Integration with App Rotator |
|---|---|---|
| WikiStub-Seed | World-Building & Markdown Documentation Tools | Provides structured technical knowledge bases. |
| PrivacyMailDesk | Local-First Private Email Triage & Review | Zero-egress local communication desk. |
| githubbot | Multi-Repository GitHub Ecosystem Orchestration | Manages releases, metadata parity, and CI hygiene. |
| system-auditor | Multi-Host Audit Engine & Lock Governance | Audits file mutexes and multi-host synchronization status. |
| system-explorer | Local System Introspection & Hardware Discovery | Inspects process memory, GPU utilization, and system thermals. |
| ellmos-controlcenter-mcp | Governance & Capability Routing Hub | Governs tool allocations and operational boundaries. |
| ellmos-delegation-authority | Cryptographic Task Delegation Tokens | Provides receipt-based delegation for automated workflows. |
| sqlite-transit-sync | Zero-Conflict Database Synchronization | Manages atomic state sync across multi-host environments. |
| clip-storyboard-director | Local-First AI Video Directing Pipeline | Heavy client benefited by scheduled time-sliced execution. |
| ellmos-voice-io | Speech Synthesis & Audio Streaming Engine | Local audio engine managed in rotation. |
| automation-master | Enterprise Background Task Scheduling | Coordinates headless background job triggers. |
| ExplorerPro | Tabbed Desktop File Manager | High-productivity file explorer for development assets. |
| ProSync | Resilient File Mirroring & Backup Utility | Synchronizes project backups across local volumes. |
| CleanMarkdown | Markdown Document Sanitizer & Linter | Normalizes markdown documentation and notes. |
| KlangpultLight | Zero-Egress Soundboard & Desktop Audio Recorder | Media companion utility for live sessions. |
| open-bricks | Umbrella Open-Source Ecosystem | Architectural standards, security SLAs, and release governance. |
- Operating System: Microsoft Windows 10 or Windows 11 (64-bit)
- Python: Version 3.11, 3.12, or 3.13
- Dependencies:
Pillow,psutil,pystray
$env:PYTHONIOENCODING = "utf-8"
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"# Initialize default fail-closed configuration
app-rotator config-init
# Validate configuration structure and syntax
app-rotator config-validate# Start the system tray application
app-rotator tray
# Run the rotator engine in foreground console mode
app-rotator run
# Query current engine status, active provider, and countdown
app-rotator status
# Send atomic control commands via mailbox
app-rotator play
app-rotator pause
app-rotator stopApp Rotator configuration is stored under %LOCALAPPDATA%\AppRotator\config.json.
{
"schema_version": 2,
"enabled": false,
"dry_run": true,
"cycle_pause_seconds": 300,
"default_gap_seconds": 60,
"overall_stop_seconds": 0,
"codex_controller": {
"executable": "",
"missing_behavior": "block",
"reactivation_spacing_seconds": 60
},
"providers": [
{
"name": "Codex Desktop",
"process_name": "ChatGPT.exe",
"path_contains": "WindowsApps\\OpenAI.Codex_",
"aumid": "OpenAI.Codex_2p2nqsd0c76g0!App",
"duration_seconds": 1800,
"enabled": true
},
{
"name": "Claude Desktop",
"process_name": "Claude.exe",
"path_contains": "WindowsApps\\Claude_",
"aumid": "Claude_pzs8sxrjxfjjc!Claude",
"duration_seconds": 1800,
"enabled": true
},
{
"name": "Antigravity",
"process_name": "Antigravity.exe",
"path_exact": "C:\\Users\\User\\AppData\\Local\\Programs\\Antigravity\\Antigravity.exe",
"executable": "C:\\Users\\User\\AppData\\Local\\Programs\\Antigravity\\Antigravity.exe",
"duration_seconds": 1800,
"enabled": false
}
]
}- Automatic v1 to v2 Upgrade: Older configurations are automatically upgraded to schema version 2 without data loss.
- Additive Inactive Providers: When older configurations are upgraded, newly recognized built-in providers are inserted with
enabled: false, ensuring a live user rotation is never expanded silently.
App Rotator is engineered from the ground up for strict security:
- Zero Network Ingestion or Egress: Does not open outbound network connections, make telemetry calls, or contact remote servers.
- Unprivileged Execution (
RunAsInvoker): Never requires or requests administrative privileges. Runs entirely inside standard user context. - Strict Path Sandboxing: Refuses to terminate processes without a verified, matched executable path on disk.
- 48-Hour Response SLA & 5-Day Triage: We provide an initial response to security vulnerability disclosures within 48 hours and a comprehensive technical triage within 5 business days. See SECURITY.md for vulnerability reporting procedures.
$env:PYTHONIOENCODING = "utf-8"
python -m ruff check .
python -m compileall -q src tests$env:PYTHONIOENCODING = "utf-8"
python -m pytest -vPart of the dev-bricks suite under the open-bricks umbrella.