refactor: centralize bootstrap system prompt assembly

Centralize embedded attempt system prompt assembly so override and default prompts share bootstrap Project Context handling and provider transforms. Make bootstrap context routing explicit: full bootstrap can enter system Project Context, while runtime/user-message context remains disabled.
This commit is contained in:
Peter Steinberger
2026-05-04 02:02:40 +01:00
committed by GitHub
parent d5ecee2cf3
commit 786fdeb366
8 changed files with 298 additions and 117 deletions

View File

@@ -15,7 +15,8 @@ export type AttemptBootstrapRoutingInput = {
export type AttemptBootstrapRouting = {
bootstrapMode: BootstrapMode;
shouldStripBootstrapFromContext: boolean;
includeBootstrapInSystemContext: boolean;
includeBootstrapInRuntimeContext: boolean;
};
export type AttemptWorkspaceBootstrapRoutingInput = Omit<
@@ -25,10 +26,16 @@ export type AttemptWorkspaceBootstrapRoutingInput = Omit<
isWorkspaceBootstrapPending: (workspaceDir: string) => Promise<boolean>;
};
export function shouldStripBootstrapFromEmbeddedContext(_params: {
export function resolveBootstrapContextTargets(params: {
bootstrapMode: BootstrapMode;
}): boolean {
return _params.bootstrapMode !== "full";
}): Pick<
AttemptBootstrapRouting,
"includeBootstrapInSystemContext" | "includeBootstrapInRuntimeContext"
> {
return {
includeBootstrapInSystemContext: params.bootstrapMode === "full",
includeBootstrapInRuntimeContext: false,
};
}
function resolveAttemptBootstrapRouting(
@@ -47,9 +54,7 @@ function resolveAttemptBootstrapRouting(
return {
bootstrapMode,
shouldStripBootstrapFromContext: shouldStripBootstrapFromEmbeddedContext({
bootstrapMode,
}),
...resolveBootstrapContextTargets({ bootstrapMode }),
};
}

View File

@@ -0,0 +1,90 @@
import { describe, expect, it } from "vitest";
import { buildAttemptSystemPrompt } from "./attempt-system-prompt.js";
const baseProviderTransform = {
provider: "openai",
workspaceDir: "/tmp/openclaw",
context: {
provider: "openai",
modelId: "gpt-5.5",
promptMode: "full" as const,
},
};
describe("buildAttemptSystemPrompt", () => {
it("preserves bootstrap Project Context when a system prompt override is configured", () => {
const result = buildAttemptSystemPrompt({
isRawModelRun: false,
systemPromptOverrideText: "Custom override prompt.",
embeddedSystemPrompt: {
workspaceDir: "/tmp/openclaw",
reasoningTagHint: false,
runtimeInfo: {
host: "test-host",
os: "Darwin",
arch: "arm64",
node: "v22.0.0",
model: "openai/gpt-5.5",
},
tools: [],
modelAliasLines: [],
userTimezone: "UTC",
bootstrapMode: "full",
bootstrapTruncationNotice: "Bootstrap context was truncated.",
contextFiles: [
{
path: "/tmp/openclaw/BOOTSTRAP.md",
content: "Reply with BOOTSTRAP_OK.",
},
{
path: "/tmp/openclaw/USER.md",
content: "User profile should stay in normal prompt context only.",
},
],
},
providerTransform: baseProviderTransform,
});
expect(result.systemPrompt).toContain("Custom override prompt.");
expect(result.systemPrompt).toContain("## Bootstrap Pending");
expect(result.systemPrompt).toContain("BOOTSTRAP.md is included below in Project Context");
expect(result.systemPrompt).toContain("## Bootstrap Context Notice");
expect(result.systemPrompt).toContain("Bootstrap context was truncated.");
expect(result.systemPrompt).toContain("# Project Context");
expect(result.systemPrompt).toContain("## /tmp/openclaw/BOOTSTRAP.md");
expect(result.systemPrompt).toContain("Reply with BOOTSTRAP_OK.");
expect(result.systemPrompt).not.toContain("USER.md");
});
it("omits system prompts for raw model probes", () => {
const result = buildAttemptSystemPrompt({
isRawModelRun: true,
embeddedSystemPrompt: {
workspaceDir: "/tmp/openclaw",
reasoningTagHint: false,
runtimeInfo: {
host: "test-host",
os: "Darwin",
arch: "arm64",
node: "v22.0.0",
model: "openai/gpt-5.5",
},
tools: [],
modelAliasLines: [],
userTimezone: "UTC",
bootstrapMode: "full",
contextFiles: [
{
path: "/tmp/openclaw/BOOTSTRAP.md",
content: "Reply with BOOTSTRAP_OK.",
},
],
},
providerTransform: baseProviderTransform,
});
expect(result.baseSystemPrompt).toContain("BOOTSTRAP.md is included below in Project Context");
expect(result.systemPrompt).toBe("");
expect(result.systemPromptOverride()).toBe("");
});
});

View File

@@ -0,0 +1,56 @@
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { transformProviderSystemPrompt } from "../../../plugins/provider-runtime.js";
import type { ProviderTransformSystemPromptContext } from "../../../plugins/types.js";
import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js";
import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js";
type EmbeddedSystemPromptParams = Parameters<typeof buildEmbeddedSystemPrompt>[0];
export type BuildAttemptSystemPromptParams = {
isRawModelRun: boolean;
systemPromptOverrideText?: string;
embeddedSystemPrompt: EmbeddedSystemPromptParams;
providerTransform: {
provider: string;
config?: OpenClawConfig;
workspaceDir: string;
context: Omit<ProviderTransformSystemPromptContext, "systemPrompt">;
};
};
export type AttemptSystemPrompt = {
baseSystemPrompt: string;
systemPrompt: string;
systemPromptOverride: (defaultPrompt?: string) => string;
};
export function buildAttemptSystemPrompt(
params: BuildAttemptSystemPromptParams,
): AttemptSystemPrompt {
const baseSystemPrompt = params.systemPromptOverrideText
? appendAgentBootstrapSystemPromptSupplement({
systemPrompt: params.systemPromptOverrideText,
bootstrapMode: params.embeddedSystemPrompt.bootstrapMode,
bootstrapTruncationNotice: params.embeddedSystemPrompt.bootstrapTruncationNotice,
contextFiles: params.embeddedSystemPrompt.contextFiles,
})
: buildEmbeddedSystemPrompt(params.embeddedSystemPrompt);
const systemPrompt = params.isRawModelRun
? ""
: transformProviderSystemPrompt({
provider: params.providerTransform.provider,
config: params.providerTransform.config,
workspaceDir: params.providerTransform.workspaceDir,
context: {
...params.providerTransform.context,
systemPrompt: baseSystemPrompt,
},
});
return {
baseSystemPrompt,
systemPrompt,
systemPromptOverride: createSystemPromptOverride(systemPrompt),
};
}

View File

@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import {
resolveBootstrapContextTargets,
resolveAttemptWorkspaceBootstrapRouting,
shouldStripBootstrapFromEmbeddedContext,
} from "./attempt-bootstrap-routing.js";
describe("runEmbeddedAttempt bootstrap routing", () => {
@@ -26,7 +26,8 @@ describe("runEmbeddedAttempt bootstrap routing", () => {
expect(isWorkspaceBootstrapPending).toHaveBeenCalledWith(canonicalWorkspace);
expect(isWorkspaceBootstrapPending).not.toHaveBeenCalledWith(sandboxWorkspace);
expect(routing.bootstrapMode).toBe("none");
expect(routing.shouldStripBootstrapFromContext).toBe(true);
expect(routing.includeBootstrapInSystemContext).toBe(false);
expect(routing.includeBootstrapInRuntimeContext).toBe(false);
});
it("falls back to limited bootstrap wording when a primary run cannot read files", async () => {
@@ -41,15 +42,25 @@ describe("runEmbeddedAttempt bootstrap routing", () => {
});
expect(routing.bootstrapMode).toBe("limited");
expect(routing.shouldStripBootstrapFromContext).toBe(true);
expect(routing.includeBootstrapInSystemContext).toBe(false);
expect(routing.includeBootstrapInRuntimeContext).toBe(false);
});
it("keeps BOOTSTRAP.md in Project Context for full bootstrap turns", () => {
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "full" })).toBe(false);
expect(resolveBootstrapContextTargets({ bootstrapMode: "full" })).toEqual({
includeBootstrapInSystemContext: true,
includeBootstrapInRuntimeContext: false,
});
});
it("strips BOOTSTRAP.md from Project Context outside full bootstrap turns", () => {
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "limited" })).toBe(true);
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "none" })).toBe(true);
it("excludes BOOTSTRAP.md from every context outside full bootstrap turns", () => {
expect(resolveBootstrapContextTargets({ bootstrapMode: "limited" })).toEqual({
includeBootstrapInSystemContext: false,
includeBootstrapInRuntimeContext: false,
});
expect(resolveBootstrapContextTargets({ bootstrapMode: "none" })).toEqual({
includeBootstrapInSystemContext: false,
includeBootstrapInRuntimeContext: false,
});
});
});

View File

@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../../system-prompt-cache-boundary.js";
import { buildAgentSystemPrompt } from "../../system-prompt.js";
import { resolveBootstrapContextTargets } from "./attempt-bootstrap-routing.js";
import {
buildContextEnginePromptCacheInfo,
buildAfterTurnRuntimeContext,
@@ -24,7 +25,6 @@ import {
resolveAttemptToolPolicyMessageProvider,
resolvePromptBuildHookResult,
resolvePromptModeForSession,
shouldStripBootstrapFromEmbeddedContext,
shouldWarnOnOrphanedUserRepair,
wrapStreamFnRepairMalformedToolCallArguments,
wrapStreamFnSanitizeMalformedToolCalls,
@@ -404,11 +404,20 @@ describe("resolvePromptModeForSession", () => {
});
});
describe("shouldStripBootstrapFromEmbeddedContext", () => {
describe("resolveBootstrapContextTargets", () => {
it("keeps BOOTSTRAP.md in system Project Context only for full bootstrap turns", () => {
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "full" })).toBe(false);
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "limited" })).toBe(true);
expect(shouldStripBootstrapFromEmbeddedContext({ bootstrapMode: "none" })).toBe(true);
expect(resolveBootstrapContextTargets({ bootstrapMode: "full" })).toEqual({
includeBootstrapInSystemContext: true,
includeBootstrapInRuntimeContext: false,
});
expect(resolveBootstrapContextTargets({ bootstrapMode: "limited" })).toEqual({
includeBootstrapInSystemContext: false,
includeBootstrapInRuntimeContext: false,
});
expect(resolveBootstrapContextTargets({ bootstrapMode: "none" })).toEqual({
includeBootstrapInSystemContext: false,
includeBootstrapInRuntimeContext: false,
});
});
});

View File

@@ -32,7 +32,6 @@ import {
import {
resolveProviderSystemPromptContribution,
resolveProviderTextTransforms,
transformProviderSystemPrompt,
} from "../../../plugins/provider-runtime.js";
import { getPluginToolMeta } from "../../../plugins/tools.js";
import { isAcpSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js";
@@ -155,7 +154,6 @@ import {
import { resolveSystemPromptOverride } from "../../system-prompt-override.js";
import { buildSystemPromptParams } from "../../system-prompt-params.js";
import { buildSystemPromptReport } from "../../system-prompt-report.js";
import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js";
import { resolveAgentTimeoutMs } from "../../timeout.js";
import {
buildEmptyExplicitToolAllowlistError,
@@ -213,11 +211,7 @@ import {
resolveEmbeddedAgentBaseStreamFn,
resolveEmbeddedAgentStreamFn,
} from "../stream-resolution.js";
import {
applySystemPromptOverrideToSession,
buildEmbeddedSystemPrompt,
createSystemPromptOverride,
} from "../system-prompt.js";
import { applySystemPromptOverrideToSession } from "../system-prompt.js";
import { dropReasoningFromHistory, dropThinkingBlocks } from "../thinking.js";
import {
collectAllowedToolNames,
@@ -239,21 +233,18 @@ import { flushPendingToolResultsAfterIdle } from "../wait-for-idle-before-flush.
import { abortable as abortableWithSignal } from "./abortable.js";
import { createEmbeddedAgentSessionWithResourceLoader } from "./attempt-session.js";
export { buildContextEnginePromptCacheInfo } from "./attempt.context-engine-helpers.js";
import {
resolveAttemptWorkspaceBootstrapRouting,
shouldStripBootstrapFromEmbeddedContext,
} from "./attempt-bootstrap-routing.js";
export { shouldStripBootstrapFromEmbeddedContext } from "./attempt-bootstrap-routing.js";
import {
rotateTranscriptAfterCompaction,
shouldRotateCompactionTranscript,
} from "../compaction-successor-transcript.js";
import { resolveAttemptWorkspaceBootstrapRouting } from "./attempt-bootstrap-routing.js";
import { configureEmbeddedAttemptHttpRuntime } from "./attempt-http-runtime.js";
import {
createEmbeddedRunStageTracker,
formatEmbeddedRunStageSummary,
shouldWarnEmbeddedRunStageSummary,
} from "./attempt-stage-timing.js";
import { buildAttemptSystemPrompt } from "./attempt-system-prompt.js";
import {
assembleAttemptContextEngine,
buildLoopPromptCacheInfo,
@@ -958,7 +949,6 @@ export async function runEmbeddedAttempt(
hasBootstrapFileAccess: bootstrapHasFileAccess,
});
const bootstrapMode = bootstrapRouting.bootstrapMode;
const shouldStripBootstrapFromContext = bootstrapRouting.shouldStripBootstrapFromContext;
const {
bootstrapFiles: hookAdjustedBootstrapFiles,
contextFiles: resolvedContextFiles,
@@ -993,12 +983,12 @@ export async function runEmbeddedAttempt(
sourceWorkspaceDir: resolvedWorkspace,
targetWorkspaceDir: effectiveWorkspace,
});
const contextFiles = shouldStripBootstrapFromContext
? remappedContextFiles.filter((file) => !/(^|[\\/])BOOTSTRAP\.md$/iu.test(file.path.trim()))
: remappedContextFiles;
const bootstrapFilesForInjectionStats = shouldStripBootstrapFromContext
? hookAdjustedBootstrapFiles.filter((file) => file.name !== DEFAULT_BOOTSTRAP_FILENAME)
: hookAdjustedBootstrapFiles;
const contextFiles = bootstrapRouting.includeBootstrapInSystemContext
? remappedContextFiles
: remappedContextFiles.filter((file) => !/(^|[\\/])BOOTSTRAP\.md$/iu.test(file.path.trim()));
const bootstrapFilesForInjectionStats = bootstrapRouting.includeBootstrapInSystemContext
? hookAdjustedBootstrapFiles
: hookAdjustedBootstrapFiles.filter((file) => file.name !== DEFAULT_BOOTSTRAP_FILENAME);
const bootstrapMaxChars = resolveBootstrapMaxChars(params.config);
const bootstrapTotalMaxChars = resolveBootstrapTotalMaxChars(params.config);
const bootstrapAnalysis = analyzeBootstrapBudget({
@@ -1291,71 +1281,66 @@ export async function runEmbeddedAttempt(
config: params.config,
agentId: sessionAgentId,
});
const builtAppendPrompt = systemPromptOverrideText
? appendAgentBootstrapSystemPromptSupplement({
systemPrompt: systemPromptOverrideText,
bootstrapMode,
bootstrapTruncationNotice,
contextFiles,
})
: buildEmbeddedSystemPrompt({
workspaceDir: effectiveWorkspace,
defaultThinkLevel: params.thinkLevel,
reasoningLevel: params.reasoningLevel ?? "off",
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
ownerDisplay: ownerDisplay.ownerDisplay,
ownerDisplaySecret: ownerDisplay.ownerDisplaySecret,
reasoningTagHint,
heartbeatPrompt,
skillsPrompt: effectiveSkillsPrompt,
docsPath: openClawReferences.docsPath ?? undefined,
sourcePath: openClawReferences.sourcePath ?? undefined,
ttsHint,
workspaceNotes: workspaceNotes?.length ? workspaceNotes : undefined,
reactionGuidance,
promptMode: effectivePromptMode,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
silentReplyPromptMode: params.silentReplyPromptMode,
acpEnabled: isAcpRuntimeSpawnAvailable({
config: params.config,
sandboxed: sandboxInfo?.enabled === true,
}),
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
runtimeInfo,
messageToolHints,
sandboxInfo,
tools: effectiveTools,
modelAliasLines: buildModelAliasLines(params.config),
userTimezone,
userTime,
userTimeFormat,
contextFiles,
bootstrapMode,
bootstrapTruncationNotice,
includeMemorySection: !activeContextEngine || activeContextEngine.info.id === "legacy",
memoryCitationsMode: params.config?.memory?.citations,
promptContribution,
});
const appendPrompt = isRawModelRun
? ""
: transformProviderSystemPrompt({
provider: params.provider,
const attemptSystemPrompt = buildAttemptSystemPrompt({
isRawModelRun,
systemPromptOverrideText,
embeddedSystemPrompt: {
workspaceDir: effectiveWorkspace,
defaultThinkLevel: params.thinkLevel,
reasoningLevel: params.reasoningLevel ?? "off",
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
ownerDisplay: ownerDisplay.ownerDisplay,
ownerDisplaySecret: ownerDisplay.ownerDisplaySecret,
reasoningTagHint,
heartbeatPrompt,
skillsPrompt: effectiveSkillsPrompt,
docsPath: openClawReferences.docsPath ?? undefined,
sourcePath: openClawReferences.sourcePath ?? undefined,
ttsHint,
workspaceNotes: workspaceNotes?.length ? workspaceNotes : undefined,
reactionGuidance,
promptMode: effectivePromptMode,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
silentReplyPromptMode: params.silentReplyPromptMode,
acpEnabled: isAcpRuntimeSpawnAvailable({
config: params.config,
sandboxed: sandboxInfo?.enabled === true,
}),
nativeCommandGuidanceLines: listRegisteredPluginAgentPromptGuidance(),
runtimeInfo,
messageToolHints,
sandboxInfo,
tools: effectiveTools,
modelAliasLines: buildModelAliasLines(params.config),
userTimezone,
userTime,
userTimeFormat,
contextFiles,
bootstrapMode,
bootstrapTruncationNotice,
includeMemorySection: !activeContextEngine || activeContextEngine.info.id === "legacy",
memoryCitationsMode: params.config?.memory?.citations,
promptContribution,
},
providerTransform: {
provider: params.provider,
config: params.config,
workspaceDir: effectiveWorkspace,
context: {
config: params.config,
agentDir: params.agentDir,
workspaceDir: effectiveWorkspace,
context: {
config: params.config,
agentDir: params.agentDir,
workspaceDir: effectiveWorkspace,
provider: params.provider,
modelId: params.modelId,
promptMode: effectivePromptMode,
runtimeChannel,
runtimeCapabilities,
agentId: sessionAgentId,
systemPrompt: builtAppendPrompt,
},
});
provider: params.provider,
modelId: params.modelId,
promptMode: effectivePromptMode,
runtimeChannel,
runtimeCapabilities,
agentId: sessionAgentId,
},
},
});
const appendPrompt = attemptSystemPrompt.systemPrompt;
const systemPromptReport = buildSystemPromptReport({
source: "run",
generatedAt: Date.now(),
@@ -1384,7 +1369,7 @@ export async function runEmbeddedAttempt(
skillsPrompt,
tools: effectiveTools,
});
const systemPromptOverride = createSystemPromptOverride(appendPrompt);
const systemPromptOverride = attemptSystemPrompt.systemPromptOverride;
let systemPromptText = systemPromptOverride();
prepStages.mark("system-prompt");

View File

@@ -6,6 +6,7 @@ import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./system-prompt-cache-boundary.js"
import {
appendAgentBootstrapSystemPromptSupplement,
buildAgentBootstrapSystemContext,
buildAgentBootstrapSystemPromptSections,
buildAgentBootstrapSystemPromptSupplement,
buildAgentSystemPrompt,
buildRuntimeLine,
@@ -1111,6 +1112,22 @@ describe("buildAgentBootstrapSystemContext", () => {
});
describe("buildAgentBootstrapSystemPromptSupplement", () => {
it("can render bootstrap guidance without duplicating Project Context", () => {
const sections = buildAgentBootstrapSystemPromptSections({
bootstrapMode: "full",
bootstrapTruncationNotice: "Bootstrap context was truncated.",
contextFiles: [{ path: "/tmp/openclaw/BOOTSTRAP.md", content: "Ask who I am." }],
includeProjectContext: false,
}).join("\n");
expect(sections).toContain("## Bootstrap Pending");
expect(sections).toContain("BOOTSTRAP.md is included below in Project Context");
expect(sections).toContain("## Bootstrap Context Notice");
expect(sections).toContain("Bootstrap context was truncated.");
expect(sections).not.toContain("## /tmp/openclaw/BOOTSTRAP.md");
expect(sections).not.toContain("Ask who I am.");
});
it("adds pending bootstrap guidance and BOOTSTRAP.md contents for override prompts", () => {
const supplement = buildAgentBootstrapSystemPromptSupplement({
bootstrapMode: "full",

View File

@@ -264,6 +264,21 @@ export function buildAgentBootstrapSystemPromptSupplement(params: {
bootstrapTruncationNotice?: string;
contextFiles?: EmbeddedContextFile[];
}): string | undefined {
const supplement = buildAgentBootstrapSystemPromptSections({
...params,
includeProjectContext: true,
})
.join("\n")
.trim();
return supplement.length > 0 ? supplement : undefined;
}
export function buildAgentBootstrapSystemPromptSections(params: {
bootstrapMode?: BootstrapMode;
bootstrapTruncationNotice?: string;
contextFiles?: EmbeddedContextFile[];
includeProjectContext?: boolean;
}): string[] {
const bootstrapFiles =
params.bootstrapMode === "full"
? sortContextFilesForPrompt(params.contextFiles ?? []).filter((file) =>
@@ -280,7 +295,7 @@ export function buildAgentBootstrapSystemPromptSupplement(params: {
if (bootstrapTruncationNotice) {
lines.push("## Bootstrap Context Notice", bootstrapTruncationNotice, "");
}
if (bootstrapFiles.length > 0) {
if (params.includeProjectContext === true && bootstrapFiles.length > 0) {
lines.push(
...buildProjectContextSection({
files: bootstrapFiles,
@@ -289,8 +304,7 @@ export function buildAgentBootstrapSystemPromptSupplement(params: {
}),
);
}
const supplement = lines.join("\n").trim();
return supplement.length > 0 ? supplement : undefined;
return lines;
}
export function appendAgentBootstrapSystemPromptSupplement(params: {
@@ -819,14 +833,12 @@ export function buildAgentSystemPrompt(params: {
const orderedContextFiles = sortContextFilesForPrompt(validContextFiles);
const stableContextFiles = orderedContextFiles.filter((file) => !isDynamicContextFile(file.path));
const dynamicContextFiles = orderedContextFiles.filter((file) => isDynamicContextFile(file.path));
const hasBootstrapFileInProjectContext = orderedContextFiles.some((file) =>
isBootstrapContextFile(file.path),
);
const bootstrapSystemContext = buildAgentBootstrapSystemContext({
const bootstrapSystemPromptSections = buildAgentBootstrapSystemPromptSections({
bootstrapMode: params.bootstrapMode,
hasBootstrapFileInProjectContext,
bootstrapTruncationNotice: params.bootstrapTruncationNotice,
contextFiles: orderedContextFiles,
includeProjectContext: false,
});
const bootstrapTruncationNotice = params.bootstrapTruncationNotice?.trim();
const stablePrefixCacheKey = hashStablePromptInput({
workspaceDir: params.workspaceDir,
promptMode,
@@ -853,8 +865,7 @@ export function buildAgentSystemPrompt(params: {
workspaceGuidance,
workspaceNotes,
bootstrapMode: params.bootstrapMode,
bootstrapSystemContext,
bootstrapTruncationNotice,
bootstrapSystemPromptSections,
docsPath: params.docsPath,
sourcePath: params.sourcePath,
skillsPrompt,
@@ -1063,10 +1074,7 @@ export function buildAgentSystemPrompt(params: {
...buildTimeSection({
userTimezone,
}),
...bootstrapSystemContext,
bootstrapTruncationNotice ? "## Bootstrap Context Notice" : "",
bootstrapTruncationNotice ?? "",
bootstrapTruncationNotice ? "" : "",
...bootstrapSystemPromptSections,
"## Workspace Files (injected)",
"These user-editable files are loaded by OpenClaw and included below in Project Context.",
"",