Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion registry/agent/pi/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,21 @@ Object.defineProperty(process, "stdin", {

type SessionManagerLike = {
inMemory(cwd?: string): unknown;
continueRecent(cwd: string, sessionDir: string): unknown;
};

/** Select persistent Pi sessions only when the adapter is explicitly configured. */
export function resolveSessionManager(
SessionManager: SessionManagerLike,
cwd: string,
env: Record<string, string | undefined> = process.env,
): unknown {
const sessionDir = env.PI_SESSION_DIR?.trim();
return sessionDir
? SessionManager.continueRecent(cwd, sessionDir)
: SessionManager.inMemory(cwd);
}

type ModelLike = {
id: string;
provider: string;
Expand Down Expand Up @@ -873,7 +886,7 @@ export class PiSdkAgent implements Agent {
const { session } = await __trace.span("createAgentSession", () =>
createAgentSession({
cwd: params.cwd,
sessionManager: SessionManager.inMemory(params.cwd),
sessionManager: resolveSessionManager(SessionManager, params.cwd),
resourceLoader,
tools: this.wrapTools(
createCodingTools(params.cwd, {
Expand Down
51 changes: 50 additions & 1 deletion registry/agent/pi/tests/adapter.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { resolve as resolvePath } from "node:path";
// testing; newSession needs the real Pi SDK, so these drive the translation /
// lifecycle logic directly with a mock ACP connection + a fake session.
const packageDir = resolvePath(import.meta.dirname, "..");
const { PiSdkAgent } = await import(resolvePath(packageDir, "dist", "adapter.js"));
const { PiSdkAgent, resolveSessionManager } = await import(
resolvePath(packageDir, "dist", "adapter.js")
);

function makeConn(overrides = {}) {
return {
Expand All @@ -26,6 +28,53 @@ function fakeSession(overrides = {}) {
};
}

test("PI_SESSION_DIR persists and resumes the most recent Pi session", () => {
const calls = [];
const manager = {
inMemory(cwd) {
calls.push(["inMemory", cwd]);
return "memory";
},
continueRecent(cwd, sessionDir) {
calls.push(["continueRecent", cwd, sessionDir]);
return "persistent";
},
};

assert.equal(
resolveSessionManager(manager, "/workspace", {
PI_SESSION_DIR: " /data/pi-sessions ",
}),
"persistent",
);
assert.deepEqual(calls, [
["continueRecent", "/workspace", "/data/pi-sessions"],
]);
});

test("Pi sessions remain in memory when PI_SESSION_DIR is unset or blank", () => {
const calls = [];
const manager = {
inMemory(cwd) {
calls.push(["inMemory", cwd]);
return "memory";
},
continueRecent() {
throw new Error("continueRecent should not be called");
},
};

assert.equal(resolveSessionManager(manager, "/workspace", {}), "memory");
assert.equal(
resolveSessionManager(manager, "/workspace", { PI_SESSION_DIR: " " }),
"memory",
);
assert.deepEqual(calls, [
["inMemory", "/workspace"],
["inMemory", "/workspace"],
]);
});

// ── Fix #3: editSnapshots is cleared each turn (no unbounded leak) ──
test("pi #3: prompt() clears leaked edit snapshots from a prior aborted turn", async () => {
const agent = new PiSdkAgent(makeConn());
Expand Down
9 changes: 8 additions & 1 deletion website/public/docs/docs/agents/pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ Set the relevant variable on the session's `env`, sourced from your server's env

See [LLM Credentials](/docs/llm-credentials), and Pi's [providers docs](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/providers.md) for the full list.

## Persistent Pi sessions

Pi keeps its own conversation state in memory by default. To preserve and resume
the most recent Pi session after the adapter restarts, set `PI_SESSION_DIR` in the
session's `env` to a directory on durable VM storage. Pi writes its session JSONL
files there; an unset or blank value retains the in-memory default.

## Skills

Pi discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and Pi loads it automatically.
Expand All @@ -40,4 +47,4 @@ See the [Pi extension documentation](https://github.com/badlogic/pi-mono/tree/ma

## Customizing the agent

Pi is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked Pi build as a new agent, see [Custom Agents](/docs/agents/custom).
Pi is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked Pi build as a new agent, see [Custom Agents](/docs/agents/custom).
Loading