# How the Sidebar Polls `/api/agent/running` and Pauses in Background Tabs

> Learn how the sidebar polls agent running status and pauses in background tabs. Discover efficient network request handling to save CPU and bandwidth.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-08-18

---

**The sidebar polls `GET /api/agent/running` every 2.5 seconds while the browser tab remains visible, automatically pausing network requests via the `visibilitychange` event when users switch tabs to eliminate unnecessary CPU and bandwidth consumption.**

The `agegr/pi-web` repository implements an efficient real-time polling mechanism to keep the sidebar synchronized with active agent sessions. Through the **`useAgentSession`** custom hook, the application maintains near-real-time updates while respecting browser resource constraints through intelligent background tab detection as documented in [`AGENTS.md`](https://github.com/agegr/pi-web/blob/main/AGENTS.md).

## Polling Architecture and Interval Configuration

The polling mechanism centers on a lightweight periodic fetch operation configured to balance responsiveness against network overhead. Inside **[`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts)**, the implementation establishes a `setInterval` timer set to **2500 milliseconds** (2.5 seconds).

Each interval tick executes an asynchronous fetch to `/api/agent/running`, retrieving a JSON array of currently active session IDs. The hook then reconciles this server state with the local React state, ensuring the sidebar UI reflects the latest running agents without requiring a page refresh.

## Background Tab Detection with the Page Visibility API

To prevent wasteful network activity when users are not actively viewing the interface, the implementation leverages the standard **Page Visibility API**. This browser-native interface provides reliable detection of tab visibility states without relying on blur or focus events that can trigger inconsistently across browsers.

### Listening for `visibilitychange` Events

The `useAgentSession` hook attaches an event listener to `document.addEventListener('visibilitychange', handler)` during its initialization phase. Within the handler function, the code inspects the **`document.hidden`** property—a boolean that returns `true` when the tab is backgrounded, minimized, or otherwise obscured from view.

This approach provides deterministic state detection across all modern browsers, distinguishing between true background states and temporary UI interactions that might trigger focus events.

### Pausing and Resuming the Interval

The polling state machine implements two distinct transitions based on visibility:

- **Pause**: When `document.hidden` evaluates to `true`, the hook executes `clearInterval(timer)`, immediately halting further network requests and releasing the timer resource.
- **Resume**: When the tab regains visibility (`document.hidden` is `false`), the hook reinitializes the interval via `setInterval`, resuming the 2.5-second polling cycle without requiring a full page reload.

The cleanup function within the `useEffect` hook ensures that both the interval timer and the visibility event listener are properly disposed when the component unmounts, preventing memory leaks in long-running single-page applications.

## Implementation in [`useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/useAgentSession.ts)

The following pseudocode illustrates the core polling logic extracted from the hook implementation:

```typescript
useEffect(() => {
  let timer: NodeJS.Timeout | null = null;

  const startPolling = () => {
    timer = setInterval(async () => {
      const running = await fetch('/api/agent/running').then(r => r.json());
      setRunningSessions(running);
    }, 2500);
  };

  const stopPolling = () => {
    if (timer) clearInterval(timer);
    timer = null;
  };

  // Initialize polling only if tab is currently visible
  if (!document.hidden) startPolling();

  // Handle tab visibility changes
  const onVisibilityChange = () => {
    if (document.hidden) stopPolling();
    else startPolling();
  };
  
  document.addEventListener('visibilitychange', onVisibilityChange);

  return () => {
    stopPolling();
    document.removeEventListener('visibilitychange', onVisibilityChange);
  };
}, []);

```

This pattern ensures that the sidebar maintains session awareness during active use while conserving client and server resources during periods of inactivity.

## The `/api/agent/running` Endpoint

The server-side counterpart resides in **[`app/api/agent/running/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/agent/running/route.ts)**. This route handler returns a lightweight JSON payload containing an array of currently executing session identifiers. The endpoint is optimized for high-frequency polling, minimizing database query overhead and response payload size to support the 2.5-second client-side polling interval efficiently.

According to the source code analysis, this endpoint provides the snapshot data consumed by the `useAgentSession` hook to reconcile local state against the server truth.

## Summary

- The sidebar polls **`/api/agent/running`** at **2.5-second intervals** to maintain real-time session awareness.
- Polling automatically **pauses** when `document.hidden` is `true` via the Page Visibility API, conserving network and CPU resources.
- The **`useAgentSession`** hook in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) encapsulates both the polling logic and visibility state management.
- The **[`app/api/agent/running/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/agent/running/route.ts)** endpoint provides optimized, lightweight session data for frequent client requests.
- Cleanup handlers prevent memory leaks by removing interval timers and event listeners on component unmount.

## Frequently Asked Questions

### How often does the sidebar poll the running agents endpoint?

The sidebar initiates a `fetch` request to `/api/agent/running` every **2.5 seconds** (2500 milliseconds) while the browser tab remains visible. This interval is hardcoded in the `useAgentSession` hook to balance real-time updates against network efficiency.

### What happens to the polling when I switch browser tabs?

When you switch to a different tab or minimize the window, the `visibilitychange` event fires and sets `document.hidden` to `true`. The hook immediately clears the polling interval using `clearInterval`, completely stopping network requests until you return to the tab.

### Where is the polling logic implemented in the codebase?

The primary polling logic lives in **[`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts)**, which manages the `setInterval` timer, visibility change detection, and state reconciliation. The server endpoint that responds to these requests is located at **[`app/api/agent/running/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/agent/running/route.ts)**.

### Why does the application pause polling instead of continuing in the background?

Pausing polling prevents unnecessary network traffic and reduces server load when users are not actively viewing the interface. This approach also conserves client-side battery life and CPU cycles, following best practices for background tab behavior in modern web applications as outlined in the repository's [`AGENTS.md`](https://github.com/agegr/pi-web/blob/main/AGENTS.md) documentation.