> ## 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.

# Restore Agent State

> Restore the last agent state in a new sandbox and reject writes from the old sandbox.

Use this pattern when a person or an agent must continue work in a new sandbox. The new sandbox
restores the last commit and continues on the same ephemeral branch.

This guide uses the branch layout from [Capture Agent State](https://code.storage/docs/guides/capture-agent-state.md).

## Load the last state

Store the repository name, branch name, and current commit SHA in your backend. Update this record
after each successful state write.

When your backend starts a replacement worker, pass the stored values as environment variables. Set
`PIERRE_ORG` to your organization slug. `PIERRE_PRIVATE_KEY` holds the private key for your Code
Storage organization. Keep this key in trusted server code.

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

const repoName = process.env.REPO_NAME!;
const sessionId = process.env.SESSION_ID!;
const tipSha = process.env.TIP_SHA!;

const pierre = new Pierre(); // reads PIERRE_ORG and PIERRE_PRIVATE_KEY

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

```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import os

from pierre import Pierre

repo_name = os.environ["REPO_NAME"]
session_id = os.environ["SESSION_ID"]
tip_sha = os.environ["TIP_SHA"]

pierre = Pierre()  # reads PIERRE_ORG and PIERRE_PRIVATE_KEY

session_branch = f"sessions/{session_id}"
```

```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import (
	"context"
	"fmt"
	"os"

	pierre "github.com/pierrecomputer/sdk/go"
)

repoName := os.Getenv("REPO_NAME")
sessionID := os.Getenv("SESSION_ID")
tipSHA := os.Getenv("TIP_SHA")

ctx := context.Background()
client, err := pierre.NewClient() // reads PIERRE_ORG and PIERRE_PRIVATE_KEY
if err != nil {
	return err
}

sessionBranch := fmt.Sprintf("sessions/%s", sessionID)
```

If your backend loses the tip SHA, call [`repos.branches.get`](https://code.storage/docs/api/repos/branch-get) with the
session branch name and `ephemeral` set to true. The result's `branch.head_sha` is the tip SHA.

## Restore the files

Use an archive when the sandbox only needs a file tree.

```typescript TypeScript theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
const archive = await pierre.repos.archive.get({ repo_name: repoName, ref: tipSha });

// Stream archive.body to the sandbox and extract the tar.gz response.
```

```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
archive = pierre.repos.archive.get(repo_name=repo_name, ref=tip_sha)

# Stream the archive to the sandbox. Then extract the tar.gz response.
archive.close()
```

```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
archive, err := client.Repos.Archive.Get(ctx, pierre.ReposArchiveGetParams{
	RepoName: repoName,
	Ref:      pierre.String(tipSHA),
})
if err != nil {
	return err
}
defer archive.Close()

// Stream the archive to the sandbox. Then extract the tar.gz response.
```

Use Git when the sandbox must create commits. Use a normal read URL to fetch a full SHA. The SHA
must remain reachable from an ephemeral ref.

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

const readUrl = await remoteUrl(pierre, {
  repo_name: repoName,
  scopes: ['git:read'],
  ttl: 600,
});
```

```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
from pierre.git import remote_url

read_url = remote_url(
    pierre,
    repo_name=repo_name,
    scopes=["git:read"],
    ttl=600,
)
```

```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import (
	"time"

	"github.com/pierrecomputer/sdk/go/git"
)

readURL, err := git.RemoteURL(ctx, client, git.RemoteURLParams{
	RepoName: repoName,
	Scopes:   []string{"git:read"},
	TTL:      10 * time.Minute,
})
if err != nil {
	return err
}
_ = readURL // Pass it to the new sandbox. The URL holds a token, so do not log it.
```

Pass `readUrl`, `sessionBranch`, and `tipSha` to the new sandbox as `READ_URL`, `SESSION_BRANCH`,
and `TIP_SHA`.

```bash theme={"theme":{"light":"github-light","dark":"min-dark"}}
git clone --no-checkout "$READ_URL" work
git -C work fetch origin "$TIP_SHA"
git -C work switch -c "$SESSION_BRANCH" "$TIP_SHA"
```

Use the full 40-character SHA that the API returns. Do not use an abbreviated SHA or a revision
expression.

See [`repos.archive.get`](https://code.storage/docs/api/repos/archive) for archive filters. See
[External Sandbox Integration](https://code.storage/docs/repos/external-sandboxes.md#confine-a-sandbox-to-one-session-branch)
for the separate push URL.

## Reject stale writes

Write the next state only when the branch still has the tip that you restored.

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

const updatedAuthSource = await readFile('src/auth.ts', 'utf8');

const nextState = await pierre.repos.commits.create({
  repo_name: repoName,
  author: { email: 'worker@example.com', name: 'Sandbox worker' },
  commit_message: 'Resume sandbox work',
  expected_target_sha: tipSha,
  target_branch: sessionBranch,
  target_is_ephemeral: true,
  files: [{ path: 'src/auth.ts', content: updatedAuthSource }],
});

console.log(nextState.commit.commit_sha);
```

```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
from pathlib import Path

updated_auth_source = Path("src/auth.ts").read_text()

next_state = pierre.repos.commits.create(
    repo_name=repo_name,
    author={"email": "worker@example.com", "name": "Sandbox worker"},
    commit_message="Resume sandbox work",
    expected_target_sha=tip_sha,
    target_branch=session_branch,
    target_is_ephemeral=True,
    files=[{"path": "src/auth.ts", "content": updated_auth_source}],
)

print(next_state["commit"]["commit_sha"])
```

```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import "bytes"

updatedAuthSource, err := os.ReadFile("src/auth.ts")
if err != nil {
	return err
}

nextState, err := client.Repos.Commits.Create(ctx, pierre.ReposCommitsCreateParams{
	RepoName:          repoName,
	Author:            pierre.GitIdentity{Email: "worker@example.com", Name: "Sandbox worker"},
	CommitMessage:     "Resume sandbox work",
	ExpectedTargetSHA: pierre.String(tipSHA),
	TargetBranch:      sessionBranch,
	TargetIsEphemeral: pierre.Bool(true),
	Files: []pierre.CommitFile{
		{Path: "src/auth.ts", Content: bytes.NewReader(updatedAuthSource)},
	},
})
if err != nil {
	return err
}

fmt.Println(nextState.Commit.CommitSHA)
```

If another sandbox wrote first, this call fails with a `RefGuardError`. Stop the old sandbox. Do not
retry until your backend assigns a new owner.

For Git pushes, keep `no-force-push` on the session branch. An old checkout cannot replace the new
commits with a non-fast-forward push.

## Record the handoff

Store `nextState.commit.commit_sha` as the current tip. Record which person, agent, and sandbox own
the session.

Use short URL lifetimes. Mint a new write URL for the replacement sandbox. Do not reuse the old
sandbox URL.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.