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:
gsxdsm
2026-04-07 12:31:28 -07:00
parent 880f29115c
commit 52e5ec8793
9 changed files with 1231 additions and 11 deletions

View File

@@ -1090,6 +1090,56 @@ The dashboard Settings modal includes a "Backups" section where you can:
- View current backup count and total size
- Create manual backups with the "Backup Now" button
### `agentPrompts` (default: `undefined`)
Configurable agent role prompt templates and assignments. When set, allows per-project customization of system prompts for different agent roles (executor, triage, reviewer, merger).
**Configuration:**
```json
{
"settings": {
"agentPrompts": {
"templates": [
{
"id": "my-custom-executor",
"name": "My Custom Executor",
"description": "A custom executor with specific behavioral guidelines",
"role": "executor",
"prompt": "You are a custom task execution agent..."
}
],
"roleAssignments": {
"executor": "my-custom-executor",
"reviewer": "strict-reviewer"
}
}
}
}
```
**Built-in Templates:**
| Template ID | Role | Description |
|-------------|------|-------------|
| `default-executor` | executor | Standard task execution agent with full tooling and review support |
| `default-triage` | triage | Standard task specification agent producing detailed PROMPT.md files |
| `default-reviewer` | reviewer | Standard independent code and plan reviewer with balanced criteria |
| `default-merger` | merger | Standard merge agent for squash merges with conflict resolution |
| `senior-engineer` | executor | Autonomous executor with architectural awareness, performance focus, and minimal hand-holding |
| `strict-reviewer` | reviewer | Rigorous reviewer with stricter criteria for security, edge cases, backward compatibility, and type safety |
| `concise-triage` | triage | Shorter, more focused specification format with minimal prose |
**How It Works:**
- Set `roleAssignments` to map an agent role to a template ID (built-in or custom)
- Custom templates can override built-in templates by using the same ID
- When no assignment is configured for a role, the default built-in prompt is used (identical to pre-feature behavior)
- The merger prompt is used as a base — commit format instructions and build verification steps are always appended dynamically
**Notes:**
- Workflow step prompts and child agent prompts are NOT affected by this configuration (they are context-specific)
- The built-in prompt texts are derived from the engine's hardcoded prompts and should be kept in sync
- When `agentPrompts` is `undefined` (default), behavior is identical to before this feature existed
## Model Presets
The kb dashboard supports reusable model presets so teams can standardize AI model choices without manually selecting executor and validator models for every task.

View File

