fix(engine): preserve memory-before-instructions ordering in dynamic layer

This commit is contained in:
Matthew Greenberg
2026-05-10 12:59:08 -04:00
parent 9adfeeca61
commit 7a299ca543
3 changed files with 28 additions and 16 deletions

View File

@@ -83,15 +83,19 @@ describe("prompt layers backward compatibility", () => {
});
describe("reviewer assembly pattern", () => {
it("produces the expected reviewer prompt with memory in dynamic layer", () => {
it("preserves memory-before-instructions ordering in dynamic layer", () => {
const basePrompt = "You are an independent code and plan reviewer.";
const memoryInstructions = "\n## Memory\n\nUse fn_memory_search.";
const memoryInstructions = "## Memory\n\nUse fn_memory_search.";
const agentInstructions = "Focus on security.";
const plugins = "## Plugin: lint\n\nCheck eslint.";
// New reviewer pattern: memory goes in dynamic layer (not stable) so
// the stable prefix is byte-identical across sessions even when memory
// changes mid-task.
// The old reviewer pattern was:
// buildSystemPromptWithInstructions(base + memory, instructions) + plugins
// which produced: base + memory → instructions → plugins
//
// The new pattern moves memory from stable to dynamic (so stable prefix
// is byte-identical even if memory changes mid-task), but preserves the
// relative ordering: memory → instructions → plugins in the dynamic layer.
const layers = buildPromptLayers({
basePrompt,
agentInstructions,
@@ -100,12 +104,16 @@ describe("prompt layers backward compatibility", () => {
});
const result = collapsePromptLayers(layers);
// Base prompt is the stable layer
// Base prompt is the stable layer (no memory)
expect(layers.stable).toBe(basePrompt);
// Dynamic layer contains instructions, memory, and plugins
expect(result).toContain(agentInstructions);
expect(result).toContain("Memory");
expect(result).toContain(plugins);
// Dynamic layer preserves: memory → instructions → plugins ordering
const memoryIdx = result.indexOf("## Memory");
const instructionsIdx = result.indexOf("## Custom Instructions");
const pluginsIdx = result.indexOf("## Plugin:");
expect(memoryIdx).toBeGreaterThan(0);
expect(instructionsIdx).toBeGreaterThan(memoryIdx);
expect(pluginsIdx).toBeGreaterThan(instructionsIdx);
});
});

View File

@@ -64,7 +64,7 @@ describe("buildPromptLayers", () => {
});
expect(layers.dynamic).toBe(
"## Custom Instructions\n\nInstructions.\n\nMemory.\n\nPlugins."
"Memory.\n\n## Custom Instructions\n\nInstructions.\n\nPlugins."
);
});

View File

@@ -42,16 +42,20 @@ export function buildPromptLayers(input: PromptLayerInput): SystemPromptLayers {
const dynamicParts: string[] = [];
const trimmedInstructions = agentInstructions?.trim() ?? "";
if (trimmedInstructions) {
dynamicParts.push(`## Custom Instructions\n\n${trimmedInstructions}`);
}
// Memory section comes before instructions to preserve the relative
// ordering from the legacy buildSystemPromptWithInstructions approach,
// where memory was concatenated onto basePrompt before instructions
// were appended.
const trimmedMemory = memorySection?.trim() ?? "";
if (trimmedMemory) {
dynamicParts.push(trimmedMemory);
}
const trimmedInstructions = agentInstructions?.trim() ?? "";
if (trimmedInstructions) {
dynamicParts.push(`## Custom Instructions\n\n${trimmedInstructions}`);
}
const trimmedPlugins = pluginContributions?.trim() ?? "";
if (trimmedPlugins) {
dynamicParts.push(trimmedPlugins);