refactor(agent): integrate system prompt builder into profile, runner, and subagent

ProfileManager.buildSystemPrompt() now delegates to the structured builder.
Runner assembles prompt after tool resolution with safety, tooling summary,
and runtime info. Subagent prompts use minimal mode with safety constitution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yushen 2026-02-04 15:41:22 +08:00
parent 5a06bed549
commit 8333615fb4
4 changed files with 111 additions and 87 deletions

View file

@ -17,6 +17,7 @@ import {
writeProfileFile,
} from "./storage.js";
import { PROFILE_FILES } from "./types.js";
import { buildSystemPrompt as buildPrompt } from "../system-prompt/index.js";
export { type AgentProfile, type CreateProfileOptions, type ProfileConfig, type ProfileManagerOptions } from "./types.js";
export { DEFAULT_TEMPLATES } from "./templates.js";
@ -135,41 +136,24 @@ export class ProfileManager {
return getProfileDir(this.profileId, { baseDir: this.baseDir });
}
/** 构建 system prompt */
/** Build system prompt using the structured prompt builder */
buildSystemPrompt(): string {
const profile = this.getProfile();
console.log('[ProfileManager] buildSystemPrompt called, profile exists:', !!profile);
if (!profile) {
return "";
}
const parts: string[] = [];
if (profile.soul) {
console.log('[ProfileManager] Adding soul, length:', profile.soul.length);
parts.push(profile.soul);
}
if (profile.user) {
console.log('[ProfileManager] Adding user, content:', profile.user.substring(0, 100));
parts.push(profile.user);
} else {
console.log('[ProfileManager] No user content in profile');
}
if (profile.workspace) {
parts.push(profile.workspace);
}
if (profile.memory) {
parts.push(profile.memory);
}
// 注入 profile 目录路径,让 Agent 知道文件在哪里
const profileDir = this.getProfileDir();
parts.push(`## Profile Directory\n\nYour profile files are located at: \`${profileDir}\`\n\nUse \`edit\` or \`write\` tools to update these files when needed.`);
return parts.join("\n\n");
return buildPrompt({
mode: "full",
profile: {
soul: profile.soul,
user: profile.user,
workspace: profile.workspace,
memory: profile.memory,
config: profile.config,
},
profileDir: this.getProfileDir(),
});
}
/** 获取 tools 配置 */

View file

@ -28,6 +28,11 @@ import {
markAuthProfileUsed,
markAuthProfileGood,
} from "./auth-profiles/index.js";
import {
buildSystemPrompt as buildStructuredSystemPrompt,
collectRuntimeInfo,
type SystemPromptMode,
} from "./system-prompt/index.js";
import type { AuthProfileFailureReason } from "./auth-profiles/index.js";
// ============================================================
@ -153,7 +158,6 @@ export class Agent {
// Load Agent Profile (if profileId is specified)
// Every Agent should have a Profile for memory, tools config, and other settings
let systemPrompt: string | undefined;
if (options.profileId) {
this.profile = new ProfileManager({
profileId: options.profileId,
@ -161,10 +165,6 @@ export class Agent {
});
// Ensure profile directory exists (creates with default templates if new)
this.profile.getOrCreateProfile(true);
systemPrompt = this.profile.buildSystemPrompt();
} else if (options.systemPrompt) {
// Use provided systemPrompt directly (no profile - memory tools won't work)
systemPrompt = options.systemPrompt;
}
// Initialize SkillManager (enabled by default)
@ -174,17 +174,6 @@ export class Agent {
profileBaseDir: options.profileBaseDir,
config: options.skills,
});
// Append skills prompt to system prompt
const skillsPrompt = this.skillManager.buildSkillsPrompt();
if (skillsPrompt) {
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillsPrompt}` : skillsPrompt;
}
}
// Set the combined system prompt
if (systemPrompt) {
this.agent.setSystemPrompt(systemPrompt);
}
this.sessionId = options.sessionId ?? uuidv7();
@ -249,7 +238,7 @@ export class Agent {
compactionMode,
// Token 模式参数
contextWindowTokens: this.contextWindowGuard.tokens,
systemPrompt,
// systemPrompt is set later via setSystemPrompt() after tools are resolved
reserveTokens: options.reserveTokens,
targetRatio: options.compactionTargetRatio,
minKeepMessages: options.minKeepMessages,
@ -287,6 +276,14 @@ export class Agent {
}
this.agent.setTools(tools);
// Build the system prompt using the structured builder
const toolNames = tools.map((t: { name: string }) => t.name);
const systemPrompt = this.buildFullSystemPrompt(options, toolNames);
if (systemPrompt) {
this.agent.setSystemPrompt(systemPrompt);
this.session.setSystemPrompt(systemPrompt);
}
this.session.saveMeta({
provider: this.agent.state.model?.provider,
model: this.agent.state.model?.id,
@ -578,35 +575,91 @@ export class Agent {
this.profile?.updateStyle(style);
}
/**
* Build the full system prompt using the structured builder.
* Combines profile content, tools, skills, and runtime info.
*/
private buildFullSystemPrompt(
options: AgentOptions,
toolNames: string[],
): string | undefined {
// If a raw systemPrompt is provided directly, use it as-is (backward compat)
if (!options.profileId && options.systemPrompt) {
return options.systemPrompt;
}
const profile = this.profile?.getProfile();
if (!profile && !options.profileId) {
return undefined;
}
const mode: SystemPromptMode = options.isSubagent ? "minimal" : "full";
const skillsPrompt = this.skillManager?.buildSkillsPrompt();
const runtime = collectRuntimeInfo({
agentName: this.profile?.getName(),
provider: this.resolvedProvider,
model: this.agent.state.model?.id,
});
return buildStructuredSystemPrompt({
mode,
profile: profile
? {
soul: profile.soul,
user: profile.user,
workspace: profile.workspace,
memory: profile.memory,
config: profile.config,
}
: undefined,
profileDir: this.profile?.getProfileDir(),
tools: toolNames,
skillsPrompt,
runtime,
});
}
/**
* Reload profile from disk and rebuild system prompt.
* Call this after updating profile files to apply changes immediately.
*/
reloadSystemPrompt(): void {
if (!this.profile) {
console.log('[Agent] reloadSystemPrompt: no profile');
return;
}
// Reload profile from disk
console.log('[Agent] Reloading profile from disk...');
this.profile.reloadProfile();
// Rebuild system prompt
let systemPrompt = this.profile.buildSystemPrompt();
console.log('[Agent] Built system prompt, length:', systemPrompt?.length);
// Rebuild using current tool names
const toolNames = (this.agent.state.tools ?? []).map((t: { name: string }) => t.name);
const skillsPrompt = this.skillManager?.buildModelSkillsPrompt();
// Re-add skills prompt if skills are enabled
if (this.skillManager) {
const skillsPrompt = this.skillManager.buildModelSkillsPrompt();
if (skillsPrompt) {
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillsPrompt}` : skillsPrompt;
}
}
const runtime = collectRuntimeInfo({
agentName: this.profile.getName(),
provider: this.resolvedProvider,
model: this.agent.state.model?.id,
});
const profile = this.profile.getProfile();
const systemPrompt = buildStructuredSystemPrompt({
mode: "full",
profile: profile
? {
soul: profile.soul,
user: profile.user,
workspace: profile.workspace,
memory: profile.memory,
config: profile.config,
}
: undefined,
profileDir: this.profile.getProfileDir(),
tools: toolNames,
skillsPrompt,
runtime,
});
// Apply new system prompt
if (systemPrompt) {
console.log('[Agent] Applying system prompt, first 200 chars:', systemPrompt.substring(0, 200));
this.agent.setSystemPrompt(systemPrompt);
}
}

View file

@ -10,11 +10,12 @@ describe("buildSubagentSystemPrompt", () => {
task: "Analyze the auth module for security issues",
});
expect(prompt).toContain("You are a subagent spawned to complete a specific task");
expect(prompt).toContain("## Subagent Rules");
expect(prompt).toContain("Analyze the auth module for security issues");
expect(prompt).toContain("parent-123");
expect(prompt).toContain("child-456");
expect(prompt).toContain("Do NOT spawn nested subagents");
expect(prompt).toContain("## Safety");
});
it("includes label when provided", () => {

View file

@ -9,6 +9,7 @@
import { readEntries } from "../session/storage.js";
import { getHub } from "../../hub/hub-singleton.js";
import { buildSystemPrompt } from "../system-prompt/index.js";
import type {
SubagentAnnounceParams,
SubagentRunOutcome,
@ -17,33 +18,18 @@ import type {
/**
* Build the system prompt injected into a subagent session.
* Uses the structured prompt builder with "minimal" mode.
*/
export function buildSubagentSystemPrompt(params: SubagentSystemPromptParams): string {
const { requesterSessionId, childSessionId, label, task } = params;
const lines: string[] = [
"You are a subagent spawned to complete a specific task.",
"",
"## 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: ${requesterSessionId}`,
`Child session: ${childSessionId}`,
];
if (label) {
lines.push(`Label: "${label}"`);
}
lines.push("", "## Task", task);
return lines.join("\n");
return buildSystemPrompt({
mode: "minimal",
subagent: {
requesterSessionId: params.requesterSessionId,
childSessionId: params.childSessionId,
label: params.label,
task: params.task,
},
});
}
/**