feat(engine): add systemPromptLayers to AgentRuntimeOptions and wire into DefaultResourceLoader
Extends AgentRuntimeOptions and AgentOptions with an optional systemPromptLayers field (SystemPromptLayers) for cross-session prompt caching. DefaultResourceLoader now uses the stable layer as systemPromptOverride and the dynamic layer as appendSystemPromptOverride when layers are provided, falling back to the flat systemPrompt string for backward compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
31
packages/engine/src/__tests__/agent-runtime-layers.test.ts
Normal file
31
packages/engine/src/__tests__/agent-runtime-layers.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import type { AgentRuntimeOptions } from "../agent-runtime.js";
|
||||
import type { SystemPromptLayers } from "../prompt-layers.js";
|
||||
|
||||
describe("AgentRuntimeOptions.systemPromptLayers", () => {
|
||||
it("accepts systemPromptLayers alongside systemPrompt", () => {
|
||||
const layers: SystemPromptLayers = {
|
||||
stable: "You are a reviewer.",
|
||||
dynamic: "Check for bugs.",
|
||||
};
|
||||
|
||||
const options: AgentRuntimeOptions = {
|
||||
cwd: "/tmp/test",
|
||||
systemPrompt: "You are a reviewer.\n\nCheck for bugs.",
|
||||
systemPromptLayers: layers,
|
||||
};
|
||||
|
||||
expect(options.systemPromptLayers).toBeDefined();
|
||||
expect(options.systemPromptLayers!.stable).toBe("You are a reviewer.");
|
||||
expect(options.systemPromptLayers!.dynamic).toBe("Check for bugs.");
|
||||
});
|
||||
|
||||
it("works without systemPromptLayers (backward compatible)", () => {
|
||||
const options: AgentRuntimeOptions = {
|
||||
cwd: "/tmp/test",
|
||||
systemPrompt: "You are a reviewer.",
|
||||
};
|
||||
|
||||
expect(options.systemPromptLayers).toBeUndefined();
|
||||
});
|
||||
});
|
||||
53
packages/engine/src/__tests__/pi-prompt-layers.test.ts
Normal file
53
packages/engine/src/__tests__/pi-prompt-layers.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
describe("createFnAgent prompt layer configuration", () => {
|
||||
it("uses stable layer as systemPromptOverride when layers provided", () => {
|
||||
const options = {
|
||||
systemPrompt: "Stable.\n\nDynamic.",
|
||||
systemPromptLayers: { stable: "Stable.", dynamic: "Dynamic." },
|
||||
};
|
||||
|
||||
const systemPromptOverride =
|
||||
options.systemPromptLayers?.stable ?? options.systemPrompt;
|
||||
const appendSystemPromptOverride = options.systemPromptLayers?.dynamic
|
||||
? [options.systemPromptLayers.dynamic]
|
||||
: [];
|
||||
|
||||
expect(systemPromptOverride).toBe("Stable.");
|
||||
expect(appendSystemPromptOverride).toEqual(["Dynamic."]);
|
||||
});
|
||||
|
||||
it("falls back to full systemPrompt when layers not provided", () => {
|
||||
const options = {
|
||||
systemPrompt: "Full prompt.",
|
||||
systemPromptLayers: undefined as
|
||||
| { stable: string; dynamic: string }
|
||||
| undefined,
|
||||
};
|
||||
|
||||
const systemPromptOverride =
|
||||
options.systemPromptLayers?.stable ?? options.systemPrompt;
|
||||
const appendSystemPromptOverride = options.systemPromptLayers?.dynamic
|
||||
? [options.systemPromptLayers.dynamic]
|
||||
: [];
|
||||
|
||||
expect(systemPromptOverride).toBe("Full prompt.");
|
||||
expect(appendSystemPromptOverride).toEqual([]);
|
||||
});
|
||||
|
||||
it("handles empty dynamic layer", () => {
|
||||
const options = {
|
||||
systemPrompt: "Stable.",
|
||||
systemPromptLayers: { stable: "Stable.", dynamic: "" },
|
||||
};
|
||||
|
||||
const systemPromptOverride =
|
||||
options.systemPromptLayers?.stable ?? options.systemPrompt;
|
||||
const appendSystemPromptOverride = options.systemPromptLayers?.dynamic
|
||||
? [options.systemPromptLayers.dynamic]
|
||||
: [];
|
||||
|
||||
expect(systemPromptOverride).toBe("Stable.");
|
||||
expect(appendSystemPromptOverride).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,7 @@ import type { PermanentAgentGatingContext } from "@fusion/core";
|
||||
import type { SkillSelectionContext } from "./skill-resolver.js";
|
||||
import type { FallbackModelUsedPayload } from "./pi.js";
|
||||
import type { AgentActionGateContext } from "./agent-action-gate.js";
|
||||
import type { SystemPromptLayers } from "./prompt-layers.js";
|
||||
|
||||
/**
|
||||
* Options for creating an agent session.
|
||||
@@ -36,6 +37,17 @@ export interface AgentRuntimeOptions {
|
||||
cwd: string;
|
||||
/** System prompt for the agent */
|
||||
systemPrompt: string;
|
||||
/**
|
||||
* Optional structured prompt layers for cross-session caching.
|
||||
* When present, runtimes that support prompt caching use the `stable`
|
||||
* layer as a cacheable prefix and the `dynamic` layer as the per-session
|
||||
* suffix. Runtimes that don't support caching ignore this and use
|
||||
* `systemPrompt` (the collapsed string) instead.
|
||||
*
|
||||
* Callers MUST also provide `systemPrompt` as the collapsed equivalent
|
||||
* for backward compatibility.
|
||||
*/
|
||||
systemPromptLayers?: SystemPromptLayers;
|
||||
/** Tool set to use: "coding" for full tools, "readonly" for read-only access */
|
||||
tools?: "coding" | "readonly";
|
||||
/** Additional custom tools to merge with the base toolset */
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
type AgentActionGateContext,
|
||||
} from "./agent-action-gate.js";
|
||||
import { resolvePermanentAgentToolDecision } from "./permanent-agent-gating.js";
|
||||
import type { SystemPromptLayers } from "./prompt-layers.js";
|
||||
|
||||
export interface AgentResult {
|
||||
session: AgentSession;
|
||||
@@ -711,6 +712,11 @@ export type BuiltinWebToolName = "WebSearch" | "WebFetch";
|
||||
export interface AgentOptions {
|
||||
cwd: string;
|
||||
systemPrompt: string;
|
||||
/** Structured prompt layers for cross-session caching. When provided,
|
||||
* the stable layer is used as systemPromptOverride and the dynamic
|
||||
* layer as appendSystemPromptOverride. Falls back to systemPrompt
|
||||
* when not provided. */
|
||||
systemPromptLayers?: SystemPromptLayers;
|
||||
tools?: "coding" | "readonly";
|
||||
customTools?: ToolDefinition[];
|
||||
/** Optional allowlist of builtin runtime web tools to keep enabled. */
|
||||
@@ -1715,8 +1721,11 @@ export async function createFnAgent(options: AgentOptions): Promise<AgentResult>
|
||||
cwd: resolvedProjectRoot,
|
||||
agentDir: getFusionAgentDir(),
|
||||
settingsManager,
|
||||
systemPromptOverride: () => options.systemPrompt,
|
||||
appendSystemPromptOverride: () => [],
|
||||
systemPromptOverride: () => options.systemPromptLayers?.stable ?? options.systemPrompt,
|
||||
appendSystemPromptOverride: () =>
|
||||
options.systemPromptLayers?.dynamic
|
||||
? [options.systemPromptLayers.dynamic]
|
||||
: [],
|
||||
...(effectiveExtensionPaths.length > 0 ? { additionalExtensionPaths: [...effectiveExtensionPaths] } : {}),
|
||||
...(skillsOverrideFn ? { skillsOverride: skillsOverrideFn } : {}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user