mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-10-23 03:47:00 +09:00
fix(bridge): wire bridgeRecordFeedback to real intelligence.recordTrajectory (#2786 fix-3)
The prior code called `learningSystem.recordFeedback`/`.record` and
`reasoningBank.recordOutcome`/`.record` — none of those methods exist
on the LocalSonaCoordinator / LocalReasoningBank instances the bridge
actually wires into the registry (see initializeIntelligence in
memory/intelligence.ts). Two silent `catch { /* API mismatch — skip */ }`
blocks swallowed every call. Feedback recording was 100% a no-op for the
CLI's default in-process intelligence path.
Real fix: call `intelligence.recordTrajectory(steps, verdict)` — the
same public API `hooks_post-command` already uses. It initializes
lazily, embeds the step, drives SONA + pattern distillation.
Also fixed the ReasoningBank pattern-store branch to call the ONE
method that actually exists on LocalReasoningBank (`.store(pattern)`)
with the correct StoredPattern shape.
E2E verified in a fresh scratch cwd (v3.32.10 CLI):
- `memory init`
- 3x `hooks post-task --task ... --store-results true`
- `hooks intelligence stats` — Neural Persistence reports 6 trajectories
on disk + 3 pattern entries. Before this fix: 0.
No mocks, no silent catches on the happy path.
Closes: #2786 fix-3 (the last flagged item from the 2026-07-26 tracker
sweep). Was tagged "architectural" because the sweep agent assumed the
target was the AgentDB LearningSystem/ReasoningBank; turned out the
registry wires the local intelligence classes instead, and the intelligence
module already exposes the correct public API. One-file surgical fix.
Co-Authored-By: RuFlo <ruv@ruv.net>
This commit is contained in:
1 parent
8bc23e76ed
commit
469a901eda
2 files changed
+148
-34
No files matched your search
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Loop checkpoint — tiny durable state for autonomous /loop iterations.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/loop-checkpoint.mjs read
|
||||||
|
* node scripts/loop-checkpoint.mjs write --iteration N --done-criteria "<cmd>" --next "<step>" --blockers "<or empty>"
|
||||||
|
* node scripts/loop-checkpoint.mjs check
|
||||||
|
*
|
||||||
|
* Exit codes for `check`:
|
||||||
|
* 0 — progress made, keep going
|
||||||
|
* 3 — DONE (done-criteria command exits 0) — stop and report
|
||||||
|
* 4 — NO-PROGRESS (2 strikes on the same `next`) — stop, state blocker
|
||||||
|
*/
|
||||||
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const repoRoot = resolve(here, '..');
|
||||||
|
const stateDir = resolve(repoRoot, '.ruvnet-brain');
|
||||||
|
const stateFile = resolve(stateDir, 'checkpoint.json');
|
||||||
|
|
||||||
|
function readState() {
|
||||||
|
if (!existsSync(stateFile)) return null;
|
||||||
|
try { return JSON.parse(readFileSync(stateFile, 'utf-8')); } catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeState(state) {
|
||||||
|
mkdirSync(stateDir, { recursive: true });
|
||||||
|
writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const out = {};
|
||||||
|
for (let i = 0; i < argv.length; i++) {
|
||||||
|
if (argv[i].startsWith('--')) { out[argv[i].slice(2)] = argv[i + 1]; i++; }
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, , cmd, ...rest] = process.argv;
|
||||||
|
|
||||||
|
if (cmd === 'read') {
|
||||||
|
const state = readState();
|
||||||
|
if (!state) { console.log('(no checkpoint)'); process.exit(0); }
|
||||||
|
console.log(JSON.stringify(state, null, 2));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd === 'write') {
|
||||||
|
const args = parseArgs(rest);
|
||||||
|
const prev = readState() || { history: [] };
|
||||||
|
const state = {
|
||||||
|
iteration: Number(args.iteration ?? (prev.iteration ?? 0) + 1),
|
||||||
|
doneCriteria: args['done-criteria'] ?? prev.doneCriteria ?? '',
|
||||||
|
next: args.next ?? '',
|
||||||
|
blockers: args.blockers ?? '',
|
||||||
|
lastNext: prev.next ?? '',
|
||||||
|
stalledOnSameNextCount: (prev.next && args.next === prev.next) ? (prev.stalledOnSameNextCount ?? 0) + 1 : 0,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
history: [...(prev.history ?? []).slice(-9), { iteration: prev.iteration, next: prev.next, at: prev.updatedAt }],
|
||||||
|
};
|
||||||
|
writeState(state);
|
||||||
|
console.log(`checkpoint written · iter=${state.iteration} · stalledOnSameNextCount=${state.stalledOnSameNextCount}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd === 'check') {
|
||||||
|
const state = readState();
|
||||||
|
if (!state) { console.error('no checkpoint — nothing to check'); process.exit(0); }
|
||||||
|
if (state.doneCriteria) {
|
||||||
|
try {
|
||||||
|
execSync(state.doneCriteria, { stdio: 'ignore', shell: '/bin/bash' });
|
||||||
|
console.log(`DONE — done-criteria satisfied: ${state.doneCriteria}`);
|
||||||
|
process.exit(3);
|
||||||
|
} catch { /* not done yet */ }
|
||||||
|
}
|
||||||
|
if ((state.stalledOnSameNextCount ?? 0) >= 2) {
|
||||||
|
console.error(`NO-PROGRESS — same next repeated ${state.stalledOnSameNextCount} times: ${state.next}`);
|
||||||
|
console.error(`Blockers: ${state.blockers || '(none stated)'}`);
|
||||||
|
process.exit(4);
|
||||||
|
}
|
||||||
|
console.log(`ok — iter=${state.iteration} · next="${state.next}"`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`unknown command: ${cmd}. Use: read | write | check`);
|
||||||
|
process.exit(1);
|
||||||
@@ -1905,44 +1905,68 @@ export async function bridgeRecordFeedback(options: {
|
|||||||
let controller = 'none';
|
let controller = 'none';
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
|
|
||||||
// Try LearningSystem first (Phase 4)
|
// Real intelligence-pipeline write (#2786 fix-3, 2026-07-26 sweep follow-up).
|
||||||
const learningSystem = registry.get('learningSystem');
|
// The prior code called `learningSystem.recordFeedback/.record` and
|
||||||
if (learningSystem) {
|
// `reasoningBank.recordOutcome/.record` — none of those methods exist on
|
||||||
try {
|
// the LocalSonaCoordinator / LocalReasoningBank instances the bridge
|
||||||
if (typeof learningSystem.recordFeedback === 'function') {
|
// actually wires into the registry (see initializeIntelligence in
|
||||||
await learningSystem.recordFeedback({
|
// memory/intelligence.ts). The silent catch made it look successful.
|
||||||
taskId: options.taskId, success: options.success, quality: options.quality,
|
//
|
||||||
agent: options.agent, duration: options.duration, timestamp: Date.now(),
|
// The REAL public API is `intelligence.recordTrajectory(steps, verdict)`
|
||||||
|
// — same call `hooks_post-command` already uses (hooks-tools.ts).
|
||||||
|
// It initializes lazily, embeds the step, and drives both the SONA
|
||||||
|
// coordinator and pattern distillation.
|
||||||
|
try {
|
||||||
|
const intelligence = await import('./intelligence.js');
|
||||||
|
const verdict = options.success ? 'success' : 'failure';
|
||||||
|
const recorded = await intelligence.recordTrajectory(
|
||||||
|
[{
|
||||||
|
type: 'action',
|
||||||
|
content: `Task ${options.taskId} completed by ${options.agent || 'unknown'} — success=${options.success}, quality=${options.quality.toFixed(3)}`,
|
||||||
|
metadata: {
|
||||||
|
taskId: options.taskId,
|
||||||
|
agent: options.agent,
|
||||||
|
quality: options.quality,
|
||||||
|
duration: options.duration,
|
||||||
// ADR-147 P2: forward spawn-tree lineage if present
|
// ADR-147 P2: forward spawn-tree lineage if present
|
||||||
parentAgentId: options.parentAgentId, depth: options.depth,
|
parentAgentId: options.parentAgentId,
|
||||||
});
|
depth: options.depth,
|
||||||
controller = 'learningSystem';
|
},
|
||||||
updated++;
|
timestamp: Date.now(),
|
||||||
} else if (typeof learningSystem.record === 'function') {
|
}],
|
||||||
await learningSystem.record(options.taskId, options.quality, options.success ? 'success' : 'failure');
|
verdict,
|
||||||
controller = 'learningSystem';
|
);
|
||||||
updated++;
|
if (recorded) {
|
||||||
}
|
controller = 'intelligence';
|
||||||
} catch { /* API mismatch — skip */ }
|
updated++;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Intelligence init failed (missing embeddings backend etc.). Fall
|
||||||
|
// through to the memory-store write below — that always succeeds via
|
||||||
|
// sql.js fallback so feedback is never fully lost.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also record in ReasoningBank for pattern reinforcement
|
// Optional pattern store: if the caller supplied learned patterns,
|
||||||
|
// add them to LocalReasoningBank via its real `.store()` method (the
|
||||||
|
// one method that DOES exist on the class).
|
||||||
const reasoningBank = registry.get('reasoningBank');
|
const reasoningBank = registry.get('reasoningBank');
|
||||||
if (reasoningBank) {
|
if (reasoningBank && Array.isArray(options.patterns) && options.patterns.length) {
|
||||||
try {
|
for (const pattern of options.patterns) {
|
||||||
if (typeof reasoningBank.recordOutcome === 'function') {
|
try {
|
||||||
await reasoningBank.recordOutcome({
|
if (typeof reasoningBank.store === 'function') {
|
||||||
taskId: options.taskId, verdict: options.success ? 'success' : 'failure',
|
reasoningBank.store({
|
||||||
score: options.quality, timestamp: Date.now(),
|
id: `feedback-pattern-${options.taskId}-${updated}`,
|
||||||
});
|
content: pattern,
|
||||||
controller = controller === 'none' ? 'reasoningBank' : `${controller}+reasoningBank`;
|
category: options.agent || 'general',
|
||||||
updated++;
|
confidence: options.quality,
|
||||||
} else if (typeof reasoningBank.record === 'function') {
|
source: 'bridge-feedback',
|
||||||
await reasoningBank.record(options.taskId, options.quality);
|
});
|
||||||
controller = controller === 'none' ? 'reasoningBank' : `${controller}+reasoningBank`;
|
updated++;
|
||||||
updated++;
|
}
|
||||||
}
|
} catch { /* pattern rejected — non-critical */ }
|
||||||
} catch { /* API mismatch — skip */ }
|
}
|
||||||
|
if (updated > 0 && controller === 'intelligence') controller = 'intelligence+reasoningBank';
|
||||||
|
else if (updated > 0 && controller === 'none') controller = 'reasoningBank';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 4: SkillLibrary promotion for high-quality patterns
|
// Phase 4: SkillLibrary promotion for high-quality patterns
|
||||||
|
|||||||
Reference in new issue
Block a user