Scan do Motor — 2026-10-11 relatório do workflow wf_a0cfad9b-369 sobre Motor main 69e0b28

SHA-256 da fonte: cca54076 (completo: cca540766f9bfd0628dc5e86b34f0ee5311d11db4ab7d9f116c9930799646f57)
Tamanho do .md: 203.3 KB · 1 253 linhas
Renderizado em 2026-10-11T09:35:38Z no sandbox motor

Relatório final — Scan do Motor

Data: 2026-10-11 · Repositório: PedroPaduelo/motor (sandbox motor, /workspace) · HEAD auditado: 69e0b28

Produção: motor-be-ws e motor-worker-ws rodam 6fc7d65. Conferido com git nesta revisão: o HEAD está só 2 commits à frente, ambos de frontend (markdown do chat). O backend auditado é idêntico ao de produção, e o fix do Parar (020aa42) já está em produção. Uma métrica de scanner fala em "4 merges à frente"; vale a conferência acima.

0. Metodologia e como ler

O scan cobriu 8 dimensões: loop do agente e SDK; tempo real; tools e system prompt; agente e subagentes; workflows; banco, cache, threads e escala; segurança; qualidade e modularidade. Cada dimensão descreveu como o Motor funciona, respondeu às perguntas do dono com evidência arquivo:linha, mediu o que dava para medir e listou perguntas abertas. Depois vieram uma pesquisa de boas práticas (Anthropic como referência, mais mercado e tempo real) e uma investigação das lacunas (SDK nativo e socket.io; capacidade e topologia de produção; modularidade de tools e subagentes; workflows frente ao mercado; webhooks, rotas e versionamento de contratos).

Revisão cética. Todos os achados que os scanners classificaram como crítica ou alta passaram por revisores adversários, que procuraram contra-evidência no código, nos docs e nos logs de produção. Os achados média, baixa e info não passaram por essa rodada.

Status usados:

Severidade: crítica, alta, média, baixa, info. Esforço: P (horas a 1 dia), M (dias), G (semana ou mais). Dentro de cada área, a ordem é do mais grave ao mais leve, pela severidade revisada. As linhas de código citadas referem-se ao HEAD 69e0b28.

Limites. Produção estava ociosa nas janelas de log lidas (2,66 req/min numa janela; 115 requests, todos 200, noutra). Não houve teste de carga: números de capacidade são estimativas por leitura de código e configuração. Segredos e .env de produção não foram lidos (aparecem mascarados no painel). Tudo que é estimativa ou incerto está marcado.

1. Sumário executivo — resposta direta às 9 perguntas

P1. O loop atual é o melhor? Vale trocar para o SDK nativo da Anthropic (@anthropic-ai/sdk, API Messages), sem Agent SDK nem Claude Code? Como ficaria? O loop atual é bom e cheio de cicatrizes pagas: usage somado à mão (existe mesmo em falha), parcial gravado antes de classificar o erro, aborto com nome AbortError mais guarda de 5s, reparo de tool-calls. Seria o melhor se o Motor precisasse de abstração multiprovider. Mas o Motor fala um único formato: Anthropic Messages via Load Balance, com model: "proxy-managed" sempre e até 362 tools por turno. Nesse cenário o pacote ai v5 cobra pedágio sem entregar valor: aborto por nome mágico, retry escondido, cache_creation perdido, breakpoints de cache montados por interceptor que reparseia o body a cada step, buffer acumulado manual no prepareStep, replay de thinking inoperante. Recomendação: migrar para o SDK nativo de forma incremental (tradutor de histórico, runner nativo atrás de flag por setup, shadow do wire, rollback por flag), nunca big-bang: o contrato byte a byte com o LB precisa ser preservado e o histórico persistido está em formato do AI SDK. Desenho na seção 5.1.

P2. O Motor aguenta alto volume, com muitas pessoas usando ao mesmo tempo? Não, como está. O núcleo foi desenhado para várias réplicas (lock distribuído, mailbox durável, drain atômico), mas hoje roda 1 réplica de API e 1 de worker, e há travas medidas: (1) banco: o postgresql.conf de produção mostra max_connections = 100, contra um pool pretendido de 1000 por processo (2000 com API e worker; o runbook pede ≥ 2100). Falta confirmar com SHOW max_connections, porque o container pode sobrescrever na linha de comando; (2) custo e escala: 999 steps por turno por padrão (um turno real queimou cerca de 14 milhões de tokens até overloaded_error), nenhum timeout padrão (turnTimeoutMs = 0), fan-out de subagentes sem teto por árvore; (3) gates por processo: rate limit, concorrência por org e circuit breaker multiplicam por réplica; e a 2ª réplica hoje quebraria Parar, "rodando", eventos ao vivo e fim de sessão; (4) polling: cerca de 3.000 req/min só de polling com 100 abas no pico, sem cache, e um rate limit de 30 req/min por IP que uma única aba com o modal de subagente já estoura. Estimativa sem teste de carga: ~10–30 conversas simultâneas se o banco estiver mesmo em 100 conexões; ~50–80 turnos simultâneos por réplica, com teto duro de 100 turnos por org e por processo, se o banco seguir o runbook. Recomendação: ondas 1 e 2 do roadmap (tetos, timeout, preço real, abort, isBusy e broadcast distribuídos, banco e Redis dimensionados) e um teste de carga 10→50→100 turnos antes de prometer volume.

P3. Como funciona o tempo real hoje (WebSocket, polling, SSE)? Como ficaria tudo via WebSocket com socket.io, sem polling e sem SSE? Hoje há dois canais WebSocket nativos (thread e run) em GET /ws, com handshake e replay; 9 pontos de polling REST (lista de threads 8s; árvore e graph 3–5s; modal de subagente 2s + 3s; lista de runs 5s; log 4s; passo 3s; saúde 30s); e zero SSE na camada do app — o único SSE é interno, entre o SDK e o proxy. O chat não faz streaming token a token: manda o parcial uma vez por step. Com 2 réplicas o tempo real quebra, porque broadcaster, isBusy, abort e fim de sessão vivem na memória de cada processo. Recomendação: gateway socket.io com Redis adapter (Redis Streams adapter se quiser recuperação nativa de conexão), salas por thread, run, usuário e org montadas pelo servidor a partir da identidade autenticada, seq por sala com replay pelo banco, acks do cliente para o servidor e catálogo de eventos versionado. Migração em 7 fases (F0–F6), cada uma desligando um polling, com REST como fallback. Desenho na seção 5.2. Decisão do dono: socket.io (pedido) ou evoluir o ws nativo com Redis pub/sub (seção 7).

P4. O código está modular e bem escrito? As integrações estão bem feitas? Há contradições, vazamento de informação ou problemas com as tools? Em grande parte sim, acima da média para o tamanho (backend com cerca de 57 mil linhas, frontend com cerca de 73 mil). Separação clara entre transporte (ws), execução (engine), estado (persistence) e tools; os cabeçalhos explicam o porquê; o contrato Motor↔LB é o melhor documento do repositório; MCP usa o SDK oficial v2 com pool consciente; o frontend tem zero as any (o backend tem 60). Contradições doc×código. Confirmadas pelos revisores: o aviso de colisão de tools diz "manteve o primeiro" e o código mantém o último; o abort entre réplicas é documentado ("a dona escuta e aborta") e não tem assinante. Apontadas sem revisão: cabeçalho generateText × código streamText; append da thread "soma" × "substitui"; defaults de cache do schema × contrato do LB; prompt "sem MCP externo"; "token na chave do pool"; header 16 × código 100; docs/operacao.md defasado. Vazamentos. Snapshot da config com segredos em claro gravado a cada turno no Postgres e no Redis; SSRF em 4 superfícies (webhooks, /api/mcp/test, test-connection, MCPs por thread); config da org default herdada por qualquer outra org (latente hoje, crítico com multi-org); ••• ecoado pela UI vira header real enviado ao MCP. Tools. Resultado sem teto; required decorativo para JSON válido com campo faltando; descrição de MCP entra verbatim no prompt; notificação de subagente entra com moldura de autoridade; 362 tools por turno no padrão.

P5. Como as tools são montadas, interpoladas e enviadas ao modelo, e como o system prompt é gerado? Como modularizar e ter domínio? Por turno: MCPs conectados em paralelo via pool (listTools) → filtro opcional mcps[].tools → merge com as 23 nativas em ordem alfabética (prefixo byte-idêntico para o cache) → recorte pela toolPolicy do setup. O Record inteiro vai no tools de cada step, sem limite de tamanho. O system prompt é concatenação de 3 camadas: base (system-prompt.md, 25,9 mil chars) + systemPromptAppend efetivo (org + persona do setup, ou override que substitui) + bloco de voz. Interpolação só em 3 pontos: ${VAR} no chat-config.json em disco, token de MCP virando Authorization: Bearer, e expressões ${...} de workflow; não há motor de template no prompt. Custo medido: nativas ≈ 9,4 mil tokens + system ≈ 6,5 mil = ~15,8 mil tokens por request antes de qualquer MCP; com os MCPs de produção, estimativa de 80–214 mil tokens só no bloco de tools (premissa de 150–400 tokens por tool; não medido em produção), numa janela de 230 mil. Domínio hoje: só contagens no log e a config crua. Recomendação: (1) builder de prompt por camadas versionadas (nome, hash, tamanho) com semântica explícita de soma ou substituição; (2) registro com namespace servidor__tool; (3) núcleo pequeno com carregamento sob demanda (tool search ou activeTools por step); (4) painel de inspeção por turno (hash de cada camada, tools finais com origem e tamanho, breakpoints, tokens de cache); (5) tetos de descrição, schema, resultado e steps; (6) validação de argumentos. Desenho na seção 5.3.

P6. A comunicação agente↔subagentes, que é assíncrona, é eficiente, sem overhead? Está claro para a IA como abrir subagente, escolher o setup e conduzir o processo? O desenho é bom e assíncrono de verdade: agent_spawn cria a thread filha e retorna na hora; o filho roda com lock próprio, em paralelo real; o resultado volta por push na mailbox do pai com exactly-once (notifiedParentAt); N notificações viram 1 turno; com o pai rodando, entram no meio do turno; o spawn é idempotente por toolCallId. Overhead real: o finalText do filho volta inteiro e fica no histórico do pai até a compactação; largura e profundidade são ilimitadas (D4/D6) sem orçamento por árvore; cancelar o pai não para filhos e netos; o reaper está desligado em produção, então um crash deixa o pai esperando para sempre. Clareza para a IA: descrições e seção do system prompt são boas (quando paralelizar, tarefa autocontida, setup_list antes de setupId, não esperar porque a notificação chega sozinha), mas faltam o limite de tamanho do retorno e o aviso de que filhos paralelos escrevem no mesmo workspace; e "o filho herda o setup" não traz a persona. Recomendação: contrato de delegação com retorno capado (4–8 mil chars + referência ao threadId), tetos por árvore, abort em cascata, reaper ligado em uma réplica, persona e escopo de escrita explícitos (seção 5.4).

P7. Está eficiente em banco de dados, em threads (concorrência) e em cache? Parcialmente. Banco: transações curtas, bons índices, mailbox com FOR UPDATE SKIP LOCKED; mas um turno simples custa ~20–25 statements, a autenticação faz 2 leituras por request sem cache, cada appendEvent publica o JSON num canal Redis sem consumidor, events de thread ativa cresce sem purga e o pool (1000 por processo) não casa com o max_connections = 100 lido em produção. Concorrência: lock por thread correto (Redis SET NX + heartbeat); mas rate limit, gate por org, circuit breaker, isBusy, abort e broadcaster são locais ao processo, e mapas em memória crescem sem expiração. Cache: janela quente de events bem desenhada; porém o convo no Redis é write-only (escrito a cada turno, nunca lido) — e um turno sem o convo em memória (restart seguido de um workflow que acorda a conversa, ou outra réplica) manda ao LLM só as mensagens novas, sem o histórico. Fila e cache dividem a mesma instância Redis de 512 MB (decisão registrada). Recomendação: fallback do convo (memória → Redis → banco), remover ou aproveitar o publish, cache de autenticação, retenção de events, gates em Redis, confirmar e dimensionar max_connections (ou PgBouncer) antes da 2ª réplica.

P8. O que é o workflow no Motor e como ele funciona? Uma receita JSON imutável por versão (motor.workflow/v1) com 11 tipos de passo: 5 expansores, que materializam filhos (map, verify, tournament, loop, switch), e 6 executáveis no worker (agent, transform, http, wait, human, workflow). No arranque, o run congela a versão, a base técnica da org e um snapshot por setup citado (D002). O run tem 3 tetos: usd (mole), maxSteps (1–10.000, contando filhos) e wallClockMs (pausas e esperas humanas descontadas). O reconciliador é o cérebro: único que enfileira passo, com lease no Postgres; o Postgres é a verdade e o BullMQ (5 filas de trabalho + wf-ping) é transporte; 3 barreiras impedem execução dupla. O passo agent reusa o runTurn com saída validada; pausar é gracioso em 3 estados; cancelar é cooperativo, com fechamento imediato se ninguém está vivo; tudo se propaga a sub-runs. Eventos saem em 3 trilhas (log append-only com seq, filas, runbus efêmero para a tela) e a UI mostra 1 cartão por fan-out. Fronteira deliberada: agent_spawn é quando o LLM decide paralelizar; workflow é quando a definição decide. Dentro de um passo, tools de subagente e de governança são negadas.

P9. Quais são as melhores práticas de mercado, com a Anthropic como referência, para multiagentes e runs de workflow, e onde o Motor está? O Motor cobre as 5 categorias da Anthropic — chaining, routing, paralelização (seções e votação), orchestrator-workers e evaluator-optimizer — e está à frente da média em reconciliação pelo banco, pause/cancel cooperativo com escape, custo de sub-run consolidado no pai e no modo assíncrono com notificação, que a Anthropic ainda lista como evolução futura. Falta: carregar tools sob demanda (tool search, defer_loading) — todo passo paga o catálogo inteiro, e um passo "trivial" custou US$ 0,25 e ~83 mil tokens; TTL de 1h de cache declarativo por setup; regras de cardinalidade e um "amortecedor" de delegação para a orquestradora; contrato de delegação completo (fronteiras, esforço, parada); evals reais com juiz LLM; tetos determinísticos por árvore; e observabilidade de cache e latência (TTFT, taxa de acerto). Tabela completa na seção 4.

Recomendação geral. O Motor é um runtime bem arquitetado, com dívidas de escala e segurança mapeadas e, nas mais graves, confirmadas. Ordem sugerida: (i) estancar custo e segurança (tetos, timeout, preço real, SSRF, segredos, reaper); (ii) destravar várias réplicas (abort, isBusy, broadcast e sessão distribuídos, banco e Redis dimensionados, cross-tenant antes de liberar troca de org); (iii) migrar o estrutural (SDK nativo, socket.io, tools sob demanda, painel de inspeção) com flags e shadow, sem big-bang. Nada exige reescrever o Motor: exige completar o que o desenho já prevê.

2. Como o Motor funciona hoje

2.1 O turno e o loop

Um turno é um ator por thread: cada thread tem lock próprio (uma conversa nunca espera outra), uma mailbox que agrupa tudo o que chegou num turno só e um loop de agente que chama o LLM em steps, via streaming SSE, sempre contra o proxy Load Balance em formato Anthropic Messages. O Motor nunca sabe provider, modelo real ou credencial: envia model: "proxy-managed" para POST {customUrl}/v1/messages, e o LB transpila, autentica e devolve SSE canônico (be/docs/contrato-load-balancer.md).

Passo a passo no caminho do chat:

  1. Recebimento e fila. O WS recebe a mensagem e o dispatcher faz submit (be/src/engine/dispatcher.ts:265): rate limit por (org, usuário) só para submits externos (60/min por processo), push na mailbox (ilimitada por desenho, D4) e pump sem esperar. O drain é atômico: tudo o que estava pendente vira um turno (dispatcher.ts:143-151).
  2. Lock e preparo. O pump pega o lock da thread no registry (memória ou Redis com TTL de 30s e heartbeat de 10s) e chama runTurn (be/src/engine/turn-runner.ts:427). O prepareTurn lê a thread e a config efetiva (global < setup < thread) e congela tudo numa linha Turn, para auditoria e replay.
  3. Compactação na fronteira, antes do LLM (turn-runner.ts:524-548). O gatilho olha a ocupação medida no fim do turno anterior (thread.metadata.contextTokens). Se passou de window × compactAt (padrão 230 mil × 0,9), o compactador resume o histórico antigo, persiste um evento compaction e o LLM passa a ver [resumo] + eventos novos. Falha aqui nunca derruba o turno; após 3 falhas seguidas, escala alerta.
  4. Bootstrap do agente (turn-runner.ts:650 e be/src/agent/bootstrap.ts). Monta o model (placeholder proxy-managed), o system prompt (base + apêndice do setup + voz), conecta os MCPs em paralelo (falha de um não trava os outros) e soma as tools nativas (subagentes, workflows, setups). A toolPolicy do setup recorta as tools. Um metadata.user_id estável por conversa maximiza o cache no proxy.
  5. Persistência da entrada (turn-runner.ts:704-798). Primeiro as notificações de subagentes (uma mensagem sintética combinada), depois cada mensagem do usuário, cada uma como evento append-only. Pré-cheque do orçamento mensal da org (Redis, em USD e/ou tokens): estourou, aborta antes de chamar o LLM.
  6. O loop (be/src/agent/loop.ts:402, runAgent). Monta messages com breakpoints de cache (system + pontos do histórico, até 4 por request; tools via interceptor), poda ou reanexa thinking antigo, reserva 1 slot no gate da org e chama streamText do pacote ai v5 com stopWhen: stepCountIs(maxSteps ?? 999), maxRetries ?? 3 e o abortSignal do turno. A cada step: o modelo gera texto e/ou tool calls, as tools executam, onStepFinish soma o usage à mão e emite onStep para a timeline ao vivo. Entre steps, o prepareStep checa o orçamento e drena notificações novas da mailbox, reenviando um buffer acumulado. O aborto tem rede dupla: reason com nome AbortError (o único que o SDK reconhece) e uma guarda de 5s que encerra à força.
  7. Persistência da resposta e fechamento (turn-runner.ts:1091-1271). gravarRespostas planeja os eventos por passo (reasoning só para exibição → mensagens limpas com tool-calls reparadas → step_done), grava, registra o usage no bucket de custo da org, mede a ocupação pelo usage do último step e fecha o Turn (done, ou errored se um turno humano saiu vazio). Em falha no meio, o parcial dos steps fechados é gravado antes de classificar o erro (orçamento, cancelamento, timeout, provider 4xx/5xx, MCP, interno).

Caminho dos workflows: runAgentStep (be/src/engine/agent-step.ts:534) é uma casca sobre runTurn sem mailbox: cria ou continua uma thread headless, segura o lock durante as rodadas (original + reparos de schema, padrão 1), acumula deltas token a token, soma usage e custo e devolve saída validada ou falha tipada (inclui timeout próprio e mcp_indisponivel antes de gastar).

flowchart LR
    WS[WS: user_message] --> SUB[dispatcher.submit<br/>rate limit + mailbox.push]
    SUB --> LOCK[pump: lock da thread<br/>Redis TTL 30s + heartbeat]
    LOCK --> PREP[prepareTurn<br/>config global, setup, thread<br/>congelada no Turn]
    PREP --> COMPACT{ocupação acima de<br/>window x compactAt?}
    COMPACT -->|sim| SUM[compactador resume<br/>evento compaction]
    COMPACT -->|não| BOOT[bootstrap<br/>model + system + MCPs + nativas<br/>toolPolicy]
    SUM --> BOOT
    BOOT --> PERSIST[persiste notificações e<br/>user_messages + pré-cheque de orçamento]
    PERSIST --> LOOP[runAgent: streamText v5<br/>até 999 steps · prepareStep<br/>orçamento + drain no meio do turno]
    LOOP --> SAVE[gravarRespostas por passo<br/>usage + contextTokens]
    SAVE --> DONE[finishTurn done ou errored<br/>evento final + limpa o ao vivo]

Arquivos-chave: be/src/agent/loop.ts, be/src/engine/turn-runner.ts, be/src/engine/dispatcher.ts, be/src/engine/agent-step.ts, be/src/agent/bootstrap.ts, be/src/provider/anthropic.ts, be/src/agent/messages.ts, be/src/agent/compaction.ts, be/src/engine/response-events.ts, be/src/engine/org-concurrency.ts, be/src/engine/cost-tracker.ts, be/src/engine/registry.ts, be/src/engine/registry-redis.ts, be/docs/contrato-load-balancer.md.

2.2 Tools e system prompt

O prepareTurn (be/src/engine/turn-runner.ts:346) calcula a config efetiva com loadEffectiveConfig (be/src/setups/service.ts:763): global da org (OrgConfig no Postgres) < setup da conversa (Agent.configOverride + persona + toolPolicy) < override legado da thread (metadata.config), fundidos por resolveEffectiveConfig (be/src/config/thread-config.ts:473). O bootstrapTurn monta o que o modelo vai ver:

  1. buildNativeTools: 23 tools in-process (4 de subagente, 4 de setup, 15 de workflow);
  2. bootstrapAgent (be/src/agent/bootstrap.ts:51): conecta os MCPs da config em paralelo via pool (acquireMcp, be/src/tools/mcp-pool.ts:136) e filtra cada um pela allowlist mcps[].tools (filterMcpTools, be/src/tools/registry.ts:28);
  3. buildMcpTools (registry.ts:67): junta tudo num Record único com as nativas, em ordem alfabética, para o prefixo ficar byte-idêntico e o cache render;
  4. applyToolPolicy (registry.ts:125): recorta pela toolPolicy do setup.

O Record final vai inteiro, a cada step, no tools do streamText (be/src/agent/loop.ts:499); o SDK serializa cada tool como {name, description, input_schema} sem validar nem limitar o tamanho.

O system prompt é montado no bootstrapAgent (linhas 66-95) em camadas: base (cfg.systemPrompt inline ou o arquivo be/system-prompt.md, 25,9 mil chars, lido do disco a cada turno) + systemPromptAppend efetivo (append da org + persona do setup concatenados, ou um override da thread ou do setup que substitui tudo) + bloco de voz (só no modo voz, be/src/agent/voice-prompt.ts:88). Entra como primeira message de role system (buildMessages, be/src/agent/messages.ts:72) com cache_control; o interceptor do provider (be/src/provider/anthropic.ts:178) pode marcar a última tool com breakpoint de cache. Durante o turno, o prepareStep (turn-runner.ts:956) injeta notificações de subagentes e workflows como message sintética de role user ([NOTIFICAÇÃO DO SISTEMA], be/src/engine/mailbox.ts:232). No fim, o usage do último step mede a janela (turn-runner.ts:1180) e pode disparar a compactação no turno seguinte.

flowchart TD
    CFG[loadEffectiveConfig<br/>org, setup, thread] --> NAT[buildNativeTools: 23 nativas]
    CFG --> MCP[acquireMcp em paralelo<br/>listTools por servidor]
    MCP --> FIL[filterMcpTools<br/>allowlist mcps.tools opcional]
    FIL --> MERGE[buildMcpTools<br/>Record único em ordem alfabética]
    NAT --> MERGE
    MERGE --> POL[applyToolPolicy do setup]
    CFG --> SYS[system: base + append efetivo + voz]
    POL --> REQ[streamText a cada step<br/>system + messages + tools inteiras]
    SYS --> REQ

Interpolação existe em três lugares distintos: ${VAR} de ambiente só no chat-config.json em disco (be/src/config/load.ts:57; não vale para config no banco); token de MCP virando Authorization: Bearer no resolver (thread-config.ts:329-342); e ${...} das expressões de workflow (interpretador próprio, fora do chat).

2.3 Agente e subagentes

O pai chama agent_spawn (title, task e, opcionalmente, systemPrompt, tools e setupId). O orchestrator cria a thread filha (kind: subagent) num único INSERT (be/src/engine/orchestrator.ts:234-249), herdando metadata.config do pai — mesma sandbox, mesmos MCPs, mesma rota —, mas nunca o system prompt do pai (orchestrator.ts:175-184). Com setupId explícito, valida o setup (ativo, não arquivado, visível) e injeta a persona uma vez no systemPromptAppend do filho; sem setupId, o filho só herda o agentId e roda sem a persona (orchestrator.ts:186-223). O spawn retorna logo após dispatcher.submit com trigger spawn; a tarefa vira a primeira user_message do filho, que roda com thread e lock próprios, em paralelo real.

Quando o filho termina um turno com a mailbox vazia, onTurnEnd faz o claim exactly-once (updateMany onde notifiedParentAt é nulo, orchestrator.ts:317-321), monta o SubagentResultItem (status, finalText integral, steps, duração, tokens, irmãos ainda rodando) e empurra na mailbox do pai. Pai ocioso: abre turno com trigger subagent_notification. Pai rodando: o prepareStep drena só as notificações e injeta no meio do turno. N notificações juntas viram 1 turno (drain atômico com FOR UPDATE SKIP LOCKED, be/src/engine/mailbox-db.ts:98-146). Falha vira notificação errored; pai deletado entre claim e push dispara rollback com evento lost_notification; crash de processo depende do reaper, que é opt-in e está desligado em produção.

sequenceDiagram
    participant Pai
    participant Mail as Mailbox do pai
    participant Filha
    Pai->>Filha: agent_spawn: thread filha + primeira user_message
    Note over Pai: retorna na hora, sem esperar
    Filha->>Filha: turnos próprios, com lock próprio
    Filha->>Mail: onTurnEnd: claim exactly-once + subagent_result
    alt pai ocioso
        Mail->>Pai: turno novo, trigger subagent_notification
    else pai rodando
        Mail->>Pai: prepareStep drena e injeta no meio do turno
    end

Na UI, o filho aparece inline na timeline do pai. O desenho prevê fan-out dos eventos do filho para a sala da raiz (be/src/ws/handler.ts:234-251), mas na prática o progresso do filho não chega à sala do pai — só spawn e result, emitidos direto (achado confirmado em 3.2) —; o modal de subagente compensa com polling de 2s.

2.4 Workflows

Um workflow é uma receita JSON imutável por versão (schema: "motor.workflow/v1"; be/src/workflows/definition.ts, 1911 linhas, 11 tipos de passo). Cada versão publicada vira uma WorkflowVersion. O run congela versionId, a base técnica da org (orgConfigSnapshot), um snapshot por setup citado (setupSnapshot.__porSetup[setupId]) e o setup padrão (__padrao) quando algum passo de IA fica sem setupId (D002, be/src/workflows/runs.ts:57-179, 252-267, 404-436). O input é validado contra o schema da versão antes de qualquer cobrança, e a idempotencyKey é única por org (repetir o POST devolve o mesmo runId).

Os 11 tipos. Cinco expansores, que materializam filhos em vez de ir para a fila: map, verify, tournament, loop, switch (be/src/workflows/expanders.ts). Seis executáveis, que rodam no worker: agent, transform, http, wait, human, workflow (be/src/workflows/executors.ts). Só agent cobra dinheiro: usa a ponte runAgentStep com saída estruturada validada (1 reparo) e propaga usage e custo na mesma transação que fecha o passo. O loop roda uma iteração por vez; tournament tem bye e empate (A vence); switch decide no tick e marca os outros ramos como skipped.

Orçamento por run: usd (mole; 0 = tolerância zero), maxSteps (1–10.000 contando filhos; o expansor falha com fanout_excede_maxsteps antes de criar) e wallClockMs (espera humana e pausas descontadas por união de janelas).

Ciclo de vida. O reconciliador é o único que enfileira passo (be/src/workflows/reconcile.ts): cada tick segura o run por lease no Postgres (30s, claim atômico com a condição do run dentro do UPDATE), materializa o que desbloqueou, enfileira o que está pronto (em lote, respeitando a concorrência do fan-out, 1–200, padrão 100) e fecha o run. Quem executa é o motor-worker-ws, processo separado, com workers dedicados às filas wf-reconcile, wf-step-ai, wf-step-det, wf-notify, wf-maintenance e wf-ping. Três barreiras impedem execução dupla: jobId determinístico (step:<stepId>:<attempt>), claim condicional e @@unique([stepId, attempt]); e um passo com output gravado nunca reexecuta. A recuperação é por batimento: tentativa sem heartbeat por 45s vira lost e volta para pending; run sem progresso por 120s fecha como run_travado.

Pausar, retomar, cancelar. Pausar é gracioso: running → pausing → paused (a porta fecha antes de contar o que está em voo). Retomar restaura waiting se ainda há portão humano aberto. Cancelar é cooperativo: se há worker vivo, marca a bandeira e espera; se ninguém está vivo, fecha na hora. Tudo se propaga recursivamente a sub-runs; o custo do filho sobe ao pai uma vez só.

Eventos e tela. Três trilhas: workflow_events (log append-only, seq monotônico por UPDATE … RETURNING), filas BullMQ duráveis e runbus:* no Redis Pub/Sub (efêmero, para o WS). O canal WS do run coalesce step_partial a 2 Hz e libera até 8 passos em foco. A tela agrupa os filhos de um fan-out num cartão com histograma: "40 revisores" é uma linha só.

Fronteira com subagentes (intencional). Dentro de um passo, as tools de subagente são negadas (o filho sobreviveria ao run e furaria o orçamento; paralelismo dentro de workflow é map/fan-out orçado), e as de governança (workflow_run, pause, resume, decide, cancel) também (um passo não aprova o próprio portão).

flowchart TD
    AUT[autoria: create, draft, add_step, validate] --> PUB[publicação: versão imutável]
    PUB --> ADM[startRun: valida input<br/>congela setups e org<br/>idempotencyKey]
    ADM --> TICK[reconciliador: tick com lease de 30s<br/>materializa e enfileira]
    TICK --> AI[wf-step-ai: passo agent<br/>runAgentStep com saída validada]
    TICK --> DET[wf-step-det: transform, http, wait]
    TICK --> HUM[human: portão, run em waiting]
    AI --> BAR[barreira no banco<br/>expected e completed]
    DET --> BAR
    HUM --> BAR
    BAR --> TICK
    BAR --> AVISO[aviso para a conversa de origem]

2.5 Tempo real

Há dois canais WebSocket nativos (sem socket.io e sem SSE no app) numa rota só, GET /ws (be/src/plugins/websocket.ts:94): ?thread= vai para o canal de chat (be/src/ws/handler.ts) e ?run= para o canal de workflow (be/src/ws/run-channel.ts). A identidade nunca vem da query: o hook de auth (be/src/http/auth.ts:7-27) autentica no upgrade por bearer de máquina, cookie de sessão da Conta ou ?ticket= de uso único, e cada socket ganha um connId.

Canal de thread. O front abre 1 socket por thread ativa (fe/src/features/thread-runtime/model/use-thread-runtime.ts:90) com o cliente fe/src/api/ws.ts: backoff exponencial de 1s a 15s, sem jitter; fila de envio de 100 itens/256 KB; watchdog que fecha o socket após 60s sem frame. Ao conectar, o servidor resolve a thread, entra na sala, manda connected e reemite tudo: replay do histórico persistido mais o turno em andamento, vindo do buffer vivo no Redis. Durante o turno, o engine emite para a sala da thread e para a da raiz. O chat não faz streaming token a token: assistant_partial sai uma vez por step com o texto inteiro; deltas existem só como opção usada pelo executor de workflow. O thread_busy (início e fim de turno, e batimento de 25s enquanto ocupado) vai só para os sockets do dono, e a sidebar mantém um segundo socket por aba só para ouvi-lo.

Canal de run. O passo roda no worker e o socket está na API, então os frames atravessam processos por Redis Pub/Sub (be/src/workflows/bus.ts): o worker publica em <prefixo>:runbus:<runId> e cada réplica da API faz um psubscribe global e filtra por run. O front recebe run_connected (estado completo), run_status, step_status, step_output, run_done e step_partial.

Superfície Mecanismo hoje Intervalo Onde
Chat da thread WS /ws?thread= com replay parcial 1x por step be/src/ws/handler.ts
Run de workflow WS /ws?run= + Redis Pub/Sub step_partial até 2 Hz be/src/ws/run-channel.ts, be/src/workflows/bus.ts
Badge "rodando" da sidebar 2º WS por aba, só thread_busy batimento de 25s fe/src/components/thread-sidebar/model/use-thread-busy.ts:82-88
Lista de threads polling REST 8s fe/src/features/thread/model/queries.ts:39
Árvore e graph de subagentes polling REST 3–5s com filho rodando fe/src/features/subagent/model/queries.ts:57-63, 82-87
Modal de subagente polling REST 2s eventos + 3s status use-subagent-events.ts:30, subagent/model/queries.ts:111-117
Lista de runs polling REST 5s, até 11 queries fe/src/features/run/model/queries.ts:60-66, RunsListPage.tsx:142-148
Log do run polling REST 4s run/model/queries.ts:132
Detalhe do passo polling REST 3s run/model/queries.ts:178
Saúde do backend polling REST 30s fe/src/features/system/model/queries.ts:28
Voz e TTS REST + streaming do corpo sem polling speech-player.ts:451
SSE só interno, SDK → proxy LB — be/src/agent/loop.ts:450-451

↑ voltar ao índice

Na reconexão do chat, o front refaz o histórico por REST e poda o buffer ao vivo por turnId; turnos fechados sem conteúdo disparam um catch-up por seq.

Várias réplicas (ponto central). O broadcaster é 100% em memória (be/src/ws/broadcaster.ts:59-65); o isBusy enxerga só a réplica local (be/src/engine/registry-redis.ts:154); o abort entre réplicas publica num canal que ninguém assina (registry-redis.ts:156-167); o fechamento de sockets no fim de sessão só alcança o processo atual (be/src/auth/session.ts:104-113). Já são distribuídos: o lock de turno (Redis com TTL e heartbeat), o buffer vivo (Redis com TTL), a mailbox (Postgres com drain atômico) e o barramento de runs.

2.6 Dados e cache

Caminho do turno no banco. Mensagem → dispatcher.submit → mailbox.push (1 leitura thread.findUnique + 1 escrita mailboxItem.create, be/src/engine/mailbox-db.ts:75-96) → pump() (hasPending → tryAcquire com SET NX e TTL de 30s → drain atômico com FOR UPDATE SKIP LOCKED → runTurn → release em Lua). Dentro do runTurn: prepareTurn (getThread com cache Redis + loadEffectiveConfig + startTurn, que faz UPDATE threads.last_turn + INSERT turn numa transação) → compactação → bootstrap → registry.getConvo (conversa em memória, turn-runner.ts:705) → 1 appendEvent por mensagem (apaga o cache, transação UPDATE last_seq + INSERT event e escrita pós-commit no Redis via outbox com retry) → pré-check de orçamento (Redis HGETALL com espelho local de 1,5s) → runAgent (1 slot de concorrência da org pelo turno inteiro) → a cada step, prepareStep com checagem de orçamento e drainNotifications (1 UPDATE) → respostas (1 appendEvent por mensagem + reasoning e step_done) → finishTurn → onTurnEnd.

Cache de events. Por thread, um ZSET (score = seq) + HASH de payloads + sentinela synced, com janela quente de 200 events (trim a cada push, TTL de 1800s). Se a janela não cobre o pedido, lê do Postgres sem re-hidratar. O histórico para o LLM é reconstruído por eventsToLlmMessages (respeita o último checkpoint de compactação, be/src/agent/history.ts:102-120), mas só no connect do WS e depois de compactar; durante o turno vale o getConvo em memória.

Fila de workflows (BullMQ): transporte, com a verdade no Postgres — lease do reconciliador, claim condicional, heartbeat de tentativa a cada ~15s, varredura de órfãos a cada 30s em lote de 200, purga horária de workflow_events e retenção diária (threads na lixeira 30 dias, mailbox consumida 7, entregas de webhook 90, runs 180).

Topologia de produção medida (investigação de lacunas):

Peça RAM Observação
motor-be-ws (API + WS) 2 GB 1 réplica; uso 242 MB e 1,3% de CPU na leitura
motor-worker-ws (BullMQ) 1 GB 1 réplica; o boot loga sharesCacheInstance: true e purga ligada (30 dias)
<container interno (prod PG)> (postgres:16-alpine) 4 GB postgresql.conf com max_connections = 100 e shared_buffers = 128MB (defaults); confirmar override com SHOW
<container interno (prod Redis)> (redis-stack 7.4) 512 MB sem maxmemory nem maxmemory-policy explícitos (padrão noeviction); fila e cache na mesma instância; 24 MB em uso

↑ voltar ao índice

Logs do motor-be-ws: 115 requests, todas com status 200, p50 de 0,8 ms, p99 de 1.250 ms, máximo de 2.188 ms; três avisos [mcp-pool] LEAK detected (browser, agentpack, mp).

3. Achados por área

Formato: os achados revisados (originalmente crítica ou alta) aparecem em blocos com evidência, impacto, recomendação, esforço e a nota do revisor. Os não verificados (média, baixa, info) aparecem em tabela, com evidência, impacto e recomendação curtos. Os refutados estão em 3.9.

3.0 Temas transversais (o mesmo fato em várias dimensões)

Vários problemas foram achados por mais de um scanner. Esta tabela junta as leituras, inclusive quando os revisores divergiram.

Tema Achados e status Leitura consolidada
Parar/abort entre réplicas loop-sdk-abort-cross-replica (confirmado, alta→crítica); qualidade-abort-sem-assinante (confirmado, alta→crítica); subagentes-abort-cross-replica-morto (confirmado, alta); tempo-real-stop-abort-multi (confirmado, alta→média); dados-abort-sem-subscriber (refutado como bug ativo) Contestado só na severidade; o fato é unânime: o único publish (be/src/engine/registry-redis.ts:164) leva só {threadId, message} e nenhum código assina o canal. Latente hoje (1 réplica). Com 2 ou mais, Parar não para, a UI mostra "Erro do provider" e abortChild marca aborted no banco com o turno rodando. Tratado como bloqueador da 2ª réplica; esforço P.
Estado local ao processo tempo-real-broadcast-local e dados-broadcaster-local (confirmados, alta); tempo-real-busy-local (confirmado, alta) × dados-isbusy-local (refutado como "alta"; revisor: média); tempo-real-sessao-outra-replica e tempo-real-dedupe-so-local (confirmados, alta) Broadcaster, isBusy, fim de sessão e dedupe de envio vivem em Map de processo. Contestado apenas na severidade do isBusy. Todos latentes com 1 réplica; todos precisam ir para o Redis antes da 2ª.
Reaper desligado subagentes-reaper-desligado (confirmado por 2 revisores, crítica→alta); dados-reaper-desligado (confirmado, alta) REAPER_ENABLED não está setado em nenhum serviço de produção. Crash entre startTurn e finishTurn deixa o turno running para sempre e o pai de subagente esperando; a rede do reconciler de notificações (BE-ERR-09) também fica desligada.
Turno sem teto loop-sdk-maxsteps-999 e tools-prompt-maxsteps-999 (confirmados, alta); loop-sdk-timeout-zero (confirmado, alta) O chat cai sempre no default de 999 steps; não há teto de tokens por turno nem timeout padrão. Turno real: ~14 milhões de tokens até overloaded_error.
Custo real loop-sdk-usage-cache-creation (confirmado, alta); tools-prompt-cache-creation-ignorado (não verificado); loop-sdk-preco-fixo (confirmado, alta→média) cache_creation fora do contexto, do custo e do orçamento; preço fixo de Sonnet para qualquer rota (o teto em tokens é a rede).
Escritas colaterais loop-sdk-sideeffect-retry (confirmado, alta); seguranca-tools-02 (não verificado, média); loop-sdk-p07-injeta-sem-persistir (confirmado, alta); seguranca-tools-03 (não verificado) O helper de retry não retenta; notificação injetada sem persistir; metadados de compactação gravados com snapshot velho.
Canal :stream sem assinante tempo-real-events-canal-sem-ouvinte (confirmado, alta) × dados-publish-stream-sem-subscriber (não verificado, baixa) Fato confirmado: cada appendEvent publica o JSON inteiro num canal que ninguém escuta. Remover ou usar como transporte do broadcast distribuído.
SSRF seguranca-ssrf-01 a 04 e tools-prompt-ssrf-mcp-test (confirmados, alta); investigação de lacunas (refresh de credencial MCP por HTTP sem guard; allowPrivateHosts morta; TLS opcional) O guard existe e é bom (be/src/net/ssrf.ts), mas só cobre o passo http do workflow (safeRequest) e a checagem inicial dos webhooks.
Fan-out de subagentes loop-sdk-fanout-ilimitado (refutado) × subagentes-fanout-ilimitado (confirmado, alta) Contestado, e as duas leituras convivem: há defesas por org (rate limit externo, gate de 100 por org e circuit breaker), o que refutou a tese de "bypass"; mas não há teto por árvore (largura, profundidade, orçamento), o que foi confirmado.
Argumentos de tools loop-sdk-parse-args (refutado) × tools-prompt-args-sem-validacao (confirmado, alta→média) parseToolArgs só alimenta a UI e JSON quebrado o SDK recusa; mas JSON válido com campo faltando chega ao execute — agent_spawn cria filho com tarefa "undefined".
Concorrência e rate limit locais loop-sdk-slot-por-turno (refutado: intencional, RATE-003); dados-org-slot-por-turno, loop-sdk-rate-limit-local, dados-rate-limit-local, dados-org-concurrency-local, loop-sdk-gate-denial-vira-erro (não verificados) Slot por turno é decisão de produto. O que sobra: contadores locais (multiplicam por réplica) e negação do gate virando "Erro do provider" sem retry.
Conexões do Postgres dados-pool-1000 (confirmado, alta→baixa) × investigação de lacunas (max_connections = 100 no postgresql.conf de produção) Contestado. O revisor rebaixou assumindo o runbook (≥ 2100), 1 réplica e pico medido de 151 conexões no teste L7-16. Se os 100 se confirmarem, a carga do próprio L7-16 não caberia: vira alta. Ação: SHOW max_connections.
Handshake e replay tempo-real-handshake-repete-historico (confirmado, alta) × dados-events-rebuild-full (refutado no impacto de UI) O servidor relê e reenvia a thread inteira a cada (re)conexão; o front descarta. O custo é do servidor, não trava a UI; o watchdog de 60s multiplica (tempo-real-watchdog-reconecta-ocioso).
Mapas sem teto tempo-real-mapas-sem-teto (confirmado, alta); dados-mapas-sem-eviccao, loop-sdk-live-sem-teto, seguranca-tools-01 (não verificados) Vazamento lento de memória por thread, org e conexão até o restart.
Retorno e resultados sem teto subagentes-notificacao-sem-teto (confirmado, alta); tools-prompt-sem-limite-resultado (confirmado, alta→média); tools-prompt-notificacao-role-user (confirmado, alta) finalText do filho e resultado de tool entram inteiros (workflow já corta em 6 mil chars); notificação do filho chega com moldura de autoridade.
Persona do setup tools-prompt-persona-herdada-perdida, subagentes-persona-herdada-sumida, tools-prompt-thread-append-apaga-persona (não verificados) Herança de setup sem persona; apêndice da thread apaga a persona inteira.
Versão do pacote ai loop-sdk-lockfile-divergente (refutado) Produção instala pelo be/package-lock.json (ai 5.0.253, @ai-sdk/anthropic 2.0.101); o 5.0.179 é do lock da raiz (dev/CI) e do node_modules da sandbox. Isso também explica o warning "System messages…" visto em produção. Resta: não existe check-lock:be para pegar drift.
Header 16 × 100 subagentes-doc-concurrency-divergente, dados-default-divergente (não verificados) O refutador do slot confirmou: só o JSDoc de be/src/engine/org-concurrency.ts:14 está defasado; código, .env e docs operacionais estão em 100.

↑ voltar ao índice

3.1 Loop do agente e SDK

[crítica · confirmado; sev. contestada alta→crítica] loop-sdk-abort-cross-replica — Aborto entre réplicas perde as flags e vira "Erro do provider" · Esforço P

[alta · confirmado] loop-sdk-usage-cache-creation — cache_creation_input_tokens fica fora do usage, do contexto e do custo · Esforço M

[alta · confirmado] loop-sdk-timeout-zero — Sem timeout padrão, um stream pendurado prende lock e slot para sempre · Esforço P

[alta · confirmado] loop-sdk-maxsteps-999 — Teto de 999 steps permitiu um turno real de ~14 milhões de tokens · Esforço P

[alta · confirmado] loop-sdk-sideeffect-retry — sideEffectWrite promete 3 tentativas e executa uma · Esforço P

[alta · confirmado] loop-sdk-p07-injeta-sem-persistir — Notificação no meio do turno é injetada mesmo quando a gravação falha · Esforço M

[média · confirmado; sev. contestada alta→média] loop-sdk-preco-fixo — Preço fixo de Sonnet para qualquer rota deixa o teto em USD aproximado · Esforço M

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
loop-sdk-compactacao-proximo-turno — compactação só enxerga a explosão no turno seguinte (TURN-12) média M be/src/engine/turn-runner.ts:516-525 turno gigante paga o contexto cheio até o fim checar o usage por step com teto de tokens; estourou, encerra gracioso, compacta e orienta a continuar
loop-sdk-compactador-sem-custo — compactador fora do slot, sem retry e sem custo média P be/src/agent/compaction.ts:44, 347-356; turn-runner.ts:535-548 fura o gate sob carga; até ~75 mil tokens por compactação invisíveis ao orçamento passar pelo slot, retry limitado, somar o usage no bucket da org
loop-sdk-parar-compactacao — Parar ou timeout durante a compactação é engolido média P turn-runner.ts:547, 587-644 o turno segue para o LLM e gasta mesmo depois do Parar no catch, relançar se o abortSignal estiver abortado
loop-sdk-replay-morto — replay de thinking assinado é inoperante média M be/src/engine/response-events.ts:51-62; turn-runner.ts:926-928; be/src/agent/loop.ts:159-186; warnings "unsupported reasoning metadata" em produção pruneOldThinking: false não funciona depois de persistir; doc, contrato e código divergem assumir a poda sempre e documentar, ou guardar as assinaturas fora da conversa e reanexar
loop-sdk-erro-sem-status — erro de stream sem status vira erro genérico média P loop.ts:501-503; turn-runner.ts:1330-1334, 1495-1563 overloaded e rate limit aparecem como "Erro do provider"; workflow não distingue o que é retentável mapear o tipo do erro para errorKind com status 429/503 e expor retryAfterMs
loop-sdk-overflow-sem-retry — estouro de janela (400) não compacta nem retenta média M be/src/config/schemas.ts:156; turn-runner.ts:1330-1344 o usuário precisa reenviar; com janela padrão de 230 mil e modelo de 200 mil é questão de tempo detectar overflow, compactar e retentar 1 vez; calibrar a janela por rota
loop-sdk-bootstrap-perde-itens — falha no bootstrap descarta mensagens já drenadas média P turn-runner.ts:650-693, 704-798; be/src/engine/dispatcher.ts:150 mensagem do usuário some (Turn errored sem o texto); o reenvio duplica persistir os itens antes do bootstrap ou reenfileirar em falha antes do LLM
loop-sdk-guard-partial-vazio — a guarda de aborto devolve parcial vazio média P loop.ts:563-572; dispatcher.ts:158-170; agent-step.ts:632-637 no chat, cancelar no meio joga fora o texto já gerado e pago acumular deltas no runAgent e devolver na guarda
loop-sdk-repair-apaga-args — o reparo troca input corrompido por {} média P response-events.ts:71-90 o histórico passa a mentir sobre os argumentos usados preservar o texto cru truncado com marcador ou anotar no tool_result
loop-sdk-persistencia-fim — a resposta só é gravada no fim do turno média G turn-runner.ts:1092; agent-step.ts:52-54 crash no meio de um turno longo: tools com efeito real e custo pago, zero eventos persistir por step (o loop nativo deve nascer assim)
loop-sdk-rate-limit-local — rate limit por (org, usuário) local ao processo média M be/src/engine/rate-limit.ts:40-44; dispatcher.ts:269-274 o teto efetivo vira 60 × réplicas mover para Redis (INCR + EXPIRE)
loop-sdk-retry-tempestade — retries sincronizados contra o LB média P loop.ts:507; be/src/engine/org-concurrency.ts:58-60 antes do circuit abrir, dezenas de turnos retentam juntos em 2s, 4s, 8s contra um proxy já sobrecarregado jitter nos retries, ou menos retries e circuit mais rápido
loop-sdk-gate-denial-vira-erro — negação do gate vira "Erro do provider" média M loop.ts:434-448; turn-runner.ts:1330-1344 sob carga o usuário vê erro genérico e reenvia (duplica a mensagem); retryAfterMs é ignorado errorKind próprio e retentável; reenfileirar com atraso ou responder 429 honesto
loop-sdk-injected-ordem — ordem memória × banco das notificações diverge baixa M turn-runner.ts:932-935, 1017-1030 depois de um restart, o histórico reconstruído difere do que o modelo viu inserir na posição vista ou documentar a divergência
loop-sdk-system-messages — system dentro de messages gera warning a cada step baixa P be/src/agent/messages.ts:77-79; log de produção ruído de log que dilui sinais reais usar a opção system ou allowSystemInMessages explícito
loop-sdk-fetch-interceptor — o interceptor reparseia o body a cada step baixa M be/src/provider/anthropic.ts:159-197 CPU por step com 362 tools; em falha de parse envia sem user_id e quebra o roteamento de cache do LB preservar o user_id no fallback; no loop nativo, montar o body direto
loop-sdk-cache-5bps — sem garantia do teto de 4 breakpoints baixa P messages.ts:4, 72-107; anthropic.ts:178 a config pode plantar 5; a API ignora o excedente e o cache rende menos impor o teto com prioridade e avisar ao descartar
loop-sdk-live-sem-teto — buffer ao vivo e convo crescem sem teto no turno baixa P be/src/engine/registry.ts:186-189; registry-redis.ts:172-177 turno longo acumula MBs por thread em memória e no Redis janela deslizante e limite defensivo
loop-sdk-compactacao-memoria — compactação carrega todos os events na memória baixa M compaction.ts:163-180, 208 thread com mais de 10 mil events = dezenas de MB por compactação projeção SQL enxuta e estimativa sem materializar tudo
loop-sdk-extendedthinking-morto — a opção extendedThinking nunca é passada info P loop.ts:250, 409-418; turn-runner.ts:940-955 código morto com budget_tokens, rejeitado nos modelos atuais remover ou ligar à config com tipo adaptativo
loop-sdk-extras-ignorados — temperature, max_tokens e stream em extras são ignorados info P anthropic.ts:31-38, 114-148 o operador configura e nada acontece, sem aviso documentar como não suportado ou avisar
loop-sdk-custo-fail-open — orçamento é fail-open por padrão info P cost-tracker.ts:50-53, 369-371 Redis fora + carga = estouro sem alarme (decisão documentada) manter, mas alertar quando o gate decidir pelo espelho local
loop-sdk-preparestep-v7 — buffer acumulado manual exigido na v5 info M loop.ts:281-290; turn-runner.ts:1040-1047 atualizar o ai para v7 muda a semântica e pode duplicar injeções registrar como restrição; no loop nativo vira append direto

