# Pi Web AgentSession Idle Timeout: How the 10-Minute Auto-Cleanup Works

> Understand Pi Web's AgentSession idle timeout. Learn how the 10-minute auto-cleanup works and how it resets with every action to ensure efficient session management.

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

---

**The `AgentSession` in Pi Web automatically destroys itself after 10 minutes of idle time, with the timer resetting on every session start or command sent.**

Pi Web, an open-source AI coding assistant by **agegr/pi-web**, implements automatic session lifecycle management through the `AgentSessionWrapper` class. The **10-minute idle timeout** ensures server resources are reclaimed from inactive sessions while keeping active workflows uninterrupted. This behavior is hard-coded in the RPC manager and triggers cleanup only when no work is in progress.

## How the 10-Minute Idle Timer Works

The idle timeout mechanism centers on `resetIdleTimer()`, a private method in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) that creates a 10-minute countdown. The timer starts when a session begins and refreshes with every user interaction.

### Timer Initialization and Reset

The timeout duration is fixed at `10 * 60 * 1000` milliseconds (600,000 ms):

```ts
// lib/rpc-manager.ts
private resetIdleTimer(): void {
  if (this.idleTimer) clearTimeout(this.idleTimer);
  this.idleTimer = setTimeout(() => {
    // If something is still busy, keep the timer alive.
    if (this.isRunning()) {
      this.resetIdleTimer();
      return;
    }
    // No activity → clean up the session.
    this.destroy();
  }, 10 * 60 * 1000); // ← 10-minute idle timeout
}

```

Every call to `resetIdleTimer()` first clears any existing timer, then schedules a new one. This pattern ensures **only one active timer exists** at any moment, preventing duplicate cleanup attempts.

## When the Timer Resets

Three specific events extend the session lifetime by refreshing the idle timer:

### 1. Session Start (`start()`)

When `startRpcSession()` creates a new wrapper, it immediately invokes `start()`:

```ts
// lib/rpc-manager.ts#L44-L46
public start(): void {
  this.resetIdleTimer();
}

```

This begins the countdown as soon as the session becomes operational.

### 2. Command Execution (`send()`)

Every RPC command routes through `send()`, which resets the timer before processing:

```ts
// lib/rpc-manager.ts#L105-L107
public async send(req: RpcRequest): Promise<void> {
  this.resetIdleTimer();
  // ... command handling logic
}

```

Supported commands include `prompt`, `abort`, `bash`, and other agent operations. Each extends the deadline by another 10 minutes.

### 3. Running State Check Prevents Premature Cleanup

The `isRunning()` helper determines whether the session qualifies for destruction:

```ts
// lib/rpc-manager.ts#L40-L42
private isRunning(): boolean {
  return this.promptRunning || this.streaming || this.compacting || this.bashRunning;
}

```

If **any** of these flags are `true` when the timer fires, `resetIdleTimer()` is called instead of `destroy()`. This prevents active work from being interrupted mid-stream.

## What Happens When the Timeout Fires

When 10 minutes pass without activity **and** `isRunning()` returns `false`, the wrapper executes `destroy()`:

- Marks the wrapper as **dead** (`this.dead = true`)
- Removes all event listeners
- Aborts any running Bash process
- Notifies the UI of session termination

This cleanup is **non-recoverable**—the session ID becomes invalid and must be recreated.

## Code Examples

### Starting a Session (Timer Begins Automatically)

```ts
import { startRpcSession } from "./lib/rpc-manager";

const { session } = await startRpcSession(
  "my-session-id",   // requested ID (may be generated)
  "",                // no pre-existing session file → new session
  "/home/user/project",
  []                 // optional tool list
);
// 10-minute idle timer starts here via session.start()

```

### Sending Commands Extends Timeout

```ts
// Each call resets the 10-minute deadline
await session.send({ type: "prompt", message: "Refactor this function." });
await session.send({ type: "bash", command: "npm test" });
await session.send({ type: "prompt", message: "Explain the results." });
// Deadline now 10 minutes from last send()

```

### Early Manual Cleanup

```ts
// Bypass the idle timer entirely
session.destroy();   // Immediate teardown

```

## Key Implementation Details in Pi Web

| Component | File Location | Purpose |
|-----------|---------------|---------|
| `AgentSessionWrapper` class | [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) | Core wrapper managing session lifetime and idle timeout |
| `resetIdleTimer()` | `lib/rpc-manager.ts#L61-L70` | Private method implementing 10-minute countdown logic |
| `start()` | `lib/rpc-manager.ts#L44-L46` | Public method initializing the idle timer on session start |
| `send()` | `lib/rpc-manager.ts#L105-L107` | Public RPC handler that refreshes timer on every command |
| `isRunning()` | `lib/rpc-manager.ts#L40-L42` | Private state checker preventing cleanup during active work |
| `startRpcSession()` | `lib/rpc-manager.ts#L34-L45` | Factory function creating wrappers and triggering `start()` |

## Summary

- **Hard-coded duration**: 10 minutes (`600,000 ms`) with no configuration option
- **Timer reset events**: `start()` and every `send()` call
- **Safety check**: `isRunning()` prevents destruction during active prompts, streaming, compaction, or Bash execution
- **Cleanup action**: `destroy()` terminates session, cleans resources, and notifies UI
- **Source location**: All logic resides in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) within the `AgentSessionWrapper` class

## Frequently Asked Questions

### Can the 10-minute idle timeout be configured or disabled?

**No.** The timeout value is hard-coded as `10 * 60 * 1000` in `resetIdleTimer()` with no external configuration mechanism. To change the behavior, you would need to modify [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) and rebuild the project.

### What counts as "activity" that resets the timer?

**Any RPC command sent via `send()`** qualifies, including `prompt`, `abort`, `bash`, and other operation types. Session initialization via `start()` also triggers a reset. Passive operations like UI renders or status polling do not extend the timer unless they route through `send()`.

### Will an idle timeout interrupt a running code generation task?

**No.** The `isRunning()` check specifically guards against this. If `promptRunning`, `streaming`, `compacting`, or `bashRunning` is `true` when the timer expires, the wrapper resets the timer instead of destroying. The session only terminates when truly idle.

### How can I detect when a session was destroyed by idle timeout versus explicit closure?

The `destroy()` method sets `this.dead = true` and emits cleanup events regardless of cause. Currently, the `AgentSessionWrapper` does not expose a distinct reason code for idle timeout versus manual `destroy()` calls. You can infer timeout by checking if no explicit `destroy()` was invoked in your application logic.