mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-20 18:00:23 +00:00
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:
parent
66036bfac9
commit
2280550c18
9 changed files with 60 additions and 9 deletions
25
README.md
25
README.md
|
|
@ -119,18 +119,35 @@ Available steps vary by agent:
|
||||||
| `telegram` | openclaw | Telegram bot (set `TELEGRAM_BOT_TOKEN` for non-interactive) |
|
| `telegram` | openclaw | Telegram bot (set `TELEGRAM_BOT_TOKEN` for non-interactive) |
|
||||||
| `whatsapp` | openclaw | WhatsApp linking (interactive QR scan, skipped in headless) |
|
| `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
|
```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 |
|
| Feature | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `tarball` | Use pre-built tarball for agent install (faster, skips live install) |
|
| `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
|
### Without the CLI
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@openrouter/spawn",
|
"name": "@openrouter/spawn",
|
||||||
"version": "0.24.1",
|
"version": "0.24.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"spawn": "cli.js"
|
"spawn": "cli.js"
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
export async function waitForCloudInit(maxAttempts = 60): Promise<void> {
|
||||||
await waitForSsh();
|
await waitForSsh();
|
||||||
const keyOpts = getSshKeyOpts(await ensureSshKeys());
|
const keyOpts = getSshKeyOpts(await ensureSshKeys());
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
runServer,
|
runServer,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
waitForCloudInit,
|
waitForCloudInit,
|
||||||
|
waitForSshOnly,
|
||||||
} from "./aws";
|
} from "./aws";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|
@ -58,7 +59,11 @@ async function main() {
|
||||||
},
|
},
|
||||||
getServerName,
|
getServerName,
|
||||||
async waitForReady() {
|
async waitForReady() {
|
||||||
await waitForCloudInit();
|
if (cloud.skipCloudInit) {
|
||||||
|
await waitForSshOnly();
|
||||||
|
} else {
|
||||||
|
await waitForCloudInit();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
interactiveSession,
|
interactiveSession,
|
||||||
getConnectionInfo,
|
getConnectionInfo,
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ async function main() {
|
||||||
},
|
},
|
||||||
getServerName,
|
getServerName,
|
||||||
async waitForReady() {
|
async waitForReady() {
|
||||||
if (marketplaceImage) {
|
if (marketplaceImage || cloud.skipCloudInit) {
|
||||||
await waitForSshOnly();
|
await waitForSshOnly();
|
||||||
} else {
|
} else {
|
||||||
await waitForCloudInit();
|
await waitForCloudInit();
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
export async function waitForCloudInit(maxAttempts = 120): Promise<void> {
|
||||||
await waitForSsh();
|
await waitForSsh();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import {
|
||||||
runServer,
|
runServer,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
waitForCloudInit,
|
waitForCloudInit,
|
||||||
|
waitForSshOnly,
|
||||||
} from "./gcp";
|
} from "./gcp";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|
@ -64,7 +65,11 @@ async function main() {
|
||||||
},
|
},
|
||||||
getServerName,
|
getServerName,
|
||||||
async waitForReady() {
|
async waitForReady() {
|
||||||
await waitForCloudInit();
|
if (cloud.skipCloudInit) {
|
||||||
|
await waitForSshOnly();
|
||||||
|
} else {
|
||||||
|
await waitForCloudInit();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
interactiveSession,
|
interactiveSession,
|
||||||
getConnectionInfo,
|
getConnectionInfo,
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ async function main() {
|
||||||
},
|
},
|
||||||
getServerName,
|
getServerName,
|
||||||
async waitForReady() {
|
async waitForReady() {
|
||||||
if (snapshotId) {
|
if (snapshotId || cloud.skipCloudInit) {
|
||||||
await waitForSshOnly();
|
await waitForSshOnly();
|
||||||
} else {
|
} else {
|
||||||
await waitForCloudInit();
|
await waitForCloudInit();
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ export interface CloudOrchestrator {
|
||||||
runner: CloudRunner;
|
runner: CloudRunner;
|
||||||
/** When true, skip tarball + agent install (e.g. booting from a pre-baked snapshot). */
|
/** When true, skip tarball + agent install (e.g. booting from a pre-baked snapshot). */
|
||||||
skipAgentInstall?: boolean;
|
skipAgentInstall?: boolean;
|
||||||
|
/** When true, skip cloud-init wait — just wait for SSH (e.g. minimal-tier agent with tarball). */
|
||||||
|
skipCloudInit?: boolean;
|
||||||
authenticate(): Promise<void>;
|
authenticate(): Promise<void>;
|
||||||
checkAccountReady?(): Promise<void>;
|
checkAccountReady?(): Promise<void>;
|
||||||
promptSize(): Promise<void>;
|
promptSize(): Promise<void>;
|
||||||
|
|
@ -121,6 +123,18 @@ export async function runOrchestration(
|
||||||
const fastMode = process.env.SPAWN_FAST === "1" || betaFeatures.has("parallel");
|
const fastMode = process.env.SPAWN_FAST === "1" || betaFeatures.has("parallel");
|
||||||
const useTarball = fastMode || betaFeatures.has("tarball");
|
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)
|
// 1b. Size/bundle selection (must happen before createServer)
|
||||||
await cloud.promptSize();
|
await cloud.promptSize();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue