-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathai-runlevel.mjs
More file actions
executable file
·190 lines (161 loc) · 6.91 KB
/
ai-runlevel.mjs
File metadata and controls
executable file
·190 lines (161 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env node
/**
* ai-runlevel.mjs
*
* Purpose: Node.js ES module controlling boot services for a WSL1 Debian instance.
* Functionality:
* - Implements a dependency-aware service orchestrator.
* - Manages a dependency graph for agent startup.
* - Performs readiness checks for external services (Ollama).
* - Uses health latches (files) to signal state.
* - Auto-retries startup for offline resilience.
*/
import { spawn } from "node:child_process";
import fs from "fs";
import path from "path";
import http from "node:http";
// --- Configuration & Constants ---
const HOME_DIR = process.env.HOME || "/home/loop";
const RUNLEVEL_DIR = "/home/loop/ai-runlevel";
const LOGS_DIR = path.join(RUNLEVEL_DIR, "logs");
const SOCKETS_DIR = path.join(RUNLEVEL_DIR, "sockets");
// External Service Checkpoints
const OLLAMA_SOCKET = "/run/ollama.sock"; // Standard UNIX socket for Ollama
const OLLAMA_LATCH = path.join(SOCKETS_DIR, "ollama_ready.latch"); // Fallback file latch
const OLLAMA_HOST = process.env.OLLAMA_HOST || '127.0.0.1';
const OLLAMA_PORT = process.env.OLLAMA_PORT || 11434;
// --- Initialization ---
// Ensure critical directories exist before attempting to write logs or latches
[LOGS_DIR, SOCKETS_DIR].forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
// --- Dependency Graph ---
// Defines the startup order. Agents are keys, and their values are lists of dependencies
// that must be 'running' (started) before the agent itself launches.
const DEPENDENCIES = {
"CORE": [], // Foundation agent, no dependencies
"WAVE": ["CORE"], // Relies on CORE
"LOOP": ["CORE"], // Relies on CORE
"COIN": ["CORE"], // Relies on CORE
"SIGN": ["CORE"], // Relies on CORE
"WORK": ["CORE", "LOOP"], // Relies on CORE and LOOP
"CUBE": ["CORE", "WAVE"], // Relies on CORE and WAVE
"CODE": ["CORE", "WORK"], // Relies on CORE and WORK
"LINE": ["CORE", "SIGN"], // Relies on CORE and SIGN
};
const AGENTS = Object.keys(DEPENDENCIES);
const startedAgents = new Set(); // Tracks successfully started agents
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
/**
* Validates existence of Ollama readiness via UNIX socket or latch file.
* Returns true if the service appears ready.
*/
async function checkOllamaReady() {
// Preferred: real HTTP health check
try {
await new Promise((resolve, reject) => {
const req = http.get(`http://${OLLAMA_HOST}:${OLLAMA_PORT}/api/tags`, res => {
res.statusCode === 200 ? resolve() : reject();
});
req.on("error", reject);
req.setTimeout(1000, () => req.destroy());
});
return true;
} catch {
// Fallback latch (offline / emulation mode)
return fs.existsSync(OLLAMA_LATCH);
}
}
/**
* Spawns an agent with automatic restart and dependency awareness.
* @param {string} agentName - The name of the agent to start (e.g., "CORE")
*/
function startAgent(agentName) {
const script = path.join(RUNLEVEL_DIR, `nexus-${agentName.toLowerCase()}.mjs`);
// Continuous loop to ensure the agent runs and restarts on failure
const spawnLoop = async () => {
while (true) {
// 1. Dependency Check
const deps = DEPENDENCIES[agentName];
const allDepsStarted = deps.every(dep => startedAgents.has(dep));
if (!allDepsStarted) {
// Wait and retry if dependencies aren't ready yet
await sleep(1000);
continue;
}
// 2. Script Existence Check
if (!fs.existsSync(script)) {
console.error(`[${agentName}] Script missing: ${script}. Retrying in 5s...`);
await sleep(5000);
continue;
}
// 3. Spawn Process
console.log(`[${agentName}] Starting...`);
const proc = spawn("node", [script], {
stdio: ["ignore", "pipe", "pipe"], // Ignore stdin, pipe stdout/stderr
env: { ...process.env, AGENT_NAME: agentName, RUNLEVEL_DIR }
});
// 4. Log Management
// Create a specific log file for this agent
const logFile = path.join(LOGS_DIR, `${agentName}.log`);
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
// Pipe output to the log file
proc.stdout.pipe(logStream);
proc.stderr.pipe(logStream);
// Also echo to the main runlevel stdout for immediate visibility
proc.stdout.on("data", d => process.stdout.write(`[${agentName}] ${d}`));
proc.stderr.on("data", d => process.stderr.write(`[${agentName}-ERR] ${d}`));
// Mark agent as started for dependency resolution of others
startedAgents.add(agentName);
// 5. Exit Handling
await new Promise(resolve => proc.on("exit", code => {
console.log(`[${agentName}] Exited with code ${code}. Restarting in 2s...`);
// Note: We remove it from startedAgents if we want strict dependency health,
// but for loose coupling, we might keep it. Here we remove it to be safe.
startedAgents.delete(agentName);
setTimeout(resolve, 2000);
}));
}
};
spawnLoop();
}
/**
* Main boot sequence
*/
async function main() {
console.log("[ai-runlevel] Initializing systemd-like environment for WSL1...");
// --- Phase 1: Pre-flight Checks (Ollama) ---
console.log("[ai-runlevel] Waiting for Ollama signal...");
let retries = 0;
while (!(await checkOllamaReady())) {
if (retries % 10 === 0) {
console.log("[ai-runlevel] Ollama not ready. Checking /run/ollama.sock or " + OLLAMA_LATCH);
}
// Auto-retry loop for offline/not-yet-ready scenarios
await sleep(2000);
retries++;
// Timeout handling: Force proceed after ~30 seconds if strictly offline
if (retries > 15) {
console.log("[ai-runlevel] Ollama timeout. Creating placeholder latch to proceed...");
fs.writeFileSync(OLLAMA_LATCH, "LATCH_BYPASS");
}
}
console.log("[ai-runlevel] Ollama signal detected.");
// --- Phase 2: Agent Orchestration ---
// Iterate through all agents; the startAgent function handles dependency waiting
AGENTS.forEach(agent => {
startAgent(agent);
});
console.log("[ai-runlevel] Orchestrator running. Managing " + AGENTS.length + " agents.");
// --- Phase 3: System Health ---
// Signal that the runlevel itself is fully operational
fs.writeFileSync(path.join(SOCKETS_DIR, "runlevel.latch"), "RUNNING");
// Prevent the Node.js process from exiting
process.stdin.resume();
}
main().catch(err => {
console.error("[ai-runlevel] FATAL ERROR:", err);
process.exit(1);
});