SLOPSHOPPER

agent-models

Each Claude subagent's model and effort in the native agent list, beside its task

newagents
★ 1v0.1.0Apache-2.0updated 2026-10-01alex2481kobe/claude-mods/plugins/agent-models
A shopper browsing a rack in a slop shop
README

agent-models

Each Claude subagent's model and effort in Claude Code's agent list.

Two Claude subagents in the agent list, labelled Opus 5.5 (high) and Haiku

Claude Code's agent list shows a subagent's task but not what it runs on. With this mod each row carries it, the way the codex mod's rows do:

Review auth · Opus 5.5 (high)
Check label.ts · Sonnet 5.5 (medium)
Say ok · Haiku
The agentIts label
general-purpose, a fork, an agent file's inheritthe parent's model and effort
an agent file with model: (and effort:)that model, and that effort
the Agent tool's model optionthat model; the parent's effort only if the same model
Explore, Plan and other built-insnone: their model is the engine's to choose
a plugin's agent (codex:read)none: the plugin labels its own

Where the model cannot be known before the agent starts, the row is left as it was rather than guessed.

Requirements

  • Claude Code with mods (tested on 2.1.287)

Install

/plugin marketplace add alex2481kobe/claude-mods
/plugin install agent-models@claude-mods

How it works

  • An agent.spawn hook, which runs before the subagent starts, appends · <model> (<effort>) to the task's description; the agent list shows that description.
  • The model is the Agent tool's model option, else the agent file's, else the parent's for the agents that inherit it. Agent files are read from the project's .claude/agents/ and your own (~/.claude/agents/, or $CLAUDE_CONFIG_DIR/agents/), matched by their name.
  • The effort is the agent file's effort, else the effort of the parent's latest request, and that only when the agent runs on the parent's own model: another model may take no effort at all (Haiku takes none).
  • Model ids read as people say them: claude-opus-5-5 is Opus 5.5.

Limits

  • An agent file in a subfolder of agents/, or one defined in settings, a flag or policy, is not read, so its agent goes unlabelled.
  • An agent file with no model: line is left unlabelled.
  • An alias reads as its name without a version: haiku is Haiku.
  • An agent file's effort is shown even when the Agent tool's model option overrides its model.
  • Tried in the interactive terminal and headless (claude -p).
  • The mods API is early access and may change between Claude Code releases.

Develop

claude plugin validate plugins/agent-models
claude plugin test plugins/agent-models
claude --plugin-dir plugins/agent-models
Source 2 files
hooks/register.ts 62 lines
1import type { AgentSpawnInput, EngineInterface, Register } from 'claude-code'
2
3import { definitionOf, labelOf, type Definition } from './label'
4
5// Shows each Claude subagent's model and effort in the native agent list:
6// ` · Opus 5.5 (high)` after its task, as the codex mod does for Codex.
7
8// The effort of each loop's latest request: '' is the main loop.
9const efforts = new Map<string, string>()
10
11// Where agent files live: the project's own first, then the person's. A
12// definition from anywhere else (settings, a flag, policy) is not read, and
13// its agent goes unlabelled.
14async function agentDirs($: EngineInterface, provider: string): Promise<string[]> {
15  const home = await $.env.get('HOME')
16  const config = (await $.env.get('CLAUDE_CONFIG_DIR')) || (home && `${home}/.claude`)
17  const user = config ? [`${config}/agents`] : []
18  return provider === 'user' ? user : [`${await $.session.root()}/.claude/agents`, ...user]
19}
20
21async function definitionNamed($: EngineInterface, type: string, provider: string): Promise<Definition | undefined> {
22  for (const dir of await agentDirs($, provider)) {
23    const files = await $.fs.list(dir).catch(() => [])
24    for (const file of files) {
25      if (file.kind !== 'file' || !file.name.endsWith('.md')) continue
26      const text = await $.fs.read(`${dir}/${file.name}`).catch(() => '')
27      const definition = definitionOf(text, file.name)
28      if (definition?.name === type) return definition
29    }
30  }
31  return undefined
32}
33
34export const register: Register = on => {
35  on('turn.step', async function* ($, e, next) {
36    if (e.effort === undefined) efforts.delete(e.agentId ?? '')
37    else efforts.set(e.agentId ?? '', String(e.effort))
38    return yield* next(e)
39  })
40
41  on('agent.spawn', async ($, e, next) => {
42    // The label is decoration: an agent starts whether or not it can be made.
43    const label = await labelFor($, e).catch(() => undefined)
44    return next(label ? { ...e, description: `${e.description} · ${label}` } : e)
45  })
46}
47
48async function labelFor($: EngineInterface, e: AgentSpawnInput): Promise<string | undefined> {
49  if (e.subagentType.includes(':')) return undefined
50  const isBuiltIn = e.provider.plugin === 'engine'
51  const definition = e.fork || isBuiltIn ? undefined : await definitionNamed($, e.subagentType, e.provider.plugin)
52  const spawn = {
53    type: e.subagentType,
54    model: e.model,
55    fork: e.fork,
56    isBuiltIn,
57    parentModel: e.parentModel,
58    parentEffort: efforts.get(e.parentAgentId ?? ''),
59  }
60  return labelOf(spawn, definition)
61}
62
hooks/label.ts 59 lines
1// What a subagent runs on, worked out before it starts: the agent list shows
2// the spawn's description, and only a rewrite made before the spawn shows.
3
4// An agent file's frontmatter, as far as the label needs it.
5export type Definition = { name: string; model?: string; effort?: string }
6
7export type Spawn = {
8  type: string
9  // The Agent tool's `model` parameter, when given.
10  model?: string
11  fork: boolean
12  isBuiltIn: boolean
13  parentModel: string
14  // The effort of the parent's latest request, when its model has one.
15  parentEffort?: string
16}
17
18// Built-in types that run on the parent's model and effort.
19const INHERITS = new Set(['general-purpose'])
20
21// The frontmatter of an agent file: its `name` (else the file's name), and
22// `model` and `effort` when set. A file without frontmatter is no agent.
23export function definitionOf(text: string, fileName: string): Definition | undefined {
24  const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)
25  if (!match) return undefined
26  const fields: Record<string, string> = {}
27  for (const line of match[1]!.split(/\r?\n/)) {
28    const field = /^([A-Za-z]+):\s*(.*?)\s*$/.exec(line)
29    if (field) fields[field[1]!] = field[2]!.replace(/^(["'])(.*)\1$/, '$2')
30  }
31  const name = fields.name || fileName.replace(/\.md$/, '')
32  return { name, model: fields.model || undefined, effort: fields.effort || undefined }
33}
34
35// Claude model ids as people say them: `claude-opus-5-5` reads `Opus 5.5`,
36// a dated id like `claude-haiku-4-5-20251001` reads `Haiku 4.5`, an alias
37// like `haiku` reads `Haiku`. Anything else is shown as it is.
38export function nameOf(id: string): string {
39  const match = /^claude-([a-z]+)-(\d+)-(\d+)(?:-\d{8})?(\[1m\])?$/i.exec(id)
40  const word = /^[a-z]+$/.test(id) ? id : match?.[1]
41  if (!word) return id
42  const name = `${word[0]!.toUpperCase()}${word.slice(1)}`
43  return match ? `${name} ${match[2]}.${match[3]}` : name
44}
45
46// The label for the agent list, e.g. `Opus 5.5 (high)`, or undefined when
47// the model cannot be known here. Plugin types (`codex:read`) label
48// themselves. Effort shows only where it is known to carry over: the
49// definition sets it, or the agent runs on the parent's own model.
50export function labelOf(spawn: Spawn, definition?: Definition): string | undefined {
51  if (spawn.type.includes(':')) return undefined
52  const chosen = spawn.fork ? undefined : (spawn.model ?? definition?.model)
53  const inherits = spawn.fork || chosen === 'inherit' || (!chosen && spawn.isBuiltIn && INHERITS.has(spawn.type))
54  const model = inherits ? spawn.parentModel : chosen
55  if (!model) return undefined
56  const effort = definition?.effort ?? (model === spawn.parentModel ? spawn.parentEffort : undefined)
57  return effort ? `${nameOf(model)} (${effort})` : nameOf(model)
58}
59