diff --git a/src/resources/extensions/sf/escalation.ts b/src/resources/extensions/sf/escalation.ts index e6c16babc..3208ccccb 100644 --- a/src/resources/extensions/sf/escalation.ts +++ b/src/resources/extensions/sf/escalation.ts @@ -8,6 +8,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; +import { createMemory } from "./memory-store.js"; import { resolveSlicePath } from "./paths.js"; import type { TaskRow } from "./sf-db.js"; import { @@ -375,6 +376,26 @@ export function resolveEscalation( }), ); + // Persist as a durable memory so the choice + rationale auto-injects into + // future prompts via getActiveMemoriesRanked. Mirrors the decisions->memories + // backfill pattern (category="architecture", "[decision:] ..." prefix). + // Best-effort — never block resolution if the memory write fails. + try { + const memoryContent = formatEscalationMemoryContent(art, chosenOption, rationale); + createMemory({ + category: "architecture", + content: memoryContent, + confidence: 0.85, + source_unit_type: "execute-task", + source_unit_id: taskId, + }); + } catch (memoryErr) { + logWarning( + "tool", + `escalation: failed to persist resolution as memory: ${(memoryErr as Error).message}`, + ); + } + return { status: "resolved", message: `Escalation resolved. Next ${sliceId} dispatch will run normally.`, @@ -382,3 +403,28 @@ export function resolveEscalation( chosenOption, }; } + +/** Synthesize a 1–3 sentence memory line from a resolved escalation artifact. + * The "[escalation:]" prefix mirrors the decisions->memories backfill + * format so de-duplication and search work the same way. */ +function formatEscalationMemoryContent( + art: EscalationArtifact, + chosenOption: EscalationOption | undefined, + userRationale: string, +): string { + const choiceLabel = chosenOption + ? `${chosenOption.label} (${chosenOption.id})` + : "unknown"; + const rationale = userRationale.trim() + ? userRationale.trim() + : art.recommendationRationale; + const tradeoffs = chosenOption?.tradeoffs?.trim(); + return [ + `[escalation:${art.taskId}] ${art.question}`, + `Chose: ${choiceLabel}.`, + `Rationale: ${rationale}`, + tradeoffs ? `Tradeoffs: ${tradeoffs}` : "", + ] + .filter(Boolean) + .join(" "); +}