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

# Workspace backups

> Persist Daytona session files in organization-shared S3-compatible storage and restore deleted sandboxes.

OAO can copy each Daytona thread workspace to S3-compatible object storage.
This is separate from Daytona's own sandbox lifecycle: if Daytona no longer has
the sandbox, OAO creates a replacement and restores the latest verified archive
before the agent receives any sandbox tools.

The same organization-shared storage connections also hold raw run
attachments. Attachment
objects use separate deterministic keys, bind to the selected provider at
upload time, and are downloaded and integrity-checked before Daytona receives
them. PostgreSQL stores their run manifest but never their bytes or extracted
text. See [Send files to an agent](/integrations/files).

## Add a storage provider

1. Configure the same `OAO_CREDENTIAL_ENCRYPTION_KEY` for the API and runtime.
2. Open **Storage providers** in the console.
3. Select **Add storage provider**.
4. Enter the region, bucket, optional HTTP(S) endpoint and object prefix, URL
   style, and write-only S3 credentials.
5. Keep **Make default** selected, or make the connection the project default
   later.

AWS S3 works with an empty endpoint. Other compatible services can provide an
endpoint and enable path-style URLs when required. Connection coordinates are
immutable after creation so a thread's existing archive remains addressable.
Credentials can be rotated, and the project default can be changed.

<Warning>
  OAO encrypts the S3 credential in PostgreSQL, but it does not add a second
  encryption layer to archive objects. Restrict bucket access and configure the
  storage provider's server-side encryption, retention, versioning, and deletion
  policy for your requirements.
</Warning>

## Backup lifecycle

The first successful backup binds a thread to the selected storage provider.
Later default changes affect new threads; existing backed-up threads continue
using their original provider.

At each awaited agent-finish seam, OAO:

1. asks Daytona for the sandbox workdir used by the agent and creates a
   gzip-compressed tar archive of that directory inside the sandbox, excluding
   Daytona's `.daytona/` runtime directory and the snapshot's standard root
   shell/profile files (`.bash_logout`, `.bashrc`, `.face`, `.face.icon`,
   `.profile`, and `.zshrc`);
2. lists the regular files in that workdir and uploads a bounded JSON manifest
   beside the archive in the same tenant-scoped object-storage prefix;
3. uploads the archive to the thread's deterministic tenant-scoped object key;
4. stores the archive byte length, SHA-256 digest, latest owning run, timestamp, and generation
   in PostgreSQL; and
5. overwrites the previous latest archive and manifest for that thread.

The file manifest contains relative paths, names, and byte sizes. It is bound to
the archive's byte length and SHA-256, and it stays in object storage. PostgreSQL
does not store the workspace file list or any file contents. The manifest uses
the same exclusions as the archive, so ignored Daytona runtime files do not
appear in the session's **Files** list. Restoration also applies these
exclusions to older archives that may still contain those paths.

The finish hook is at least once. Repeating it is safe because the object key is
stable and PostgreSQL advances the recorded generation. A backup failure fails
the run instead of reporting durable completion while the newest files are
missing.

The compressed archive limit is 512 MiB, and restoration rejects archives whose
expanded tar stream exceeds 2 GiB. OAO deletes its temporary archive in the
sandbox after upload or restore.

## Restore lifecycle

When another run uses the thread, OAO first asks Daytona for the recorded
sandbox. A running or restartable sandbox is reused normally. If the recorded
sandbox is gone, OAO creates a replacement with the agent version's selected
snapshot, then:

1. downloads the latest thread archive;
2. verifies tenant metadata, byte length, and SHA-256 against PostgreSQL;
3. validates the tar listing;
4. asks the replacement sandbox for its workdir and extracts the archive there;
5. records the restore timestamp before exposing tools to the model.

If a recorded object is missing, exceeds the limit, is corrupt, or cannot be
extracted, restoration fails closed and the replacement sandbox is marked
failed. OAO does not silently start the agent with an empty workspace.

The session detail response exposes safe backup diagnostics under
`debug.workspaceBackups`, including provider key, bucket, object key, byte
length, generation, backup time, and last restore time. Credentials and archive
contents are never returned.

The console session panel reads the authoritative object-storage manifest and
lists every regular file captured in the latest workspace archive, including
files created indirectly by shell commands or APIs. Uploaded run attachments
are also taken from their durable upload manifest and merged by their exact
`.oao/attachments/{runId}/{filename}` path. An uploaded file can therefore show
**Uploaded + backed up**, while a generated workspace file shows **Backed up**.
Legacy archives without a sidecar manifest retain the earlier command-audit
fallback until a later run writes a new backup. The panel never downloads or
returns the archive itself. Each manifest-backed file has a download action
that calls the authenticated session Files API. That API reads the verified
archive from the bound persistent storage provider and never contacts Daytona.

## Retrieve a workspace file

List the latest persisted files with
`GET /projects/{projectId}/sessions/{sessionId}/files`, then download an exact
relative path with
`GET /projects/{projectId}/sessions/{sessionId}/files/{path}`. These routes use
`session:read`, the same organization/project tenant checks as session detail,
and the workspace owner's storage binding. This also makes a coordinator's
shared workspace available through an authorized delegated child session.

The list is empty until the first successful backup. Download verifies the
manifest binding, archive length, archive SHA-256, entry type, and file size
before returning bytes as a non-cacheable attachment. A missing or corrupt
object fails closed. OAO does not query or revive the Daytona sandbox for this
read path.

## API example

```bash theme={null}
curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $OAO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: workspace-primary-1' \
  --data '{
    "key":"workspace-primary",
    "displayName":"Workspace primary",
    "providerType":"s3",
    "endpoint":null,
    "region":"eu-central-1",
    "bucket":"oao-workspaces",
    "prefix":"production/oao",
    "forcePathStyle":false,
    "setDefault":true,
    "accessKeyId":"write-only-access-key",
    "secretAccessKey":"write-only-secret-key"
  }' \
  "$OAO_API_URL/projects/$OAO_PROJECT_ID/storage-providers"
```

See [HTTP API](/reference/http-api) for list, credential rotation, and default
selection routes.

## Current boundaries

* One latest archive is retained per thread. Historical generations depend on
  bucket versioning; OAO does not expose a generation browser or rollback API.
* Backup is automatic for Daytona-enabled threads when a default storage
  provider exists. There is no per-agent or per-session storage selector.
* OAO does not test bucket connectivity during connection creation. The first
  backup is the first live write.
* File downloads currently read and verify the latest compressed archive before
  extracting the requested entry; the API does not maintain one object per
  workspace file or support HTTP range requests.
* Removing remote objects or changing bucket policy outside OAO can make a
  recorded backup unrestorable.


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