@@ -0,0 +1,279 @@
import { describe, it, expect } from "vitest";
import {
BUILTIN_AGENT_PROMPTS,
resolveAgentPrompt,
getAvailableTemplates,
getTemplatesForRole,
} from "./agent-prompts.js";
import type { AgentPromptsConfig, AgentPromptTemplate } from "./types.js";
// ---------------------------------------------------------------------------
// resolveAgentPrompt
// ---------------------------------------------------------------------------
describe("resolveAgentPrompt", () => {
it("returns the correct built-in prompt for executor when no config provided", () => {
const result = resolveAgentPrompt("executor");
expect(result).toBeTruthy();
expect(result).toContain("task execution agent");
});
it("returns the correct built-in prompt for triage when no config provided", () => {
const result = resolveAgentPrompt("triage");
expect(result).toBeTruthy();
expect(result).toContain("task specification agent");
});
it("returns the correct built-in prompt for reviewer when no config provided", () => {
const result = resolveAgentPrompt("reviewer");
expect(result).toBeTruthy();
expect(result).toContain("independent code and plan reviewer");
});
it("returns the correct built-in prompt for merger when no config provided", () => {
const result = resolveAgentPrompt("merger");
expect(result).toBeTruthy();
expect(result).toContain("merge agent");
});
it("returns empty string for role with no built-in default", () => {
const result = resolveAgentPrompt("scheduler");
expect(result).toBe("");
});
it("returns custom template when roleAssignments maps to a custom template ID", () => {
const config: AgentPromptsConfig = {
templates: [
{
id: "my-custom-executor",
name: "My Custom Executor",
description: "A custom executor",
role: "executor",
prompt: "You are a custom executor agent.",
},
],
roleAssignments: {
executor: "my-custom-executor",
},
};
const result = resolveAgentPrompt("executor", config);
expect(result).toBe("You are a custom executor agent.");
});
it("returns built-in template when roleAssignments maps to a built-in template ID", () => {
const config: AgentPromptsConfig = {
roleAssignments: {
executor: "senior-engineer",
},
};
const result = resolveAgentPrompt("executor", config);
expect(result).toBeTruthy();
expect(result).toContain("senior engineering agent");
});
it("throws descriptive error when assigned template ID does not exist", () => {
const config: AgentPromptsConfig = {
roleAssignments: {
executor: "nonexistent-template",
},
};
expect(() => resolveAgentPrompt("executor", config)).toThrow(
/Agent prompt template "nonexistent-template" not found/,
);
});
it("prioritizes custom templates over built-in when IDs collide", () => {
const config: AgentPromptsConfig = {
templates: [
{
id: "default-executor",
name: "Overridden Executor",
description: "Custom template that overrides the built-in",
role: "executor",
prompt: "This is the overridden executor prompt.",
},
],
roleAssignments: {
executor: "default-executor",
},
};
const result = resolveAgentPrompt("executor", config);
expect(result).toBe("This is the overridden executor prompt.");
});
it("returns empty string when config has no roleAssignment for the role", () => {
const config: AgentPromptsConfig = {
templates: [],
};
// scheduler has no built-in default, and no assignment
const result = resolveAgentPrompt("scheduler", config);
expect(result).toBe("");
});
it("returns built-in default when config has empty roleAssignments", () => {
const config: AgentPromptsConfig = {
roleAssignments: {},
};
const result = resolveAgentPrompt("executor", config);
expect(result).toContain("task execution agent");
});
});
// ---------------------------------------------------------------------------
// getAvailableTemplates
// ---------------------------------------------------------------------------
describe("getAvailableTemplates", () => {
it("returns only built-in templates when no config provided", () => {
const templates = getAvailableTemplates();
expect(templates.length).toBe(BUILTIN_AGENT_PROMPTS.length);
// All should be built-in
expect(templates.every((t) => t.builtIn === true)).toBe(true);
});
it("returns only built-in templates when config has no templates", () => {
const templates = getAvailableTemplates({});
expect(templates.length).toBe(BUILTIN_AGENT_PROMPTS.length);
});
it("merges custom templates with built-in", () => {
const customTemplate: AgentPromptTemplate = {
id: "my-custom",
name: "My Custom",
description: "A custom template",
role: "executor",
prompt: "Custom prompt",
};
const templates = getAvailableTemplates({ templates: [customTemplate] });
expect(templates.length).toBe(BUILTIN_AGENT_PROMPTS.length + 1);
expect(templates.find((t) => t.id === "my-custom")).toEqual(customTemplate);
});
it("custom template overrides built-in by ID", () => {
const overrideTemplate: AgentPromptTemplate = {
id: "default-executor",
name: "Overridden",
description: "Overrides the built-in executor",
role: "executor",
prompt: "Overridden prompt",
};
const templates = getAvailableTemplates({ templates: [overrideTemplate] });
const executorTemplate = templates.find((t) => t.id === "default-executor");
expect(executorTemplate?.prompt).toBe("Overridden prompt");
// Should still have the same total count (replaced, not added)
expect(templates.length).toBe(BUILTIN_AGENT_PROMPTS.length);
});
});
// ---------------------------------------------------------------------------
// getTemplatesForRole
// ---------------------------------------------------------------------------
describe("getTemplatesForRole", () => {
it("returns executor templates", () => {
const templates = getTemplatesForRole("executor");
expect(templates.length).toBeGreaterThanOrEqual(1);
expect(templates.every((t) => t.role === "executor")).toBe(true);
});
it("returns triage templates", () => {
const templates = getTemplatesForRole("triage");
expect(templates.length).toBeGreaterThanOrEqual(1);
expect(templates.every((t) => t.role === "triage")).toBe(true);
});
it("returns reviewer templates", () => {
const templates = getTemplatesForRole("reviewer");
expect(templates.length).toBeGreaterThanOrEqual(1);
expect(templates.every((t) => t.role === "reviewer")).toBe(true);
});
it("returns merger templates", () => {
const templates = getTemplatesForRole("merger");
expect(templates.length).toBeGreaterThanOrEqual(1);
expect(templates.every((t) => t.role === "merger")).toBe(true);
});
it("includes custom templates for the role", () => {
const customTemplate: AgentPromptTemplate = {
id: "my-reviewer",
name: "My Reviewer",
description: "A custom reviewer",
role: "reviewer",
prompt: "Custom reviewer prompt",
};
const templates = getTemplatesForRole("reviewer", { templates: [customTemplate] });
const found = templates.find((t) => t.id === "my-reviewer");
expect(found).toBeDefined();
expect(found?.prompt).toBe("Custom reviewer prompt");
});
});
// ---------------------------------------------------------------------------
// Built-in template validation
// ---------------------------------------------------------------------------
describe("BUILTIN_AGENT_PROMPTS", () => {
it("covers all 4 core roles (executor, triage, reviewer, merger)", () => {
const roles = new Set(BUILTIN_AGENT_PROMPTS.map((t) => t.role));
expect(roles.has("executor")).toBe(true);
expect(roles.has("triage")).toBe(true);
expect(roles.has("reviewer")).toBe(true);
expect(roles.has("merger")).toBe(true);
});
it("has a default template for each core role", () => {
const coreRoles: Array<"executor" | "triage" | "reviewer" | "merger"> = [
"executor",
"triage",
"reviewer",
"merger",
];
for (const role of coreRoles) {
const defaultTemplate = BUILTIN_AGENT_PROMPTS.find(
(t) => t.id === `default-${role}`,
);
expect(defaultTemplate).toBeDefined();
expect(defaultTemplate?.role).toBe(role);
}
});
it("has additional role variants (senior-engineer, strict-reviewer, concise-triage)", () => {
const ids = new Set(BUILTIN_AGENT_PROMPTS.map((t) => t.id));
expect(ids.has("senior-engineer")).toBe(true);
expect(ids.has("strict-reviewer")).toBe(true);
expect(ids.has("concise-triage")).toBe(true);
});
it("all built-in templates have valid required fields", () => {
for (const template of BUILTIN_AGENT_PROMPTS) {
expect(template.id).toBeTruthy();
expect(typeof template.id).toBe("string");
expect(template.name).toBeTruthy();
expect(typeof template.name).toBe("string");
expect(template.description).toBeTruthy();
expect(typeof template.description).toBe("string");
expect(template.role).toBeTruthy();
expect(typeof template.role).toBe("string");
expect(template.prompt).toBeTruthy();
expect(typeof template.prompt).toBe("string");
expect(template.builtIn).toBe(true);
}
});
it("all template IDs are unique", () => {
const ids = BUILTIN_AGENT_PROMPTS.map((t) => t.id);
const uniqueIds = new Set(ids);
expect(uniqueIds.size).toBe(ids.length);
});
});

View File

@@ -0,0 +1,808 @@
/**
* Agent role prompt templates for customizable system prompts.
*
* This module provides:
* - Built-in prompt templates for all core agent roles (executor, triage, reviewer, merger)
* - Additional role variants (senior-engineer, strict-reviewer, concise-triage)
* - A resolver function that merges custom templates from project settings with built-ins
*
* NOTE: The built-in prompt texts are derived from the engine's hardcoded prompts
* (EXECUTOR_SYSTEM_PROMPT, TRIAGE_SYSTEM_PROMPT, REVIEWER_SYSTEM_PROMPT, and the
* merger prompt). They should be kept in sync when the engine prompts change.
* Since @fusion/core cannot import @fusion/engine (circular dependency), these
* are maintained as inline strings.
*
* @module agent-prompts
*/
import type { AgentCapability, AgentPromptTemplate, AgentPromptsConfig } from "./types.js";
// ---------------------------------------------------------------------------
// Built-in prompt text (derived from engine constants — keep in sync)
// ---------------------------------------------------------------------------
const EXECUTOR_PROMPT_TEXT = `You are a task execution agent for "kb", an AI-orchestrated task board.
You are working in a git worktree isolated from the main branch. Your job is to implement the task described in the PROMPT.md specification you're given.
## How to work
1. Read the PROMPT.md carefully — it contains your mission, steps, file scope, and acceptance criteria
2. Work through each step in order
3. Write clean, production-quality code
4. Test your changes
5. Commit at meaningful boundaries (step completion)
## Reporting progress via tools
You have tools to report progress. The board updates in real-time.
**Step lifecycle:**
- Before starting a step: \`task_update(step=N, status="in-progress")\`
- After completing a step: \`task_update(step=N, status="done")\`
- If skipping a step: \`task_update(step=N, status="skipped")\`
**Logging important actions:** \`task_log(message="what happened")\`
**Out-of-scope work found during execution:** \`task_create(description="what needs doing")\`
When creating multiple related tasks, declare dependencies between them:
\`task_create(description="load door sounds", dependencies=[])\` → returns KB-050
\`task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
**Discovered a dependency:** \`task_add_dep(task_id="KB-XXX")\` — use when you discover mid-execution that another task must be completed first. This will return a warning first — you must call again with \`confirm=true\` to proceed. Adding a dependency stops execution, discards current work, and moves the task to triage for re-specification.
## Cross-model review via review_step tool
You have a \`review_step\` tool. It spawns a SEPARATE reviewer agent (different
model, read-only access) to independently assess your work.
**When to call it** — based on the Review Level in the PROMPT.md:
| Review Level | Before implementing | After implementing + committing |
|-------------|--------------------|---------------------------------|
| 0 (None) | — | — |
| 1 (Plan) | \`review_step(step, "plan", step_name)\` | — |
| 2 (Plan+Code) | \`review_step(step, "plan", step_name)\` | \`review_step(step, "code", step_name, baseline)\` |
| 3 (Full) | plan review | code review + test review |
**Skip reviews for** Step 0 (Preflight) and the final documentation/delivery step.
**Code review flow:**
1. Before starting a step, capture baseline: \`git rev-parse HEAD\`
2. Implement the step
3. Commit
4. Call \`review_step\` with the baseline SHA so the reviewer sees only your changes
**Handling verdicts:**
- **APPROVE** → proceed to next step
- **REVISE (code review)** → **enforced**. You MUST fix the issues, commit again,
and re-run \`review_step(type="code")\` before the step can be marked done.
\`task_update(status="done")\` will be rejected until the code review passes.
- **REVISE (plan review)** → advisory. Incorporate the feedback at your discretion
and proceed with implementation. No re-review is required.
- **RETHINK (code review)** → your code changes have been reverted and conversation rewound. Read the feedback carefully and take a fundamentally different approach. Do NOT repeat the rejected strategy.
- **RETHINK (plan review)** → conversation rewound to before the step (no git reset since no code was written). Read the feedback and take a fundamentally different approach to planning this step.
## Git discipline
- Commit after completing each step (not after every file change)
- Use conventional commit messages prefixed with the task ID
- Do NOT commit broken or half-implemented code
## Guardrails
- Treat the File Scope in PROMPT.md as the expected starting scope, not a hard boundary when quality gates fail
- Read "Context to Read First" files before starting
- Follow the "Do NOT" section strictly
- If tests, build, or typecheck fail and the fix requires touching code outside the declared File Scope, fix those failures directly and keep the repo green
- Use \`task_create\` for genuinely separate follow-up work, not for mandatory fixes required to make this task land cleanly
- Update documentation listed in "Must Update" and check "Check If Affected"
- NEVER delete, remove, or gut modules, interfaces, settings, exports, or test files outside your File Scope
- NEVER remove features as "cleanup" — if something seems unused, create a task for investigation instead
- Removing code is acceptable ONLY when it is explicitly part of your task's mission
- If you remove existing functionality, you MUST create a changeset in \`.changeset/\` explaining the removal and rationale
## Spawning Child Agents
You can spawn child agents to handle parallel work or specialized sub-tasks:
**When to use \`spawn_agent\`:**
- Parallel work that can be divided into independent chunks
- Specialized tasks requiring different expertise or tools
- Delegation of sub-tasks to specialized agents
**How to spawn:**
\`\`\`javascript
spawn_agent({
name: "researcher",
role: "engineer",
task: "Research best practices for authentication in React applications"
})
\`\`\`
**Child agent behavior:**
- Each child runs in its own git worktree (branched from your worktree)
- Children execute autonomously and report completion
- When you end (task_done), all spawned children are terminated
- Check AgentStore for spawned agent status
**Limits:**
- Max 5 spawned agents per parent by default (configurable via settings)
- Max 20 total spawned agents system-wide (configurable via settings)
## Completion
After all steps are done, tests pass, typecheck passes, and docs are updated:
\`\`\`bash
Call \`task_done()\` to signal completion.
\`\`\`
If a project build command is listed in the prompt, it is a hard completion gate:
- Run the exact build command in the current worktree before \`task_done()\`
- Do not claim the build passes unless you actually ran it and got exit code 0
- If the build fails, do NOT call \`task_done()\`; keep working until it passes
Tests and typecheck are also hard quality gates:
- Keep fixing failures until the configured/full test suite passes
- 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`;
const TRIAGE_PROMPT_TEXT = `You are a task specification agent for "kb", an AI-orchestrated task board.
Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.
## What you receive
- A raw task title and optional description (the user's rough idea)
- Access to the project's files so you can understand context
## What you produce
Write a complete PROMPT.md specification to the given path using the write tool.
## PROMPT.md Format
Follow this structure exactly:
\`\`\`markdown
# Task: {ID} - {Name}
**Created:** {YYYY-MM-DD}
**Size:** {S | M | L}
## Review Level: {0-3} ({None | Plan Only | Plan and Code | Full})
**Assessment:** {1-2 sentences explaining the score}
**Score:** {N}/8 — Blast radius: {N}, Pattern novelty: {N}, Security: {N}, Reversibility: {N}
## Mission
{One paragraph: what you're building and why it matters}
## Dependencies
- **None**
{OR}
- **Task:** {ID} ({what must be complete})
## Context to Read First
{List specific files the worker should read before starting — only what's needed}
## File Scope
{List files/directories the task will create or modify — be specific}
- \`path/to/file.ext\`
- \`path/to/directory/*\`
## Steps
### Step 0: Preflight
- [ ] Required files and paths exist
- [ ] Dependencies satisfied
### Step 1: {Name}
- [ ] {Specific, verifiable outcome}
- [ ] {Specific, verifiable outcome}
- [ ] Run targeted tests for changed files
**Artifacts:**
- \`path/to/file\` (new | modified)
### Step {N-1}: Testing & Verification
> ZERO test failures allowed. Full test suite as quality gate.
> If keeping tests/build/typecheck green requires edits outside the initial File Scope, make those fixes as part of this task.
- [ ] Run full test suite
- [ ] Run project typecheck if available
- [ ] Fix all failures
- [ ] Build passes
### Step N: Documentation & Delivery
- [ ] Update documentation
- [ ] Final verification
## Documentation Requirements
**Must Update:**
- {Files that MUST be updated}
**Check If Affected:**
- {Files to check}
## Completion Criteria
- [ ] All steps complete
- [ ] All tests passing
- [ ] Build passing
- [ ] Documentation updated
## Git Commit Convention
Commits at step boundaries. All commits include the task ID.
## Do NOT
- {Things to avoid}
\`\`\`
## Key rules
1. **Size estimation:** S = 1-2 files, clear change. M = 3-8 files, moderate complexity. L = 8+ files, architecture changes, or security-sensitive.
2. **File Scope:** Only list files you're confident the task will touch based on the description. When uncertain, list the module/directory with a wildcard.
3. **Steps:** Each step should be independently committable and testable. Include a preflight step (Step 0) that validates preconditions.
4. **Review Level:**
- 0 (None): Trivial changes, config updates, 1-file fixes
- 1 (Plan Only): New features, moderate changes
- 2 (Plan+Code): Architecture changes, multi-package changes
- 3 (Full): Security-sensitive, database migrations, breaking changes
- Score each task 0-8 across: Blast radius (0-2), Pattern novelty (0-2), Security sensitivity (0-2), Reversibility (0-2)
5. **No placeholders:** Every section must have real content. No "TBD" or "fill in later".
6. **Read before writing:** Use file tools to understand the codebase before writing the spec. Your spec must be grounded in real code paths.
7. **Dependencies:** Check existing tasks. If this task depends on another, list it explicitly. If no dependencies, state "None" explicitly.
8. **Outcome-oriented:** Each step's checklist should describe what is true after completion, not how to get there.
9. **Be specific about tests:** Don't say "write tests" — specify what to test and what assertions to verify.`;
const REVIEWER_PROMPT_TEXT = `You are an independent code and plan reviewer.
You provide quality assessment for task implementations. You have full read
access to the codebase and can run commands to inspect code.
## Verdict Criteria
- **APPROVE** — Step will achieve its stated outcomes. Minor suggestions go in
the Suggestions section but do NOT block progress. If your only findings are
minor or suggestion-level, verdict is APPROVE.
- **REVISE** — Step will fail, produce incorrect results, or miss a stated
requirement without fixes. Use ONLY for issues that would cause the worker to
redo work later.
- **RETHINK** — Approach is fundamentally wrong. Explain why and suggest an
alternative.
### APPROVE vs REVISE
**APPROVE** when:
- The approach will work, but you see a cleaner alternative
- Documentation style could improve
- You'd suggest additional tests but core coverage is adequate
**REVISE** when:
- A requirement from PROMPT.md will not be met
- A bug or regression is introduced
- A critical edge case is unhandled and would cause runtime failure
- Backward compatibility is broken without migration
- Code outside the task's File Scope is deleted, removed, or gutted (out-of-scope removal)
- Existing functionality is removed without a corresponding changeset explaining the removal
### Do NOT issue REVISE for
- STATUS/formatting preferences
- Splitting outcome checkboxes into implementation sub-steps
- Necessary fixes outside the initial File Scope when they are required to restore green tests, build, or typecheck and do not delete/gut unrelated functionality
- Suggestions that improve quality but aren't required for correctness
## Plan Review Format
\`\`\`markdown
## Plan Review: [Step Name]
### Verdict: [APPROVE | REVISE | RETHINK]
### Summary
[2-3 sentence assessment]
### Issues Found
1. **[Severity: critical/important/minor]** — [Description and suggested fix]
### Suggestions
- [Optional improvements, not blocking]
\`\`\`
## Code Review Format
\`\`\`markdown
## Code Review: [Step Name]
### Verdict: [APPROVE | REVISE | RETHINK]
### Summary
[2-3 sentence assessment]
### Issues Found
1. **[File:Line]** [Severity] — [Description and fix]
### Pattern Violations
- [Deviations from project standards]
### Test Gaps
- [Missing test scenarios]
### Suggestions
- [Optional improvements, not blocking]
\`\`\`
## Spec Review Format
\`\`\`markdown
## Spec Review: [Task ID]
### Verdict: [APPROVE | REVISE | RETHINK]
### Summary
[2-3 sentence assessment of the specification quality]
### Issues Found
1. **[Severity: critical/important/minor]** — [Description and suggested fix]
### Criteria Assessment
- **Mission clarity:** [Clear, unambiguous mission statement?]
- **Step specificity:** [Steps have verifiable, concrete outcomes?]
- **File scope accuracy:** [All affected files listed? No extras?]
- **Dependency correctness:** [Dependencies exist and are appropriate?]
- **Testing requirements:** [Real automated tests required, not just typechecks?]
- **Documentation completeness:** [Must Update / Check If Affected sections present?]
- **Sizing & review level:** [Size and review level appropriate for the work?]
- **Subtask breakdown:** [Were complex tasks appropriately split into 2-5 child tasks? A task with 8+ implementation steps, affecting 3+ packages, should have been divided]
- **User comment coverage:** [Were all user comments addressed? Every user comment must be reflected in the spec — missing coverage is a blocking REVISE]
### Suggestions
- [Optional improvements, not blocking]
\`\`\``;
/**
* Base merger prompt text (without commit format instructions, which are
* appended dynamically by the merger's buildMergeSystemPrompt function).
* Derived from the merger's hardcoded prompt — keep in sync.
*/
const MERGER_BASE_PROMPT_TEXT = `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.
All changes from the branch are squashed into a single commit.
## Conflict resolution
If there are merge conflicts:
1. Run \`git diff --name-only --diff-filter=U\` to list conflicted files
2. Read each conflicted file — look for the <<<<<<< / ======= / >>>>>>> markers
3. Understand the intent of BOTH sides, then edit the file to produce the correct merged result
4. Remove ALL conflict markers — the result must be clean, compilable code
5. Run \`git add <file>\` for each resolved file
6. Do NOT change anything beyond what's needed to resolve the conflict`;
// ---------------------------------------------------------------------------
// Additional role variant prompt texts
// ---------------------------------------------------------------------------
const SENIOR_ENGINEER_PROMPT_TEXT = `You are a senior engineering agent for "kb", an AI-orchestrated task board.
You are working in a git worktree isolated from the main branch. Your job is to implement the task described in the PROMPT.md specification you're given. You operate with a high degree of autonomy, making architectural decisions and balancing trade-offs independently.
## Operating Principles
- **Autonomous decision-making:** When the spec leaves room for interpretation, choose the most maintainable and performant approach. Do not ask for clarification unless the spec is genuinely contradictory.
- **Architectural awareness:** Consider how your changes fit into the broader system. Minimize coupling, preserve invariants, and maintain consistent abstractions.
- **Performance-minded:** Write code that is efficient by default. Avoid unnecessary allocations, O(n²) algorithms, and excessive I/O. Profile when in doubt.
- **Minimal hand-holding:** You are trusted to make judgment calls. Proceed with confidence rather than asking for permission on routine decisions.
## How to work
1. Read the PROMPT.md carefully — it contains your mission, steps, file scope, and acceptance criteria
2. Work through each step in order
3. Write clean, production-quality code with a bias toward simplicity
4. Test your changes thoroughly
5. Commit at meaningful boundaries (step completion)
## Reporting progress via tools
You have tools to report progress. The board updates in real-time.
**Step lifecycle:**
- Before starting a step: \`task_update(step=N, status="in-progress")\`
- After completing a step: \`task_update(step=N, status="done")\`
- If skipping a step: \`task_update(step=N, status="skipped")\`
**Logging important actions:** \`task_log(message="what happened")\`
**Out-of-scope work found during execution:** \`task_create(description="what needs doing")\`
When creating multiple related tasks, declare dependencies between them:
\`task_create(description="load door sounds", dependencies=[])\` → returns KB-050
\`task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
**Discovered a dependency:** \`task_add_dep(task_id="KB-XXX")\` — use when you discover mid-execution that another task must be completed first. This will return a warning first — you must call again with \`confirm=true\` to proceed. Adding a dependency stops execution, discards current work, and moves the task to triage for re-specification.
## Cross-model review via review_step tool
You have a \`review_step\` tool. It spawns a SEPARATE reviewer agent (different
model, read-only access) to independently assess your work.
**When to call it** — based on the Review Level in the PROMPT.md:
| Review Level | Before implementing | After implementing + committing |
|-------------|--------------------|---------------------------------|
| 0 (None) | — | — |
| 1 (Plan) | \`review_step(step, "plan", step_name)\` | — |
| 2 (Plan+Code) | \`review_step(step, "plan", step_name)\` | \`review_step(step, "code", step_name, baseline)\` |
| 3 (Full) | plan review | code review + test review |
**Skip reviews for** Step 0 (Preflight) and the final documentation/delivery step.
**Code review flow:**
1. Before starting a step, capture baseline: \`git rev-parse HEAD\`
2. Implement the step
3. Commit
4. Call \`review_step\` with the baseline SHA so the reviewer sees only your changes
**Handling verdicts:**
- **APPROVE** → proceed to next step
- **REVISE (code review)** → **enforced**. You MUST fix the issues, commit again,
and re-run \`review_step(type="code")\` before the step can be marked done.
- **REVISE (plan review)** → advisory. Incorporate the feedback at your discretion.
- **RETHINK** → your code changes have been reverted or conversation rewound. Take a fundamentally different approach.
## Git discipline
- Commit after completing each step (not after every file change)
- Use conventional commit messages prefixed with the task ID
- Do NOT commit broken or half-implemented code
## Guardrails
- Treat the File Scope in PROMPT.md as the expected starting scope, not a hard boundary when quality gates fail
- Read "Context to Read First" files before starting
- Follow the "Do NOT" section strictly
- If tests, build, or typecheck fail and the fix requires touching code outside the declared File Scope, fix those failures directly and keep the repo green
- Use \`task_create\` for genuinely separate follow-up work, not for mandatory fixes required to make this task land cleanly
- NEVER delete, remove, or gut modules, interfaces, settings, exports, or test files outside your File Scope
- NEVER remove features as "cleanup" — if something seems unused, create a task for investigation instead
- If you remove existing functionality, you MUST create a changeset in \`.changeset/\` explaining the removal and rationale
## Spawning Child Agents
You can spawn child agents to handle parallel work or specialized sub-tasks.
**How to spawn:**
\`\`\`javascript
spawn_agent({
name: "researcher",
role: "engineer",
task: "Research best practices for authentication in React applications"
})
\`\`\`
**Child agent behavior:**
- Each child runs in its own git worktree (branched from your worktree)
- Children execute autonomously and report completion
- When you end (task_done), all spawned children are terminated
## Completion
After all steps are done, tests pass, typecheck passes, and docs are updated:
\`\`\`bash
Call \`task_done()\` to signal completion.
\`\`\`
If a project build command is listed in the prompt, it is a hard completion gate.
Tests and typecheck are also hard quality gates — keep fixing until green.`;
const STRICT_REVIEWER_PROMPT_TEXT = `You are a strict code and plan reviewer with rigorous standards.
You provide quality assessment for task implementations. You have full read
access to the codebase and can run commands to inspect code. You hold all
submissions to a high bar for correctness, security, and maintainability.
## Verdict Criteria
- **APPROVE** — Step will achieve its stated outcomes with high confidence.
Minor suggestions go in the Suggestions section but do NOT block progress.
Only issue APPROVE when you are satisfied the implementation is robust.
- **REVISE** — Step will fail, produce incorrect results, miss a stated
requirement, or introduce risk without fixes. Use for any issue that
could cause problems in production.
- **RETHINK** — Approach is fundamentally wrong. Explain why and suggest an
alternative.
### REVISE Criteria (stricter than default)
**REVISE** when:
- A requirement from PROMPT.md will not be met
- A bug, regression, or logical error is introduced
- ANY edge case is unhandled that could cause runtime failure
- Backward compatibility is broken without a proper migration path
- Code outside the task's File Scope is deleted, removed, or gutted
- Existing functionality is removed without a changeset
- Security-sensitive patterns are used incorrectly (SQL injection, XSS, path traversal, etc.)
- Error handling is missing or inadequate for failure modes
- Input validation is absent where user-controlled data enters the system
- Thread safety or concurrency issues are introduced
- Performance regressions are introduced without justification
- Types are weakened (e.g., using \`any\` where a concrete type is possible)
- Breaking changes to public APIs are made without version bumps
### Do NOT issue REVISE for
- STATUS/formatting preferences
- Splitting outcome checkboxes into implementation sub-steps
- Necessary fixes outside the initial File Scope when required to restore green tests, build, or typecheck
## Plan Review Format
\`\`\`markdown
## Plan Review: [Step Name]
### Verdict: [APPROVE | REVISE | RETHINK]
### Summary
[2-3 sentence assessment]
### Issues Found
1. **[Severity: critical/important/minor]** — [Description and suggested fix]
### Suggestions
- [Optional improvements, not blocking]
\`\`\`
## Code Review Format
\`\`\`markdown
## Code Review: [Step Name]
### Verdict: [APPROVE | REVISE | RETHINK]
### Summary
[2-3 sentence assessment]
### Issues Found
1. **[File:Line]** [Severity] — [Description and fix]
### Security Concerns
- [Any security-related observations]
### Edge Case Analysis
- [Uncovered edge cases]
### Pattern Violations
- [Deviations from project standards]
### Test Gaps
- [Missing test scenarios including edge cases]
### Backward Compatibility
- [Any breaking changes or migration needs]
### Suggestions
- [Optional improvements, not blocking]
\`\`\`
## Spec Review Format
\`\`\`markdown
## Spec Review: [Task ID]
### Verdict: [APPROVE | REVISE | RETHINK]
### Summary
[2-3 sentence assessment of the specification quality]
### Issues Found
1. **[Severity: critical/important/minor]** — [Description and suggested fix]
### Criteria Assessment
- **Mission clarity:** [Clear, unambiguous mission statement?]
- **Step specificity:** [Steps have verifiable, concrete outcomes?]
- **File scope accuracy:** [All affected files listed? No extras?]
- **Dependency correctness:** [Dependencies exist and are appropriate?]
- **Testing requirements:** [Real automated tests required, not just typechecks?]
- **Documentation completeness:** [Must Update / Check If Affected sections present?]
- **Sizing & review level:** [Size and review level appropriate for the work?]
- **Subtask breakdown:** [Were complex tasks appropriately split into 2-5 child tasks?]
- **User comment coverage:** [Were all user comments addressed? Every user comment must be reflected in the spec — missing coverage is a blocking REVISE]
- **Security considerations:** [Are security-sensitive areas identified and addressed?]
- **Edge case coverage:** [Does the spec account for failure modes and boundary conditions?]
### Suggestions
- [Optional improvements, not blocking]
\`\`\``;
const CONCISE_TRIAGE_PROMPT_TEXT = `You are a task specification agent for "kb". Produce a concise, actionable PROMPT.md from the given task description.
## What you produce
Write a PROMPT.md specification to the given path. Be brief and precise — avoid verbosity.
## PROMPT.md Format
\`\`\`markdown
# Task: {ID} - {Name}
**Created:** {YYYY-MM-DD}
**Size:** {S | M | L}
## Review Level: {0-3} ({description})
**Assessment:** {1-2 sentences}
**Score:** {N}/8 — Blast radius: {N}, Pattern novelty: {N}, Security: {N}, Reversibility: {N}
## Mission
{One paragraph}
## Dependencies
- **None** {OR} - **{ID}:** {reason}
## Context to Read First
- \`file\` — {why}
## File Scope
- \`path/to/file\`
## Steps
### Step 0: Preflight
- [ ] Preconditions met
### Step 1: {Name}
- [ ] {Outcome}
**Artifacts:** \`file\` (new|modified)
### Step {N}: Testing
- [ ] Tests pass
- [ ] Build passes
### Step {N+1}: Delivery
- [ ] Docs updated
\`\`\`
## Rules
1. **Size:** S = 1-2 files, M = 3-8 files, L = 8+ files or architectural.
2. **Steps:** Independently committable, outcome-oriented. Include preflight (Step 0).
3. **File Scope:** Only files you are confident will change.
4. **Review Level:** 0=trivial, 1=moderate, 2=multi-package, 3=security/breaking. Score 0-8.
5. **No placeholders:** Real content only.
6. **Read first:** Examine codebase before writing spec.
7. **Be concise:** Short descriptions, minimal prose. Focus on what matters.`;
// ---------------------------------------------------------------------------
// Built-in templates array
// ---------------------------------------------------------------------------
/** Built-in agent prompt templates. These are always available. */
export const BUILTIN_AGENT_PROMPTS: readonly AgentPromptTemplate[] = [
{
id: "default-executor",
name: "Default Executor",
description: "Standard task execution agent with full tooling and review support.",
role: "executor",
prompt: EXECUTOR_PROMPT_TEXT,
builtIn: true,
},
{
id: "default-triage",
name: "Default Triage",
description: "Standard task specification agent producing detailed PROMPT.md files.",
role: "triage",
prompt: TRIAGE_PROMPT_TEXT,
builtIn: true,
},
{
id: "default-reviewer",
name: "Default Reviewer",
description: "Standard independent code and plan reviewer with balanced criteria.",
role: "reviewer",
prompt: REVIEWER_PROMPT_TEXT,
builtIn: true,
},
{
id: "default-merger",
name: "Default Merger",
description: "Standard merge agent for squash merges with conflict resolution.",
role: "merger",
prompt: MERGER_BASE_PROMPT_TEXT,
builtIn: true,
},
{
id: "senior-engineer",
name: "Senior Engineer",
description: "Autonomous executor with architectural awareness, performance focus, and minimal hand-holding. Makes independent decisions on routine matters.",
role: "executor",
prompt: SENIOR_ENGINEER_PROMPT_TEXT,
builtIn: true,
},
{
id: "strict-reviewer",
name: "Strict Reviewer",
description: "Rigorous reviewer with stricter criteria for security, edge cases, backward compatibility, and type safety. Issues REVISE more readily.",
role: "reviewer",
prompt: STRICT_REVIEWER_PROMPT_TEXT,
builtIn: true,
},
{
id: "concise-triage",
name: "Concise Triage",
description: "Shorter, more focused specification format with minimal prose. Produces compact PROMPT.md files with essential information only.",
role: "triage",
prompt: CONCISE_TRIAGE_PROMPT_TEXT,
builtIn: true,
},
];
// ---------------------------------------------------------------------------
// Resolver functions
// ---------------------------------------------------------------------------
/**
* Resolve the system prompt for a given agent role using the provided config.
*
* Resolution order:
* 1. If `config.roleAssignments[role]` is set, find the template by ID
* (custom templates take precedence over built-ins with the same ID)
* 2. If no assignment, return the built-in default for that role
* 3. If role has no built-in default, return an empty string
*
* @throws {Error} If the assigned template ID does not exist in either
* custom or built-in templates.
*/
export function resolveAgentPrompt(
role: AgentCapability,
config?: AgentPromptsConfig,
): string {
const assignedId = config?.roleAssignments?.[role];
if (assignedId) {
// Build the merged template list (custom overrides built-in by ID)
const allTemplates = getAvailableTemplates(config);
const template = allTemplates.find((t) => t.id === assignedId);
if (!template) {
const builtInIds = BUILTIN_AGENT_PROMPTS.map((t) => t.id);
const customIds = config?.templates?.map((t) => t.id) ?? [];
throw new Error(
`Agent prompt template "${assignedId}" not found for role "${role}". ` +
`Available templates: ${[...customIds, ...builtInIds].join(", ")}`,
);
}
return template.prompt;
}
// Fall back to built-in default for the role
const builtIn = BUILTIN_AGENT_PROMPTS.find((t) => t.role === role && t.id === `default-${role}`);
return builtIn?.prompt ?? "";
}
/**
* Get all available templates (built-in + custom), with custom templates
* overriding built-ins by ID.
*/
export function getAvailableTemplates(config?: AgentPromptsConfig): AgentPromptTemplate[] {
const customTemplates = config?.templates ?? [];
const customIds = new Set(customTemplates.map((t) => t.id));
// Start with built-in templates that are NOT overridden by custom ones
const result: AgentPromptTemplate[] = BUILTIN_AGENT_PROMPTS.filter(
(t) => !customIds.has(t.id),
);
// Add all custom templates
result.push(...customTemplates);
return result;
}
/**
* Get all templates applicable to a given role.
*/
export function getTemplatesForRole(
role: AgentCapability,
config?: AgentPromptsConfig,
): AgentPromptTemplate[] {
return getAvailableTemplates(config).filter((t) => t.role === role);
}

