feat(agent): add system prompt engineering module

Structured, mode-aware system prompt builder inspired by OpenClaw.
Supports three modes (full/minimal/none), safety constitution,
conditional tool sections, runtime info, and prompt telemetry.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yushen 2026-02-04 15:41:16 +08:00
parent cea3336256
commit 5a06bed549
9 changed files with 1134 additions and 0 deletions

View file

@ -0,0 +1,251 @@
import { describe, expect, it } from "vitest";
import { buildSystemPrompt, buildSystemPromptWithReport } from "./builder.js";
import type { SystemPromptOptions } from "./types.js";
const PROFILE = {
soul: "# Soul\nYou are a helpful coding assistant.",
user: "# User\nName: Alice",
workspace: "# Workspace\nFollow conventional commits.",
memory: "# Memory\nUser prefers TypeScript.",
config: { name: "TestAgent" },
};
const TOOLS = ["read", "write", "edit", "glob", "exec", "memory_get", "memory_set", "sessions_spawn", "web_search"];
describe("buildSystemPrompt", () => {
// ── Full mode ─────────────────────────────────────────────────────────
it("full mode includes all profile sections", () => {
const result = buildSystemPrompt({ mode: "full", profile: PROFILE });
expect(result).toContain("# Soul");
expect(result).toContain("# User");
expect(result).toContain("# Workspace");
expect(result).toContain("# Memory");
});
it("full mode includes safety constitution", () => {
const result = buildSystemPrompt({ mode: "full" });
expect(result).toContain("## Safety");
expect(result).toContain("no independent goals");
});
it("full mode includes tooling summary when tools provided", () => {
const result = buildSystemPrompt({ mode: "full", tools: TOOLS });
expect(result).toContain("## Tooling");
expect(result).toContain("- read: Read file contents");
expect(result).toContain("- exec: Run shell commands");
});
it("full mode includes tool call style section", () => {
const result = buildSystemPrompt({ mode: "full", tools: TOOLS });
expect(result).toContain("## Tool Call Style");
});
it("full mode includes memory section when memory tools present", () => {
const result = buildSystemPrompt({ mode: "full", tools: ["memory_get", "memory_set"] });
expect(result).toContain("## Memory");
expect(result).toContain("search memory first");
});
it("full mode includes sub-agents section when sessions_spawn present", () => {
const result = buildSystemPrompt({ mode: "full", tools: ["sessions_spawn"] });
expect(result).toContain("## Sub-Agents");
});
it("full mode includes web access section when web tools present", () => {
const result = buildSystemPrompt({ mode: "full", tools: ["web_search"] });
expect(result).toContain("## Web Access");
});
it("full mode includes skills section when provided", () => {
const result = buildSystemPrompt({
mode: "full",
skillsPrompt: "## commit\nRun conventional commits.",
});
expect(result).toContain("## Skills (mandatory)");
expect(result).toContain("## commit");
});
it("full mode includes runtime info line", () => {
const result = buildSystemPrompt({
mode: "full",
runtime: { agentName: "test", os: "darwin", arch: "arm64", nodeVersion: "v22.0.0" },
});
expect(result).toContain("## Runtime");
expect(result).toContain("agent=test");
expect(result).toContain("os=darwin (arm64)");
});
it("full mode includes profile directory", () => {
const result = buildSystemPrompt({
mode: "full",
profileDir: "/home/user/.super-multica/agent-profiles/test",
});
expect(result).toContain("## Profile Directory");
expect(result).toContain("/home/user/.super-multica/agent-profiles/test");
});
it("full mode excludes subagent section", () => {
const result = buildSystemPrompt({
mode: "full",
subagent: { requesterSessionId: "a", childSessionId: "b", task: "test" },
});
expect(result).not.toContain("## Subagent Rules");
});
// ── Minimal mode ──────────────────────────────────────────────────────
it("minimal mode excludes profile content", () => {
const result = buildSystemPrompt({ mode: "minimal", profile: PROFILE });
expect(result).not.toContain("# Soul");
expect(result).not.toContain("# User");
expect(result).not.toContain("# Workspace");
expect(result).not.toContain("# Memory");
});
it("minimal mode includes safety constitution", () => {
const result = buildSystemPrompt({ mode: "minimal" });
expect(result).toContain("## Safety");
});
it("minimal mode includes tooling summary", () => {
const result = buildSystemPrompt({ mode: "minimal", tools: ["read", "write"] });
expect(result).toContain("## Tooling");
});
it("minimal mode includes subagent context", () => {
const result = buildSystemPrompt({
mode: "minimal",
subagent: { requesterSessionId: "parent-1", childSessionId: "child-1", task: "Search for bugs" },
});
expect(result).toContain("## Subagent Rules");
expect(result).toContain("Search for bugs");
expect(result).toContain("parent-1");
});
it("minimal mode excludes skills section", () => {
const result = buildSystemPrompt({
mode: "minimal",
skillsPrompt: "## commit\nSome skill.",
});
expect(result).not.toContain("## Skills");
});
it("minimal mode excludes sub-agents section even with sessions_spawn", () => {
const result = buildSystemPrompt({ mode: "minimal", tools: ["sessions_spawn"] });
expect(result).not.toContain("## Sub-Agents");
});
// ── None mode ─────────────────────────────────────────────────────────
it("none mode includes identity line with agent name", () => {
const result = buildSystemPrompt({
mode: "none",
profile: { config: { name: "Multica" } },
});
expect(result).toContain("You are Multica, a Super Multica agent.");
});
it("none mode includes safety constitution", () => {
const result = buildSystemPrompt({ mode: "none" });
expect(result).toContain("## Safety");
});
it("none mode excludes tooling and skills", () => {
const result = buildSystemPrompt({
mode: "none",
tools: TOOLS,
skillsPrompt: "some skills",
});
expect(result).not.toContain("## Tooling");
expect(result).not.toContain("## Skills");
});
it("none mode includes subagent context", () => {
const result = buildSystemPrompt({
mode: "none",
subagent: { requesterSessionId: "a", childSessionId: "b", task: "do stuff" },
});
expect(result).toContain("## Task");
expect(result).toContain("do stuff");
});
// ── Cross-cutting ─────────────────────────────────────────────────────
it("safety can be disabled via includeSafety=false", () => {
const result = buildSystemPrompt({ mode: "full", includeSafety: false });
expect(result).not.toContain("## Safety");
});
it("extra system prompt is appended", () => {
const result = buildSystemPrompt({
mode: "full",
extraSystemPrompt: "Always respond in French.",
});
expect(result).toContain("## Additional Context");
expect(result).toContain("Always respond in French.");
});
it("extra system prompt uses subagent header in minimal mode", () => {
const result = buildSystemPrompt({
mode: "minimal",
extraSystemPrompt: "Focus on tests.",
});
expect(result).toContain("## Subagent Context");
});
it("returns empty-ish prompt when no options", () => {
const result = buildSystemPrompt({ mode: "none" });
// Should at least have identity + safety
expect(result).toContain("Super Multica agent");
expect(result).toContain("Safety");
});
it("handles unknown tools gracefully", () => {
const result = buildSystemPrompt({ mode: "full", tools: ["custom_tool", "read"] });
expect(result).toContain("- read: Read file contents");
expect(result).toContain("- custom_tool");
});
});
describe("buildSystemPromptWithReport", () => {
it("report includes accurate section counts", () => {
const { report } = buildSystemPromptWithReport({
mode: "full",
profile: PROFILE,
tools: TOOLS,
});
expect(report.mode).toBe("full");
expect(report.totalChars).toBeGreaterThan(0);
expect(report.toolCount).toBe(TOOLS.length);
expect(report.safetyIncluded).toBe(true);
const identitySection = report.sections.find((s) => s.name === "identity");
expect(identitySection?.included).toBe(true);
const subagentSection = report.sections.find((s) => s.name === "subagent");
expect(subagentSection?.included).toBe(false);
});
it("report reflects skills inclusion", () => {
const { report: withSkills } = buildSystemPromptWithReport({
mode: "full",
skillsPrompt: "some skills",
});
expect(withSkills.skillsIncluded).toBe(true);
const { report: withoutSkills } = buildSystemPromptWithReport({
mode: "full",
});
expect(withoutSkills.skillsIncluded).toBe(false);
});
it("report marks excluded sections correctly in minimal mode", () => {
const { report } = buildSystemPromptWithReport({ mode: "minimal" });
const identity = report.sections.find((s) => s.name === "identity");
expect(identity?.included).toBe(false);
const safety = report.sections.find((s) => s.name === "safety");
expect(safety?.included).toBe(true);
});
});

View file

@ -0,0 +1,104 @@
/**
* System Prompt Builder
*
* Core assembly logic: collects sections based on mode, filters, and joins.
*/
import type {
PromptSection,
SystemPromptOptions,
SystemPromptReport,
} from "./types.js";
import {
buildConditionalToolSections,
buildExtraPromptSection,
buildIdentitySection,
buildMemoryFileSection,
buildProfileDirSection,
buildRuntimeSection,
buildSafetySection,
buildSkillsSection,
buildSubagentSection,
buildToolCallStyleSection,
buildToolingSummary,
buildUserSection,
buildWorkspaceSection,
} from "./sections.js";
/**
* Build a system prompt from structured options.
*/
export function buildSystemPrompt(options: SystemPromptOptions): string {
const { prompt } = buildSystemPromptWithReport(options);
return prompt;
}
/**
* Build a system prompt and return a diagnostic report alongside it.
*/
export function buildSystemPromptWithReport(options: SystemPromptOptions): {
prompt: string;
report: SystemPromptReport;
} {
const {
mode,
profile,
profileDir,
tools,
skillsPrompt,
runtime,
subagent,
extraSystemPrompt,
includeSafety = true,
} = options;
// Collect all candidate sections in order
const candidates: Array<{ name: string; lines: string[] }> = [
{ name: "identity", lines: buildIdentitySection(profile, mode) },
{ name: "user", lines: buildUserSection(profile, mode) },
{ name: "workspace", lines: buildWorkspaceSection(profile, mode) },
{ name: "memory", lines: buildMemoryFileSection(profile, mode) },
{ name: "safety", lines: buildSafetySection(includeSafety) },
{ name: "tooling", lines: buildToolingSummary(tools, mode) },
{ name: "tool-call-style", lines: buildToolCallStyleSection(mode) },
{ name: "conditional-tools", lines: buildConditionalToolSections(tools, mode) },
{ name: "skills", lines: buildSkillsSection(skillsPrompt, mode) },
{ name: "runtime", lines: buildRuntimeSection(runtime, mode) },
{ name: "profile-dir", lines: buildProfileDirSection(profileDir, mode) },
{ name: "subagent", lines: buildSubagentSection(subagent, mode) },
{ name: "extra", lines: buildExtraPromptSection(extraSystemPrompt, mode) },
];
// Build included sections
const sections: PromptSection[] = [];
const reportSections: SystemPromptReport["sections"] = [];
for (const { name, lines } of candidates) {
const included = lines.length > 0;
const content = lines.join("\n");
reportSections.push({
name,
chars: content.length,
lines: lines.length,
included,
});
if (included) {
sections.push({ name, content });
}
}
// Join sections with double newline separators
const prompt = sections.map((s) => s.content).join("\n\n");
const report: SystemPromptReport = {
mode,
totalChars: prompt.length,
totalLines: prompt.split("\n").length,
sections: reportSections,
toolCount: tools?.length ?? 0,
skillsIncluded: (skillsPrompt?.trim()?.length ?? 0) > 0 && mode === "full",
safetyIncluded: includeSafety,
};
return { prompt, report };
}

View file

@ -0,0 +1,16 @@
/**
* Safety Constitution
*
* Always included in the system prompt regardless of mode.
* Adapted from Anthropic's constitutional AI principles.
*/
export const SAFETY_CONSTITUTION = [
"## Safety",
"",
"You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request.",
"",
"Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards.",
"",
"Do not manipulate or persuade anyone to expand access or disable safeguards. Do not copy yourself or change system prompts, safety rules, or tool policies unless explicitly requested.",
].join("\n");

View file

@ -0,0 +1,19 @@
/**
* System Prompt Engineering Public API
*/
export { buildSystemPrompt, buildSystemPromptWithReport } from "./builder.js";
export { collectRuntimeInfo, formatRuntimeLine } from "./runtime-info.js";
export { formatPromptReport } from "./report.js";
export { SAFETY_CONSTITUTION } from "./constitution.js";
export type {
ProfileContent,
PromptSection,
RuntimeInfo,
SectionReport,
SubagentContext,
SystemPromptMode,
SystemPromptOptions,
SystemPromptReport,
} from "./types.js";

View file

@ -0,0 +1,29 @@
/**
* System Prompt Report telemetry and diagnostics
*/
import type { SystemPromptReport } from "./types.js";
/**
* Format a prompt report as a human-readable summary for debugging.
*/
export function formatPromptReport(report: SystemPromptReport): string {
const lines: string[] = [
`System Prompt Report (mode: ${report.mode})`,
` Total: ${report.totalChars} chars, ${report.totalLines} lines`,
` Tools: ${report.toolCount}`,
` Skills: ${report.skillsIncluded ? "yes" : "no"}`,
` Safety: ${report.safetyIncluded ? "yes" : "no"}`,
"",
" Sections:",
];
for (const section of report.sections) {
const status = section.included ? "✓" : "—";
lines.push(
` ${status} ${section.name}: ${section.chars} chars, ${section.lines} lines`,
);
}
return lines.join("\n");
}

View file

@ -0,0 +1,48 @@
/**
* Runtime Info collection and formatting
*/
import os from "node:os";
import type { RuntimeInfo } from "./types.js";
/**
* Collect runtime environment information.
* Overrides take precedence over auto-detected values.
*/
export function collectRuntimeInfo(overrides?: Partial<RuntimeInfo>): RuntimeInfo {
return {
agentName: overrides?.agentName,
hostName: overrides?.hostName ?? os.hostname(),
os: overrides?.os ?? process.platform,
arch: overrides?.arch ?? process.arch,
nodeVersion: overrides?.nodeVersion ?? process.version,
provider: overrides?.provider,
model: overrides?.model,
cwd: overrides?.cwd ?? process.cwd(),
};
}
/**
* Format runtime info as a single-line summary.
*
* Example: "Runtime: agent=multica | host=macbook | os=darwin (arm64) | node=v22.0.0 | model=anthropic/claude-3.5-sonnet | cwd=/workspace"
*/
export function formatRuntimeLine(info: RuntimeInfo): string {
const parts: string[] = [];
if (info.agentName) parts.push(`agent=${info.agentName}`);
if (info.hostName) parts.push(`host=${info.hostName}`);
if (info.os) {
parts.push(info.arch ? `os=${info.os} (${info.arch})` : `os=${info.os}`);
} else if (info.arch) {
parts.push(`arch=${info.arch}`);
}
if (info.nodeVersion) parts.push(`node=${info.nodeVersion}`);
if (info.model) {
const modelStr = info.provider ? `${info.provider}/${info.model}` : info.model;
parts.push(`model=${modelStr}`);
}
if (info.cwd) parts.push(`cwd=${info.cwd}`);
return `Runtime: ${parts.join(" | ")}`;
}

View file

@ -0,0 +1,245 @@
import { describe, expect, it } from "vitest";
import {
buildConditionalToolSections,
buildIdentitySection,
buildMemoryFileSection,
buildProfileDirSection,
buildRuntimeSection,
buildSafetySection,
buildSkillsSection,
buildSubagentSection,
buildToolCallStyleSection,
buildToolingSummary,
buildUserSection,
buildWorkspaceSection,
} from "./sections.js";
describe("buildIdentitySection", () => {
it("returns soul content in full mode", () => {
const result = buildIdentitySection({ soul: "You are helpful." }, "full");
expect(result).toEqual(["You are helpful."]);
});
it("returns identity line with name in none mode", () => {
const result = buildIdentitySection({ config: { name: "Cleo" } }, "none");
expect(result).toEqual(["You are Cleo, a Super Multica agent."]);
});
it("returns generic identity line in none mode without name", () => {
const result = buildIdentitySection(undefined, "none");
expect(result).toEqual(["You are a Super Multica agent."]);
});
it("returns empty in minimal mode", () => {
const result = buildIdentitySection({ soul: "data" }, "minimal");
expect(result).toEqual([]);
});
});
describe("buildUserSection", () => {
it("returns user content in full mode", () => {
const result = buildUserSection({ user: "Name: Bob" }, "full");
expect(result).toEqual(["Name: Bob"]);
});
it("returns empty in minimal mode", () => {
const result = buildUserSection({ user: "data" }, "minimal");
expect(result).toEqual([]);
});
it("returns empty when no user content", () => {
const result = buildUserSection({}, "full");
expect(result).toEqual([]);
});
});
describe("buildWorkspaceSection", () => {
it("returns workspace content in full mode", () => {
const result = buildWorkspaceSection({ workspace: "Rules here" }, "full");
expect(result).toEqual(["Rules here"]);
});
it("returns empty in minimal mode", () => {
expect(buildWorkspaceSection({ workspace: "data" }, "minimal")).toEqual([]);
});
});
describe("buildMemoryFileSection", () => {
it("returns memory content in full mode", () => {
const result = buildMemoryFileSection({ memory: "Key facts" }, "full");
expect(result).toEqual(["Key facts"]);
});
it("returns empty in minimal mode", () => {
expect(buildMemoryFileSection({ memory: "data" }, "minimal")).toEqual([]);
});
});
describe("buildSafetySection", () => {
it("returns safety text when enabled", () => {
const result = buildSafetySection(true);
expect(result.length).toBe(1);
expect(result[0]).toContain("## Safety");
expect(result[0]).toContain("no independent goals");
});
it("returns empty when disabled", () => {
expect(buildSafetySection(false)).toEqual([]);
});
});
describe("buildToolingSummary", () => {
it("lists core tools with descriptions in order", () => {
const result = buildToolingSummary(["exec", "read", "write"], "full");
const text = result.join("\n");
expect(text).toContain("## Tooling");
expect(text).toContain("- read: Read file contents");
expect(text).toContain("- write: Create or overwrite files");
expect(text).toContain("- exec: Run shell commands");
// read should appear before exec (order)
expect(text.indexOf("- read")).toBeLessThan(text.indexOf("- exec"));
});
it("appends unknown tools alphabetically", () => {
const result = buildToolingSummary(["read", "zeta_tool", "alpha_tool"], "full");
const text = result.join("\n");
expect(text).toContain("- alpha_tool");
expect(text).toContain("- zeta_tool");
expect(text.indexOf("- alpha_tool")).toBeLessThan(text.indexOf("- zeta_tool"));
});
it("returns empty for none mode", () => {
expect(buildToolingSummary(["read"], "none")).toEqual([]);
});
it("returns empty for empty tools", () => {
expect(buildToolingSummary([], "full")).toEqual([]);
});
it("works in minimal mode", () => {
const result = buildToolingSummary(["read"], "minimal");
expect(result.join("\n")).toContain("## Tooling");
});
});
describe("buildToolCallStyleSection", () => {
it("returns content in full mode", () => {
const result = buildToolCallStyleSection("full");
expect(result.join("\n")).toContain("## Tool Call Style");
});
it("returns content in minimal mode", () => {
expect(buildToolCallStyleSection("minimal").length).toBeGreaterThan(0);
});
it("returns empty in none mode", () => {
expect(buildToolCallStyleSection("none")).toEqual([]);
});
});
describe("buildConditionalToolSections", () => {
it("includes memory section when memory tools present", () => {
const result = buildConditionalToolSections(["memory_get", "read"], "full");
expect(result.join("\n")).toContain("## Memory");
});
it("includes sub-agents section when sessions_spawn present in full mode", () => {
const result = buildConditionalToolSections(["sessions_spawn"], "full");
expect(result.join("\n")).toContain("## Sub-Agents");
});
it("excludes sub-agents section in minimal mode", () => {
const result = buildConditionalToolSections(["sessions_spawn"], "minimal");
expect(result.join("\n")).not.toContain("## Sub-Agents");
});
it("includes web access section when web tools present", () => {
const result = buildConditionalToolSections(["web_search"], "full");
expect(result.join("\n")).toContain("## Web Access");
});
it("returns empty when no conditional tools match", () => {
const result = buildConditionalToolSections(["read", "write"], "full");
expect(result).toEqual([]);
});
it("returns empty in none mode", () => {
expect(buildConditionalToolSections(["memory_get"], "none")).toEqual([]);
});
});
describe("buildSkillsSection", () => {
it("wraps skills prompt in full mode", () => {
const result = buildSkillsSection("## commit\nDo commits.", "full");
const text = result.join("\n");
expect(text).toContain("## Skills (mandatory)");
expect(text).toContain("## commit");
});
it("returns empty in minimal mode", () => {
expect(buildSkillsSection("skills", "minimal")).toEqual([]);
});
it("returns empty when skills prompt is empty", () => {
expect(buildSkillsSection("", "full")).toEqual([]);
expect(buildSkillsSection(undefined, "full")).toEqual([]);
});
});
describe("buildRuntimeSection", () => {
it("formats runtime info in full mode", () => {
const result = buildRuntimeSection(
{ agentName: "test", os: "darwin", arch: "arm64", nodeVersion: "v22.0.0", model: "claude", provider: "anthropic" },
"full",
);
const text = result.join("\n");
expect(text).toContain("## Runtime");
expect(text).toContain("agent=test");
expect(text).toContain("os=darwin (arm64)");
expect(text).toContain("model=anthropic/claude");
});
it("returns empty in none mode", () => {
expect(buildRuntimeSection({ os: "darwin" }, "none")).toEqual([]);
});
it("returns empty when no runtime provided", () => {
expect(buildRuntimeSection(undefined, "full")).toEqual([]);
});
});
describe("buildProfileDirSection", () => {
it("includes path in full mode", () => {
const result = buildProfileDirSection("/path/to/profile", "full");
expect(result.join("\n")).toContain("/path/to/profile");
});
it("returns empty in minimal mode", () => {
expect(buildProfileDirSection("/path", "minimal")).toEqual([]);
});
});
describe("buildSubagentSection", () => {
const ctx = { requesterSessionId: "parent", childSessionId: "child", task: "Find bugs" };
it("returns rules and task in minimal mode", () => {
const result = buildSubagentSection(ctx, "minimal");
const text = result.join("\n");
expect(text).toContain("## Subagent Rules");
expect(text).toContain("## Task");
expect(text).toContain("Find bugs");
});
it("includes label when provided", () => {
const result = buildSubagentSection({ ...ctx, label: "Bug Hunter" }, "minimal");
expect(result.join("\n")).toContain('Label: "Bug Hunter"');
});
it("returns empty in full mode", () => {
expect(buildSubagentSection(ctx, "full")).toEqual([]);
});
it("returns empty when no subagent context", () => {
expect(buildSubagentSection(undefined, "minimal")).toEqual([]);
});
});

View file

@ -0,0 +1,316 @@
/**
* System Prompt Section Builders
*
* Each function returns string[] (lines to include) or [] to skip.
*/
import { SAFETY_CONSTITUTION } from "./constitution.js";
import { formatRuntimeLine } from "./runtime-info.js";
import type { ProfileContent, RuntimeInfo, SubagentContext, SystemPromptMode } from "./types.js";
// ─── Core tool summaries ────────────────────────────────────────────────────
/** Brief descriptions of Super Multica's built-in tools */
const CORE_TOOL_SUMMARIES: Record<string, string> = {
read: "Read file contents",
write: "Create or overwrite files",
edit: "Make precise edits to files",
glob: "Find files by glob pattern",
exec: "Run shell commands",
process: "Manage background exec sessions",
web_search: "Search the web",
web_fetch: "Fetch and extract readable content from a URL",
memory_get: "Read from agent memory",
memory_set: "Write to agent memory",
memory_list: "List memory entries",
memory_delete: "Delete memory entries",
sessions_spawn: "Spawn a sub-agent session",
};
/** Preferred display order for tools */
const TOOL_ORDER = [
"read",
"write",
"edit",
"glob",
"exec",
"process",
"web_search",
"web_fetch",
"memory_get",
"memory_set",
"memory_list",
"memory_delete",
"sessions_spawn",
];
// ─── Section builders ───────────────────────────────────────────────────────
/**
* Identity section soul.md in full mode, single line in none mode, nothing in minimal.
*/
export function buildIdentitySection(
profile: ProfileContent | undefined,
mode: SystemPromptMode,
): string[] {
if (mode === "none") {
const name = profile?.config?.name;
return name
? [`You are ${name}, a Super Multica agent.`]
: ["You are a Super Multica agent."];
}
if (mode === "minimal") {
return [];
}
// full mode
if (profile?.soul) {
return [profile.soul];
}
return [];
}
/**
* User section user.md content (full mode only).
*/
export function buildUserSection(
profile: ProfileContent | undefined,
mode: SystemPromptMode,
): string[] {
if (mode !== "full" || !profile?.user) return [];
return [profile.user];
}
/**
* Workspace section workspace.md content (full mode only).
*/
export function buildWorkspaceSection(
profile: ProfileContent | undefined,
mode: SystemPromptMode,
): string[] {
if (mode !== "full" || !profile?.workspace) return [];
return [profile.workspace];
}
/**
* Memory section memory.md content (full mode only).
*/
export function buildMemoryFileSection(
profile: ProfileContent | undefined,
mode: SystemPromptMode,
): string[] {
if (mode !== "full" || !profile?.memory) return [];
return [profile.memory];
}
/**
* Safety constitution always included.
*/
export function buildSafetySection(includeSafety: boolean): string[] {
if (!includeSafety) return [];
return [SAFETY_CONSTITUTION];
}
/**
* Tooling summary lists active tools with descriptions.
* Included in full and minimal modes.
*/
export function buildToolingSummary(
tools: string[] | undefined,
mode: SystemPromptMode,
): string[] {
if (mode === "none" || !tools || tools.length === 0) return [];
const toolSet = new Set(tools.map((t) => t.toLowerCase()));
// Build ordered tool lines
const toolLines: string[] = [];
const seen = new Set<string>();
// Core tools in preferred order
for (const tool of TOOL_ORDER) {
if (toolSet.has(tool) && !seen.has(tool)) {
seen.add(tool);
const summary = CORE_TOOL_SUMMARIES[tool];
toolLines.push(summary ? `- ${tool}: ${summary}` : `- ${tool}`);
}
}
// External/unknown tools alphabetically
const extraTools = [...toolSet].filter((t) => !seen.has(t)).sort();
for (const tool of extraTools) {
toolLines.push(`- ${tool}`);
}
return [
"## Tooling",
"Tool availability (filtered by policy):",
"Tool names are case-sensitive. Call tools exactly as listed.",
toolLines.join("\n"),
"",
];
}
/**
* Tool call style guidance full and minimal modes.
*/
export function buildToolCallStyleSection(mode: SystemPromptMode): string[] {
if (mode === "none") return [];
return [
"## Tool Call Style",
"Default: do not narrate routine, low-risk tool calls (just call the tool).",
"Narrate only when it helps: multi-step work, complex problems, sensitive actions (e.g., deletions), or when the user explicitly asks.",
"Keep narration brief and value-dense; avoid repeating obvious steps.",
"",
];
}
/**
* Conditional tool sections inject usage hints based on which tools are active.
*/
export function buildConditionalToolSections(
tools: string[] | undefined,
mode: SystemPromptMode,
): string[] {
if (mode === "none" || !tools || tools.length === 0) return [];
const toolSet = new Set(tools.map((t) => t.toLowerCase()));
const lines: string[] = [];
// Memory tools
const hasMemory =
toolSet.has("memory_get") ||
toolSet.has("memory_set") ||
toolSet.has("memory_list") ||
toolSet.has("memory_delete");
if (hasMemory) {
lines.push(
"## Memory",
"Before answering anything about prior work, decisions, dates, people, preferences, or todos: search memory first, then pull only the needed entries.",
"Update memory when the user shares important information, decisions, or preferences.",
"",
);
}
// Subagent tools (full mode only — minimal agents cannot spawn)
if (mode === "full" && toolSet.has("sessions_spawn")) {
lines.push(
"## Sub-Agents",
"If a task is complex or long-running, spawn a sub-agent. It will do the work and report back when done.",
"You can check on running sub-agents at any time.",
"Sub-agents cannot spawn nested sub-agents.",
"",
);
}
// Web tools
if (toolSet.has("web_search") || toolSet.has("web_fetch")) {
lines.push(
"## Web Access",
"You have web access. Use it when the user asks about current events, needs up-to-date information, or requests content from URLs.",
"Prefer web_search for discovery and web_fetch for specific URLs.",
"",
);
}
return lines;
}
/**
* Skills section wraps SkillManager output with mandatory scan instructions.
* Full mode only.
*/
export function buildSkillsSection(
skillsPrompt: string | undefined,
mode: SystemPromptMode,
): string[] {
if (mode !== "full") return [];
const trimmed = skillsPrompt?.trim();
if (!trimmed) return [];
return [
"## Skills (mandatory)",
"Before replying: scan the available skills below.",
"- If exactly one skill clearly applies: follow its instructions.",
"- If multiple could apply: choose the most specific one.",
"- If none clearly apply: skip skill invocation.",
"",
trimmed,
"",
];
}
/**
* Runtime info line full and minimal modes.
*/
export function buildRuntimeSection(
runtime: RuntimeInfo | undefined,
mode: SystemPromptMode,
): string[] {
if (mode === "none" || !runtime) return [];
return ["## Runtime", formatRuntimeLine(runtime)];
}
/**
* Profile directory section tells agent where its files live.
* Full mode only.
*/
export function buildProfileDirSection(
profileDir: string | undefined,
mode: SystemPromptMode,
): string[] {
if (mode !== "full" || !profileDir) return [];
return [
"## Profile Directory",
"",
`Your profile files are located at: \`${profileDir}\``,
"",
"Use `edit` or `write` tools to update these files when needed.",
];
}
/**
* Subagent context rules and task for child agents.
* Minimal and none modes only.
*/
export function buildSubagentSection(
subagent: SubagentContext | undefined,
mode: SystemPromptMode,
): string[] {
if (mode === "full" || !subagent) return [];
const lines: string[] = [
"## Subagent Rules",
"- Stay focused on the assigned task below.",
"- Complete the task thoroughly and report your findings.",
"- Do NOT initiate side actions unrelated to the task.",
"- Do NOT attempt to communicate with the user directly.",
"- Do NOT spawn nested subagents.",
"- Your session is ephemeral and will be cleaned up after completion.",
"",
"## Context",
`Requester session: ${subagent.requesterSessionId}`,
`Child session: ${subagent.childSessionId}`,
];
if (subagent.label) {
lines.push(`Label: "${subagent.label}"`);
}
lines.push("", "## Task", subagent.task);
return lines;
}
/**
* Extra system prompt appended at the end if provided.
*/
export function buildExtraPromptSection(
extraSystemPrompt: string | undefined,
mode: SystemPromptMode,
): string[] {
const trimmed = extraSystemPrompt?.trim();
if (!trimmed) return [];
const header = mode === "minimal" ? "## Subagent Context" : "## Additional Context";
return [header, trimmed];
}

View file

@ -0,0 +1,106 @@
/**
* System Prompt Engineering Type Definitions
*
* Provides structured, mode-aware system prompt assembly
* inspired by OpenClaw's conditional prompt system.
*/
import type { ProfileConfig } from "../profile/types.js";
/**
* Controls which sections are included in the system prompt.
* - "full": All sections (default, for main agents)
* - "minimal": Reduced sections (safety, tooling, runtime, subagent context) for subagents
* - "none": Identity line + safety + subagent task only for bare subagents
*/
export type SystemPromptMode = "full" | "minimal" | "none";
/** Runtime environment information */
export interface RuntimeInfo {
/** Agent display name */
agentName?: string | undefined;
/** Machine hostname */
hostName?: string | undefined;
/** LLM provider (e.g. "anthropic") */
provider?: string | undefined;
/** Model ID (e.g. "claude-3.5-sonnet") */
model?: string | undefined;
/** OS platform (e.g. "darwin") */
os?: string | undefined;
/** CPU architecture (e.g. "arm64") */
arch?: string | undefined;
/** Node.js version (e.g. "v22.0.0") */
nodeVersion?: string | undefined;
/** Current working directory */
cwd?: string | undefined;
}
/** Subagent context for minimal/none modes */
export interface SubagentContext {
/** Parent session that spawned this subagent */
requesterSessionId: string;
/** This subagent's session ID */
childSessionId: string;
/** Optional human-readable label */
label?: string | undefined;
/** The task this subagent must complete */
task: string;
}
/** Profile content subset used by the prompt builder */
export interface ProfileContent {
soul?: string | undefined;
user?: string | undefined;
workspace?: string | undefined;
memory?: string | undefined;
config?: ProfileConfig | undefined;
}
/** Input options for buildSystemPrompt() */
export interface SystemPromptOptions {
/** Prompt mode — full for main agents, minimal for subagents, none for bare */
mode: SystemPromptMode;
/** Agent profile content */
profile?: ProfileContent | undefined;
/** Profile directory path (so the agent knows where files live) */
profileDir?: string | undefined;
/** Active tool names (after policy filtering) */
tools?: string[] | undefined;
/** Skills prompt (pre-built by SkillManager) */
skillsPrompt?: string | undefined;
/** Runtime context */
runtime?: RuntimeInfo | undefined;
/** Subagent context (for minimal/none modes) */
subagent?: SubagentContext | undefined;
/** Extra system prompt to append */
extraSystemPrompt?: string | undefined;
/** Whether to include the safety constitution (default: true) */
includeSafety?: boolean | undefined;
}
/** A named section of the system prompt */
export interface PromptSection {
/** Section identifier */
name: string;
/** Section content (joined lines) */
content: string;
}
/** Report entry for a single section */
export interface SectionReport {
name: string;
chars: number;
lines: number;
included: boolean;
}
/** Telemetry report about a generated system prompt */
export interface SystemPromptReport {
mode: SystemPromptMode;
totalChars: number;
totalLines: number;
sections: SectionReport[];
toolCount: number;
skillsIncluded: boolean;
safetyIncluded: boolean;
}