openskynet / src /omega /durable-memory.ts
Darochin's picture
Mirror OpenSkyNet workspace snapshot from Git HEAD
fc93158 verified
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import type {
OmegaSessionOutcomeSnapshot,
OmegaSessionValidationSnapshot,
} from "./session-context.js";
import { withOmegaSessionLock } from "./state-lock.js";
export type OmegaDurableMemoryKind = "verified_success" | "repeated_failure";
export type OmegaDurableMemoryEntry = {
id: string;
kind: OmegaDurableMemoryKind;
task: string;
targets: string[];
errorKind?: string;
observedChangedFiles: string[];
structuredOk?: boolean;
writeOk?: boolean;
localityScore?: number;
protectedPreservationRate?: number;
successCount: number;
failureCount: number;
learnedConstraints: string[];
firstSeenAt: number;
lastSeenAt: number;
lastOutcomeStatus: OmegaSessionOutcomeSnapshot["status"];
};
type OmegaDurableMemoryStore = {
sessionKey: string;
revision?: number;
updatedAt: number;
entries: OmegaDurableMemoryEntry[];
};
const OMEGA_DURABLE_MEMORY_LIMIT = 48;
function normalizeText(value: string): string {
return value.toLowerCase().replace(/\s+/g, " ").trim();
}
function normalizePaths(values: string[]): string[] {
return Array.from(
new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)),
).sort();
}
function normalizeUnitInterval(value: unknown): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value)) {
return undefined;
}
return Math.max(0, Math.min(1, value));
}
function durableMemoryId(task: string, targets: string[], errorKind?: string): string {
return crypto
.createHash("sha256")
.update(`${normalizeText(task)}::${normalizePaths(targets).join("|")}::${errorKind ?? ""}`)
.digest("hex")
.slice(0, 16);
}
function sanitizeSessionKey(sessionKey: string): string {
const normalized = sessionKey.trim() || "main";
const readable = normalized.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 48) || "main";
const digest = crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 12);
return `${readable}-${digest}.json`;
}
function resolveOmegaDurableMemoryDir(workspaceRoot: string): string {
return path.join(workspaceRoot, ".openskynet", "omega-durable-memory");
}
export function resolveOmegaDurableMemoryFile(params: {
workspaceRoot: string;
sessionKey: string;
}): string {
// Durable Memory is shared across all sessions in the workspace to enable cross-session learning.
return path.join(
resolveOmegaDurableMemoryDir(params.workspaceRoot),
"global-durable-memory.json",
);
}
function parseDurableMemoryEntry(value: unknown): OmegaDurableMemoryEntry | undefined {
if (!value || typeof value !== "object") {
return undefined;
}
const entry = value as Partial<OmegaDurableMemoryEntry>;
if (
typeof entry.id !== "string" ||
typeof entry.kind !== "string" ||
typeof entry.task !== "string" ||
typeof entry.firstSeenAt !== "number" ||
typeof entry.lastSeenAt !== "number" ||
typeof entry.lastOutcomeStatus !== "string"
) {
return undefined;
}
if (entry.kind !== "verified_success" && entry.kind !== "repeated_failure") {
return undefined;
}
return {
id: entry.id,
kind: entry.kind,
task: entry.task,
targets: Array.isArray(entry.targets)
? normalizePaths(entry.targets.filter((v): v is string => typeof v === "string"))
: [],
errorKind: typeof entry.errorKind === "string" ? entry.errorKind : undefined,
observedChangedFiles: Array.isArray(entry.observedChangedFiles)
? normalizePaths(entry.observedChangedFiles.filter((v): v is string => typeof v === "string"))
: [],
structuredOk: typeof entry.structuredOk === "boolean" ? entry.structuredOk : undefined,
writeOk: typeof entry.writeOk === "boolean" ? entry.writeOk : undefined,
localityScore: normalizeUnitInterval(entry.localityScore),
protectedPreservationRate: normalizeUnitInterval(entry.protectedPreservationRate),
successCount: typeof entry.successCount === "number" ? entry.successCount : 0,
failureCount: typeof entry.failureCount === "number" ? entry.failureCount : 0,
learnedConstraints: Array.isArray(entry.learnedConstraints)
? entry.learnedConstraints.filter((v): v is string => typeof v === "string")
: [],
firstSeenAt: entry.firstSeenAt,
lastSeenAt: entry.lastSeenAt,
lastOutcomeStatus: entry.lastOutcomeStatus,
};
}
export async function loadOmegaDurableMemory(params: {
workspaceRoot: string;
sessionKey: string;
}): Promise<OmegaDurableMemoryEntry[]> {
try {
const raw = await fs.readFile(resolveOmegaDurableMemoryFile(params), "utf-8");
const parsed = JSON.parse(raw) as Partial<OmegaDurableMemoryStore>;
if (!Array.isArray(parsed.entries)) {
return [];
}
return parsed.entries
.map(parseDurableMemoryEntry)
.filter((value): value is OmegaDurableMemoryEntry => value !== undefined);
} catch {
return [];
}
}
function queryScore(entry: OmegaDurableMemoryEntry, task: string, expectedPaths: string[]): number {
const normalizedTask = normalizeText(task);
const tokens = normalizedTask.split(/[^a-z0-9]+/i).filter((token) => token.length >= 4);
const normalizedTargets = normalizePaths(expectedPaths).map((value) => normalizeText(value));
let score = 0;
if (
normalizeText(entry.task).includes(normalizedTask) ||
normalizedTask.includes(normalizeText(entry.task))
) {
score += 5;
}
for (const token of tokens) {
if (normalizeText(entry.task).includes(token)) {
score += 2;
}
if (entry.targets.some((target) => normalizeText(target).includes(token))) {
score += 2;
}
}
if (normalizedTargets.length > 0) {
for (const target of normalizedTargets) {
if (entry.targets.some((candidate) => normalizeText(candidate) === target)) {
score += 4;
}
}
}
if (entry.kind === "verified_success") {
score += Math.min(entry.successCount, 3);
if (typeof entry.localityScore === "number" && entry.localityScore >= 0.8) {
score += 1;
}
if (
typeof entry.protectedPreservationRate === "number" &&
entry.protectedPreservationRate >= 0.9
) {
score += 1;
}
} else {
score += Math.min(entry.failureCount, 3);
if (
typeof entry.localityScore === "number" &&
entry.localityScore < 0.5 &&
typeof entry.protectedPreservationRate === "number" &&
entry.protectedPreservationRate < 0.75
) {
score += 2;
}
}
return score;
}
export async function queryOmegaDurableMemory(params: {
workspaceRoot: string;
sessionKey: string;
task: string;
expectedPaths?: string[];
maxResults?: number;
}): Promise<OmegaDurableMemoryEntry[]> {
const entries = await loadOmegaDurableMemory(params);
const scored = entries
.filter((entry) => entry.kind !== "repeated_failure" || entry.failureCount >= 2)
.map((entry) => ({
entry,
score: queryScore(entry, params.task, params.expectedPaths ?? []),
}))
.filter((item) => item.score > 0)
.sort(
(left, right) => right.score - left.score || right.entry.lastSeenAt - left.entry.lastSeenAt,
);
return scored.slice(0, params.maxResults ?? 4).map((item) => item.entry);
}
export async function admitOmegaDurableMemory(params: {
workspaceRoot: string;
sessionKey: string;
task: string;
learnedConstraints?: string[];
validation: OmegaSessionValidationSnapshot;
outcome: OmegaSessionOutcomeSnapshot;
}): Promise<void> {
await withOmegaSessionLock(params, async () => {
const targets = normalizePaths([
...(params.validation.expectedPaths ?? []),
...(params.outcome.observedChangedFiles ?? []),
]);
const existing = await loadOmegaDurableMemory(params);
let currentRevision = 0;
try {
const raw = await fs.readFile(resolveOmegaDurableMemoryFile(params), "utf-8");
const parsed = JSON.parse(raw) as Partial<OmegaDurableMemoryStore>;
currentRevision = typeof parsed.revision === "number" ? parsed.revision : 0;
} catch {}
const id = durableMemoryId(params.task, targets, params.outcome.errorKind);
const now = Date.now();
const index = existing.findIndex((entry) => entry.id === id);
const current = index >= 0 ? existing[index] : undefined;
const verifiedSuccess =
params.outcome.status === "ok" &&
(params.outcome.writeOk === true ||
params.outcome.structuredOk === true ||
(params.outcome.observedChangedFiles?.length ?? 0) > 0);
const repeatedFailure =
params.outcome.status !== "ok" &&
typeof params.outcome.errorKind === "string" &&
(current?.failureCount ?? 0) + 1 >= 1;
const learnedConstraints = Array.from(
new Set([...(current?.learnedConstraints ?? []), ...(params.learnedConstraints ?? [])]),
);
if (!verifiedSuccess && !repeatedFailure) {
if (params.outcome.status !== "ok" && typeof params.outcome.errorKind === "string") {
const nextFailureEntry: OmegaDurableMemoryEntry = current
? {
...current,
failureCount: current.failureCount + 1,
learnedConstraints,
lastSeenAt: now,
lastOutcomeStatus: params.outcome.status,
}
: {
id,
kind: "repeated_failure",
task: params.task,
targets,
errorKind: params.outcome.errorKind,
observedChangedFiles: normalizePaths(params.outcome.observedChangedFiles ?? []),
structuredOk: params.outcome.structuredOk,
writeOk: params.outcome.writeOk,
localityScore: normalizeUnitInterval(params.outcome.localityScore),
protectedPreservationRate: normalizeUnitInterval(
params.outcome.protectedPreservationRate,
),
successCount: 0,
failureCount: 1,
learnedConstraints,
firstSeenAt: now,
lastSeenAt: now,
lastOutcomeStatus: params.outcome.status,
};
if (index >= 0) {
existing[index] = nextFailureEntry;
} else {
existing.push(nextFailureEntry);
}
}
} else {
const next: OmegaDurableMemoryEntry = {
id,
kind: verifiedSuccess ? "verified_success" : "repeated_failure",
task: params.task,
targets,
errorKind: params.outcome.errorKind,
observedChangedFiles: normalizePaths(
params.outcome.observedChangedFiles ?? current?.observedChangedFiles ?? [],
),
structuredOk: params.outcome.structuredOk ?? current?.structuredOk,
writeOk: params.outcome.writeOk ?? current?.writeOk,
localityScore:
normalizeUnitInterval(params.outcome.localityScore) ?? current?.localityScore,
protectedPreservationRate:
normalizeUnitInterval(params.outcome.protectedPreservationRate) ??
current?.protectedPreservationRate,
successCount: (current?.successCount ?? 0) + (verifiedSuccess ? 1 : 0),
failureCount: (current?.failureCount ?? 0) + (!verifiedSuccess ? 1 : 0),
learnedConstraints,
firstSeenAt: current?.firstSeenAt ?? now,
lastSeenAt: now,
lastOutcomeStatus: params.outcome.status,
};
if (index >= 0) {
existing[index] = next;
} else {
existing.push(next);
}
}
const trimmed = existing
.sort((left, right) => right.lastSeenAt - left.lastSeenAt)
.slice(0, OMEGA_DURABLE_MEMORY_LIMIT);
const payload: OmegaDurableMemoryStore = {
sessionKey: params.sessionKey,
revision: currentRevision + 1,
updatedAt: now,
entries: trimmed,
};
await fs.mkdir(resolveOmegaDurableMemoryDir(params.workspaceRoot), { recursive: true });
await fs.writeFile(
resolveOmegaDurableMemoryFile(params),
JSON.stringify(payload, null, 2),
"utf-8",
);
});
}