feat(FN-1048): add configurable agent prompts with built-in templates
- Add AgentPromptTemplate and AgentPromptsConfig types to ProjectSettings - Create agent-prompts module with 7 built-in prompt templates and role resolver - Wire engine agents (executor, reviewer, merger, triage) to use resolved prompts - Add 25 test cases covering template resolution, role assignment, and validation - Export new types from @fusion/core package - Document agentPrompts configuration and built-in templates in AGENTS.md
This commit is contained in:
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
||||
import { existsSync } from "node:fs";
|
||||
import type { TaskStore, Task, TaskDetail, StepStatus, Settings, WorkflowStep, MissionStore, Slice, AgentState, AgentCapability } from "@fusion/core";
|
||||
import type { AgentStore } from "@fusion/core";
|
||||
import { buildExecutionMemoryInstructions } from "@fusion/core";
|
||||
import { buildExecutionMemoryInstructions, resolveAgentPrompt } from "@fusion/core";
|
||||
import { findWorktreeUser } from "./merger.js";
|
||||
import { generateWorktreeName, slugify } from "./worktree-names.js";
|
||||
import { Type, type Static } from "@mariozechner/pi-ai";
|
||||
@@ -216,6 +216,12 @@ Tests and typecheck are also hard quality gates:
|
||||
- If the repository exposes a typecheck command, run it and keep fixing failures until it passes
|
||||
- Do not stop at "out of scope" if additional fixes are required to restore green tests, build, or typecheck`;
|
||||
|
||||
/** Resolve the executor system prompt from settings, falling back to the hardcoded constant. */
|
||||
function getExecutorSystemPrompt(settings: Settings): string {
|
||||
const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts);
|
||||
return customPrompt || EXECUTOR_SYSTEM_PROMPT;
|
||||
}
|
||||
|
||||
export interface TaskExecutorOptions {
|
||||
semaphore?: AgentSemaphore;
|
||||
/** Worktree pool for recycling idle worktrees across tasks. */
|
||||
@@ -979,7 +985,7 @@ export class TaskExecutor {
|
||||
|
||||
let { session, sessionFile } = await createKbAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: EXECUTOR_SYSTEM_PROMPT,
|
||||
systemPrompt: getExecutorSystemPrompt(settings),
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
@@ -1166,7 +1172,7 @@ export class TaskExecutor {
|
||||
|
||||
const { session: retrySession, sessionFile: retrySessionFile } = await createKbAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: EXECUTOR_SYSTEM_PROMPT,
|
||||
systemPrompt: getExecutorSystemPrompt(settings),
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
@@ -1744,6 +1750,7 @@ export class TaskExecutor {
|
||||
validatorFallbackModelId: settings.validatorFallbackModelId,
|
||||
store,
|
||||
taskId,
|
||||
agentPrompts: settings.agentPrompts,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { getTaskMergeBlocker, type TaskStore, type MergeResult, type MergeDetails, type WorkflowStep, type WorkflowStepResult, type Settings } from "@fusion/core";
|
||||
import { getTaskMergeBlocker, type TaskStore, type MergeResult, type MergeDetails, type WorkflowStep, type WorkflowStepResult, type Settings, type AgentPromptsConfig } from "@fusion/core";
|
||||
import { resolveAgentPrompt } from "@fusion/core";
|
||||
import { createKbAgent, describeModel, promptWithFallback } from "./pi.js";
|
||||
import type { WorktreePool } from "./worktree-pool.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
@@ -447,7 +448,7 @@ export function resolveConflicts(
|
||||
* the commit format uses `<type>(<scope>): <summary>` where scope is the
|
||||
* task ID. When false, it uses `<type>: <summary>` with no scope.
|
||||
*/
|
||||
function buildMergeSystemPrompt(includeTaskId: boolean): string {
|
||||
function buildMergeSystemPrompt(includeTaskId: boolean, agentPrompts?: AgentPromptsConfig): string {
|
||||
const commitFormat = includeTaskId
|
||||
? `\`\`\`
|
||||
git commit -m "<type>(<scope>): <summary>" -m "<body>"
|
||||
@@ -487,6 +488,40 @@ git commit -m "feat: add user profile page" -m "- Add /profile route with avatar
|
||||
- Add profile e2e tests"
|
||||
\`\`\``;
|
||||
|
||||
// Resolve the base merger prompt from agent prompts config, falling back to the inline default
|
||||
const basePrompt = resolveAgentPrompt("merger", agentPrompts);
|
||||
|
||||
// If a custom merger prompt is configured, use it as the base with commit format appended
|
||||
const customAssignment = agentPrompts?.roleAssignments?.merger;
|
||||
if (customAssignment && basePrompt) {
|
||||
return `${basePrompt}
|
||||
|
||||
## Commit message
|
||||
After all conflicts are resolved (or if there were none), write and execute the squash commit.
|
||||
|
||||
Look at the branch commits and diff to understand what was done, then run:
|
||||
${commitFormat}
|
||||
|
||||
Do NOT use generic messages like "merge branch" or "resolve conflicts".
|
||||
Base the message on the ACTUAL work done in the branch commits.
|
||||
|
||||
## Build verification
|
||||
|
||||
If a build command is configured for this project, build verification is a hard gate.
|
||||
You MUST run the exact configured build command in this worktree before committing.
|
||||
Do not assume the build passes. Do not describe it as passing unless you actually ran it
|
||||
and the bash tool returned exit code 0.
|
||||
|
||||
1. Run the build command (shown in the prompt context below)
|
||||
2. If the build succeeds (exit code 0), proceed with the commit
|
||||
3. If the build fails (non-zero exit code), DO NOT commit. Instead:
|
||||
- Call the \`report_build_failure\` tool with the real error details
|
||||
- Stop immediately and do not run \`git commit\`
|
||||
- Do not claim success in plain text
|
||||
|
||||
The merge will only be completed if the build passes or no build command is configured.`;
|
||||
}
|
||||
|
||||
return `You are a merge agent for "kb", an AI-orchestrated task board.
|
||||
|
||||
Your job is to finalize a squash merge: resolve any conflicts and write a good commit message.
|
||||
@@ -1298,7 +1333,7 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt: buildMergeSystemPrompt(includeTaskId),
|
||||
systemPrompt: buildMergeSystemPrompt(includeTaskId, settings.agentPrompts),
|
||||
tools: "coding",
|
||||
customTools: [reportBuildFailureTool],
|
||||
onText: agentLogger.onText,
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
* - Verdict + feedback is returned to the worker
|
||||
*/
|
||||
|
||||
import type { TaskStore, TaskComment } from "@fusion/core";
|
||||
import type { TaskStore, TaskComment, AgentPromptsConfig } from "@fusion/core";
|
||||
import { resolveAgentPrompt } from "@fusion/core";
|
||||
import { createKbAgent, describeModel, promptWithFallback } from "./pi.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import { reviewerLog } from "./logger.js";
|
||||
@@ -195,6 +196,8 @@ export interface ReviewOptions {
|
||||
taskId?: string;
|
||||
/** User comments on the task (author === "user"). For spec reviews, the reviewer explicitly checks that every comment is addressed. */
|
||||
userComments?: TaskComment[];
|
||||
/** Agent prompt configuration for resolving custom reviewer prompts. */
|
||||
agentPrompts?: AgentPromptsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +248,7 @@ export async function reviewStep(
|
||||
// Spawn a reviewer agent with read-only tools
|
||||
const { session } = await createKbAgent({
|
||||
cwd,
|
||||
systemPrompt: REVIEWER_SYSTEM_PROMPT,
|
||||
systemPrompt: resolveAgentPrompt("reviewer", options.agentPrompts) || REVIEWER_SYSTEM_PROMPT,
|
||||
tools: "readonly",
|
||||
onText: agentLogger ? agentLogger.onText : (delta) => options.onText?.(delta),
|
||||
onThinking: agentLogger?.onThinking,
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
TaskAttachment,
|
||||
Settings,
|
||||
} from "@fusion/core";
|
||||
import { buildTriageMemoryInstructions } from "@fusion/core";
|
||||
import { buildTriageMemoryInstructions, resolveAgentPrompt } from "@fusion/core";
|
||||
import type { ImageContent } from "@mariozechner/pi-ai";
|
||||
import { Type, type Static } from "@mariozechner/pi-ai";
|
||||
import type {
|
||||
@@ -494,7 +494,7 @@ export class TriageProcessor {
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: TRIAGE_SYSTEM_PROMPT,
|
||||
systemPrompt: resolveAgentPrompt("triage", settings.agentPrompts) || TRIAGE_SYSTEM_PROMPT,
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
|
||||
Reference in New Issue
Block a user