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
|
||||
|
||||
- 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
|
||||
|
||||
## 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
|
||||
|
||||
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,
|
||||
* the engine uses pi's automatic model resolution. */
|
||||
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.
|
||||
* Controls how much reasoning effort the model uses — higher levels
|
||||
* produce better results but cost more. When undefined, the engine
|
||||
@@ -342,6 +358,10 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
includeTaskIdInCommit: true,
|
||||
defaultProvider: undefined,
|
||||
defaultModelId: undefined,
|
||||
planningProvider: undefined,
|
||||
planningModelId: undefined,
|
||||
validatorProvider: undefined,
|
||||
validatorModelId: undefined,
|
||||
defaultThinkingLevel: undefined,
|
||||
autoResolveConflicts: true,
|
||||
smartConflictResolution: true,
|
||||
|
||||
@@ -269,6 +269,12 @@ export function SettingsModal({
|
||||
const selectedValue = 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 (
|
||||
<>
|
||||
<h4 className="settings-section-heading">Model</h4>
|
||||
@@ -279,6 +285,7 @@ export function SettingsModal({
|
||||
No models available. Configure authentication first.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<label htmlFor="defaultModel">Default Model</label>
|
||||
<CustomModelDropdown
|
||||
@@ -300,8 +307,55 @@ export function SettingsModal({
|
||||
}}
|
||||
placeholder="Use default"
|
||||
/>
|
||||
<small>Select the AI model used for agent sessions. "Use default" lets the engine choose automatically.</small>
|
||||
<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(
|
||||
|
||||
@@ -851,8 +851,9 @@ export class TaskExecutor {
|
||||
defaultProvider: settings.defaultProvider,
|
||||
defaultModelId: settings.defaultModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
validatorModelProvider: detail.validatorModelProvider,
|
||||
validatorModelId: detail.validatorModelId,
|
||||
// Per-task validator overrides take precedence over global validator settings
|
||||
validatorModelProvider: detail.validatorModelProvider ?? settings.validatorProvider,
|
||||
validatorModelId: detail.validatorModelId ?? settings.validatorModelId,
|
||||
store,
|
||||
taskId,
|
||||
},
|
||||
|
||||
@@ -465,8 +465,13 @@ export class TriageProcessor {
|
||||
onThinking: agentLogger.onThinking,
|
||||
onToolStart: agentLogger.onToolStart,
|
||||
onToolEnd: agentLogger.onToolEnd,
|
||||
defaultProvider: settings.defaultProvider,
|
||||
defaultModelId: settings.defaultModelId,
|
||||
// Use planning model settings if both provider and modelId are set, otherwise fall back to defaults
|
||||
defaultProvider: settings.planningProvider && settings.planningModelId
|
||||
? settings.planningProvider
|
||||
: settings.defaultProvider,
|
||||
defaultModelId: settings.planningProvider && settings.planningModelId
|
||||
? settings.planningModelId
|
||||
: settings.defaultModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
});
|
||||
|
||||
@@ -846,6 +851,8 @@ export class TriageProcessor {
|
||||
defaultProvider?: string;
|
||||
defaultModelId?: string;
|
||||
defaultThinkingLevel?: string;
|
||||
validatorProvider?: string;
|
||||
validatorModelId?: string;
|
||||
},
|
||||
): ToolDefinition {
|
||||
const store = this.store;
|
||||
@@ -904,6 +911,8 @@ export class TriageProcessor {
|
||||
onText: (delta) => options.onAgentText?.(taskId, delta),
|
||||
defaultProvider: settings.defaultProvider,
|
||||
defaultModelId: settings.defaultModelId,
|
||||
validatorModelProvider: settings.validatorProvider,
|
||||
validatorModelId: settings.validatorModelId,
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
store,
|
||||
taskId,
|
||||
|
||||
Reference in New Issue
Block a user