> ## Documentation Index
> Fetch the complete documentation index at: https://code.storage/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Store Session State

> Store session state as ephemeral commits, exchange commit pointers, and keep normal branches unchanged.

Use one ephemeral branch for each agent session. Write one commit for each durable state, and pass
commit pointers between sandboxes and services.

This pattern keeps intermediate work out of normal branches. It also makes each prior state
available after its sandbox stops.

<Note>
  Create the session ID in your backend. Pass it to the worker as `SESSION_ID`. Keep the repository
  ID, branch name, and current commit SHA in the same session record.
</Note>

## Create the session branch

Create an ephemeral branch from the repository's normal default branch. One session branch holds
every state of one session.

Run this code in a trusted backend service. Set `PIERRE_PRIVATE_KEY` to the private key in PEM
format from the Code Storage dashboard. Do not give this key to a sandbox.

```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import { GitStorage } from '@pierre/storage';

const sessionId = process.env.SESSION_ID;

const store = new GitStorage({
  key: process.env.PIERRE_PRIVATE_KEY,
  name: 'acme',
});

const repositoryId = 'acme/storefront';
const repo = await store.findOne({ id: repositoryId });

const sessionBranch = `sessions/${sessionId}`;

const branch = await repo.createBranch({
  baseRef: repo.defaultBranch,
  targetBranch: sessionBranch,
  targetIsEphemeral: true,
});

if (!branch.commitSha) {
  throw new Error('The session branch has no base commit');
}

let headSha = branch.commitSha;
```

`baseRef` can also be a full commit SHA. Use a full SHA when the session must start from an exact
state.

## Save each state

Add one commit for each state that another process must read or restore. Each commit inherits
unchanged files from the prior state, so send only changed or deleted paths.

```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import { readFile } from 'node:fs/promises';

const previousHeadSha = headSha;
const updatedAuthSource = await readFile('src/auth.ts', 'utf8');
const taskState = { status: 'in_progress' };

const snapshot = await repo
  .createCommit({
    author: { email: 'agent@example.com', name: 'Agent' },
    commitMessage: 'Save session state',
    ephemeral: true,
    expectedHeadSha: headSha,
    targetBranch: sessionBranch,
  })
  .addFileFromString('src/auth.ts', updatedAuthSource)
  .addFileFromString('.agent/task.json', JSON.stringify(taskState))
  .send();

headSha = snapshot.commitSha;
```

`expectedHeadSha` lets the write succeed only if the branch still points to `headSha`. If another
writer changed the branch, the write fails with the `precondition_failed` reason. Stop the old
writer and reload the session record.

Store `headSha` in the backend session record after each successful write. See
[`createCommit()`](/docs/reference/sdk/create-commit) for file and stream inputs.

## Exchange commit pointers

Pass a pointer instead of file contents. We recommend a repository ID, a commit SHA, and an optional
path.

```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
const pointer = {
  path: 'src/auth.ts',
  repositoryId: repo.id,
  sha: headSha,
};

const pointerRepo = await store.findOne({ id: pointer.repositoryId });

const response = await pointerRepo.getFileStream({
  path: pointer.path,
  ref: pointer.sha,
});
const source = await response.text();

const diff = await pointerRepo.getCommitDiff({
  baseSha: previousHeadSha,
  sha: pointer.sha,
});

console.log(source, diff.stats);
```

A full commit SHA stays readable while any repository ref can reach it. Keep the session branch
until no reader needs its commits.

Use [`getFileStream()`](/docs/reference/sdk/get-file-stream) for files and
[`getCommitDiff()`](/docs/reference/sdk/get-commit-diff) for changes.

## Confine a sandbox to its own session

A sandbox never holds your private key. Give each sandbox a credential with a short TTL. Use
`refPolicies` to confine that credential to the session's own ref. The sandbox can then fast-forward
its own session branch only. It cannot change a normal branch or another session branch.

A backend service that writes states through the API needs none of this. It signs each request with
the private key and never hands a credential out.

[Connect a Sandbox](/docs/guides/sandboxes#confine-a-sandbox-to-one-session-branch) shows the URLs and
the Git setup. See [Ephemeral Namespace](/docs/guides/ephemeral-branches) for namespace details and
[Branch Protection](/docs/guides/branch-protection) for `refPolicies` rules.

<Warning>
  On GitHub App sync repositories, Git LFS uploads from an ephemeral URL still pass through to
  GitHub. Keep LFS-tracked files out of session states for now. See [GitHub
  Sync](/docs/guides/github-sync#how-git-sync-behaves).
</Warning>

## Next steps

* [Resume Sandbox Work](/docs/guides/resume-sandbox-work) restores the latest state after a sandbox
  stops.
* [Run Parallel Attempts](/docs/guides/parallel-attempts) creates several isolated branches from one
  state.
* [Show Live Diffs](/docs/guides/live-diffs) renders the state as it changes.
