fix(#2072): thread resolved model into routed-agent spawns (assumptions-analyzer, code-reviewer, code-fixer)

model_overrides / models.<phaseType> were silently inert for gsd-assumptions-analyzer,
gsd-code-reviewer, and gsd-code-fixer on Claude Code: resolveModelInternal honors them,
but the workflows spawned these agents with no model= param, so the resolved value
never reached the Agent tool and the agents inherited the session model — no warning.

Fix — thread each agent's resolved model at every spawn site (the established
plan-phase pattern; the architecture-consistent Claude mechanism, since 13 other
agents already thread their model):
- discuss-phase-assumptions.md: `resolve-model gsd-assumptions-analyzer --raw`
  → ANALYZER_MODEL, threaded.
- code-review.md + code-review-fix.md (re-review): `resolve-model gsd-code-reviewer --raw`
  → REVIEWER_MODEL, threaded.
- code-review-fix.md (both fixer spawns): `resolve-model gsd-code-fixer --raw`
  → FIXER_MODEL, threaded (same silently-inert bug, same file — folded in per review).
- quick.md review step: was reusing `{executor_model}` for gsd-code-reviewer (so the
  reviewer's own override was ignored); init.quick now resolves `reviewer_model`
  (gsd-code-reviewer) and the spawn threads it.

resolve-model --raw returns the bare model string (resolve-execution --raw would
return effort — wrong). The resolver maps these agents to phaseType discuss /
verification / execution, so models.<phaseType> apply too.

Scope: the three agents reachable from the two issue-named workflows + quick.md. The
wider systemic class (other agents in UNTOUCHED workflows with the same pattern) stays
documented on the issue for a maintainer-scoped structural decision (thread-at-source
vs embed-at-install like #2256), not widened here.

Docs: the stale "discuss — reserved, no subagent today" model-profile tables now list
gsd-assumptions-analyzer and the verification row includes gsd-code-reviewer, across
the English docs, the shipped gsd-core/references/model-profiles.md reference, and the
ja-JP / zh-CN / ko-KR / pt-BR locale mirrors.

Tests:
- tests/model-resolver.test.cjs: #2072 acceptance — model_overrides and
  models.discuss/verification/execution resolve for all three agents.
- tests/model-routing-spawn-threading.test.cjs: every spawn of the three agents threads
  a resolved model (fails pre-fix); a header-precise parity guard fails the suite if a
  new un-threaded spawn of any of them regresses.
All 16 golden-install-parity fixtures + the workflow size baseline regenerated for the
changed shipped files (4 workflows + the reference doc); bin/lib is excluded from parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-07 20:28:15 -04:00
parent cce405c9b7
commit 4483300253
37 changed files with 318 additions and 115 deletions

View File

@@ -23,6 +23,10 @@ INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_FIXER=$(gsd_run query agent-skills gsd-code-fixer)
AGENT_SKILLS_REVIEWER=$(gsd_run query agent-skills gsd-code-reviewer)
# #2072: resolve the routed models so model_overrides / models.<phaseType> are honored
# (gsd-code-reviewer → "verification", gsd-code-fixer → "execution"); thread them below.
REVIEWER_MODEL=$(gsd_run query resolve-model gsd-code-reviewer --raw)
FIXER_MODEL=$(gsd_run query resolve-model gsd-code-fixer --raw)
```
Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
@@ -194,7 +198,7 @@ echo "Fix scope: ${FIX_SCOPE}"
Use Agent() to spawn agent:
```text
Agent(subagent_type="gsd-code-fixer", prompt="
Agent(subagent_type="gsd-code-fixer", model="{FIXER_MODEL}", prompt="
<files_to_read>
${REVIEW_PATH}
</files_to_read>
@@ -277,7 +281,7 @@ if [ "$AUTO_MODE" = "true" ]; then
# Spawn gsd-code-reviewer agent to re-review (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
# (This overwrites REVIEW_PATH with latest review state)
Agent(subagent_type="gsd-code-reviewer", prompt="
Agent(subagent_type="gsd-code-reviewer", model="{REVIEWER_MODEL}", prompt="
<config>
depth: ${REVIEW_DEPTH}
phase_dir: ${PHASE_DIR}
@@ -311,7 +315,7 @@ ${AGENT_SKILLS_REVIEWER}")
# Still has issues — spawn fixer again (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
echo "Issues remain. Applying fixes for iteration ${ITERATION}..."
Agent(subagent_type="gsd-code-fixer", prompt="
Agent(subagent_type="gsd-code-fixer", model="{FIXER_MODEL}", prompt="
<files_to_read>
${REVIEW_PATH}
</files_to_read>

View File

@@ -22,6 +22,9 @@ PHASE_ARG="${1}"
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_REVIEWER=$(gsd_run query agent-skills gsd-code-reviewer)
# #2072: resolve the routed model so model_overrides / models.verification are honored
# (the resolver maps gsd-code-reviewer → phaseType "verification"); thread it below.
REVIEWER_MODEL=$(gsd_run query resolve-model gsd-code-reviewer --raw)
```
Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
@@ -482,7 +485,7 @@ Spawn the gsd-code-reviewer agent:
Print: `◆ Spawning code reviewer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(subagent_type="gsd-code-reviewer", prompt="
Agent(subagent_type="gsd-code-reviewer", model="{REVIEWER_MODEL}", prompt="
<files_to_read>
${FILES_TO_READ}
</files_to_read>

View File

@@ -68,6 +68,9 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
INIT=$(gsd_run query init.phase-op "${PHASE}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_ANALYZER=$(gsd_run query agent-skills gsd-assumptions-analyzer)
# #2072: resolve the routed model so model_overrides / models.discuss are honored
# (the resolver maps gsd-assumptions-analyzer → phaseType "discuss"); thread it below.
ANALYZER_MODEL=$(gsd_run query resolve-model gsd-assumptions-analyzer --raw)
```
Parse JSON for: `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`,
@@ -255,7 +258,7 @@ If no USER-PROFILE.md: calibration_tier = "standard"
**Spawn Explore subagent** (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)**:**
```
Agent(subagent_type="gsd-assumptions-analyzer", prompt="""
Agent(subagent_type="gsd-assumptions-analyzer", model="{ANALYZER_MODEL}", prompt="""
Analyze the codebase for Phase {PHASE}: {phase_name}.
Phase goal: {roadmap_description}

View File

@@ -134,7 +134,7 @@ AGENT_SKILLS_CHECKER=$(gsd_run query agent-skills gsd-plan-checker)
AGENT_SKILLS_VERIFIER=$(gsd_run query agent-skills gsd-verifier)
```
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `reviewer_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
```bash
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
@@ -868,7 +868,7 @@ Agent(
Output: ${QUICK_DIR}/${quick_id}-REVIEW.md
Depth: quick",
subagent_type="gsd-code-reviewer",
model="{executor_model}"
model="{reviewer_model}"
)
```