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

# Forking

> Copy a Sailbox, or start a fleet of identical ones

Copy an existing Sailbox. The copy gets the original's disk and its memory, so
whatever was running continues in the copy.

## Usage

Take a checkpoint, then start as many Sailboxes from it as you like:

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box checkpoint <id> --name after-setup
    sail box from-checkpoint <checkpoint-id> --name worker-1
    ```

    ```python Python theme={null}
    checkpoint = sb.checkpoint(name="after-setup")
    child = sail.Sailbox.from_checkpoint(checkpoint.checkpoint_id, name="worker-1")
    ```

    ```typescript TypeScript theme={null}
    const checkpoint = await sb.checkpoint({ name: "after-setup" });
    const child = await Sailbox.fromCheckpoint({
      checkpointId: checkpoint.checkpointId,
      name: "worker-1",
    });
    ```

    ```rust Rust theme={null}
    use sail::CheckpointOptions;

    let checkpoint = sb
        .checkpoint(CheckpointOptions {
            name: Some("after-setup".to_string()),
            ..Default::default()
        })
        .await?;
    let child = client
        .create_from_checkpoint(&checkpoint.checkpoint_id, "worker-1", /* max_lifetime */ None)
        .await?;
    ```
  </CodeGroup>
</div>

A sleeping or paused Sailbox can be copied without waking it: `checkpoint`
returns its existing checkpoint. Starting several copies from one checkpoint
reuses the same checkpoint data, so only the first copy pays for it.

## Fan out to many

Set up one Sailbox (install dependencies, warm caches, start servers),
checkpoint it, and start every worker from that checkpoint instead of
repeating the setup in each one. This is the fast path to a fleet for agent
rollouts, parallel test shards, or grading many submissions at once.

<CodeGroup>
  ```python Python theme={null}
  import asyncio

  checkpoint = sb.checkpoint()

  async def fan_out(n: int) -> list[sail.Sailbox]:
      results = await asyncio.gather(
          *(
              sail.Sailbox.from_checkpoint.aio(
                  checkpoint.checkpoint_id,
                  name=f"worker-{i}",
              )
              for i in range(n)
          ),
          return_exceptions=True,  # keep the copies that came up
      )
      return [r for r in results if isinstance(r, sail.Sailbox)]

  children = asyncio.run(fan_out(32))
  ```

  ```typescript TypeScript theme={null}
  const checkpoint = await sb.checkpoint();

  const results = await Promise.allSettled(
    Array.from({ length: 32 }, (_, i) =>
      Sailbox.fromCheckpoint({
        checkpointId: checkpoint.checkpointId,
        name: `worker-${i}`,
      }),
    ),
  );

  // Keep the copies that came up even if some restores fail.
  const children = results.flatMap((r) =>
    r.status === "fulfilled" ? [r.value] : [],
  );
  ```

  ```rust Rust theme={null}
  use sail::CheckpointOptions;

  let checkpoint = sb.checkpoint(CheckpointOptions::default()).await?;

  let names: Vec<String> = (0..32).map(|i| format!("worker-{i}")).collect();
  let results = futures::future::join_all(names.iter().map(|name| {
      client.create_from_checkpoint(
          &checkpoint.checkpoint_id,
          name.as_str(),
          /* max_lifetime */ None,
      )
  }))
  .await;

  // Keep the copies that came up even if some restores fail.
  let children: Vec<sail::Sailbox> = results.into_iter().flatten().collect();
  ```
</CodeGroup>

Start the copies concurrently, as above, to overlap the restores. Collect
results per copy so one failed restore does not cost you the rest, and give
each a distinct name. Each copy is a full Sailbox: it bills like one and runs
until it sleeps or you terminate it, so clean up the fleet when the work is
done.

## What a copy gets

* **The disk and the memory.** Processes running in the original continue.
  A command started with `exec` stops in the copy, though its writes up to the
  checkpoint are kept. One started in the background keeps running. Start
  anything else the copy needs again.
* **A new identity and new networking.** Open TCP connections are reset, and
  the copy doesn't inherit exposed ports. Expose the ones it should serve.

## Checkpoints

A checkpoint is a durable snapshot with a name, an id, and an expiry. It lasts
seven days unless you set a TTL. Set one when a checkpoint is a template you
will keep using, so it does not expire while you still need it. Starting a
copy from an expired checkpoint fails.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box checkpoint <id> --name template --ttl-seconds 2592000
    ```

    ```python Python theme={null}
    checkpoint = sb.checkpoint(name="template", ttl_seconds=30 * 86400)
    print(checkpoint.expires_at)
    ```

    ```typescript TypeScript theme={null}
    const checkpoint = await sb.checkpoint({
      name: "template",
      ttlSeconds: 30 * 86400,
    });
    console.log(checkpoint.expiresAt);
    ```

    ```rust Rust theme={null}
    use std::time::Duration;
    use sail::CheckpointOptions;

    let checkpoint = sb
        .checkpoint(CheckpointOptions {
            name: Some("template".to_string()),
            ttl: Some(Duration::from_secs(30 * 86400)),
        })
        .await?;
    println!("{:?}", checkpoint.expires_at);
    ```
  </CodeGroup>
