mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

Dev Tools

Beep Locator: Hot-Swappable JavaScript Modules Over WebSockets

How Simon Willison built a distributed audio triangulation system with Claude Code using WebSocket module injection and browser microphone APIs.

Source: simonwillison.net
Beep Locator: Hot-Swappable JavaScript Modules Over WebSockets

Simon Willison just published a detailed build log showing how he gave Claude Code “ears” through browser microphone APIs. The result is Beep Locator, a distributed audio sensing system that coordinates multiple devices via WebSockets and uses hot-swappable JavaScript modules to let a coding agent iterate on live listeners without triggering browser permission prompts.

The practical problem: a carbon monoxide detector beeping somewhere in a room full of unpacked boxes. The technical solution: a Python orchestrator that deploys arbitrary JavaScript to browser-based listeners, coordinates audio clip uploads, and triangulates sound sources using academic research from 2007.

Architecture Overview

The system has three layers:

  1. Python web server (orchestrator): Runs the primary UI, manages WebSocket connections, coordinates chirp playback timing, and performs triangulation calculations.
  2. Browser listeners (sensors): Request microphone access once, record 60-second rolling audio buffers, run client-side FFT-based beep detection, and accept hot-swapped JavaScript modules over WebSocket.
  3. WebSocket control plane: Bidirectional channel for deploying new code, triggering audio uploads, and synchronizing state across heterogeneous devices (iPads, phones, laptops).

The key innovation is the installModule() method. The Python server can push arbitrary JavaScript to any connected listener without a page reload. This sidesteps the browser permission model: once a user grants microphone access, the page can be reprogrammed indefinitely without re-prompting.

Hot-Swap Module System

When Claude Code wants to change how listeners detect beeps or process audio, it sends a message over the WebSocket:

// Server-side Python sends this
await websocket.send(json.dumps({
  "type": "installModule",
  "code": "function detectBeep(audioData) { /* new logic */ }"
}))

// Client-side listener receives and executes
socket.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === "installModule") {
    eval(msg.code);  // Yes, eval. Controlled context.
  }
};

This pattern lets the agent iterate on detection algorithms, adjust FFT parameters, or add new features (like clap detection) while devices remain live in the room. No manual interaction required after initial setup.

Security boundary: The WebSocket is unauthenticated by default. Anyone on the local network can push code to listeners. For a home debugging tool, this is acceptable. For production, you would add token-based auth or mTLS.

Audio Coordination Flow

When a listener detects a potential beep:

  1. Client-side FFT filter (detector.js) identifies high-frequency transients and sends a beep event to the server with a timestamp.
  2. Server selects a chirp device (the one with lowest round-trip latency) and tells it to play a short chirp.
  3. Server requests audio clips from all listeners: 0.6 seconds before the beep to 0.8 seconds after the chirp.
  4. Listeners upload clips as base64-encoded WAV files over WebSocket.
  5. Server aligns clips using the chirp as a synchronization marker, then triangulates the beep location using time-of-arrival differences.

The chirp solves the clock skew problem. Each device has a different system clock and audio buffer alignment. By injecting a known sound after the beep, the server can align all clips to a common reference point without requiring NTP or GPS.

Acoustic Ranging Implementation

Claude Code found and implemented the BeepBeep paper (Peng et al., 2007), which describes two-way acoustic ranging using commodity hardware. The core technique:

  • Two-way sensing: Devices play sounds to each other and measure round-trip time.
  • Self-recording: Each device records its own speaker output to eliminate speaker-to-microphone delay uncertainty.
  • Sample counting: Use audio sample timestamps instead of system clocks for sub-millisecond precision.

In practice, this didn’t work well for Willison’s use case. The system includes a “rough mode” where you manually drag device icons to approximate positions, then the acoustic ranging refines those estimates. This hybrid approach handles real-world problems like echo, furniture occlusion, and non-line-of-sight paths.

State Management Trade-offs

ComponentState LocationPersistenceFailure Mode
Audio buffersClient (60s rolling)Memory onlyLost on disconnect
Beep eventsServer (in-memory log)NoneLost on restart
Device positionsServer (coordinate dict)NoneManual reposition required
Uploaded clipsServer (temp files)DiskFills disk if not cleaned
Module codeClient (eval scope)NoneRevert on page reload

The system is stateless by design. If the Python server crashes, you lose all beep history and device positions. For a one-time debugging tool, this is fine. For continuous monitoring, you would add SQLite persistence and a recovery mechanism.

Observability Hooks

The UI includes several debugging features that emerged during development:

  • Replay button: Plays back recorded clips from each listener in sequence, with on-screen labels showing which device captured which audio.
  • Timestamp labels: Every symbol on the map (beep, clap, device) shows HH:MM:SS to correlate with real-world events.
  • Clap mode: Lets you test triangulation by clapping at known locations and comparing map output to ground truth.
  • Suggested listener position: The system calculates where an additional listener would most improve triangulation accuracy based on current geometry.

These aren’t just UI polish. They expose the internal state machine to the agent, letting Claude Code query audio logs, verify detection accuracy, and adjust parameters without human interpretation.

Deployment Shape

The entire system runs on a single laptop:

git clone https://github.com/simonw/beep-locator
cd beep-locator
python server.py
# Open http://localhost:8000 on orchestrator
# Scan QR code on listener devices

No Docker, no Kubernetes, no cloud dependencies. The Python server uses aiohttp for WebSockets and serves static HTML/JS. Listeners are just browser tabs with microphone access.

This is the right deployment shape for agent-driven development. Claude Code can see the entire stack in one terminal, make changes to server.py or detector.js, and observe results immediately. No build pipeline, no deployment delay.

Likely Failure Modes

Clock skew accumulation: If devices run for hours, their clocks will drift. The chirp-based alignment only works for individual beep events. Long-running sessions would need periodic re-synchronization.

Audio buffer overflow: The 60-second rolling buffer is fixed-size. If the server requests a clip but the listener’s buffer has already overwritten that time range, the upload fails silently. The system should send a “buffer miss” error back to the server.

WebSocket reconnection: If a listener loses connection, it doesn’t automatically rejoin. You have to manually reload the page and grant microphone access again. A production version would implement exponential backoff reconnection with session resumption.

Chirp collision: If two beeps happen close together, the server might play a chirp while listeners are still uploading clips from the first beep. The system needs a mutex or queue to serialize chirp playback.

FFT false positives: The high-pass filter in detector.js triggers on any high-frequency transient (door slams, keyboard clicks, phone notifications). The server relies on triangulation consistency to filter these out, but a noisy environment could overwhelm the system.

Technical Verdict

Use Beep Locator’s architecture when:

  • You need to coordinate multiple browser-based sensors without a native app.
  • You want an agent to iterate on client-side logic without manual device interaction.
  • You can tolerate stateless operation and manual recovery from crashes.
  • You control the network and don’t need authentication.

Avoid this pattern when:

  • You need sub-second latency (WebSocket round-trip adds 50-200ms).
  • You require cryptographic guarantees about code provenance (eval is a trust boundary).
  • You need to survive server restarts without losing state.
  • You’re deploying to untrusted networks (no auth, no encryption by default).

The hot-swap module system is the real contribution here. It turns browsers into remotely programmable sensors, letting an agent treat client-side JavaScript as mutable infrastructure. That’s a useful primitive for agent-driven development of distributed systems.