rlhf-feedback-loop 0.6.10 → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +120 -74
- package/adapters/README.md +3 -3
- package/adapters/amp/skills/rlhf-feedback/SKILL.md +2 -0
- package/adapters/chatgpt/INSTALL.md +6 -3
- package/adapters/chatgpt/openapi.yaml +5 -2
- package/adapters/claude/.mcp.json +3 -3
- package/adapters/codex/config.toml +3 -3
- package/adapters/gemini/function-declarations.json +2 -2
- package/adapters/mcp/server-stdio.js +19 -5
- package/bin/cli.js +295 -25
- package/openapi/openapi.yaml +5 -2
- package/package.json +25 -9
- package/scripts/a2ui-engine.js +73 -0
- package/scripts/adk-consolidator.js +267 -0
- package/scripts/billing.js +192 -681
- package/scripts/code-reasoning.js +26 -1
- package/scripts/context-engine.js +86 -4
- package/scripts/contextfs.js +130 -0
- package/scripts/disagreement-mining.js +315 -0
- package/scripts/export-kto-pairs.js +310 -0
- package/scripts/feedback-ingest-watcher.js +290 -0
- package/scripts/feedback-loop.js +153 -8
- package/scripts/feedback-quality.js +139 -0
- package/scripts/feedback-schema.js +31 -5
- package/scripts/feedback-to-memory.js +13 -1
- package/scripts/hook-auto-capture.sh +6 -0
- package/scripts/hook-stop-self-score.sh +51 -0
- package/scripts/install-mcp.js +168 -0
- package/scripts/intent-router.js +88 -0
- package/scripts/jsonl-watcher.js +151 -0
- package/scripts/local-model-profile.js +207 -0
- package/scripts/pr-manager.js +112 -0
- package/scripts/prove-adapters.js +137 -15
- package/scripts/prove-attribution.js +6 -6
- package/scripts/prove-automation.js +41 -8
- package/scripts/prove-data-quality.js +16 -8
- package/scripts/prove-intelligence.js +7 -4
- package/scripts/prove-lancedb.js +7 -7
- package/scripts/prove-local-intelligence.js +244 -0
- package/scripts/prove-loop-closure.js +16 -8
- package/scripts/prove-training-export.js +7 -4
- package/scripts/prove-workflow-contract.js +116 -0
- package/scripts/reminder-engine.js +132 -0
- package/scripts/risk-scorer.js +458 -0
- package/scripts/rlaif-self-audit.js +7 -1
- package/scripts/self-heal.js +24 -4
- package/scripts/status-dashboard.js +155 -0
- package/scripts/sync-version.js +159 -0
- package/scripts/test-coverage.js +76 -0
- package/scripts/validate-workflow-contract.js +287 -0
- package/scripts/vector-store.js +115 -17
- package/src/api/server.js +372 -25
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent Development Kit (ADK) Memory Consolidator
|
|
4
|
+
*
|
|
5
|
+
* 'Always-On' background service that reads disparate feedback logs and uses
|
|
6
|
+
* Gemini (Flash-Lite/Flash) to actively consolidate, compress, and dream up
|
|
7
|
+
* generalized prevention rules. This moves the system from 'passive logging'
|
|
8
|
+
* to 'active semantic memory consolidation'.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { GoogleGenAI } = require('@google/genai');
|
|
16
|
+
|
|
17
|
+
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
18
|
+
const { getFeedbackPaths, readJSONL } = require('./feedback-loop');
|
|
19
|
+
const { compactContext } = require('./context-engine');
|
|
20
|
+
const { resolveModelRole } = require('./local-model-profile');
|
|
21
|
+
const { trackEvent, shouldInjectReminder, injectReminder } = require('./reminder-engine');
|
|
22
|
+
const { validateApiKey, reportUsageToStripe } = require('./billing');
|
|
23
|
+
|
|
24
|
+
// Keep track of the last processed ID to avoid re-consolidating the exact same logs
|
|
25
|
+
const STATE_FILE = process.env.ADK_STATE_FILE || path.join(PROJECT_ROOT, '.rlhf', 'adk-state.json');
|
|
26
|
+
|
|
27
|
+
function ensureDir(dirPath) {
|
|
28
|
+
if (!fs.existsSync(dirPath)) {
|
|
29
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadState() {
|
|
34
|
+
if (fs.existsSync(STATE_FILE)) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
37
|
+
} catch {
|
|
38
|
+
return { lastProcessedFeedbackId: null };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { lastProcessedFeedbackId: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function saveState(state) {
|
|
45
|
+
ensureDir(path.dirname(STATE_FILE));
|
|
46
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { createRuleProposal, createReasoningTrace } = require('./a2ui-engine');
|
|
50
|
+
|
|
51
|
+
function buildFakeConsolidation(anchorLogs, newLogs) {
|
|
52
|
+
const combined = [...anchorLogs, ...newLogs].filter(Boolean);
|
|
53
|
+
const connectedLogIds = combined.slice(0, 3).map((log) => log.id).filter(Boolean);
|
|
54
|
+
const issueHints = combined
|
|
55
|
+
.map((log) => log.whatWentWrong || log.context || '')
|
|
56
|
+
.map((text) => String(text).trim())
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
|
|
59
|
+
const primaryHint = issueHints[0] || 'Environment and rollout checks were skipped';
|
|
60
|
+
return {
|
|
61
|
+
consolidatedInsights: [
|
|
62
|
+
{
|
|
63
|
+
pattern: primaryHint,
|
|
64
|
+
rule: 'ALWAYS verify environment, approvals, and rollout prerequisites before executing workflow changes',
|
|
65
|
+
severity: 'high',
|
|
66
|
+
connectedLogIds,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
a2uiPayload: {
|
|
70
|
+
reasoningGraph: {
|
|
71
|
+
summary: 'Synthetic consolidation used for hermetic test mode.',
|
|
72
|
+
connections: connectedLogIds.slice(1).map((id) => ({
|
|
73
|
+
from: connectedLogIds[0],
|
|
74
|
+
to: id,
|
|
75
|
+
label: 'Shared failure pattern',
|
|
76
|
+
})),
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function consolidateMemory() {
|
|
83
|
+
const requestedFakeConsolidation = process.env.ADK_FAKE_CONSOLIDATION === 'true';
|
|
84
|
+
const useFakeConsolidation = requestedFakeConsolidation && process.env.NODE_ENV === 'test';
|
|
85
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
86
|
+
if (requestedFakeConsolidation && !useFakeConsolidation) {
|
|
87
|
+
console.warn('[ADK Consolidator] Ignoring ADK_FAKE_CONSOLIDATION outside test mode.');
|
|
88
|
+
}
|
|
89
|
+
if (!useFakeConsolidation && !apiKey) {
|
|
90
|
+
console.warn('[ADK Consolidator] GEMINI_API_KEY is not set. Skipping active consolidation.');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const ai = useFakeConsolidation ? null : new GoogleGenAI({ apiKey });
|
|
95
|
+
const paths = getFeedbackPaths();
|
|
96
|
+
const state = loadState();
|
|
97
|
+
|
|
98
|
+
const allLogs = readJSONL(paths.FEEDBACK_LOG_PATH);
|
|
99
|
+
|
|
100
|
+
if (allLogs.length === 0) {
|
|
101
|
+
console.log('[ADK Consolidator] No logs to consolidate.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 1. Anchor-Memories: Always include the first 5 "foundational" logs of the session.
|
|
106
|
+
// These act as 'attention sinks' that provide global context and numerical anchors
|
|
107
|
+
// for the model's reasoning stability.
|
|
108
|
+
const anchorLogs = allLogs.slice(0, 5);
|
|
109
|
+
|
|
110
|
+
// 2. Incremental Window: Find where we left off
|
|
111
|
+
const hasPriorState = Boolean(state.lastProcessedFeedbackId);
|
|
112
|
+
let newLogs = [];
|
|
113
|
+
if (hasPriorState) {
|
|
114
|
+
const lastIdx = allLogs.findIndex(l => l.id === state.lastProcessedFeedbackId);
|
|
115
|
+
if (lastIdx !== -1) {
|
|
116
|
+
newLogs = allLogs.slice(lastIdx + 1);
|
|
117
|
+
} else {
|
|
118
|
+
newLogs = allLogs.slice(-50);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
newLogs = allLogs.slice(-50);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Filter anchors out of newLogs if they overlap to save tokens
|
|
125
|
+
const rawNewLogs = newLogs.filter(nl => !anchorLogs.some(al => al.id === nl.id));
|
|
126
|
+
|
|
127
|
+
if (rawNewLogs.length === 0 && anchorLogs.length > 0 && hasPriorState) {
|
|
128
|
+
console.log('[ADK Consolidator] No new logs since last consolidation cycle.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Adaptive context compaction: reduce prompt size before sending to Gemini
|
|
133
|
+
const compactionResult = compactContext(rawNewLogs, anchorLogs, { windowSize: 30, perEntryMaxChars: 512 });
|
|
134
|
+
const filteredNewLogs = compactionResult.entries.filter(e => !anchorLogs.some(a => a.id === e.id));
|
|
135
|
+
if (compactionResult.compacted) {
|
|
136
|
+
console.log(`[ADK Consolidator] Context compacted: removed ${compactionResult.removedCount} entries (stage ${compactionResult.stage}).`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Resolve model via role router instead of hardcoding
|
|
140
|
+
const modelConfig = resolveModelRole('normal');
|
|
141
|
+
const activationLabel = useFakeConsolidation ? `Gemini test stub (${modelConfig.model})` : `Gemini (${modelConfig.model})`;
|
|
142
|
+
console.log(`[ADK Consolidator] Activating ${activationLabel} with ${anchorLogs.length} anchors and ${filteredNewLogs.length} new events...`);
|
|
143
|
+
|
|
144
|
+
const prompt = `
|
|
145
|
+
You are the Agent Development Kit (ADK) 'Always-On' Memory Consolidator.
|
|
146
|
+
Synthesize the latest feedback into generalized prevention rules AND dynamic A2UI components.
|
|
147
|
+
|
|
148
|
+
Foundational Anchors (Numerical Sinks):
|
|
149
|
+
${JSON.stringify(anchorLogs.map(l => ({ id: l.id, signal: l.signal, context: l.context, whatWentWrong: l.whatWentWrong })), null, 2)}
|
|
150
|
+
|
|
151
|
+
Latest Feedback Events (Spikes):
|
|
152
|
+
${JSON.stringify(filteredNewLogs.map(l => ({ id: l.id, signal: l.signal, context: l.context, whatWentWrong: l.whatWentWrong })), null, 2)}
|
|
153
|
+
|
|
154
|
+
Output ONLY valid JSON:
|
|
155
|
+
{
|
|
156
|
+
"consolidatedInsights": [
|
|
157
|
+
{
|
|
158
|
+
"pattern": "Underlying flaw",
|
|
159
|
+
"rule": "ALWAYS/NEVER directive",
|
|
160
|
+
"severity": "critical|high|medium|low",
|
|
161
|
+
"connectedLogIds": ["fb_1", "fb_2"]
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
"a2uiPayload": {
|
|
165
|
+
"reasoningGraph": {
|
|
166
|
+
"summary": "Synthesis summary",
|
|
167
|
+
"connections": [{"from": "fb_1", "to": "fb_2", "label": "Same environment issue"}]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const result = useFakeConsolidation
|
|
175
|
+
? buildFakeConsolidation(anchorLogs, filteredNewLogs)
|
|
176
|
+
: JSON.parse((await ai.models.generateContent({
|
|
177
|
+
model: modelConfig.model,
|
|
178
|
+
contents: prompt,
|
|
179
|
+
config: { responseMimeType: 'application/json' }
|
|
180
|
+
})).text);
|
|
181
|
+
console.log('[ADK Consolidator] Consolidation complete.');
|
|
182
|
+
|
|
183
|
+
if (result.consolidatedInsights) {
|
|
184
|
+
// Append to markdown (legacy fallback)
|
|
185
|
+
appendRules(result.consolidatedInsights, paths.PREVENTION_RULES_PATH);
|
|
186
|
+
|
|
187
|
+
// Track guardrail spikes and emit reminders when threshold is met
|
|
188
|
+
const criticalInsights = result.consolidatedInsights.filter(
|
|
189
|
+
i => i.severity === 'critical' || i.severity === 'high',
|
|
190
|
+
);
|
|
191
|
+
if (criticalInsights.length > 0) {
|
|
192
|
+
trackEvent('guardrail_spike');
|
|
193
|
+
if (shouldInjectReminder('guardrail_spike')) {
|
|
194
|
+
const topRule = criticalInsights[0].rule;
|
|
195
|
+
const reminderTurns = injectReminder([], 'guardrail_spike', { rule: topRule });
|
|
196
|
+
const reminderPath = path.join(PROJECT_ROOT, '.rlhf', `reminder_${Date.now()}.json`);
|
|
197
|
+
fs.writeFileSync(reminderPath, JSON.stringify(reminderTurns[0], null, 2));
|
|
198
|
+
console.log(`[ADK Consolidator] Emitted system reminder: ${reminderPath}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Emit A2UI components (New Model)
|
|
203
|
+
result.consolidatedInsights.forEach(insight => {
|
|
204
|
+
const proposal = createRuleProposal(insight.pattern, insight.rule, insight.severity);
|
|
205
|
+
const a2uiPath = path.join(PROJECT_ROOT, '.rlhf', `a2ui_proposal_${Date.now()}.json`);
|
|
206
|
+
fs.writeFileSync(a2uiPath, JSON.stringify(proposal, null, 2));
|
|
207
|
+
console.log(`[ADK Consolidator] Emitted A2UI Proposal: ${a2uiPath}`);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
state.lastProcessedFeedbackId = newLogs[newLogs.length - 1].id;
|
|
212
|
+
saveState(state);
|
|
213
|
+
|
|
214
|
+
// Usage-based billing: Report this consolidation to Stripe if a valid API key exists
|
|
215
|
+
const cloudKey = process.env.RLHF_API_KEY;
|
|
216
|
+
if (cloudKey) {
|
|
217
|
+
const validation = validateApiKey(cloudKey);
|
|
218
|
+
if (validation.valid && validation.metadata.subscriptionItemId) {
|
|
219
|
+
await reportUsageToStripe(validation.metadata.subscriptionItemId);
|
|
220
|
+
console.log(`[ADK Consolidator] Billable event reported to Stripe for customer: ${validation.metadata.customerId}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
} catch (err) {
|
|
225
|
+
console.error('[ADK Consolidator] Consolidation failed:', err.message);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function appendRules(insights, rulesPath) {
|
|
230
|
+
let existingContent = '';
|
|
231
|
+
if (fs.existsSync(rulesPath)) {
|
|
232
|
+
existingContent = fs.readFileSync(rulesPath, 'utf-8');
|
|
233
|
+
} else {
|
|
234
|
+
existingContent = '# Prevention Rules\n\nGenerated from active semantic memory consolidation.\n\n';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let newRulesBlock = '\n## ADK Semantic Consolidations\n';
|
|
238
|
+
const timestamp = new Date().toISOString();
|
|
239
|
+
insights.forEach(insight => {
|
|
240
|
+
newRulesBlock += `- [${insight.severity.toUpperCase()}] **${insight.pattern}**\n - Rule: ${insight.rule} *(Consolidated at ${timestamp})*\n`;
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const updatedContent = existingContent + newRulesBlock;
|
|
244
|
+
ensureDir(path.dirname(rulesPath));
|
|
245
|
+
fs.writeFileSync(rulesPath, updatedContent);
|
|
246
|
+
console.log(`[ADK Consolidator] Appended ${insights.length} new consolidated rules to ${rulesPath}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (require.main === module) {
|
|
250
|
+
const args = process.argv.slice(2);
|
|
251
|
+
const isWatchMode = args.includes('--watch');
|
|
252
|
+
|
|
253
|
+
if (isWatchMode) {
|
|
254
|
+
console.log('[ADK Consolidator] Started in Always-On Watch Mode (interval: 5 minutes)');
|
|
255
|
+
consolidateMemory(); // Run once immediately
|
|
256
|
+
setInterval(() => {
|
|
257
|
+
consolidateMemory();
|
|
258
|
+
}, 5 * 60 * 1000); // Check every 5 minutes
|
|
259
|
+
} else {
|
|
260
|
+
consolidateMemory().then(() => {
|
|
261
|
+
console.log('[ADK Consolidator] Cycle finished.');
|
|
262
|
+
process.exit(0);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { consolidateMemory };
|