What Happens to the AgentSessionWrapper When AgentSession.fork() Is Called
When AgentSession.fork() is called, the AgentSessionWrapper is immediately destroyed via this.destroy() after capturing the new session ID, preventing registry corruption and ensuring the original session reloads cleanly on the next request.
The agegr/pi-web repository implements a session management system where calling AgentSession.fork() triggers a critical lifecycle event for the AgentSessionWrapper. According to the architecture notes in AGENTS.md and the implementation details in lib/rpc-manager.ts, the fork operation mutates the wrapper's internal state in-place, requiring immediate destruction to maintain a valid parentSession chain.
Why Forking Requires Immediate Wrapper Destruction
When AgentSession.fork() executes, it mutates the wrapper’s inner state directly. Specifically, the inner.sessionId property is overwritten with the ID of the newly created session rather than the original one.
If the wrapper remained registered in the global session registry under the old session ID, subsequent requests targeting that original ID would retrieve the already-forked state. This corruption leads to an invalid parentSession chain and causes subsequent fork operations to produce inconsistent or broken session hierarchies.
The Destruction Sequence in rpc-manager.ts
The implementation in lib/rpc-manager.ts handles this by following a strict sequence:
- Capture the new session ID: The
send("fork")method first extracts thenewSessionIdfrom the fork result. - Destroy the wrapper: Before returning, it invokes
this.destroy()on the currentAgentSessionWrapperinstance. - Return the result: The method returns the
newSessionIdto the caller. - Reload on next access: The next request for the original session ID triggers a fresh load of a clean
AgentSessionfrom the original session file, as the old wrapper no longer exists in the registry.
This ensures that the forked session operates independently without leaking its state back into the original session's context.
Practical Implementation Examples
Triggering a Fork via the HTTP API
You can trigger this behavior by sending a fork command to the agent API endpoint:
// Trigger fork on current session
const response = await fetch(`/api/agent/${sessionId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cmd: 'fork' })
});
const { newSessionId } = await response.json();
console.log('New forked session:', newSessionId);
// The original wrapper has been destroyed; newSessionId references the fresh session
Using the agent-client.ts Helper
For client-side applications, the repository provides a helper in lib/agent-client.ts that abstracts the fork operation:
import { agentClient } from '@/lib/agent-client';
async function forkCurrentSession(currentSessionId) {
const result = await agentClient(currentSessionId).post({ cmd: 'fork' });
// result.newSessionId contains the forked session reference
// The original session wrapper was automatically destroyed by the server
return result.newSessionId;
}
Summary
AgentSession.fork()overwritesinner.sessionIdwith the new session ID, mutating the wrapper in-place.- The wrapper must be destroyed immediately via
this.destroy()to prevent corruptedparentSessionchains. - The implementation captures
newSessionIdbefore destruction to return a valid reference to the caller. - Subsequent requests for the original session trigger a clean reload from the session file, as the stale wrapper no longer exists in the registry.
Frequently Asked Questions
What is the AgentSessionWrapper?
The AgentSessionWrapper is a container class defined in lib/rpc-manager.ts that manages the lifecycle and state of an AgentSession within the global registry. It holds the inner session object—including the sessionId—and provides methods like destroy() for cleanup.
Why can't the wrapper remain in the registry after forking?
If the wrapper remained under the original session ID, its mutated state (specifically the updated inner.sessionId) would be returned to subsequent requests for that original ID. This causes a logical error where requests believe they are accessing the original session but are actually receiving the forked session state, breaking the parentSession relationship chain.
How does the system reload the original session after the wrapper is destroyed?
Once this.destroy() removes the wrapper from the global registry (globalThis.__piSessions), the next incoming request for that session ID finds no active entry. This triggers the session loader to create a fresh AgentSessionWrapper instance from the original session file stored on disk, restoring the original state without the fork mutation.
Is the forked session immediately ready when AgentSession.fork() returns?
Yes. The method captures the newSessionId before destroying the original wrapper, ensuring the forked session is fully initialized and its ID returned to the caller. The destruction of the original wrapper happens only after the new session reference is safely captured, guaranteeing continuity of operations while maintaining state isolation.
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 →