AUDIT / src /lib /agentLoop /actionSimulator.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
3.82 kB
/**
* actionSimulator.ts — S-PRED: Predizione esito tool prima dell'esecuzione
*
* Consulta lo storico errori (errorLog.recent — già in-memory, zero I/O)
* e produce un "outcome prediction" PRIMA della tool call.
*
* Zero LLM calls, Safari-safe, additive, non-blocking.
* Integrato in case2Block.ts prima di executeToolGated.
*
* @module actionSimulator
*/
import type { ErrorEntry } from "../agentBrain";
export interface ActionPrediction {
toolName: string;
predictedOutcome: "likely_success" | "likely_fail" | "uncertain";
reason: string;
similarPastFailures: number;
}
// ── Tool rischiosi — modificano stato filesystem/git/rete ─────────────────────
const RISKY_TOOLS = new Set([
"apply_patch", "write_file", "iterate_file", "move_file", "delete_file",
"git_push", "git_push_multiple",
"run_code", "run_terminal_cmd", "run_python",
]);
function _kwFromText(text: string): string[] {
return text
.toLowerCase()
.replace(/[^a-z0-9_./-]+/g, " ")
.split(" ")
.filter(w => w.length > 3)
.slice(0, 15);
}
function _kwSim(a: string[], b: string[]): number {
if (a.length === 0 || b.length === 0) return 0;
const sa = new Set(a);
return b.filter(w => sa.has(w)).length / Math.max(a.length, b.length);
}
/**
* Predice l'esito di un tool call usando errori recenti in-memory.
*
* @param toolName Nome del tool da eseguire
* @param args Argomenti del tool
* @param pastErrors Errori recenti (da errorLog.recent(50))
*/
export function predictOutcome(
toolName: string,
args: Record<string, unknown>,
pastErrors: ErrorEntry[],
): ActionPrediction {
// Tool non rischiosi → sempre likely_success senza analisi
if (!RISKY_TOOLS.has(toolName)) {
return { toolName, predictedOutcome: "likely_success", reason: "tool non rischioso", similarPastFailures: 0 };
}
const argSig = JSON.stringify(args).slice(0, 150);
const argKw = _kwFromText(argSig);
// Filtra errori non risolti per questo tool
const toolErrors = pastErrors.filter(e => !e.resolved && e.context.includes(toolName));
// U6: path identity → similarity = 1.0 (exact path match = stesso file → forte segnale)
// U6: threshold Jaccard 0.35 → 0.25 (più sensibile su argomenti parzialmente overlapping)
const _argPath = String(args.path ?? args.file_path ?? "");
const similar = toolErrors.filter(e => {
if (_argPath && e.context.includes(_argPath)) return true; // path identity
return _kwSim(argKw, _kwFromText(e.message + " " + e.context)) > 0.25;
});
if (similar.length >= 2) {
return {
toolName,
predictedOutcome: "likely_fail",
reason: `${similar.length} fallimenti simili non risolti su "${toolName}"`,
similarPastFailures: similar.length,
};
}
if (toolErrors.length >= 4 && similar.length >= 1) {
return {
toolName,
predictedOutcome: "likely_fail",
reason: `${toolErrors.length} errori non risolti su "${toolName}" (1 con args simili)`,
similarPastFailures: similar.length,
};
}
return {
toolName,
predictedOutcome: "uncertain",
reason: toolErrors.length > 0
? `${toolErrors.length} errori passati su "${toolName}", nessuno simile agli args correnti`
: "nessun pattern noto",
similarPastFailures: similar.length,
};
}
/**
* Bypass immediato di REVISION_THRESHOLD: se la predizione era "likely_fail"
* e l'esito reale è un errore → attiva belief revision al primo fallimento confermato.
*/
export function shouldReviseImmediately(
predicted: ActionPrediction,
actualFailed: boolean,
): boolean {
return predicted.predictedOutcome === "likely_fail" && actualFailed;
}