fix(#3602): bind model resolution for every workflow subagent spawn (#3670)

* test(#3602): guard every spawned gsd-* subagent has a model resolution

* fix(#3602): bind model resolution for every workflow subagent spawn

* test(#3602): merge drift-ack entries into their owning fragments

* fix(#3602): address review findings - docs-update verifier binding, ack merge, guard residuals

* chore(#3602): backfill changeset pr number

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-19 12:35:45 -04:00
committed by GitHub
parent dae134b960
commit 7cf6a079fa
11 changed files with 314 additions and 7 deletions

View File

@@ -56,6 +56,9 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/dev/null || echo "")
INIT=$(gsd_run init ingest-docs)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
CLASSIFIER_MODEL=$(gsd_run query resolve-model gsd-doc-classifier --raw)
SYNTHESIZER_MODEL=$(gsd_run query resolve-model gsd-doc-synthesizer --raw)
ROADMAPPER_MODEL=$(gsd_run query resolve-model gsd-roadmapper --raw)
```
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
@@ -174,6 +177,10 @@ mkdir -p .planning/intel/classifications/
For each discovered doc, spawn `gsd-doc-classifier` in parallel. In Claude Code, issue all Task calls in a single message with multiple tool uses so the harness runs them concurrently. For Copilot / sequential runtimes, fall back to sequential dispatch.
<!-- #2517 model-omit-on-inherit -->
> **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`CLASSIFIER_MODEL`, `SYNTHESIZER_MODEL`, `ROADMAPPER_MODEL`) is `"inherit"` or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.
Per-spawn prompt fields:
- `FILEPATH` — absolute path to the doc
- `OUTPUT_DIR` — `{intel_dir}/classifications` (absolute — from `init ingest-docs`; #2376: a spawned classifier's own cwd may differ from the orchestrator's)
@@ -181,6 +188,8 @@ Per-spawn prompt fields:
- `MANIFEST_PRECEDENCE` — the precedence integer from the manifest if present, else omit
- `<required_reading>` — `agents/gsd-doc-classifier.md` (the agent definition itself)
**Model on every classifier spawn (#3602):** `model="{CLASSIFIER_MODEL}"` is a parameter of each Task/Agent call — not a prompt field, never folded into the prompt text — so `dynamic_routing`/`model_profile` tiers apply instead of the caller's session model. Omit the parameter per the rule above when the value is `"inherit"` or empty.
Collect the one-line confirmations from each classifier. If any classifier errors out, surface the error and abort without touching `.planning/` further.
</step>
@@ -196,6 +205,7 @@ Spawn `gsd-doc-synthesizer` once (runs in a subagent — no output until it retu
```
Agent({
subagent_type: "gsd-doc-synthesizer",
model: "{SYNTHESIZER_MODEL}",
prompt: "
CLASSIFICATIONS_DIR: {intel_dir}/classifications
INTEL_DIR: {intel_dir}
@@ -263,6 +273,7 @@ Delegate to `gsd-roadmapper` (runs in a subagent — no output until it returns,
```
Agent({
subagent_type: "gsd-roadmapper",
model: "{ROADMAPPER_MODEL}",
prompt: "
Mode: new-project-from-ingest
Intel: {intel_dir}/SYNTHESIS.md (entry point)