A real-time racing visualization of all active Claude Code sessions on your machine. Each session is a racer on a track, and its position is driven by context window utilization -- as a conversation consumes more tokens, the racer advances toward the finish line. Switch between a car racing view and a footrace view with pixel runners.
Sessions are discovered automatically via process scanning. State is read directly from Claude Code's JSONL session files. No configuration, wrappers, or hooks required -- just start Claude Code sessions anywhere on your machine and watch them race.
JSONL File Discovery Go Backend Browser (Canvas)
| | |
|-- ~/.claude/projects/*/*.jsonl ->| |
|-- parse token usage ----------->| |
|-- classify activity ----------->| |
| |-- WebSocket -------->|
| | snapshots + deltas |
| |-- Canvas renders
| |
| Terminal (TUI)
| |
|-- WebSocket -------->|
| |-- Bubble Tea views
Position = context window fill. Each API response in Claude Code's session log contains token usage (input_tokens + cache_read_input_tokens + cache_creation_input_tokens). That total divided by the model's max context (200K) gives a 0.0-1.0 utilization value that maps to track position. As the conversation grows, the car moves forward. Reaching the limit means the finish line (or compaction time).
Prerequisites: Go 1.22+, a modern browser (for web UI) or terminal (for TUI).
Localhost-only: Agent Racer is intended to run on your local machine only. It is not designed or supported for public or multi-user deployment.
# Clone and run in mock mode (demo with 5 simulated sessions)
cd agent-racer
make dev
# Open http://localhost:8080# Run with real session monitoring
make run
# Open http://localhost:8080, then start Claude Code sessions in other terminals# Build the server binary with embedded frontend
make build
./agent-racer-server --mock # demo mode
./agent-racer-server # real mode# Or use the terminal dashboard (no browser needed)
make tui
./agent-racerThe TUI is a terminal dashboard built with Bubble Tea that connects to the same backend as the browser frontend. No browser needed -- everything runs in your terminal.
make tui
./agent-racerIf the backend uses an auth token (see auth_token in config.yaml), pass it with -token:
./agent-racer -token <your-token>To connect to a backend on a different host or port:
./agent-racer -url ws://192.168.1.10:9090/ws| Key | Action |
|---|---|
j / k |
Navigate sessions up/down |
Tab |
Cycle zone (Racing → Pit → Parked) |
1 |
Jump to Racing zone |
2 |
Jump to Pit zone |
3 |
Jump to Parked zone |
Enter |
Open session detail overlay |
f |
Focus session in tmux (requires tmux target) |
a |
Achievements overlay |
g |
Garage overlay |
b |
Battle pass overlay |
d |
Debug log overlay |
r |
Resync sessions from backend |
Esc |
Close overlay |
q |
Quit |
Mock mode (--mock) simulates 5 sessions with distinct behaviors for demo and development:
| Session | Model | Behavior |
|---|---|---|
| opus-refactor | Opus | Steady token growth to 180K, completes successfully |
| sonnet-tests | Sonnet | Burst pattern (fast writes), completes quickly |
| opus-debug | Opus | Stalls mid-conversation (waiting for user input), then resumes |
| sonnet-feature | Sonnet | Errors out at ~60% context utilization |
| opus-review | Opus | Slow and methodical, heavy tool use (Read, LSP, Grep) |
In real mode (the default), the dashboard:
- Scans
~/.claude/projects/*/*.jsonlfor recently updated session files - Incrementally parses new JSONL entries each poll (1s interval, only reads new bytes)
- Extracts token usage, model, activity, and tool calls from the session log
- Marks sessions complete after a Claude SessionEnd hook (preferred) or a configurable inactivity timeout
Agent Racer can use Claude Code's SessionEnd hook to mark a session complete immediately and remove the racer after the completion animation.
The install script sets this up automatically:
./scripts/install.shIf you manage hooks manually, add a SessionEnd hook that runs:
~/.config/agent-racer/hooks/session-end.shCompletion markers are written to ~/.local/state/agent-racer/session-end/ by default (XDG state directory). Configure monitor.session_end_dir if you want a different path.
No wrappers, hooks, or environment variables needed. Just run claude anywhere and it shows up.
Agent Racer has early support for monitoring OpenAI Codex CLI and Google Gemini CLI sessions alongside Claude Code. This support is pre-alpha -- expect bugs with progress tracking, model labels, and session lifecycle. Both sources are disabled by default. To opt in, enable them in your config:
sources:
codex: true
gemini: trueSee docs/multi-agent-guide.md for detailed documentation on supported CLIs, configuration options, how to add new sources, and a manual validation checklist.
Agent Racer supports multiple visualization styles. Press V to cycle between them. Your choice is saved in localStorage and persists across page reloads.
The original view. Each session is rendered as a car with:
- Color by model: Purple = Opus, Blue/Cyan = Sonnet, Green = Haiku
- Position: Horizontal position maps to context window utilization (0% to 100%)
- Name label: Session name (derived from working directory) above the car
- Model badge: Small colored badge showing the model family
- Token counter: Current/max tokens below the car (e.g.
142K/200K) - Tool indicator: Current tool name shown below when actively using a tool
| Activity | Visual |
|---|---|
| Thinking | Car moves with exhaust particles + thought bubble |
| Tool Use | Sparks flying + tool name displayed |
| Waiting | Hazard lights flashing (amber blinkers) |
| Idle | Car stationary, no effects |
| Churning | Subtle wheel rotation + occasional exhaust puff (active processing, no output yet) |
| Complete | Trophy icon + confetti explosion + victory fanfare |
| Errored | Spin-out animation + smoke particles + error sound |
| Lost | Car fades to transparent |
Note: "Starting" state is used only in mock mode for demo purposes.
An alternate view where sessions are pixel runners on a running track. Press V to switch to it.
Each session is a procedurally drawn character with:
- Model-colored headband and jersey (same color scheme as car view)
- Number bib with session name abbreviation
- Animated limbs -- arms pump and legs cycle based on activity
| Activity | Animation |
|---|---|
| Thinking | Running -- arms pumping, legs cycling, leaning forward |
| Tool Use | Sprinting -- exaggerated motion, speed lines |
| Idle/Starting | Stretching -- gentle bounce, jogging in place |
| Waiting | Standing -- head turns, occasional yawn |
| Complete | Celebrating -- arms raised, jumping |
| Errored | Tripping -- stumble, face-plant, stars |
| Lost | Ghost -- translucent, slow walk |
The track surface is grass and dirt with lane ropes, mile markers, a rest area (bench) for idle runners, and a finish banner with podium for completed sessions.
Footrace-specific particle effects: dust clouds, sweat drops, celebration stars, trip sparks, and footprints.
Idle, waiting, and starting sessions (that aren't churning) automatically move off the main track into a side area. In car view this is a pit lane; in footrace view it's a rest area with benches. This keeps the active lanes uncluttered while giving visual persistence to sessions that are still alive but not actively working.
- Racers smoothly animate between track and pit/rest area with opacity/scale dimming
- When a session becomes active again (thinking, tool use, or churning), the racer moves back onto the track
- Terminal states (complete, errored, lost) stay on track for their exit animations
- Dark asphalt surface with lane dividers
- Checkerboard start line (0 tokens) and red checkerboard finish line (200K tokens)
- Dotted markers at 50K, 100K, and 150K tokens
- Active sessions get their own lane on the main track
- Pit area below the track holds idle sessions
| Key | Action |
|---|---|
V |
Cycle view (car racing / footrace) |
A |
Toggle achievements panel |
G |
Toggle garage (cosmetics) |
D |
Toggle debug panel (shows raw WebSocket messages) |
M |
Toggle sound effects (mute/unmute) |
Shift+F |
Toggle fullscreen |
? |
Toggle help popup |
Esc |
Close active panel or flyout |
Click on any car to open the detail panel with: activity, token progress bar, model, working directory, message count, tool call count, current tool, timestamps, PID, and session ID.
Agent Racer follows the XDG Base Directory Specification. The default config location is:
~/.config/agent-racer/config.yaml
You can override this with --config or set XDG_CONFIG_HOME to use a custom config directory.
See config.example.yaml for a complete example. Key configuration options:
server:
port: 8080 # HTTP/WebSocket port
host: "127.0.0.1" # Bind address (localhost by default)
# allowed_origins: # Optional: restrict browser origins for WS
# - "http://localhost:8080"
# - "http://127.0.0.1:8080"
# auth_token: "" # Auto-generated if empty; weak placeholders (dev/test/changeme/default) are rejected
sources:
claude: true # Claude Code session monitoring (default: true)
codex: false # OpenAI Codex CLI monitoring (default: false, pre-alpha)
gemini: false # Google Gemini CLI monitoring (default: false, pre-alpha)
monitor:
poll_interval: 1s # How often to scan for processes and read JSONL
snapshot_interval: 5s # Full state broadcast interval
broadcast_throttle: 100ms # Minimum time between delta broadcasts
session_stale_after: 2m # Mark sessions complete after no new data
completion_remove_after: 8s # Remove racers after completion animation
session_end_dir: "" # Defaults to $XDG_STATE_HOME/agent-racer/session-end
churning_cpu_threshold: 15.0 # CPU% for detecting active processing
churning_requires_network: false # Require TCP connections for churning state
models:
claude-opus-4-5-20251101: 200000
claude-sonnet-4-5-20250929: 200000
claude-sonnet-4-20250514: 200000
claude-haiku-3-5-20241022: 200000
gpt-5-codex: 272000 # Codex models
gemini-2.5-pro: 1048576 # Gemini models (1M tokens)
default: 200000 # Fallback for unrecognized models
sound:
enabled: true # Master enable/disable
master_volume: 1.0 # 0.0 - 1.0
ambient_volume: 1.0 # Crowd, wind, engine hums
sfx_volume: 1.0 # Gear shifts, victory, crashes
enable_ambient: true
enable_sfx: trueIf no config file exists, agent-racer uses sensible defaults. See docs/configuration.md for detailed documentation.
Server (agent-racer-server):
Usage: agent-racer-server [flags]
--mock Use mock session data (demo mode)
--dev Serve frontend from filesystem (for development)
--config string Path to config file (default: ~/.config/agent-racer/config.yaml)
--port int Override server port
TUI (agent-racer):
Usage: agent-racer [flags]
-url string WebSocket URL of the backend (default: ws://127.0.0.1:8080/ws)
-token string Auth token (if backend requires it)
Connects to the real-time event stream. Messages are JSON with a type field:
snapshot -- Full state of all sessions (sent on connect and every 5s):
{
"type": "snapshot",
"payload": {
"sessions": [
{
"id": "abc-123",
"name": "my-project",
"activity": "thinking",
"tokensUsed": 142000,
"maxContextTokens": 200000,
"contextUtilization": 0.71,
"currentTool": "",
"model": "claude-opus-4-5-20251101",
"workingDir": "/home/user/my-project",
"startedAt": "2026-01-30T10:00:00Z",
"lastActivityAt": "2026-01-30T10:05:00Z",
"messageCount": 42,
"toolCallCount": 18,
"pid": 12345,
"lane": 0
}
]
}
}delta -- Only changed sessions (throttled to 100ms):
{
"type": "delta",
"payload": {
"updates": [ /* ...session objects... */ ],
"removed": [ "session-id-1" ]
}
}completion -- Session finished:
{
"type": "completion",
"payload": {
"sessionId": "abc-123",
"activity": "complete",
"name": "my-project"
}
}Returns a JSON array of all current session states.
agent-racer/
├── Makefile
├── config.yaml
├── backend/
│ ├── go.mod
│ ├── cmd/server/
│ │ └── main.go # Entry point, flag parsing
│ └── internal/
│ ├── config/config.go # YAML config loading
│ ├── session/
│ │ ├── state.go # SessionState, Activity enum
│ │ └── store.go # Thread-safe session store
│ ├── ws/
│ │ ├── protocol.go # WebSocket message types
│ │ ├── broadcast.go # Per-client write channels, throttled broadcasting
│ │ └── server.go # HTTP/WS handlers
│ ├── monitor/
│ │ ├── source.go # Unified Source interface for multi-agent support
│ │ ├── claude_source.go # Claude Code session discovery
│ │ ├── codex_source.go # OpenAI Codex CLI session discovery
│ │ ├── gemini_source.go # Google Gemini CLI session discovery
│ │ ├── process.go # Process scanning via gopsutil
│ │ ├── jsonl.go # Incremental JSONL parser
│ │ └── monitor.go # Main poll loop + churning detection
│ ├── mock/
│ │ └── generator.go # 5 simulated sessions
│ └── frontend/
│ ├── embed.go # go:embed for production builds
│ └── noembed.go # Filesystem fallback for dev
├── tui/
│ ├── go.mod
│ ├── cmd/racer-tui/
│ │ └── main.go # TUI entry point, flag parsing
│ └── internal/
│ ├── app/ # Bubble Tea application model
│ ├── client/ # WebSocket + HTTP client
│ ├── theme/ # Terminal color theme
│ └── views/
│ ├── track/ # ASCII race track
│ ├── dashboard/ # Session stats overview
│ ├── detail/ # Session detail panel
│ ├── achievements/ # Achievements view
│ ├── battlepass/ # Battle pass view
│ ├── garage/ # Garage view
│ ├── debug/ # Raw WebSocket messages
│ └── status/ # Status bar
└── frontend/
├── index.html
├── styles.css
└── src/
├── main.js # Bootstrap, shortcuts, sound, view switching
├── ViewRenderer.js # Pluggable view registry (registerView/createView)
├── websocket.js # Auto-reconnecting WebSocket client
├── notifications.js # Browser notifications
├── session/
│ ├── constants.js # Shared constants (TERMINAL_ACTIVITIES, etc.)
│ ├── colors.js # Model/source color utilities
│ └── zones.js # Zone classification (track/pit/parkingLot)
├── audio/
│ ├── SoundEngine.js # Web Audio sound effects
│ └── engineSync.js # Pure function: sync engine sounds to entity state
├── canvas/
│ ├── Particles.js # Exhaust, sparks, smoke, confetti, dust, sweat
│ ├── Track.js # Car racing track rendering
│ ├── FootraceTrack.js # Running track rendering (grass, lanes, markers)
│ ├── RaceCanvas.js # Car racing view (requestAnimationFrame loop)
│ └── FootraceCanvas.js # Footrace view (pixel runners)
└── entities/
├── Racer.js # Car entity with activity animations
└── Character.js # Pixel runner entity with animation state machine
No build tools for the frontend. Vanilla JS with ES modules, served via Vite in dev mode. The backend is a single Go binary with minimal dependencies (gorilla/websocket, gopsutil, yaml.v3). The TUI is a separate Go binary using Bubble Tea.
Frontend embedding: The frontend/ directory is the single source of truth. During builds (make build), it's copied to backend/internal/frontend/static/ as a build artifact (git-ignored) for Go's embed directive.
| Target | Description |
|---|---|
make dev |
Run mock mode with filesystem frontend (hot-reload friendly) |
make run |
Run real mode with filesystem frontend fallback |
make build |
Embed frontend into Go binary, produce ./agent-racer-server |
make tui |
Build TUI binary, produce ./agent-racer |
make tui-build |
Alias for make tui |
make dist |
Cross-compile server + TUI for linux/darwin amd64/arm64 |
make test |
Run backend Go tests |
make tui-test |
Run TUI Go tests |
make test-frontend |
Run frontend Vitest suite |
make test-e2e |
Run Playwright E2E tests |
make lint |
Run go vet on backend |
make tui-lint |
Run go vet on TUI |
make ci |
Run all checks: test, lint, test-frontend, test-e2e, tui-test, tui-lint |
make clean |
Remove binaries and embedded files |
make deps |
Download backend Go dependencies |
make tui-deps |
Download TUI Go dependencies |
# Option 1: Build and install
make build
make tui
cp agent-racer-server agent-racer /usr/local/bin/
# Option 2: Use the install script (builds + installs both binaries)
./scripts/install.sh- Go 1.22+ for building
- Linux or macOS for real mode (process discovery via gopsutil)
- Mock mode works on any platform
- Modern browser with Canvas support


