Skip to content

sub-agents: per-persona model configuration (agents[].subagents), resolved by the runner, dispatched by subagent_type #7031

Description

@waynesun09

What happens

#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:

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 vendor
agents:
  - name: review
    subagents:
      default: haiku                 # floor: personas with no frontmatter model, and anonymous children
      correctness: opus
      security: opus
      challenger: xai/grok-4.6       # cross-vendor is explicit, pi only
      style-conventions: google-vertex/gemini-3.7-flash
  - name: retro
    subagents:
      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".
  • Codex. codex runtime: run the review/retro sub-agent rosters through codex's native spawn_agent roles #6970 F1 consumes the same map (subagents.default replaces FULLSEND_CODEX_SUBAGENT_MODEL; OpenAI ids only).

Ordering

#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

  1. 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.
  2. 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.
  3. Editing the manifest between iterations still exits 95; a dispatch with subagent_type: securty is rejected naming the registered personas.
  4. 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.
  5. retro with subagents.default: sonnet shows every child on sonnet.
  6. fullsend agent list --subagents offline, from the lock cache, prints the same nine names Bootstrap registers.
  7. A cross-vendor models.aliases value prints the deprecation warning in the plan block and still runs.

Not in scope

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions