feat(KB-147): add planning and validator model settings
- Extend core Settings types with planningProvider, planningModelId, validatorProvider, validatorModelId fields - Add model selectors in Settings UI for planning and validator models - Use planning model for triage task specification generation - Use validator model for specification review in triage - Use validator model for code reviews in executor agent - Update AGENTS.md with model hierarchy documentation
This commit is contained in:
13
.changeset/add-planning-validator-models.md
Normal file
13
.changeset/add-planning-validator-models.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
"@dustinbyrne/kb": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add separate planning and verification model settings
|
||||||
|
|
||||||
|
Users can now configure separate AI models for:
|
||||||
|
- **Planning Model**: Used for triage/specification when writing PROMPT.md files
|
||||||
|
- **Validator Model**: Used for code and specification review
|
||||||
|
|
||||||
|
Both settings are available in the dashboard under Settings > Model, alongside the existing Default Model setting. When not configured, they fall back to the Default Model.
|
||||||
|
|
||||||
|
This allows using specialized models for different phases of task execution - for example, a fast/cheap model for planning and a powerful model for verification.
|
||||||
24
AGENTS.md
24
AGENTS.md
@@ -286,9 +286,31 @@ To clear overrides, select "Use default" for both fields and save.
|
|||||||
|
|
||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
- Triage (task specification) always uses global defaults — per-task overrides apply only to execution and review
|
- **Planning Model**: Task specification (triage) uses the global `planningProvider`/`planningModelId` settings — there is no per-task override for planning
|
||||||
- Both provider and modelId must be set together; partial configuration falls back to defaults
|
- Both provider and modelId must be set together; partial configuration falls back to defaults
|
||||||
|
|
||||||
|
## Model Settings Hierarchy
|
||||||
|
|
||||||
|
The system uses the following precedence for model selection:
|
||||||
|
|
||||||
|
**For Task Specification (Triage):**
|
||||||
|
1. Global `planningProvider`/`planningModelId` (if both set)
|
||||||
|
2. Global `defaultProvider`/`defaultModelId` (if both set)
|
||||||
|
3. Automatic model resolution (fallback)
|
||||||
|
|
||||||
|
**For Task Execution (Executor):**
|
||||||
|
1. Per-task `modelProvider`/`modelId` (if both set)
|
||||||
|
2. Global `defaultProvider`/`defaultModelId` (if both set)
|
||||||
|
3. Automatic model resolution (fallback)
|
||||||
|
|
||||||
|
**For Code/Spec Review (Reviewer):**
|
||||||
|
1. Per-task `validatorModelProvider`/`validatorModelId` (if both set)
|
||||||
|
2. Global `validatorProvider`/`validatorModelId` (if both set)
|
||||||
|
3. Global `defaultProvider`/`defaultModelId` (if both set)
|
||||||
|
4. Automatic model resolution (fallback)
|
||||||
|
|
||||||
|
Configure global model settings in the dashboard under **Settings > Model**.
|
||||||
|
|
||||||
## Archive Cleanup
|
## Archive Cleanup
|
||||||
|
|
||||||
Archived tasks can be cleaned up from the filesystem to reduce storage overhead while preserving the ability to restore them later.
|
Archived tasks can be cleaned up from the filesystem to reduce storage overhead while preserving the ability to restore them later.
|
||||||
|
|||||||
@@ -290,6 +290,22 @@ export interface Settings {
|
|||||||
* Must be set together with `defaultProvider`. When both are undefined,
|
* Must be set together with `defaultProvider`. When both are undefined,
|
||||||
* the engine uses pi's automatic model resolution. */
|
* the engine uses pi's automatic model resolution. */
|
||||||
defaultModelId?: string;
|
defaultModelId?: string;
|
||||||
|
/** AI model provider for planning/triage (specification) agent.
|
||||||
|
* Must be set together with `planningModelId`. When both are undefined,
|
||||||
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
|
planningProvider?: string;
|
||||||
|
/** AI model ID for planning/triage (specification) agent.
|
||||||
|
* Must be set together with `planningProvider`. When both are undefined,
|
||||||
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
|
planningModelId?: string;
|
||||||
|
/** AI model provider for validator/reviewer agent.
|
||||||
|
* Must be set together with `validatorModelId`. When both are undefined,
|
||||||
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
|
validatorProvider?: string;
|
||||||
|
/** AI model ID for validator/reviewer agent.
|
||||||
|
* Must be set together with `validatorProvider`. When both are undefined,
|
||||||
|
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||||
|
validatorModelId?: string;
|
||||||
/** Default thinking effort level for AI agent sessions.
|
/** Default thinking effort level for AI agent sessions.
|
||||||
* Controls how much reasoning effort the model uses — higher levels
|
* Controls how much reasoning effort the model uses — higher levels
|
||||||
* produce better results but cost more. When undefined, the engine
|
* produce better results but cost more. When undefined, the engine
|
||||||
@@ -342,6 +358,10 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
includeTaskIdInCommit: true,
|
includeTaskIdInCommit: true,
|
||||||
defaultProvider: undefined,
|
defaultProvider: undefined,
|
||||||
defaultModelId: undefined,
|
defaultModelId: undefined,
|
||||||
|
planningProvider: undefined,
|
||||||
|
planningModelId: undefined,
|
||||||
|
validatorProvider: undefined,
|
||||||
|
validatorModelId: undefined,
|
||||||
defaultThinkingLevel: undefined,
|
defaultThinkingLevel: undefined,
|
||||||
autoResolveConflicts: true,
|
autoResolveConflicts: true,
|
||||||
smartConflictResolution: true,
|
smartConflictResolution: true,
|
||||||
|
|||||||
@@ -269,6 +269,12 @@ export function SettingsModal({
|
|||||||
const selectedValue = form.defaultProvider && form.defaultModelId
|
const selectedValue = form.defaultProvider && form.defaultModelId
|
||||||
? `${form.defaultProvider}/${form.defaultModelId}`
|
? `${form.defaultProvider}/${form.defaultModelId}`
|
||||||
: "";
|
: "";
|
||||||
|
const planningValue = form.planningProvider && form.planningModelId
|
||||||
|
? `${form.planningProvider}/${form.planningModelId}`
|
||||||
|
: "";
|
||||||
|
const validatorValue = form.validatorProvider && form.validatorModelId
|
||||||
|
? `${form.validatorProvider}/${form.validatorModelId}`
|
||||||
|
: "";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h4 className="settings-section-heading">Model</h4>
|
<h4 className="settings-section-heading">Model</h4>
|
||||||
@@ -279,29 +285,77 @@ export function SettingsModal({
|
|||||||
No models available. Configure authentication first.
|
No models available. Configure authentication first.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="form-group">
|
<>
|
||||||
<label htmlFor="defaultModel">Default Model</label>
|
<div className="form-group">
|
||||||
<CustomModelDropdown
|
<label htmlFor="defaultModel">Default Model</label>
|
||||||
id="defaultModel"
|
<CustomModelDropdown
|
||||||
label="Default Model"
|
id="defaultModel"
|
||||||
models={availableModels}
|
label="Default Model"
|
||||||
value={selectedValue}
|
models={availableModels}
|
||||||
onChange={(val) => {
|
value={selectedValue}
|
||||||
if (!val) {
|
onChange={(val) => {
|
||||||
setForm((f) => ({ ...f, defaultProvider: undefined, defaultModelId: undefined }));
|
if (!val) {
|
||||||
} else {
|
setForm((f) => ({ ...f, defaultProvider: undefined, defaultModelId: undefined }));
|
||||||
const slashIdx = val.indexOf("/");
|
} else {
|
||||||
setForm((f) => ({
|
const slashIdx = val.indexOf("/");
|
||||||
...f,
|
setForm((f) => ({
|
||||||
defaultProvider: val.slice(0, slashIdx),
|
...f,
|
||||||
defaultModelId: val.slice(slashIdx + 1),
|
defaultProvider: val.slice(0, slashIdx),
|
||||||
}));
|
defaultModelId: val.slice(slashIdx + 1),
|
||||||
}
|
}));
|
||||||
}}
|
}
|
||||||
placeholder="Use default"
|
}}
|
||||||
/>
|
placeholder="Use default"
|
||||||
<small>Select the AI model used for agent sessions. "Use default" lets the engine choose automatically.</small>
|
/>
|
||||||
</div>
|
<small>Default AI model used for task execution when no per-task override is set. "Use default" lets the engine choose automatically.</small>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="planningModel">Planning Model</label>
|
||||||
|
<CustomModelDropdown
|
||||||
|
id="planningModel"
|
||||||
|
label="Planning Model"
|
||||||
|
models={availableModels}
|
||||||
|
value={planningValue}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (!val) {
|
||||||
|
setForm((f) => ({ ...f, planningProvider: undefined, planningModelId: undefined }));
|
||||||
|
} else {
|
||||||
|
const slashIdx = val.indexOf("/");
|
||||||
|
setForm((f) => ({
|
||||||
|
...f,
|
||||||
|
planningProvider: val.slice(0, slashIdx),
|
||||||
|
planningModelId: val.slice(slashIdx + 1),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="Use default"
|
||||||
|
/>
|
||||||
|
<small>AI model used for task planning and specification (triage). Falls back to Default Model when not set.</small>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="validatorModel">Validator Model</label>
|
||||||
|
<CustomModelDropdown
|
||||||
|
id="validatorModel"
|
||||||
|
label="Validator Model"
|
||||||
|
models={availableModels}
|
||||||
|
value={validatorValue}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (!val) {
|
||||||
|
setForm((f) => ({ ...f, validatorProvider: undefined, validatorModelId: undefined }));
|
||||||
|
} else {
|
||||||
|
const slashIdx = val.indexOf("/");
|
||||||
|
setForm((f) => ({
|
||||||
|
...f,
|
||||||
|
validatorProvider: val.slice(0, slashIdx),
|
||||||
|
validatorModelId: val.slice(slashIdx + 1),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="Use default"
|
||||||
|
/>
|
||||||
|
<small>AI model used for code and specification review. Falls back to Default Model when not set.</small>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{(() => {
|
{(() => {
|
||||||
const selectedModel = availableModels.find(
|
const selectedModel = availableModels.find(
|
||||||
|
|||||||
@@ -851,8 +851,9 @@ export class TaskExecutor {
|
|||||||
defaultProvider: settings.defaultProvider,
|
defaultProvider: settings.defaultProvider,
|
||||||
defaultModelId: settings.defaultModelId,
|
defaultModelId: settings.defaultModelId,
|
||||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||||
validatorModelProvider: detail.validatorModelProvider,
|
// Per-task validator overrides take precedence over global validator settings
|
||||||
validatorModelId: detail.validatorModelId,
|
validatorModelProvider: detail.validatorModelProvider ?? settings.validatorProvider,
|
||||||
|
validatorModelId: detail.validatorModelId ?? settings.validatorModelId,
|
||||||
store,
|
store,
|
||||||
taskId,
|
taskId,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -465,8 +465,13 @@ export class TriageProcessor {
|
|||||||
onThinking: agentLogger.onThinking,
|
onThinking: agentLogger.onThinking,
|
||||||
onToolStart: agentLogger.onToolStart,
|
onToolStart: agentLogger.onToolStart,
|
||||||
onToolEnd: agentLogger.onToolEnd,
|
onToolEnd: agentLogger.onToolEnd,
|
||||||
defaultProvider: settings.defaultProvider,
|
// Use planning model settings if both provider and modelId are set, otherwise fall back to defaults
|
||||||
defaultModelId: settings.defaultModelId,
|
defaultProvider: settings.planningProvider && settings.planningModelId
|
||||||
|
? settings.planningProvider
|
||||||
|
: settings.defaultProvider,
|
||||||
|
defaultModelId: settings.planningProvider && settings.planningModelId
|
||||||
|
? settings.planningModelId
|
||||||
|
: settings.defaultModelId,
|
||||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -846,6 +851,8 @@ export class TriageProcessor {
|
|||||||
defaultProvider?: string;
|
defaultProvider?: string;
|
||||||
defaultModelId?: string;
|
defaultModelId?: string;
|
||||||
defaultThinkingLevel?: string;
|
defaultThinkingLevel?: string;
|
||||||
|
validatorProvider?: string;
|
||||||
|
validatorModelId?: string;
|
||||||
},
|
},
|
||||||
): ToolDefinition {
|
): ToolDefinition {
|
||||||
const store = this.store;
|
const store = this.store;
|
||||||
@@ -904,6 +911,8 @@ export class TriageProcessor {
|
|||||||
onText: (delta) => options.onAgentText?.(taskId, delta),
|
onText: (delta) => options.onAgentText?.(taskId, delta),
|
||||||
defaultProvider: settings.defaultProvider,
|
defaultProvider: settings.defaultProvider,
|
||||||
defaultModelId: settings.defaultModelId,
|
defaultModelId: settings.defaultModelId,
|
||||||
|
validatorModelProvider: settings.validatorProvider,
|
||||||
|
validatorModelId: settings.validatorModelId,
|
||||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||||
store,
|
store,
|
||||||
taskId,
|
taskId,
|
||||||
|
|||||||
Reference in New Issue
Block a user