fix(agent-pool): map Grok effort + Kimi reasoning slugs to Cloud API

cursor-grok-4.5-{low,medium,high}[-fast] and kimi-k3-low were falling
back to composer-2.5 against live /v1/models (grok-4.5 + effort,
kimi-k3 + reasoning). Teach resolvePoolModel the mapping, document
when to pick each Grok effort in model-selection, and align plan/work
skills.
This commit is contained in:
Cursor Agent 2026-08-01 03:32:16 +00:00
parent 009bcb732c
commit 629364f3e4
No known key found for this signature in database
7 changed files with 196 additions and 57 deletions

View file

@ -4,34 +4,59 @@ description: >-
Escolhe o modelo (e o nível de raciocínio/effort) com melhor custo/valor
para cada classe de tarefa do Teqo, e despacha subagentes já no modelo certo
via o parâmetro `model` da tool Task. Caminho feliz único: Composer 2.5 ↔
Grok 4.5 (effort) ↔ Kimi K3 Low (exec bipartida). Usar no início de
implementação, ao despachar subagentes, antes de /simplify, ou quando o
usuário perguntar "qual modelo usar".
Grok 4.5 (effort low|medium|high) ↔ Kimi K3 Low (exec bipartida). Usar no
início de implementação, ao despachar subagentes, antes de /simplify, ou
quando o usuário perguntar "qual modelo usar".
---
# Seleção de modelo × effort
Dois eixos, nesta ordem: **(1) classe da tarefa → modelo**, **(2) custo de uma falha → effort**. Preço e contexto compram capacidade; effort compra deliberação.
Dois eixos, nesta ordem: **(1) classe da tarefa → família de modelo**, **(2) se Grok → effort**. Preço e contexto compram capacidade; effort compra deliberação. **Não pule o eixo 2** — High não é o default automático só porque a Issue saiu de Composer.
## Tabela canônica (2026-07-31)
## Tabela canônica (2026-08-01)
| Prioridade | Modelo | Quando |
| ---------- | ------ | ------ |
| 1 (default) | **Composer 2.5** (`composer-2.5`, sem `-fast`) | Features, chores simples, fixes localizados, docs leves — sempre que couber |
| 2 | **Grok 4.5** (`cursor-grok-4.5-*`) | Effort por complexidade (abaixo) |
| 3 | **Kimi K3 Low** (`kimi-k3-low`) | Só na **fase de execução** de issues bipartidas (refactor grande / simplify / migrations+RBAC de blast radius alto), depois de um plano fechado |
| Prioridade | Família | Slug(s) no frontmatter `model:` | Quando |
| ---------- | ------- | -------------------------------- | ------ |
| 1 (default) | **Composer 2.5** | `composer-2.5` (evitar `-fast` salvo pedido explícito) | Features, chores simples, fixes localizados, docs leves — sempre que couber |
| 2 | **Grok 4.5** | `cursor-grok-4.5-low` \| `cursor-grok-4.5-medium` \| `cursor-grok-4.5-high` | Precisa de deliberação além do Composer (abaixo) |
| 3 | **Kimi K3 Low** | `kimi-k3-low` | Só na **fase de execução** de issues bipartidas (refactor grande / simplify / migrations+RBAC de blast radius alto), depois de um plano fechado |
**Removidos** desta skill (não usar como 1ª escolha nem fallback documentado): Kimi K2.7, Haiku, GLM, Gemini, Luna. Se a API Cloud rejeitar um slug, o pool já faz fallback para `composer-2.5` no código — não documentar outros modelos aqui.
**Removidos** desta skill (não usar como 1ª escolha nem fallback documentado): Kimi K2.7, Haiku, GLM, Gemini, Luna. O pool mapeia os slugs acima para a API Cloud (`grok-4.5`+`effort`, `kimi-k3`+`reasoning`); slug desconhecido → fallback `composer-2.5`.
### Effort (Grok 4.5)
### Quando sair do Composer → Grok (eixo 1)
| Effort | Uso |
| ------ | --- |
| Low | Bookkeeping, Prettier, merges, rebase, chores mecânicos |
| Medium | Discovery / análise moderada |
| **High** | **Default para issues complexas** (multi-domínio, design não óbvio) |
Suba para Grok só se **pelo menos um** for verdade:
Slug Task de referência: `cursor-grok-4.5-high` (High). Para Low/Medium na sessão principal, use o seletor de modelo/effort do produto — o pool resolve o id da Issue via `resolvePoolModel`.
- design / approach **não óbvio** (várias opções plausíveis, trade-offs reais);
- **discovery** de produto ou síntese de evidência (entrevistas, jargão, ilhas novas);
- **multi-domínio** (auth+schema+UI, Consent/LGPD, paradigm/skills com julgamento);
- critique visual / harvest de guardrails onde o critério é qualitativo.
Se o caminho está no plano e é execução mecânica → **fique em Composer** (ou Kimi na exec bipartida).
### Effort Grok (eixo 2) — escolha explícita
Depois de escolher Grok, **obrigatório** pickar um effort. Escreva o slug completo na Issue (`model: cursor-grok-4.5-…`).
| Effort | Slug (`model:`) | Nome no produto / Task | Quando |
| ------ | --------------- | ---------------------- | ------ |
| **Low** | `cursor-grok-4.5-low` | Cursor Grok 4.5 · Effort Low | Já é Grok-class, mas o trabalho é **mecânico com leve julgamento**: reescrever skills/docs após decisão travada, cutover de strings/base branch, bookkeeping que Composer erra por falta de contexto do paradigma. **Não** use Low para Prettier/merge puro — isso é Composer. |
| **Medium** | `cursor-grok-4.5-medium` | Cursor Grok 4.5 · Effort Medium | **Discovery / análise moderada**, critique/polish visual, harvest de padrões → guardrail, glossário a partir de evidência, chores de processo com trade-offs claros. Default quando Grok cabe e **não** é multi-domínio de alto risco. |
| **High** | `cursor-grok-4.5-high` | Cursor Grok 4.5 · Effort High | **Multi-domínio ou design não óbvio** com custo de falha alto: fundações (auth/sessão/schema), Consent/LGPD, plan half de bipartição, arquitetura nova. |
Opcional: sufixo `-fast` (`cursor-grok-4.5-high-fast`) só sob pedido explícito — o pool mapeia para `fast=true` na API.
**Heurística anti-viés:** se você ia marcar High por hábito, pergunte “a falha é cara **e** o desenho é aberto?”. Se só uma das duas → Medium (ou Low). Se nenhuma → Composer.
### Pool × API Cloud
| Slug na Issue | API `model.id` | params |
| ------------- | -------------- | ------ |
| `cursor-grok-4.5-low` | `grok-4.5` | `effort=low` |
| `cursor-grok-4.5-medium` | `grok-4.5` | `effort=medium` |
| `cursor-grok-4.5-high` | `grok-4.5` | `effort=high` |
| `kimi-k3-low` | `kimi-k3` | `reasoning=low` |
| `composer-2.5` | `composer-2.5` | — |
## Issues muito complexas (bipartir)
@ -45,24 +70,21 @@ Não despachar Kimi K3 Low sem plano fechado na Issue de plan.
## Regras
1. **Composer 2.5 é o default.** Se a tarefa cabe nele, não suba de modelo.
2. **Grok High para complexidade**, não para volume mecânico.
2. **Grok exige effort explícito** (`-low` / `-medium` / `-high` no slug). Não deixe `cursor-grok-4.5` sem sufixo; não use High como coringa.
3. **Kimi K3 Low só na exec bipartida** (ou quando a Issue já declara `kimi-k3-low` após um plan).
4. **Nunca economizar em:** migrations (história congelada), access control (`overrideAccess: false`), Consent/LGPD — se for uma Issue só disso e complexa, bipartir (plan Grok High → exec K3).
5. **Usuário pediu modelo específico:** a escolha dele vence; avise uma vez se houver desperdício claro.
## Como aplicar
- **Subagentes (`Task.model`):** só estes slugs nesta skill:
- `composer-2.5`
- `cursor-grok-4.5-high`
- `kimi-k3-low`
- `inherit` só quando a classe do subagente é a mesma da sessão
- **Frontmatter da Issue / pool:** use exatamente os slugs da tabela (incl. `-low`/`-medium`/`-high`).
- **Subagentes (`Task.model`):** prefira o slug da Issue quando o enum do produto listar; hoje o enum costuma expor `composer-2.5`, `cursor-grok-4.5-high`, `cursor-grok-4.5-high-fast`, `kimi-k3-low`. Se a Issue é `-low`/`-medium` e o enum não tem o slug, use `inherit` (sessão já no effort certo) ou `cursor-grok-4.5-high` só se a subtarefa for a parte difícil — **não** “promova” a Issue inteira para High.
- **Sessão principal:** o agente não troca o próprio modelo. Se o atual não for o da tabela, diga em uma linha e siga.
- **Pool:** `POOL_DEFAULT_MODEL_SLUG` = `composer-2.5`; `resolvePoolModel` cai para Composer se o slug da Issue for desconhecido.
- **Pool:** `POOL_DEFAULT_MODEL_SLUG` = `composer-2.5`; `resolvePoolModel` mapeia os slugs canônicos → API.
## Gates por fluxo
- **`/simplify`:** herda o modelo da sessão. Preferir sessão em Kimi K3 Low **só** quando houver Issue de exec bipartida; caso contrário Composer ou Grok High conforme complexidade.
- **`work-issue`:** verifica `model:` da Issue (assimétrico: sessão mais fraca → informa e segue; mais forte → informa e pausa). Ausente → aplica esta tabela.
- **`plan-issue`:** sugere via esta tabela; em issues muito complexas, registra o par plan/exec.
- **`agent-pool` smoke:** Composer default; Grok High / Kimi K3 Low em Issues de teste bipartidas.
- **`/simplify`:** herda o modelo da sessão. Preferir sessão em Kimi K3 Low **só** quando houver Issue de exec bipartida; caso contrário Composer ou Grok (effort pela tabela) conforme complexidade.
- **`work-issue`:** verifica `model:` da Issue (assimétrico: sessão mais fraca → informa e segue; mais forte → informa e pausa). Ausente → aplica esta tabela **incluindo** o effort Grok.
- **`plan-issue`:** sugere via esta tabela (slug completo com effort); em issues muito complexas, registra o par plan/exec.
- **`agent-pool` smoke:** Composer default; Grok com effort explícito / Kimi K3 Low em Issues de teste bipartidas.

View file

@ -61,7 +61,7 @@ Ordem topológica intra-lote (o plano do dependente cita o ID do dependido, que
Aplique a skill `model-selection` (tabela canônica) por item e registre a sugestão no cabeçalho do plano (`Model:`) e no resumo do gate. Caminho feliz único:
- `composer-2.5` — default (features, chores simples, fixes localizados, docs leves)
- `cursor-grok-4.5-high` — issues complexas / discovery (effort High); Low/Medium via seletor para bookkeeping
- `cursor-grok-4.5-low` | `cursor-grok-4.5-medium` | `cursor-grok-4.5-high` — Grok com **effort explícito** (ver model-selection: Low = mecânico com julgamento; Medium = discovery/análise; High = multi-domínio / falha cara)
- `kimi-k3-low` — **só** fase de execução de issues bipartidas (refactor / simplify / migration+RBAC de blast radius alto)
**Issues muito complexas:** registre **duas** Issues encadeadas — `{id}-plan` (`model: cursor-grok-4.5-high`, entregável = plano + critérios de aceite) e `{id}`/`{id}-exec` (`model: kimi-k3-low`, `depends: [{id}-plan]`). O valor vai ao frontmatter `model:` — propriedade da Issue, verificada (não recalculada) por `work-issue`.

View file

@ -12,7 +12,7 @@ Status: rascunho
Atualizado em: <YYYY-MM-DD>
Issue: #<N> (preencher após `pnpm agent:register`; enquanto não registrado: "—")
Priority: <P0 | P1 | P2 | P3>
Model: <slug sugerido por model-selection: composer-2.5 | cursor-grok-4.5-high | kimi-k3-low>
Model: <slug sugerido por model-selection: composer-2.5 | cursor-grok-4.5-low|medium|high | kimi-k3-low>
Impeccable: <A | B | C | D> — <uma linha: N/A sem UI | encaixe em tela X | UI nova em rota Y | ref Nome.png>
Appetite: <ex.: ~1–2 dias eng; migration + 1 action + encaixe em lista existente>
Responsável: —

View file

@ -99,7 +99,7 @@ Leia a propriedade `model:` do brief e compare com o modelo da sessão atual pel
- Propriedade **ausente** → aplique `model-selection` uma vez e registre a escolha na Issue (`gh issue edit <N>` no body, frontmatter `model:`).
- Issue `{id}-exec` / `kimi-k3-low` sem a dep de plan `done` → **pare** (não execute sem plano fechado).
Subagentes despachados via `Task` saem **no modelo da propriedade** quando couber (`Task.model` ∈ `composer-2.5` | `cursor-grok-4.5-high` | `kimi-k3-low`).
Subagentes despachados via `Task` saem **no modelo da propriedade** quando couber (`Task.model` ∈ `composer-2.5` | `cursor-grok-4.5-high` | `kimi-k3-low`; se a Issue for `-low`/`-medium` e o enum não listar, `inherit` — ver model-selection).
## Passo 3 — Freshness audit (enxuto)

View file

@ -272,7 +272,10 @@ const spawnPoolWorker = async ({ entry, workerUuid, models }) => {
{ event: 'spawn', agentId: agent.id, runId: run.id, url: agent.url },
`Worker do pool em execução: ${agent.url}`,
)
log(` spawn #${entry.issue.number} → ${agent.url} (modelo ${chosen.model.id})`)
const modelLabel = chosen.model.params?.length
? `${chosen.model.id}(${chosen.model.params.map((param) => `${param.id}=${param.value}`).join(',')})`
: chosen.model.id
log(` spawn #${entry.issue.number} → ${agent.url} (modelo ${modelLabel})`)
} catch (error) {
if (error instanceof CursorApiError && error.status === 409) {
const existing = await getCursorAgent(agentId)

View file

@ -1,25 +1,41 @@
/**
* Issue `model:` slug → Cursor Cloud `model.id` resolution
* (docs/plans/agent-pool-orchestrator.md §5).
* Issue `model:` slug → Cursor Cloud `model.id` (+ params) resolution
* (docs/plans/agent-pool-orchestrator.md §5; skill model-selection).
*
* Two namespaces meet here: the repo's model-selection slugs (frontmatter
* `model:`) and the Cloud API ids. The live table (`GET /v1/models`) is the
* authority — a slug resolves by exact id or alias, the repo `-fast` suffix
* maps to the model's `fast` param when the table says it exists, and
* anything else falls back to composer-2.5 (pool "Cursor Models", included
* authority.
*
* Repo slugs (Issue frontmatter / pool / skills):
* - `composer-2.5` / `composer-2.5-fast`
* - `cursor-grok-4.5-low` | `cursor-grok-4.5-medium` | `cursor-grok-4.5-high`
* (+ optional `-fast`) → API `grok-4.5` + `effort` (+ `fast`)
* - `kimi-k3-low` (also `kimi-k3-high` / `kimi-k3-max`) → API `kimi-k3` + `reasoning`
*
* Anything else falls back to composer-2.5 (pool "Cursor Models", included
* cost — model-selection rule 1). The fallback never crashes a tick.
*/
export const POOL_DEFAULT_MODEL_SLUG = 'composer-2.5'
const FAST_SUFFIX = '-fast'
const GROK_EFFORT_SLUG = /^cursor-grok-4\.5-(low|medium|high)(-fast)?$/
const KIMI_REASONING_SLUG = /^kimi-k3-(low|high|max)$/
const findModel = (models, slug) =>
models.find((model) => model.id === slug || (model.aliases ?? []).includes(slug)) ?? null
const paramSupportsValue = (model, paramId, value) => {
const param = (model.parameters ?? []).find((entry) => entry.id === paramId)
if (!param) return false
const values = param.values
if (!Array.isArray(values) || values.length === 0) return true
return values.some((entry) => entry.value === value)
}
/**
* @param {unknown} issueModel frontmatter `model:` value
* @param {Array<{ id: string, aliases?: string[], parameters?: Array<{ id: string }> }>} [apiModels] live /v1/models items
* @param {Array<{ id: string, aliases?: string[], parameters?: Array<{ id: string, values?: Array<{ value: string }> }> }>} [apiModels] live /v1/models items
* @returns {{ model: { id: string, params?: Array<{ id: string, value: string }> }, requested: string | null, usedFallback: boolean, warn?: string }}
*/
export const resolvePoolModel = (issueModel, apiModels = []) => {
@ -43,9 +59,46 @@ export const resolvePoolModel = (issueModel, apiModels = []) => {
if (!requested) return fallback()
const grokMatch = GROK_EFFORT_SLUG.exec(requested)
if (grokMatch) {
const effort = grokMatch[1]
const wantFast = Boolean(grokMatch[2])
const base =
findModel(apiModels, 'grok-4.5') ??
findModel(apiModels, 'cursor-grok-4.5') ??
(apiModels.length === 0
? { id: 'grok-4.5', parameters: [{ id: 'effort' }, { id: 'fast' }] }
: null)
if (
base &&
paramSupportsValue(base, 'effort', effort) &&
(!wantFast || paramSupportsValue(base, 'fast', 'true'))
) {
/** @type {Array<{ id: string, value: string }>} */
const params = [{ id: 'effort', value: effort }]
if (wantFast) params.push({ id: 'fast', value: 'true' })
return { model: { id: base.id, params }, requested, usedFallback: false }
}
}
const kimiMatch = KIMI_REASONING_SLUG.exec(requested)
if (kimiMatch) {
const reasoning = kimiMatch[1]
const base =
findModel(apiModels, 'kimi-k3') ??
(apiModels.length === 0 ? { id: 'kimi-k3', parameters: [{ id: 'reasoning' }] } : null)
if (base && paramSupportsValue(base, 'reasoning', reasoning)) {
return {
model: { id: base.id, params: [{ id: 'reasoning', value: reasoning }] },
requested,
usedFallback: false,
}
}
}
if (requested.endsWith(FAST_SUFFIX)) {
const base = findModel(apiModels, requested.slice(0, -FAST_SUFFIX.length))
const supportsFast = (base?.parameters ?? []).some((param) => param.id === 'fast')
const supportsFast = paramSupportsValue(base ?? {}, 'fast', 'true')
if (base && supportsFast) {
return {
model: { id: base.id, params: [{ id: 'fast', value: 'true' }] },

View file

@ -5,24 +5,40 @@ import { describe, expect, it } from 'vitest'
import { POOL_DEFAULT_MODEL_SLUG, resolvePoolModel } from '../../scripts/lib/agent-pool-models.mjs'
const API_MODELS = [
{ id: 'composer-2.5', aliases: ['composer-latest'], parameters: [{ id: 'fast' }] },
{ id: 'cursor-grok-4.5-high' },
{ id: 'kimi-k3-low' },
{
id: 'composer-2.5',
aliases: ['composer-latest'],
parameters: [{ id: 'fast', values: [{ value: 'false' }, { value: 'true' }] }],
},
{
id: 'grok-4.5',
parameters: [
{
id: 'effort',
values: [{ value: 'low' }, { value: 'medium' }, { value: 'high' }],
},
{ id: 'fast', values: [{ value: 'false' }, { value: 'true' }] },
],
},
{
id: 'kimi-k3',
parameters: [
{
id: 'reasoning',
values: [{ value: 'low' }, { value: 'high' }, { value: 'max' }],
},
],
},
]
describe('resolvePoolModel', () => {
it('resolves a declared slug by exact id', () => {
expect(resolvePoolModel('kimi-k3-low', API_MODELS)).toEqual({
model: { id: 'kimi-k3-low' },
requested: 'kimi-k3-low',
it('resolves composer by exact id and alias', () => {
expect(resolvePoolModel('composer-2.5', API_MODELS)).toEqual({
model: { id: 'composer-2.5' },
requested: 'composer-2.5',
usedFallback: false,
})
})
it('resolves by alias', () => {
const result = resolvePoolModel('composer-latest', API_MODELS)
expect(result.model).toEqual({ id: 'composer-2.5' })
expect(result.usedFallback).toBe(false)
expect(resolvePoolModel('composer-latest', API_MODELS).model).toEqual({ id: 'composer-2.5' })
})
it('maps the repo -fast suffix to the fast param when the table supports it', () => {
@ -34,11 +50,44 @@ describe('resolvePoolModel', () => {
expect(result.usedFallback).toBe(false)
})
it('falls back when the base of a -fast slug lacks the fast param', () => {
const result = resolvePoolModel('kimi-k3-low-fast', API_MODELS)
it.each([
['cursor-grok-4.5-low', 'low'],
['cursor-grok-4.5-medium', 'medium'],
['cursor-grok-4.5-high', 'high'],
] as const)('maps %s → grok-4.5 effort=%s', (slug, effort) => {
expect(resolvePoolModel(slug, API_MODELS)).toEqual({
model: { id: 'grok-4.5', params: [{ id: 'effort', value: effort }] },
requested: slug,
usedFallback: false,
})
})
it('maps cursor-grok-4.5-high-fast → effort high + fast', () => {
expect(resolvePoolModel('cursor-grok-4.5-high-fast', API_MODELS)).toEqual({
model: {
id: 'grok-4.5',
params: [
{ id: 'effort', value: 'high' },
{ id: 'fast', value: 'true' },
],
},
requested: 'cursor-grok-4.5-high-fast',
usedFallback: false,
})
})
it('maps kimi-k3-low → kimi-k3 reasoning=low', () => {
expect(resolvePoolModel('kimi-k3-low', API_MODELS)).toEqual({
model: { id: 'kimi-k3', params: [{ id: 'reasoning', value: 'low' }] },
requested: 'kimi-k3-low',
usedFallback: false,
})
})
it('falls back when a -fast slug base lacks the fast param', () => {
const result = resolvePoolModel('kimi-k3-fast', API_MODELS)
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
expect(result.usedFallback).toBe(true)
expect(result.warn).toContain('kimi-k3-low-fast')
})
it('falls back to composer-2.5 with a warn for unknown slugs', () => {
@ -58,7 +107,19 @@ describe('resolvePoolModel', () => {
}
})
it('still resolves when the live table is unavailable (degraded mode)', () => {
it('still emits canonical Cloud shapes for known slugs when the table is empty', () => {
expect(resolvePoolModel('cursor-grok-4.5-medium', [])).toEqual({
model: { id: 'grok-4.5', params: [{ id: 'effort', value: 'medium' }] },
requested: 'cursor-grok-4.5-medium',
usedFallback: false,
})
expect(resolvePoolModel('kimi-k3-low', []).model).toEqual({
id: 'kimi-k3',
params: [{ id: 'reasoning', value: 'low' }],
})
})
it('falls back for unknown slugs when the live table is unavailable', () => {
const result = resolvePoolModel('qualquer-coisa', [])
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
expect(result.warn).toContain('/v1/models')