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:
50
AGENTS.md
50
AGENTS.md
@@ -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.
|
||||
|
||||
279
packages/core/src/agent-prompts.test.ts
Normal file
279
packages/core/src/agent-prompts.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
808
packages/core/src/agent-prompts.ts
Normal file
808
packages/core/src/agent-prompts.ts
Normal 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);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -954,6 +954,10 @@ export interface ProjectSettings {
|
||||
* enabled and steps have non-overlapping file scopes. Range: 1–4.
|
||||
* 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") */
|
||||
|
||||
@@ -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