You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#6756 runs the review roster on pi, but the model a child uses is whatever the orchestrator puts in the Agent call, and nothing in the repo that runs the review can influence that:
skills/pr-review/SKILL.md passes a model for two pre-passes only (security-triage → haiku with subagent_type: Explore, risk-assessment → sonnet). The seven dimension and challenger dispatches (step 4, step 6d) pass prompt alone, so those children inherit the parent whatever their frontmatter model: says — five personas pinned to claude-sonnet-4-6@default run on opus most of the time. The mix changes run to run when the LLM volunteers a model on its own (Review orchestrator passes roster-table model alias instead of frontmatter model to sub-agents agents#256, agents#1116).
resolveModel in internal/runtime/pi_extension/fullsend-agent.js honours the argument; subagent_type only knows Explore — there are no named personas on pi.
retro's children name no model at all and always inherit opus.
So a review's model mix is nondeterministic, the cheap tier the roster was designed around mostly does not run, and a repo that wants cheaper or cross-vendor sub-agents has no supported lever.
What should happen
Dispatch by persona identity; the runner resolves the model at Bootstrap.
models:
aliases:
sonnet: claude-sonnet-5 # picks the generation; never another vendoragents:
- name: reviewsubagents:
default: haiku # floor: personas with no frontmatter model, and anonymous childrencorrectness: opussecurity: opuschallenger: xai/grok-4.6 # cross-vendor is explicit, pi onlystyle-conventions: google-vertex/gemini-3.7-flash
- name: retrosubagents:
default: sonnet # retro's anonymous children
agents[].subagents: {default, <persona>} on the ADR 0091 entry (no new top-level key). Values are ValidModelRef through the repo alias table; per-key layered merge like ConfigModelAliases; key: ~ tombstones an inherited entry. A key that matches no discovered persona is an error when written in the repo's own layers and a warning when inherited from a preset layer (ADR 0103). AgentEntry.HasSettings / IsOverrideOnly / UpsertAgentSettings / entry validation learn the field.
Discovery by convention, no table. Bootstrap discovers every sub-agents/*.md directly under an uploaded skill root. Frontmatter name: must be present and equal the file basename; shape ^[a-z0-9]+(-[a-z0-9]+)*$ (≤64); unique across the harness's skills; reserved: default, explore, the run's own agent name and any existing agent-file destination, ValidAgentNames(), and Claude Code's built-in agent types (list verified against the pinned CLI before enforcement). One validation function shared by Bootstrap, fullsend lock and harness.CheckGenerated (feat(#6966): add fullsend agent new to generate a complete custom agent #6972).
Resolution order per persona (amended 2026-09-05 — default is a floor, not an override, so a review-wide default: haiku cannot flatten the roster's opus/sonnet split): repo subagents.<persona> > frontmatter model: (alias-resolved) > repo subagents.default (personas with no frontmatter model, and anonymous children) > parent's live model. Persona tools: absent = parent's set, present = intersected with the parent's, Agent/Task never included; the persona's Bash(...) allowlist is applied by the hook adapter in the child.
pi (first PR). Each resolved spec is canonicalised (@suffix stripped, alias resolved, xai normalised, translatePiModel) and checked against the trusted closed set (models table ∪ piAgentProviderModels ∪ the agent's own model) before it is written as a personas table into the manifest — persona specs never widen the set they are checked against, so an invented id still never reaches Vertex. The manifest is already hash-guarded (exit 95 + FULLSEND_PI_MANIFEST_SHA256) for the same-process Bootstrap+Run path; the table inherits exactly that scope. fullsend-agent.js: a subagent_type naming a persona uses its model and tool set and logs an ignored model argument; explore keeps its meaning; any other non-empty type is rejected with the registered names (mirrors Claude Code); empty type = anonymous child, default fills an empty argument, an explicit argument still wins. per_model_usage unchanged; usage lines and transcript basenames carry the persona.
Claude Code (second PR). Register each persona as a native sub-agent with model: rewritten to the resolved full id (bypasses the CLI's stale alias table) via a runner-owned path: --agents '<json>' if the pinned CLI supports it, else files under CLAUDE_CONFIG_DIR/agents/ re-hashed before dispatch — the config dir is agent-writable, so unverified files are not acceptable. Cross-vendor specs are refused on this runtime with a message naming pi. Persona tools: are checked against the parent's set at Bootstrap.
Alias family rule.models.aliases picks a generation of the family the alias names and must not swap vendors (an alias called sonnet resolving to Gemini is misleading in every log and cost line). translatePiModel and docs/runtimes/pi.md accept a cross-vendor alias target today, so this lands as a deprecation warning in this release and a validation error one release later; the docs example changes here.
UX.fullsend agent set review --subagent correctness=opus --subagent default=haiku; fullsend agent list --subagents / fullsend agent show review print the discovered personas with fleet default and repo override, resolving the same pinned harness a run would through the lock resolver (offline from the lock cache); the run's plan block prints the resolved table; fullsend lock records the persona set. Docs: docs/runtimes/pi.md § Sub-agents, docs/runtimes/claude.md, docs/runtimes.md model row, docs/cli/run.md, docs/cli/agent.md. ADR (0104 or later — 0101–0103 are claimed by open PRs): "sub-agent model resolution is a runner concern; personas name aliases, repos map personas to models, the orchestrator never carries a model string".
#7025 (pi 0.85.0 + fullsend-owned anthropic-vertex extension, in the merge queue) → #7028 (closes #7020: ModelAliases into BootstrapInput; the sonnet/opus generation bump is deferred there until the fleet's Vertex projects serve the 5 generation) → this issue's pi PR → this issue's Claude PR → then fullsend-ai/agents flips skills/pr-review/SKILL.md to dispatch by subagent_type and omit model (#6970 F2). Two Claude Code facts force that order (read from the 2.1.261 binary, to be confirmed on the fleet's 2.1.260 pin): the Agent tool's model argument "takes precedence over the agent definition", so the Claude PR is inert until the skill stops passing it; and an unknown subagent_type is Agent type '<x>' not found, so the skill must not flip before the personas are registered. Independent and safe now: agents#1116 (de-pin the five personas to sonnet, give security-triage an explicit read-only tools:). Implementation overlaps #7025 and #7028 on every pi file this issue edits (pi_run.go, pi_bootstrap.go, fullsend-agent.js, bootstrap.go, cli/bootstrap_input.go, config.go) and #6972 on internal/config/config.go and internal/harness/harness.go — start from the post-#7028 tree, and on top of #6972 or after it merges.
Validation criteria
review on pi with subagents: {default: haiku, correctness: opus, style-conventions: google-vertex/gemini-3.7-flash} shows three model specs in per_model_usage and persona names in the transcript files; a model argument, when passed, is logged as ignored.
style-conventions: google-vertex/gemini-9 fails at Bootstrap, before the agent starts and before any inference is billed, naming the accepted ids (config validation catches malformed keys and model refs earlier, before the sandbox exists; the closed-set check needs the uploaded skills and so runs in Bootstrap); correctnes: opus in config.yaml fails validation; the inherited-preset warning is PR B.
Editing the manifest between iterations still exits 95; a dispatch with subagent_type: securty is rejected naming the registered personas.
Same config on the Claude runtime: sonnet-tier personas run claude-sonnet-5 with no "not available" retry; the Gemini mapping is refused with a message naming pi; editing a registered persona inside the sandbox before dispatch does not change the child's model.
retro with subagents.default: sonnet shows every child on sonnet.
fullsend agent list --subagents offline, from the lock cache, prints the same nine names Bootstrap registers.
A cross-vendor models.aliases value prints the deprecation warning in the plan block and still runs.
A provider-neutral tier: on personas (moves a whole tier across vendors without names) — parked until the name-based UX is measured.
Related
#7020 / #7028 (P0, lands first, behind #7025), #6756, #6882, #6464, ADR 0091, #6970 (codex roles), #6972 (file overlap), agents#1116 / agents#515 / agents#256 (persona format), #6666 / #6922 (failure symptoms this removes), agents#1177 (review budget — cheaper children are also faster, but the budget stays the ceiling).
Plan with sources and the Codex review that corrected it: research/fullsend-subagent-model-config-plan.md in ai-workspace-public.
Researched and drafted with Claude (Fable 5.1) for @waynesun09; reviewed by Codex (gpt-5.6-sol); source facts read from fullsend 403540d77 and fullsend-ai/agents ac9f2e1 on 2026-09-04.
What happens
#6756 runs the review roster on pi, but the model a child uses is whatever the orchestrator puts in the
Agentcall, and nothing in the repo that runs the review can influence that:skills/pr-review/SKILL.mdpasses amodelfor two pre-passes only (security-triage→haikuwithsubagent_type: Explore,risk-assessment→sonnet). The seven dimension and challenger dispatches (step 4, step 6d) passpromptalone, so those children inherit the parent whatever their frontmattermodel:says — five personas pinned toclaude-sonnet-4-6@defaultrun on opus most of the time. The mix changes run to run when the LLM volunteers a model on its own (Review orchestrator passes roster-table model alias instead of frontmatter model to sub-agents agents#256, agents#1116).resolveModelininternal/runtime/pi_extension/fullsend-agent.jshonours the argument;subagent_typeonly knowsExplore— there are no named personas on pi.models.aliasesmap never reaches the child table:piAgentModelsininternal/runtime/pi_bootstrap.gobuilds it frommergedPiModelAliases(nil)(pi: the default model alias table still points at the previous Claude generations (sonnet 4-6, opus 4-6) #7020).sonnet→ 4.5), which is the Stale sonnet model version mapping inflates review sub-agent costs ~5x #6666 / Sonnet 4.5 model unavailable on Vertex causes review sub-agent failures and wasted retries #6922 failure..fullsend/config.yamlcan name a sub-agent. ADR 0091'sagents[].modeltunes the parent only. Changing one persona's model means vendoring the wholeskills/pr-review/tree (feat(harness): fine-grained skill sub-agent overrides without vendoring entire skill trees #6157).retro's children name no model at all and always inherit opus.So a review's model mix is nondeterministic, the cheap tier the roster was designed around mostly does not run, and a repo that wants cheaper or cross-vendor sub-agents has no supported lever.
What should happen
Dispatch by persona identity; the runner resolves the model at Bootstrap.
agents[].subagents: {default, <persona>}on the ADR 0091 entry (no new top-level key). Values areValidModelRefthrough the repo alias table; per-key layered merge likeConfigModelAliases;key: ~tombstones an inherited entry. A key that matches no discovered persona is an error when written in the repo's own layers and a warning when inherited from a preset layer (ADR 0103).AgentEntry.HasSettings/IsOverrideOnly/UpsertAgentSettings/ entry validation learn the field.sub-agents/*.mddirectly under an uploaded skill root. Frontmattername:must be present and equal the file basename; shape^[a-z0-9]+(-[a-z0-9]+)*$(≤64); unique across the harness's skills; reserved:default,explore, the run's own agent name and any existing agent-file destination,ValidAgentNames(), and Claude Code's built-in agent types (list verified against the pinned CLI before enforcement). One validation function shared by Bootstrap,fullsend lockandharness.CheckGenerated(feat(#6966): addfullsend agent newto generate a complete custom agent #6972).defaultis a floor, not an override, so a review-widedefault: haikucannot flatten the roster's opus/sonnet split): reposubagents.<persona>> frontmattermodel:(alias-resolved) > reposubagents.default(personas with no frontmatter model, and anonymous children) > parent's live model. Personatools:absent = parent's set, present = intersected with the parent's,Agent/Tasknever included; the persona'sBash(...)allowlist is applied by the hook adapter in the child.@suffixstripped, alias resolved, xai normalised,translatePiModel) and checked against the trusted closed set (modelstable ∪piAgentProviderModels∪ the agent's own model) before it is written as apersonastable into the manifest — persona specs never widen the set they are checked against, so an invented id still never reaches Vertex. The manifest is already hash-guarded (exit 95 +FULLSEND_PI_MANIFEST_SHA256) for the same-process Bootstrap+Run path; the table inherits exactly that scope.fullsend-agent.js: asubagent_typenaming a persona uses its model and tool set and logs an ignoredmodelargument;explorekeeps its meaning; any other non-empty type is rejected with the registered names (mirrors Claude Code); empty type = anonymous child,defaultfills an empty argument, an explicit argument still wins.per_model_usageunchanged; usage lines and transcript basenames carry the persona.model:rewritten to the resolved full id (bypasses the CLI's stale alias table) via a runner-owned path:--agents '<json>'if the pinned CLI supports it, else files underCLAUDE_CONFIG_DIR/agents/re-hashed before dispatch — the config dir is agent-writable, so unverified files are not acceptable. Cross-vendor specs are refused on this runtime with a message naming pi. Personatools:are checked against the parent's set at Bootstrap.models.aliasespicks a generation of the family the alias names and must not swap vendors (an alias calledsonnetresolving to Gemini is misleading in every log and cost line).translatePiModelanddocs/runtimes/pi.mdaccept a cross-vendor alias target today, so this lands as a deprecation warning in this release and a validation error one release later; the docs example changes here.fullsend agent set review --subagent correctness=opus --subagent default=haiku;fullsend agent list --subagents/fullsend agent show reviewprint the discovered personas with fleet default and repo override, resolving the same pinned harness a run would through the lock resolver (offline from the lock cache); the run's plan block prints the resolved table;fullsend lockrecords the persona set. Docs:docs/runtimes/pi.md§ Sub-agents,docs/runtimes/claude.md,docs/runtimes.mdmodel row,docs/cli/run.md,docs/cli/agent.md. ADR (0104 or later — 0101–0103 are claimed by open PRs): "sub-agent model resolution is a runner concern; personas name aliases, repos map personas to models, the orchestrator never carries a model string".subagents.defaultreplacesFULLSEND_CODEX_SUBAGENT_MODEL; OpenAI ids only).Ordering
#7025 (pi 0.85.0 + fullsend-owned anthropic-vertex extension, in the merge queue) → #7028 (closes #7020:
ModelAliasesintoBootstrapInput; the sonnet/opus generation bump is deferred there until the fleet's Vertex projects serve the 5 generation) → this issue's pi PR → this issue's Claude PR → then fullsend-ai/agents flipsskills/pr-review/SKILL.mdto dispatch bysubagent_typeand omitmodel(#6970 F2). Two Claude Code facts force that order (read from the 2.1.261 binary, to be confirmed on the fleet's 2.1.260 pin): theAgenttool'smodelargument "takes precedence over the agent definition", so the Claude PR is inert until the skill stops passing it; and an unknownsubagent_typeisAgent type '<x>' not found, so the skill must not flip before the personas are registered. Independent and safe now: agents#1116 (de-pin the five personas tosonnet, givesecurity-triagean explicit read-onlytools:). Implementation overlaps #7025 and #7028 on every pi file this issue edits (pi_run.go,pi_bootstrap.go,fullsend-agent.js,bootstrap.go,cli/bootstrap_input.go,config.go) and #6972 oninternal/config/config.goandinternal/harness/harness.go— start from the post-#7028 tree, and on top of #6972 or after it merges.Validation criteria
reviewon pi withsubagents: {default: haiku, correctness: opus, style-conventions: google-vertex/gemini-3.7-flash}shows three model specs inper_model_usageand persona names in the transcript files; amodelargument, when passed, is logged as ignored.style-conventions: google-vertex/gemini-9fails at Bootstrap, before the agent starts and before any inference is billed, naming the accepted ids (config validation catches malformed keys and model refs earlier, before the sandbox exists; the closed-set check needs the uploaded skills and so runs in Bootstrap);correctnes: opusinconfig.yamlfails validation; the inherited-preset warning is PR B.subagent_type: securtyis rejected naming the registered personas.claude-sonnet-5with no "not available" retry; the Gemini mapping is refused with a message naming pi; editing a registered persona inside the sandbox before dispatch does not change the child's model.retrowithsubagents.default: sonnetshows every child on sonnet.fullsend agent list --subagentsoffline, from the lock cache, prints the same nine names Bootstrap registers.models.aliasesvalue prints the deprecation warning in the plan block and still runs.Not in scope
tier:on personas (moves a whole tier across vendors without names) — parked until the name-based UX is measured.Related
#7020 / #7028 (P0, lands first, behind #7025), #6756, #6882, #6464, ADR 0091, #6970 (codex roles), #6972 (file overlap), agents#1116 / agents#515 / agents#256 (persona format), #6666 / #6922 (failure symptoms this removes), agents#1177 (review budget — cheaper children are also faster, but the budget stays the ceiling).
Plan with sources and the Codex review that corrected it:
research/fullsend-subagent-model-config-plan.mdin ai-workspace-public.Researched and drafted with Claude (Fable 5.1) for @waynesun09; reviewed by Codex (gpt-5.6-sol); source facts read from fullsend
403540d77and fullsend-ai/agentsac9f2e1on 2026-09-04.