feat(OPS127): launch do worktree nasce na variante max (Closes #1149)

This commit is contained in:
Francisco Solla 2026-09-18 16:38:45 -03:00
parent 161c49a260
commit fd68014edc
12 changed files with 216 additions and 30 deletions

View file

@ -17,9 +17,10 @@
# `/bug-fix <bag>` no `fix`; `new` e `plan` sem bag abrem só a sessão) e ANEXA
# o TUI (`opencode attach -s <id>`) —
# fechar o terminal NÃO encerra o run; reentre com `pnpm agent:session attach` e
# encerre com `pnpm agent:session stop`. Sem `--variant` (OPS95: o yargs do TUI
# rejeita o flag e só imprime o helper; variantes ficam na config global da
# máquina, via Ctrl+T). Modelo por invocação: `--cheap`
# encerre com `pnpm agent:session stop`. Sem `--variant` na diretiva nem no attach
# (OPS95: o yargs do TUI rejeita o flag). OPS127: a sessão nasce na variante `max`
# — ela vai no body do `POST /session` e no argv do `opencode run` do
# driver/headless (o TUI anexado herda), nunca na linha do launch. Modelo por invocação: `--cheap`
# (cheapestinference/deepseek-v4-flash), `--pro` (deepseek/deepseek-v4-pro),
# `--zen` (opencode/muse-spark-1.3-contributor-free), `--go` (opencode-go/deepseek-v4.1-flash), `--alibaba`
# (alibaba-token-plan/deepseek-v4-flash), `--glm` (opencode-go/glm-5.3-flash),

View file

@ -26,7 +26,7 @@ VMs Cloud **não rodam** `docker compose` / `pnpm db:start`. Postgres nativo: `.
## Per-worktree environments (`pnpm worktree next`)
Parallel agents must not fight over port 3000, the shared `teqo_test`, or a second Postgres container. `pnpm worktree next [--stay] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]` provisions each worktree with a deterministic isolated env (`next`/`plan`/`new` print `cd <dir>` by default — apply it, or pass `--stay` to suppress; `--cheap/--pro/--zen/--go/--alibaba/--glm/--free` selects the model per invocation via `WORKTREE_MODEL_MAP`):
Parallel agents must not fight over port 3000, the shared `teqo_test`, or a second Postgres container. `pnpm worktree next [--stay] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]` provisions each worktree with a deterministic isolated env (`next`/`plan`/`new` print `cd <dir>` by default — apply it, or pass `--stay` to suppress; `--cheap/--pro/--zen/--go/--alibaba/--glm/--free` selects the model per invocation via `WORKTREE_MODEL_MAP` — the session is created in variant `max` (OPS127)):
- `slot` = numeric part of the issue code (`C15` → 15, `B164` → 164; hash fallback without digits; bumped on collision among live worktrees).
- Dev server port `3100+slot`; databases `teqo_wt<slot>` (dev) and `teqo_wt<slot>_test` (tests), created on the **one shared container** with the full migration chain **and the minimal seed** applied (one-time, ~30 s each; OPS28 parity with CI, which runs `migrate → seed:minimal` before e2e — a worktree's `pnpm test:e2e` mirrors CI).

View file

@ -31,7 +31,7 @@ No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.op
**Default-go:** `next`, `plan` e `new` imprimem `cd <dir>` na última linha **por padrão**; `--stay` suprime; modelos por invocação via `--cheap/--pro/--zen/--go/--alibaba/--glm/--free` (mapa fixo `WORKTREE_MODEL_MAP`, at-most-one; sem flag o preset permanece); `--go` remapeado em OPS93 para o provider OpenCode Go, em OPS95 para `opencode-go/hy3` e em OPS112 para `opencode-go/deepseek-v4.1-flash` (antes no-op do OPS24). No terminal interativo, use a função `worktree()` de **`.agents/shell/worktree.sh`** (uma linha de `source` no profile) para o `cd` ser aplicado de verdade no shell que te chamou — node não consegue mudar o cwd do shell pai. `kill` também imprime `cd <main>` no fim (a sessão nunca fica num diretório destruído).
**Launch do opencode (OPS26 + OPS33 + OPS31 + OPS93 + OPS95 + OPS110):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new`/`fix` imprimem também a diretiva `launch node <abs>/scripts/agent-session.mjs start --purpose=<next|plan|new|fix> --dir=<dir> --model <preset|map> [--issue <N>] [--argument "<bag>"]` **antes** do `cd`, e a função a executa depois do cd. Desde a OPS110 o launch não abre um TUI dono da sessão: o `agent-session` sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o driver destacado (auto-submete `/work-issue --issue <N>` no `next`, `/plan-issue <bag>` no `plan` com bag, `/bug-fix <bag>` no `fix`; `new` e `plan` sem bag abrem só a sessão) e anexa o TUI (`opencode attach -s <id>`) — fechar o terminal **não** encerra o run; reentre com `pnpm agent:session attach` e encerre com `pnpm agent:session stop` (ver a seção "Sessões persistentes" de `docs/AGENT-OPS.md`). Sem `--variant` na diretiva (OPS95: o yargs do TUI rejeita o flag desconhecido e só imprime o helper — variantes ficam na config global do opencode, selecionáveis via Ctrl+T; `opencode run --variant max` segue sendo a via CLI para runs headless). Modelo por invocação via `--cheap` (cheapestinference/deepseek-v4-flash), `--pro` (deepseek/deepseek-v4-pro), `--zen` (opencode/muse-spark-1.3-contributor-free), `--go` (opencode-go/deepseek-v4.1-flash), `--alibaba` (alibaba-token-plan/deepseek-v4-flash) — valores corrigidos no OPS95 (`--go`/`--zen` em OPS112) — e `--glm` (opencode-go/glm-5.3-flash, OPS100), `--free` (openrouter/openrouter/free, OPS100); sem flag o preset `deepseek/deepseek-flash` permanece. Sem `exec`: sair do opencode volta ao shell no worktree. Presets e mapa são constantes em `scripts/lib/worktree.mjs`; o ciclo de vida das sessões (serve/start/attach/stop/list) vive em `scripts/agent-session.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.**
**Launch do opencode (OPS26 + OPS33 + OPS31 + OPS93 + OPS95 + OPS110):** só a função shell marca `TEQO_WORKTREE_TERMINAL=1`; com o marcador, `next`/`plan`/`new`/`fix` imprimem também a diretiva `launch node <abs>/scripts/agent-session.mjs start --purpose=<next|plan|new|fix> --dir=<dir> --model <preset|map> [--issue <N>] [--argument "<bag>"]` **antes** do `cd`, e a função a executa depois do cd. Desde a OPS110 o launch não abre um TUI dono da sessão: o `agent-session` sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o driver destacado (auto-submete `/work-issue --issue <N>` no `next`, `/plan-issue <bag>` no `plan` com bag, `/bug-fix <bag>` no `fix`; `new` e `plan` sem bag abrem só a sessão) e anexa o TUI (`opencode attach -s <id>`) — fechar o terminal **não** encerra o run; reentre com `pnpm agent:session attach` e encerre com `pnpm agent:session stop` (ver a seção "Sessões persistentes" de `docs/AGENT-OPS.md`). Sem `--variant` na diretiva nem no attach (OPS95: o yargs do TUI rejeita o flag desconhecido e só imprime o helper). OPS127: a sessão nasce na variante `max` — ela viaja no body do `POST /session` e no argv do `opencode run` do driver/headless (o TUI anexado herda), nunca na linha do launch. Modelo por invocação via `--cheap` (cheapestinference/deepseek-v4-flash), `--pro` (deepseek/deepseek-v4-pro), `--zen` (opencode/muse-spark-1.3-contributor-free), `--go` (opencode-go/deepseek-v4.1-flash), `--alibaba` (alibaba-token-plan/deepseek-v4-flash) — valores corrigidos no OPS95 (`--go`/`--zen` em OPS112) — e `--glm` (opencode-go/glm-5.3-flash, OPS100), `--free` (openrouter/openrouter/free, OPS100); sem flag o preset `deepseek/deepseek-flash` permanece. Sem `exec`: sair do opencode volta ao shell no worktree. Presets e mapa são constantes em `scripts/lib/worktree.mjs`; o ciclo de vida das sessões (serve/start/attach/stop/list) vive em `scripts/agent-session.mjs`. **O comando `/worktree` do opencode (e qualquer automação sem o marcador) nunca lança o TUI.**
## Fluxo quando invocado como skill (agentes que não têm opencode)

View file

@ -10,7 +10,7 @@ Rode o script determinístico abaixo (não improvise a lógica) e reporte o resu
- `fix [bag] [--stay] [--auto] [--cheap|--pro|--zen|--go|--alibaba|--glm|--free]`: cria um worktree de **correção de bug** (skill `/bug-fix`) **diferente a cada invocação** (branch `fix/<bag>`, ou `fix/<n>` sequencial sem bag; sufixo `-2`/`-3` em colisão); o prefixo minúsculo `fix/…` nunca conflita com `next`, `plan` nem `new`. **Não claima nem cria Issues** — o registro do bug é o post-mortem da skill. O bag é a descrição do bug: no terminal, o launch carrega `--argument="<bag>"` e o driver destacado auto-submete `/bug-fix <bag>` (a descrição chega com a skill). Mesmo contrato de `cd` do `plan`: aplique a última linha `cd <dir>` por padrão; `--stay` suprime; flag de modelo como no `next`.
- `kill [--force]`: destrói o worktree em que você está, encerra a sessão persistente do run (OPS110) e remove seus bancos. O script imprime `cd <main>` na última linha — aplique-o (`cd <main>`) para a sessão sair do diretório destruído.
> **OPS122 — flags honradas ou falha alto.** `--auto` (opt-out do GATE da skill) transposta para a invocation do driver: `/work-issue --issue <N> --auto` (next), `/plan-issue --auto <bag>` (plan com bag), `/bug-fix --auto <bag>` (fix). Vale em `next`/`fix` e em `plan` COM bag; falha alto em `new`, `plan` sem bag, `--stay --auto`, `--headless --auto` e `--auto` **neste comando** (sem `TEQO_WORKTREE_TERMINAL` não há launch — rode `pnpm worktree` no terminal). Flag desconhecida falha alto com sugestão, nunca é descartada. O modelo pedido (`--zen/--go/…`) agora é gravado na própria SESSÃO do servidor, então o TUI anexado o herda inclusive nos driverless (`new`, `plan` sem bag).
> **OPS122 — flags honradas ou falha alto.** `--auto` (opt-out do GATE da skill) transposta para a invocation do driver: `/work-issue --issue <N> --auto` (next), `/plan-issue --auto <bag>` (plan com bag), `/bug-fix --auto <bag>` (fix). Vale em `next`/`fix` e em `plan` COM bag; falha alto em `new`, `plan` sem bag, `--stay --auto`, `--headless --auto` e `--auto` **neste comando** (sem `TEQO_WORKTREE_TERMINAL` não há launch — rode `pnpm worktree` no terminal). Flag desconhecida falha alto com sugestão, nunca é descartada. O modelo pedido (`--zen/--go/…`) agora é gravado na própria SESSÃO do servidor — com a variante `max` (OPS127: body do `POST /session` e argv do driver/headless; o `attach` segue sem flag) —, então o TUI anexado herda modelo e variante inclusive nos driverless (`new`, `plan` sem bag).
> **Este comando roda SEM o marcador `TEQO_WORKTREE_TERMINAL`, então nunca recebe a diretiva `launch`** — só o `cd`. O launch (OPS26/OPS33+OPS93+OPS95+**OPS110**: delega ao `scripts/agent-session.mjs`, que sobe o servidor compartilhado, dispara o driver destacado e anexa o TUI — fechar o terminal não encerra o run) acontece apenas na função `worktree()` de `.agents/shell/worktree.sh` (terminal interativo); nunca abra um TUI aninhado a partir daqui.

View file

@ -62,12 +62,12 @@ Labels (GitHub): `ready|in-progress|blocked|done|in-prod`, `prio:*`, `kind:*`, `
## Sessões persistentes (attach/detach — OPS110)
O launch do terminal (`worktree next|plan|new|fix`) **não abre mais um TUI dono da sessão**: ele chama `pnpm agent:session start …`, que sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o **driver destacado** (`opencode run --attach … --auto --command <skill>`, que auto-aprova permissões enquanto vive) e **anexa** o TUI (`opencode attach -s <sessionID>`). Fechar o terminal/TUI desconecta — o run continua; encerrar é ato explícito.
O launch do terminal (`worktree next|plan|new|fix`) **não abre mais um TUI dono da sessão**: ele chama `pnpm agent:session start …`, que sobe/reaproveita o `opencode serve` compartilhado, cria a sessão endereçável, dispara o **driver destacado** (`opencode run --attach … --auto --command <skill>`, que auto-aprova permissões enquanto vive) e **anexa** o TUI (`opencode attach -s <sessionID>`). A sessão nasce na variante `max` (OPS127): ela vai no body do `POST /session` e no argv do `opencode run` do driver/headless — o `opencode attach` não emite flag. Fechar o terminal/TUI desconecta — o run continua; encerrar é ato explícito.
| Comando | Faz |
| -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `pnpm agent:session serve [--hostname=H] [--port=P]` | Sobe/reaproveita o servidor (loopback `127.0.0.1:4199`; `TEQO_AGENT_SERVER_HOST/PORT`) |
| `pnpm agent:session start --purpose=<next\|plan\|new\|fix> --dir=<D> --model=<M> [--issue=N] [--argument="<bag>"] [--new]` | Cria a sessão, dispara o driver (quando há comando a auto-submeter — `plan` sem bag é driverless) e anexa o TUI |
| `pnpm agent:session start --purpose=<next\|plan\|new\|fix> --dir=<D> --model=<M> [--issue=N] [--argument="<bag>"] [--new]` | Cria a sessão (variante `max` no body — OPS127), dispara o driver (quando há comando a auto-submeter — `plan` sem bag é driverless) e anexa o TUI |
| `pnpm agent:session attach [--session=<ses_…>\|--branch=<B>\|--issue=N]` | (Re)entra na sessão — default: branch do cwd; sobe o servidor se ele estiver fora |
| `pnpm agent:session stop [--session=<ses_…>\|--branch=<B>\|--issue=N]` | Ato explícito de encerrar: abort via API + fim do driver + `stoppedAt` |
| `pnpm agent:session list [--json]` | Status dos runs (`working\|idle\|stopped\|unknown`); `--json` é o contrato do OPS109 |

View file

@ -0,0 +1 @@
**OPS127 — launch do worktree nasce na variante `max` do modelo selecionado (2026-09-18):** todo lançamento (`worktree next|plan|new|fix`, incluindo `fix --headless`/auto-unblock e o preset sem flag) passa a pedir a variante `max` do modelo escolhido, sem flag nova no terminal. A variante viaja no **body do `POST /session`** (`buildCreateSessionBody` → `Session.model.variant`, herdada pelo TUI anexado) e no argv do `opencode run` do driver destacado (`driverArgs`) e do headless (`opencodeHeadlessArgs`); o `opencode attach` e a diretiva `launch` continuam **sem** `--variant` (o yargs do TUI rejeita o flag — veto OPS95 preservado) e a config global da máquina fica intocada. Reabre o veto OPS95 apenas para o caminho da sessão: o fato novo é que o opencode 1.18.31 aceita `variant` dentro de `model` no `POST /session` e persiste. Dono único do literal é `MODEL_VARIANT` em `scripts/lib/agent-session.mjs`; o cardápio/preset de modelos (`WORKTREE_MODEL_MAP`, `OPENCODE_PRESET_MODEL`) e o mapa de flags não mudam — `--go` segue `opencode-go/deepseek-v4.1-flash` (OPS112). Sem migration, sem schema, sem UI. Verificação: `pnpm test:unit` (pins de `agentSession.unit.spec.ts` e `worktree.unit.spec.ts` atualizados) e `pnpm gate:fast`.

View file

@ -0,0 +1,144 @@
# Impl: `pnpm worktree … --go` abre a sessão no DeepSeek V4.1 Flash (`opencode-go`) na variante `max`
Status: aprovado
Atualizado em: 2026-09-18
Issue: #1149
Intenção: docs/plans/worktree-go-variant-max.md
Appetite restante: herdado (~0,5 dia eng — plumbing de launch + textos + pins). Sem migration, sem schema, sem UI, sem DB.
Modo: autônomo (`--auto`) — impl plan nasce aprovado pelo agente.
## Leitura da intenção
- **Outcome:** todo lançamento de worktree (`next`, `plan`, `new`, `fix` — inclusive `fix --headless`/auto-unblock e o preset sem flag) cria a sessão do servidor com `Session.model.variant === 'max'` para o modelo selecionado, e `--go` continua em `opencode-go/deepseek-v4.1-flash`. Verificável no body de `POST /session` virado `Session.model.variant`, no argv do driver/headless e nos pins unitários. A TUI anexada (`opencode attach`, sem flag) herda.
- **O que NÃO negociar:**
- Nenhuma flag `--variant` no `opencode attach` nem na diretiva `launch` (o yargs do TUI rejeita o flag — veto OPS95).
- Config global da máquina (`~/.config/opencode/…` / Ctrl+T) intocada; `opencode.json` do repo intocado.
- Lançamento nunca quebra por variante: nenhum lookup/`throw` local por capacidade de modelo; o literal é constante.
- Cardápio/modelos tocados **só** na variante — `WORKTREE_MODEL_MAP`/`OPENCODE_PRESET_MODEL` inalterados (OPS112 deixa `go = opencode-go/deepseek-v4.1-flash`).
- Docs históricos congelados (`docs/plans/*` anteriores, HISTORY) intocados.
- **O que reavaliar:** a hipótese "variante nunca entra na diretiva" permanece correta, mas o dono da variante **não** é `resolveWorktreeModel` (que segue devolvendo string `provider/model`); é `scripts/lib/agent-session.mjs` (dono do ciclo de vida da sessão). Reavalia-se também a hipótese de guardar `variant` no estado: **não** guardar (ver D2). O sintoma "nem no provider correto" já foi atacado pela OPS122 (`createSession` passa o modelo no body); a variante não altera esse caminho — a verificação viva fica no aceite.
## Abordagem recomendada
```mermaid
flowchart LR
W["pnpm worktree next|plan|new|fix (--go …)"] --> R["resolveWorktreeModel\n→ 'provider/model' (string, sem variante)"]
W --> H["fix --headless\n(scripts/worktree.mjs:761)"]
R --> D["opencodeLaunchDirective\n--model <model> (SEM --variant)"]
D --> S["agent-session start --model <model>"]
S --> B["buildCreateSessionBody({ model })\n→ model:{ providerID, id, variant:'max' }"]
S --> DR["driverArgs → --model <m> --variant max"]
B --> SV["POST /session\ndirectory=…"]
SV --> P["Session.model.variant = 'max'"]
P --> TUI["opencode attach (sem flags) herda"]
H --> HA["opencodeHeadlessArgs\n→ --model <m> --variant max"]
C["MODEL_VARIANT='max'\n(dono: lib/agent-session.mjs)"] --> B
C --> DR
C --> HA
```
**Opções consideradas:** A | B | C | D
**Recomendação:** **A — variante como constante `MODEL_VARIANT = 'max'` aplicada **dentro** de `scripts/lib/agent-session.mjs`** (body da sessão + argv do driver) e reusada por `opencodeHeadlessArgs` em `scripts/lib/worktree.mjs`. A diretiva `launch`/`attach` permanece model-only. É a única opção que (1) faz a variante chegar à sessão pelo caminho já validado pela OPS122 (`POST /session`), (2) mantém o pin "diretiva sem `--variant`" verdadeiro (nenhum flag novo no TUI), (3) cobre **todos** os propósitos — terminal, driverless, driver e headless — com um único dono de literal, e (4) cabe no appetite (poucas linhas + textos/pins).
**Rejeitadas:**
- **B — `--variant` roteado pela diretiva `opencodeLaunchDirective` até o CLI de sessão.** Rejeitada: reintroduz o flag no canal tokenizado da diretiva/TUI (a classe exata da quebra OPS95 — yargs do TUI rejeita flag desconhecida) e torna a diretiva inconsistente com o attach, que continua sem o flag. A variante é atributo do _modelo da sessão_, não um parâmetro de linha do TUI.
- **C — mapa de capacidade por modelo (só enviar `max` a quem suporta).** Rejeitada: over-engineering; a intenção corta explicitamente a revisão do cardápio e diz que `max` é a única variante, em todos os propósitos. Um mapa seria mais um lugar para divergir do provider real.
- **D — variante só no body da sessão, sem argv no driver/headless.** Rejeitada: o driver é um `opencode run` separado (`driverArgs`, `opencodeHeadlessArgs`) — sem `--variant` ele abriria no variant default e divergiria da sessão; o aceite exige **todos** os lançamentos.
**Decisões caras registradas:**
- **D1 — Dono do literal:** `scripts/lib/agent-session.mjs` exporta `MODEL_VARIANT = 'max'`; `scripts/lib/worktree.mjs` o importa no import já existente com `assertKnownFlags, SKILL_AUTO_FLAG` (`lib/worktree.mjs:10`) e o usa em `opencodeHeadlessArgs`. `worktree.mjs` e o CLI `agent-session.mjs` **não** re-spelam `'max'` (single source).
- **D2 — Estado da sessão continua `model` string, variante implícita.** Não gravar `variant` no estado: a variante é constante global; registrá-la forçaria migrar estados antigos (sem `variant`) e a checagem de reuso (`agent-session.mjs:520`) passaria a falhar fechado por diferença de `undefined`, quebrando reuso de runs vivos. **Gatilho de revisitação:** se um dia existir uma segunda variante real ou seleção por sessão, aí sim gravar e comparar.
- **D3 — Posição do flag no argv headless:** `['opencode','run','--model',model,'--variant',MODEL_VARIANT,'--auto','--command',command, report]` — `--variant` **antes** do `report` posicional (não pode ir depois, viraria argumento do report). Pin de array exato no teste.
- **D4 — Guardrail "nunca quebra":** a variante entra como propriedade string constante; helpers seguem puros (aceitam/devolvem string, sem lookup que lance). Se um modelo do cardápio não declarar `max`, o servidor persiste o que recebe e o provider decide — comportamento do upstream, não do nosso launch. Verificação viva dos modelos usados nos gates.
### Componentes / mudanças
- **`MODEL_VARIANT`** (`scripts/lib/agent-session.mjs`, novo, acima de `buildCreateSessionBody`): `export const MODEL_VARIANT = 'max'` + docblock citando 1.18.31 (`Session.model.variant`) e o porquê de não ir no attach (OPS95/OPS127).
- **`buildCreateSessionBody`** (`scripts/lib/agent-session.mjs:245-248`): assinatura inalterada `({ model = null } = {})`; quando há modelo, retorna `{ model: { ...parseModelRef(model), variant: MODEL_VARIANT } }` (variante incondicional — sem parâmetro de override); sem modelo segue `{}`. Trocar o comentário "No `variant` (OPS95: variants stay on the machine's global config)" (`:242`) pela nova semântica.
- **`driverArgs`** (`scripts/lib/agent-session.mjs:368-394`): inserir `'--variant', MODEL_VARIANT` imediatamente após `--model <model>` (antes de `--auto`), incondicional. Nenhum parâmetro de override, nenhum `throw` novo (simplify pós-revisão: override era generalidade especulativa — sem caller de produção).
- **`opencodeHeadlessArgs`** (`scripts/lib/worktree.mjs:333-345`): inserir `'--variant', MODEL_VARIANT` após o modelo; importar `MODEL_VARIANT` de `./agent-session.mjs` na linha `:10`. `headlessDirective` (`:351-356`) herda o argv sem mudança própria.
- **CLI `createSession`/`cmdStart`** (`scripts/agent-session.mjs:272-279`, `:468-572`): **sem mudança de código** — `buildCreateSessionBody({ model })` já ganha o default e `driverArgs({ …, model, invocation })` também; o estado `model` (`:538`) permanece string (D2).
- **Migration:** sem migration — tooling de launch; nenhum schema/coleção Payload muda.
- **Access / Consent:** N/A — nenhum dado de cidadão, login de campanha ou Consent envolvido.
- **UI:** N/A — nenhum componente Next/Payload. O TUI do opencode é consumidor externo (sem superfície de design; Impeccable A).
### Dados → forma (se aplicável)
N/A — tooling. Não há dado de negócio modelado; a mudança é uma constante de variante aplicada ao body da sessão e ao argv do driver. Perguntas de data-presentation não se aplicam.
## Pins de teste a atualizar (valores exatos)
**`tests/unit/agentSession.unit.spec.ts`:**
- **L328-337** (`buildCreateSessionBody` com modelo) — passa a esperar:
`{ model: { providerID: 'opencode-go', id: 'deepseek-v4.1-flash', variant: MODEL_VARIANT } }` (referência à constante; o literal é pinado à parte).
Os casos `null`/`undefined`/`{}`/`{}` (L332-335) seguem `{}`; o `throw` de `'no-slash'` (L336) inalterado.
- **L339-342** — o `it` "never emits a variant (OPS95)" vira o pin único da constante: `expect(MODEL_VARIANT).toBe('max')` (o body exato de L328 já cobre a emissão).
- **L458-483** (`driverArgs` com invocation) — inserir `'--variant', 'max'` logo após `'--model', 'deepseek/deepseek-flash'` (L476-477), antes de `--auto`.
- **L486-509** (`driverArgs` sem argumentos, model `'m'`) — inserir `'--variant', 'max'` após `'--model', 'm'`.
- **L550-560** (`attachArgs`) — manter o array exato e acrescentar `expect(argv).not.toContain('--variant')` (guardrail OPS127/OPS95).
- **Import novo no topo** — `MODEL_VARIANT` (usado no pin e no `toEqual` do body).
**`tests/unit/worktree.unit.spec.ts`:**
- **L296-303** — a asserção `not.toContain('--variant')` **permanece** (a diretiva segue model-only); atualizar só a descrição do `it` para "diretiva segue sem --variant; a variante viaja no body/driver (OPS127)".
- **L426-439** (`opencodeHeadlessArgs`) — array esperado vira:
`['opencode','run','--model','deepseek/deepseek-flash','--variant','max','--auto','--command',OPENCODE_HEADLESS_COMMAND,'run 1 falhou']`.
- **L442-450, L452-463** — sem mudança estrutural (o `:452` só confere `argv[0]`).
- **L466-479** (mapa/preset) e **L482-519** (resolução/diretiva) — **inalterados** (cardápio intocado).
- Nenhuma mudança em `tests/unit/autoUnblock.unit.spec.ts` / `autoUnblockWorkflow.unit.spec.ts` — `parseHeadlessDirective` valida só `dir/branch/argv` e o wrapper passa `directive.argv` adiante; o `--variant max` flui sem contrato novo.
**`tests/unit/opencodeCommands.unit.spec.ts`** — **não tocar** (pina frontmatter dos comandos; anti-goal).
## Textos / superfícies a sincronizar (mesma entrega)
- **`scripts/lib/agent-session.mjs:238-248`** — docblock de `buildCreateSessionBody`: remove "No `variant` (OPS95)" e explica `variant: 'max'` no body (1.18.31), attach sem flag.
- **`scripts/lib/agent-session.mjs:360-367`** — docblock de `driverArgs`: acrescentar `--variant max`.
- **`scripts/lib/worktree.mjs:31-38`** (OPS95 no header), **`:43-52`** (mapa "No `--variant` is emitted") e **`:333-345`** — nota OPS127: diretiva/attach sem flag; variante no body e no argv run.
- **`scripts/worktree.mjs:33-49`** (docblock do launch) e **help `:991-1004`** — acrescentar que a sessão nasce em variante `max` (body/driver), sem alterar a linha `--model`.
- **`.agents/shell/worktree.sh:20-27`** e **`:76-77`** — trocar a frase "Sem `--variant` (OPS95… config global/Ctrl+T)" por: variante `max` aplicada na criação da sessão; o attach não leva flag.
- **`.opencode/commands/worktree.md:13`** — nota OPS122 estendida: modelo e variante `max` gravados na sessão (TUI anexado herda, inclusive driverless).
- **`.agents/skills/worktree-next-issue/SKILL.md:34`** — parágrafo de launch: substituir o trecho OPS95 de variante/global por `max` no body da sessão + driver.
- **`.agents/skills/local-database/SKILL.md:29`** — menção curta: o modelo selecionado é gravado na sessão na variante `max`.
- **`docs/AGENT-OPS.md:65`** (parágrafo "Sessões persistentes") e **`:70`** (linha da tabela `agent:session start`) — registrar que a criação fixa a variante `max` (ex.: `--model <M>` + variante `max` no body). **Não** tocar `:24` (ladder de design — só cita o modelo `opencode-go/…`, sem vínculo com variante).
- **Changelog novo:** `docs/changelog/2026-09-18-ops127.md` (uma entrada curta; nunca editar `docs/CHANGELOG-AGENTS.md`/HISTORY).
## Fases verificáveis
1. **Tracer (dono da variante) — quota ~40%:** `scripts/lib/agent-session.mjs` (`MODEL_VARIANT`, `buildCreateSessionBody`, `driverArgs`) + `tests/unit/agentSession.unit.spec.ts` (pins L328-342, L458-509, L550-560 + novo pin da constante). Rodar `pnpm vitest run tests/unit/agentSession.unit.spec.ts` verde antes de seguir.
2. **Launch (headless + textos de código) — quota ~30%:** `scripts/lib/worktree.mjs` import `:10` + `opencodeHeadlessArgs` `:333-345`; pins de `tests/unit/worktree.unit.spec.ts` (L426-439; descrição L296-303). Rodar `pnpm vitest run tests/unit/worktree.unit.spec.ts`.
3. **Superfícies/labels — quota ~20%:** textos listados acima (`worktree.mjs` docblock/help, `worktree.sh`, `worktree.md`, 2 skills, `AGENT-OPS.md`) + `docs/changelog/2026-09-18-ops127.md`.
4. **Gates — quota ~10%:** `pnpm gate:fast` (lint+typecheck+unit); verificação viva: criar sessão de teste (`pnpm worktree plan --go` em branch descartável ou `agent-session start --model opencode-go/deepseek-v4.1-flash`), ler `~/.local/state/teqo/agent-sessions/<slug>.json` e a sessão no servidor (`Session.model.variant === 'max'` / TUI mostrando `max`); `pnpm push` (origin GitHub) → PR `--base main`. **Atenção CI:** `scripts/worktree.mjs` é `HIGH_RISK_EXACT` — o PR roda unit/int full + e2e curado (esperado, não é surpresa).
## Rabbit holes / Não escopo (engenharia)
- **Não** emitir `--variant` na diretiva `launch` nem no `opencode attach` (classe OPS95; attach sem flags por design).
- **Não** criar mapa de capacidade modelo→variante nem lookup que lance (opção C rejeitada).
- **Não** gravar/derivar `variant` no estado da sessão nem na checagem de reuso (D2; gatilho de revisitação registrado).
- **Não** alterar `WORKTREE_MODEL_MAP`/`OPENCODE_PRESET_MODEL` nem o cardápio de flags (intenção corta).
- **Não** tocar `opencode.json` do repo nem a config global da máquina / Ctrl+T.
- **Não** mudar `opencodeCommands.unit.spec.ts` nem frontmatter de comandos de execução.
- **Não** editar docs históricos congelados (`docs/plans/*` antigos, `CHANGELOG-AGENTS.md`, HISTORY).
- **Não** adicionar flag nova de variante, nem parâmetro de CLI `--variant` no `agent-session start` (a variante é default interno).
## Riscos e mitigação
- **Sessão reusada de antes do OPS127 não tem `variant`.** `start` com driver vivo/sessão busy reusa sem re-aplicar o body (`agent-session.mjs:515-526`). Mitigação: worktrees de `plan`/`new` nascem com sessão nova; para runs longevos, `--new` força recriação; documentar o efeito no changelog. Revisitar se virar recorrente.
- **`--variant` em posição errada no argv do `run`.** Se cair depois do `report` posicional vira parte do report. Mitigação: D3 + pin de array exato (L426-439).
- **Servidor/provider rejeitar `max` em algum modelo do cardápio.** Guardrail pede "nunca quebra"; sem mapa (opção C rejeitada), o risco residual existe. Mitigação: verificação viva no PR para o preset e `--go` (os usados no fluxo) e flag de revisitação; o body é string constante, sem `throw` local.
- **Divergência do sintoma "nem sempre no provider correto".** O caminho de modelo da sessão é o da OPS122; a variante não o altera. Mitigação: o aceite de engenharia exige sessão viva conferindo provider/modelo **e** variante antes de marcar aprovado.
- **Deriva de texto vs. constante.** Vários docblocks citavam OPS95. Mitigação: pins unit + os pontos de texto são atualizados na mesma fase (3), e `MODEL_VARIANT` é importado (não re-digitado) em `lib/worktree.mjs`.
- **PR dispara suíte ampla por `worktree.mjs` ser HIGH_RISK_EXACT.** Esperado; não é regressão. Mitigação: rodar `gate:fast` local antes do push.
## Aceite de engenharia
- [ ] Aceite de produto da intenção coberto: `next`/`plan`/`new`/`fix` (inclui `fix --headless`) e o preset criam a sessão com `Session.model.variant === 'max'`; `--go` segue em `opencode-go/deepseek-v4.1-flash`.
- [ ] Guardrails: **nenhum** `--variant` em `attachArgs`/diretiva `launch`; `opencode.json` e config global intocados; variante é constante sem lookup que lance; cardápio/preset de modelos inalterados.
- [ ] Invariantes AGENTS/engineering-standards: single owner do literal (`lib/agent-session.mjs`), sem twinning, sem migration/schema/Consent/access.
- [ ] Pins unitários atualizados conforme lista acima (`agentSession.unit.spec.ts`, `worktree.unit.spec.ts`); `tests/unit/opencodeCommands.unit.spec.ts` intacto.
- [ ] Superfícies sincronizadas na mesma entrega (código, help/docblocks, shell, command, skills, AGENT-OPS, changelog novo).
- [ ] `pnpm test:unit` (specs citados) e `pnpm gate:fast` verdes; verificação viva de sessão em `max` no PR; push via `pnpm push`.
**Self-score decision-quality: 5/5.** (1) Decisão cara — dono do literal e formato do body/argv — tem opções e rejeitadas explícitas (A recomendada; B/C/D rejeitadas com motivo técnico). (2) Cabe no appetite: poucas linhas funcionais + textos + pins, sem schema/UI. (3) Rabbit holes nomeados (mapa de capacidade, attach, estado da sessão, config global, cardápio, docs congelados). (4) Depth check: reusa o caminho OPS122 `buildCreateSessionBody`/`driverArgs`/`opencodeHeadlessArgs` em vez de criar passe-through novo; literal em single owner. (5) Outcome preservado: a engenharia cobre todos os propósitos via body/argv e mantém o guardrail do attach, sem reescrever o aceite.

View file

@ -235,16 +235,27 @@ export const parseModelRef = (model) => {
return { providerID: model.slice(0, slash), id: model.slice(slash + 1) }
}
/**
* Variante única pedida em todo lançamento de worktree (OPS127). Opencode
* 1.18.31 aceita `variant` dentro de `model` no `POST /session` e persiste em
* `Session.model.variant`; a TUI anexada herda — por isso a variante viaja no
* body da sessão (e no `opencode run` do driver/headless), nunca como flag no
* `opencode attach`/diretiva do TUI (o yargs rejeita flag desconhecida —
* OPS95). `max` é a única variante usada, em todos os propósitos.
*/
export const MODEL_VARIANT = 'max'
/**
* Body of `POST /session` — `{}` when no model was requested, or
* `{ model: { providerID, id } }` when one was (the session then reports the
* model, so the attached TUI inherits it even on a driverless launch). No
* `variant` (OPS95: variants stay on the machine's global config).
* `{ model: { providerID, id, variant } }` when one was (the session then
* reports the model+variant, so the attached TUI inherits them even on a
* driverless launch). The variant rides the body (OPS127); the machine's
* global config stays untouched.
* @param {{ model?: string | null }} [options]
*/
export const buildCreateSessionBody = ({ model = null } = {}) => {
if (model === null || model === undefined || model === '') return {}
return { model: parseModelRef(model) }
return { model: { ...parseModelRef(model), variant: MODEL_VARIANT } }
}
/**
@ -363,6 +374,8 @@ export const validateSessionFlags = ({ subcommand = null, flags = {} } = {}) =>
* argument starting with `-` (`--issue 1019`) is not parsed as an opencode flag
* by yargs — verified live on 1.18.31. No `-p`: the credential rides
* `OPENCODE_SERVER_PASSWORD` in the inherited env (never in `ps`).
* `--variant` (OPS127) keeps the run on the same variant as the session body —
* `opencode run` supports it; the attached TUI does not.
* @param {{ url: string, sessionID: string, dir: string, model: string, invocation: { command: string, arguments?: string } | null }} options
*/
export const driverArgs = ({ url, sessionID, dir, model, invocation }) => {
@ -385,6 +398,8 @@ export const driverArgs = ({ url, sessionID, dir, model, invocation }) => {
dir,
'--model',
model,
'--variant',
MODEL_VARIANT,
'--auto',
'--command',
invocation.command,

View file

@ -7,7 +7,7 @@
*/
import { slugify } from '../../src/lib/slug.ts'
import { assertKnownFlags, SKILL_AUTO_FLAG } from './agent-session.mjs'
import { assertKnownFlags, MODEL_VARIANT, SKILL_AUTO_FLAG } from './agent-session.mjs'
/**
* Prefix of every `/plan-issue` planning-worktree branch (`pnpm worktree
@ -47,9 +47,10 @@ export const OPENCODE_PRESET_MODEL =
* `--zen` → Muse Spark 1.3 Free.
* The directive picks `WORKTREE_MODEL_MAP[flag]` when
* a single flag is present, otherwise falls back to `OPENCODE_PRESET_MODEL`.
* No `--variant` is emitted (the TUI yargs rejects the flag — OPS95):
* variants live on the machine's global config, selectable via Ctrl+T
* (`opencode run --variant max` remains the headless path).
* No `--variant` is emitted in the directive (the TUI yargs rejects the flag —
* OPS95). OPS127: the session is created in variant `max` — it rides the
* `POST /session` body and the `opencode run` argv of the driver/headless
* (`MODEL_VARIANT` in `./agent-session.mjs`), never the directive/attach.
*/
export const WORKTREE_MODEL_MAP = {
cheap: 'cheapestinference/deepseek-v4-flash',
@ -327,7 +328,10 @@ export const opencodeLaunchDirective = ({
export const OPENCODE_HEADLESS_COMMAND = 'bug-fix'
/**
* opencode `run` argv for the auto-unblock agent (model + report, no shell).
* opencode `run` argv for the auto-unblock agent (model + variant + report, no
* shell). `--variant` (OPS127) rides before the positional report — after it,
* it would be swallowed into the report argument. Unlike the attached TUI,
* `opencode run` accepts the flag.
* @param {{ model?: string, report?: string, command?: string }} [options]
*/
export const opencodeHeadlessArgs = ({
@ -341,7 +345,18 @@ export const opencodeHeadlessArgs = ({
if (typeof report !== 'string' || report.trim().length === 0) {
throw new Error('opencodeHeadlessArgs: report vazio')
}
return ['opencode', 'run', '--model', model, '--auto', '--command', command, report]
return [
'opencode',
'run',
'--model',
model,
'--variant',
MODEL_VARIANT,
'--auto',
'--command',
command,
report,
]
}
/**

View file

@ -43,9 +43,12 @@
* (`opencode attach -s <sessionID>`) — fechar o
* terminal NÃO encerra o run; encerrar é
* `pnpm agent:session stop`. OPS95 continua: sem
* `--variant`; variantes ficam na config global da
* máquina (Ctrl+T). Presets e mapa em
* scripts/lib/worktree.mjs; ciclo de vida em
* `--variant` na diretiva nem no attach (o yargs do
* TUI rejeita o flag). OPS127: a sessão nasce na
* variante `max` — ela viaja no body do
* `POST /session` e no argv do `opencode run` do
* driver/headless, nunca na diretiva. Presets e mapa
* em scripts/lib/worktree.mjs; ciclo de vida em
* scripts/lib/agent-session.mjs.
* OPS122: `--auto` (opt-out do GATE da skill)
* transposta para a invocation; flag desconhecida
@ -988,10 +991,10 @@ if (!subcommand) {
console.log(' opencode command, ou a função `worktree()` de .agents/shell/worktree.sh);')
console.log(' no terminal (TEQO_WORKTREE_TERMINAL=1) imprime também a diretiva')
console.log(
' `launch node scripts/agent-session.mjs start --purpose=next --dir=<dir> --model <preset|map> --issue=<N>` (OPS26+OPS33+OPS93+OPS95+OPS110:',
' `launch node scripts/agent-session.mjs start --purpose=next --dir=<dir> --model <preset|map> --issue=<N>` (OPS26+OPS33+OPS93+OPS95+OPS110+OPS127:',
)
console.log(
` abre a sessão com ${OPENCODE_PRESET_MODEL} (sem flag) ou com o mapa ${[
` abre a sessão (variante \`max\`, herdada pelo TUI anexado) com ${OPENCODE_PRESET_MODEL} (sem flag) ou com o mapa ${[
...WORKTREE_MODEL_FLAGS,
]
.map((flag) => `--${flag}=${WORKTREE_MODEL_MAP[flag]}`)

View file

@ -15,6 +15,7 @@ import {
formatSessionList,
isLoopbackHost,
isStaleLock,
MODEL_VARIANT,
nearestAcceptedFlag,
parseModelRef,
parseServerState,
@ -325,9 +326,9 @@ describe('parseModelRef + buildCreateSessionBody (OPS122 — o modelo chega à S
expect(() => parseModelRef(null as unknown as string)).toThrow()
})
it('builds the POST /session body with the model ref, or an empty body without one', () => {
it('builds the POST /session body with the model ref + variant, or an empty body without one', () => {
expect(buildCreateSessionBody({ model: 'opencode-go/deepseek-v4.1-flash' })).toEqual({
model: { providerID: 'opencode-go', id: 'deepseek-v4.1-flash' },
model: { providerID: 'opencode-go', id: 'deepseek-v4.1-flash', variant: MODEL_VARIANT },
})
expect(buildCreateSessionBody({ model: null })).toEqual({})
expect(buildCreateSessionBody({ model: undefined })).toEqual({})
@ -336,9 +337,8 @@ describe('parseModelRef + buildCreateSessionBody (OPS122 — o modelo chega à S
expect(() => buildCreateSessionBody({ model: 'no-slash' })).toThrow(/inválido|provider/)
})
it('never emits a variant (OPS95 — variants live on the machine config)', () => {
const body = buildCreateSessionBody({ model: 'opencode-go/deepseek-v4.1-flash' })
expect(body.model).not.toHaveProperty('variant')
it('pins max as the single launch variant (OPS127 — the TUI inherits, no attach flag)', () => {
expect(MODEL_VARIANT).toBe('max')
})
})
@ -475,6 +475,8 @@ describe('driverArgs + attachArgs (o argv verificado ao vivo)', () => {
'/work/OPS110',
'--model',
'deepseek/deepseek-flash',
'--variant',
'max',
'--auto',
'--command',
'work-issue',
@ -502,6 +504,8 @@ describe('driverArgs + attachArgs (o argv verificado ao vivo)', () => {
'/work/fix',
'--model',
'm',
'--variant',
'max',
'--auto',
'--command',
'bug-fix',
@ -553,11 +557,12 @@ describe('driverArgs + attachArgs (o argv verificado ao vivo)', () => {
expect(() => attachArgs({ url: 'http://x', sessionID: 'ses_a', dir: '' })).toThrow(/dir/)
})
it('attach resumes exactly the same session/dir (never passes --auto/--model)', () => {
it('attach resumes exactly the same session/dir (never passes --auto/--model/--variant)', () => {
const argv = attachArgs({ url: 'http://127.0.0.1:4199', sessionID: 'ses_abc', dir: '/work/x' })
expect(argv).toEqual(['attach', 'http://127.0.0.1:4199', '--dir', '/work/x', '-s', 'ses_abc'])
expect(argv).not.toContain('--auto')
expect(argv).not.toContain('--model')
expect(argv).not.toContain('--variant')
})
it('serverArgs is loopback-flagged and never enables --mdns', () => {

View file

@ -293,7 +293,7 @@ describe('opencodeLaunchDirective (terminal-only agent-session launch, OPS26 + O
)
})
it('never emits --variant — the TUI yargs rejects it (OPS95); variants live on machine config (Ctrl+T)', () => {
it('never emits --variant in the directive — the TUI yargs rejects it (OPS95); the variant rides the session body/driver (OPS127)', () => {
for (const model of Object.values(WORKTREE_MODEL_MAP)) {
for (const purpose of ['next', 'plan', 'new']) {
const directive = opencodeLaunchDirective({ dir, purpose, terminal: true, model })
@ -432,6 +432,8 @@ describe('opencodeHeadlessArgs + headlessDirective (OPS106 auto-unblock)', () =>
'run',
'--model',
'deepseek/deepseek-flash',
'--variant',
'max',
'--auto',
'--command',
OPENCODE_HEADLESS_COMMAND,