Deduplicate reviewer policy text by making workflow IR review seams the engine's built-in prompt source. - Move the canonical built-in reviewer prompt into core agent prompts and export seam prompt resolution helpers. - Resolve reviewer prompts from explicit role overrides first, then workflow IR review seams, with the built-in prompt as a fallback. - Update prompt cache and reviewer tests to cover single-source reviewer prompt behavior. - Add a patch changeset for the published Fusion package. Files changed: .../FN-6235-reviewer-prompt-single-source.md | 5 + packages/core/src/__tests__/agent-prompts.test.ts | 8 +- packages/core/src/agent-prompts.ts | 109 +++++++++-- packages/core/src/index.ts | 2 + packages/core/src/workflow-ir-resolver.ts | 31 ++- .../src/__tests__/prompt-cache-integration.test.ts | 10 +- .../reviewer-prompt-single-source.test.ts | 160 +++++++++++++++ packages/engine/src/__tests__/reviewer.test.ts | 97 ++++----- packages/engine/src/prompt-layers.ts | 2 +- packages/engine/src/reviewer.ts | 217 ++------------------- 10 files changed, 370 insertions(+), 271 deletions(-) Fusion-Task-Id: FN-6235 Fusion-Task-Lineage: 725932d1-2469-4507-a0f9-0946c83a1572
93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
/**
|
|
* Structured system prompt layers for cross-session caching.
|
|
*
|
|
* The `stable` layer contains content that is identical across sessions of
|
|
* the same role (base role prompt). The `dynamic` layer holds per-session
|
|
* content (agent instructions, memory, performance feedback, plugins).
|
|
*
|
|
* When the stable layer is byte-identical across consecutive API calls,
|
|
* Anthropic's prompt cache gives a 90% read discount. OpenAI caches
|
|
* matching prefixes automatically at 50% discount.
|
|
*/
|
|
export interface SystemPromptLayers {
|
|
/** Role-specific base prompt — identical across all sessions of this role. */
|
|
stable: string;
|
|
/** Per-session content: agent instructions, memory, feedback, plugins. */
|
|
dynamic: string;
|
|
}
|
|
|
|
export interface PromptLayerInput {
|
|
/** The base role system prompt (for reviewer, the workflow IR review seam prompt). */
|
|
basePrompt: string;
|
|
/** Resolved agent instructions (instructionsText + instructionsPath + soul). */
|
|
agentInstructions?: string;
|
|
/** Formatted memory section (agent memory + workspace memory). */
|
|
memorySection?: string;
|
|
/** Formatted active-goals context section. */
|
|
goalContext?: string;
|
|
/** Formatted plugin prompt contributions. */
|
|
pluginContributions?: string;
|
|
/** Formatted performance feedback section. */
|
|
performanceFeedback?: string;
|
|
}
|
|
|
|
/**
|
|
* Build structured prompt layers from the components that currently get
|
|
* concatenated into a single system prompt string.
|
|
*
|
|
* The stable layer is ONLY the base role prompt. Everything else goes into
|
|
* the dynamic layer so that the stable prefix is byte-identical across
|
|
* sessions of the same role, enabling cross-session prompt caching.
|
|
*/
|
|
export function buildPromptLayers(input: PromptLayerInput): SystemPromptLayers {
|
|
const { basePrompt, agentInstructions, memorySection, goalContext, pluginContributions, performanceFeedback } = input;
|
|
|
|
const dynamicParts: string[] = [];
|
|
|
|
// 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 trimmedGoalContext = goalContext?.trim() ?? "";
|
|
if (trimmedGoalContext) {
|
|
dynamicParts.push(trimmedGoalContext);
|
|
}
|
|
|
|
const trimmedInstructions = agentInstructions?.trim() ?? "";
|
|
if (trimmedInstructions) {
|
|
dynamicParts.push(`## Custom Instructions\n\n${trimmedInstructions}`);
|
|
}
|
|
|
|
const trimmedPlugins = pluginContributions?.trim() ?? "";
|
|
if (trimmedPlugins) {
|
|
dynamicParts.push(trimmedPlugins);
|
|
}
|
|
|
|
const trimmedFeedback = performanceFeedback?.trim() ?? "";
|
|
if (trimmedFeedback) {
|
|
dynamicParts.push(trimmedFeedback);
|
|
}
|
|
|
|
return {
|
|
stable: basePrompt,
|
|
dynamic: dynamicParts.join("\n\n"),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Collapse layers back into a single string for backward compatibility.
|
|
* Runtimes that don't support structured caching use this to get the same
|
|
* concatenated prompt as before.
|
|
*/
|
|
export function collapsePromptLayers(layers: SystemPromptLayers): string {
|
|
if (!layers.dynamic) {
|
|
return layers.stable;
|
|
}
|
|
return `${layers.stable}\n\n${layers.dynamic}`;
|
|
}
|