ADR-0007: Alvo de implantação
- Status: Accepted
- Data: 2026-05-12
- Responsáveis pela decisão: Waldemar Szemat
Contexto e Definição do Problema
Seção intitulada “Contexto e Definição do Problema”Esta é uma implementação de referência pública. Uma URL de demo ao vivo é em si
um sinal determinante: um leitor está a dois scrolls de clicar em um link que
abre um agente conversacional multi-turno real em um navegador. Essa URL deve
ser alcançável sem um muro de cartão de crédito, deve rodar a mesma imagem que o
Dockerfile do projeto constrói localmente, e deve ser implantada de uma forma
que nunca publique um commit não validado. Qualquer divergência entre a imagem
de desenvolvimento e a imagem implantada compromete a história do harness de
avaliação; o ponto inteiro é que o que roda em CI é o que roda em produção.
O agente é FastAPI / Uvicorn de processo único, com um armazenamento Chroma embarcado e um embedder de fallback embutido, despachando chamadas de LLM para um provedor externo (OpenAI por padrão; ver ADR-0002). Sem GPU, sem peso de modelo para hospedar, sem disco persistente além da pequena KB sintética. Suas primitivas de resiliência em processo (limitador de taxa, cache de respostas, checkpointer de HITL pausado) não são compartilhadas entre workers, então a implantação deve conseguir rodar uma única instância de forma determinística.
Como distribuímos uma URL de demo pública e sempre alcançável deste agente a custo quase nulo sob carga de demonstração, a partir do mesmo Dockerfile que o projeto distribui, implantada do lado do servidor e condicionada a uma matriz de CI verde para que uma build vermelha nunca alcance produção, e revertida em uma única operação quando algo quebra?
Direcionadores da Decisão
Seção intitulada “Direcionadores da Decisão”- Mesma imagem em todo lugar: a implantação deve construir a partir do mesmo
Dockerfileque o projeto distribui. Sem divergência de Dockerfile exclusivo de produção entre o laptop de um contribuidor, a CI e a produção. - Implantação condicionada, do lado do servidor: a produção deve publicar apenas a partir de uma tag de release cuja matriz de CI completa ficou verde, decidido pela plataforma, não por um operador rodando um comando à mão.
- Credenciais sem chaves: sem chave JSON de conta de serviço de longa duração armazenada como segredo de CI; a implantação se autentica com um token federado de curta duração.
- Redeploys que preservam segredos: as credenciais de provedores e integrações vivem em um armazenamento de segredos gerenciado e sobrevivem a cada redeploy sem serem reinseridas.
- Custo quase nulo em escala de demo: a plataforma de demonstração é aberta; um serviço de escala a zero e instância única mantém o gasto real dentro da franquia sempre gratuita do host em tráfego de demonstração.
- Determinismo de instância única: o limitador de taxa, o cache e o checkpointer de HITL em processo exigem que uma única instância atenda todo o tráfego; o host deve tornar o teto de instância única uma configuração de primeira classe.
- Rollback em uma única operação: uma implantação ruim é revertida migrando o tráfego de volta para a revisão anterior saudável, sem reconstrução.
- Domínio personalizado: a demo ao vivo é alcançável em um domínio próprio do projeto, não apenas em um hostname gerado pela plataforma.
Opções Consideradas
Seção intitulada “Opções Consideradas”- Google Cloud Run, contêiner de instância única (escolhida): mesmo
Dockerfile via um build a partir do fonte,
--max-instances 1, regiãous-central1, implantação sem chaves com Workload Identity Federation condicionada a uma matriz de CI verde da tag, rollback baseado em revisões, mapeamento de domínio personalizado, segredos gerenciados, escala a zero. - Hugging Face Spaces, Docker SDK, CPU Basic gratuito: a mesma imagem em um host genuinamente de custo zero e sempre ativo; mantido como o alvo de implantação secundário documentado. Sua borda não oferece o pipeline condicionado e sem chaves, o controle de domínio personalizado nem o armazenamento de segredos gerenciado que a postura de produção quer, e seu caminho de implantação é um espelho com force-push em vez de um build condicionado.
- Hugging Face Spaces, SDK Gradio / Streamlit: mesmo host, mas o SDK constrói a UI; a superfície FastAPI que o projeto distribui não corresponderia à imagem implantada.
- Render Web Service, nível gratuito: mesmo alvo de Dockerfile via
$PORT; dorme após 15 minutos de ociosidade com um cold start de 30-60 s; sem pipeline de implantação condicionado e sem chaves de fábrica. - Fly.io, nível gratuito: o nível gratuito original terminou em outubro de 2024, substituído por uma postura de crédito de teste de $5 / mês.
- Railway, nível gratuito: plano gratuito retirado em agosto de 2023 em favor de créditos de teste de $5 / mês; não é de custo quase nulo.
- Vercel, plano Hobby: timeout de função serverless de 10 s no Hobby mata qualquer stream de agente multi-turno assim que a ida e volta do LLM excede o limite.
- Cloudflare Workers AI: uma plataforma de roteamento de modelos em vez de um host Docker genérico; o stack Python + Chroma embarcado precisaria ser reescrito contra o runtime do Workers.
- Streamlit Community Cloud: gratuito, mas atrela a demo a uma UI Streamlit; a superfície FastAPI fica inacessível.
- Modal: créditos de teste, depois pague-por-segundo; o regime permanente não é de custo quase nulo.
Resultado da Decisão
Seção intitulada “Resultado da Decisão”Opção escolhida: Google Cloud Run, contêiner de instância única, com
Hugging Face Spaces, Docker SDK documentado como o alvo secundário do
operador na referência de implantação e alcançável a partir da mesma imagem com
uma mudança de porta ($PORT).
O serviço ai-agent-eval-harness roda no projeto ai-agent-eval-harness,
região us-central1, limitado a --max-instances 1, e é alcançável no domínio
personalizado agent.szemat.pro. O Cloud Build constrói a imagem a partir do
Dockerfile do repositório via uma implantação a partir do fonte, de modo que a
mesma imagem roda em três lugares: o laptop de um contribuidor, o
harness de avaliação e a demo pública. Uma imagem, um modelo mental, um conjunto
de comportamentos sob teste.
A implantação é do lado do servidor, sem chaves e condicionada. Uma tag de
release que corresponde a vX.Y.Z dispara o workflow tag-guard, que roda
novamente a matriz de CI completa contra o commit marcado com a tag. Somente
quando essa execução conclui success é que o workflow deploy dispara (via
workflow_run), autentica-se no Google Cloud com Workload Identity Federation
(um token OIDC de curta duração, sem chave JSON armazenada) e roda um deploy a
partir do fonte do Cloud Run que preserva os segredos. Uma
matriz de CI vermelha significa que o job de implantação é ignorado e nada é
publicado. A reconstrução a partir do fonte preserva as variáveis de ambiente e
os vínculos com o
Secret Manager existentes no serviço; uma revisão com falha mantém o tráfego na
anterior saudável.
O teto de instância única é determinante: o limitador de taxa, o cache de respostas e o checkpointer de HITL em processo não são compartilhados entre instâncias, então o serviço roda exatamente uma instância por design. A escala a zero do Cloud Run mantém o gasto real dentro da franquia sempre gratuita do host em tráfego de demonstração; o contêiner não hospeda nenhum LLM local, então só lida com recuperação de RAG, orquestração e HTTP, despachando as completudes para um provedor externo.
Confirmação
Seção intitulada “Confirmação”- Uma matriz de CI do
tag-guardverde em uma tagvX.Y.Z, seguida de uma execução verde do workflowdeployque se autenticou sem chaves via WIF. - Uma URL pública alcançável em
https://agent.szemat.pro. - O serviço responde a
GET /healthcom200 OK. - O serviço responde a
POST /chatem modo demo contra um cliente stub offline (sem chaves do lado do chamador necessárias para a demo pública). - A referência de implantação documenta o bootstrap do operador (pool / provider de WIF + conta de serviço de implantação de privilégio mínimo, vínculos com o Secret Manager, primeira implantação).
Consequências
Seção intitulada “Consequências”Positivas
Seção intitulada “Positivas”- Implantações condicionadas, do lado do servidor: apenas uma tag de release publicada-em-verde pode alcançar produção; um operador não pode publicar à mão um commit não validado.
- Credenciais sem chaves: nenhuma chave de conta de serviço de longa duração existe ou é armazenada; a implantação usa um token federado de curta duração.
- Mesmo Dockerfile em todo lugar: desenvolvimento, CI, produção.
- Redeploys que preservam segredos: os vínculos com segredos gerenciados sobrevivem a cada implantação a partir do fonte.
- Rollback em uma única operação: migre o tráfego de volta para a revisão anterior saudável; sem reconstrução.
- Domínio personalizado: a demo ao vivo é servida em
agent.szemat.pro. - Sem hospedagem de pesos de modelo: evita LFS e qualquer nível de armazenamento no host; mantém a imagem pequena.
Negativas
Seção intitulada “Negativas”- Cold start com escala a zero: a primeira requisição após uma janela de ociosidade paga um cold start de contêiner (importação de dependências, ingestão inicial da KB, carregamento do embedder).
- Teto de instância única:
--max-instances 1limita a concorrência horizontal; escalar para fora exigiria mover as primitivas em processo para um armazenamento compartilhado (Redis, Postgres), fora de escopo para a demo. - Cabeamento de nuvem único: a implantação sem chaves precisa de um pool / provider de WIF e uma conta de serviço de implantação de privilégio mínimo configurados uma vez antes de o pipeline poder se autenticar.
- Somente CPU: o contêiner não consegue hospedar um LLM local; a demo depende de um provedor externo para completudes. Por design (ver ADR-0002).
Neutras
Seção intitulada “Neutras”- Um gate
tag-guarde um workflowdeployse tornam parte do layout de CI do repositório. - As variáveis de repositório (
WIF_PROVIDER,WIF_SERVICE_ACCOUNT) se tornam necessárias para a implantação ao vivo; contribuidores sem direitos de implantação trabalham localmente sem elas. - A mesma imagem é mantida implantável no Hugging Face Spaces como alvo secundário, então a referência de implantação documenta dois hosts.
Prós e Contras das Opções
Seção intitulada “Prós e Contras das Opções”Google Cloud Run, contêiner de instância única
Seção intitulada “Google Cloud Run, contêiner de instância única”- Boa, porque a implantação é condicionada a uma matriz de CI verde e roda do lado do servidor, sem chaves via WIF.
- Boa, porque um build a partir do fonte roda o
Dockerfiledo projeto como está e preserva os segredos gerenciados através dos redeploys. - Boa, porque o rollback é uma migração de tráfego em uma única operação para a revisão anterior.
- Boa, porque
--max-instances 1torna a postura exigida de instância única uma configuração de primeira classe, e a escala a zero mantém o custo da demo perto de zero. - Boa, porque o serviço mapeia para um domínio personalizado próprio do projeto.
- Ruim, porque a escala a zero significa um cold start na primeira requisição após a ociosidade.
- Ruim, porque o escalonamento horizontal para fora precisa de um armazenamento compartilhado para as primitivas em processo.
Hugging Face Spaces, Docker SDK, CPU Basic gratuito
Seção intitulada “Hugging Face Spaces, Docker SDK, CPU Basic gratuito”- Boa, porque a mesma imagem roda sem alterações a custo genuinamente zero, sempre ativa, o que a torna um alvo de demo secundário ideal.
- Boa, porque o domínio
huggingface.cosinaliza “implementação de referência de IA” em uma olhada. - Ruim, porque sua borda não oferece pipeline de implantação condicionado e sem chaves, nem armazenamento de segredos gerenciado, nem controle de domínio personalizado; o caminho de implantação é um espelho com force-push em vez de um build condicionado.
- Ruim, porque o front-matter YAML do card do Space exige uma troca do README raiz exclusiva de implantação.
Hugging Face Spaces, SDK Gradio / Streamlit
Seção intitulada “Hugging Face Spaces, SDK Gradio / Streamlit”- Boa, porque os SDKs distribuem uma UI opinativa e hospedada.
- Ruim, porque a superfície da demo diverge da superfície FastAPI que o projeto distribui; a coisa implantada deixa de ser a mesma coisa que a coisa construída localmente.
Render Web Service, nível gratuito
Seção intitulada “Render Web Service, nível gratuito”- Boa, porque o mesmo
Dockerfileimplanta com uma mudança de variável de ambiente ($PORT). - Ruim, porque o sono por ociosidade de 15 minutos é mais agressivo que a escala a zero em um serviço já aquecido, e não há pipeline de implantação condicionado e sem chaves de fábrica.
Níveis gratuitos do Fly.io / Railway
Seção intitulada “Níveis gratuitos do Fly.io / Railway”- Ruim, porque os níveis gratuitos históricos foram retirados (Fly.io outubro de 2024, Railway agosto de 2023) em favor de créditos de teste; o regime permanente não é de custo quase nulo.
Vercel Hobby
Seção intitulada “Vercel Hobby”- Ruim, porque o timeout de função de 10 segundos mata qualquer stream de agente multi-turno assim que a ida e volta do LLM o excede.
Cloudflare Workers AI
Seção intitulada “Cloudflare Workers AI”- Ruim, porque o stack Python + Chroma embarcado teria que ser reescrito contra o runtime do Workers.
Streamlit Community Cloud
Seção intitulada “Streamlit Community Cloud”- Ruim, porque a UI atrela a demo ao Streamlit; a superfície FastAPI fica inacessível.
- Ruim, porque os créditos de teste expiram; o regime permanente custa dinheiro.
Mais Informações
Seção intitulada “Mais Informações”- Runbook do operador: referência de implantação
- Documentação do Google Cloud Run: https://cloud.google.com/run/docs
- Workload Identity Federation para GitHub Actions: https://cloud.google.com/iam/docs/workload-identity-federation
- Documentação do Docker SDK do Hugging Face Spaces: https://huggingface.co/docs/hub/spaces-sdks-docker
- Documentação do nível gratuito do Render: https://render.com/docs/free
- Thread de post-mortem do nível gratuito do Fly.io: https://community.fly.io/t/free-tier-is-dead/20651
- Limites de runtime de função do Vercel: https://vercel.com/docs/functions/runtimes#max-duration
- MADR 4.0.0: https://adr.github.io/madr/
Camada de resiliência de implantação
Seção intitulada “Camada de resiliência de implantação”Três primitivas por processo são distribuídas para fazer a demo degradar graciosamente sob carga em vez de expor erros upstream crus. Todas as três são acionadas por flags de habilitação para que fiquem desligadas por padrão para testes determinísticos e ligadas para o serviço ao vivo:
- Rate limiter por sessão. Um limiter de janela deslizante por chave
(por IP de cliente). Um chamador acima do limite recebe um HTTP 429 com um
cabeçalho
Retry-Afterem vez de um 502 cru. Ajustável via configurações de máximo de requisições e segundos da janela. Uma dependência foi deliberadamente não adicionada; o limiter em processo mantém a postura sem framework. - Cadeia de fallback de provedor. Um wrapper em torno do Protocol
LLMClient: uma falha transitória da OpenAI (HTTP 429, 5xx ou uma falha de transporte pura) cascateia para a Anthropic antes que qualquer erro alcance o frontend. Um 4xx que não seja 429 é um erro genuíno de cliente e é relançado inalterado. Consistente com a abstração de fornecedor do ADR-0002 - o fallback é um wrapper em nível de Protocol, não uma mudança em nível de nó. - Cache de resposta com TTL curto. Um cache em processo limitado e de TTL curto, chaveado na tupla normalizada (entrada, localidade, modelo), de modo que os cliques de “carregar exemplo” da SPA sejam atendidos sem atingir o provedor.
Consequência de instância única. Todas as três primitivas são por processo,
assim como o checkpointer HITL em memória (ver
ADR-0001). O serviço, portanto, roda uma
única instância por design (--max-instances 1); uma segunda instância não
compartilharia o limiter, o cache ou o estado de thread pausada. Uma implantação
multi-instância precisaria de um armazenamento compartilhado (Redis, Postgres), o
que está fora de escopo para a demo. Isso é documentado na referência de
implantação e no comentário do Dockerfile.
Embedder embutido. O embedder de fallback local embutido é
BAAI/bge-small-en-v1.5, compatível com CPU no serviço de instância única; o
embedder primário é Voyage voyage-3.5 quando uma chave da Voyage está configurada.
Ver ADR-0004 para a decisão do embedder; a
postura de implantação aqui não é afetada pela escolha do modelo.