Concepts

Sessions

Inspect token usage and history, branch or duplicate a conversation, name it, and export it — all without sending a prompt.

An agent's conversation is a Pi session, stored as a file in the container. The methods here read and reshape it without streaming a new prompt — for dashboards, cost tracking, and trying an alternative from an earlier point. None of them touch the filesystem: a session is a conversation, not a sandbox.

Token usage and cost

agent.getSessionStats() returns a snapshot of the current session's resource consumption:

const stats = await agent.getSessionStats();

console.log(`Session:       ${stats.sessionId}`);
console.log(`Messages:      ${stats.userMessages} user / ${stats.assistantMessages} assistant`);
console.log(`Tool calls:    ${stats.toolCalls}`);
console.log(`Tokens:        ${stats.tokens.input} in / ${stats.tokens.output} out`);
console.log(`Cache:         ${stats.tokens.cacheRead} read / ${stats.tokens.cacheWrite} write`);
console.log(`Cost:          $${stats.cost.toFixed(6)}`);

if (stats.contextUsage) {
  const { tokens, contextWindow, percent } = stats.contextUsage;
  console.log(`Context:       ${percent.toFixed(1)}% (${tokens} / ${contextWindow})`);
}

This is a synchronous read from Pi — it doesn't start a new prompt. Call it after any prompt() completes to track cumulative usage across a long session.

Context window pressure

contextUsage.percent tells you how full Pi's context window is. Pi will auto-compact when it approaches the limit (see Streaming — Compaction events), but you can also monitor it proactively:

const stats = await agent.getSessionStats();
if ((stats.contextUsage?.percent ?? 0) > 80) {
  await agent.compact(); // manual compaction before it becomes automatic
}

Retrieving the last response

agent.getLastAssistantText() returns the text of Pi's most recent assistant message without opening a new stream:

const text = await agent.getLastAssistantText();
if (text) {
  console.log(`Last response: ${text.slice(0, 200)}`);
}

Returns null if Pi has not yet responded in the current session. Useful for building "copy last response" buttons or logging the final assistant message after a long autonomous run.

Branching a conversation

agent.getBranchPoints() lists the points in the current session a conversation can be branched from:

const points = await agent.getBranchPoints();

for (const { entryId, text } of points) {
  console.log(`${entryId}  "${text.slice(0, 60)}"`);
}

// Branch from a specific point
if (points.length > 0) {
  const { text, cancelled } = await agent.branchSession(points[0].entryId);
  console.log(`Branched at: "${text.slice(0, 60)}" (cancelled: ${cancelled})`);
}

Each entry corresponds to a user turn in the harness's session history. Branching creates a new session branch at that point — useful for trying alternative responses or replaying from a known state. It is a conversation branch, not a sandbox copy: agent.sandbox.fork() is what does the latter.

Three more methods start a different conversation in the same container:

MethodResult
agent.newSession()A fresh, empty session. The filesystem is unchanged.
agent.duplicateSession()A copy of the current session, to continue in a different direction.
agent.switchSession(path)Another session already on disk, by its session file path.

Signatures are on Pi RPC passthroughs.

Available commands

agent.getCommands() lists all slash commands Pi can understand in the current environment — including commands contributed by installed extensions, prompt templates, and skills:

const commands = await agent.getCommands();

for (const cmd of commands) {
  const source = cmd.source; // "extension" | "prompt" | "skill"
  const desc = cmd.description ?? "(no description)";
  console.log(`/${cmd.name} [${source}]  ${desc}`);
}

This is primarily useful when building a command palette or auto-complete UI on top of the agent.

Naming sessions

agent.setSessionName() sets a display name for the current Pi session. The name is stored as metadata in the session file — it doesn't affect the session ID or behaviour:

await agent.setSessionName(`audit-${new Date().toISOString().slice(0, 10)}`);

Exporting an HTML transcript

agent.exportHtml() generates a static HTML file containing the full session transcript and writes it to the sandbox filesystem:

const { path } = await agent.exportHtml();
// path is a container-relative path, e.g. /root/pi-session-2024-01-15_abc123.html

// Read the file from the sandbox back to the host
const html = await agent.sandbox.readFile(path);

// Write it somewhere local
await Bun.write("./session-transcript.html", html);

You can optionally specify where the file should be written inside the container:

const { path } = await agent.exportHtml("/workspace/exports/session.html");