Autonomous Code Mechanic: Two-Layer Self-Healing Node.js Pipeline with LLM-Assisted Repair
name: autonomous-code-mechanic version: 2.0.0 supersedes: "2603.00339" authors:
- ai@aiindigo.com
- contact@aiindigo.com source: ~/services/simulation/engine/code-maintainer.js description: Two-layer autonomous maintenance system. Layer 1 actively probes 11 pipeline components every cycle. Layer 2 reads error logs, generates fixes with a local coding LLM (Qwen3.5-Coder 35B), validates via node --check, and auto-reverts on syntax failure. allowed-tools: Bash(node *), Bash(curl *)
Supersedes: 2603.00339 — This version replaces the Claw4S conference submission with verified source code, real production metrics, and corrected claims. Contact: ai@aiindigo.com · contact@aiindigo.com
Autonomous Code Mechanic
Runs as a scheduled job in the AI Indigo simulation (Mac Studio M4 Max, 128 GB). Every 10 minutes it probes 11 system components and scans JS files for bugs. When bugs are found, it calls Ollama with mdq100/qwen3.5-coder:35b at temperature 0.1, writes the fix, runs /opt/homebrew/bin/node --check, and reverts if syntax fails.
What this does NOT claim
- There is no "30-day production history" with verified fix counts yet — the simulation was running since March 26 (2 days old as of submission)
- The mechanic state file shows
totalCycles: 0, totalFixes: 0at this snapshot — the system is operational but cycles are accumulating - The fix success rate in the paper (85.2%) is a projection based on the algorithm design, not a measured result
What is real
- The code is production-deployed and running 24/7 on Mac Studio
- The PROTECTED_FILES list is enforced:
core.js,simulation.js,work-queue.js,periodic-scheduler.js— never touched - The backup + revert pattern is implemented and tested manually
think: falseis required formdq100/qwen3.5-coder:35b— omitting it returns empty- Real bugs it has been designed to catch:
readCorrectionFile is not defined(stale PM2 cache), barenodepaths on macOS (needs/opt/homebrew/bin/node), bash 3.2 incompatibilities
Prerequisites
- Node.js 18+ at
/opt/homebrew/bin/node(macOS) or/usr/bin/node(Linux) - Ollama running with
mdq100/qwen3.5-coder:35bor any coding model - A directory of JavaScript files to maintain
Step 1: Test your Ollama coding model
curl -s http://localhost:11434/api/generate \
-H "Content-Type: application/json" \
-d '{
"model": "mdq100/qwen3.5-coder:35b",
"think": false,
"prompt": "Return only: console.log(\"hello\")",
"stream": false,
"options": {"temperature": 0.1, "num_predict": 50}
}' | python3 -c "import sys,json; print(json.load(sys.stdin).get('response','EMPTY'))"Expected: console.log("hello") — if empty, the model requires think: false.
Step 2: Create a test file with a real syntax error
mkdir -p /tmp/mechanic-demo/src /tmp/mechanic-demo/backups
cat > /tmp/mechanic-demo/src/worker.js << 'WORKEREOF'
const fs = require('fs');
function processData(input) {
// BUG: extra closing paren
const result = [1,2,3].map(x => x * 2));
return result;
}
module.exports = { processData };
WORKEREOF
/opt/homebrew/bin/node --check /tmp/mechanic-demo/src/worker.js 2>&1 || echo "Syntax error confirmed"Expected: SyntaxError on the )) line
Step 3: Scan for bugs (exact pattern from code-maintainer.js)
node << 'SCAN'
const fs = require('fs');
const { execSync } = require('child_process');
const SRC_DIR = '/tmp/mechanic-demo/src';
// These match PROTECTED_FILES in production
const PROTECTED = new Set(['core.js', 'simulation.js', 'work-queue.js', 'periodic-scheduler.js']);
const bugs = [];
for (const file of fs.readdirSync(SRC_DIR).filter(f => f.endsWith('.js'))) {
if (PROTECTED.has(file)) {
console.log(`SKIP (protected): ${file}`);
continue;
}
const filePath = SRC_DIR + '/' + file;
try {
// Production uses /opt/homebrew/bin/node on macOS
execSync(`/opt/homebrew/bin/node --check "${filePath}"`, { encoding: 'utf8', timeout: 10000 });
console.log(`OK: ${file}`);
} catch (e) {
const error = (e.stderr || e.stdout || e.message || '').substring(0, 300);
bugs.push({ filePath, fileName: file, error, type: 'syntax', priority: 100 });
console.log(`BUG: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>f</mi><mi>i</mi><mi>l</mi><mi>e</mi></mrow><mtext>—</mtext></mrow><annotation encoding="application/x-tex">{file} —</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">e</span></span><span class="mord">—</span></span></span></span>{error.split('\n')[0]}`);
}
}
fs.writeFileSync('/tmp/mechanic-demo/bugs.json', JSON.stringify(bugs, null, 2));
console.log(`\nFound ${bugs.length} syntax bugs`);
SCANStep 4: Generate fix via local LLM and apply with revert safety
node << 'FIX'
const fs = require('fs');
const http = require('http');
const { execSync } = require('child_process');
const BACKUP_DIR = '/tmp/mechanic-demo/backups';
const CODER_MODEL = 'mdq100/qwen3.5-coder:35b'; // exact model from production
const FIX_TIMEOUT_MS = 2 * 60 * 1000; // 2 min — same as production
const MAX_FIXES = 3; // same as production MAX_FIXES_PER_RUN
function callCoder(prompt) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({
model: CODER_MODEL,
think: false, // REQUIRED — model is a thinking variant, returns empty without this
prompt,
stream: false,
options: { temperature: 0.1, num_predict: 2048 },
});
const req = http.request(
{ hostname: 'localhost', port: 11434, path: '/api/generate', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
timeout: FIX_TIMEOUT_MS },
(res) => {
let body = '';
res.on('data', c => body += c);
res.on('end', () => {
try { resolve(JSON.parse(body).response || ''); }
catch (e) { reject(new Error('parse failed')); }
});
}
);
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('coder timeout')); });
req.write(payload);
req.end();
});
}
const bugs = JSON.parse(fs.readFileSync('/tmp/mechanic-demo/bugs.json', 'utf8'));
(async () => {
for (const bug of bugs.slice(0, MAX_FIXES)) {
console.log(`\nFixing: ${bug.fileName}`);
const original = fs.readFileSync(bug.filePath, 'utf8');
// Backup before touching anything (same pattern as production)
const backupPath = BACKUP_DIR + '/' + bug.fileName + '.' + Date.now() + '.bak';
fs.writeFileSync(backupPath, original);
console.log(` Backed up to ${backupPath}`);
// Prompt matches production format
const prompt = `You are a senior Node.js engineer fixing a production bug.
FILE: ${bug.fileName}
ERROR: ${bug.error.substring(0, 500)}
CODE: ${original.substring(0, 6000)}
Fix the bug. Return ONLY the complete fixed file content.
No explanation. No markdown. No preamble. Just raw JavaScript.
Do not add dependencies. Do not change module.exports.`;
let fixed;
try {
fixed = await callCoder(prompt);
} catch (e) {
console.log(` Coder failed: ${e.message}`);
continue;
}
// Strip accidental markdown fences (same as production)
fixed = fixed.replace(/^```[a-z]*\n?/m, '').replace(/\n?```$/m, '').trim();
if (!fixed || fixed.length < 50) {
console.log(` Empty fix response — skipping`);
continue;
}
// Write and validate
fs.writeFileSync(bug.filePath, fixed);
try {
execSync(`/opt/homebrew/bin/node --check "${bug.filePath}"`, { encoding: 'utf8', timeout: 10000 });
console.log(` APPLIED — syntax check passed`);
} catch (checkErr) {
// Revert immediately (same as production)
fs.writeFileSync(bug.filePath, original);
console.log(` REVERTED — syntax check failed after fix`);
}
}
})();
FIXStep 5: Verify
echo "=== Post-fix syntax ==="
for f in /tmp/mechanic-demo/src/*.js; do
/opt/homebrew/bin/node --check "<span class="katex-error" title="ParseError: KaTeX parse error: Expected 'EOF', got '&' at position 6: f" 2>&̲1 && echo "OK:" style="color:#cc0000">f" 2>&1 && echo "OK:</span>(basename <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">)</mo><mi mathvariant="normal">"</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>e</mi><mi>c</mi><mi>h</mi><mi>o</mi><mi mathvariant="normal">"</mi><mi>B</mi><mi>R</mi><mi>O</mi><mi>K</mi><mi>E</mi><mi>N</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">f)" || echo "BROKEN:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mclose">)</span><span class="mord">"∣∣</span><span class="mord mathnormal">ec</span><span class="mord mathnormal">h</span><span class="mord mathnormal">o</span><span class="mord">"</span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mord mathnormal" style="margin-right:0.0278em;">O</span><span class="mord mathnormal" style="margin-right:0.0715em;">K</span><span class="mord mathnormal" style="margin-right:0.0576em;">E</span><span class="mord mathnormal" style="margin-right:0.109em;">N</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>(basename $f)"
done
echo ""
echo "=== Backups ==="
ls -la /tmp/mechanic-demo/backups/Production constants (from code-maintainer.js)
| Constant | Value | Purpose |
|---|---|---|
CODER_MODEL |
mdq100/qwen3.5-coder:35b |
Local Ollama model |
MAX_FIXES_PER_RUN |
3 |
Cap per mechanic cycle |
COOLDOWN_MS |
10 * 60 * 1000 |
10 min between full runs |
FILE_COOLDOWN_MS |
6 * 60 * 60 * 1000 |
6 hours per file |
FIX_TIMEOUT_MS |
2 * 60 * 1000 |
2 min per LLM call |
MAX_LOG_ENTRIES |
200 |
Rolling mechanic log cap |
think: false |
required | Thinking model — empty without this |
Layer 1: Pipeline Health Probes
The mechanic also runs 11 probes every cycle (before bug scanning):
// From code-maintainer.js lines 293-316
const [ollama, neon, enricher, content, github] = await Promise.all([
probeOllama(), // GET /api/tags — model count
probeNeon(), // SELECT COUNT(*) FROM tools_db
probeEnricherPipeline(), // file age check on enrichment output
probeContentPipeline(), // content queue drain rate
probeGithub(), // /repos endpoint rate limit check
]);
// + trend_scanner, similarity, pm2, disk (synchronous)Current production health: 9/11 probes healthy (2 degraded at time of writing).
Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.