kisenon
Agent-Safe Change Control

Armadilhas comuns

Antipadrões que silenciosamente derrotam o scale-to-zero e a segurança de agentes — e como evitá-los.

Esses padrões não dão erro. Eles passam no CI, parecem razoáveis em um diff e silenciosamente cancelam uma garantia pela qual você está pagando — economias de scale-to-zero ou segurança de agentes. Cada um abaixo: o que acontece, por que dói e a correção.

Código keep-alive derrota o scale-to-zero

Um agente "prestativamente" adiciona código que toca no banco de dados em um temporizador. Cada consulta periódica conta como atividade e reinicia o temporizador de suspensão, então um endpoint que você projetou para escalar a zero nunca dorme — e a ociosidade deixa de ser gratuita.

Ele chega em várias formas. Um heartbeat de frontend ou backend:

// runs forever, holds the compute awake forever
setInterval(async () => {
  await pool.query("SELECT 1");   // "health check"
}, 30_000);

Um monitor de uptime externo apontado para um endpoint cujo handler por acaso consulta o BD — então cada probe acorda o compute:

app.get("/health", async (_req, res) => {
  await pool.query("SELECT 1");   // monitored every 60s → never idle
  res.send("ok");
});

Ou configurações de pool de conexões que mantêm uma conexão viva com consultas de validação — um min acima de zero, ou um "warmer" serverless:

const pool = new Pool({
  min: 2,                         // keeps ≥2 connections open + validated
  // a validation/keep-alive query on each idle connection resets the timer
});

Correção: audite os diffs escritos por agentes em busca de acesso periódico ao banco de dados. Mantenha as verificações de liveness na camada HTTP — faça probe da aplicação, não do BD — e deixe o pool drenar para zero (min: 0). A visão de compute do console mostra por que um endpoint não está escalando a zero, o que revela um heartbeat perdido rapidamente.

Entregar ao agente as credenciais de main curto-circuita o ASCC

Dar a um agente a string de conexão da branch main — ou deixá-lo executar keon autenticado como seu próprio usuário — contorna tudo o que o ASCC impõe: isolamento de sandbox, orçamentos LEASH, verificações LINT e BLAST, portões de promoção, atribuição e âncoras de undo. O agente agora está gravando produção diretamente, e nada do laço que torna as mudanças de agente seguras jamais roda.

O agente nunca deveria portar uma credencial capaz de gravar em main. A promoção é no lado do servidor por design — cp é o único gravador em produção.

Correção: cunhe uma chave de API com escopo de agente (capacidade = agent, escopo = um projeto, expiração curta) em Settings → API keys, entregue ao agente apenas strings de conexão de sandbox, e remova credenciais mais fortes do seu ambiente (sem DATABASE_URL completo, sem ~/.pgpass, sem sessão keon autenticada no mesmo shell). Veja Entregue a um agente uma credencial com escopo e a seção SCOPE de Guardrails.

Tempestades de retry em torno do despertar

A primeira conexão após uma suspensão precisa acordar o compute, então ela é mais lenta que uma quente. Um agente que vê uma única conexão lenta pode "consertá-la" — com timeouts de cliente agressivos e laços de retry que martelam o endpoint, ou adicionando um keep-alive (armadilha #1) para que ele nunca tenha de acordar. Ambos trocam as economias para encobrir um custo pontual.

Correção: defina um connect_timeout de cliente em torno de 10s e seja paciente na primeira conexão. O cold start é sub-segundo na mesma região quando os caminhos quentes estão em jogo — uma conexão levemente lenta é esperada, não uma falha para se contornar com engenharia.

postgresql://…?connect_timeout=10

Credenciais commitadas no código

Agentes fixam strings de conexão e chaves de API no código-fonte e em arquivos .env que então chegam ao git. Chaves vazadas são colhidas em questão de horas após atingirem um repositório público (ou que se tornará público depois) — a janela é medida em horas, não dias.

Correção: injete segredos pelo ambiente, nunca os comite. Mantenha arquivos .env fora do git. Em qualquer vazamento, rotacione imediatamente em Settings → API keys — revogue a chave exposta e cunhe uma nova.

DDL direto em main contorna o laço de promoção

Rodar uma migração direto contra main pula a captura que torna uma mudança reversível e auditável. Você perde a migração up/down emitida (EMIT), a entrada de ledger assinada (LEDGER) e a âncora de undo (UNDO). O esquema mudou, mas não há registro que a plataforma possa reexecutar ou reverter.

Correção: sandbox → promote, mesmo para uma mudança de esquema "trivial". O laço de promoção é o que produz a migração, a entrada de ledger e a âncora; um ALTER direto não produz nenhum deles.

Gravações dentro de uma transação revertida ainda aterrissam

A captura registra as instruções que o agente executa — ela não tem consciência de limites de transação. Instruções executadas dentro de um BEGIN … ROLLBACK ainda são capturadas, e se aquele sandbox for promovido, elas são reexecutadas e aplicadas. Um ROLLBACK no sandbox não as descaptura.

BEGIN;
UPDATE users SET plan = 'pro';   -- captured
ROLLBACK;                        -- does NOT remove it from the capture

Correção: não confie no ROLLBACK dentro de um sandbox para "desfazer" trabalho exploratório. Se você quer o trabalho eliminado, descarte o sandbox — esse é o caminho de descarte que a captura respeita.

Estacionar trabalho em um sandbox além do seu orçamento ou TTL

Sandboxes são efêmeros. Quando um sandbox esgota seu orçamento LEASH ele é autodescartado (status=discarded), e qualquer trabalho não promovido nele é perdido. Tratar um sandbox como uma branch de vida longa à qual você "vai voltar" é como as mudanças de um dia desaparecem.

Correção: promova-o, ou recapture o trabalho em um sandbox novo. Trate sandboxes como descartáveis por padrão — o artefato durável é o promote, não o sandbox.

Interpretar mal as garantias

Três armadilhas específicas no que as verificações de fato prometem:

  • O ensaio do raio de destruição é consultivo. Ele mede o que um promote faria — locks, durações, contagens de linhas reais — mas nunca condiciona o promote. Um ensaio limpo é informação, não uma luz verde; e se um ensaio é exigido antes do promote é decisão da POLICY, não do ensaio.

  • Approve não pode substituir um block de política. Um Approve humano nunca pode deixar passar um block de política ou uma instrução fora da faixa. A única forma de passar por um block é uma mudança de política auditada — não há interruptor de substituição manual, por design.

  • Uma bifurcação mascarada mascara valores, não formato. Em uma bifurcação mascarada os valores são mascarados, então DML dependente de dados que o agente escreve contra esses valores mascarados não corresponderá às linhas reais no pai no replay. O trabalho de faixa de esquema é independente de valor e reexecuta fielmente; DML dependente de valor desenvolvido contra dados mascarados não.