How the 10-Minute Idle Timeout Works for AgentSession in pi-web

The 10-minute idle timeout automatically shuts down inactive AgentSession instances by resetting a timer on every activity event and calling shutdown() when no activity occurs for 600,000 milliseconds.

Pi Web wraps the Pi SDK's native AgentSession with an AgentSessionWrapper that enforces automatic cleanup of idle sessions. This mechanism prevents resource leaks in long-running server environments while ensuring active sessions remain available. According to the agegr/pi-web source code, the timeout logic resides in lib/rpc-manager.ts and tracks multiple activity signals to determine when a session is truly idle.

Where the Idle Timer Is Registered

The AgentSessionWrapper class initializes and manages the idle timer throughout a session's lifecycle. Three distinct events trigger resetIdleTimer():

This comprehensive coverage ensures any meaningful interaction keeps the session alive.

The resetIdleTimer Implementation

The core timeout logic appears in a private method that clears existing timers and schedules fresh ones:

private resetIdleTimer(): void {
  if (this.idleTimer) clearTimeout(this.idleTimer);
  this.idleTimer = setTimeout(() => {
    // If a run is still active, postpone the shutdown.
    if (this.isRunning()) {
      this.resetIdleTimer();
      return;
    }
    // No activity → gracefully shut down the session.
    void this.shutdown().catch(err => {
      console.error("[pi-web] failed to shut down idle session:", err);
    });
  }, 10 * 60 * 1000);   // 10 minutes
}

The 10-minute duration is hardcoded as 10 * 60 * 1000 milliseconds. When the timer fires, the wrapper performs one final safety check before terminating.

How isRunning() Determines Session Activity

The isRunning() method prevents premature shutdown by checking four active state conditions:

Condition Source Property Description
Pending prompts pendingPromptCount > 0 User input awaiting response
Active streaming inner.isStreaming SDK transmitting response chunks
Compaction in progress inner.isCompacting Background context compression
Bash command running inner.isBashRunning Subprocess executing shell command

If any condition evaluates to true, resetIdleTimer() reschedules itself. Only when all conditions are false does shutdown() execute, closing the SDK session and removing the wrapper from the global registry.

Practical Code Examples

Manually Trigger Idle Shutdown for Testing

import { startRpcSession } from "@/lib/rpc-manager";

// Create a temporary session.
const wrapper = await startRpcSession({ cwd: "/tmp", toolNames: [] });

// Immediately clear the idle timer and fire it manually.
wrapper['idleTimer'] && clearTimeout(wrapper['idleTimer']);
wrapper['idleTimer'] = setTimeout(() => wrapper['shutdown'](), 0);

This pattern bypasses the 10-minute wait during development or automated tests.

Observing Timeout Behavior in the UI

import { useAgentSession } from "@/hooks/useAgentSession";

export default function IdleDemo({ sessionId }: { sessionId: string }) {
  const { state, send } = useAgentSession(sessionId);

  // After a prompt finishes, the UI will automatically show the session as
  // "offline" roughly 10 minutes later if no further activity occurs.
  useEffect(() => {
    if (!state.isRunning) console.log("Session is idle – will shut down soon");
  }, [state.isRunning]);
}

The useAgentSession hook consumes wrapper state and reflects shutdown status in the client interface.

Key Files in the Idle Timeout Flow

Summary

  • Activity resets occur on wrapper start, every RPC send, and each finishPrompt event
  • 10-minute threshold is enforced via setTimeout with 10 * 60 * 1000 ms duration
  • Safety check via isRunning() prevents shutdown during streaming, compaction, or bash execution
  • Graceful cleanup calls shutdown() to release SDK resources and unregister the wrapper
  • Cross-stack visibility connects server-side timeout logic to UI state through useAgentSession

Frequently Asked Questions

Can the 10-minute idle timeout duration be configured?

No. The duration is hardcoded as 10 * 60 * 1000 milliseconds in lib/rpc-manager.ts. The resetIdleTimer() method contains no configurable parameter for the timeout value.

What happens if a session is actively streaming when the timer fires?

The isRunning() check detects inner.isStreaming and calls resetIdleTimer() to postpone shutdown. The session continues until streaming completes and no other activity indicators remain active.

How can I verify a session shut down due to idle timeout?

Check server logs for the [pi-web] failed to shut down idle session error message on failure, or monitor the state.isRunning property via useAgentSession on the client. A transition from true to false without explicit user action indicates idle timeout.

Does the idle timeout conflict with manual session closure?

No. The shutdown() method is idempotent. Calling it manually clears the idle timer and closes the session immediately. Subsequent timer callbacks detect the closed state and exit silently.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →