-
Notifications
You must be signed in to change notification settings - Fork 5
feat: supervisor model resolution from subagent frontmatter (t132.5) #787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -75,6 +75,12 @@ | |||||
| # Optimistic locking: claim = commit+push TODO.md; push failure = race lost | ||||||
| # GH Issue creation: opt-in via --with-issue flag or SUPERVISOR_AUTO_ISSUE=true | ||||||
| # | ||||||
| # Model resolution (t132.5): | ||||||
| # Priority: task explicit model > subagent frontmatter > fallback chain > static default | ||||||
| # Tier names (haiku/sonnet/opus/flash/pro/grok) mapped to concrete provider/model strings | ||||||
| # resolve_task_model() orchestrates: resolve_model_from_frontmatter() + resolve_model() | ||||||
| # Integrates with: fallback-chain-helper.sh (t132.4), model-availability-helper.sh (t132.3) | ||||||
| # | ||||||
| # Database: ~/.aidevops/.agent-workspace/supervisor/supervisor.db | ||||||
| # | ||||||
| # Integration: | ||||||
|
|
@@ -2692,6 +2698,12 @@ resolve_model() { | |||||
| return 0 | ||||||
| fi | ||||||
|
|
||||||
| # If tier is already a full provider/model string (contains /), return as-is | ||||||
| if [[ "$tier" == *"/"* ]]; then | ||||||
| echo "$tier" | ||||||
| return 0 | ||||||
| fi | ||||||
|
|
||||||
| # Try fallback-chain-helper.sh for full chain resolution (t132.4) | ||||||
| # This walks the configured chain including gateway providers | ||||||
| local chain_helper="${SCRIPT_DIR}/fallback-chain-helper.sh" | ||||||
|
|
@@ -2718,19 +2730,140 @@ resolve_model() { | |||||
| log_verbose "model-availability-helper.sh could not resolve tier '$tier', using static default" | ||||||
| fi | ||||||
|
|
||||||
| # Static fallback (no availability helper or resolution failed) | ||||||
| # Static fallback: map tier names to concrete models (t132.5) | ||||||
| case "$tier" in | ||||||
| coding) | ||||||
| opus|coding) | ||||||
| echo "anthropic/claude-opus-4-6" | ||||||
| ;; | ||||||
| eval|health) | ||||||
| sonnet|eval|health) | ||||||
| echo "anthropic/claude-sonnet-4-5" | ||||||
| ;; | ||||||
| haiku) | ||||||
| echo "anthropic/claude-3-5-haiku-20241022" | ||||||
| ;; | ||||||
| flash) | ||||||
| echo "google/gemini-2.5-flash-preview-05-20" | ||||||
| ;; | ||||||
| pro) | ||||||
| echo "google/gemini-2.5-pro-preview-06-05" | ||||||
| ;; | ||||||
| grok) | ||||||
| echo "xai/grok-3" | ||||||
| ;; | ||||||
| *) | ||||||
| # Unknown tier — treat as coding tier default | ||||||
| echo "anthropic/claude-opus-4-6" | ||||||
| ;; | ||||||
| esac | ||||||
|
|
||||||
| return 0 | ||||||
| } | ||||||
|
|
||||||
| ####################################### | ||||||
| # Read model: field from subagent YAML frontmatter (t132.5) | ||||||
| # Searches deployed agents dir and repo .agents/ dir | ||||||
| # Returns the model value or empty string if not found | ||||||
| ####################################### | ||||||
| resolve_model_from_frontmatter() { | ||||||
| local subagent_name="$1" | ||||||
| local repo="${2:-.}" | ||||||
|
|
||||||
| # Search paths for subagent files | ||||||
| local -a search_paths=( | ||||||
| "${HOME}/.aidevops/agents" | ||||||
| "${repo}/.agents" | ||||||
| ) | ||||||
|
|
||||||
| local agents_dir subagent_file model_value | ||||||
| for agents_dir in "${search_paths[@]}"; do | ||||||
| [[ -d "$agents_dir" ]] || continue | ||||||
|
|
||||||
| # Try exact path first (e.g., "tools/ai-assistants/models/opus.md") | ||||||
| subagent_file="${agents_dir}/${subagent_name}" | ||||||
| [[ -f "$subagent_file" ]] || subagent_file="${agents_dir}/${subagent_name}.md" | ||||||
|
|
||||||
| if [[ -f "$subagent_file" ]]; then | ||||||
| # Extract model: from YAML frontmatter (between --- delimiters) | ||||||
| model_value=$(sed -n '/^---$/,/^---$/{ /^model:/{ s/^model:[[:space:]]*//; p; q; } }' "$subagent_file" 2>/dev/null) || true | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of
Suggested change
References
|
||||||
| if [[ -n "$model_value" ]]; then | ||||||
| echo "$model_value" | ||||||
| return 0 | ||||||
| fi | ||||||
| fi | ||||||
|
|
||||||
| # Try finding by name in subdirectories | ||||||
| # shellcheck disable=SC2044 | ||||||
| local found_file | ||||||
| found_file=$(find "$agents_dir" -name "${subagent_name}.md" -type f 2>/dev/null | head -1) || true | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of
Suggested change
References
|
||||||
| if [[ -n "$found_file" && -f "$found_file" ]]; then | ||||||
| model_value=$(sed -n '/^---$/,/^---$/{ /^model:/{ s/^model:[[:space:]]*//; p; q; } }' "$found_file" 2>/dev/null) || true | ||||||
| if [[ -n "$model_value" ]]; then | ||||||
| echo "$model_value" | ||||||
| return 0 | ||||||
| fi | ||||||
| fi | ||||||
| done | ||||||
|
|
||||||
| return 1 | ||||||
| } | ||||||
|
|
||||||
| ####################################### | ||||||
| # Resolve the model for a task (t132.5) | ||||||
| # Priority: 1) Task's explicit model (if not default) | ||||||
| # 2) Subagent frontmatter model: | ||||||
| # 3) resolve_model() with tier/fallback chain | ||||||
| # Returns the resolved provider/model string | ||||||
| ####################################### | ||||||
| resolve_task_model() { | ||||||
| local task_id="$1" | ||||||
| local task_model="${2:-}" | ||||||
| local task_repo="${3:-.}" | ||||||
| local ai_cli="${4:-opencode}" | ||||||
|
|
||||||
| local default_model="anthropic/claude-opus-4-6" | ||||||
|
|
||||||
| # 1) If task has an explicit non-default model, use it | ||||||
| if [[ -n "$task_model" && "$task_model" != "$default_model" ]]; then | ||||||
| # Could be a tier name or full model string — resolve_model handles both | ||||||
| local resolved | ||||||
| resolved=$(resolve_model "$task_model" "$ai_cli") | ||||||
| if [[ -n "$resolved" ]]; then | ||||||
| log_info "Model for $task_id: $resolved (from task config)" | ||||||
| echo "$resolved" | ||||||
| return 0 | ||||||
| fi | ||||||
| fi | ||||||
|
|
||||||
| # 2) Try to find a model-specific subagent definition matching the task | ||||||
| # Look for tools/ai-assistants/models/*.md files that match the task's | ||||||
| # model tier or the task description keywords | ||||||
| local model_agents_dir="${HOME}/.aidevops/agents/tools/ai-assistants/models" | ||||||
| if [[ -d "$model_agents_dir" ]]; then | ||||||
| # If task_model is a tier name, check for a matching model agent | ||||||
| if [[ -n "$task_model" && ! "$task_model" == *"/"* ]]; then | ||||||
| local tier_agent="${model_agents_dir}/${task_model}.md" | ||||||
| if [[ -f "$tier_agent" ]]; then | ||||||
| local frontmatter_model | ||||||
| frontmatter_model=$(resolve_model_from_frontmatter "tools/ai-assistants/models/${task_model}" "$task_repo") || true | ||||||
| if [[ -n "$frontmatter_model" ]]; then | ||||||
| local resolved | ||||||
| resolved=$(resolve_model "$frontmatter_model" "$ai_cli") | ||||||
| log_info "Model for $task_id: $resolved (from subagent frontmatter: ${task_model}.md)" | ||||||
| echo "$resolved" | ||||||
| return 0 | ||||||
| fi | ||||||
| fi | ||||||
| fi | ||||||
| fi | ||||||
|
|
||||||
| # 3) Fall back to resolve_model with default tier | ||||||
| local resolved | ||||||
| resolved=$(resolve_model "coding" "$ai_cli") | ||||||
| log_info "Model for $task_id: $resolved (default coding tier)" | ||||||
| echo "$resolved" | ||||||
| return 0 | ||||||
| } | ||||||
|
Comment on lines
+2817
to
+2865
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current logic for resolving the model from "subagent frontmatter" is complex and brittle. It relies on a hardcoded path ( A more robust and general approach would be to use the frontmatter of the subagent assigned to the task. The subagent name is available as I recommend refactoring this function to accept the subagent name and use it with resolve_task_model() {
local task_id
task_id="$1"
local task_model
task_model="${2:-}"
local subagent_name
subagent_name="${3:-}"
local task_repo
task_repo="${4:-.}"
local ai_cli
ai_cli="${5:-opencode}"
local default_model="anthropic/claude-opus-4-6"
local resolved
# 1) If task has an explicit non-default model, use it
if [[ -n "$task_model" && "$task_model" != "$default_model" ]]; then
resolved=$(resolve_model "$task_model" "$ai_cli")
if [[ -n "$resolved" ]]; then
log_info "Model for $task_id: $resolved (from task config)"
echo "$resolved"
return 0
fi
fi
# 2) Try to find model from the task's subagent frontmatter
if [[ -n "$subagent_name" ]]; then
local frontmatter_model
frontmatter_model=$(resolve_model_from_frontmatter "$subagent_name" "$task_repo") || true
if [[ -n "$frontmatter_model" ]]; then
resolved=$(resolve_model "$frontmatter_model" "$ai_cli")
if [[ -n "$resolved" ]]; then
log_info "Model for $task_id: $resolved (from subagent frontmatter: $subagent_name)"
echo "$resolved"
return 0
fi
fi
fi
# 3) Fall back to resolve_model with default tier
resolved=$(resolve_model "coding" "$ai_cli")
log_info "Model for $task_id: $resolved (default coding tier)"
echo "$resolved"
return 0
} |
||||||
|
|
||||||
| ####################################### | ||||||
| # Pre-dispatch model health check (t132.3) | ||||||
| # Two-tier probe strategy: | ||||||
|
|
@@ -3513,7 +3646,7 @@ cmd_dispatch() { | |||||
| echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" | ||||||
| echo "worktree=$worktree_path" | ||||||
| echo "branch=$branch_name" | ||||||
| echo "model=${tmodel:-default}" | ||||||
| echo "model=${resolved_model:-${tmodel:-default}}" | ||||||
| echo "ai_cli=$(resolve_ai_cli 2>/dev/null || echo unknown)" | ||||||
| echo "dispatch_mode=$(detect_dispatch_mode 2>/dev/null || echo unknown)" | ||||||
| echo "=== END DISPATCH METADATA ===" | ||||||
|
|
@@ -3537,16 +3670,21 @@ cmd_dispatch() { | |||||
| log_info "Injecting ${#memory_context} bytes of memory context for $task_id" | ||||||
| fi | ||||||
|
|
||||||
| # Resolve model via frontmatter + fallback chain (t132.5) | ||||||
| local resolved_model | ||||||
| resolved_model=$(resolve_task_model "$task_id" "$tmodel" "${trepo:-.}" "$ai_cli") | ||||||
|
|
||||||
| log_info "Dispatching $task_id via $ai_cli ($dispatch_mode mode)" | ||||||
| log_info "Worktree: $worktree_path" | ||||||
| log_info "Model: $resolved_model" | ||||||
| log_info "Log: $log_file" | ||||||
|
|
||||||
| # Build and execute dispatch command | ||||||
| # Use NUL-delimited read to preserve multi-line prompts as single arguments | ||||||
| local -a cmd_parts=() | ||||||
| while IFS= read -r -d '' part; do | ||||||
| cmd_parts+=("$part") | ||||||
| done < <(build_dispatch_cmd "$task_id" "$worktree_path" "$log_file" "$ai_cli" "$memory_context" "$tmodel" "$tdesc") | ||||||
| done < <(build_dispatch_cmd "$task_id" "$worktree_path" "$log_file" "$ai_cli" "$memory_context" "$resolved_model" "$tdesc") | ||||||
|
|
||||||
| # Ensure PID directory exists before dispatch | ||||||
| mkdir -p "$SUPERVISOR_DIR/pids" | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repository style guide (line 11) instruction "declare and assign separately for exit code safety" is a best practice. For consistency and to strictly follow the style guide's text, please separate declaration and assignment for local variables initialized from positional parameters.
References
local var="$1"pattern in functions (declare and assign separately for exit code safety). (link)