feat(sf): persist escalation resolutions as durable memories
When an escalation is resolved (auto-mode accept or user override), write the choice + rationale into the memories table with category="architecture". The "[escalation:<task>] <question>. Chose: <option>. Rationale: ..." prefix mirrors the decisions->memories backfill format so search and de-duplication work the same way. Why: getActiveMemoriesRanked auto-injects top memories into every execute-task prompt, so a resolved escalation now travels forward as implicit context across the whole project — not just the immediate carry-forward into the next task. The artifact JSON stays as the audit trail; the memory is the discoverable, semantically-ranked surface. Best-effort write — never blocks resolution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7c6140517e
commit
00c13bc5a1
1 changed files with 46 additions and 0 deletions
|
|
@ -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:<id>] ..." 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:<task>]" 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(" ");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue