Files
fusion/packages/engine/src/prompt-layers.ts
gsxdsm ff575f8b06 FN-5653: inject active goal context into heartbeat and executor prompts
Align heartbeat and executor prompt assembly so both lanes receive the same active-goal context block.

- inject goalContext into heartbeat and executor buildPromptLayers inputs using buildGoalContextSection
- extend goal-context regression coverage with parity tests for heartbeat/executor injection and empty-goal behavior
- update executor test helpers for goal-store access and document the shared prompt-lane behavior in architecture/agents docs

Files changed:
 docs/agents.md                                     |  1 +
 docs/architecture.md                               |  1 +
 packages/engine/src/__tests__/executor-test-helpers.ts  |  3 +
 packages/engine/src/__tests__/goal-context-injection.test.ts   | 72 ++++++++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             | 12 +++-
 packages/engine/src/executor.ts                    | 12 +++-
 packages/engine/src/prompt-layers.ts               |  9 ++-
 7 files changed, 107 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-5653
Fusion-Task-Lineage: d2670585-57f7-409d-bb11-cef3448267a6
2026-05-29 20:05:14 -07:00

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 (e.g. REVIEWER_SYSTEM_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}`;
}