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

# Capture Agent State

> Capture agent 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
> name, branch name, and current commit SHA in the same session record.

## 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_ORG` to your organization slug. 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 { Pierre } from '@pierre/sdk';

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

const repoName = 'acme/storefront';
const sessionId = process.env.SESSION_ID!;
const repo = await pierre.repos.get({ repo_name: repoName });

const branch = await pierre.repos.branches.create({
  repo_name: repoName,
  base_ref: repo.default_branch,
  target_branch: `sessions/${sessionId}`,
  target_is_ephemeral: true,
});

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

const sessionBranch = branch.target_branch;
let headSha = branch.commit_sha;
```

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

from pierre import Pierre

pierre = Pierre()  # reads PIERRE_ORG and PIERRE_PRIVATE_KEY

repo_name = "acme/storefront"
session_id = os.environ["SESSION_ID"]
repo = pierre.repos.get(repo_name=repo_name)

branch = pierre.repos.branches.create(
    repo_name=repo_name,
    base_ref=repo["default_branch"],
    target_branch=f"sessions/{session_id}",
    target_is_ephemeral=True,
)

if not branch.get("commit_sha"):
    raise RuntimeError("The session branch has no base commit")

session_branch = branch["target_branch"]
head_sha = branch["commit_sha"]
```

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

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

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

repoName := "acme/storefront"
sessionID := os.Getenv("SESSION_ID")
repo, err := client.Repos.Get(ctx, pierre.ReposGetParams{RepoName: repoName})
if err != nil {
	return err
}

branch, err := client.Repos.Branches.Create(ctx, pierre.ReposBranchesCreateParams{
	RepoName:          repoName,
	BaseRef:           pierre.String(repo.DefaultBranch),
	TargetBranch:      pierre.String(fmt.Sprintf("sessions/%s", sessionID)),
	TargetIsEphemeral: pierre.Bool(true),
})
if err != nil {
	return err
}

headSHA, ok := branch.CommitSHA.Get()
if !ok {
	return errors.New("the session branch has no base commit")
}

sessionBranch := branch.TargetBranch
```

`base_ref` 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 pierre.repos.commits.create({
  repo_name: repoName,
  author: { email: 'agent@example.com', name: 'Agent' },
  commit_message: 'Save session state',
  expected_target_sha: headSha,
  target_branch: sessionBranch,
  target_is_ephemeral: true,
  files: [
    { path: 'src/auth.ts', content: updatedAuthSource },
    { path: '.agent/task.json', content: JSON.stringify(taskState) },
  ],
});

headSha = snapshot.commit.commit_sha;
```

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

previous_head_sha = head_sha
updated_auth_source = Path("src/auth.ts").read_text()
task_state = {"status": "in_progress"}

snapshot = pierre.repos.commits.create(
    repo_name=repo_name,
    author={"email": "agent@example.com", "name": "Agent"},
    commit_message="Save session state",
    expected_target_sha=head_sha,
    target_branch=session_branch,
    target_is_ephemeral=True,
    files=[
        {"path": "src/auth.ts", "content": updated_auth_source},
        {"path": ".agent/task.json", "content": json.dumps(task_state)},
    ],
)

head_sha = snapshot["commit"]["commit_sha"]
```

```go Go theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
import (
	"bytes"
	"encoding/json"
)

previousHeadSHA := headSHA
updatedAuthSource, err := os.ReadFile("src/auth.ts")
if err != nil {
	return err
}
taskState, err := json.Marshal(map[string]string{"status": "in_progress"})
if err != nil {
	return err
}

snapshot, err := client.Repos.Commits.Create(ctx, pierre.ReposCommitsCreateParams{
	RepoName:          repoName,
	Author:            pierre.GitIdentity{Email: "agent@example.com", Name: "Agent"},
	CommitMessage:     "Save session state",
	ExpectedTargetSHA: pierre.String(headSHA),
	TargetBranch:      sessionBranch,
	TargetIsEphemeral: pierre.Bool(true),
	Files: []pierre.CommitFile{
		{Path: "src/auth.ts", Content: bytes.NewReader(updatedAuthSource)},
		{Path: ".agent/task.json", Content: bytes.NewReader(taskState)},
	},
})
if err != nil {
	return err
}

headSHA = snapshot.Commit.CommitSHA
```

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

Store `headSha` in the backend session record after each successful write. See
[`repos.commits.create`](https://code.storage/docs/api/repos/commit-pack) for file and stream inputs.

## Exchange commit pointers

Pass a pointer instead of file contents. We recommend a repository name, 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',
  repoName,
  sha: headSha,
};

const response = await pierre.repos.files.get({
  repo_name: pointer.repoName,
  path: pointer.path,
  ref: pointer.sha,
});
const source = await response.text();

const diff = await pierre.repos.diff.get({
  repo_name: pointer.repoName,
  base_ref: previousHeadSha,
  ref: pointer.sha,
});

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

```python Python theme={null} theme={"theme":{"light":"github-light","dark":"min-dark"}}
pointer = {
    "path": "src/auth.ts",
    "repo_name": repo_name,
    "sha": head_sha,
}

response = pierre.repos.files.get(
    repo_name=pointer["repo_name"],
    path=pointer["path"],
    ref=pointer["sha"],
)
source = response.read().decode()

diff = pierre.repos.diff.get(
    repo_name=pointer["repo_name"],
    base_ref=previous_head_sha,
    ref=pointer["sha"],
)

print(source, diff["stats"])
```

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

pointer := struct {
	Path     string
	RepoName string
	SHA      string
}{
	Path:     "src/auth.ts",
	RepoName: repoName,
	SHA:      headSHA,
}

response, err := client.Repos.Files.Get(ctx, pierre.ReposFilesGetParams{
	RepoName: pointer.RepoName,
	Path:     pointer.Path,
	Ref:      pierre.String(pointer.SHA),
})
if err != nil {
	return err
}
defer response.Close()
source, err := io.ReadAll(response)
if err != nil {
	return err
}

diff, err := client.Repos.Diff.Get(ctx, pierre.ReposDiffGetParams{
	RepoName: pointer.RepoName,
	BaseRef:  pierre.String(previousHeadSHA),
	Ref:      pierre.String(pointer.SHA),
})
if err != nil {
	return err
}

fmt.Println(string(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 [`repos.files.get`](https://code.storage/docs/api/repos/file-get) for files and [`repos.diff.get`](https://code.storage/docs/api/repos/diff-get)
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
`ref_policies` 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.

[External Sandbox Integration](https://code.storage/docs/repos/external-sandboxes.md#confine-a-sandbox-to-one-session-branch)
shows the URLs and the Git setup. See [Ephemeral Namespace](https://code.storage/docs/repos/ephemeral-branches.md) for namespace
details and [Ref Policies](https://code.storage/docs/repos/ref-policies.md) for the rules of `ref_policies`.

## Next steps

* [Restore Agent State](https://code.storage/docs/guides/restore-agent-state.md) restores the latest state after a sandbox
  stops.
* [Run Parallel Attempts](https://code.storage/docs/guides/parallel-attempts.md) creates several isolated branches from one
  state.
* [Show Live Diffs](https://code.storage/docs/guides/live-diffs.md) renders the state as it changes.


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