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:
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user