---
name: resolve-archetype-model
description: >-
  Resolve a diligence profile's per-archetype model (and, where the dispatch mechanism accepts it,
  effort) for Claude's NATIVE sub-agent dispatch (the Agent/Task tool, or Workflow's agent()) — as
  opposed to hera worker spawn, which already gets this via internal/agent.ResolveModel. Use when a
  coordinator or pipeline skill dispatches a sequence of in-context sub-agent stages that map to
  archetypes (e.g. a migration stage ~ code_slice, a review pass ~ review, a CI-fix loop ~ ci_loop)
  and wants each stage to run at the project's configured model/effort instead of silently
  inheriting the caller's own default. Generalizes the pattern hera-spawn-review already uses for
  the review archetype's [panel] block to the other twelve archetypes. NOT for hera worker spawn
  (already handled by hera_spawn_worker's archetype param) — this is for work that stays in-session
  with no worktree/branch/PR of its own.
---

# resolve-archetype-model — archetype→model resolution for native sub-agent dispatch

## 1. What this is, and is not

This is the native-dispatch counterpart to hera worker spawn's archetype resolution. A hera worker
gets its model from the project's bound diligence profile automatically (`hera_spawn_worker`'s
`archetype` param drives `internal/agent.ResolveModel` at spawn time). A native sub-agent — spawned
via the `Agent`/`Task` tool, or via a `Workflow` script's `agent()` — has no such path: it silently
runs at whatever model the calling session inherits, regardless of what the project's profile says
that kind of work should run at.

This skill is the convention that closes that gap. It is **not** a new MCP tool — `profile_resolve`
already returns everything needed in one call. It is not specific to review panels —
`hera-spawn-review` already does exactly this pattern for the `review` archetype's `[panel]` block;
this skill generalizes it to any archetype a pipeline's stages map to. For guidance on **choosing
and assigning `archetype` values when authoring a plan-DAG**, see the `hera-plan` skill (the plan
authar's counterpart to this dispatch-side resolver).

## 2. Resolve once per pipeline

Call `mcp__argus__profile_resolve(cwd=$PWD)` **exactly once** per pipeline or session, not once per
stage — the response already carries every archetype's entry. Build a local map from it:

```
archetypes = resolved.archetype   # {} if resolved.resolved == false
```

The response shape (per `internal/mcp/profiles.go`):

```json
{"resolved": true|false, "name": "...", "source": "...",
 "archetype": {"code_slice": {"model": "sonnet", "effort": ""}, "review": {"model": "opus", "effort": ""}, ...},
 "rigor": {...}, "panel": {...}, "errors": [...]}
```

Field names are lowercase/snake_case (`model`, `effort`, `window`) — read them by exact key, no
case-normalization needed.

## 3. Fail-open fallback

Never treat a miss as an error — always fall back to the dispatch mechanism's own default model:

- **`resolved: false`** (no profile, invalid profile, malformed `[panel]`) — every stage in the
  pipeline dispatches with no model override.
- **A specific archetype absent from `archetype`, or present with an empty `model`** — only that
  stage falls back; other stages whose archetypes ARE present still get their resolved model. A
  profile author may legitimately leave some archetypes unset.

## 4. The in-session model gate (mandatory before dispatch)

A profile's archetype `model` is validated against the union of **every configured backend's**
models — it may legitimately name a codex model, not just a Claude one. Claude's native sub-agent
dispatch only runs **in-session Claude models**. Before threading a resolved model into a dispatch
call, check it against the same four values `hera-spawn-review` already checks finders against
(mirrors `internal/review.knownInSessionModels`):

```
knownInSession = {"opus", "sonnet", "haiku", "fable"}
```

- **Model is one of these four** → forward it: `Agent(model=<resolved>)` or, for a `Workflow`
  script, `agent(prompt, {model: <resolved>})`.
- **Model is anything else** (a foreign backend's model name, e.g. a codex model) → native
  dispatch has **no path to a different backend at all** — it can only ever spawn one of the four
  in-session Claude models. Rather than dropping model selection entirely, map to the **closest
  available in-session Claude model** and forward that instead:
  - A foreign flagship/top-tier model (e.g. `gpt-5`, `gpt-5-codex`, or any other backend's
    highest-capability model) → `opus`.
  - A foreign backend's smaller/cheaper tier (e.g. a `-mini`-class model) → `haiku`.
  - Anything ambiguous, or a foreign model whose tier isn't obvious from its name → `sonnet` (the
    safe middle default).
  - Always emit a loud, visible note when this substitution happens — e.g.
    `[resolve-archetype-model] archetype "code_slice" resolved to a non-in-session model
    ("gpt-5-codex") — substituting "opus" (closest in-session equivalent) for native dispatch.`
    Never silently substitute; the caller (or a report reader) needs to know the profile's actual
    choice wasn't honored. **This tiering is a best-effort heuristic, not a principled
    cross-vendor equivalence** — there is no validated quality mapping between vendors' model
    tiers; it exists so native dispatch degrades gracefully (some in-session model, correctly
    tiered by rough capability) instead of silently reverting to the caller's own default,
    which could be any tier regardless of what the archetype was configured for.

## 5. Effort — only where the mechanism accepts it

An archetype's `effort` field (`low`/`medium`/`high`) is a real, validated part of the profile, but
whether it can be *applied* depends entirely on the dispatch mechanism:

- **Claude's built-in `Agent`/`Task` tool has no effort parameter as of this writing.** Check the
  tool's current schema before assuming otherwise — if it gains one later, thread `effort=` the same
  way as `model=`, gated the same way. Until then, effort is unusable here: omit it, and don't imply
  in a report that it was applied.
- **`Workflow`'s `agent()` accepts `opts.effort`** (`'low'|'medium'|'high'|'xhigh'|'max'` — a strict
  superset of a profile's three-value enum). When dispatching through a `Workflow` script, thread the
  resolved `effort` straight into `opts.effort` — no gate needed beyond "non-empty."

This mirrors the already-documented Fable-effort gotcha in `hera-spawn-review` (§12): a real
capability gap in the current tooling, not a design choice — don't let a report claim effort was
honored by a mechanism that has no way to honor it.

## 6. Worked example

```
resolved = profile_resolve(cwd=$PWD)
models = resolved.archetype if resolved.resolved else {}
knownInSession = {"opus", "sonnet", "haiku", "fable"}
foreignFlagshipHints = ["gpt-5", "opus", "large", "pro"]      # rough, name-based, best-effort
foreignCheapHints     = ["mini", "haiku", "small", "flash"]

def modelFor(archetype):
    entry = models.get(archetype, {})
    m = entry.get("model", "")
    if not m:
        return None                        # unset — use the caller's/tool's own default
    if m in knownInSession:
        return m                           # forward as-is
    # Foreign backend model — native dispatch can't spawn it at all. Substitute the
    # closest in-session tier rather than dropping model selection entirely.
    substitute = "sonnet"
    if any(h in m for h in foreignFlagshipHints):
        substitute = "opus"
    elif any(h in m for h in foreignCheapHints):
        substitute = "haiku"
    note(f'[resolve-archetype-model] archetype "{archetype}" resolved to a non-in-session '
         f'model ("{m}") — substituting "{substitute}" (closest in-session equivalent) for '
         f'native dispatch.')
    return substitute

# Agent tool (no effort parameter available):
Agent(prompt=migration_prompt, model=modelFor("code_slice"))          # e.g. "sonnet", or omitted
Agent(prompt=review_prompt,    model=modelFor("review"))              # e.g. "opus", or omitted

# Workflow script's agent() (effort IS available):
entry = models.get("ci_loop", {})
await agent(ci_fix_prompt, {
    model: modelFor("ci_loop"),
    effort: entry.get("effort") or undefined,   # omit rather than pass an empty string
})
```

## 7. Gotchas

- **One `profile_resolve` call per pipeline, not per stage.** The whole point of returning every
  archetype's entry in one response is to avoid N round-trips for an N-stage pipeline.
- **The in-session gate is not optional, and a mismatch is a substitution, not a silent drop.**
  Native dispatch cannot spawn a different backend at all — an archetype tuned for a codex worker
  must fall back to the closest in-session Claude tier (§4), loudly noted, rather than either
  erroring or quietly running with no model override (which could land on any tier, unrelated to
  what the archetype was configured for).
- **Don't claim effort was applied when it wasn't.** The `Agent` tool's lack of an effort parameter
  is a real, current limitation — state it plainly in any report, the same way `hera-spawn-review`
  documents its Fable-effort gap rather than silently ignoring it.
- **This skill does not modify `hera`, `hera-plan`, `hera-review`, or `hera-spawn-review`.** It is a
  standalone reference; `hera-spawn-review` is prior art for this same pattern (already resolves
  `profile_resolve`'s `panel` block and gates finders the same way), not something this skill wraps
  or depends on.
