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
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 (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}`;
|
|
}
|