perf: skip cloud-init for minimal-tier agents with tarballs/snapshots (#2804)

* perf: skip cloud-init for minimal-tier agents with tarballs/snapshots

Ubuntu 24.04 base images already have curl + git, so minimal-tier
agents (claude, opencode, zeroclaw, hermes) don't need the cloud-init
package install step when using tarballs or snapshots.

Adds skipCloudInit flag to CloudOrchestrator — set automatically when
(tarball || snapshot) && tier === "minimal". Each cloud's waitForReady
checks this flag and calls waitForSshOnly instead of waitForCloudInit.

Saves ~30-60s on minimal-tier agent deploys with --fast or --beta tarball.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add --fast mode and updated beta features to README

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: remove timing table from README

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
This commit is contained in:
Ahmed Abushagur 2026-03-19 16:14:49 -07:00 committed by GitHub
parent 66036bfac9
commit 2280550c18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 60 additions and 9 deletions

View file

@ -119,18 +119,35 @@ Available steps vary by agent:
| `telegram` | openclaw | Telegram bot (set `TELEGRAM_BOT_TOKEN` for non-interactive) |
| `whatsapp` | openclaw | WhatsApp linking (interactive QR scan, skipped in headless) |
#### Beta Features
#### Fast Mode
Experimental features can be enabled with `--beta <feature>`. The flag is repeatable:
Use `--fast` for significantly faster deploys. Enables all speed optimizations:
```bash
spawn claude gcp --beta tarball
spawn claude hetzner --fast
```
What `--fast` does:
- **Parallel boot**: server creation runs concurrently with API key prompt and account checks
- **Tarballs**: installs agents from pre-built tarballs instead of live install
- **Skip cloud-init**: for lightweight agents (Claude, OpenCode, ZeroClaw, Hermes), skips the package install wait since the base OS already has what's needed
- **Snapshots**: uses pre-built cloud images when available (Hetzner, DigitalOcean)
#### Beta Features
Individual optimizations can be enabled separately with `--beta <feature>`. The flag is repeatable:
```bash
spawn claude gcp --beta tarball --beta parallel
```
| Feature | Description |
|---------|-------------|
| `tarball` | Use pre-built tarball for agent install (faster, skips live install) |
| `images` | Use pre-built DigitalOcean marketplace images (faster boot, skips cloud-init) |
| `images` | Use pre-built cloud images/snapshots (faster boot) |
| `parallel` | Parallelize server boot with setup prompts |
`--fast` enables all three.
### Without the CLI

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.24.1",
"version": "0.24.2",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -1039,6 +1039,11 @@ async function waitForSsh(maxAttempts = 36): Promise<void> {
});
}
export async function waitForSshOnly(): Promise<void> {
await waitForSsh();
logInfo("SSH available (skipping cloud-init)");
}
export async function waitForCloudInit(maxAttempts = 60): Promise<void> {
await waitForSsh();
const keyOpts = getSshKeyOpts(await ensureSshKeys());

View file

@ -22,6 +22,7 @@ import {
runServer,
uploadFile,
waitForCloudInit,
waitForSshOnly,
} from "./aws";
async function main() {
@ -58,7 +59,11 @@ async function main() {
},
getServerName,
async waitForReady() {
await waitForCloudInit();
if (cloud.skipCloudInit) {
await waitForSshOnly();
} else {
await waitForCloudInit();
}
},
interactiveSession,
getConnectionInfo,

View file

@ -103,7 +103,7 @@ async function main() {
},
getServerName,
async waitForReady() {
if (marketplaceImage) {
if (marketplaceImage || cloud.skipCloudInit) {
await waitForSshOnly();
} else {
await waitForCloudInit();

View file

@ -899,6 +899,11 @@ async function waitForSsh(maxAttempts = 36): Promise<void> {
});
}
export async function waitForSshOnly(): Promise<void> {
await waitForSsh();
logInfo("SSH available (skipping cloud-init)");
}
export async function waitForCloudInit(maxAttempts = 120): Promise<void> {
await waitForSsh();

View file

@ -23,6 +23,7 @@ import {
runServer,
uploadFile,
waitForCloudInit,
waitForSshOnly,
} from "./gcp";
async function main() {
@ -64,7 +65,11 @@ async function main() {
},
getServerName,
async waitForReady() {
await waitForCloudInit();
if (cloud.skipCloudInit) {
await waitForSshOnly();
} else {
await waitForCloudInit();
}
},
interactiveSession,
getConnectionInfo,

View file

@ -67,7 +67,7 @@ async function main() {
},
getServerName,
async waitForReady() {
if (snapshotId) {
if (snapshotId || cloud.skipCloudInit) {
await waitForSshOnly();
} else {
await waitForCloudInit();

View file

@ -38,6 +38,8 @@ export interface CloudOrchestrator {
runner: CloudRunner;
/** When true, skip tarball + agent install (e.g. booting from a pre-baked snapshot). */
skipAgentInstall?: boolean;
/** When true, skip cloud-init wait — just wait for SSH (e.g. minimal-tier agent with tarball). */
skipCloudInit?: boolean;
authenticate(): Promise<void>;
checkAccountReady?(): Promise<void>;
promptSize(): Promise<void>;
@ -121,6 +123,18 @@ export async function runOrchestration(
const fastMode = process.env.SPAWN_FAST === "1" || betaFeatures.has("parallel");
const useTarball = fastMode || betaFeatures.has("tarball");
// Skip cloud-init for minimal-tier agents when using tarballs or snapshots.
// Ubuntu 24.04 base images already have curl + git, so minimal agents (claude,
// opencode, zeroclaw, hermes) don't need the cloud-init package install step.
// This saves ~30-60s by just waiting for SSH instead of polling for cloud-init completion.
if (
cloud.cloudName !== "local" &&
(useTarball || cloud.skipAgentInstall) &&
(agent.cloudInitTier === "minimal" || !agent.cloudInitTier)
) {
cloud.skipCloudInit = true;
}
// 1b. Size/bundle selection (must happen before createServer)
await cloud.promptSize();