> ## 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 carries on 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", /* timeout */ 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}",
                  timeout=600,
              )
              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}`,
        timeoutSeconds: 600,
      }),
    ),
  );

  // 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 std::time::Duration;

  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(),
          Some(Duration::from_secs(600)),
      )
  }))
  .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, so the restores overlap. Collect
results per copy so one failed restore does not cost you the rest, give each a
distinct name, and pass a timeout so a stuck restore fails that copy instead
of stalling the batch. 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 the original was running carry on.
  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 inherits no 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 underneath you. 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.
