- Create patterns/ directory with README, manifest, and 10 initial patterns covering Ollama JSON fallback, API escaping, deprecation, PTY auth, queue-poll, LLM-as-parser, credential rotation, reverse proxy binding, human approval gates, and transient retry. - Wire pattern loading into architecture/pipeline.js based on task tags. - Update architecture/orchestrator.js to load patterns and surface them in the system prompt. - Update MEMORY.md, ARCHITECTURE.md, and CONTEXT.md to document the registry and record the decision.
241 lines
6.1 KiB
JavaScript
241 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Context Pipeline - Preprocessing Layer
|
|
*
|
|
* Every request flows through:
|
|
* 1. Load Rules → 2. Load Preferences → 3. Load Memory → 4. Build Packet
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
class ContextPipeline {
|
|
constructor() {
|
|
this.rules = [];
|
|
this.preferences = [];
|
|
this.memory = [];
|
|
this.patterns = [];
|
|
this.context = {};
|
|
}
|
|
|
|
/**
|
|
* Step 3b: Load Relevant Cross-Project Patterns
|
|
*/
|
|
async loadPatterns(taskInput) {
|
|
const manifestPath = path.join('/home/jcbeasley/.openclaw/workspace', 'patterns', 'patterns.json');
|
|
try {
|
|
const manifestRaw = fs.readFileSync(manifestPath, 'utf8');
|
|
const manifest = JSON.parse(manifestRaw);
|
|
const query = (taskInput || '').toLowerCase();
|
|
|
|
for (const pattern of manifest.patterns || []) {
|
|
const tokens = [
|
|
...(pattern.tags || []),
|
|
...(pattern.affected_projects || []),
|
|
pattern.id,
|
|
pattern.name
|
|
].map(t => t.toLowerCase());
|
|
|
|
const matched = tokens.some(token => query.includes(token));
|
|
if (!matched) continue;
|
|
|
|
const patternFile = path.join('/home/jcbeasley/.openclaw/workspace', pattern.file);
|
|
try {
|
|
const content = fs.readFileSync(patternFile, 'utf8');
|
|
this.patterns.push({
|
|
source: pattern.file,
|
|
content: content,
|
|
priority: 6,
|
|
id: pattern.id
|
|
});
|
|
} catch (err) {
|
|
// Pattern file missing; skip
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// Manifest missing or invalid; skip pattern loading
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Step 1: Load Behavior Rules (always injected)
|
|
*/
|
|
async loadRules() {
|
|
const ruleFiles = [
|
|
'SOUL.md',
|
|
'AGENTS.md',
|
|
'IDENTITY.md',
|
|
'MEMORY.md'
|
|
];
|
|
|
|
for (const file of ruleFiles) {
|
|
const content = await this.readFile(file);
|
|
if (content) {
|
|
this.rules.push({
|
|
source: file,
|
|
content: content,
|
|
priority: 100 // Absolute priority
|
|
});
|
|
}
|
|
}
|
|
|
|
// Load active workflow rules if specified
|
|
const workflow = process.env.ACTIVE_WORKFLOW;
|
|
if (workflow) {
|
|
const workflowRules = await this.readFile(`workflows/${workflow}.md`);
|
|
if (workflowRules) {
|
|
this.rules.push({
|
|
source: `workflows/${workflow}.md`,
|
|
content: workflowRules,
|
|
priority: 95
|
|
});
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Step 2: Load Persistent Preferences (from structured memory)
|
|
*/
|
|
async loadPreferences() {
|
|
const prefFiles = [
|
|
'memory/items/mem_pref:communication_style.json',
|
|
'memory/items/mem_pref:format.json',
|
|
'memory/items/mem_pref:memory_persistence.json',
|
|
'memory/items/mem_identity:name.json',
|
|
'memory/items/mem_identity:role.json'
|
|
];
|
|
|
|
for (const file of prefFiles) {
|
|
const data = await this.readJson(file);
|
|
if (data) {
|
|
this.preferences.push({
|
|
source: file,
|
|
content: data.content,
|
|
priority: data.importance || 7,
|
|
tags: data.tags || []
|
|
});
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Step 3a: Load Relevant Memory
|
|
*/
|
|
async loadRelevantMemory(query, maxResults = 5) {
|
|
// For now, load recent episodic memories
|
|
// Future: implement vector search
|
|
|
|
const memoryFiles = [
|
|
'memory/2026-07-04.md',
|
|
'memory/2026-07-03.md'
|
|
];
|
|
|
|
for (const file of memoryFiles) {
|
|
const content = await this.readFile(file);
|
|
if (content) {
|
|
this.memory.push({
|
|
source: file,
|
|
content: content.substring(0, 2000), // Truncate
|
|
priority: 5
|
|
});
|
|
}
|
|
}
|
|
|
|
// Load project memory if PROJECT context set
|
|
const project = process.env.CURRENT_PROJECT;
|
|
if (project) {
|
|
const projectStatus = await this.readFile(`Projects/${project}/memory/STATUS.md`);
|
|
if (projectStatus) {
|
|
this.memory.push({
|
|
source: `Projects/${project}/memory/STATUS.md`,
|
|
content: projectStatus,
|
|
priority: 8
|
|
});
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Step 4: Build Context Packet
|
|
*/
|
|
buildPacket(taskInput) {
|
|
// Sort by priority (highest first)
|
|
const allContext = [
|
|
...this.rules,
|
|
...this.preferences.sort((a, b) => b.priority - a.priority),
|
|
...this.memory,
|
|
...this.patterns
|
|
];
|
|
|
|
const packet = {
|
|
system: allContext.filter(c => c.priority >= 90).map(c => c.content).join('\n\n---\n\n'),
|
|
preferences: allContext.filter(c => c.priority >= 5 && c.priority < 90).map(c => c.content).join('\n\n'),
|
|
memory: allContext.filter(c => c.priority < 5).map(c => c.content).join('\n\n'),
|
|
task: taskInput,
|
|
metadata: {
|
|
rules_count: this.rules.length,
|
|
prefs_count: this.preferences.length,
|
|
memory_count: this.memory.length,
|
|
patterns_count: this.patterns.length
|
|
}
|
|
};
|
|
|
|
return packet;
|
|
}
|
|
|
|
/**
|
|
* Utility: Read file from workspace
|
|
*/
|
|
async readFile(filepath) {
|
|
const fullPath = path.join('/home/jcbeasley/.openclaw/workspace', filepath);
|
|
try {
|
|
return fs.readFileSync(fullPath, 'utf8');
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility: Read JSON file
|
|
*/
|
|
async readJson(filepath) {
|
|
const fullPath = path.join('/home/jcbeasley/.openclaw/workspace', filepath);
|
|
try {
|
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
return JSON.parse(content);
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export for use
|
|
module.exports = ContextPipeline;
|
|
|
|
// CLI usage
|
|
if (require.main === module) {
|
|
const pipeline = new ContextPipeline();
|
|
|
|
pipeline
|
|
.loadRules()
|
|
.then(() => pipeline.loadPreferences())
|
|
.then(() => pipeline.loadRelevantMemory(process.argv[2] || ''))
|
|
.then(() => pipeline.loadPatterns(process.argv[2] || ''))
|
|
.then(() => {
|
|
const packet = pipeline.buildPacket(process.argv[2] || 'No task specified');
|
|
console.log(JSON.stringify(packet, null, 2));
|
|
})
|
|
.catch(err => {
|
|
console.error('Pipeline error:', err);
|
|
process.exit(1);
|
|
});
|
|
}
|