↑ voltar ao índice

Métricas medidas (loop):

3.2 Tempo real (backend e frontend)

[alta · confirmado] tempo-real-broadcast-local — Broadcaster 100% em memória: a 2ª réplica quebra o chat ao vivo · Esforço G

[alta · confirmado; contestado entre dimensões] tempo-real-busy-local — isBusy enxerga só a réplica local, mas alimenta REST, handshake e árvore · Esforço M

[alta · confirmado] tempo-real-sessao-outra-replica — Sessão encerrada fecha sockets só na réplica local · Esforço P

[alta · confirmado] tempo-real-watchdog-reconecta-ocioso — Toda aba ociosa reconecta cerca de 1 vez por minuto · Esforço P

[alta · confirmado] tempo-real-handshake-repete-historico — Cada (re)conexão relê a thread inteira e a reenvia para o cliente descartar · Esforço M

[alta · confirmado] tempo-real-dialog-estoura-ratelimit — Modal de subagente e lista estouram o teto de 30 req/min por IP com uma aba · Esforço P

[alta · confirmado] tempo-real-runs-lista-explosao — A tela de runs dispara até 11 queries a cada 5s por aba · Esforço M

[alta · confirmado] tempo-real-events-canal-sem-ouvinte — Todo evento persistido é publicado num canal Redis que ninguém assina · Esforço P

[alta · confirmado] tempo-real-mapas-sem-teto — Mapas em memória sem expiração crescem com o uso · Esforço P

[alta · confirmado] tempo-real-dedupe-so-local — Dedupe de envio não vale entre réplicas; a mailbox não tem unique · Esforço M

[alta · confirmado] tempo-real-protocolo-sem-versao — Protocolo sem versão: deploy be/fe acoplado e eventos novos invisíveis · Esforço M

[alta · confirmado] tempo-real-metricas-cegas — O tempo real só conta sockets · Esforço P

[alta · confirmado] tempo-real-busy-travado-desconexao — A badge "rodando" pode ficar acesa depois de uma queda · Esforço P

[média · confirmado; sev. contestada alta→média] tempo-real-stop-abort-multi — "Parar" entre réplicas não cancela e ainda marca aborted no banco · Esforço P

[média · confirmado; sev. contestada alta→média] tempo-real-fanout-filho-sem-raiz — O progresso do filho nunca chega à sala do pai · Esforço P

[média · confirmado; sev. contestada alta→média] tempo-real-polling-sem-cache — O polling lê o Postgres direto · Esforço M

[média · confirmado; sev. contestada alta→média] tempo-real-socket-busy-duplica — O socket da sidebar recebe tudo para usar só o thread_busy · Esforço M

[média · confirmado; sev. contestada alta→média] tempo-real-runbus-psubscribe — Cada réplica recebe os frames de todos os runs · Esforço M

[média · confirmado; sev. contestada alta→média] tempo-real-sem-acks-perdas — Sem ack nem sequência: o que se perde numa queda · Esforço M

[média · confirmado; sev. contestada alta→média] tempo-real-backoff-sem-jitter — Reconexão sem jitter: todas as abas voltam juntas no deploy · Esforço P

Não verificados (baixa e info):

Achado Sev. Esf. Evidência Impacto Recomendação
tempo-real-health-por-aba — saúde a cada 30s por aba consulta Postgres e Redis baixa P fe/src/features/system/model/queries.ts:17-32; be/src/routes/health.ts:90-113 carga evitável e mistura probe de infra com status de UI F5: derivar "backend offline" do socket e dos erros REST; deixar readyz para o orquestrador
tempo-real-chat-sem-streaming-token — o chat emite parcial por step info M be/src/sinks/broadcast.ts:65-105; be/src/agent/loop.ts:218-227, 514-550; be/src/engine/agent-step.ts:621-687 resposta longa de step único chega em blocos na F6, ligar onTextDelta no chat com evento versionado e coalescência, como no run
tempo-real-sse-so-vendor — nenhum SSE na camada do app info P busca em fe/src e be/src; be/src/agent/loop.ts:450-451 "sem SSE" já é verdade; a migração só precisa eliminar o polling manter a proibição de SSE no app
tempo-real-tts-voz-rest — voz e TTS não têm polling próprio info P fe/src/features/voice-output/model/speech-player.ts:451; use-conversation.ts:198 fora da migração de polling nenhuma ação
tempo-real-run-cauda-efemera — a cauda do parcial do run some na reconexão info M be/src/workflows/bus.ts:12-16; fe/src/features/run/model/runtime-reducer.ts:63-81 passo em andamento fica sem o texto parcial até o próximo frame (por desenho) se quiser zero perda visível, guardar os últimos parciais por passo no Redis com TTL curto
tempo-real-comentario-run-stale — comentário diz que o backend "não serve ?run=" info P fe/src/features/run/model/channel.ts:39-41; be/src/plugins/websocket.ts:122-130 confunde o planejamento; o canal existe atualizar o comentário

↑ voltar ao índice

Métricas medidas (tempo real):

3.3 Tools e system prompt

[alta · confirmado] tools-prompt-snapshot-com-segredos — Snapshot da config com segredos em claro vai para Postgres e Redis a cada turno · Esforço P

[alta · confirmado] tools-prompt-ssrf-mcp-test — Teste de MCP e MCPs por thread aceitam URL arbitrária sem trava SSRF · Esforço M

[alta · confirmado] tools-prompt-descricao-mcp-verbatim — Descrição e schema de MCP entram verbatim no prompt, sem limite · Esforço M

[alta · confirmado] tools-prompt-notificacao-role-user — Resultado de subagente ou workflow entra como user, com moldura de autoridade · Esforço M

[alta · confirmado] tools-prompt-maxsteps-999 — Default de 999 steps por turno permite custo descontrolado · Esforço P

[alta · confirmado] tools-prompt-tudo-para-o-modelo — O padrão registra todas as tools de cada MCP · Esforço G

[média · confirmado; sev. contestada alta→média] tools-prompt-colisao-mantem-ultimo-mas-avisa-primeiro — O aviso de colisão diz o contrário do que o código faz · Esforço M

[média · confirmado; sev. contestada alta→média] tools-prompt-sem-limite-resultado — Resultado de tool sem teto · Esforço M

[média · confirmado; sev. contestada alta→média] tools-prompt-estimativa-ignora-tools — A estimativa de contexto ignora o bloco de tools · Esforço P

[média · confirmado; sev. contestada alta→média] tools-prompt-args-sem-validacao — Argumentos de tools nativas não são validados · Esforço M

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
tools-prompt-system-via-messages — system dentro de messages gera warning do SDK média P be/src/agent/messages.ts:77-79; be/src/agent/loop.ts:499-505; log de produção hoje é ruído (o SDK converte), mas o SDK indica que o caminho pode endurecer usar a opção system com o mesmo cache, ou allowSystemInMessages explícito
tools-prompt-system-mente-builtin — o prompt diz que as tools são "built-in, sem MCP externo" média M be/system-prompt.md:19; be/src/agent/bootstrap.ts:120-173 o modelo aprende um mapa falso e gasta steps em tools ausentes quando um MCP cai ou a política restringe gerar a seção de ferramentas a partir das tools montadas no turno
tools-prompt-duplicacao-prompt-tools — mesmas regras no prompt, nas descrições e nos resultados média M be/system-prompt.md:89-274; be/src/tools/native/subagent.ts:94-105; setup.ts:249-266; workflow.ts:603-635, 1270-1277 cada palavra duplicada é paga em todo request e há três lugares para manter sincronizados fonte única: detalhe na descrição da tool, prompt como índice curto, resultados só com fatos
tools-prompt-append-concat-vs-substitui — a thread soma ou substitui o apêndice da org? média P be/src/config/schemas.ts:333-335 (soma) × be/src/config/thread-config.ts:208-210, 417-420 (substitui) quem configura a thread acha que preserva as regras da org e as apaga decidir a semântica (sugestão: org + setup + thread concatenados e rotulados) e alinhar comentários e UI
tools-prompt-cache-defaults-divergem — defaults de cache do schema × contrato do LB média P be/src/config/schemas.ts:27-28, 356-357 × be/docs/contrato-load-balancer.md:41-45, 67; be/chat-config.json:22-31 orgs criadas fora do seed plantam breakpoints inúteis alinhar os defaults ao contrato e corrigir o comentário de markLastAssistant
tools-prompt-toolpolicy-colisao — toolPolicy por servidor pode liberar a tool do servidor errado média M be/src/tools/registry.ts:74-83, 139-145 a política não garante a origem que o admin imagina resolver com namespace: a política cita servidor ou servidor__tool
tools-prompt-cache-creation-ignorado — escrita de cache fora do custo média M @ai-sdk/anthropic expõe cacheCreationInputTokens; loop.ts:307-313; be/src/engine/response-events.ts:98-110; be/src/engine/cost-tracker.ts:86-91 custo real subestimado no orçamento igual a loop-sdk-usage-cache-creation, confirmado em 3.1
tools-prompt-sem-painel-inspecao — não dá para ver o que foi enviado em cada step média G be/src/provider/anthropic.ts:221-227 (só contagens); turn-runner.ts:364; be/src/routes/threads.ts:605-627 todo debug de "por que o modelo fez X" vira reconstrução mental; divergências de cache ficam invisíveis persistir por turno o hash e o tamanho de cada camada, as tools finais e os breakpoints; expor em endpoint e aba de debug (5.3)
tools-prompt-persona-herdada-perdida — subagente com setup herdado perde a persona média P thread-config.ts:500; orchestrator.ts:202-223 o filho responde fora do papel esperado, em silêncio injetar a persona herdada ou documentar e avisar no setup_list
tools-prompt-thread-append-apaga-persona — apêndice na thread apaga a persona inteira média M thread-config.ts:444-447, 512 um ajuste pequeno remove toda a persona do setup concatenar rotulado em vez de tudo-ou-nada; até lá, avisar na UI
tools-prompt-setup-update-reenvio — setup_update exige reenviar o override inteiro média M be/src/tools/native/setup.ts:208-215, 421-424 caro em tokens e propenso a apagar campos ou segredos (•••) semântica de patch no serviço, com clearSecrets explícito
tools-prompt-warpgrep-fantasma — o prompt cita a tool warpgrep, que não existe baixa P be/system-prompt.md:85 o modelo tenta chamar tool inexistente justo quando está travado remover ou mapear para a tool real
tools-prompt-pool-key-comentario — comentário diz que o token renovado entra na chave do pool baixa P mcp-client.ts:223-227; schemas.ts:239-242; be/src/tools/mcp-pool.ts:65-70 doc enganosa (a rotação funciona por invalidação no 401) corrigir os comentários ou pôr o hash do token na chave
tools-prompt-readfile-por-turno — o system prompt é lido do disco a cada turno baixa P be/src/agent/bootstrap.ts:66-70 I/O de ~26 KB por turno e por subagente cache em memória invalidado por mtime
tools-prompt-midturn-ordem — notificações vistas intercaladas e persistidas no fim baixa M turn-runner.ts:932-935, 1040-1047 depois de um restart, a ordem reconstruída difere da vista pelo modelo persistir a posição ou documentar a divergência
tools-prompt-parsetoolargs-sem-teto — parser de argumentos quadrático e sem teto baixa P loop.ts:56-64 entrada patológica pode prender a CPU do event loop teto de tamanho (ex.: 1 MB) antes do parse
tools-prompt-voice-cache — trocar o modo de voz invalida o cache do system info P bootstrap.ts:93-95; be/src/agent/voice-prompt.ts:88-120 comportamento esperado só documentar
tools-prompt-unsupported-reasoning — warnings de reasoning sem assinatura info P loop.ts:159-186; log de produção ruído de log; o SDK descarta os blocos filtrar reasoning sem metadata em todos os caminhos ou silenciar depois de confirmar
tools-prompt-activetools-nao-usado — o SDK aceita activeTools por step e o Motor não usa info G pacote ai (PrepareStepResult); loop.ts:291-297 não é problema: é a porta para carregar tools sob demanda estender o prepareStep para devolver activeTools quando houver busca de tools

↑ voltar ao índice

Métricas medidas (tools e prompt):

3.4 Agente e subagentes

[alta · confirmado por 2 revisores; sev. contestada crítica→alta] subagentes-reaper-desligado — Reaper desligado em produção: filho órfão trava o pai para sempre · Esforço P

[alta · confirmado] subagentes-fanout-ilimitado — Largura e profundidade ilimitadas, sem orçamento por árvore · Esforço M

[alta · confirmado] subagentes-notificacao-sem-teto — O resultado do filho volta inteiro, sem corte nem condensação · Esforço P

[alta · confirmado] subagentes-sem-cascata — Cancelar o pai não desce para filhos e netos · Esforço M

[alta · confirmado] subagentes-abort-cross-replica-morto — Abort entre réplicas publica num canal que ninguém escuta · Esforço M

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
subagentes-filtro-tools-fura-nativas — o filtro tools do spawn não alcança as nativas média P be/src/engine/orchestrator.ts:226-228; be/src/tools/native/subagent.ts:128-134 o filho "só leitura" ainda abre netos e usa workflow_* e setup_* aplicar o filtro também às nativas, ou documentar que só cobre MCP
subagentes-persona-herdada-sumida — herdar o setup não traz a persona; setupId explícito traz média P orchestrator.ts:186-223; be/src/config/thread-config.ts:500; subagent.ts:137-141 os dois caminhos geram agentes diferentes sem aviso injetar a persona também no caso herdado, ou explicitar nas descrições
subagentes-workspace-compartilhado — filhos paralelos no mesmo workspace média G orchestrator.ts:15-16; be/system-prompt.md:100-107 edições intercaladas ou sobrescritas silenciosas no mesmo arquivo orientar a particionar o escopo de escrita por filho, ou worktree/branch por filho
subagentes-doc-concurrency-divergente — header diz 16; o código aplica 100 baixa P be/src/engine/org-concurrency.ts:14, 50; be/.env.example:251 premissa errada (fator 6) em dimensionamento e incidentes corrigir o header
subagentes-shortid-morto — shortId existe e nunca é usado baixa P orchestrator.ts:469-471 id longo (UUID) no transcript e nas tools usar como alias em agent_status/agent_abort ou remover
subagentes-exatamente-uma-vez-ok — idempotência e exactly-once bem resolvidos info — orchestrator.ts:108-155, 317-321; be/src/engine/mailbox-db.ts:98-119 ponto forte: retry não duplica filho; duas réplicas não notificam duas vezes manter; testar concorrência real entre réplicas
subagentes-agrupamento-midturn-ok — agrupamento e injeção no meio do turno funcionam info — be/src/engine/dispatcher.ts:143-149; turn-runner.ts:956-1048; mailbox.ts:232-239 ponto forte: fan-out não vira enxurrada de turnos manter; medir a latência ponta a ponta

↑ voltar ao índice

Métricas medidas (subagentes):

3.5 Workflows

[alta · confirmado] workflows-outputs-in-list-payload — Lista de passos e handshake do WS carregam o output inteiro de cada passo · Esforço M

[média · confirmado; sev. contestada alta→média] workflows-absence-budget-no-cap — Run sem budget não tem teto nenhum · Esforço M

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
workflows-eventlog-no-cursor — o log do run relê desde seq=0 a cada 4s e corta em 500 média P fe/src/features/run/model/queries.ts:122-134; be/src/routes/workflows.ts:645-687 (já aceita after) runs com mais de 500 eventos nunca mostram o resto; o mesmo payload volta a cada 4s passar o último seq como after, ou usar os frames run_event do WS
workflows-races-can-reopen-after-terminal — requestResume pode reabrir run cancelado média P be/src/workflows/runs.ts:556-573, 614-626, 687-697; be/src/workflows/reconcile.ts:1063-1064 run que deveria ficar canceled volta a running incluir cancelRequestedAt: null e status não terminais no WHERE; teste L4
workflows-runpubrun-pauses-carried — cancelamento na janela pausing baixa P runs.ts:583-657, 779-850; reconcile.ts:1166-1168 caso raro: depois de cancelado nessa janela, não dá para retomar WHERE com pauseRequestedAt: null e status não terminais, como no cancel; teste L4
workflows-api-runs-no-pagination-large-result — total por count separado pode divergir baixa P be/src/routes/workflows.ts:505-582 "número errado que parece certo" quando um run nasce entre as duas queries transação com isolamento adequado, ou documentar o total como aproximado
workflows-runpubrun-idempotency-race — dois startRun com a mesma chave ao mesmo tempo baixa P runs.ts:352-357; be/prisma/schema.prisma:794 o segundo recebe 500 em vez do run reutilizado capturar o conflito de unique (P2002) e reler o run existente
workflows-cancelreason-overload-pausecolumn — motivo do cancelamento gravado em pauseReason baixa P runs.ts:616-622, 791-797; schema.prisma:782 auditoria confusa, com dois domínios na mesma coluna coluna cancelReason ou documentar o uso
workflows-rest-pause-expires-deadcolumn — pause_expires_at nunca é calculada nem consultada baixa P schema.prisma:784; runs.ts:695; reconcile.ts:1166-1168; be/src/queue/retention.ts:200-204 run pausado fica pausado para sempre e escapa da retenção calcular o prazo (ex.: WF_PAUSE_MAX_DAYS, 30 dias) e cancelar ao vencer
workflows-lease-no-renewal-mid-tick — a lease do reconciliador não é renovada durante o tick baixa P reconcile.ts:66, 129-153, 1696-1905; be/src/queue/sweeper.ts:256-266 run muito grande pode ter dois ticks concorrentes (trabalho desperdiçado; as outras barreiras impedem execução dupla) renovar a lease a cada ~20s durante o tick, ou lease maior
workflows-docs-operacao-stale — docs/operacao.md diverge do código baixa M docs/operacao.md:175 ("concorrência de IA = 2") × be/src/workers/index.ts:168 (padrão 100); docs/workflows/02-arquitetura.md:58 (wf:reconcile × wf-reconcile) o runbook consultado em incidente orienta errado sincronizar os docs e marcar a versão do código que vale
workflows-fanout-default-concurrency-100-doc-divergence — concorrência padrão do fan-out info — reconcile.ts:1759; docs/workflows/00-visao-geral.md:98; be/src/workflows/definition.ts:114 consistente hoje; só rastreabilidade ligar na doc o teto (200) ao padrão de runtime (100)
workflows-isc-mcp-pool-leak-info — produção registra "LEAK detected" no pool MCP info M log do motor-be-ws (browser, agentpack, mp); be/src/observability/metrics.ts:156 algum acquire sem release num caminho de erro: vazamento gradual de handles rastrear o caminho e alertar sobre mcpLeakedRefs
workflows-cron-no-sheduler-info — trigger aceita schedule, mas não há agendador info P a G schema.prisma:706; runs.ts:488; be/src/queue/queues.ts o dono pode achar que existe agendamento; hoje só por fora (cron + curl) implementar o agendamento ou remover schedule e documentar

↑ voltar ao índice

Métricas medidas (workflows):

3.6 Banco, cache, threads e escala

[alta · confirmado] dados-convo-redis-writeonly — O convo no Redis é write-only e um miss zera o contexto do LLM · Esforço M

[alta · confirmado] dados-reaper-desligado — Reaper desligado: turnos órfãos ficam running e pais travam · Esforço P

[alta · confirmado] dados-broadcaster-local — Evento de turno pode ir para a réplica que não tem o socket · Esforço G

[média · confirmado; sev. contestada alta→média] dados-events-sem-retencao — events de thread ativa cresce sem teto nem purga · Esforço G

[baixa · confirmado; contestado (alta→baixa pelo revisor; a investigação de lacunas aponta risco alto)] dados-pool-1000 — Pool de 1000 conexões por processo × max_connections do Postgres · Esforço P a M

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
dados-org-concurrency-local — gate de concorrência e circuit breaker por processo média M be/src/engine/org-concurrency.ts:37-39, 50-60, 75 teto real de 100 × (réplicas + worker) por org; o aprendizado de 429/503 não se propaga contador no Redis, ou publicar o hit de rate limit para as réplicas abrirem o breaker juntas
dados-rate-limit-local — rate limits HTTP e de submit por processo e por IP média M be/src/engine/rate-limit.ts:40-41, 69; be/src/server/index.ts:181-185; be/src/routes/workflows.ts:198-204 multiplica por réplica; atrás de NAT, usuários dividem um balde store Redis no @fastify/rate-limit e chave por token ou sessão
dados-auth-sem-cache — cada request autenticado faz 2 leituras no banco média P be/src/auth/session.ts:201, 217; be/src/http/auth.ts:336 com polling, autenticação vira QPS constante no Postgres cache de sessão e vínculo no Redis (30–60s), invalidado no fim de sessão e na troca de papel
dados-webhook-fanout-turno — cada turno faz 2–4 buscas de webhooks média P be/src/webhooks/dispatcher.ts:239-243, 305; turn-runner.ts:506-511, 528, 578, 1248-1259 org sem webhook paga as buscas; com N webhooks, N POSTs com retry e N INSERTs por turno cache da lista por org e entrega em background com concorrência limitada
dados-listas-sem-paginacao — árvore, turns e passos sem paginação média M be/src/persistence/threads.ts:324-329; be/src/persistence/turns.ts:191-211; be/src/routes/workflows.ts:597; be/src/routes/webhooks.ts:139 fan-out grande devolve tudo de uma vez (query pesada e JSON gigante) paginar por cursor, com teto padrão e máximo
dados-compaction-full-scan — a compactação lê a thread inteira dentro do turno média M be/src/agent/compaction.ts:148-179, 408-409; turn-runner.ts:525-575 piora o p95 justamente nas conversas mais ativas compactar em background (ou no fim do turno anterior) e limitar páginas
dados-mapas-sem-eviccao — mapas por thread, org e conexão crescem até o restart média P registry-redis.ts:70-72, 216, 222; dispatcher.ts:107; handler.ts:212; busy-owner.ts:34; org-concurrency.ts:75; rate-limit.ts:69; cost-tracker.ts:181 vazamento lento de memória (um convo pode ter MBs) TTL ou LRU e limpeza garantida também no caminho de erro
dados-hydrate-sem-singleflight — misses concorrentes disparam N hidratações média P be/src/persistence/events.ts:138-217, 344-349 5 abas abrindo a mesma thread fria = 5 leituras completas simultâneas promessa compartilhada por thread e marcador SET NX de hidratação
dados-payload-grande — imagens de até MBs persistidas em 3 lugares média M be/src/http/ws-protocol.ts:19, 25; mailbox-db.ts:87-94; events.ts:97, 108-113 poucas mensagens com imagem por minuto saturam rede e Redis e incham o banco gravar o binário em attachments (o model já existe) e trafegar referência e miniatura
dados-org-slot-por-turno — o slot é por turno: teto de 100 turnos simultâneos por org média P be/src/agent/loop.ts:434-448, 599; org-concurrency.ts:13-17 acima disso o turno falha em vez de esperar granularidade é decisão de produto (ver 3.9); o conserto é a negação virar espera ou retry
dados-publish-stream-sem-subscriber — publish do JSON num canal sem consumidor baixa P events.ts:70, 112; be/src/persistence/keys.ts:60-61 custo sem leitor ver tempo-real-events-canal-sem-ouvinte (confirmado, alta)
dados-convo-rewrite-full — o turno regrava o convo inteiro no Redis baixa P turn-runner.ts:705-706; registry-redis.ts:172-177 CPU e rede a cada turno, sem leitor gravar só quando mudar; com o fallback de leitura, de forma incremental
dados-indices-faltantes — três consultas quentes sem índice composto ideal baixa P threads.ts:278-288; turns.ts:205-211; mailbox-db.ts:132-142; schema.prisma:218-229, 283, 347, 352 com volume, ordenação em memória e drain por step mais caro índices compostos para lista de threads, turns por thread e drain da mailbox, conferindo com EXPLAIN
dados-index-redundante — índice events(thread, seq) duplica o unique baixa P schema.prisma:312-313 um índice a mais em cada INSERT da tabela mais escrita migration removendo o índice redundante
dados-submit-duplo-db — cada submit faz 2 idas ao banco baixa P mailbox-db.ts:66-96 dobra as operações de mailbox sob fan-out receber o orgId no push e tratar o erro de thread inexistente
dados-sweeper-n1 — o sweeper faz 1 SELECT + 1 transação por tentativa órfã baixa P be/src/queue/sweeper.ts:213-251 em incidente, centenas de operações a cada 30s competindo com o reconciliador buscar em lote e recolher com poucos updateMany
dados-heartbeats — heartbeats geram escrita contínua info P registry-redis.ts:55-56, 113-129; be/src/workflows/state.ts:460-489 pequena (~13 UPDATEs/s com 200 tentativas) só monitorar
dados-default-divergente — header diz 16; o código diz 100 info P org-concurrency.ts:14, 50 estimativas e alertas com premissa errada alinhar header, código e .env.example
dados-doc-purge-divergente — doc diz purga desligada; o código liga por padrão info P docs/operacao.md:132; be/src/queue/purge.ts:50-52 dúvida sobre o que vale em produção o boot do worker de produção mostra a purga ligada (30 dias): corrigir a doc

↑ voltar ao índice

Métricas medidas (dados e escala):

3.7 Segurança e vazamento de informação

[crítica ou alta · confirmado; contestado entre revisores (crítica × alta)] seguranca-cross-tenant-01 — Toda org herda a config da org default no turno · Esforço G

[alta · confirmado por 2 revisores; sev. contestada crítica→alta] seguranca-ssrf-01 — Entrega de webhook usa fetch cru: o guard SSRF não cobre redirect e a assinatura acompanha o salto · Esforço P

[alta · confirmado por 2 revisores; sev. contestada crítica→alta] seguranca-ssrf-02 — POST /api/mcp/test, aberto a qualquer membro, conecta sem guard SSRF · Esforço P

[alta · confirmado] seguranca-ssrf-03 — Teste de conexão do setup faz fetch cru na baseURL e ecoa a resposta · Esforço P

[alta · confirmado] seguranca-ssrf-04 — MCPs configurados na thread (por qualquer membro) alcançam a rede interna no turno seguinte · Esforço M

[alta · confirmado] seguranca-segredo-01 — No escopo da thread, o ••• ecoado pela UI vira header real enviado ao MCP · Esforço P

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
seguranca-info-01 — GET /info público e sem rate limit média P be/src/routes/health.ts:115-127; be/src/http/auth.ts:208-213 um anônimo descobre modelo, caminho do workspace, host, porta e número de WebSockets proteger com o token de métricas ou expor só versão e uptime
seguranca-info-02 — o teste de conexão ecoa a mensagem do provider média P be/src/routes/agents.ts:82-109, 114-127 canal de exfiltração junto com o SSRF do setup devolver só status e mensagem genérica
seguranca-tools-02 — contrato do sideEffectWrite quebrado média P turn-runner.ts:91-131 e chamadores as 3 tentativas são 1 confirmado como loop-sdk-sideeffect-retry (3.1, alta)
seguranca-tools-03 — metadados de compactação gravados com snapshot velho média P turn-runner.ts:562-573, 627-631 (o padrão certo já existe em 1193-1200) um PATCH do usuário no meio do turno é sobrescrito em silêncio reler a thread antes de gravar, como no write de contextTokens
seguranca-tools-01 — seenClientMessageIds cresce sem teto em sessão longa baixa P be/src/ws/handler.ts:186-205, 650-660 vazamento lento enquanto houver socket conectado TTL por entrada ou fila limitada
seguranca-info-03 — PATCH de webhook rejeita secret: "", contra o próprio comentário baixa P be/src/routes/webhooks.ts:46, 215 cliente que siga o comentário recebe 400; o ramo é código morto decidir a semântica e alinhar schema e comentário
seguranca-tools-04 — cache de OrgConfig lê sem prefixo e grava com prefixo baixa P be/src/persistence/orgConfig.ts:58, 101-108, 165 o cache nunca acerta: toda leitura vai ao Postgres padronizar a chave nas três operações
seguranca-rbac-01 — submits internos ignoram o rate limit baixa M be/src/engine/rate-limit.ts:20-40; be/src/engine/dispatcher.ts:265-280 por desenho, mas abre caminho para um cliente acionar muitos spawns teto de spawns por turno no setup (ver subagentes-fanout-ilimitado)
seguranca-info-04 — em dev, a chave de sessão é derivada do segredo OIDC sem aviso baixa P be/src/auth/config.ts:86-111 surpresa em dev; produção exige chave própria (fail-closed) aviso explícito ao usar a derivação
seguranca-info-05 — webhook aceita http:// em host público baixa P be/src/webhooks/dispatcher.ts:200-206; be/src/net/ssrf.ts:356-358 assinatura e corpo expostos a MITM aceitar só https: em produção
seguranca-tools-05 — setup_update ecoa o override (redigido) no resultado baixa P be/src/tools/native/setup.ts:494-498 cópias de ••• no histórico; nenhuma chave real vaza documentar; opcionalmente encurtar o eco
seguranca-tools-06 — tools nativas de setup rechecam o papel info — setup.ts:62, 246, 425; be/src/setups/visibility.ts:18-19 defesa em profundidade intacta (sem achado) manter

↑ voltar ao índice

Varredura focada da investigação de lacunas (webhooks, rotas REST e versionamento; sem rodada adversária):

Ponto Evidência O que encontrou Recomendação
Webhooks fora do guard (A1, A2, A5) be/src/webhooks/dispatcher.ts:113-137, 218, 237-238 o fetch global segue até 20 redirects sem revalidar e leva a assinatura HMAC no salto; DNS rebinding entre validação e conexão o mesmo de seguranca-ssrf-01: safeRequest
allowPrivateHosts() morta (A3) dispatcher.ts:79-81 a função existe e nada a consulta apagar ou usar de fato
Webhook sem TLS obrigatório (A4) dispatcher.ts:206; docs/api-terceiros.md:393-398 aceita http:; a doc promete o guard e não fala de TLS só https: em produção
/api/mcp/test sem admin e sem limite próprio (B1) be/src/routes/mcp.ts:12-62; be/src/routes/index.ts:48 qualquer membro; 100 sondagens/min por IP o mesmo de seguranca-ssrf-02
test-connection com fetch global (B2) be/src/routes/agents.ts:312-321 sem fixar o endereço nem redirect: 'manual' o mesmo de seguranca-ssrf-03
/info e /readyz expõem topologia (B3, B4) be/src/routes/health.ts:90-127 modelo, workspace, host e porta; latências internas /info autenticado ou enxuto
Rate limit global só por IP (B5) be/src/server/index.ts:181-206 atrás de NAT, uma org divide 100/min; varreduras cabem na cota limite por org e usuário nas rotas sensíveis
Contrato Motor↔LB sem versão (C1) be/docs/contrato-load-balancer.md; be/src/provider/anthropic.ts nenhum header ou campo de versão no wire; a mudança SETUP-SIMPLE-01 do LB não tinha identificador X-Motor-Contract: lb-v1 no request e eco na resposta
SSO da Conta sem versão (C2) be/src/auth/oidc-client.ts:84-103, 181-218; be/src/auth/provisioning.ts:91-100, 195-200 "SSO Nomad v1" só em comentário; o webhook de provisionamento não tem versão de schema versão no envelope e recusa de versão maior desconhecida
MCP sem versão de contrato (C3, C5) be/src/tools/mcp-client.ts:240-243; be/package-lock.json:1444-1451 o handshake envia a versão do Motor (0.2.0), não a do contrato com MyPanel e AgentPack negociar e logar a versão do protocolo; fixar a lista de tools por servidor
Refresh de credencial MCP por HTTP sem guard be/src/tools/mcp-client.ts:97-134; be/src/config/schemas.ts:201 auth.refresh.url aceita qualquer URL (ex.: metadata da nuvem) resolveAndValidate também no refresh

↑ voltar ao índice

Pontos confirmados como corretos pela mesma varredura: RBAC com exigirAdmin em webhooks, workflows e test-connection (be/src/http/rbac.ts:29-46); remoção de identidade do corpo e da query (be/src/http/auth.ts:161-169, 322-338); redação de segredos (be/src/config/redact.ts) e chave presa à origem (D12, be/src/config/secret-origin.ts); boot fail-closed fora de desenvolvimento (be/src/auth/config.ts:146-174); JWT OIDC com iss, aud, exp e nonce e recarga de JWKS; webhook de provisionamento da Conta com HMAC, janela de 5 min e idempotência; sessão com refresh_token cifrado (AES-256-GCM) e rotação com trava no Redis; passo http do workflow com safeRequest (há teste provando que o redirect entre origens derruba credenciais); pool MCP com chave por destino e watchdog de vazamento.

Métricas (segurança): be/src/net/ssrf.ts 563 linhas (guard completo: IPv4 e IPv6 normalizados, "qualquer registro interno bloqueia", conexão fixada no IP validado, redirect manual); be/src/webhooks/dispatcher.ts 334; be/src/tools/mcp-client.ts 390; be/src/auth/session.ts 374; be/src/auth/jit.ts 346; be/src/http/auth.ts 348; be/src/config/secret-origin.ts 212; be/src/setups/visibility.ts 88. Bloqueio de 28 chaves no corpo de erro do provider antes de chegar à UI (turn-runner.ts:1398-1432).

3.8 Qualidade e modularidade

[crítica · confirmado; sev. contestada alta→crítica] qualidade-abort-sem-assinante — "Parar" entre réplicas documentado e sem assinante · Esforço P

Não verificados (média, baixa, info):

Achado Sev. Esf. Evidência Impacto Recomendação
qualidade-any-no-turn-runner — turn-runner.ts concentra casts any apesar de InboxItem ser bem tipado média M be/src/engine/turn-runner.ts:561, 745, 761, 776-777, 793-795, 805, 809; be/src/engine/mailbox.ts:45-52. Medido nesta revisão: 16 as any e 15 : any (o scanner citou 29) o caminho mais quente (mailbox → LLM) roda sem o cinto do TypeScript; mudança de formato quebra em produção, não no build type predicates para InboxItem e tipar o metadata da thread; remover os casts num lote dedicado
qualidade-arquivos-gigantes-workflows — workflows concentrados em 3 arquivos (~6 mil linhas) média M be/src/workflows/reconcile.ts (2456); definition.ts (1911); be/src/tools/native/workflow.ts (1581); contraste: expanders.ts (860, sem as any) difícil revisar, testar isolado e paralelizar trabalho sem conflito fatiar o reconcile por tipo de passo, como o expanders.ts já faz; quebrar workflow.ts por verbo
qualidade-turn-runner-mistura — turn-runner.ts mistura 7+ responsabilidades média M turn-runner.ts (1583 linhas: drain, compactação, título automático, custo, persistência, taxonomia de erro, estimativa de tokens); turn-runner.ts:1401 (re-export de response-events) qualquer mudança (ex.: regra de custo) mexe no arquivo que decide o que o LLM vê extrair taxonomia de erro e estimativa para provider/ e o título automático para módulo próprio; deixar só a orquestração
qualidade-loop-cabecalho-generateText — o cabeçalho fala generateText; o código usa streamText baixa P be/src/agent/loop.ts:2, 6, 499, 508; o comentário da linha 452 confirma o streamText confunde quem lê o mecanismo atualizar o cabeçalho
qualidade-scripts-sem-guarda — 8 scripts de dev executam no import baixa P be/src/engine/__spike-prepare-step.ts; be/src/engine/__it-cost.ts; be/src/persistence/__smoke.ts (e outros __it-*) importar por engano executa o script (o spike chama o provider real) guarda process.argv[1] === fileURLToPath(import.meta.url)
qualidade-console-em-vez-de-logger — events.ts usa console.warn baixa P be/src/persistence/events.ts:124, 130, 318 avisos do caminho mais lido sem correlação de request, org e thread logger estruturado ou o canal de métricas
qualidade-zadd-any — cast as any no zadd esconde o bug que já aconteceu baixa P events.ts:176-184, 194 (o comentário documenta o incidente da timeline em branco) a mesma classe de bug pode voltar sem aviso helper tipado para o zadd com teste do flag NX único
qualidade-acoplamento-engine-setups — o engine importa o serviço de setups info — be/src/engine/turn-runner.ts (importa loadEffectiveConfig de setups/service); be/src/engine/orchestrator.ts:319, 391 funciona; atrito se setups ganhar ciclo de vida próprio decidir: manter e documentar, ou injetar a config efetiva pronta
qualidade-testes-dependem-de-ambiente — integração exige Postgres, Redis e provider reais info — be/src/engine/__it-cost.ts:17-24; __spike-prepare-step.ts:25; be/src/persistence/__smoke.ts a camada que prova comportamento real não protege cada PR promover os invariantes baratos a testes unitários sem infra
qualidade-contrato-lb-explicito — contrato Motor↔LB explícito e citado no código (ponto forte) info — be/docs/contrato-load-balancer.md; be/src/provider/anthropic.ts:1-23, 51-52, 74; be/src/tools/registry.ts:1-21 ponto forte; sem número de versão versionar o contrato e repetir o padrão nos contratos de MCP e SSO
qualidade-fe-zero-any — frontend com zero as any; backend com 60 info — busca em fe/src e be/src sem testes; fe/src/features/thread-runtime e fe/src/components/timeline o frontend prova que o padrão é viável no mesmo time lint que falhe em as any novo fora de fronteiras documentadas

↑ voltar ao índice

Métricas (qualidade): backend com ~57 mil linhas em ~220 arquivos; frontend com ~73 mil linhas em 498 arquivos. Testes: 69 arquivos no backend e 142 no frontend; workflows cobertos em níveis L1 a L7. as any em produção: 60 no backend (16 em turn-runner.ts, medido nesta revisão) e 0 no frontend. Maiores arquivos do backend: reconcile.ts 2456, definition.ts 1911, turn-runner.ts 1583, tools/native/workflow.ts 1581, expr.ts 1029, runs.ts 962, agent-step.ts 956, setups/service.ts 868. Maiores do frontend: validate.ts 1902, ws.ts 668, config-draft.ts 579, blocks.tsx 535. Scripts de dev fora do build: 8 arquivos, ~1.900 linhas, sem imports em produção.

3.9 Refutados pelos revisores

Estes achados foram derrubados por contra-evidência e não entram no roadmap. A coluna da direita registra o que sobra de válido, quando sobra.

Achado refutado Por que caiu O que sobra
loop-sdk-slot-por-turno — o slot do gate é por turno, não por request; o default do doc diverge O slot por turno é desenho intencional (RATE-003, defesa contra fan-out de subagentes), comentado no código. O "16 × 100" está só no JSDoc de org-concurrency.ts:14; o default real, o .env, os docs operacionais e o teste L4-X02 estão em 100. Corrigir o JSDoc. A granularidade do slot vira decisão de produto (seção 7).
loop-sdk-fanout-ilimitado — fan-out ilimitado fura o rate limit e abre o circuit da org As defesas RATE-001 (rate limit por org e usuário), RATE-003 (gate por org) e P-23 (circuit breaker de 3 hits/60s, 90s aberto) estão no código e nos testes; cada chamada ao LLM, de pai e de filho, adquire slot; os submits internos isentos do rate limit são intencionais. A falta de teto por árvore foi confirmada em subagentes-fanout-ilimitado (3.4).
loop-sdk-lockfile-divergente — o runtime roda ai 5.0.179, mas o abort foi conferido na 5.0.253 Erro factual: o Dockerfile do be roda npm ci com o be/package-lock.json (5.0.253 e 2.0.101); o 5.0.179 é do lock da raiz (dev e CI). O lock do be já estava em 5.0.253 antes do fix do abort. Não existe check-lock:be para pegar drift do lock do be (risco baixo).
loop-sdk-parse-args — argumento ilegível vira {} e a tool executa assim mesmo parseToolArgs só alimenta o evento enviado à UI; a tool executa com o input do SDK; JSON quebrado o SDK recusa. O caso de workflow_publish com definição vazia é comportamento documentado. JSON válido com campo faltando executa: confirmado em tools-prompt-args-sem-validacao (3.3).
tempo-real-fila-descarta-stop — fila de envio cheia descarta Parar e reset em silêncio O flush no onopen já reenvia stop e reset na reconexão; o descarte só ocorre com a fila saturada (100 itens ou 256 KB) no momento do clique — inviável no caminho típico; a mensagem de usuário é coalescida. Só o silêncio na UI quando o descarte acontece (raro).
tempo-real-resync-descarta-paginas — o RESYNC troca o histórico paginado pelos 100 mais novos O RESYNC poda com preservação (pruneLiveOnResync), mantendo o turno em andamento (há teste); páginas anteriores continuam paginando por REST; o ciclo "1 por minuto em aba ociosa" não casa com esse caminho. Nada.
dados-events-rebuild-full — abrir o WS carrega a thread inteira e manda 1 frame por evento, travando a UI O front ignora o replay (runtime.ts:140) e a UI já é paginada por REST; reabrir costuma acertar o cache quente do Redis. O desperdício no servidor foi confirmado em tempo-real-handshake-repete-historico (3.2).
dados-isbusy-local — isBusy só enxerga a réplica local (como "alta") Evidência correta, mas o doc oficial classifica como médio, a mitigação de stale (30 min) cobre a maioria dos casos e o busy:false no handshake é corrigido pelo próximo evento. Confirmado como alta em tempo-real-busy-local (3.2): contestado entre revisores.
dados-abort-sem-subscriber — o Parar entre réplicas publica num canal que ninguém assina Latente: produção roda 1 instância do be e todos os fluxos atuais abortam localmente. Confirmado por 4 revisores de outras dimensões (crítica a média): tema transversal em 3.0.
<container interno (Redis compartilhado)> — fila e cache na mesma instância Redis (perda silenciosa de jobs) Decisão registrada pelo dono (docs/operacao.md §12, item 8); a instância está em noeviction, a política que o BullMQ exige. A perda descrita não está acontecendo. Risco aceito: instância sem teto de memória para dois usos opostos; separar REDIS_QUEUE_URL é decisão do dono (seção 7).
workflows-tls-db-prod-info — Postgres sem TLS em produção DATABASE_TLS_POLICY=allow-insecure foi decisão do dono (commit 1e36998): o Postgres roda no mesmo host da aplicação; o aviso de boot cobre o caso de mudança de topologia. Revisar se o Postgres sair do host.

↑ voltar ao índice

4. O Motor frente às melhores práticas

Legenda da coluna "Motor hoje": coberto, parcial, não ou não avaliado (o scan não mediu). Todas as afirmações sobre o Motor vêm das seções 2 e 3.

4.1 Arquitetura de agentes e multiagentes

Prática (fonte) Motor hoje O que mudar
Começar simples e só adicionar complexidade medida por avaliação (Anthropic — Building effective agents) Parcial. A arquitetura já distingue chat, subagente e workflow; não há suíte de avaliação de qualidade nos dados do scan Criar avaliações por caso de uso antes de ampliar fan-out ou trocar o loop
Separar workflow (caminho em código) de agente (o modelo decide) (Anthropic; OpenAI Agents SDK — orquestração) Coberto. "agent_spawn é quando o LLM decide paralelizar; o workflow é quando a definição decide" Manter; documentar ao modelo quando usar cada um
Chaining, routing, paralelização (seções e votação), orchestrator-workers e evaluator-optimizer (Anthropic) Coberto pelos 11 tipos de passo: ${steps.X.output} (chaining), switch (routing), map (seções), verify (votação), orquestradora + workflow_* (orchestrator-workers), loop com until (evaluator-optimizer) Routing ainda depende de um passo agent classificador; o campo model por passo é ignorado com aviso (workflow.ts:178) — avaliar alias de modelo por passo
Preferir sequencial; paralelizar só trabalho independente (Cognition — Don't build multi-agents) Parcial. O prompt orienta frentes independentes; não há aviso sobre escrita concorrente no mesmo workspace Particionar o escopo de escrita por filho e dizer isso no prompt (subagentes-workspace-compartilhado)
Agente-como-ferramenta como padrão; handoff só quando outro agente assume a conversa (OpenAI Agents SDK — handoffs) Coberto (subagente com retorno por notificação); não há handoff Manter; se um dia houver handoff, filtrar o histórico (resumo não anonimiza)
Regras de dimensionamento: 1 agente com 3–10 chamadas; 2–4 subagentes para comparação; 10+ só em pesquisa complexa (Anthropic — Multi-agent research system) Não. Nenhuma regra de cardinalidade codificada; largura e profundidade ilimitadas Escrever as regras no prompt da orquestradora e impor tetos determinísticos (5.4)
"Amortecedor" de delegação: só trilha grande, independente e paralela; nunca delegar para verificar o próprio trabalho; preferir 1 filho (Anthropic — Prompting Claude Opus 5) Não codificado Incluir no system prompt da orquestradora (instrução orienta, teto impõe)
Modos de falha multiagente (MAST): repetição, ação incoerente, não encerrar; verificação em dois níveis e mensagens estruturadas (Why do multi-agent LLM systems fail?) Parcial. Tetos de run existem nos workflows; no chat, 999 steps sem teto por turno; a notificação é estruturada, mas vira texto com moldura de autoridade Teto e condição de parada por turno e por árvore; envelope estruturado com intenção, status e incerteza; checagem do objetivo antes de notificar a conclusão

↑ voltar ao índice

4.2 Delegação e subagentes

Prática (fonte) Motor hoje O que mudar
Contrato de delegação com objetivo, fronteiras, formato, tools, orçamento de esforço e condição de parada (Anthropic — Multi-agent research) Parcial. A descrição pede tarefa autocontida e o prompt pede "o resultado, não o caminho"; faltam fronteiras de escrita, orçamento e condição de parada Contrato explícito no agent_spawn (5.4)
Contexto isolado, handoff só pelo prompt de delegação, retorno só do resultado final (Claude Agent SDK — Subagents) Coberto. O filho nasce sem o histórico e sem o system prompt do pai; devolve o finalText Capar o retorno (hoje integral)
Filhas gravam artefatos e devolvem referências leves (Anthropic — Multi-agent research) Não. O finalText volta inteiro e fica no histórico do pai Resumo denso de até 4–8 mil chars + referência ao threadId ou artefato
Background assíncrono com notificação; foreground só quando bloqueia (Claude Code — Subagents) Coberto e à frente: a Anthropic ainda lista o modo assíncrono como evolução; o Motor já o opera com exactly-once e agrupamento Manter; medir a latência ponta a ponta
Tetos determinísticos: profundidade, concorrência, orçamento e limite de turnos por filho (Claude Agent SDK — Subagents) Parcial. Gate de 100 chamadas por org (por processo) e orçamento mensal; sem profundidade, largura, orçamento por árvore nem limite de turnos por filho Tetos por árvore com erros legíveis para a orquestradora (5.4)
Papéis com tools mínimas (allowlist) (Claude Agent SDK — Subagents) Parcial. O filtro tools do spawn só alcança MCP; as nativas passam Aplicar o filtro também às nativas; padrão de negação
Escanear a saída do filho contra imitação de tags e marcadores de papel (Claude Agent SDK — Subagents) Não. O texto do filho entra cru dentro de [NOTIFICAÇÃO DO SISTEMA] Delimitar como dado não confiável e neutralizar marcadores antes de entregar ao pai
Retomar subagente pela identidade, com transcript persistente (Claude Agent SDK — Subagents) Não avaliado pelo scan (o transcript do filho persiste na thread dele) Avaliar retomada explícita de um filho pelo threadId

↑ voltar ao índice

4.3 Contexto e prompt

Prática (fonte) Motor hoje O que mudar
Contexto mínimo útil, recuperação sob demanda e revelação progressiva (Anthropic — Effective context engineering) Não. Todo step leva todas as tools (362 num turno real) e o system completo Núcleo pequeno + busca de tools; detalhes em descrições, não no prompt
System prompt "na altitude certa", mínimo e testado, com exemplos (Anthropic — Effective context engineering; Prompting best practices) Parcial. Prompt nativo de 25,9 mil chars com as mesmas regras repetidas nas descrições das tools; cita a tool inexistente warpgrep; afirma "sem MCP externo" Fonte única; seção de tools gerada a partir das tools reais do turno
Compactação que preserva decisões; notas externas; filhos que devolvem resumos (Anthropic — Effective context engineering) Parcial. Compactação própria na fronteira do turno, com checkpoint; só enxerga a explosão no turno seguinte; sem notas externas por thread Teto por step; avaliar memória por thread em store dedicada
Context editing no servidor para limpar resultados antigos de tools (Anthropic — Context editing, beta) Não Avaliar quando o LB repassar o header beta (5.6)
Histórico só com acréscimo; blocos de thinking devolvidos intactos (Anthropic — Thinking) Parcial. Histórico append-only no banco; mas o reasoning é removido antes de persistir, então o replay de thinking assinado não funciona Decidir: poda sempre (documentada) ou guardar as assinaturas e devolvê-las byte a byte
Thinking adaptativo com effort (não budget_tokens) (Anthropic — Thinking) Coberto pelo contrato com o LB (adaptativo, resumido, effort alto); há código morto com budget_tokens Remover o código morto; effort por tipo de tarefa e fixo por thread (não quebra cache)

↑ voltar ao índice

4.4 Tools

Prática (fonte) Motor hoje O que mudar
Poucas tools compostas por fluxo, em vez de CRUD cru (Anthropic — Writing tools for agents) Parcial. As 23 nativas são compostas; os MCPs entram como o servidor os expõe Curadoria de MCPs por setup
Namespaces por serviço e recurso (Anthropic — Writing tools) Não. Nomes crus; colisão decidida em silêncio pelo último MCP servidor__tool com alias curto, validado por avaliação
Descrições explícitas e sem ambiguidade; erros acionáveis com exemplo (Anthropic — Writing tools) Parcial. Descrições nativas ricas (e longas); workflow_* recusa argumento ruim com erro legível; agent_spawn não valida Validação de argumentos em todas as nativas
Paginação, filtro e truncamento com padrões sensatos (o Claude Code limita resposta de tool a ~25 mil tokens) (Anthropic — Writing tools) Não. Resultado de tool sem teto (só o workflow corta em 6 mil chars) Teto por resultado com marcador e leitura paginada
Busca de tools com defer_loading para catálogos grandes (Anthropic — Tool search tool; Advanced tool use) Não. Um passo "trivial" de workflow custou US$ 0,25 e ~83 mil tokens de entrada, boa parte de catálogo Adotar quando o LB repassar server tools; antes disso, activeTools por step com seleção própria
Retornos semânticos (nomes, não IDs crus) e verbosidade controlável (Anthropic — Writing tools) Não avaliado para os MCPs; o shortId de subagente existe e não é usado (o id exposto é UUID) Avaliar nos MCPs próprios (MyPanel e AgentPack)
Autorização no ponto da tool; guardrail como camada, não como permissão (OpenAI Agents SDK — Guardrails) Parcial. toolPolicy por setup e negação de tools dentro de passos; SSRF aberto em 4 superfícies; MCP de thread aceito sem allowlist Guard SSRF em toda saída; allowlist de servidores por org

↑ voltar ao índice

4.5 Loop, API Messages e SDK

Prática (fonte) Motor hoje O que mudar
Consumir o SSE por eventos tipados; acumular input_json_delta até content_block_stop (Anthropic — Streaming) Coberto pelo pacote ai No SDK nativo, messages.stream com finalMessage (TypeScript SDK)
Tool use manual: tool_result primeiro, todos os resultados numa mensagem só, is_error instrutivo (Handle tool calls; Parallel tool use) Coberto pelo SDK; argumento inválido não vira is_error No loop nativo: validar contra o schema e devolver is_error em vez de executar
Ramificar por stop_reason (tool_use, pause_turn, refusal, max_tokens, janela excedida) (Handling stop reasons) Parcial. Overflow de janela vira erro terminal sem compactar e retentar Ramo próprio para cada motivo (5.1)
Cancelamento com AbortController e stream.controller.abort() (SDK helpers) Coberto com nome mágico AbortError + guarda de 5s Cancelamento documentado do SDK nativo, mantendo a guarda
Retries só para transitórios (408, 409, 429, 5xx, rede), respeitando retry-after; timeout por request (TypeScript SDK; Rate limits) Parcial. 3 retries sem jitter visível; nenhum timeout padrão Jitter, timeout por request e detector de stall
Prompt caching com até 4 breakpoints e TTL de 5 min ou 1 h (Prompt caching) Parcial. O seed usa 2 de 4 breakpoints (system e último user com 1 h); o default do schema é 5 min e diverge do contrato; cache_creation não é contado TTL e breakpoints declarativos por setup; contabilizar a escrita de cache
Contar tokens antes de enviar (Token counting) Não. Estimativa por chars/4 que ignora as tools Contar com o modelo de destino quando o LB permitir; senão, incluir as tools na estimativa
Batch API para volume assíncrono, com 50% de desconto (Batch processing) Não Avaliar para avaliações, backfills e classificações em lote
Tool runner do SDK como base do loop, com livro-razão durável fora dele (TypeScript SDK, beta) Não Avaliar na migração; a persistência por step continua sendo do Motor
Conferir GA × beta antes de produção (Features overview) Não avaliado Recursos beta só atrás de flag por setup, com fallback

↑ voltar ao índice

4.6 Execução durável de workflows (mercado)

Prática (fonte) Motor hoje O que mudar
Cada chamada externa como passo durável com identidade estável (Inngest — Steps; DBOS) Coberto e à frente: stepKey determinístico, jobId determinístico, claim condicional, unique por tentativa e "passo com output nunca reexecuta" Manter
Idempotência no destino: o journal não substitui a deduplicação externa (Restate — Durable steps) Parcial. No máximo um passo por run pode ser pago duas vezes num crash; o passo http depende de chave de idempotência do autor Gerar e enviar uma chave de idempotência por passo nas chamadas externas
Retry no nível da atividade, com backoff exponencial com teto e erros não retentáveis (Temporal — Retry policies; Activities) Coberto: até 10 retries, backoff de até 600s, 4xx terminal, 5xx retenta, 429 com Retry-After Manter
Humano no loop com estado persistido e retomada (LangGraph — Interrupts; AutoGen — Human in the loop) Coberto: passo human com waiting persistido e timeout de até 30 dias Manter
Terminação explícita com motivo (AutoGen — Teams) Coberto nos workflows (orçamento, maxIterations, until, motivo de parada); não no chat (999 steps) Teto e motivo de parada também no turno do chat
Controle determinístico no fluxo; agentes só dentro dos passos (CrewAI — Flows) Coberto pelo reconciliador Manter
Trace único por workflow, com redação de dados sensíveis (OpenAI Agents SDK — Tracing) Parcial. Trilha de auditoria (Turn com snapshot e hash, events com seq, log do run), sem painel do que foi enviado; o snapshot grava segredos em claro Painel de inspeção com redação (5.3)
Estado serializável para retomar em infraestrutura sem estado (AutoGen — State) Parcial. Estado durável no Postgres, mas o convo do turno vive em memória sem fallback Fallback do convo (memória → Redis → banco)

↑ voltar ao índice

4.7 Tempo real

Prática (fonte) Motor hoje O que mudar
Salas por thread e usuário, estado só no servidor (Socket.IO — Rooms) Parcial. Salas por thread, em memória de processo Salas distribuídas por adapter
Não assumir entrega (padrão "no máximo uma vez"); persistir antes de emitir e reenviar por cursor (Socket.IO — Delivery guarantees) Parcial. O histórico persiste antes; não há seq por sala nem ack; o replay reenvia tudo seq por sala com replay a partir do cursor do cliente
Recuperação de estado para quedas curtas, com fallback de ressincronização (Connection state recovery) Não connectionStateRecovery (exige o adapter de Redis Streams) + replay por seq
Adapter Redis entre nós; o Streams adapter suporta recuperação (Redis Streams adapter; Redis adapter; Multiple nodes) Não (o runbus usa Pub/Sub próprio só para runs) Redis Streams adapter; sticky só se mantiver o transporte por polling
Autenticar no handshake, com renovação de token (Socket.IO — Middlewares) Coberto no upgrade (bearer, cookie, ticket de uso único) Revalidar a sessão periodicamente
Reconexão com backoff e jitter; ack com timeout para comandos (Client options; Emitting events) Parcial. Backoff sem jitter; sem ack Jitter; ack para envio, Parar e reset
Backpressure na aplicação: agregar deltas, volatile para intermediários, marcos numerados (Socket.IO — Emitting events; Server options) Parcial. O canal de run coalesce a 2 Hz; o chat não faz streaming token a token Coalescer deltas no chat (janela de 30–100 ms) se ligar o streaming
Uma conexão multiplexando várias conversas (OpenAI — WebSocket mode) Não. 1 socket por thread + 1 socket só para a sidebar Uma conexão por aba com várias salas
SSE como transporte só entre servidor e provider (Anthropic — Streaming) Coberto: SSE só entre o SDK e o LB; a UI usa WebSocket Manter

↑ voltar ao índice

5. Arquitetura-alvo proposta

Princípio: completar o que o desenho já prevê, sem big-bang. Cada mudança estrutural entra atrás de flag, com shadow e rollback.

5.1 Loop com o SDK nativo da Anthropic (@anthropic-ai/sdk, API Messages, loop próprio)

O SDK nativo troca só o transporte e o loop. O contrato com o LB fica igual: Anthropic Messages, model: "proxy-managed", SSE, metadata.user_id estável por conversa.

flowchart TD
    A[montar request<br/>system em camadas + messages do log<br/>tools com cache_control orçado] --> B[messages.stream no proxy LB]
    B --> C[consumir SSE<br/>deltas para o sink, inputs de tool acumulados<br/>usage com cache_creation]
    C --> D{stop_reason}
    D -->|end_turn| E[persistir e fechar o turno]
    D -->|tool_use| F[validar inputs contra o schema<br/>executar em paralelo com timeout<br/>todos os tool_result numa mensagem]
    F --> G[persistir o step<br/>conferir orçamento e tetos<br/>anexar notificações já persistidas]
    G --> B
    D -->|max_tokens, refusal, pause_turn| H[ramo próprio por motivo]
    D -->|janela estourada| I[compactar + 1 retry]
  1. Montagem por turno. system em blocos (as camadas do builder de 5.3); messages traduzidas do log de events (user e assistant com blocos text, tool_use e tool_result; thinking preservado byte a byte ou podado por flag); tools com input_schema, em ordem alfabética estável, com cache_control dentro do orçamento de 4 breakpoints (prioridade: system, último user, checkpoint, tools).
  2. Por request. client.messages.stream({ model: 'proxy-managed', max_tokens: 64000, system, messages, tools, thinking: { type: 'adaptive', display: 'summarized' }, output_config: { effort }, metadata: { user_id } }) apontado para o proxy. Confirmar no repositório oficial os nomes exatos das opções de endpoint e autenticação (baseURL, apiKey ou authToken, headers por request) antes de codar.
  3. Consumo do SSE. Deltas de texto vão para o sink (WebSocket); inputs de tool acumulados até content_block_stop; usage lido de message_start e message_delta, incluindo cache_creation_input_tokens.
  4. stop_reason. end_turn persiste e fecha. tool_use valida cada input contra o schema (inválido vira tool_result com is_error, nunca {} silencioso), executa em paralelo (Promise.allSettled, timeout por tool, abort encadeado), anexa todos os resultados numa única mensagem user, persiste o step (fecha o buraco do crash), confere orçamento e tetos de steps e tokens e continua. max_tokens, refusal, pause_turn e janela excedida têm ramos próprios; o 400 de janela estourada dispara compactação e 1 retry automático.
  5. Cancelamento. Parar, timeout e orçamento chamam stream.controller.abort() e abortam as tools; a guarda de 5s fica como rede; detector de stall (N minutos sem evento SSE).
  6. Retries e erros. Só transitórios tipados (429, 5xx, rede), respeitando retry-after, com jitter. O SDK oficial tem maxRetries (padrão 2) e timeout por request (padrão 10 min) — configurar explicitamente; os erros tipados do SDK (limite de taxa, erro interno, timeout de conexão) viram errorKind próprios, inclusive a negação do gate de concorrência como "ocupado, tente de novo".
  7. Notificações no meio do turno. Append direto em messages entre requests, só depois de persistir — o problema do P-07 desaparece por construção, junto com o buffer acumulado manual.
  8. Compactação. A própria continua na fronteira, com fallback de emergência; o compactador passa pelo mesmo slot e entra no custo.

Ganhos. Controle total do array por request; cache fino (TTL de 1h, leitura de cache_creation); thinking assinado preservado; cancelamento previsível; retries e timeouts explícitos; fim do reparse do body por step e do interceptor; caminho aberto para tool search, context editing e structured outputs (que pode aposentar o scraping de JSON do agent-step) — se o LB repassar betas e server tools.

Riscos. Reescrever o runner de tools (paralelismo, validação, JSON parcial, is_error); traduzir o histórico legado (o Postgres guarda formas do AI SDK); quebrar o contrato byte a byte com o LB (o SDK oficial compõe o caminho /v1/messages a partir do baseURL, diferente do AI SDK; headers anthropic-version e betas; nomes de eventos SSE; Authorization: Bearer com x-api-key placeholder; metadata.user_id); perder a abstração multiprovider (que o Motor hoje não usa).

Migração em 4 etapas.

  1. Tradutor de histórico (log ↔ Messages nativo) com suíte de paridade sobre amostra real; snapshot do wire atual validado contra o LB real, junto com o dono do LB.
  2. Interface AgentTransport com duas implementações (AI SDK e nativa); runner nativo atrás de flag por setup; shadow-diff do wire.
  3. Compactador no transporte novo.
  4. Nativo como padrão, com rollback por flag; depois, remover ai e @ai-sdk/anthropic.

Esforço. A investigação de lacunas estima ~400 linhas alteradas, concentradas em loop.ts, e 6–8 dias úteis de um dev sênior com a rede de testes. A dimensão do loop estima semanas, contando o runner de tools, o histórico legado, o compactador e a bateria de regressão. Planejar com a estimativa maior.

5.2 Tempo real só por WebSocket, com socket.io

flowchart LR
    FE[front: 1 conexão por aba<br/>salas user, thread, run] -->|handshake autenticado<br/>ack em user_message, stop, reset| GW[gateway socket.io /v1<br/>em cada réplica da API]
    GW --- AD[(Redis Streams adapter<br/>salas globais + recuperação)]
    WK[worker: passos de workflow] -->|emite nas salas run| AD
    ENG[engine do turno] -->|eventos com seq e v<br/>persistidos antes de emitir| AD
    GW -->|replay por seq| DB[(Postgres: events e workflow_events)]
Fase O que entra Polling que sai
F0 gateway, adapter, salas e métricas, em paralelo ao /ws —
F1 thread_busy com seq na sala user: 2º socket da sidebar
F2 thread.created, thread.updated, thread.deleted na sala user: lista de threads (8s)
F3 subagent.spawned, subagent.result, subagent.status na sala da raiz árvore e graph (3–5s); modal (2s + 3s)
F4 run.status e step.updated nas salas user: e run: lista de runs (5s); log (4s); passo (3s)
F5 saúde pelo estado do socket + navigator.onLine (readyz só para infra) saúde (30s)
F6 handshake magro; opcional: streaming token a token no chat —

↑ voltar ao índice

Corte. socket.io não conversa com cliente WebSocket puro (nem o contrário): o corte é por cliente. Rodar os dois em caminhos separados, com shadow (comparar frames), antes de desligar o /ws. A investigação de lacunas estima ~5–7 dias úteis para a troca de transporte, além das fases F1–F6.

Alternativa (decisão do dono): manter o ws nativo e publicar os eventos de turno no Redis (o canal :stream já existe) com um assinante por réplica. Resolve as várias réplicas com menos mudança, mas sem ack, recuperação e salas prontas: seq, replay e ack teriam de ser construídos à mão.

5.3 Tools e system prompt modulares, com painel de inspeção

Builder de prompt por camadas. Cada camada tem nome, versão, hash e tamanho, em ordem fixa: (1) núcleo do Motor; (2) política da org; (3) persona do setup; (4) instruções da thread; (5) voz; (6) notificações, sempre como dado delimitado. Semântica explícita: o padrão sugerido é somar com rótulo; substituir só com flag explícita e aviso na UI. A seção "suas ferramentas" é gerada a partir das tools realmente montadas no turno (acaba o "sem MCP externo" e o warpgrep). O arquivo base fica em cache por mtime.

Registro de tools.

Módulos (sugeridos pela investigação de lacunas): tools/transport.ts (handshake, listagem, timeouts, refresh); tools/serialize.ts (cache_control, metadata, log); agent/system-prompt.ts (o builder); engine/spawn-policy.ts (idempotência, herança, tetos).

Painel de inspeção — "o que foi enviado ao modelo".

5.4 Protocolo agente↔subagente

sequenceDiagram
    participant O as Orquestradora
    participant M as Motor
    participant F as Filho
    O->>M: agent_spawn com contrato<br/>objetivo, fronteiras, formato, tools, orçamento, parada
    M->>M: confere tetos de profundidade, largura e orçamento da árvore
    M->>F: thread própria + tarefa
    F->>M: resultado: status, resumo de até 4–8 mil chars,<br/>referência ao threadId ou artefato, usage
    M->>O: notificação delimitada como dado não confiável
    O->>M: Parar ou agent_abort
    M->>F: abort em cascata na subárvore

5.5 Workflows

Manter o núcleo — reconciliador, barreiras, snapshot por setup, pause e cancel, custo consolidado —, que está à frente do mercado. Mudanças:

5.6 Contratos entre aplicativos

Motor ↔ Load Balance (proxy de LLM):

Item Hoje Proposta
Versão do contrato nenhuma no wire X-Motor-Contract: lb-v1 no request e eco na resposta; o Motor loga e alerta se faltar
Caminho e autenticação o AI SDK compõe /messages a partir do baseURL normalizado; Authorization: Bearer + x-api-key placeholder com o SDK oficial, confirmar /v1/messages, anthropic-version, betas e autenticação; snapshot do wire atual comparado em shadow
Usage o contrato (§6) diz que o total soma input, leitura e escrita de cache; o Motor só lê os dois primeiros o LB garantir cache_creation_input_tokens em message_start/message_delta; o Motor passa a ler
Custo e modelo o Motor nunca sabe o modelo; preço fixo de Sonnet o LB informar o modelo real e/ou o custo por request (header ou evento SSE), ou o Motor manter preço por rota
Erros overloaded_error chega sem status status HTTP coerente e retry-after em 429, 503 e 529
Recursos novos — o LB repassar betas e server tools (tool search, context editing, compactação) se o Motor adotar; confirmar antes de prometer
Roteamento de cache metadata.user_id estável por conversa manter o formato

↑ voltar ao índice

Outros aplicativos:

6. Roadmap priorizado em ondas

Esforço: P (horas a 1 dia), M (dias), G (semana ou mais). Risco = risco da mudança em si.

Onda 1 — Rápido (dias): estancar custo, segurança e travamentos

# Ação Achados Esforço Risco Depende de
1.1 Teto de steps por turno no chat (ex.: 50, configurável por setup), teto de tokens por turno e alerta loop-sdk-maxsteps-999, tools-prompt-maxsteps-999 P baixo (tarefas longas legítimas: tornar configurável) —
1.2 Timeout padrão do turno ou detector de stall loop-sdk-timeout-zero P baixo —
1.3 sideEffectWrite com retry real; notificação no meio do turno só depois de persistir, com reenfileiramento loop-sdk-sideeffect-retry, loop-sdk-p07-injeta-sem-persistir P a M baixo —
1.4 Fechar SSRF: webhooks via safeRequest (só https); resolveAndValidate em connectMcp, refresh de credencial e test-connection; /api/mcp/test só para admin, com rate limit seguranca-ssrf-01 a 04, tools-prompt-ssrf-mcp-test, varredura de lacunas P a M baixo (MCP interno legítimo: exceção explícita por env) —
1.5 Redigir o configSnapshot antes de gravar (hash calculado antes); headers no escopo da thread; o front não reenviar ••• tools-prompt-snapshot-com-segredos, seguranca-segredo-01 P baixo —
1.6 Abort entre réplicas: assinante + AbortReason serializado; abortChild só marca aborted se confirmou tema "Parar/abort" (3.0) P baixo — (pré-requisito da 2ª réplica)
1.7 Ligar REAPER_ENABLED=1 em exatamente uma réplica, com alerta no boot subagentes-reaper-desligado, dados-reaper-desligado P médio: o reaper usa o isBusy local, seguro com 1 réplica; antes da 2ª, ver 2.3 —
1.8 Cortar o finalText da notificação (4–8 mil chars) e o resultado de tool (20–50 mil), com marcador; notificação delimitada como dado não confiável subagentes-notificacao-sem-teto, tools-prompt-sem-limite-resultado, tools-prompt-notificacao-role-user P a M baixo —
1.9 Validação de argumentos nas tools nativas tools-prompt-args-sem-validacao M baixo —
1.10 Tempo real barato: keepalive do servidor (ou watchdog pausado com aba oculta), jitter na reconexão, badge "rodando" reconciliada, modal de subagente a 4s tempo-real-watchdog-reconecta-ocioso, -backoff-sem-jitter, -busy-travado-desconexao, -dialog-estoura-ratelimit P baixo —
1.11 Confirmar SHOW max_connections em produção e alinhar ao runbook (≥ 2100) ou ajustar DB_CONNECTION_LIMIT dados-pool-1000 × lacuna P médio (mudar o Postgres exige restart) decisão do dono
1.12 Correções de texto e doc: aviso de colisão, header 16→100, cabeçalho generateText, warpgrep, "sem MCP externo", docs/operacao.md; definir WF_AGENT_MAX_TOOL_STEPS e WF_DEFAULT_MAX_* vários (3.1–3.5) P baixo valores: decisão do dono
1.13 /info protegido ou enxuto; parar de ecoar a mensagem do provider; remover (ou reaproveitar) o publish do canal :stream seguranca-info-01, -info-02, tempo-real-events-canal-sem-ouvinte P baixo —

↑ voltar ao índice

Onda 2 — Médio (semanas): custo real e várias réplicas

# Ação Achados Esforço Risco Depende de
2.1 Fallback do convo (memória → Redis → banco) — vale já com 1 réplica dados-convo-redis-writeonly M médio —
2.2 cache_creation no contexto, custo e orçamento; preço por rota ou setup, ou custo vindo do LB loop-sdk-usage-cache-creation, loop-sdk-preco-fixo M baixo coordenação com o LB
2.3 Estado distribuído: isBusy pelo lock no Redis; fim de sessão por pub/sub; unique (threadId, clientMessageId); mapas com TTL/LRU; rate limit, gate por org e circuit breaker no Redis; negação do gate como "ocupado" retentável tema "Estado local" (3.0) e afins M médio 1.6
2.4 Tetos por árvore de subagentes, abort em cascata, filtro de tools também nas nativas, persona e escopo de escrita explícitos subagentes-* M médio (muda o comportamento do agente: medir) —
2.5 Cross-tenant: loadEffectiveConfig com a OrgConfig da org do turno seguranca-cross-tenant-01 G alto (toda a resolução de config; testar por org) antes de liberar troca de org na UI
2.6 Infra: separar REDIS_QUEUE_URL; PgBouncer ou max_connections dimensionado; reaper com liderança dados-pool-1000; risco aceito do Redis (3.9) M médio decisões do dono
2.7 Workflows: lista de passos sem output; log por cursor; lista de runs numa query; corridas de pause/resume/start; prazo de pausa workflows-* M baixo —
2.8 Compactação: overflow → compactar + retry; respeitar o Parar; compactador no slot e no custo; checagem por step loop-sdk-* de compactação M médio —
2.9 Protocolo WS versionado (v, catálogo único) e métricas do tempo real tempo-real-protocolo-sem-versao, -metricas-cegas M baixo — (base da onda 3)
2.10 Namespace de tools e toolPolicy por servidor__tool; seção de tools gerada; fonte única de instruções tools-prompt-* M médio (muda nomes vistos pelo modelo; quebra o cache uma vez) avaliação antes e depois
2.11 Retenção de events só de exibição; índices compostos; cache de autenticação; cache da lista de webhooks dados-* M baixo decisão de retenção

↑ voltar ao índice

Onda 3 — Estrutural (meses)

# Ação Seção Esforço Risco Depende de
3.1 Loop nativo @anthropic-ai/sdk (tradutor, AgentTransport, shadow, flag por setup, persistência por step) 5.1 G alto (contrato com o LB) snapshot do wire e acordo com o dono do LB; decisão sobre replay de thinking
3.2 socket.io F0–F6 com Redis Streams adapter, salas, seq, ack e recuperação 5.2 G médio a alto (corte por cliente) decisão socket.io × ws; 2.3 e 2.9; config do proxy
3.3 Tools sob demanda, curadoria por setup e painel de inspeção 5.3 G médio LB repassar server tools (para tool search); 2.10
3.4 Contratos versionados (LB, SSO, MCP, webhooks) 5.6 M baixo acordo com os donos dos outros apps
3.5 Avaliações reais (~20 casos por uso, juiz LLM e revisão humana) 4.1 M baixo —
3.6 Workflows: cache declarativo, alias de modelo por passo, agendamento, sinal para sub-run 5.5 M a G baixo —
3.7 Persistência por step; particionamento ou arquivamento de events 3.1, 3.6 G médio 3.1

↑ voltar ao índice

flowchart LR
    A16[1.6 abort entre réplicas] --> B23[2.3 estado distribuído]
    A17[1.7 reaper ligado] --> B23
    A111[1.11 max_connections] --> B26[2.6 banco e Redis]
    B23 --> R2[2ª réplica da API]
    B26 --> R2
    B29[2.9 protocolo versionado] --> C32[3.2 socket.io]
    B23 --> C32
    C32 --> R2
    B25[2.5 cross-tenant] --> ORG[troca de org na UI]
    B210[2.10 namespace de tools] --> C33[3.3 tools sob demanda]
    LB[acordo com o dono do LB] --> C31[3.1 loop nativo]
    LB --> B22[2.2 custo real]
    LB --> C33

Leitura do grafo: a 2ª réplica da API só deve subir depois de 1.6, 2.3, 2.6 e do broadcast distribuído (3.2 ou a alternativa ws + pub/sub); a troca de org na UI só depois de 2.5; tool search e o custo exato dependem do LB.

7. Perguntas de negócio e decisões do dono

Decisões necessárias:

  1. Escala: quando subir a 2ª réplica da API e qual a meta de simultaneidade (usuários e turnos)? Define a urgência da onda 2.
  2. Banco: confirmar max_connections em produção; manter "nunca reduzir o pool" e subir o Postgres para ≥ 2100, ou adotar PgBouncer?
  3. Redis: separar fila e cache (REDIS_QUEUE_URL), hoje juntos numa instância de 512 MB sem teto de memória?
  4. Política de custo: tetos padrão por turno (steps e tokens), por run (WF_DEFAULT_MAX_*) e por árvore de subagentes; orçamento fail-open (hoje) ou fail-closed quando o Redis cai?
  5. Cobrança: o custo exibido precisa ser exato para cobrar clientes? Isso decide entre preço por rota no Motor e custo informado pelo LB.
  6. SDK nativo: aprovar a migração incremental, abrindo mão da abstração multiprovider (hoje o Motor só fala Anthropic via LB)?
  7. Tempo real: socket.io (como pedido) ou evoluir o ws nativo com pub/sub? Streaming token a token no chat é desejado? O corte por cliente é aceitável?
  8. Multi-org: quando a UI terá troca de org? O conserto cross-tenant precisa vir antes.
  9. Tools: "nega por padrão" com curadoria por setup? Quais MCPs ficam no núcleo? Só admin cadastra MCP? Allowlist de hosts?
  10. System prompt: a camada da thread soma ou substitui a da org e a persona? O filho que herda o setup deve herdar a persona?
  11. Subagentes: rever D4/D6 (largura e profundidade ilimitadas) e adotar tetos por árvore? Isolar a escrita por filho (worktree)?
  12. Slot de concorrência: manter por turno (desenho RATE-003) ou passar a por request?
  13. Workflows: exigir budget? Implementar o agendamento (schedule) ou retirá-lo?
  14. Retenção: política para events de threads ativas (hoje mantidos para sempre, por decisão)?
  15. Thinking: assumir a poda (e documentar) ou investir em preservar o thinking assinado?
  16. Load Balance: quem coordena com o dono do LB as mudanças de contrato (cache_creation, modelo e custo, betas, versão)?
  17. Publicação: este relatório deve ir para o AgentPack de produção atual (agentpack-v0)? Requer conexão MCP autenticada com a instalação correta (ver o fim do documento).

Perguntas abertas que dependem de dados que o scan não leu:

8. Fontes

Anthropic — engenharia e pesquisa

Anthropic — Claude Code e Agent SDK

Anthropic — plataforma e API Messages

Mercado — multiagentes e execução durável

Tempo real

Fontes internas (repositório motor, HEAD 69e0b28)

Publicação

O relatório completo está neste arquivo, no sandbox motor: /workspace/.sbcache/scan-motor-2026-10-11/RELATORIO.md.

Não foi publicado no AgentPack. O MCP agentpack-orq desta sessão aponta para a instalação antiga (<host interno (AgentPack antigo)>), não para a produção atual (<host interno (AgentPack prod)>). Por instrução, nada foi publicado na instalação antiga e nenhuma configuração ou credencial foi alterada para contornar isso. Publicar na produção atual depende de uma conexão MCP autenticada com a instalação correta; o knowledgeId fica vazio até lá. Isso não bloqueia a conclusão do scan.