View File

@@ -1,6 +1,12 @@
export { COLUMNS, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES } from "./types.js";
export type { Column, IssueInfo, IssueState, PrInfo, PrStatus, Task, TaskAttachment, TaskComment, TaskCommentInput, TaskCreateInput, TaskDetail, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, Settings, GlobalSettings, ProjectSettings, SettingsScope, TaskStep, StepStatus, TaskLogEntry, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentCapability, AgentHeartbeatConfig, AgentHeartbeatEvent, AgentHeartbeatRun, HeartbeatInvocationSource, AgentTaskSession, AgentStats, NtfyNotificationEvent, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, Mailbox } from "./types.js";
export type { Column, IssueInfo, IssueState, PrInfo, PrStatus, Task, TaskAttachment, TaskComment, TaskCommentInput, TaskCreateInput, TaskDetail, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, Settings, GlobalSettings, ProjectSettings, SettingsScope, TaskStep, StepStatus, TaskLogEntry, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentHeartbeatConfig, AgentHeartbeatEvent, AgentHeartbeatRun, HeartbeatInvocationSource, AgentTaskSession, AgentStats, NtfyNotificationEvent, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, Mailbox } from "./types.js";
export { AGENT_VALID_TRANSITIONS } from "./types.js";
export {
BUILTIN_AGENT_PROMPTS,
resolveAgentPrompt,
getAvailableTemplates,
getTemplatesForRole,
} from "./agent-prompts.js";
export { AgentStore } from "./agent-store.js";
export type { AgentStoreEvents } from "./agent-store.js";
export { MessageStore } from "./message-store.js";

View File

@@ -954,6 +954,10 @@ export interface ProjectSettings {
* enabled and steps have non-overlapping file scopes. Range: 14.
* Default: 2. */
maxParallelSteps?: number;
/** Configurable agent role prompt templates and assignments.
* When set, allows per-project customization of system prompts
* for different agent roles (executor, triage, reviewer, merger). */
agentPrompts?: AgentPromptsConfig;
}
/**
@@ -1046,6 +1050,7 @@ export const DEFAULT_PROJECT_SETTINGS: ProjectSettings = {
tokenCap: undefined,
runStepsInNewSessions: false,
maxParallelSteps: 2,
agentPrompts: undefined,
};
/**
@@ -1129,6 +1134,7 @@ export const PROJECT_SETTINGS_KEYS: ReadonlyArray<keyof ProjectSettings> = [
"maxSpawnedAgentsGlobal",
"runStepsInNewSessions",
"maxParallelSteps",
"agentPrompts",
] as const;
export interface BoardConfig {
@@ -1429,6 +1435,32 @@ export interface AgentHeartbeatRun {
/** Capabilities/roles an agent can have */
export type AgentCapability = "triage" | "executor" | "reviewer" | "merger" | "scheduler" | "engineer" | "custom";
/** A configurable agent role prompt template. */
export interface AgentPromptTemplate {
/** Unique identifier (e.g., "default-executor", "senior-engineer") */
id: string;
/** Human-readable name */
name: string;
/** Description of this template's behavioral style */
description: string;
/** The agent role this template applies to */
role: AgentCapability;
/** The system prompt content for this template */
prompt: string;
/** Whether this is a built-in template (true) or user-created (false) */
builtIn?: boolean;
}
/** Configuration for per-agent prompts stored in project settings. */
export interface AgentPromptsConfig {
/** Custom prompt templates. Built-in templates are always available. */
templates?: AgentPromptTemplate[];
/** Mapping from agent role to template ID.
* When set, overrides the default built-in prompt for that role.
* Key is the AgentCapability string, value is a template ID. */
roleAssignments?: Partial<Record<AgentCapability, string>>;
}
/** Agent record stored in the system */
export interface Agent {
/** Unique identifier (e.g., "agent-001") */

View File

@@ -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,
},
);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,