</div>

Checkpoints also protect the original. Take one after important setup, such
as installing packages or fetching data: if the machine under a Sailbox fails,
Sail restores it from the most recent completed checkpoint and does not replay
commands that ran before it.

### Find and delete checkpoints

Checkpoints belong to your organization. Listing shows the ones a Sailbox can
still be started from, newest first, and can narrow them by name, by the
Sailbox they were taken from, or by app. Deleting a checkpoint stops new
Sailboxes from starting from it. Sailboxes already started from it keep
running.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail checkpoint list --name template
    sail checkpoint delete <checkpoint-id>
    ```

    ```python Python theme={null}
    for checkpoint in sail.SailboxCheckpoint.list(name="template"):
        print(checkpoint.checkpoint_id, checkpoint.created_at)
    sail.SailboxCheckpoint.get(checkpoint_id).delete()
    ```

    ```typescript TypeScript theme={null}
    for (const checkpoint of await SailboxCheckpoint.list({ name: "template" })) {
      console.log(checkpoint.checkpointId, checkpoint.createdAt);
    }
    await (await SailboxCheckpoint.get(checkpointId)).delete();
    ```

    ```rust Rust theme={null}
    use sail::ListCheckpointsQuery;

    let checkpoints = client
        .list_checkpoints(&ListCheckpointsQuery {
            name: Some("template".to_string()),
            ..Default::default()
        })
        .await?;
    client.delete_checkpoint(&checkpoints[0].checkpoint_id).await?;
    ```
  </CodeGroup>
</div>

### Start fresh from a checkpoint's disk

A copy normally resumes the checkpoint's memory. To get only its files, start
the copy without memory. The new Sailbox boots fresh from the checkpoint's
disk: nothing that was running in the original runs in the copy, and nothing
that was only in memory carries over. It still boots this way if Sail has to
restore it before it takes a checkpoint of its own. Booting takes longer than
resuming memory.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box from-checkpoint <checkpoint-id> --name worker-1 --no-restore-memory
    ```

    ```python Python theme={null}
    child = sail.Sailbox.from_checkpoint(
        checkpoint_id, name="worker-1", restore_memory=False
    )
    ```

    ```typescript TypeScript theme={null}
    const child = await Sailbox.fromCheckpoint({
      checkpointId,
      name: "worker-1",
      restoreMemory: false,
    });
    ```

    ```rust Rust theme={null}
    use sail::FromCheckpointOptions;

    let child = client
        .create_from_checkpoint_with_options(
            &checkpoint_id,
            "worker-1",
            FromCheckpointOptions {
                restore_memory: false,
                ..Default::default()
            },
        )
        .await?;
    ```
  </CodeGroup>
</div>
