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

128
src/snapshot.ts Normal file
View File

@@ -0,0 +1,128 @@
import { spawn, type ChildProcess } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { CONFIG } from "./config.js";
import * as api from "./firecracker-api.js";
import {
ensureBridge,
ensureNat,
createTap,
deleteTap,
macFromOctet,
} from "./network.js";
import {
ensureBaseImage,
ensureSshKeypair,
createRunCopy,
injectSshKey,
} from "./rootfs.js";
import { waitForSsh } from "./ssh.js";
import { copyFileSync } from "node:fs";
function log(msg: string) {
process.stderr.write(`[snapshot] ${msg}\n`);
}
function waitForSocket(socketPath: string): Promise<void> {
return new Promise((resolve, reject) => {
const deadline = Date.now() + 5_000;
const check = () => {
if (existsSync(socketPath)) {
setTimeout(resolve, 200);
return;
}
if (Date.now() > deadline) {
reject(new Error("Firecracker socket did not appear"));
return;
}
setTimeout(check, 50);
};
check();
});
}
export function snapshotExists(): boolean {
return (
existsSync(CONFIG.snapshot.statePath) &&
existsSync(CONFIG.snapshot.memPath) &&
existsSync(CONFIG.snapshot.rootfsPath)
);
}
export async function createSnapshot() {
ensureBaseImage();
ensureSshKeypair();
const snap = CONFIG.snapshot;
const socketPath = join(CONFIG.socketDir, "snapshot.sock");
log("Preparing snapshot rootfs...");
mkdirSync(CONFIG.socketDir, { recursive: true });
copyFileSync(CONFIG.baseRootfs, snap.rootfsPath);
injectSshKey(snap.rootfsPath);
log("Setting up network...");
ensureBridge();
ensureNat();
createTap(snap.tapDevice);
let proc: ChildProcess | null = null;
try {
log("Booting VM for snapshot...");
proc = spawn(CONFIG.firecrackerBin, ["--api-sock", socketPath], {
stdio: "pipe",
detached: false,
});
await waitForSocket(socketPath);
const bootArgs = [
"console=ttyS0",
"reboot=k",
"panic=1",
"pci=off",
"root=/dev/vda",
"rw",
`ip=${snap.ip}::${CONFIG.bridge.gateway}:${CONFIG.bridge.netmask}::eth0:off`,
].join(" ");
await api.putBootSource(socketPath, CONFIG.kernelPath, bootArgs);
await api.putDrive(socketPath, "rootfs", snap.rootfsPath);
await api.putNetworkInterface(
socketPath,
"eth0",
snap.tapDevice,
macFromOctet(snap.octet)
);
await api.putMachineConfig(
socketPath,
CONFIG.vm.vcpuCount,
CONFIG.vm.memSizeMib
);
await api.startInstance(socketPath);
log("Waiting for SSH...");
await waitForSsh(snap.ip);
log("Pausing VM...");
await api.patchVm(socketPath, "Paused");
log("Creating snapshot...");
await api.putSnapshotCreate(socketPath, snap.statePath, snap.memPath);
log("Snapshot created successfully.");
log(` State: ${snap.statePath}`);
log(` Memory: ${snap.memPath}`);
log(` Rootfs: ${snap.rootfsPath}`);
} finally {
if (proc && !proc.killed) {
proc.kill("SIGKILL");
}
try {
const { unlinkSync } = await import("node:fs");
unlinkSync(socketPath);
} catch {}
deleteTap(snap.tapDevice);
}
}