You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Pi can change the working directory of a live session in-process. We never found out, so this extension relaunches the whole pi process through a terminal multiplexer instead.
The mechanism we missed
ctx.switchSession(sessionPath, { withSession }) tears down the current runtime and rebuilds it bound to the target session's cwd:
// pi-coding-agent/dist/core/agent-session-runtime.js:128asyncswitchSession(sessionPath,options){awaitthis.teardownCurrent("resume",sessionManager.getSessionFile());this.apply(awaitthis.createRuntime({cwd: sessionManager.getCwd(),// the whole runtime is rebound here
...
}));awaitthis.finishSessionReplacement(options?.withSession);}
Verified against the dist, not inferred:
Tools rebind.createBashToolDefinition(cwd, …) closes over the cwd handed to it at creation. New runtime, new cwd, correct bash/read/edit.
Resources reload from the new directory.core/agent-session-services.js:53 rebuilds SettingsManager and DefaultResourceLoader against the new cwd — project extensions, skills, AGENTS.md. Equivalent to a restart.
It is on our floor.switchSession and SessionManager.forkFrom both exist in the 0.83.0 copy in node_modules. Our peer dep is already >=0.83.0.
All three modes wire it — interactive, print and rpc (modes/*.js). Headless included.
Prior art: @narumitw/pi-worktree does the switch in about 100 lines (src/session.ts): fork the current session into the target path with SessionManager.forkFrom, switchSession to it, report from withSession.
Why it matters
Roughly 1,000–1,500 of our ~5,500 extension lines exist only because we restart the process:
~half is relaunch plumbing (FORK_IF_USABLE, buildRelaunchCommand); caveats and continuations survive
buildVerifiedTeardownScript (in worktree.ts)
~150
exists only because the process stands in the doomed directory
extensions/worktree-receipt.ts
694
claim/receipt logic survives; waiter-ownership proving does not
It also retires the outcomes we are least proud of. manual-restart and most of path-target exist because a multiplexer was absent — a constraint that stops applying. Headless runs get a real hop for the first time.
process.cwd() never changes.grep process.chdir across the pi dist returns nothing. Tools take an explicit cwd so they are fine, but after a dispose the process's OS cwd points at a deleted directory, and any bare process.cwd() call then throws ENOENT.
A tool cannot switch its own session. Calling switchSession mid-tool tears down the agent awaiting the result, and waitForIdle() would deadlock on our own execution. The prior art sidesteps this by registering no tool at all; we cannot.
What is not on the table
The switch replaces the transport, not the product. Provisioning hooks, provisioning receipts, concurrency claims, the discipline guard, pi --worktree, conventional-commit branch resolution, the worktree_session tool, and dispose-the-tree-you-are-standing-in all stay. The prior art has none of them.
The truthful outcome contract stays too. Fewer outcomes will be reachable; none may start lying.
Pi can change the working directory of a live session in-process. We never found out, so this extension relaunches the whole pi process through a terminal multiplexer instead.
The mechanism we missed
ctx.switchSession(sessionPath, { withSession })tears down the current runtime and rebuilds it bound to the target session's cwd:Verified against the dist, not inferred:
createBashToolDefinition(cwd, …)closes over the cwd handed to it at creation. New runtime, new cwd, correct bash/read/edit.core/agent-session-services.js:53rebuildsSettingsManagerandDefaultResourceLoaderagainst the new cwd — project extensions, skills, AGENTS.md. Equivalent to a restart.switchSessionandSessionManager.forkFromboth exist in the 0.83.0 copy innode_modules. Our peer dep is already>=0.83.0.modes/*.js). Headless included.Prior art:
@narumitw/pi-worktreedoes the switch in about 100 lines (src/session.ts): fork the current session into the target path withSessionManager.forkFrom,switchSessionto it, report fromwithSession.Why it matters
Roughly 1,000–1,500 of our ~5,500 extension lines exist only because we restart the process:
extensions/worktree-transport.tsextensions/worktree-handoff.tsFORK_IF_USABLE,buildRelaunchCommand); caveats and continuations survivebuildVerifiedTeardownScript(inworktree.ts)extensions/worktree-receipt.tsIt also retires the outcomes we are least proud of.
manual-restartand most ofpath-targetexist because a multiplexer was absent — a constraint that stops applying. Headless runs get a real hop for the first time.Order of work
/worktree enter, command-driven and idleagent_settledTwo risks that must be tested, not assumed
process.cwd()never changes.grep process.chdiracross the pi dist returns nothing. Tools take an explicit cwd so they are fine, but after a dispose the process's OS cwd points at a deleted directory, and any bareprocess.cwd()call then throws ENOENT.A tool cannot switch its own session. Calling
switchSessionmid-tool tears down the agent awaiting the result, andwaitForIdle()would deadlock on our own execution. The prior art sidesteps this by registering no tool at all; we cannot.What is not on the table
The switch replaces the transport, not the product. Provisioning hooks, provisioning receipts, concurrency claims, the discipline guard,
pi --worktree, conventional-commit branch resolution, theworktree_sessiontool, and dispose-the-tree-you-are-standing-in all stay. The prior art has none of them.The truthful outcome contract stays too. Fewer outcomes will be reachable; none may start lying.