Three Claude Code subagent files, three models in frontmatter: what each launch cost, and the field the validator waved through
DEV Community

Three Claude Code subagent files, three models in frontmatter: what each launch cost, and the field the validator waved through

Introduction

We keep three custom Claude Code subagents in .claude/agents/ : a bulk reader that fetches pages and logs so the main conversation does not have to, an independent reviewer that judges things without seeing the parent's reasoning, and a pre-ship reviewer that runs three checklists in one body. Each file pins a model and an effort level in its YAML frontmatter. Until this week we had never checked whether those two lines do anything. This is the measurement: what one launch of each agent costs on its first API request, which model actually answered, whether effort survived a parent session running at a different level, and what Claude Code does with a frontmatter key it has never heard of. Everything below was run on Claude Code 2.1.278 on macOS, on 2026-09-22, with seven claude -p invocations in a throwaway directory. The documentation quotes come from https://code.claude.com/docs/en/sub-agents , fetched the same day with trafilatura .

The three subagents

The three files, stripped of their Japanese system prompts, are almost identical in shape. Each has four frontmatter keys and nothing else:

---  
name : bulk-reader  
description : <one sentence on when to delegate reading work here>  
model : sonnet  
effort : xhigh  
---  
---  
name : deep-reviewer  
description : <one sentence on when an independent verdict is needed>  
model : opus  
effort : xhigh  
---  
---  
name : preflight-reviewer  
description : <one sentence on the three-perspective pre-ship review>  
model : inherit  
effort : high  
---  

None of the three sets tools, so each inherits the full tool pool. The bodies are short: 5 to 8 bullet rules about quoting primary sources, separating verdicts from evidence, and never writing to anything public. The system prompt the longest one actually received was 1,702 characters, of which 422 were our file and the rest were the harness's own notes for subagents. That number matters later, when we ask where the launch tokens go.

Experiment setup

The check you can rerun in ten minutes:

D=$( mktemp -d )
mkdir -p " $D /.claude/agents"
cp .claude/agents/bulk-reader.md " $D /.claude/agents/"
cd " $D "
claude plugin validate .claude/agents
claude -p --output-format json --permission-mode default \
 "Use the Agent tool to launch the subagent named bulk-reader (subagent_type: bulk-reader) exactly once, with exactly this prompt: \" This is a measurement probe. Do not read anything, do not call tools. Return 'ok'. \" \
 Do not read any files yourself and do not call any other tool. When it returns, reply with only the subagent's reply verbatim." \
 > run1.json

Two places hold the answer. The JSON on stdout has a modelUsage object keyed by model, so if the subagent ran on a different model than the parent you get its usage as a separate bucket for free. The transcript is more precise: Claude Code writes the subagent's own JSONL at ~/.claude/projects/<cwd with slashes replaced by dashes>/<session_id>/subagents/agent-<id>.jsonl, and each assistant record there carries message.model, message.usage, and, new to us, a top-level effort key.

To read the first request:

SID=$( python3 -c "import json;print(json.load(open('run1.json'))['session_id'])" )
P=~/.claude/projects/$( pwd | sed 's#/#-#g' )/$SID/subagents
python3 - "$P" /agent-*.jsonl << 'EOF'
import json, sys
for line in open(sys.argv[1]):
    r = json.loads(line)
    if r.get("type") == "assistant":
        u = r["message"]["usage"]
        print(r["message"]["model"], r.get("effort"),
              u["cache_creation_input_tokens"], u["cache_read_input_tokens"],
              u["input_tokens"], u["output_tokens"],
              u.get("output_tokens_details", {}).get("thinking_tokens"))
        break
EOF

Add --effort low to the claude -p line for a second run to see whether the file's effort beats the session's. That is the whole experiment; the rest of this post is what came out of it.

What the documentation promises

The sub‑agents page has a table titled "Supported frontmatter fields", introduced by one sentence:

"The following fields can be used in the YAML frontmatter. Only name and description are required."

The rows we rely on:

  • model: "Model to use: sonnet, opus, haiku, fable, a full model ID such as claude-opus-5, or inherit. When you omit it, Claude Code picks the model in the subagent model order."
  • effort: "Effort level when this subagent is active. Overrides the session effort level. Default: inherits from session. Options: low, medium, high, xhigh, max; available levels depend on the model"
  • tools: "Tools the subagent can use, as a comma-separated string such as Read, Grep, Bash or a YAML list. Inherits every tool available to subagents if omitted."

The resolution order for the model is spelled out too:

"The per-invocation model parameter", then "The subagent definition's model frontmatter, where inherit selects the main conversation's model", then "The CLAUDE_CODE_SUBAGENT_MODEL environment variable", then "The main conversation's model".

So a model: line in the file loses only to an explicit model argument on the Agent tool call. We checked the parent transcripts: in all seven runs the Agent call carried exactly four keys, description, prompt, run_in_background, subagent_type, and no model. Whatever model the child ran on, the file decided it.

On unknown keys the page says nothing directly. The section "Subagent files Claude Code skips" lists five conditions, all about name, description, the opening ---, and YAML that does not parse. A key the schema does not know is not among them. The validator is described just as narrowly:

"Claude Code checks only the directory you name, and doesn't flag a file whose frontmatter parses but has no name."

We read that as "an unknown key is silently accepted" and then tested it, because reading is not measuring.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.