@diegosouzacdv/jev-browser-mcp 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,1818 +1,354 @@
1
- # Orquestrador Multi-Agente Corporativo
1
+ # Automação de tela com Jev e Playwright
2
2
 
3
- Orquestrador de desenvolvimento de software com LangGraph, LangChain, MCP,
4
- persistência SQLite, revisão multiagente, gates determinísticos, HITL e
5
- artefatos versionados por manifesto.
3
+ ## Instalar em outros harnesses com npm/npx
6
4
 
7
- O provedor LLM padrão do código é a Groq quando nenhuma variável é definida.
8
- O modelo padrão do perfil operacional deste projeto é
9
- `deepseek/deepseek-v4-flash-0731`, usado via OpenRouter.
10
- A chave nunca deve ser gravada no código, no `.env.example`, em checkpoints,
11
- logs ou relatórios.
5
+ O repositório também contém um pacote Node independente do harness. Ele fala
6
+ MCP por `stdio`, executa o Playwright no mesmo processo e pode ser iniciado por
7
+ qualquer harness que aceite `command` e `args` para um servidor MCP. O pacote
8
+ usa o mesmo `config/ui-testing.json` deste repositório; não precisa instalar o
9
+ Python do orquestrador. As opções específicas do pacote ficam isoladas no
10
+ bloco `jev_browser_mcp` para preservar o contrato do servidor Python.
12
11
 
13
- As configurações operacionais dos agentes ficam centralizadas em
14
- `config/agent-policy.json` (modelo, limite, timeout, retry, contrato, contexto,
15
- skills, MCP e ownership). Valide ou projete as variáveis de um workflow com:
16
-
17
- ```bash
18
- python scripts/validate_agent_policy.py validate
19
- python scripts/render_agent_policy.py show
20
- python scripts/render_agent_policy.py --github-env
21
- ```
22
-
23
- `ORQUESTRADOR_AGENT_RUNTIME_JSON` e demais variáveis legadas existem apenas
24
- para compatibilidade explícita; os workflows canônicos derivam a configuração
25
- do arquivo central.
26
-
27
- ## 1. Pré-requisitos
28
-
29
- - Python 3.12+;
30
- - Git;
31
- - Docker Desktop/Engine para o sandbox estrito e para o Gitea local;
32
- - Node.js/npm somente quando houver validação TypeScript;
33
- - uma chave Groq criada no [console de API da Groq](https://console.groq.com/keys)
34
- ou uma chave OpenRouter criada em [OpenRouter Keys](https://openrouter.ai/settings/keys);
35
- - OpenSSH no Windows para o acesso ao Gitea;
36
- - portas locais `3000` (HTTP) e `2222` (SSH) livres.
37
-
38
- Se uma chave foi publicada em chat, commit ou log, revogue-a e crie outra antes
39
- de continuar.
40
-
41
- ## 2. Instalação local
42
-
43
- Windows PowerShell:
44
-
45
- ```powershell
46
- py -3.12 -m venv .venv
47
- .venv\Scripts\Activate.ps1
48
- python -m pip install --upgrade pip
49
- pip install -r requirements.txt
50
- Copy-Item .env.example .env
51
- ```
52
-
53
- Linux/macOS:
54
-
55
- ```bash
56
- python3.12 -m venv .venv
57
- source .venv/bin/activate
58
- python -m pip install --upgrade pip
59
- pip install -r requirements.txt
60
- cp .env.example .env
61
- ```
62
-
63
- Edite `.env` localmente. O arquivo `.env.example` é o catálogo versionado das
64
- variáveis suportadas; o `.env` é ignorado pelo Git e contém os valores
65
- específicos da máquina. Para o perfil local deste projeto:
66
-
67
- ```dotenv
68
- ORQUESTRADOR_LLM_PROVIDER=openrouter
69
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
70
- ORQUESTRADOR_AGENT_MODEL=deepseek/deepseek-v4-flash-0731
71
- MCP_FILESYSTEM_ROOTS=.
72
- ORQUESTRADOR_SANDBOX_MODE=docker
73
- ```
74
-
75
- Quando a chave estiver cadastrada no ambiente de usuário do Windows, use a
76
- expansão `${OPENROUTER_API_KEY}` acima; não copie o segredo para o `.env`. Para
77
- CI e produção, use secrets/variáveis do ambiente; não publique o arquivo `.env`.
78
-
79
- ### Conferir a integridade da `.venv`
80
-
81
- A `.venv` é ignorada pelo Git: se algo reescrever um arquivo dela, nada o traz de
82
- volta. Em 2026-08-28 um script de tradução reescreveu 23 pacotes (`resolver` →
83
- `resolve`), e isso só apareceu semanas depois, como erros sem relação aparente.
84
- Cada pacote instalado guarda o sha256 de cada arquivo no próprio `RECORD`, e o
85
- script abaixo confere tudo contra ele:
86
-
87
- ```powershell
88
- .venv\Scripts\python.exe scripts\venv_integrity.py
89
- ```
90
-
91
- ```bash
92
- .venv/bin/python scripts/venv_integrity.py
93
- ```
94
-
95
- Sai `0` com `"clean": true`; sai `1` nomeando pacote e arquivo em `mismatched`
96
- (conteúdo diferente), `missing` (arquivo apagado) ou `unverifiable` (pacote sem
97
- `RECORD`, que não dá para conferir e por isso não conta como limpo). Rode com o
98
- interpretador da `.venv` que quer conferir. Leva de 1 a 11 s, conforme o cache de
99
- disco. Para reparar um pacote apontado:
100
- `pip install --force-reinstall --no-deps <pacote>==<versão>`.
101
-
102
- Na agenda do Hermes, uma vez por semana, pela skill
103
- `.hermes/skills/orquestrador-venv-integrity/`, que só reporta e nunca repara; o
104
- comando `hermes cron create` está no `SKILL.md` dela.
105
-
106
- ## 2.1. Gitea local para Actions e espelhamento do GitHub
107
-
108
- O GitHub é a origem canônica deste projeto. O Gitea é uma cópia local usada para
109
- executar os workflows self-hosted; portanto, o fluxo normal é publicar primeiro
110
- em `origin` (GitHub) e depois atualizar o remote `gitea`. Não trate o clone local
111
- do Gitea como fonte de verdade.
112
-
113
- | Componente | Caminho/URL nesta máquina |
114
- |---|---|
115
- | Checkout de desenvolvimento | `C:\orquestrador` |
116
- | Servidor Gitea, compose e dados persistentes | `C:\gitea` |
117
- | Runner Linux/Docker | `C:\gitea-runner` |
118
- | Runner Windows/host | `C:\gitea-runner-win` |
119
- | URL configurada no compose | `http://192.168.1.3:3000` |
120
- | SSH do Gitea | `ssh://gitea-local/...` na porta `2222` |
121
-
122
- O `docker-compose.yml` atual usa `192.168.1.3`. Se o endereço da máquina mudar, atualize
123
- `ROOT_URL`, `DOMAIN` e `SSH_DOMAIN` no compose antes de registrar novos runners.
124
- Não misture `localhost`, o IP da LAN e outro hostname no mesmo registro: o endereço usado
125
- pelo runner precisa ser alcançável por ele.
126
-
127
- Referências oficiais: [instalação com Docker](https://docs.gitea.com/next/installation/install-with-docker/),
128
- [Gitea Actions](https://docs.gitea.com/usage/actions/overview/) e
129
- [Gitea Runner](https://docs.gitea.com/usage/actions/act-runner/).
130
-
131
- ### Subir o servidor Gitea
132
-
133
- Inicie o Docker Desktop com o engine Linux e confirme que ele está saudável:
134
-
135
- ~~~powershell
136
- docker info
137
- docker compose version
138
- ~~~
139
-
140
- Suba a instância já configurada em `C:\gitea`; não recrie o volume de dados:
141
-
142
- ~~~powershell
143
- Set-Location C:\gitea
144
- docker compose pull
145
- docker compose up -d
146
- docker compose ps
147
- docker compose logs --tail 100 server
148
- Invoke-WebRequest http://192.168.1.3:3000/api/v1/version -TimeoutSec 10
149
- ~~~
150
-
151
- Na primeira instalação, acesse <http://192.168.1.3:3000>, crie o usuário
152
- administrador, ative Actions e confirme que o repositório
153
- `admin/projeto-global` existe. Os dados ficam em `C:\gitea\gitea`; não use
154
- `docker compose down -v`, pois isso remove o armazenamento persistente.
155
-
156
- Para parar temporariamente sem apagar dados:
157
-
158
- ~~~powershell
159
- Set-Location C:\gitea
160
- docker compose stop
161
- ~~~
162
-
163
- ### GitHub como origem e Gitea como espelho
164
-
165
- No checkout principal, preserve `origin` apontando para o GitHub e mantenha o
166
- Gitea em um segundo remote:
167
-
168
- ~~~powershell
169
- Set-Location C:\orquestrador
170
- git remote -v
171
- git remote set-url origin https://github.com/diegosouzacdv/orquestrador.git
172
- git remote set-url gitea ssh://gitea-local/admin/projeto-global.git
173
- git fetch origin --prune
174
- ~~~
175
-
176
- Se `gitea` ainda não existir, use `git remote add gitea ...` em vez de
177
- `set-url`. A chave SSH do Gitea pode ser criada assim:
178
-
179
- ~~~powershell
180
- $sshDir = Join-Path $env:USERPROFILE ".ssh"
181
- New-Item -ItemType Directory -Force $sshDir | Out-Null
182
- ssh-keygen -t ed25519 -C "gitea-local" -f (Join-Path $sshDir "gitea_local")
183
- Get-Content (Join-Path $sshDir "gitea_local.pub")
184
- ~~~
185
-
186
- Adicione a chave pública em **Gitea → Settings → SSH / GPG Keys** e crie
187
- `C:\Users\<usuario>\.ssh\config`:
188
-
189
- ~~~sshconfig
190
- Host gitea-local
191
- HostName 192.168.1.3
192
- Port 2222
193
- User git
194
- IdentityFile ~/.ssh/gitea_local
195
- IdentitiesOnly yes
196
- ~~~
197
-
198
- Valide a autenticação e publique o mesmo commit nos dois destinos:
199
-
200
- ~~~powershell
201
- ssh -T gitea-local
202
- git pull --ff-only origin master
203
- git push origin master
204
- git push gitea master:master
205
- git push gitea --tags
206
- git ls-remote origin refs/heads/master
207
- git ls-remote gitea refs/heads/master
208
- ~~~
209
-
210
- O `ssh -T` pode responder que não existe shell interativo; isso é esperado se a
211
- autenticação funcionar. O último bloco deve mostrar o mesmo SHA no GitHub e
212
- no Gitea. O clone atual em `C:\gitea-runner\repositorio` é apenas um bootstrap
213
- antigo e não deve ser usado como origem do espelho sem antes conferir seus
214
- remotes e seu estado:
215
-
216
- ~~~powershell
217
- git -C C:\gitea-runner\repositorio remote -v
218
- git -C C:\gitea-runner\repositorio status --short --branch
219
- ~~~
220
-
221
- Para espelhar também branches e tags removidas, use um clone bare dedicado.
222
- O comando `git push --mirror` substitui todas as refs do repositório de destino;
223
- confirme o destino antes de executá-lo e não aponte para um repositório com
224
- trabalho independente:
225
-
226
- ~~~powershell
227
- $mirror = "C:\gitea-runner\github-mirror.git"
228
- if (Test-Path $mirror) { throw "O mirror já existe; valide-o manualmente antes de reutilizar o caminho." }
229
- git clone --mirror https://github.com/diegosouzacdv/orquestrador.git $mirror
230
- Set-Location $mirror
231
- git remote add gitea ssh://gitea-local/admin/projeto-global.git
232
- git fetch origin --prune
233
- git push --mirror gitea
234
- ~~~
235
-
236
- ### Runner Linux/Docker — `C:\gitea-runner`
237
-
238
- Este é o runner usado pelos workflows com `runs-on: ubuntu-latest`, como
239
- `.gitea/workflows/agentic-graph-e2e.yml` e
240
- `.gitea/workflows/mcp-integration-smoke.yml`. Ele usa Docker Desktop e o
241
- socket `/var/run/docker.sock` dentro dos jobs. O socket dá ao job controle do
242
- Docker do host; aceite jobs somente do repositório confiável.
243
-
244
- O binário e a configuração já ficam em `C:\gitea-runner`. Se for necessário
245
- registrar novamente, gere o arquivo uma única vez, solicite um token novo na
246
- interface do Gitea e não salve o token em arquivo, histórico ou Git:
247
-
248
- ~~~powershell
249
- Set-Location C:\gitea-runner
250
- if (!(Test-Path .\config.yaml)) {
251
- .\act_runner.exe config generate | Out-File -Encoding utf8 .\config.yaml
252
- }
253
- $runnerToken = Read-Host "Token do runner Gitea"
254
- .\act_runner.exe register --no-interactive --instance "http://192.168.1.3:3000" --token $runnerToken --name "orquestrador-local" --labels "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04,ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
255
- Remove-Variable runnerToken
256
- ~~~
257
-
258
- Na configuração, preserve pelo menos estas opções para os workflows Docker:
259
-
260
- ~~~yaml
261
- container:
262
- options: --volume /var/run/docker.sock:/var/run/docker.sock --add-host=host.docker.internal:host-gateway
263
- valid_volumes:
264
- - '**'
265
- require_docker: true
266
- docker_timeout: 60s
267
- bind_workdir: true
268
- ~~~
269
-
270
- Teste em primeiro plano antes de depender do serviço do Windows:
271
-
272
- ~~~powershell
273
- docker info
274
- Set-Location C:\gitea-runner
275
- .\act_runner.exe daemon --config C:\gitea-runner\config.yaml
276
- ~~~
277
-
278
- Nesta máquina, o serviço `GiteaActRunner` já está configurado para executar
279
- `C:\gitea-runner\act_runner.exe daemon --config C:\gitea-runner\config.yaml`.
280
- Depois que o Docker Desktop estiver pronto, reinicie-o e confira o log:
281
-
282
- ~~~powershell
283
- Get-Service GiteaActRunner
284
- Restart-Service GiteaActRunner
285
- Get-Content C:\gitea-runner\service.err.log -Tail 80
286
- ~~~
287
-
288
- O serviço deve aparecer como `Running` e o runner deve ficar online em
289
- **Gitea → Actions → Runners**. Se `docker info` falhar ou o log mencionar
290
- `Docker Engine socket not found`, corrija o Docker Desktop primeiro; reiniciar
291
- o runner sozinho não resolve essa causa.
292
-
293
- Estado verificado em 2026-09-13: serviço `Running`, runner `orquestrador-local`
294
- (id 3) em `v3.3.1`, com as três labels `ubuntu-*` e `last_online` corrente. O
295
- log de sucesso tem exatamente três linhas — `Starting runner daemon`,
296
- `Docker is ready` e `declare successfully`. A ausência de `Docker is ready`
297
- neste runner é defeito; no runner Windows, não.
298
-
299
- Uma enxurrada de `failed to fetch task ... target machine actively refused it`
300
- **não** é defeito do runner: é o container do Gitea parado. O runner fica em
301
- retry e volta sozinho quando o servidor sobe — foi o que ocorreu entre 07:41 e
302
- 07:55 de 2026-09-13, com recuperação às 07:59 sem nenhuma intervenção no
303
- runner. Antes de mexer no runner, confira `docker compose ps` em `C:\gitea`.
304
-
305
- ### Runner Windows/host — `C:\gitea-runner-win`
306
-
307
- Este é um segundo runner, independente do runner Docker, registrado como
308
- `orquestrador-windows` (id 4) com a label `windows-latest:host`. Ele é usado
309
- pelos jobs Windows da matriz `os: [ubuntu-latest, windows-latest]` de
310
- `.gitea/workflows/antigravity-sdk-homologation.yml` — hoje o único workflow
311
- Gitea que não é só `ubuntu-latest`. Sem este runner, a perna `windows-latest`
312
- dessa matriz fica enfileirada para sempre em vez de falhar, que é o sintoma
313
- pelo qual essa ausência costuma ser percebida.
314
-
315
- **O serviço `GiteaActRunnerWindows` já existe nesta máquina** e está correto:
316
- NSSM, `AUTO_START`, `Application=C:\gitea-runner-win\act_runner.exe`,
317
- `AppDirectory=C:\gitea-runner-win`,
318
- `AppParameters=daemon --config C:\gitea-runner-win\config.yaml`. Confira antes
319
- de tentar criá-lo de novo — `nssm install` sobre um serviço existente não é o
320
- caminho:
321
-
322
- ~~~powershell
323
- sc.exe query GiteaActRunnerWindows
324
- reg query "HKLM\SYSTEM\CurrentControlSet\Services\GiteaActRunnerWindows\Parameters"
325
- ~~~
326
-
327
- #### As duas causas que mantiveram este runner parado
328
-
329
- Diagnosticadas em 2026-09-13, com o serviço existente e bem configurado:
330
-
331
- 1. **O binário não estava lá.** `C:\gitea-runner-win\` tinha `.runner` e
332
- `config.yaml`, mas não `act_runner.exe`. O serviço sobe, não acha o
333
- executável e morre com `ESTADO: STOPPED`, `WIN32_EXIT_CODE: 1066 (0x42a)` e
334
- `SERVICE_EXIT_CODE: 3` — `ERROR_PATH_NOT_FOUND`. O 1066 é genérico
335
- ("erro específico do serviço") e não aponta o arquivo; quem lê só o 1066
336
- procura defeito de registro e não encontra nada.
337
- 2. **`require_docker: true` herdado do runner Docker.** O `config.yaml` deste
338
- diretório é cópia do config do runner Linux, inclusive do bloco
339
- `container:`. A opção está documentada no próprio arquivo como *"Always
340
- require a reachable docker daemon, **even if not required by runner**"* —
341
- ou seja, ela amarra a subida deste runner ao Docker Desktop, que um runner
342
- `host` não usa. Com o Docker parado, o runner Windows recusa subir sem ter
343
- motivo próprio para isso.
344
-
345
- Correção das duas, na ordem:
346
-
347
- ~~~powershell
348
- Set-Location C:\gitea-runner-win
349
- if (!(Test-Path .\act_runner.exe)) {
350
- Copy-Item C:\gitea-runner\act_runner.exe .\act_runner.exe
351
- }
352
- .\act_runner.exe --version # precisa casar com o runner Docker: v3.3.1
353
- ~~~
354
-
355
- No `config.yaml` deste runner, e **somente** neste, deixe:
356
-
357
- ~~~yaml
358
- container:
359
- require_docker: false
360
- ~~~
361
-
362
- O resto do bloco `container:` (socket Docker, `valid_volumes`, `bind_workdir`)
363
- é inerte aqui, porque um runner `host` não cria container — mas mantê-lo copiado
364
- é o que faz `require_docker` voltar despercebido na próxima cópia do arquivo.
365
-
366
- Valide em foreground antes de depender do serviço:
367
-
368
- ~~~powershell
369
- Set-Location C:\gitea-runner-win
370
- .\act_runner.exe daemon --config C:\gitea-runner-win\config.yaml
371
- ~~~
372
-
373
- O sucesso são **duas** linhas, sem `Docker is ready`:
374
-
375
- ~~~text
376
- level=info msg="Starting runner daemon"
377
- level=info msg="runner: orquestrador-windows, with version: v3.3.1, with labels: [windows-latest], declare successfully"
378
- ~~~
379
-
380
- `Docker is ready` aqui significa que `require_docker` voltou a `true`. A label
381
- sai como `windows-latest` porque o `:host` é o modo de execução do lado do
382
- runner, não parte do nome da label — `runs-on: windows-latest` no workflow é o
383
- que casa.
384
-
385
- O `config.yaml` desse runner aponta
386
- `ORQUESTRADOR_WINDOWS_PYTHON` para
387
- `C:\orquestrador\.venv\Scripts\python.exe`. Confira que o ambiente virtual
388
- existe e tem as dependências do projeto antes de iniciar o serviço.
389
-
390
- Encerre o foreground com `Ctrl+C` antes de subir o serviço: os dois usam o
391
- mesmo `.runner.lock` e a mesma identidade registrada.
392
-
393
- #### Subir o serviço — exige PowerShell elevado
394
-
395
- `sc.exe start` sem elevação falha com `OpenService FALHA 5: Acesso negado`, e
396
- esse 5 é do controle do serviço, não do runner. Em PowerShell **como
397
- administrador**:
398
-
399
- ~~~powershell
400
- Start-Service GiteaActRunnerWindows
401
- Get-Service GiteaActRunner, GiteaActRunnerWindows
402
- ~~~
403
-
404
- Executado pelo operador em 2026-09-13: o serviço ficou `RUNNING` e os **dois**
405
- runners passaram a reportar `last_online` no mesmo minuto — id 3
406
- (`orquestrador-local`) e id 4 (`orquestrador-windows`), com 12 segundos de
407
- diferença. É essa a evidência que fecha a subida dos dois runners; serviço
408
- `RUNNING` sozinho não prova conexão com o Gitea.
409
-
410
- Só crie o serviço com NSSM se `sc.exe query` disser que ele não existe. Ele
411
- precisa de diretório de trabalho próprio e não pode reutilizar o registro do
412
- runner Docker:
413
-
414
- ~~~powershell
415
- $nssm = (Get-Command nssm.exe).Source
416
- & $nssm install GiteaActRunnerWindows C:\gitea-runner-win\act_runner.exe
417
- & $nssm set GiteaActRunnerWindows AppDirectory C:\gitea-runner-win
418
- & $nssm set GiteaActRunnerWindows AppParameters 'daemon --config C:\gitea-runner-win\config.yaml'
419
- & $nssm set GiteaActRunnerWindows Start SERVICE_AUTO_START
420
- Start-Service GiteaActRunnerWindows
421
- ~~~
422
-
423
- O modo `host` expõe o ambiente Windows, ferramentas e credenciais da máquina
424
- ao job. Use-o apenas para workflows confiáveis.
425
-
426
- ### Secrets e workflows
427
-
428
- Secrets do GitHub não são copiados automaticamente para o Gitea. Cadastre no
429
- repositório Gitea, em **Settings → Actions → Secrets**, os valores exigidos
430
- pelos workflows, como `OPENROUTER_API_KEY`, `CONTEXT7_API_KEY`,
431
- `TWENTY_FIRST_API_KEY` e `GEMINI_API_KEY` quando aplicável. Tokens de clone e
432
- tokens de registro de runner devem ser tratados separadamente e nunca entrar
433
- no repositório.
434
-
435
- Os arquivos em `.gitea/workflows/` são executados pelo Gitea. Os arquivos em
436
- `.github/workflows/` continuam sendo executados pelo GitHub Actions; o fato de
437
- o conteúdo ser espelhado não faz o Gitea executar workflows do GitHub. Dentro
438
- de um job Docker, use `http://host.docker.internal:3000` para acessar o Gitea;
439
- `localhost` aponta para o próprio container.
440
-
441
- ### Diagnóstico e manutenção
442
-
443
- ~~~powershell
444
- Set-Location C:\gitea
445
- docker compose ps
446
- docker compose logs --tail 100 server
447
- Get-Service GiteaActRunner
448
- Get-Content C:\gitea-runner\service.err.log -Tail 80
449
- Get-Service GiteaActRunnerWindows -ErrorAction SilentlyContinue
450
- Get-Content C:\gitea-runner-win\service.err.log -Tail 80 -ErrorAction SilentlyContinue
451
- ~~~
452
-
453
- Serviço `Running` prova que o processo existe, não que o runner está falando
454
- com o Gitea. Quem responde isso é o `last_online` do servidor, e ele é
455
- consultável sem abrir a interface e sem autenticar:
456
-
457
- ~~~powershell
458
- docker exec gitea sqlite3 /data/gitea/gitea.db "select id, name, version, agent_labels, datetime(last_online,'unixepoch','localtime') from action_runner where deleted is null;"
459
- ~~~
460
-
461
- **O `where deleted is null` não é opcional.** O Gitea apaga runner por *soft
462
- delete*: a linha permanece em `action_runner` com a coluna `deleted`
463
- preenchida, e a interface e a API deixam de listá-la. Uma consulta sem esse
464
- filtro mostra registros já removidos como se estivessem ativos — foi o que
465
- aconteceu em 2026-09-13, quando os ids 1 e 2 foram lidos como "órfãos
466
- aparecendo offline na interface" e na verdade já tinham sido excluídos em
467
- 2026-08-30, minutos depois de criados e antes do id 3 existir.
468
-
469
- Um `last_online` recente é o runner online agora. Os registros vivos nesta
470
- máquina são o id 3 (`orquestrador-local`, labels `ubuntu-*`) e o id 4
471
- (`orquestrador-windows`, label `windows-latest`). Para conferir o que a
472
- interface de fato mostra, sem depender da leitura do banco:
473
-
474
- ~~~powershell
475
- docker exec gitea sqlite3 /data/gitea/gitea.db "select id, datetime(deleted,'unixepoch','localtime') from action_runner;"
476
- ~~~
477
-
478
- Nunca apague linha de `action_runner` por SQL: remover runner é operação de
479
- interface, e o soft delete existe para preservar a trilha dos jobs que aquele
480
- runner atendeu.
481
-
482
- Se um runner for removido pela interface do Gitea, pare o daemon e remova
483
- somente `.runner` e `.runner.lock` no diretório daquele runner; preserve
484
- `config.yaml`, registre um novo token e valide novamente. Nunca remova
485
- `C:\gitea\gitea` para corrigir um problema de runner.
486
-
487
- No diagnóstico desta máquina, em 2026-09-11, o endpoint do Gitea recusou
488
- conexão porque o container estava parado e `C:\gitea-runner\service.err.log`
489
- registrou `Docker Engine socket not found`. A sequência de recuperação é:
490
-
491
- ~~~powershell
492
- Set-Location C:\gitea
493
- docker compose up -d
494
- docker info
495
- Restart-Service GiteaActRunner
496
- Get-Content C:\gitea-runner\service.err.log -Tail 80
497
- ~~~
498
-
499
- Em 2026-09-13 o Gitea (`1.27.3`) e o runner Docker estavam saudáveis, e só o
500
- runner Windows estava parado: binário ausente e `require_docker: true` herdado.
501
- A lição de diagnóstico é que os dois runners falham por causas **disjuntas** —
502
- o Docker depende do Docker Desktop e do container do Gitea; o Windows/host não
503
- depende de nenhum dos dois. Tratar "os runners não sobem" como um problema só
504
- faz procurar causa comum onde não há.
505
-
506
- ## 2.2. Variáveis de ambiente
507
-
508
- O arquivo [`.env.example`](.env.example) é o catálogo versionado das variáveis
509
- suportadas. O `.env` local é ignorado pelo Git e não deve ser publicado.
510
- Para as credenciais cadastradas no ambiente de usuário do Windows, use referências
511
- sem copiar os valores:
512
-
513
- ```dotenv
514
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
515
- CONTEXT7_API_KEY=${CONTEXT7_API_KEY}
516
- TWENTY_FIRST_API_KEY=${TWENTY_FIRST_API_KEY}
517
- ```
518
-
519
- Abra um novo PowerShell depois de alterar variáveis no Windows. Confira somente
520
- a presença das credenciais:
521
-
522
- ```powershell
523
- python -c "import os; from dotenv import load_dotenv; load_dotenv('.env', override=False); names=['OPENROUTER_API_KEY','CONTEXT7_API_KEY','TWENTY_FIRST_API_KEY']; print({n:bool(os.environ.get(n)) for n in names})"
524
- git check-ignore -v .env
525
- python scripts/validate_agent_policy.py validate
526
- python scripts/plan_status.py validate
527
- ```
528
-
529
- As variáveis estão organizadas no `.env.example` por provider LLM, OpenRouter,
530
- MCP, Gitea/Git, sandbox, retry, kernel, orçamento, observabilidade, skills,
531
- HITL, identidade e mídia. O modelo padrão operacional é
532
- `deepseek/deepseek-v4-flash-0731`; o Backend usa `reasoning_effort=max` na
533
- política central `config/agent-policy.json`. Não habilite overrides legados.
534
-
535
- ## 2.3. Supermemory local (memória entre sessões)
536
-
537
- Desde 2026-09-23, por decisão do operador, a memória entre sessões mora no
538
- `supermemory-server` rodando no **WSL Ubuntu** da máquina do operador. O plano da
539
- nuvem chegou ao limite e passou a recusar gravações, e o conector da nuvem foi
540
- **desativado** no mesmo dia (claude.ai → Settings → Connectors → Supermemory). As
541
- regras de uso estão no `AGENTS.md`, subseção "Supermemory: o servidor local do
542
- operador".
543
-
544
- Tudo abaixo roda **dentro do Ubuntu** (`wsl -d Ubuntu`), a partir de `~`, nunca
545
- de `/mnt/c/...`: a pasta de dados segue o diretório atual quando não é fixada, e
546
- `/mnt/c` é o disco do Windows, mais lento.
547
-
548
- ### Instalar (uma vez)
549
-
550
- ```bash
551
- sudo apt update && sudo apt install -y curl
552
- cd ~ && curl -fsSL https://supermemory.ai/install | bash
553
- echo 'export PATH="$HOME/.supermemory/bin:$PATH"' >> ~/.bashrc
554
- ```
555
-
556
- ### Configuração fixa (uma vez)
557
-
558
- Embedding multilíngue e local, com a pasta de dados fixada. Nada disso é
559
- segredo. O `embedding-plan.json` que o assistente grava **não** é o que o
560
- servidor carrega; sem estas variáveis ele sobe com o modelo inglês de 768
561
- dimensões, que não combina com as tabelas de 1024 (medido em 2026-09-23).
562
-
563
- ```bash
564
- cat >> ~/.bashrc <<'EOF'
565
- export SUPERMEMORY_EMBEDDING_PROVIDER=local
566
- export SUPERMEMORY_EMBEDDING_MODEL=Xenova/bge-m3
567
- export SUPERMEMORY_EMBEDDING_DIMENSIONS=1024
568
- export SUPERMEMORY_DATA_DIR="$HOME/.supermemory"
569
- EOF
570
- source ~/.bashrc
571
- ```
572
-
573
- Chave do LLM que extrai as memórias (GLM, pela API compatível com OpenAI da
574
- Z.ai). A chave vai para um arquivo **fora** de `~/.supermemory`, porque o
575
- servidor lê o `~/.supermemory/env`, cifra o conteúdo em `env.enc` e apaga o
576
- original; e a extração roda num processo que só enxerga variáveis exportadas.
577
-
578
- ```bash
579
- mkdir -p ~/.config/glm && chmod 700 ~/.config/glm
580
- read -rsp "Chave GLM: " K; echo; printf '%s' "$K" > ~/.config/glm/key; unset K
581
- chmod 600 ~/.config/glm/key
582
-
583
- cat >> ~/.bashrc <<'EOF'
584
- sm() { ( export OPENAI_API_KEY="$(cat "$HOME/.config/glm/key")" \
585
- OPENAI_BASE_URL=https://api.z.ai/api/coding/paas/v4 \
586
- OPENAI_MODEL=glm-4.6 OPENAI_FAST_MODEL=glm-4.5-air
587
- exec supermemory-server "$@" ); }
588
- EOF
589
- source ~/.bashrc
590
- ```
591
-
592
- `OPENAI_BASE_URL` depende do plano da Z.ai: com a assinatura de código use a URL
593
- `coding` acima; com saldo pré-pago, `https://api.z.ai/api/paas/v4`. A URL errada
594
- responde `Insufficient balance or no resource package`.
595
-
596
- ### Subir e conferir
597
-
598
- ```bash
599
- cd ~ && sm # sobe em http://localhost:6767 e fica em primeiro plano
600
- supermemory-server doctor # noutro terminal: provedor, embedding, pasta de dados
601
- ```
602
-
603
- O quadro `supermemory ready` precisa mostrar `database /home/<você>/.supermemory` e
604
- `embeddings local · Xenova/bge-m3 · 1024d`. **Não compartilhe prints desse quadro:
605
- a linha `api key` traz a chave inteira.**
606
-
607
- ### Testar
608
-
609
- ```bash
610
- # grava um documento; no terminal do servidor a linha final deve terminar em "N memories"
611
- curl -s http://localhost:6767/v3/documents -H "Content-Type: application/json" \
612
- -d '{"content":"Teste do Supermemory local.","containerTag":"orquestrador"}'; echo
613
-
614
- # busca: containerTags é LISTA. Sem ela, ou com containerTag no singular, a
615
- # resposta é 200 com resultado vazio -- parece "nada gravado" e não é.
616
- curl -s http://localhost:6767/v4/search -H "Content-Type: application/json" \
617
- -d '{"q":"supermemory local","containerTags":["orquestrador"],"searchMode":"memories","threshold":0}'; echo
618
- ```
619
-
620
- Mande sempre `threshold`. Sem ele o servidor aplica um limiar próprio e descarta
621
- memória relevante: medido em 2026-09-23, "Hermes cron workdir" devolveu 1 memória
622
- sem `threshold` e as do mesmo assunto com `threshold: 0`. Com 0, a busca devolve
623
- as mais próximas até o `limit`, cada uma com a `similarity` ao lado. O cliente do
624
- orquestrador manda o valor de `config/durable-stores.json`,
625
- `memory_store.search_threshold`.
626
-
627
- Teste de ponta a ponta de **todas** as chamadas que o orquestrador faz (gravar,
628
- buscar, editar, grafo, ler, esquecer, apagar), num espaço descartável
629
- `orquestrador-smoke`, que ele limpa no fim. Cada linha diz `OK` ou `FAIL` com o
630
- motivo que o servidor deu:
631
-
632
- ```bash
633
- PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_local_smoke.py
634
- ```
635
-
636
- No **Windows**, que é onde a `.venv` do projeto mora, rode pelo PowerShell a
637
- partir do checkout — o Windows alcança o servidor do WSL por `127.0.0.1:6767`.
638
- Os scripts abaixo (migração e sync) seguem a mesma forma:
639
-
640
- ```powershell
641
- cd C:\orquestrador # ou a worktree da branch que tem os scripts
642
- $env:PYTHONPATH = (Get-Location).Path
643
- $env:PYTHONUTF8 = "1" # o console cp1252 quebra no ✅ das memórias
644
- $py = "C:\orquestrador\.venv\Scripts\python.exe"
645
- & $py scripts\supermemory_local_smoke.py
646
- ```
647
-
648
- A busca do smoke tenta de novo por até 30 s antes de reprovar: o servidor indexa
649
- a memória nova na própria fila, e uma espera fixa confundia "ainda não indexou"
650
- com "a busca não funciona".
651
-
652
- ### Listar as memórias
653
-
654
- ```bash
655
- # documentos do espaço, paginados
656
- curl -s http://localhost:6767/v3/documents/list -H "Content-Type: application/json" \
657
- -d '{"containerTags":["orquestrador"],"page":1,"limit":50}'; echo
658
-
659
- # um documento com as memórias extraídas dele
660
- curl -s http://localhost:6767/v3/documents/<documentId>; echo
661
- ```
662
-
663
- Pelo orquestrador, o grafo inteiro (todos os documentos com as memórias de cada
664
- um) sai de `GET /memory/graph` da API HTTP (seção 7), autenticado como as outras
665
- rotas.
666
-
667
- ### Migrar as memórias da nuvem (uma vez)
668
-
669
- Com o servidor de pé, a partir do checkout do orquestrador:
670
-
671
- ```bash
672
- PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_migrate_to_local.py --dry-run
673
- PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_migrate_to_local.py
674
- ```
675
-
676
- São as 33 memórias de `docs/supermemory/cloud-export-2026-09-23.json`, já
677
- normalizadas para o padrão de gravação, escritas com o texto exato, sem reprocessar
678
- no LLM. O script confere o export inteiro contra o padrão **antes** da primeira
679
- gravação — `--dry-run` lista em `invalid` o que seria recusado — e guarda em
680
- `data/supermemory-migration.json` o que já foi aceito: rodar de novo não duplica.
681
-
682
- ### Padrão de gravação (aplicado pelo código)
683
-
684
- Toda gravação — MCP, tela, migração, inbox — passa por `src/memory/standard.py`,
685
- que recusa e nomeia cada violação. O formato que passa:
686
-
687
- ```text
688
- TEC-002 was delivered on 2026-09-22 in commit 93bbf57.
689
- STK-004 was marked ✅, meaning developed and validated within the scope of the cited evidence, not approved by the operator, on 2026-09-22 in commit d2ab40d.
690
- ```
691
-
692
- Uma frase, em inglês, com data ISO e âncora (SHA, id de item ou `session <id>`),
693
- ícone com o sentido escrito, sem estado volátil ("still pending", "in progress")
694
- e sem segredo. Os valores estão em `config/durable-stores.json`,
695
- `memory_store.standard`.
696
-
697
- ### Sincronizar com as sessões da nuvem
698
-
699
- Sessões na nuvem não alcançam este servidor. Elas leem
700
- `docs/supermemory/snapshot.json` e gravam em `docs/supermemory/inbox/`, que chegam
701
- aqui pelo git. Depois de trazer as branches mescladas para o checkout:
702
-
703
- ```bash
704
- PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_sync.py
705
- git add docs/supermemory/snapshot.json
706
- git commit -m "memory: snapshot do servidor local" -- docs/supermemory/snapshot.json
707
- ```
708
-
709
- O script aplica cada operação do inbox uma vez (o registro fica em
710
- `data/supermemory-sync.json`) e reescreve o snapshot a partir do servidor. Rode-o
711
- depois da migração, para o snapshot passar a vir do servidor, e sempre que um
712
- inbox novo chegar. O Hermes pode rodá-lo na mesma agenda do laço do plano.
713
-
714
- As memórias seguidas de uma mesma sessão vão numa chamada só, e o servidor faz um
715
- documento por chamada: no grafo, cada sessão aparece como um documento com os
716
- fatos em volta. Até 2026-09-23 cada memória ia sozinha, e o grafo mostrava um par
717
- documento-memória para cada uma.
718
-
719
- ### Reagrupar as memórias gravadas uma por documento (uma vez)
720
-
721
- As 42 memórias gravadas antes dessa mudança continuam uma por documento. O
722
- reagrupamento junta cada lote de gravação da nuvem e cada sessão do inbox num
723
- documento só. Ele só apaga um documento antigo **depois** de conferir que cada
724
- memória dele está no documento novo, e no fim confere que o conjunto de memórias
725
- é o mesmo de antes, sem perda e sem duplicata. Memória que o repositório não
726
- conhece (gravada pela tela, por exemplo) fica onde está.
727
-
728
- ```powershell
729
- git fetch origin claude/nifty-knuth-27gns3
730
- git checkout origin/claude/nifty-knuth-27gns3
731
- & $py scripts\supermemory_sync.py # aplica o que estiver no inbox
732
- & $py scripts\supermemory_regroup.py # só o plano, não toca em nada
733
- & $py scripts\supermemory_regroup.py --apply # "verified": true no fim
734
- & $py scripts\supermemory_sync.py # snapshot a partir do servidor
735
- git add docs/supermemory/snapshot.json
736
- git commit -m "memory: snapshot reagrupado" -- docs/supermemory/snapshot.json
737
- ```
738
-
739
- `--apply` sai `0` só com `"verified": true`. Se sair `1`, nada foi apagado antes
740
- do problema: o relatório nomeia o grupo que o servidor não confirmou. Rodar de
741
- novo é seguro.
742
-
743
- ### Sessões e Hermes: o MCP `supermemory-local`
744
-
745
- O `.mcp.json` declara `supermemory-local`, um MCP `stdio` que chama o servidor
746
- local. Ferramentas: `search_memory`, `add_memory` (`action` `save`/`forget`),
747
- `list_memories`, `get_document`, `update_memory`, `delete_document`. O
748
- interpretador vem de `ORQUESTRADOR_PYTHON`:
749
-
750
- ```powershell
751
- # Windows (checkout em C:\orquestrador)
752
- [System.Environment]::SetEnvironmentVariable("ORQUESTRADOR_PYTHON", "C:\orquestrador\.venv\Scripts\python.exe", "User")
753
- ```
754
-
755
- ```bash
756
- # Linux/WSL
757
- echo "export ORQUESTRADOR_PYTHON=$PWD/.venv/bin/python" >> ~/.bashrc
758
- ```
759
-
760
- Depois, em uma sessão nova, `/mcp` deve listar `supermemory-local` conectado.
761
- Se o interpretador escolhido não tiver o pacote `mcp`, o servidor se entrega ao
762
- `python` da `.venv` do projeto. Sessões na nuvem (claude.ai/code) **não** alcançam
763
- o `localhost` do operador: o mesmo MCP responde a elas pelo snapshot e grava no
764
- inbox, com o erro do servidor ao lado para ninguém confundir espelho com servidor.
765
- Para o MCP subir num container da nuvem, o ambiente precisa da `.venv` com
766
- `requirements.txt` instalado — o script de setup do ambiente é o lugar.
767
-
768
- Hermes, em `~/.hermes/config.yaml`. O nome `supermemory` mantém as ferramentas
769
- `mcp_supermemory_search_memory` e `mcp_supermemory_add_memory` que a skill
770
- `orquestrador-plan-loop` já chama:
771
-
772
- ```yaml
773
- mcp_servers:
774
- supermemory:
775
- command: /caminho/do/orquestrador/.venv/bin/python
776
- args: ["/caminho/do/orquestrador/mcp_servers/supermemory_local_server.py"]
777
- ```
778
-
779
- ### Espaço de desenvolvimento e despacho contínuo pelo Hermes
780
-
781
- Desde 2026-09-24 a memória tem dois espaços. O geral (`orquestrador`) guarda
782
- decisões e achados; o de desenvolvimento (`orquestrador-dev`) guarda só os
783
- registros `[dispatch]`, `[result]`, `[blocked]` e `[compact]` do despacho. O mesmo
784
- servidor MCP atende os dois: `supermemory-dev` é ele com
785
- `SUPERMEMORY_LOCAL_SPACE=dev`, já declarado no `.mcp.json`. O espaço dev não tem
786
- espelho versionado; sem o servidor, ele responde com o erro e nada mais.
787
-
788
- O Hermes despacha cada task do plano para a sessão Claude Code persistente do
789
- papel dela (`backend`, `frontend`, `docs`, `dba`), um turno por task com
790
- `claude -p --resume`, até o plano acabar. Política em `config/dev-dispatch.json`;
791
- instalação, pré-requisitos (login do Claude Code, uma worktree e uma sessão por
792
- papel, o campo `role` nos itens) e operação em
793
- [`.hermes/skills/orquestrador-dev-dispatcher/SKILL.md`](.hermes/skills/orquestrador-dev-dispatcher/SKILL.md).
794
- Para ver o que ele faria sem lançar nada:
795
-
796
- ```powershell
797
- $env:HERMES_HOME = "$env:LOCALAPPDATA\hermes"
798
- C:\orquestrador\.venv\Scripts\python.exe .hermes\skills\orquestrador-dev-dispatcher\scripts\dispatch_tick.py --repo C:\orquestrador --dry-run
799
- ```
800
-
801
- ### Segurança da porta 6767
802
-
803
- O servidor escuta em `0.0.0.0:6767` (`ss -ltn | grep 6767`), e não há opção
804
- documentada para prendê-lo ao `localhost`. No WSL2 em modo NAT, o padrão, só o
805
- próprio Windows o alcança. Se o `.wslconfig` usa `networkingMode=mirrored`, ou se
806
- houver `portproxy`, a rede local também alcança. Bloqueie a entrada vinda da rede
807
- no firewall do Windows (PowerShell como administrador). O tráfego de loopback
808
- não passa por essa regra:
809
-
810
- ```powershell
811
- New-NetFirewallRule -DisplayName "Supermemory local - bloqueia rede" -Direction Inbound `
812
- -Protocol TCP -LocalPort 6767 -RemoteAddress LocalSubnet,Internet -Action Block
813
- ```
814
-
815
- ## 3. Modelos Groq
816
-
817
- Os perfis podem ser alterados sem modificar código:
818
-
819
- ```dotenv
820
- ORQUESTRADOR_MODEL_STRUCTURED=openai/gpt-oss-20b
821
- ORQUESTRADOR_MODEL_CODING=openai/gpt-oss-120b
822
- ORQUESTRADOR_MODEL_REASONING=openai/gpt-oss-120b
823
- ORQUESTRADOR_MODEL_RESEARCH=groq/compound
824
- ORQUESTRADOR_MODEL_RESEARCH_FAST=groq/compound-mini
825
- ORQUESTRADOR_MODEL_INPUT_GUARD=meta-llama/llama-prompt-guard-2-86m
826
- ORQUESTRADOR_MODEL_OUTPUT_GUARD=openai/gpt-oss-safeguard-20b
827
- ```
828
-
829
- Mapa recomendado, conforme a documentação oficial da Groq:
830
-
831
- | Perfil/modelo | Janela | Máx. saída | Uso |
832
- |---|---:|---:|---|
833
- | `openai/gpt-oss-20b` | 131.072 | 65.536 | router, classificações e JSON estruturado rápido |
834
- | `openai/gpt-oss-120b` | 131.072 | 65.536 | código, arquitetura, review, compliance e DBA |
835
- | `groq/compound` | 131.072 | 8.192 | pesquisa/agentes com ferramentas Groq |
836
- | `groq/compound-mini` | 131.072 | 8.192 | pesquisa rápida e sumarização |
837
- | `meta-llama/llama-prompt-guard-2-86m` | 512 | 512 | classificação de prompt de entrada |
838
- | `openai/gpt-oss-safeguard-20b` | 131.072 | 65.536 | inspeção de saída/safety |
839
- | `qwen/qwen3.6-27b` | 131.072 | 16.384 | alternativa experimental; modelo preview e mais caro |
840
-
841
- O perfil estruturado usa `json_schema` nos GPT-OSS; o perfil de
842
- código/raciocínio usa o GPT-OSS 120B. Compound fica reservado para pesquisa
843
- com ferramentas. Prompt Guard e Safeguard permanecem nos portões de segurança
844
- e foram validados pelo smoke real do workflow Groq.
845
-
846
- Os números da tabela são capacidades máximas dos modelos, não a quota da
847
- conta. No tier on-demand com 8.000 TPM, configure limites conservadores para
848
- que entrada mais saída solicitada caibam na quota:
849
-
850
- ```dotenv
851
- ORQUESTRADOR_LLM_MAX_INPUT_CHARS=12000
852
- ORQUESTRADOR_GROQ_MAX_COMPLETION_TOKENS=2048
853
- ```
854
-
855
- Ao migrar para um tier superior, esses dois valores podem ser aumentados sem
856
- alterar o código.
857
-
858
- Para OpenRouter, os limites não ficam presos aos valores conservadores da
859
- Groq. O catálogo operacional por modelo está centralizado em
860
- [`src/llm/llm_model_config.py`](src/llm/llm_model_config.py). A troca global do modelo
861
- é feita em um único ponto:
862
-
863
- | Modelo agentic | Contexto | Máx. saída | Input / 1M | Output / 1M |
864
- |---|---:|---:|---:|---:|
865
- | `deepseek/deepseek-v4-flash-0731` | 1.310.720 | 943.718 | US$ 0,04 | US$ 0,08 |
866
- | `minimax/minimax-m3` (fallback produção) | 1.048.576 | 512.000 | US$ 0,30 | US$ 1,20 |
867
- | `moonshotai/kimi-k2.7-code` (fallback produção) | 262.144 | 235.929 | US$ 0,67 | US$ 3,40 |
868
-
869
- Os valores são snapshot do catálogo e não substituem o preço efetivo da
870
- resposta. O adapter registra usage/custo retornados pelo OpenRouter.
871
-
872
- ```dotenv
873
- ORQUESTRADOR_LLM_PROVIDER=openrouter
874
- ORQUESTRADOR_AGENT_MODEL=deepseek/deepseek-v4-flash-0731
875
- ```
876
-
877
- O `DeepSeek V4 Flash 0731` está registrado com contexto de 1.310.720 tokens e
878
- até 943.718 tokens de saída. O limite de entrada operacional é de 1.000.000
879
- de caracteres para evitar enviar acidentalmente o repositório inteiro em uma
880
- única chamada. Esses valores podem ser sobrescritos temporariamente com
881
- `ORQUESTRADOR_MODEL_CONFIG_JSON`, sem editar agentes. Um teto operacional por
882
- ambiente (`ORQUESTRADOR_OPENROUTER_MAX_COMPLETION_TOKENS` ou
883
- `ORQUESTRADOR_LLM_MAX_INPUT_CHARS`) continua disponível e tem precedência.
884
-
885
- Referências oficiais: [Quickstart Groq](https://console.groq.com/docs/quickstart),
886
- [modelos](https://console.groq.com/docs/models), [API compatível com OpenAI](https://console.groq.com/docs/openai),
887
- [Structured Outputs](https://console.groq.com/docs/structured-outputs),
888
- [LangChain + Groq](https://console.groq.com/docs/langchain),
889
- [Prompt Caching](https://console.groq.com/docs/prompt-caching) e
890
- [limites](https://console.groq.com/docs/rate-limits).
891
- Páginas dos modelos: [Compound](https://console.groq.com/docs/compound),
892
- [Compound mini](https://console.groq.com/docs/compound/systems/compound-mini),
893
- [GPT-OSS 120B](https://console.groq.com/docs/model/openai/gpt-oss-120b),
894
- [GPT-OSS 20B](https://console.groq.com/docs/model/openai/gpt-oss-20b),
895
- [Qwen 3.6 27B](https://console.groq.com/docs/model/qwen/qwen3.6-27b),
896
- [Prompt Guard 86M](https://console.groq.com/docs/model/meta-llama/llama-prompt-guard-2-86m) e
897
- [Safeguard 20B](https://console.groq.com/docs/model/openai/gpt-oss-safeguard-20b).
898
-
899
- ## 4. Roteamento multi-provedor
900
-
901
- Os agentes usam uma fábrica única (`src/llm/llm_provider.py`) e podem ser
902
- direcionados individualmente, por perfil ou globalmente. O comportamento
903
- legado continua sendo Groq por padrão. As credenciais são sempre lidas de
904
- variáveis de ambiente:
905
-
906
- ```dotenv
907
- # Provedor único para todos os agentes
908
- ORQUESTRADOR_LLM_PROVIDER=groq
909
- GROQ_API_KEY=...
910
-
911
- # Alternativas suportadas pelo adapter
912
- ANTHROPIC_API_KEY=...
913
- OPENAI_API_KEY=...
914
- GEMINI_API_KEY=...
915
- ```
916
-
917
- Para escolher provedor e modelo por agente, use JSON no secret/variável
918
- `ORQUESTRADOR_LLM_ROUTING_JSON` (não coloque chaves dentro do JSON):
12
+ Instale a versão publicada do npm diretamente no harness. Para fixar uma
13
+ versão em produção, use o número explícito no argumento do pacote:
919
14
 
920
15
  ```json
921
16
  {
922
- "default": {"provider": "groq", "model": "openai/gpt-oss-20b"},
923
- "profiles": {
924
- "reasoning": {"provider": "anthropic", "model": "claude-sonnet-4-5"},
925
- "coding": {"provider": "openai", "model": "gpt-4o-mini"}
926
- },
927
- "agents": {
928
- "agent_architect": {"provider": "anthropic", "model": "claude-opus-4-1"},
929
- "agent_backend": {"provider": "openai", "model": "gpt-4o"},
930
- "agent_code_review": {"provider": "groq", "model": "openai/gpt-oss-120b"}
17
+ "mcpServers": {
18
+ "jev-browser": {
19
+ "command": "npx",
20
+ "args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.3.1"],
21
+ "env": {
22
+ "OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
23
+ "JEV_BROWSER_MODE": "harness"
24
+ }
25
+ }
931
26
  }
932
27
  }
933
28
  ```
934
29
 
935
- Precedência: `agents` > `profiles` > `default` > variáveis legadas de
936
- provedor/perfil. Assim, o futuro frontend poderá salvar uma política de
937
- execução sem alterar o código dos agentes. Para trocar tudo de uma vez,
938
- defina apenas `ORQUESTRADOR_LLM_PROVIDER`, a chave correspondente e os
939
- modelos `ORQUESTRADOR_MODEL_*`.
940
-
941
- ## 5. Executar o orquestrador
942
-
943
- Modo automático para CI/local:
30
+ O formato de interpolação de variáveis varia por harness. Injete a chave por
31
+ um secret manager ou pelo ambiente do processo; não grave a chave no arquivo
32
+ de configuração. Para instalar no projeto Node do próprio harness:
944
33
 
945
- ```bash
946
- python main.py --auto-aprovar --tipo new --prazo "2 semanas" \
947
- "Criar um sistema de controle de tarefas"
948
- ```
949
-
950
- Opções importantes:
951
-
952
- ```bash
953
- python main.py --projeto-id meu-projeto --documentacao docs/requisitos.md pedido
954
- python main.py --no-cache --auto-aprovar pedido
955
- python main.py --resume --thread-id minha-thread
956
- python main.py --devops --auto-aprovar pedido
957
- ```
958
-
959
- O resultado só é considerado entrega aprovada quando `compliance_status=approved`.
960
- No modo sem chave LLM, o fluxo pode terminar com artefato fallback rejeitado.
961
-
962
- ## 6. Sandbox e gates D2
963
-
964
- Construa a imagem segura:
965
-
966
- ```bash
967
- docker build -f docker/Dockerfile.sandbox -t orquestrador-sandbox:py312 .
968
- ```
969
-
970
- Execute gates em container:
971
-
972
- ```bash
973
- ORQUESTRADOR_SANDBOX_MODE=docker \
974
- ORQUESTRADOR_SANDBOX_IMAGE=orquestrador-sandbox:py312 \
975
- python -m pytest -q
34
+ ```sh
35
+ npm install @diegosouzacdv/jev-browser-mcp
36
+ npx --yes @diegosouzacdv/jev-browser-mcp --install-browser
976
37
  ```
977
38
 
978
- O container usa rede desabilitada, filesystem read-only, usuário não-root,
979
- limites de CPU/memória/PIDs, timeout e ambiente sem credenciais. O contrato
980
- `config/frontend-validation.json` fixa a versão do TypeScript usada pelo
981
- validador e pelo `docker/Dockerfile.sandbox`; o workflow instala essa mesma versão
982
- antes do graph. ESLint e Playwright continuam pertencendo ao projeto gerado e
983
- só são executados quando sua configuração e dependências versionadas existem.
984
- O modo
985
- `subprocess` é apenas uma opção local de desenvolvimento.
39
+ No modo `harness`, a instalação baixa uma vez o Chrome/Edge que o Playwright
40
+ controlará. No modo `computer`, o pacote abre o Chrome/Edge instalado e usa um
41
+ perfil persistente exclusivo em `browser.computer_user_data_dir`; personalize
42
+ as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
43
+ `JEV_BROWSER_PROFILE`. O browser permanece aquecido enquanto o processo MCP
44
+ estiver ativo e fecha quando o harness encerra o processo. `JEV_PROVIDER_URL`
45
+ e `JEV_MODEL` podem substituir os valores centrais; a credencial continua no
46
+ nome de variável declarado em `jev.credential_env`.
47
+
48
+ Uma aplicação Node também pode importar `createJevBrowserServer` por
49
+ `@diegosouzacdv/jev-browser-mcp/server` e conectar o servidor ao transporte MCP
50
+ que ela já utiliza.
51
+
52
+ O pacote é montado em uma pasta temporária isolada: o publicador usa o campo
53
+ `files` do `package.json` para copiar somente o código Node, a configuração
54
+ compartilhada e esta documentação, que também vira o `README.md` da raiz do
55
+ pacote. Assim, o npm não inclui o README geral do orquestrador na página do MCP.
56
+ Para gerar e conferir o pacote antes de publicar, execute
57
+ `npm run pack:jev-browser-mcp`; para publicar uma versão já autenticada no npm, execute
58
+ `npm run publish:jev-browser-mcp`. A instalação por npm é a recomendada; use a
59
+ referência GitHub somente quando precisar experimentar uma revisão ainda não
60
+ publicada.
61
+
62
+ ### Contrato do pacote
63
+
64
+ O executável oferece `choose_next_action` e `run_browser_flow`, mantém uma
65
+ sessão do browser por processo e reutiliza essa sessão entre chamadas. O plano
66
+ passado ao Jev continua declarativo: clique, preenchimento, seleção nativa,
67
+ hover, espera, teclas aprovadas, asserções por elemento, upload restrito a uma
68
+ raiz local configurada e reações idempotentes a um comentário único. Também
69
+ aceita um nome de iframe para as ações que ocorrem dentro dele. Não aceita
70
+ JavaScript enviado pelo harness, seletores livres nem coordenadas. Ele usa a
71
+ biblioteca Playwright diretamente, sem iniciar um segundo servidor MCP do
72
+ Playwright. O transporte MCP usa `stdio`; toda saída de diagnóstico vai para
73
+ `stderr` para não misturar com JSON-RPC.
74
+
75
+ O modo `computer` grava cookies no perfil exclusivo configurado, e conteúdo da
76
+ página pode conter instruções maliciosas. O Jev recebe a captura acessível com
77
+ uma instrução para tratar esse conteúdo como dado não confiável; não inclua
78
+ segredos no fluxo, no resultado esperado ou nas descrições dos planos.
79
+
80
+ Para um fluxo conhecido, o LLM do harness procura o cenário e os critérios de
81
+ aceitação no projeto e chama `jev_browser.run_browser_flow` com planos
82
+ candidatos declarativos. O MCP abre uma única sessão do Playwright, navega para
83
+ a página inicial, captura o snapshot acessível e pede ao Jev que escolha um
84
+ plano. Em seguida, executa o plano inteiro na mesma sessão e confere o
85
+ resultado esperado na tela.
86
+
87
+ Cada plano pode usar:
88
+
89
+ - `click`, `type`, `hover` e `select_option` com papel/nome acessível exatos;
90
+ - `wait_for_text` e `wait_for_condition` (`network_idle`, limitado pelo timeout
91
+ de ação configurado);
92
+ - `press_key` com `PageDown`, `PageUp`, `Home`, `End`, `ArrowDown`, `ArrowUp`,
93
+ `Enter`, `Escape` ou `Tab`;
94
+ - `assert_text`, `assert_value`, `assert_visible` e `assert_hidden`;
95
+ - `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
96
+ dropzone;
97
+ - `audit_accessibility` com axe-core para WCAG 2.1 A/AA;
98
+ - `like_comment` e `unlike_comment`, que localizam uma linha pelo autor e texto,
99
+ não repetem uma reação já no estado pedido e distinguem `Curtir` de
100
+ `Descurtir`.
101
+
102
+ Quando vários controles têm o mesmo papel e nome, `within` limita a busca a um
103
+ container acessível único, como uma linha ou card. `index` escolhe uma ocorrência
104
+ zero-based dentro desse escopo; sem `index`, o MCP exige exatamente um alvo.
986
105
 
987
- Os gates executam compilação Python, Ruff, mypy, pytest, TypeScript e EXPLAIN
988
- SQL quando o artefato e o ambiente suportam cada verificação.
989
-
990
- ## 7. HITL HTTP
991
-
992
- `HITL_API_TOKEN` é um token interno criado pelo operador para proteger a API
993
- HTTP local. Ele não é fornecido pelo Google, não é `GEMINI_API_KEY`, não é uma
994
- credencial OAuth do Antigravity e não é um token do Gitea. Mantenha-o fora do
995
- repositório, prompts, argumentos de processo, logs e artefatos.
996
-
997
- Em Windows PowerShell, gere um token aleatório sem colar o valor literal no
998
- histórico. Esta forma funciona também no Windows PowerShell antigo; não troque
999
- por uma expressão com parênteses ou barras invertidas:
1000
-
1001
- ~~~powershell
1002
- $bytes = New-Object byte[] 32
1003
- $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
1004
-
1005
- try {
1006
- $rng.GetBytes($bytes)
1007
- }
1008
- finally {
1009
- $rng.Dispose()
106
+ ```json
107
+ {
108
+ "action": "click",
109
+ "role": "button",
110
+ "name": "Add to cart",
111
+ "within": {"role": "group", "name": "Sauce Labs Backpack"}
1010
112
  }
1011
-
1012
- $env:HITL_API_TOKEN = [Convert]::ToBase64String($bytes)
1013
- Remove-Variable bytes, rng
1014
- ~~~
1015
-
1016
- A variável vale para a janela/processo atual. Inicie a API na mesma janela,
1017
- preferencialmente a partir do checkout:
1018
-
1019
- ~~~powershell
1020
- Set-Location C:\orquestrador
1021
- & 'C:\orquestrador\.venv\Scripts\python.exe' -m uvicorn server.app:app --host 127.0.0.1 --port 8000
1022
- ~~~
1023
-
1024
- Se a API rodar como serviço, carregue o mesmo token pelo cofre de segredos do
1025
- serviço; não coloque o valor diretamente no comando, no arquivo versionado ou
1026
- em `setx` compartilhado. Para chamar a API em outro terminal, forneça o mesmo
1027
- valor ao ambiente desse cliente e use apenas o cabeçalho Bearer:
1028
-
1029
- ~~~powershell
1030
- $headers = @{ Authorization = "Bearer $env:HITL_API_TOKEN" }
1031
- ~~~
1032
-
1033
- Não execute `$env:HITL_API_TOKEN` sozinho, pois isso imprime o segredo. Ao
1034
- terminar a sessão, limpe a variável se ela não for mais necessária:
1035
-
1036
- ~~~powershell
1037
- Remove-Item Env:HITL_API_TOKEN -ErrorAction SilentlyContinue
1038
- ~~~
1039
-
1040
- Verifique saúde:
1041
-
1042
- ```bash
1043
- curl http://127.0.0.1:8000/health
1044
- ```
1045
-
1046
- Consulte ou retome uma thread usando `Authorization: Bearer ...`:
1047
-
1048
- ```bash
1049
- curl -H "Authorization: Bearer $HITL_API_TOKEN" \
1050
- http://127.0.0.1:8000/threads/minha-thread/state
1051
-
1052
- curl -X POST -H "Authorization: Bearer $HITL_API_TOKEN" \
1053
- -H "Content-Type: application/json" \
1054
- -d '{"resume_data":{"aprovado":true}}' \
1055
- http://127.0.0.1:8000/threads/minha-thread/resume
1056
- ```
1057
-
1058
- ### 7.1. Antigravity OAuth: state dir, host e container
1059
-
1060
- `ORQUESTRADOR_ANTIGRAVITY_STATE_DIR` é o diretório privado onde a API mantém o
1061
- estado protegido do login Antigravity. Ele não é uma API key, não é o
1062
- `client_id`, não é o `client_secret` e não deve apontar para o checkout. O
1063
- diretório pode conter o refresh token; por isso, copiar seus arquivos para
1064
- prompt, log, issue, artefato ou workspace é uma violação da fronteira de
1065
- credenciais.
1066
-
1067
- Há duas visões do mesmo diretório no ambiente local:
1068
-
1069
- | Onde | Caminho | Uso |
1070
- |---|---|---|
1071
- | API no host Windows | `C:\orquestrador-secrets\antigravity-state` | persistência do login e do refresh |
1072
- | job Linux do runner Docker | `/var/lib/orquestrador/antigravity-state` | caminho usado pelo workflow |
1073
- | secret do Gitea | `/var/lib/orquestrador/antigravity-state` | valor que o job recebe; não use o caminho Windows |
1074
-
1075
- O secret do Gitea configura somente o processo do job. Ele não configura uma
1076
- API que já esteja rodando no host. A API precisa receber o caminho Windows em
1077
- seu próprio ambiente; o runner precisa montar o mesmo diretório no caminho
1078
- Linux antes de executar o workflow.
1079
-
1080
- O diretório deve ser criado e protegido pelo administrador do host, fora de
1081
- `C:\orquestrador`, com ACL/DACL privada e sem junction, symlink ou reparse
1082
- point. Para o job Linux, o runner autorizado deve montar esse mesmo diretório
1083
- como `/var/lib/orquestrador/antigravity-state`; o valor salvo no Gitea é sempre
1084
- o caminho Linux, nunca o caminho Windows. Não liste nem copie os arquivos de
1085
- estado OAuth.
1086
-
1087
- #### 1. Cadastrar o caminho no Gitea
1088
-
1089
- Em `admin/projeto-global`, abra **Settings → Actions → Secrets** e cadastre
1090
- somente o par abaixo:
1091
-
1092
- ~~~text
1093
- Nome: ORQUESTRADOR_ANTIGRAVITY_STATE_DIR
1094
- Valor: /var/lib/orquestrador/antigravity-state
1095
- ~~~
1096
-
1097
- Não coloque o caminho Windows nesse secret: o job é Linux dentro do Docker.
1098
- Não tente ler o valor de volta pela interface e não registre secrets em
1099
- artefatos. Os secrets `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_ID` e
1100
- `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET` continuam necessários nos
1101
- passos Kernel/Claude: a conta persistida contém um `refreshToken`, e o proxy
1102
- precisa desses dois valores para convertê-lo em um access token antes da probe.
1103
- Eles não são necessários para SDK ou Pi e não devem ser injetados nesses jobs.
1104
-
1105
- **Não crie um cliente OAuth próprio para esta rota.** Esta instrução existiu
1106
- aqui até 2026-09-14 e estava errada; ela custou três runs e duas sessões de
1107
- diagnóstico, e o sintoma não apontava para ela.
1108
-
1109
- Um cliente criado no seu projeto do Google Cloud **autentica com sucesso** — o
1110
- login abre, o consentimento passa, o `refresh_token` é emitido e renovado — e
1111
- mesmo assim toda chamada morre em `UPSTREAM_HTTP_403`. O Google emite token
1112
- para qualquer cliente válido; o backend do Antigravity só aceita token cunhado
1113
- pelo cliente em que ele confia. A falha aparece apenas na chamada real, muito
1114
- depois do login, e se parece com um problema de conta ou de modelo.
1115
-
1116
- O par que a rota exige é o que o snapshot upstream traz embutido, visível nas
1117
- linhas removidas de `antigravity_proxy/patches/003-account-boundary.patch`. O
1118
- patch o tirou do **default de runtime** para que uma cópia do sidecar não fosse
1119
- fonte de credencial; o valor continua no repositório, dentro do patch, e é dali
1120
- que ele deve ser lido para preencher as duas variáveis:
1121
-
1122
- ~~~text
1123
- ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_ID
1124
- ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET
1125
- ~~~
1126
-
1127
- Isso vale para o ambiente da API **e** para os secrets do Gitea: os dois lados
1128
- precisam do mesmo cliente, porque o refresh token fica preso ao cliente que o
1129
- emitiu. Trocar o cliente invalida a conta persistida — é obrigatório
1130
- `logout` seguido de `auto --force-login` depois de mudar esses valores.
1131
-
1132
- Como confirmar em segundos, sem gastar uma corrida da matriz:
1133
-
1134
- ~~~powershell
1135
- Set-Location C:\orquestrador
1136
- $env:PYTHONPATH = "."
1137
- & '.\.venv\Scripts\python.exe' scripts\list_antigravity_models.py
1138
- ~~~
1139
-
1140
- Com o cliente certo, isso lista os modelos que a conta anuncia — em 2026-09-14,
1141
- onze, incluindo `gemini-3.8-flash-tiered`, que é o default do workflow. Com o
1142
- cliente errado, responde `500 ... UPSTREAM_HTTP_403`, e nenhuma troca de conta
1143
- ou de modelo muda esse resultado.
1144
-
1145
- #### 2. Login automático
1146
-
1147
- Depois de configurar no ambiente da API `ORQUESTRADOR_ANTIGRAVITY_STATE_DIR`,
1148
- `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_ID` e
1149
- `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET`, execute somente o helper abaixo
1150
- no host autorizado:
1151
-
1152
- ~~~powershell
1153
- Set-Location C:\orquestrador
1154
- & 'C:\orquestrador\.venv\Scripts\python.exe' scripts\antigravity_auth.py auto
1155
- ~~~
1156
-
1157
- O helper valida o ambiente, abre o navegador, recebe o callback loopback sem
1158
- clipboard ou impressão de código e executa `refresh`. Se já houver uma conta,
1159
- ele não abre novo login. O resultado esperado é `ready`; a API grava o estado
1160
- protegido somente no state dir. Não execute outro fluxo OAuth em paralelo.
1161
-
1162
- O valor produzido depois do login é estado protegido da conta, não deve ser
1163
- usado como `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET` nem copiado para
1164
- prompt, log, issue ou artefato.
1165
-
1166
- Somente depois de `status` pronto, refresh aprovado, DACL/reparse verificados e
1167
- mount confirmado deve ser disparada a matriz real. O procedimento detalhado de
1168
- AGS-004/ACP está em
1169
- [`docs/ags004-acp-live-runbook.md`](docs/ags004-acp-live-runbook.md).
1170
-
1171
- ## 8. Testes locais
1172
-
1173
- ```bash
1174
- pytest -q
1175
- ruff check .
1176
- mypy agents src server main.py scripts pi_harness mcp_servers
1177
- python scripts/smoke/mcp_tools_binding.py
1178
- python scripts/smoke/full_orchestrator_e2e.py
1179
- ```
1180
-
1181
- Smoke real Groq, com limite baixo de tokens:
1182
-
1183
- ```bash
1184
- GROQ_API_KEY="$GROQ_API_KEY" ORQUESTRADOR_SMOKE_MAX_TOKENS=512 \
1185
- ORQUESTRADOR_SMOKE_RUN_GRAPH=false \
1186
- python scripts/smoke_groq.py
1187
- ```
1188
-
1189
- O smoke não imprime a chave. O prompt caching é automático na Groq; o teste
1190
- registra `cached_tokens`, mas um cache miss não é tratado como falha porque a
1191
- Groq não garante hit em toda chamada. O workflow manual aprovado percorre
1192
- Compound, Compound Mini, Prompt Guard, GPT-OSS 20B, GPT-OSS 120B, Qwen 27B e
1193
- Safeguard 20B; todas as chamadas retornaram `OK`. O grafo fica desativado nesse
1194
- workflow para separar smoke de modelos de gates HITL/Compliance.
1195
-
1196
- ## 9. Batch API
1197
-
1198
- Ative somente para workloads assíncronos:
1199
-
1200
- ```dotenv
1201
- ORQUESTRADOR_BATCH_ENABLED=true
1202
- ORQUESTRADOR_USE_BATCH=true
1203
- ```
1204
-
1205
- O adapter usa JSONL e a API Groq Batch. A janela é assíncrona; acompanhe o
1206
- `batch_id` e não coloque prompts completos no `GraphState`.
1207
-
1208
- O Batch depende de habilitação do plano Groq. Se a API responder
1209
- `403 not_available_for_plan`, a credencial está válida, mas a conta ainda não
1210
- possui esse recurso; o adapter registra a situação e usa uma simulação offline
1211
- correlacionável para testes locais. A execução real exige habilitar o Batch no
1212
- plano Groq e usar um modelo elegível.
1213
-
1214
- Documentação: [Groq Batch API](https://console.groq.com/docs/batch) e
1215
- [referência de batches](https://console.groq.com/docs/api-reference).
1216
-
1217
- ## 10. Git remoto e GitHub Checks
1218
-
1219
- Em GitHub Actions, configure secrets/variáveis:
1220
-
1221
- ```dotenv
1222
- GITHUB_TOKEN=provided-by-github-actions
1223
- GITHUB_REPOSITORY=owner/repository
1224
- GITHUB_SHA=commit-sha
1225
- ORQUESTRADOR_GIT_REMOTE=https://github.com/owner/repository.git
1226
- ```
1227
-
1228
- O workflow cria branch por thread, publica commit, abre PR draft e publica
1229
- GitHub Check. Sem credencial explícita, o sistema mantém apenas branch/commit
1230
- local e não inventa URL de PR.
1231
-
1232
- ## 11. CI
1233
-
1234
- O workflow `quality.yml` é o portão determinístico: instalação, Ruff, mypy,
1235
- pytest, os smokes e o build do sandbox. Ele é **dividido em dois jobs** —
1236
- `quality` roda `-m 'not slow and not quarantine'` e `slow-e2e` roda o
1237
- complemento —, e os dois somados são `pytest -q`.
1238
-
1239
- **Quando ele roda está nos próprios arquivos, e as duas superfícies diferem de
1240
- propósito.** No GitHub, `pull_request` e disparo manual; no Gitea, disparo
1241
- manual apenas, porque enfileirar um check obrigatório contra um runner local
1242
- que nem sempre está ligado seria pior que não ter. Esta frase dizia "em todo
1243
- push/PR" até 2026-09-15, o que estava errado nas duas — e é o tipo de cópia em
1244
- prosa que `tests/guards/test_governance_document_truth_guard.py` passou a
1245
- conferir contra o YAML.
1246
-
1247
- Ele **não bloqueia merge** em nenhuma das duas: o `master` do Gitea não tem
1248
- regra de proteção, e a do GitHub é recurso pago em repositório privado. O que
1249
- segura a qualidade antes do push é a regra de trabalho do `AGENTS.md`, aplicada
1250
- pelo hook de pre-commit; o workflow é a repetição disso num Linux, que é o
1251
- ambiente que a máquina do operador não tem.
1252
-
1253
- O workflow `groq-smoke.yml` é manual (`workflow_dispatch`) e exige o secret
1254
- `GROQ_API_KEY` em um GitHub Environment protegido. Ele não deve ser executado
1255
- em PRs de forks.
1256
-
1257
- Para executar o E2E do grafo com LLM real, use o workflow manual
1258
- `.github/workflows/groq-graph-e2e.yml`. Ele usa o mesmo Environment protegido,
1259
- executa as trilhas `new`, `feature` e `fix` com Groq e mantém os limites do tier
1260
- on-demand. O workflow define `ORQUESTRADOR_REQUIRE_REAL_LLM=true`, portanto
1261
- fallback offline faz o job falhar. A execução pode terminar com
1262
- `compliance_status=rejected` por
1263
- pendências legítimas; o objetivo desse workflow é comprovar o caminho real do
1264
- grafo, não forçar aprovação de uma entrega sem evidência.
1265
-
1266
- No GitHub: **Actions → groq-graph-e2e → Run workflow → master**. Se o
1267
- Environment exigir aprovação, aprove o job antes que os secrets sejam
1268
- liberados.
1269
-
1270
- Permissões mínimas recomendadas:
1271
-
1272
- ```yaml
1273
- permissions:
1274
- contents: read
1275
- checks: write
1276
- ```
1277
-
1278
- Para PR remoto, conceda `contents: write` e `pull-requests: write` apenas no
1279
- workflow manual protegido.
1280
-
1281
- ## 12. Observabilidade
1282
-
1283
- Para exportar spans via OTLP:
1284
-
1285
- ```dotenv
1286
- OTEL_SERVICE_NAME=orquestrador
1287
- OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
1288
113
  ```
1289
114
 
1290
- Sem endpoint, o sistema registra apenas eventos técnicos locais. Prompts,
1291
- código, tokens de autenticação e conteúdo de documentação não entram nos
1292
- traces.
1293
-
1294
- ## 13. Artefatos e D5
1295
-
1296
- O código gerado é salvo em `workspace/<thread-id>/`; o estado carrega hashes e
1297
- referências do manifesto. Checkpoints legados ainda podem ser lidos durante a
1298
- migração, mas novas execuções não precisam persistir blobs completos quando
1299
- `ORQUESTRADOR_LEGACY_CODE_STATE=false`.
1300
-
1301
- ## 14. Documentação do projeto
1302
-
1303
- - [Trilha de sessões e status](docs/plano-agentic-skills-status.md) (append-only, com índice em `docs/plano-status.json`)
1304
- - [Estrutura de pastas e nomenclatura](docs/planos/auditoria/estrutura-pastas-e-nomenclatura.md)
1305
- - [Fluxo de agentes, documentação e execução](docs/fluxo-agentes-documentacao-e-execucao.md)
1306
- - [Guia de atualização dos validadores](docs/planos/auditoria/guia-atualizacao-versoes-validadores.md)
1307
-
1308
- ## 15. Time travel, retenção e governança
1309
-
1310
- Forks são não destrutivos: o checkpoint pai permanece na thread original e o
1311
- fork recebe novo `thread_id`, workspace independente e branch local.
1312
-
1313
- ```bash
1314
- python scripts/time_travel.py list --thread-id <id>
1315
- python scripts/time_travel.py fork --thread-id <id> --checkpoint-id <cp> \
1316
- --field prazo_entrega --value "2026-09-15"
1317
- python scripts/time_travel.py retention --days 30 # dry-run
1318
- python scripts/time_travel.py retention --days 30 --purge --legal-hold <id>
1319
- ```
1320
-
1321
- Objetos D5 são endereçados por SHA-256 e o modo legado permanece opt-in:
1322
- `ORQUESTRADOR_LEGACY_CODE_STATE=true`.
1323
-
1324
- ## 16. Web escopada, qualidade e Batch
1325
-
1326
- Configure `ORQUESTRADOR_WEB_ALLOWLIST` antes de habilitar o MCP web; sem
1327
- allowlist, HTTPS, resolução pública e aprovação humana, o conteúdo é
1328
- bloqueado. Para quality gates completos, use `docker/Dockerfile.quality`, que instala
1329
- Playwright/Lighthouse e grava `data/quality-evidence.json`.
1330
-
1331
- Submissão/consulta Batch:
1332
-
1333
- ```bash
1334
- python scripts/batch_cli.py submit --prompt "health" --model deepseek/deepseek-v4-flash-0731
1335
- python scripts/batch_cli.py status <batch-id>
1336
- ```
1337
-
1338
- O workflow `.github/workflows/batch-submit.yml` usa somente modelos Groq Batch
1339
- elegíveis e um GitHub Environment protegido.
1340
-
1341
- ## 17. OpenRouter opcional (multi-provedor e multimodal)
1342
-
1343
- Groq continua sendo o padrão. Para habilitar OpenRouter, configure a chave
1344
- somente no ambiente de execução (nunca em `GraphState`, registry ou logs):
1345
-
1346
- ```dotenv
1347
- ORQUESTRADOR_LLM_PROVIDER=openrouter
1348
- OPENROUTER_API_KEY=...
1349
- OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
1350
- # opcional: OPENROUTER_HTTP_REFERER=https://seu-dominio
1351
- # opcional: OPENROUTER_TITLE=Orquestrador
1352
- # opcional: ORQUESTRADOR_OPENROUTER_TIMEOUT_SECONDS=90
1353
- # compatibilidade legada, somente com ORQUESTRADOR_POLICY_LEGACY_OVERRIDES=true
1354
- # ORQUESTRADOR_OPENROUTER_REASONING_EFFORT=low
1355
- # ORQUESTRADOR_OPENROUTER_STRUCTURED_METHOD=json_schema
1356
- # ORQUESTRADOR_OPENROUTER_REQUIRE_PARAMETERS=true
1357
- # ORQUESTRADOR_OPENROUTER_STRUCTURED_RETRIES=1
1358
- # opcional: ORQUESTRADOR_SMOKE_MAX_COMPLETION_TOKENS=16
1359
- # opcional: ORQUESTRADOR_LLM_LOG_PREVIEW_CHARS=120 (0 desabilita previews)
1360
- ```
1361
-
1362
- Para modelos marcados no catálogo local como compatíveis, o padrão é
1363
- `json_schema` com `strict=true`. No E2E canônico,
1364
- `ORQUESTRADOR_OPENROUTER_REQUIRE_PARAMETERS=true` também restringe o
1365
- roteamento a endpoints que aceitem os parâmetros enviados. Use `json_mode`
1366
- somente como override para um modelo/endpoint sem structured outputs nativos.
1367
-
1368
- As chamadas diretas de OpenRouter (catálogo, mídia, TTS/STT, vídeo e smoke)
1369
- usam o pacote oficial `openrouter==1.1.81`, com respostas tipadas e clientes
1370
- síncrono/assíncrono. O LangGraph permanece sobre LangChain para preservar
1371
- `Runnable`, saída estruturada, callbacks, ferramentas e o roteamento entre
1372
- Groq, OpenRouter e demais provedores. O Batch é a única exceção: nessa versão
1373
- do SDK o endpoint `/beta/batches` ainda não está exposto, portanto fica em um
1374
- transporte HTTP privado e isolado.
1375
-
1376
- O SDK não realiza novas tentativas por conta própria. Backoff, circuit breaker,
1377
- limite de quatro tentativas e fallback continuam centralizados no orquestrador.
1378
- O adapter aceita `max_tokens` apenas por compatibilidade e o converte para
1379
- `max_completion_tokens`; código novo deve usar o segundo nome.
1380
-
1381
- Cada chamada OpenRouter também emite logs reduzidos `[OPENROUTER] REQUEST`,
1382
- `RESPONSE` e `ERROR`, com agente, modelo, tentativa, duração, hashes,
1383
- tamanhos, tokens/cache, generation ID e erro sanitizado. Prompts e respostas
1384
- completos não são gravados; o preview é limitado por
1385
- `ORQUESTRADOR_LLM_LOG_PREVIEW_CHARS` (defina `0` em ambientes sensíveis).
1386
-
1387
- Os limites físicos de cada modelo permanecem em `src/llm/llm_model_config.py`. Já
1388
- os orçamentos operacionais por agente ficam em um único JSON:
1389
-
1390
- ```dotenv
1391
- # Somente para migração de ambientes antigos; a fonte normal é
1392
- # config/agent-policy.json e o mapa é ignorado sem opt-in explícito.
1393
- ORQUESTRADOR_POLICY_LEGACY_OVERRIDES=true
1394
- ORQUESTRADOR_AGENT_RUNTIME_JSON={"agent_clean_code":{"max_completion_tokens":8192,"reasoning_effort":"minimal","timeout_seconds":180,"structured_retries":1},"agent_code_review":{"max_completion_tokens":8192,"reasoning_effort":"minimal","timeout_seconds":180,"structured_retries":0}}
1395
- ORQUESTRADOR_REVIEW_MAX_INPUT_CHARS=30000
1396
- ORQUESTRADOR_RED_TEAM_MAX_INPUT_CHARS=30000
1397
- ```
1398
-
1399
- Falhas do Planejador ou de Requisitos são bloqueantes. Primeiro,
1400
- `structured_retries` repete apenas a chamada do agente. Se todas as tentativas
1401
- falharem, o portão `critical_failure_gate` permite repetir somente esse nó a
1402
- partir do checkpoint ou encerrar. `ORQUESTRADOR_CRITICAL_NODE_RETRIES` limita
1403
- essas repetições manuais (padrão: `1`). Em CI, `auto_aprovar` escolhe encerrar;
1404
- documentação ausente nunca é transformada em fallback aprovado.
1405
-
1406
- Planejador e Consultor persistem seus resultados antes dos nós de aprovação.
1407
- Assim, retomar um HITL não repete a chamada LLM. Clean Code, Code Review, Red
1408
- Team e Compliance usam contratos compactos; fallback de um quality gate é
1409
- registrado em `validacoes_fallback`, reprova o Compliance e faz o E2E real
1410
- falhar explicitamente.
1411
-
1412
- O dispatcher é orientado por fases: o roadmap normalizado atribui os agentes
1413
- de implementação e o grafo executa `agent_dba` (persistência/contrato),
1414
- `agent_backend` (API) e `agent_frontend` (interface) em etapas separadas,
1415
- com fan-in e aprovação HITL entre elas. Code Review e Red Team só começam após
1416
- a última fase; threads antigas sem a atribuição `agentes` mantêm o fan-out
1417
- legado para compatibilidade.
1418
-
1419
- O workflow `openrouter-graph-e2e.yml` define
1420
- `ORQUESTRADOR_ITERATIVE_REPAIR=false`: ele valida uma rodada completa e
1421
- encaminha achados ao Compliance sem repetir três ciclos de geração, que
1422
- custariam três vezes mais para dizer a mesma coisa. O padrão está em
1423
- `defaults.iterative_repair` e a variável o sobrepõe para uma corrida.
1424
-
1425
- Antes isso era `ORQUESTRADOR_E2E_NO_REPAIR`, lida com literal dentro de
1426
- `src/state/graph.py`. É deliberadamente independente de
1427
- `ORQUESTRADOR_RUN_MODE`: amarrar as duas faria quem quer uma corrida mais
1428
- barata pinar o provedor sem pedir, e tornaria impossível medir o próprio laço
1429
- de reparação.
1430
-
1431
- Rotas podem ser escolhidas por agente/perfil/thread em
1432
- `ORQUESTRADOR_LLM_ROUTING_JSON`; `fallback_models` usa a cascata nativa do
1433
- OpenRouter e `external_routes` define a cascata entre provedores. O limite é
1434
- de quatro tentativas e erros de autenticação, payload, orçamento e segurança
1435
- não fazem fallback.
1436
-
1437
- A precedência é `thread/agente > thread/perfil > projeto/agente >
1438
- projeto/perfil > defaults do projeto > ambiente`. O registry persiste apenas
1439
- JSON de política; chaves permanecem exclusivamente em variáveis de ambiente.
1440
-
1441
- ### Claude Code como harness opcional
1442
-
1443
- É possível adicionar o Claude Code/Anthropic Agent SDK como backend de
1444
- execução dos agentes de código, mantendo a OpenRouter como gateway. A
1445
- arquitetura recomendada é preservar o LangGraph como plano de controle
1446
- (roteamento, HITL, orçamento, checkpoints, Compliance e D5) e delegar apenas
1447
- uma tarefa de implementação isolada ao harness Claude Code, com workspace e
1448
- tools permitidas explicitamente.
1449
-
1450
- O processo filho receberá as credenciais somente em memória:
1451
-
1452
- ```dotenv
1453
- ANTHROPIC_BASE_URL=https://openrouter.ai/api
1454
- ANTHROPIC_AUTH_TOKEN=<valor de OPENROUTER_API_KEY injetado no processo>
1455
- ANTHROPIC_API_KEY=
1456
- ```
1457
-
1458
- O executor está disponível por política explícita. O workflow canônico usa
1459
- `deepseek/deepseek-v4-flash-0731` através do protocolo Anthropic Skin. A
1460
- documentação da OpenRouter garante formalmente o Claude Code apenas com
1461
- modelos Anthropic first-party; DeepSeek, MiniMax e Kimi são portanto uma rota
1462
- experimental do projeto e precisam de E2E próprio. A política de produção é:
1463
-
1464
115
  ```json
1465
- {"agents":{"agent_backend":{"executor":"claude_code","model":"deepseek/deepseek-v4-flash-0731","fallback_models":["minimax/minimax-m3","moonshotai/kimi-k2.7-code"]},"agent_frontend":{"executor":"claude_code","model":"deepseek/deepseek-v4-flash-0731","fallback_models":["minimax/minimax-m3","moonshotai/kimi-k2.7-code"]}}}
1466
- ```
1467
-
1468
- MiniMax M3 custa US$ 0,30/M de input e US$ 1,20/M de output; Kimi K2.7
1469
- Code custa US$ 0,67/M de input e US$ 3,40/M de output (valores de catálogo,
1470
- o custo efetivo vem do usage). Esses fallbacks só são habilitados quando
1471
- `ORQUESTRADOR_EXECUTOR_FALLBACK_MODE=production`; workflows usam
1472
- `disabled`, DeepSeek exclusivamente e no máximo três tentativas. Erros de
1473
- payload, autenticação, orçamento, segurança e ownership nunca fazem fallback.
1474
-
1475
- Documentação oficial: [Quickstart](https://openrouter.ai/docs/quickstart),
1476
- [Client SDKs](https://openrouter.ai/docs/client-sdks/overview),
1477
- [SDK Python](https://openrouter.ai/docs/client-sdks/python/overview),
1478
- [model fallbacks](https://openrouter.ai/docs/guides/routing/model-fallbacks),
1479
- [multimodal](https://openrouter.ai/docs/guides/overview/multimodal/overview),
1480
- [Batch](https://openrouter.ai/docs/batch-quickstart),
1481
- [prompt caching](https://openrouter.ai/docs/guides/best-practices/prompt-caching#caching-in-the-batch-api)
1482
- e [MCP administrativo](https://openrouter.ai/docs/guides/overview/mcp-server).
1483
- Para o harness, consulte [Claude Code com OpenRouter](https://openrouter.ai/docs/cookbook/coding-agents/claude-code-integration)
1484
- e [Anthropic Agent SDK com OpenRouter](https://openrouter.ai/docs/guides/community/anthropic-agent-sdk).
1485
-
1486
- Workflows manuais separados:
1487
-
1488
- ```text
1489
- .github/workflows/openrouter-smoke.yml
1490
- .github/workflows/openrouter-graph-e2e.yml
1491
- .github/workflows/openrouter-batch-submit.yml
1492
- .github/workflows/openrouter-batch-status.yml
1493
- ```
1494
-
1495
- Configuração no GitHub:
1496
-
1497
- 1. Crie uma chave em [OpenRouter Keys](https://openrouter.ai/settings/keys).
1498
- 2. Em **Settings → Environments**, crie exatamente `openrouter-smoke`,
1499
- `openrouter-graph-e2e` e `openrouter-batch`.
1500
- 3. Em cada Environment, adicione o secret **`OPENROUTER_API_KEY`**. Não use
1501
- Repository variable nem coloque a chave no YAML. Se o Environment exigir
1502
- aprovação, aprove a implantação antes da execução.
1503
- 4. Opcionalmente adicione as variables `OPENROUTER_HTTP_REFERER` e
1504
- `OPENROUTER_TITLE` para identificação da aplicação.
1505
- 5. Em **Actions → workflow → Run workflow**, execute primeiro o smoke. Depois
1506
- use o graph E2E. Batch exige quota/plano habilitado; submeta o lote e copie
1507
- o `batch_id` impresso para o workflow de status.
1508
-
1509
- Pela CLI do GitHub:
1510
-
1511
- ```bash
1512
- gh secret set OPENROUTER_API_KEY --env openrouter-smoke --repo OWNER/REPO
1513
- gh secret set OPENROUTER_API_KEY --env openrouter-graph-e2e --repo OWNER/REPO
1514
- gh secret set OPENROUTER_API_KEY --env openrouter-batch --repo OWNER/REPO
1515
- gh workflow run openrouter-smoke.yml --ref master -f model=deepseek/deepseek-v4-flash-0731
1516
- gh run watch --exit-status
1517
- ```
1518
-
1519
- O grafo E2E executa uma trilha por vez por padrão, reduzindo rate limit e
1520
- duração. Escolha a trilha assim:
1521
-
1522
- A trilha `new` do workflow usa deliberadamente o pedido fixo e pequeno
1523
- `CRUD simples de produtos (código identificador e nome)`, para validar o
1524
- fluxo do grafo e o Compliance sem transformar o teste em uma geração de
1525
- produto grande. Ele pode ser sobrescrito apenas por `ORQUESTRADOR_E2E_CLIENT_INPUT`
1526
- em execuções controladas.
1527
-
1528
- Quando a trilha `new` não recebe `projeto_id` nem `mcp_roots`, o Arquiteto usa
1529
- um contexto **greenfield** determinístico e não indexa o código-fonte do
1530
- próprio orquestrador. Para evoluir um código existente desde o início, registre
1531
- o projeto/base ou forneça um root MCP explícito; nesse caso a análise detalhada
1532
- do repositório continua habilitada.
1533
-
1534
- ```bash
1535
- gh workflow run openrouter-graph-e2e.yml --ref master \
1536
- -f model=deepseek/deepseek-v4-flash-0731 -f tracks=new
1537
- # Para executar todas: -f tracks=new,feature,fix
116
+ {"action":"click","role":"button","name":"Add to cart","index":0}
1538
117
  ```
1539
118
 
1540
- O primeiro passo de cada workflow executa `test -n "$OPENROUTER_API_KEY"`; se
1541
- falhar, o secret está no Environment errado, o Environment não foi aprovado ou
1542
- o nome está diferente. A API usa `https://openrouter.ai/api/v1`, conforme o
1543
- [Quickstart oficial](https://openrouter.ai/docs/quickstart), e os modelos
1544
- devem ser IDs publicados no [catálogo](https://openrouter.ai/models).
1545
- Para contas com crédito limitado, reduza opcionalmente
1546
- `ORQUESTRADOR_OPENROUTER_MAX_COMPLETION_TOKENS`; quando ausente, cada modelo
1547
- usa o limite registrado em `src/llm/llm_model_config.py`. Os workflows OpenRouter
1548
- usam uma cascata paga de DeepSeek V4 Flash e Solar Pro4, evitando as cotas
1549
- instáveis dos endpoints gratuitos.
1550
-
1551
- O smoke registra modelo efetivo, generation ID, tokens, cache, custo e BYOK.
1552
- O smoke usa `ORQUESTRADOR_SMOKE_MAX_COMPLETION_TOKENS` (16 por padrão, apenas
1553
- para manter a chamada curta); `ORQUESTRADOR_SMOKE_MAX_TOKENS` continua aceito
1554
- temporariamente. Ele não limita as execuções do grafo. No Batch, `max_tokens`
1555
- é opcional por item: se omitido, o adapter não injeta um teto artificial do
1556
- OpenRouter; informe-o somente quando quiser limitar aquele item.
1557
- Batch OpenRouter aceita apenas texto, um modelo por lote e janela de 24 horas;
1558
- simulação offline só é permitida com
1559
- `ORQUESTRADOR_BATCH_OFFLINE_SIMULATION=true`.
1560
-
1561
- Uploads multimodais são armazenados localmente por SHA-256 em D5. Imagem,
1562
- PDF, áudio e vídeo são convertidos para as partes da API apenas durante a
1563
- requisição; base64 e binários não entram em checkpoints. As APIs prontas para
1564
- frontend são `/llm/*`, `/integrations/openrouter/mcp/*`, `/media/*`,
1565
- `/batches` e `POST /threads`. Para OAuth administrativo por usuário configure
1566
- `OIDC_ISSUER_URL`, `OIDC_AUDIENCE`, `OIDC_JWKS_URL` e
1567
- `ORQUESTRADOR_CREDENTIAL_ENCRYPTION_KEY`.
1568
-
1569
- ## Planejamento especializado e executores agentic
1570
-
1571
- O grafo separa planejamento e execução: requisitos, arquitetura e Clean Code
1572
- alimentam os planos DBA, Backend e Frontend; uma validação determinística
1573
- confere cobertura/propriedade; uma única aprovação HITL libera a ordem DBA →
1574
- Backend → Frontend. Backend não pode possuir migrations e Frontend não pode
1575
- inventar endpoint, SQL ou regra de negócio. Migrations que falham no banco
1576
- efêmero não entram no manifesto D5.
1577
-
1578
- O executor padrão é o **`kernel`**, declarado em `config/agent-policy.json`
1579
- (`defaults.executor`). Os executores selecionáveis são `kernel`, `claude_code`,
1580
- `pi` e `antigravity_sdk` (`defaults.selectable.executor`); `legacy` (o adapter
1581
- LangChain) e `deepseek_harness` permanecem apenas como compatibilidade, em
1582
- `COMPATIBILITY_EXECUTOR_NAMES` de `src/execution/executors.py`, e não são o
1583
- padrão de nada. Claude Code pode ser ativado por política para Backend/Frontend
1584
- e usa o SDK `claude-agent-sdk` com OpenRouter/Anthropic Skin. DeepSeek Harness é
1585
- somente shadow/piloto em Linux, workspace isolado e sem aplicar artefatos ao
1586
- workspace canônico. Se um executor explicitamente solicitado não estiver
1587
- disponível, a execução é bloqueada; não há fallback silencioso.
119
+ ### Captura de downloads
1588
120
 
1589
- Cada executor externo trabalha numa cópia descartável do workspace. Apenas
1590
- arquivos textuais permitidos pela task card, pertencentes ao agente e dentro do
1591
- orçamento são promovidos ao armazenamento D5. Remoções, binários, paths fora do
1592
- plano e violações de propriedade bloqueiam a fase. Claude Code não recebe Bash,
1593
- WebFetch, WebSearch ou NotebookEdit; os limites de contexto/conclusão do
1594
- DeepSeek vêm de `src/llm/llm_model_config.py`, e não do arquivo Cordis.
1595
-
1596
- O executor `pi` roda o loop do harness Pi (`@earendil-works/pi-agent-core` +
1597
- `pi-ai`) num sidecar Node privado em `pi_harness/worker/`. Ele vem **desligado** em
1598
- `config/agent-policy.json` (`pi_harness.enabled=false`) e, quando ligado,
1599
- atende só os owners listados em `pi_harness.agents`. O sidecar não recebe
1600
- credencial, Git, shell nem o workspace canônico: cada inferência volta para a
1601
- fachada central em `src/llm/llm_provider.py` e cada ferramenta é revalidada contra
1602
- o WorkPacket em `pi_harness/tools.py`. Instale o worker com
1603
- `npm ci --ignore-scripts --prefix pi_harness/worker` e verifique o runtime com
1604
- `python -m pi_harness.bridge`. Detalhes de operação em
1605
- [`docs/planos/auditoria/operacao-pi-harness.md`](docs/planos/auditoria/operacao-pi-harness.md).
1606
-
1607
- Instale os SDKs opcionais em Linux/Docker com
1608
- `pip install -r requirements-agentic.txt`; a instalação padrão não os exige.
1609
- As versões testadas são `claude-agent-sdk==0.2.128` e
1610
- `deepseek-harness-sdk==0.1.1rc2`. O DeepSeek Harness não possui wheel do runtime
1611
- para Windows e, por isso, seu piloto roda apenas em Linux.
1612
-
1613
- Exemplo de política (sem credenciais; fallbacks somente em produção):
121
+ Marque o clique que deve iniciar um download com `expect_download: true`. O
122
+ Playwright começa a aguardar o evento antes do clique; após a transferência
123
+ terminar, o MCP verifica o tamanho, sanitiza o nome sugerido e copia o arquivo
124
+ para `JEV_BROWSER_ARTIFACT_DIR` (ou para o diretório de artefatos configurado).
125
+ O resultado inclui a lista `downloaded_files`, com nome, caminho local e bytes;
126
+ cada passo também contém o arquivo capturado. Os limites contam arquivos e bytes
127
+ por fluxo. A checagem de tamanho acontece depois da transferência do navegador,
128
+ antes de copiar para a pasta de artefatos.
1614
129
 
1615
130
  ```json
1616
- {"default":{"executor":"kernel"},"fallback_mode":"disabled","agents":{"agent_backend":{"executor":"claude_code","model":"deepseek/deepseek-v4-flash-0731","fallback_models":["minimax/minimax-m3","moonshotai/kimi-k2.7-code"],"shadow_executor":"pi"},"agent_frontend":{"executor":"claude_code","model":"deepseek/deepseek-v4-flash-0731","fallback_models":["minimax/minimax-m3","moonshotai/kimi-k2.7-code"],"shadow_executor":"pi"},"agent_dba":{"executor":"kernel"}}}
131
+ {"action":"click","role":"button","name":"Export report","expect_download":true}
1617
132
  ```
1618
133
 
1619
- MCPs são registrados por especialidade: Context7 (`resolve-library-id` e
1620
- `query-docs`) para planejamento/desenvolvimento, 21st somente para Frontend e
1621
- PostgreSQL somente leitura para o DBA. Configure `CONTEXT7_API_KEY`,
1622
- `TWENTY_FIRST_API_KEY` e `MCP_POSTGRES_DSN` apenas no secret manager; o estado
1623
- guarda somente hashes de evidência e capacidades liberadas.
1624
-
1625
- O cliente MCP aplica a allowlist antes de cada chamada, protege conteúdo
1626
- externo contra prompt injection e limita o texto retornado. No PostgreSQL, o
1627
- modelo não recebe a ferramenta `connect` nem o DSN: o orquestrador conecta com
1628
- um usuário read-only, entrega somente schema/query/EXPLAIN e desconecta. A
1629
- validação de migrations usa separadamente `MCP_POSTGRES_VALIDATION_DSN`, que é
1630
- aceito apenas para hosts allowlisted e bancos efêmeros. Para inspecionar a
1631
- conectividade real use `GET /mcp/capabilities?probe=true`.
134
+ `jev_browser_mcp.browser.max_download_files`, `max_download_file_bytes`,
135
+ `max_download_total_bytes` e `max_download_timeout_seconds` definem os limites.
136
+ O diretório de artefatos deve ser local e protegido: arquivos baixados podem
137
+ conter dados da aplicação.
1632
138
 
1633
- Claude Code recebe proxies MCP locais, não os servidores remotos brutos. Esses
1634
- proxies publicam exclusivamente a allowlist read-only e reaplicam sanitização e
1635
- Prompt Guard em cada resultado. Portanto, ferramentas novas ou mutantes que o
1636
- 21st venha a publicar não ficam automaticamente acessíveis ao executor.
139
+ ### Auditoria automatizada de acessibilidade
1637
140
 
1638
- ### MCP de navegador com Jev e Playwright
141
+ Use `audit_accessibility` depois de colocar a página no estado que deseja
142
+ verificar:
1639
143
 
1640
- O pacote independente `@diegosouzacdv/jev-browser-mcp` conecta qualquer harness
1641
- compatível com MCP por `stdio` ao Jev e ao Playwright. Requer Node.js 22 ou mais
1642
- recente. Instale o navegador do Playwright uma vez para execuções no modo
1643
- `harness`:
144
+ ```json
145
+ {"action":"audit_accessibility","standard":"wcag2aa"}
146
+ ```
147
+
148
+ O MCP usa `@axe-core/playwright` com as tags WCAG 2.0 e 2.1 A/AA. O retorno
149
+ resume as violações por regra, impacto e quantidade de nós, sem copiar HTML ou
150
+ trechos da página. Qualquer violação reprova esse fluxo. A auditoria automática
151
+ encontra problemas comuns, mas não comprova conformidade WCAG completa; combine-a
152
+ com revisão manual e testes com usuários assistivos.
153
+
154
+ Para ações em iframe, acrescente `"frame": "payment-iframe"`; o valor precisa
155
+ corresponder ao atributo `name` ou `title` do iframe. O snapshot inclui o
156
+ conteúdo acessível dos iframes nomeados dentro do escopo, limitado pela
157
+ configuração. Se um alvo estiver ausente, o erro inclui até três nomes acessíveis
158
+ próximos quando o snapshot os encontrar. O plano não aceita JavaScript enviado
159
+ pelo harness, coordenadas ou seletores livres.
160
+
161
+ Os aliases `value` → `text` em `type`/`wait_for_text` e `value` → `expected` em
162
+ asserções são aceitos. `comment` também é aceito em planos e passos, mas é
163
+ ignorado e não é enviado ao Jev. Campos fora do contrato continuam sendo
164
+ recusados.
165
+
166
+ ### Upload de arquivos
167
+
168
+ Defina `JEV_BROWSER_UPLOAD_ROOT` como uma pasta absoluta que contenha os arquivos
169
+ de fixture. Cada caminho passado ao fluxo também precisa ser absoluto; o MCP
170
+ resolve links simbólicos e recusa qualquer arquivo fora dessa raiz. Só aceita
171
+ arquivos regulares, até 5 arquivos por passo, 10 MiB por arquivo e 25 MiB no
172
+ total do passo, conforme `jev_browser_mcp.browser` em `config/ui-testing.json`.
173
+ Os bytes são lidos e validados no processo local antes de serem entregues ao
174
+ Playwright. O MCP não envia esses bytes ao Jev nem os inclui diretamente na
175
+ resposta; texto que o próprio site exibir na interface ainda pode aparecer no
176
+ snapshot devolvido ao harness.
1644
177
 
1645
- ```sh
1646
- npx --yes @diegosouzacdv/jev-browser-mcp --install-browser
178
+ ```json
179
+ {"action":"upload_file","target":"input","label":"Nota fiscal","file_path":"/fixtures/nota.pdf"}
1647
180
  ```
1648
181
 
1649
- Registre o servidor MCP no formato aceito pelo seu harness. Exemplo de
1650
- configuração comum:
182
+ Para uma área de arrastar, use o papel e o nome acessível da dropzone. Para um
183
+ botão que abre a janela nativa, use `target: "button"`; sem `target`, a presença
184
+ de `label` seleciona o input e `role`/`name` seleciona esse botão.
1651
185
 
1652
186
  ```json
1653
- {
1654
- "mcpServers": {
1655
- "jev-browser": {
1656
- "command": "npx",
1657
- "args": ["--yes", "@diegosouzacdv/jev-browser-mcp"],
1658
- "env": {
1659
- "OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
1660
- "JEV_BROWSER_MODE": "harness"
1661
- }
1662
- }
1663
- }
1664
- }
187
+ {"action":"upload_file","target":"dropzone","role":"region","name":"Anexos","file_paths":["/fixtures/a.pdf","/fixtures/b.png"]}
1665
188
  ```
1666
189
 
1667
- Configure `OPENROUTER_API_KEY` no secret manager ou no ambiente do processo
1668
- que inicia o harness. Não coloque a chave no JSON. O endpoint e o modelo
1669
- Decisions têm valores padrão no pacote; `JEV_PROVIDER_URL` e `JEV_MODEL` podem
1670
- substituí-los.
1671
-
1672
- Escolha o navegador por variáveis de ambiente:
190
+ Se `JEV_BROWSER_UPLOAD_ROOT` não estiver definido, a ação recusa a execução. O
191
+ limite evita que um plano transforme o MCP em leitor arbitrário de arquivos do
192
+ computador.
1673
193
 
1674
- - `JEV_BROWSER_MODE=harness` inicia um navegador isolado e sem interface,
1675
- adequado para testes automatizados e CI.
1676
- - `JEV_BROWSER_MODE=computer` abre o Chrome ou Edge instalado. O MCP usa um
1677
- perfil persistente próprio; faça login nele uma vez. Não configure o perfil
1678
- pessoal que já está aberto no computador.
1679
- - `JEV_BROWSER_CHANNEL=chrome` ou `msedge` escolhe o navegador.
1680
- - `JEV_BROWSER_PROFILE` define o caminho absoluto do perfil persistente no
1681
- modo `computer`.
1682
-
1683
- Para um fluxo conhecido, o agente do harness consulta o cenário e os critérios
1684
- de aceitação no projeto e chama `run_browser_flow` uma vez. Informe o objetivo,
1685
- a URL inicial, o resultado esperado e planos candidatos declarativos. O Jev
1686
- escolhe um dos planos com base no snapshot acessível da página; o MCP executa
1687
- os passos e confere se o resultado esperado apareceu. Exemplo resumido:
194
+ Exemplo de chamada:
1688
195
 
1689
196
  ```json
1690
197
  {
1691
- "flow": "Adicionar o produto ao carrinho",
198
+ "flow": "Adicionar o produto ao carrinho e confirmar o resumo",
1692
199
  "initial_url": "http://127.0.0.1:4173/products/coffee",
1693
200
  "expected_outcome": "Coffee added to cart",
201
+ "options": {
202
+ "fast_path": true,
203
+ "snapshot_scope": "main",
204
+ "capture_network_errors": true,
205
+ "screenshot_on_failure": true
206
+ },
1694
207
  "candidate_plans": {
1695
- "add_product": {
1696
- "description": "Adicionar o produto visível ao carrinho",
208
+ "add_and_confirm": {
209
+ "description": "Adicionar o produto visível ao carrinho e abrir o resumo",
1697
210
  "steps": [
1698
211
  {"action": "click", "role": "button", "name": "Add to cart"},
1699
- {"action": "wait_for_text", "text": "Coffee added to cart"}
212
+ {"action": "wait_for_text", "text": "Coffee added to cart"},
213
+ {"action": "click", "role": "link", "name": "View cart"},
214
+ {"action": "assert_text", "role": "heading", "name": "Order summary", "expected": "Coffee"}
1700
215
  ]
1701
216
  }
1702
217
  }
1703
218
  }
1704
219
  ```
1705
220
 
1706
- O servidor também oferece `choose_next_action` para exploração passo a passo;
1707
- esse modo exige chamadas separadas do harness e costuma ser mais lento. Os
1708
- planos aceitam ações limitadas como clique por papel e nome acessível,
1709
- preenchimento, espera por texto, rolagem e reação idempotente a um comentário
1710
- único. Não aceitam JavaScript arbitrário, coordenadas nem seletores livres.
1711
- Valores digitados ficam no Playwright e são removidos do conteúdo enviado ao
1712
- Jev. Não coloque senhas, tokens ou outros segredos na descrição do fluxo ou
1713
- nos critérios.
1714
-
1715
- O contrato também oferece `select_option`, `hover`, teclas `Enter`/`Escape`/`Tab`,
1716
- asserções por elemento (`assert_text`, `assert_value`, `assert_visible`,
1717
- `assert_hidden`), escopo de alvo por `within` e índice zero-based com `index`,
1718
- além de alvos nomeados em iframes. `upload_file` aceita inputs, botões que abrem
1719
- o seletor nativo e dropzones; configure `JEV_BROWSER_UPLOAD_ROOT` para limitar
1720
- os arquivos locais que o MCP pode ler. `click` com `expect_download: true`
1721
- captura arquivos na pasta de artefatos dentro dos limites configurados, e
1722
- `audit_accessibility` roda axe nas tags WCAG 2.0/2.1 A/AA. A análise automatizada
1723
- não substitui auditoria manual. Um único plano usa o fast-path por padrão e não
1724
- chama a API Decisions. `options` pode limitar o
1725
- snapshot a `main` ou `dialog`, bloquear rastreadores, capturar erros de console
1726
- e rede e salvar screenshot/trace local em falhas. Os caminhos e limites estão
1727
- descritos no [guia completo](docs/jev-browser-mcp.md).
1728
-
1729
- A resposta informa o status, o plano escolhido, as ações executadas, a captura
1730
- final e se o resultado esperado foi confirmado. Consulte o [guia completo do
1731
- MCP Jev Browser](docs/jev-browser-mcp.md) para schemas, limites, tempos e
1732
- detalhes de segurança.
1733
-
1734
- O sidecar PostgreSQL de teste pode ser validado localmente com:
1735
-
1736
- ```bash
1737
- docker compose -f docker-compose.agentic.yml up -d --build
1738
- python scripts/smoke_mcp.py postgres --rows
1739
- python scripts/smoke_postgres_discovery.py --seed tests/fixtures/postgres_existing_schema.sql
1740
- docker compose -f docker-compose.agentic.yml down -v
1741
- ```
1742
-
1743
- `smoke_mcp.py postgres` lista as ferramentas publicadas; `--rows` chama
1744
- `pg_query`/`pg_explain` de verdade e exige que o gate recuse a leitura de linhas
1745
- sem consent `read_only_data`. Listar ferramenta não é exercitá-la: foi por isso
1746
- que um nome de argumento errado sobreviveu a probes verdes.
1747
-
1748
- `smoke_postgres_discovery.py` homologa AUD-034-DISCOVERY ponta a ponta —
1749
- introspecta o schema físico por MCP, aplica `UP`/`DOWN` no banco efêmero,
1750
- reintrospecta e compara os três snapshots. Sai com 0 somente quando a migration
1751
- reversível é verificada **e** a irreversível é bloqueada. As duas DSNs são
1752
- deliberadamente diferentes: `MCP_POSTGRES_DSN` é read-only e é a que a sessão
1753
- MCP usa, `MCP_POSTGRES_VALIDATION_DSN` é a única que aplica migration e só é
1754
- aceita para host allowlisted e banco efêmero.
1755
-
1756
- Novas APIs autenticadas: `GET/PUT /execution/policies`,
1757
- `GET /execution/executors` e `GET /mcp/capabilities`. `POST /threads` aceita
1758
- `execution_policy` por thread. Workflows de rollout:
1759
-
1760
- ```text
1761
- .github/workflows/agentic-offline.yml
1762
- .github/workflows/mcp-integration-smoke.yml
1763
- .github/workflows/agentic-graph-e2e.yml
1764
- .github/workflows/deepseek-harness-eval.yml
1765
- .github/workflows/executor-benchmark.yml
1766
- .github/workflows/skills-offline.yml
1767
- ```
1768
-
1769
- ### Skills por agente
1770
-
1771
- O catálogo canônico fica em `.agents/skills`. A descoberta retorna apenas
1772
- nome, descrição, origem e hash; corpo e referências são carregados somente
1773
- quando a política do agente autoriza a skill e a stack é compatível. O estado,
1774
- relatório e logs recebem somente `skill_catalog_digest` e hashes de evidência.
1775
- Campos `model`, `provider`, `tools`, credenciais e orçamento encontrados em
1776
- frontmatter são inertes: a rota continua sendo resolvida exclusivamente pela
1777
- política LLM/execution policy.
1778
-
1779
- Exemplo para ativar `frontend-design` no Designer UI e Remotion no Frontend/Code Review:
1780
-
1781
- ```json
1782
- {
1783
- "default": {"executor": "kernel", "skills": [], "skill_mode": "disabled"},
1784
- "agents": {
1785
- "agent_ui_designer": {"skills": ["frontend-design"], "skill_mode": "required"},
1786
- "agent_frontend": {"skills": ["frontend-design", "remotion-best-practices"], "skill_mode": "auto"},
1787
- "agent_code_review": {"skills": ["remotion-best-practices"], "skill_mode": "auto"}
1788
- }
1789
- }
1790
- ```
1791
-
1792
- Valide o bundle e seu lockfile sem rede:
1793
-
1794
- ```bash
1795
- python scripts/validate_skills.py
1796
- python -m pytest -q tests/test_skill_registry.py
1797
- ```
1798
-
1799
- As APIs autenticadas `GET /skills`, `GET /skills/{name}`,
1800
- `GET /skills/capabilities`, `GET/PUT /skills/policies` e
1801
- `POST /skills/validate` nunca retornam o corpo da skill. Instalação remota fica
1802
- desabilitada até existir preview/diff, scanner, aprovação HITL e atualização
1803
- atômica do lockfile. `find-skills` é reservado ao futuro curador administrativo
1804
- e não é liberado aos desenvolvedores.
1805
-
1806
- Crie o Environment protegido `agentic-e2e`, exija aprovação manual e cadastre
1807
- `OPENROUTER_API_KEY`, `CONTEXT7_API_KEY` e uma `TWENTY_FIRST_API_KEY` nova. A
1808
- ordem de rollout é: `agentic-offline`, `mcp-integration-smoke`,
1809
- `agentic-graph-e2e`, `deepseek-harness-eval` e, por fim,
1810
- `executor-benchmark`. Os três últimos são validações reais/manuais: não devem
1811
- ser tratados como aprovados somente porque a suíte offline passou. Consulte os
1812
- guias oficiais:
1813
- [Claude Code com OpenRouter](https://openrouter.ai/docs/guides/coding-agents/claude-code-integration),
1814
- [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python),
1815
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/python-sdk.md),
1816
- [Context7](https://github.com/upstash/context7),
1817
- [21st MCP](https://github.com/21st-dev/magic-mcp) e
1818
- [pg-mcp-server](https://github.com/stuzero/pg-mcp-server).
221
+ O plano é montado pelo LLM do harness, mas o Jev escolhe qual plano fornecido
222
+ deve executar usando o fluxo e o snapshot inicial. O Jev não cria ações, nomes
223
+ de controles ou valores de formulário. Valores de `type` são usados localmente
224
+ pelo Playwright e são removidos do texto enviado ao provedor e da evidência de
225
+ retorno. Use valores de teste; autenticação deve ficar no perfil de navegador
226
+ configurado.
227
+
228
+ O MCP também mantém `choose_next_action` para fluxos exploratórios em que o
229
+ harness precisa inspecionar e decidir entre ações uma por vez. Esse caminho é
230
+ mais lento porque exige uma nova decisão e uma nova chamada de ferramenta por
231
+ ação; prefira `run_browser_flow` quando os passos esperados puderem ser
232
+ descritos antes da execução.
233
+
234
+ ## Configuração
235
+
236
+ Edite `config/ui-testing.json`. URL, modelo do provedor, variável de credencial
237
+ e limites compartilhados ficam nos blocos `browser` e `jev`. As opções próprias
238
+ do pacote Node ficam no bloco opcional `jev_browser_mcp`, que não altera o
239
+ contrato lido pelo servidor Python. Preserve a estrutura completa exigida pelos
240
+ validadores.
241
+
242
+ `browser.mode` aceita `harness` ou `computer`:
243
+
244
+ - `harness` usa Chrome headless e perfil isolado, adequado a execuções do
245
+ harness e CI; o estado de autenticação é descartado ao final da chamada.
246
+ - `computer` abre o Chrome ou Edge instalado em modo visível e usa o diretório
247
+ persistente `browser.computer_user_data_dir`, separado por navegador. Não
248
+ reutiliza o perfil pessoal já aberto. Faça login uma vez nesse perfil; os
249
+ cookies permanecem nele entre chamadas.
250
+
251
+ `browser.max_flow_steps` limita a soma de passos declarados entre os planos e
252
+ `browser.max_text_entry_chars` limita cada valor digitado. O resultado contém
253
+ `status`, o plano escolhido, as ações executadas, a última captura acessível e
254
+ se o critério esperado apareceu. Quando existem asserções explícitas, `status`
255
+ também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
256
+ esse resultado e `expected_outcome_visible` continua descrevendo somente o
257
+ texto global. `incomplete` significa que nenhum critério foi comprovado;
258
+ confiança do Jev não substitui essa verificação.
259
+
260
+ `jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
261
+ seletores e condições. `JEV_BROWSER_UPLOAD_ROOT` libera uploads somente dentro
262
+ de uma pasta absoluta escolhida pelo operador.
263
+ `jev_browser_mcp.browser.max_upload_files`, `max_upload_path_chars`,
264
+ `max_upload_file_bytes` e `max_upload_total_bytes` limitam quantidade e tamanho.
265
+ Não configure a raiz como o disco inteiro ou a pasta home: use uma pasta de
266
+ fixtures dedicada.
267
+
268
+ Os limites de download ficam no mesmo bloco: `max_download_files`,
269
+ `max_download_file_bytes`, `max_download_total_bytes` e
270
+ `max_download_timeout_seconds`. `jev_browser_mcp.jev.max_accessibility_violations`
271
+ limita quantas descrições de violações axe entram no resultado; a contagem total
272
+ continua informada mesmo quando a lista é truncada.
273
+
274
+ O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
275
+ `block_trackers`, `capture_console_errors`, `capture_network_errors`,
276
+ `screenshot_on_failure` e `trace_on_failure`. Sem override, os padrões são lidos
277
+ de `jev_browser_mcp` em `config/ui-testing.json`: um plano candidato pula a chamada Decisions;
278
+ captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
279
+ de recursos ficam desligados. `snapshot_scope` aceita `body`, `main` ou `dialog`.
280
+
281
+ `block_trackers: true` bloqueia os domínios e tipos de recurso listados na
282
+ configuração (analytics, Hotjar, fontes externas e mídia). Isso pode alterar o
283
+ layout ou o comportamento do site, então a opção é desligada por padrão.
284
+
285
+ Com `capture_console_errors` e `capture_network_errors`, o retorno traz
286
+ `console_errors` e `network_failures`, limitados em quantidade e tamanho.
287
+ Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
288
+ fragmentos, valores de formulário e nomes de arquivo são removidos ou
289
+ sanitizados. Em falhas, `screenshot_on_failure` salva screenshot local e retorna
290
+ `screenshot_path`. `trace_on_failure: true` também grava um `.zip` compatível
291
+ com o Trace Viewer do Playwright e retorna `trace_path`. O diretório padrão é
292
+ `~/.cache/orquestrador/jev-browser-artifacts`; `JEV_BROWSER_ARTIFACT_DIR` pode
293
+ substituí-lo por uma pasta absoluta fora do pacote. Screenshots e traces podem
294
+ conter dados visíveis da aplicação: mantenha o diretório local protegido e
295
+ compartilhe os arquivos somente se o teste permitir.
296
+
297
+ `harness_browser` e `computer_browser` aceitam `chrome` ou `msedge`.
298
+ `computer_user_data_dir` deve ficar fora do repositório e conter `{browser}`;
299
+ o diretório persistente armazena dados de login e é resolvido sob a pasta home
300
+ do usuário quando começa com `~`. O Playwright MCP fica desabilitado até
301
+ `ORQUESTRADOR_MCP_PLAYWRIGHT_ENABLED=1` ser configurado no ambiente do harness.
302
+
303
+ Defina o valor secreto na variável indicada por `jev.credential_env`, usando o
304
+ secret manager ou ambiente do processo que inicia o harness. Não grave a chave
305
+ em `config/ui-testing.json`. `provider_url` é o endpoint HTTPS completo da API
306
+ Decisions. O cliente não
307
+ segue redirects e recusa URL com credencial, query string ou fragmento. O Jev
308
+ fica indisponível quando a política de MCP está em modo offline.
309
+
310
+ Na primeira execução, aqueça uma vez o cache local do pacote declarado em
311
+ `browser.playwright_mcp_package` com `npx --yes <pacote> --help`. O MCP inicia
312
+ depois com `--offline`, evitando uma consulta ao registry npm em cada fluxo.
313
+ Quando a versão configurada mudar, aqueça o novo pacote uma vez.
314
+
315
+ ## Desempenho e evidência
316
+
317
+ O pacote Node usa a decisão remota do Jev para escolher entre múltiplos planos.
318
+ Com exatamente um plano e `fast_path` ligado (padrão), executa esse plano sem
319
+ chamar a API Decisions; `jev_decisions` fica em zero. Inclua o fluxo completo em
320
+ um plano candidato para evitar chamadas separadas ao harness; seleção,
321
+ navegação, ações e asserções ficam em uma chamada MCP. O servidor Python
322
+ `mcp_servers/jev_browser_server.py` mantém seu fluxo próprio e usa uma decisão
323
+ remota do Jev por chamada. A sessão Playwright fecha antes de o MCP Python
324
+ retornar. O perfil do modo `computer` preserva o login para chamadas seguintes;
325
+ nesse servidor o processo e a janela não são reutilizados. O pacote Node mantém
326
+ o browser aquecido até o harness encerrar o processo. O snapshot enviado ao Jev
327
+ e devolvido ao harness remove o rodapé e, se ainda exceder o limite, mantém o
328
+ início e o fim da captura com um marcador de truncamento. `snapshot_scope` pode
329
+ limitar a captura a `main` ou `dialog`; iframes nomeados contidos nesse escopo
330
+ também podem ser incluídos.
331
+
332
+ A chamada composta aquecida deve ficar dentro da meta de 20 segundos em páginas
333
+ que respondem normalmente; a inicialização fria, páginas lentas, MFA e conteúdo
334
+ sob demanda podem excedê-la. O MCP devolve `total_ms`, `browser_session_ms`,
335
+ `navigation_ms`, `initial_snapshot_ms`, `jev_decision_ms` e `browser_plan_ms`
336
+ para localizar o custo. O teto observado em um fluxo sintético local anterior
337
+ foi 6,411 s; isso não mede o Instagram nem garante o mesmo tempo em outros sites.
338
+
339
+ O snapshot inicial, o fluxo, o resultado esperado e as descrições dos planos
340
+ são enviados ao endpoint Decisions quando há mais de uma opção. Com fast-path,
341
+ um plano não gera chamada remota. Os passos, valores digitados, valores
342
+ esperados pelas asserções, caminhos e conteúdo dos arquivos não são enviados.
343
+ Não inclua segredos em `flow`, `expected_outcome` ou nas descrições. A resposta
344
+ retorna o plano escolhido quando houver decisão, custo/confiança do provedor
345
+ quando disponíveis e o snapshot final sanitizado. O fluxo passa quando o texto
346
+ esperado aparece no snapshot ou quando todas as asserções declaradas passam.
347
+
348
+ `network_idle` é uma espera limitada e opcional; páginas com polling ou conexões
349
+ contínuas podem atingir o timeout. Prefira `wait_for_text` e asserções de
350
+ elemento quando houver um sinal de interface específico.
351
+
352
+ Consulte a [introdução do Jev](https://docs.typesafe.ai/introduction) e o
353
+ [tutorial da API Decisions no OpenRouter](https://openrouter.ai/docs/guides/community/jev-tutorial)
354
+ para os tipos de resposta e autenticação.