Initial commit — fireclaw multi-agent system

Firecracker microVM-based multi-agent system with IRC orchestration and local LLMs.

Features:
- Ephemeral command runner with VM snapshots (~1.1s)
- Multi-agent orchestration via overseer IRC bot
- 5 agent templates (worker, coder, researcher, quick, creative)
- Tool access (shell + podman containers inside VMs)
- Persistent workspace + memory system (MEMORY.md pattern)
- Agent hot-reload (model/persona swap via SSH + SIGHUP)
- Non-root agents, graceful shutdown, crash recovery
- Agent-to-agent communication via IRC
- DM support, /invite support
- Systemd service, 20 regression tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 13:28:29 +00:00
commit ff694d12f6
28 changed files with 5917 additions and 0 deletions

117
src/setup.ts Normal file
View File

@@ -0,0 +1,117 @@
import { execFileSync, execSync } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { CONFIG } from "./config.js";
import { ensureBridge, ensureNat } from "./network.js";
import { ensureSshKeypair } from "./rootfs.js";
function log(msg: string) {
process.stderr.write(`[setup] ${msg}\n`);
}
function download(url: string, dest: string) {
execFileSync("curl", ["-fSL", "-o", dest, url], {
stdio: ["pipe", "pipe", "inherit"],
timeout: 300_000,
});
}
export async function runSetup() {
log("Setting up fireclaw...");
// Create directories
mkdirSync(CONFIG.baseDir, { recursive: true });
mkdirSync(CONFIG.runsDir, { recursive: true });
mkdirSync(CONFIG.socketDir, { recursive: true });
// Download kernel
if (existsSync(CONFIG.kernelPath)) {
log("Kernel already exists, skipping download.");
} else {
log("Downloading kernel...");
download(CONFIG.assets.kernelUrl, CONFIG.kernelPath);
log("Kernel downloaded.");
}
// Download and convert rootfs
if (existsSync(CONFIG.baseRootfs)) {
log("Base rootfs already exists, skipping download.");
} else {
log("Downloading rootfs...");
// Find latest rootfs key from S3 listing
const listing = execFileSync(
"curl",
["-fsSL", CONFIG.assets.rootfsListUrl],
{ encoding: "utf-8", timeout: 30_000 }
);
const keys = [...listing.matchAll(/<Key>([^<]+)<\/Key>/g)].map(
(m) => m[1]
);
const rootfsKey = keys.sort().pop();
if (!rootfsKey) throw new Error("Could not find rootfs in S3 listing");
const squashfsPath = `${CONFIG.baseDir}/rootfs.squashfs`;
download(`${CONFIG.assets.rootfsBaseUrl}/${rootfsKey}`, squashfsPath);
log("Rootfs downloaded. Converting squashfs to ext4...");
// Convert squashfs to ext4
const squashMount = "/tmp/fireclaw-squash";
const ext4Mount = "/tmp/fireclaw-ext4";
mkdirSync(squashMount, { recursive: true });
mkdirSync(ext4Mount, { recursive: true });
try {
execFileSync(
"sudo",
["mount", "-t", "squashfs", squashfsPath, squashMount],
{ stdio: "pipe" }
);
execFileSync("truncate", ["-s", "1G", CONFIG.baseRootfs], {
stdio: "pipe",
});
execFileSync("sudo", ["/usr/sbin/mkfs.ext4", CONFIG.baseRootfs], {
stdio: "pipe",
});
execFileSync("sudo", ["mount", CONFIG.baseRootfs, ext4Mount], {
stdio: "pipe",
});
execFileSync("sudo", ["cp", "-a", `${squashMount}/.`, ext4Mount], {
stdio: "pipe",
});
// Bake in DNS config
execSync(
`echo "nameserver 8.8.8.8" | sudo tee ${ext4Mount}/etc/resolv.conf > /dev/null`
);
log("Rootfs converted.");
} finally {
try {
execFileSync("sudo", ["umount", squashMount], { stdio: "pipe" });
} catch {}
try {
execFileSync("sudo", ["umount", ext4Mount], { stdio: "pipe" });
} catch {}
try {
execFileSync("rmdir", [squashMount], { stdio: "pipe" });
} catch {}
try {
execFileSync("rmdir", [ext4Mount], { stdio: "pipe" });
} catch {}
try {
execFileSync("rm", ["-f", squashfsPath], { stdio: "pipe" });
} catch {}
}
}
// Generate SSH keypair
log("Ensuring SSH keypair...");
ensureSshKeypair();
// Set up bridge and NAT
log("Setting up network bridge...");
ensureBridge();
ensureNat();
log("Setup complete! Run 'fireclaw run \"uname -a\"' to test.");
}