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

134
src/cli.ts Normal file
View File

@@ -0,0 +1,134 @@
import { Command } from "commander";
import { VMInstance } from "./vm.js";
import { installSignalHandlers } from "./cleanup.js";
import { runSetup } from "./setup.js";
import { createSnapshot } from "./snapshot.js";
import { runOverseer } from "./overseer.js";
import {
startAgent,
stopAgent,
listAgents,
} from "./agent-manager.js";
export function createCli() {
const program = new Command();
program
.name("fireclaw")
.description("Run commands in ephemeral Firecracker microVMs")
.version("0.1.0");
program
.command("run")
.description("Run a command inside a fresh microVM")
.argument("<command>", "Command to execute inside the microVM")
.option("-t, --timeout <seconds>", "Timeout in seconds", "60")
.option("-v, --verbose", "Show detailed progress", false)
.option("--mem <mib>", "Memory in MiB", "256")
.option("--vcpu <count>", "Number of vCPUs", "1")
.option("--no-snapshot", "Force cold boot, skip snapshot restore")
.action(async (command: string, opts) => {
installSignalHandlers();
const result = await VMInstance.run(command, {
timeout: parseInt(opts.timeout) * 1000,
verbose: opts.verbose,
mem: parseInt(opts.mem),
vcpu: parseInt(opts.vcpu),
noSnapshot: opts.snapshot === false,
});
if (!opts.verbose) {
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
}
process.exit(result.exitCode);
});
program
.command("setup")
.description("Download kernel, rootfs, and configure networking")
.action(async () => {
await runSetup();
});
const snapshot = program
.command("snapshot")
.description("Manage VM snapshots");
snapshot
.command("create")
.description("Boot a VM and create a snapshot for fast restores")
.action(async () => {
installSignalHandlers();
await createSnapshot();
});
// Overseer
program
.command("overseer")
.description("Start the overseer daemon (IRC bot for agent management)")
.option("--server <host>", "IRC server", "localhost")
.option("--port <port>", "IRC port", "6667")
.option("--nick <nick>", "Bot nickname", "overseer")
.option("--channel <chan>", "Control channel", "#control")
.action(async (opts) => {
await runOverseer({
server: opts.server,
port: parseInt(opts.port),
nick: opts.nick,
channel: opts.channel,
});
});
// Agent management
const agent = program
.command("agent")
.description("Manage long-running agent VMs");
agent
.command("start")
.description("Start an agent VM from a template")
.argument("<template>", "Template name")
.option("--name <name>", "Override agent name")
.option("--model <model>", "Override LLM model")
.action(async (template: string, opts) => {
installSignalHandlers();
const info = await startAgent(template, {
name: opts.name,
model: opts.model,
});
console.log(
`Agent "${info.name}" started: ${info.nick} [${info.model}] (${info.ip})`
);
process.exit(0);
});
agent
.command("stop")
.description("Stop a running agent VM")
.argument("<name>", "Agent name")
.action(async (name: string) => {
await stopAgent(name);
console.log(`Agent "${name}" stopped.`);
});
agent
.command("list")
.description("List running agent VMs")
.action(() => {
const agents = listAgents();
if (agents.length === 0) {
console.log("No agents running.");
return;
}
for (const a of agents) {
console.log(
`${a.name} (${a.template}) — ${a.nick} [${a.model}] ip=${a.ip} since ${a.startedAt}`
);
}
});
return program;
}