Skip to content

mrf/agent-racer

Repository files navigation

Agent Racer

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.

Agent Racer dashboard showing multiple Claude sessions racing on a track

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.

How It Works

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).

Quick Start

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-racer

Terminal UI (TUI)

TUI dashboard showing sessions racing in the terminal

The 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.

Build and Run

make tui
./agent-racer

If 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

TUI Keyboard Shortcuts

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 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)

Real Mode

In real mode (the default), the dashboard:

  1. Scans ~/.claude/projects/*/*.jsonl for recently updated session files
  2. Incrementally parses new JSONL entries each poll (1s interval, only reads new bytes)
  3. Extracts token usage, model, activity, and tool calls from the session log
  4. Marks sessions complete after a Claude SessionEnd hook (preferred) or a configurable inactivity timeout

SessionEnd Hook (Recommended)

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.sh

If you manage hooks manually, add a SessionEnd hook that runs:

~/.config/agent-racer/hooks/session-end.sh

Completion 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.

Multi-Agent Support (Pre-Alpha)

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: true

See docs/multi-agent-guide.md for detailed documentation on supported CLIs, configuration options, how to add new sources, and a manual validation checklist.

Views

Agent Racer supports multiple visualization styles. Press V to cycle between them. Your choice is saved in localStorage and persists across page reloads.

Car Racing (default)

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 Animations

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.

Footrace

Footrace view showing pixel runners on a grass track

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.

Pit Lane / Rest Area

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

Track

  • 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

Keyboard Shortcuts

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.

Configuration

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: true

If no config file exists, agent-racer uses sensible defaults. See docs/configuration.md for detailed documentation.

CLI Flags

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)

API

WebSocket: /ws

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"
  }
}

REST: GET /api/sessions

Returns a JSON array of all current session states.

Architecture

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.

Make Targets

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

Installing

# 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

Requirements

  • 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

License

MIT

About

Visualize your running AI-coding agents as a 2d car race

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors