
I run up to ten AI coding agents at a time. I wanted to see what they were all doing. So obviously I built a WoW raid frame on a $25 LED panel.
Each agent gets an 8x8 sprite. When it's working, you see an ability icon. When it's idle, it sleeps. When it needs approval, it catches fire. That's it. That's the project.
The Stack
The hardware is an iDotMatrix 64x64 LED panel, ~$25. You send it pixel data over Bluetooth and it displays it. btleplug handles the Bluetooth side in Rust.

The webhook handler and BLE renderer are fully decoupled. The handler updates state; a separate render loop polls every 250ms, diffs the state hash, and debounces 2 seconds before pushing. When an agent enters Requesting, animation speed doubles so the fire pulses with urgency.
The render pipeline produces a 6-frame animated GIF per cycle. It uses NeuQuant quantization to build a single global 256-color palette across all frames โ no per-frame palette switching, which the panel can't handle anyway โ then splits the final GIF into 4KB BLE packets with a 16-byte header and CRC32 for integrity. The panel reassembles and plays the animation on loop until the next push.
Each agent gets an 8x8 animated sprite from one of eleven themes โ Slimes, Ghosts, Space Invaders, Pac-Men, and more. Each IDE host gets its own theme, so I can tell Claude Code from Cursor at a glance.
![]()
The State Machine
Three states: Idle, Working, Requesting. The transitions seem obvious until you run them against real hook traffic.
Claude Code fires PermissionRequest and PreToolUse within ~100ฮผs of each other, out of order. Handle them naively and PreToolUse clobbers Requesting. Second problem: PostToolUse fires after every tool call โ transition to Idle on it and agents flash Working โ Idle โ Working constantly.
Design principle: idle and approval states are the most important to display correctly. Everything else is nice-to-have.
match event_name {
"PreToolUse" => {
// Don't override Requesting โ PreToolUse fires alongside
// PermissionRequest but the tool is still blocked on approval.
if agent.state != AgentState::Requesting {
agent.state = AgentState::Working;
}
}
"PostToolUse" => {
// If coming from a permission request, clear the fire icon.
// Otherwise keep working โ tools fire rapidly in sequence.
if agent.state == AgentState::Requesting {
agent.state = AgentState::Working;
}
}
"PermissionRequest" => {
agent.state = AgentState::Requesting;
}
"Stop" | "SessionEnd" => {
agent.state = AgentState::Idle;
}
_ => {}
}
Requesting is sticky โ only cleared by PostToolUse (approval resolved, agent is now working) or Stop. The failure mode โ agents appearing idle when they're actually waiting โ is the exact thing the whole project exists to prevent.
The code is MIT, the hardware is $25. github.com/terraboops/lfg
LFG. Looking For Group. Let's f***ing go.