Implement self-correcting memory architecture with critic engine

Adds:
- Critic Engine (critic.js): Validates output against correction store
- Auto-detection: Identifies uncorrected patterns (quantitative without proof, vague quantification, etc.)
- Auto-storage: New patterns immediately stored as corrections
- NocoDB Schema: Tables for corrections, preferences, episodes, decisions, validation
- Memory Service (memory-service.js): NocoDB integration layer
- Response Generator (response-generator.js): End-to-end pipeline with critic
- Correction Store: JSON-based with README documentation

Behavior:
- ALL output validated before delivery
- Quantitative claims without evidence → auto-corrected
- Corrections immediately block future occurrences
- System learns from its own mistakes

Test: node architecture/test-critic.js
This commit is contained in:
JC Beasley
2026-07-04 16:23:05 -07:00
parent 076e9eb605
commit cf3f1e1c53
9 changed files with 1159 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env node
/**
* Response Generator with Self-Correcting Memory
*
* Architecture:
* 1. Load context (rules + prefs + memory)
* 2. Generate draft
* 3. Critic validates
* 4. If violations → regenerate with corrections
* 5. If new patterns → auto-store as corrections
* 6. Return validated response
*/
const ContextPipeline = require('./pipeline');
const CriticEngine = require('./critic');
const MemoryService = require('./memory-service');
class ResponseGenerator {
constructor(config = {}) {
this.pipeline = new ContextPipeline();
this.critic = new CriticEngine({
strictMode: config.strictMode || false,
autoLearn: config.autoLearn !== false
});
this.memory = config.memoryService || null;
this.workflow = config.workflow || 'default';
this.maxRetries = config.maxRetries || 3;
}
/**
* Generate validated response
*/
async generate(taskInput, options = {}) {
const startTime = Date.now();
// Step 1: Load context
await this.pipeline
.loadRules()
.then(() => this.pipeline.loadPreferences())
.then(() => this.pipeline.loadRelevantMemory(taskInput));
const contextPacket = this.pipeline.buildPacket(taskInput);
// Step 2-4: Generate with validation loop
let attempts = 0;
let lastViolations = [];
let response = null;
let criticResult = null;
while (attempts < this.maxRetries) {
attempts++;
// Generate draft (in real implementation, this calls LLM)
response = await this.callLLM(contextPacket, lastViolations);
// Validate
criticResult = await this.critic.critique(response, {
task: taskInput,
workflow: this.workflow,
attempt: attempts
});
if (criticResult.passed) {
break; // Success!
}
// Blocked - need to regenerate
lastViolations = criticResult.violations;
console.log(`[GENERATOR] Attempt ${attempts} blocked: ${criticResult.violations.map(v => v.message).join(', ')}`);
}
const processingTime = Date.now() - startTime;
// Step 5: Log validation run
if (this.memory) {
await this.memory.logValidation({
id: `run_${Date.now()}`,
sessionId: options.sessionId || 'unknown',
inputLength: taskInput.length,
outputLength: response.length,
violationsFound: criticResult.violations.length,
newPatternsDetected: criticResult.newPatterns.length,
correctionsAutoStored: criticResult.violations.filter(v => v.autoStored).length,
processingTimeMs: processingTime,
blocked: attempts > 1,
workflow: this.workflow
});
}
return {
response: criticResult.corrected,
original: criticResult.original,
attempts,
passed: criticResult.passed,
violations: criticResult.violations,
newPatterns: criticResult.newPatterns,
processingTimeMs: processingTime,
context: {
rulesCount: contextPacket.metadata.rules_count,
prefsCount: contextPacket.metadata.prefs_count,
memoryCount: contextPacket.metadata.memory_count
}
};
}
/**
* Call LLM with context (placeholder - integrate with actual LLM)
*/
async callLLM(contextPacket, previousViolations = []) {
// In production, this calls OpenClaw/LLM
// For now, return a mock response
let prompt = this.buildPrompt(contextPacket, previousViolations);
// Simulate LLM call
// const response = await llm.generate(prompt);
// For demo: return context to show it works
return `## Task
${contextPacket.task}
## System Rules
${contextPacket.system.substring(0, 500)}...
## Response
This is 50% faster than before.`; // Intentional violation for testing
}
/**
* Build prompt with corrections injected
*/
buildPrompt(contextPacket, previousViolations) {
const parts = [];
// System rules (highest priority)
parts.push('# SYSTEM RULES\n' + contextPacket.system);
// Corrections from previous attempts
if (previousViolations.length > 0) {
parts.push('\n# CORRECTIONS REQUIRED\n');
for (const violation of previousViolations) {
parts.push(`- ${violation.message}`);
if (violation.suggestion) {
parts.push(` Fix: ${violation.suggestion}`);
}
}
}
// Preferences
if (contextPacket.preferences) {
parts.push('\n# USER PREFERENCES\n' + contextPacket.preferences);
}
// Memory
if (contextPacket.memory) {
parts.push('\n# RELEVANT MEMORY\n' + contextPacket.memory);
}
// Task
parts.push('\n# TASK\n' + contextPacket.task);
parts.push('\nRespond following all system rules and corrections above.');
return parts.join('\n');
}
/**
* Manual correction entry (user feedback)
*/
async addUserCorrection(originalText, correctionText, reason) {
const correction = await this.critic.addManualCorrection(
originalText,
correctionText,
reason
);
if (this.memory) {
await this.memory.storeCorrection(correction);
}
return correction;
}
}
// Export
module.exports = ResponseGenerator;
// CLI usage
if (require.main === module) {
const generator = new ResponseGenerator({
workflow: process.argv[2] || 'coding',
strictMode: true,
autoLearn: true
});
const task = process.argv[3] || 'Write a function to process data';
generator.generate(task)
.then(result => {
console.log('=== GENERATION RESULT ===');
console.log(JSON.stringify(result, null, 2));
})
.catch(err => {
console.error('Generation failed:', err);
process.exit(1);
});
}