Pi Web AgentSession Idle Timeout: How the 10-Minute Auto-Cleanup Works
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 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):
// 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():
// 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:
// 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:
// 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)
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
// 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
// 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 |
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 everysend()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.tswithin theAgentSessionWrapperclass
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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →