kisenon
Agent-Safe Change Control

Guardrails

Orçamentos, camadas de política, autoridade com escopo, análise de risco e verificações de raio de destruição — o que detém um agente antes que ele o prejudique.

Cinco guardrails cercam um sandbox de agente de IA. Dois são limites rígidos que o agente fisicamente não pode exceder (LEASH, SCOPE), um transforma dados em um veredito (POLICY), e dois produzem dados para ela pesar (LINT, BLAST). Nenhum coloca um LLM no caminho de confiança.

LEASH — orçamentos por sandbox

Bloqueia: um agente que se descontrola — queimando instruções, compute ou tempo de relógio sem limite.

Defina um orçamento quando você cria ou executa o sandbox:

keon sandbox create \
  --budget-statements 500 \
  --budget-compute-seconds 120 \
  --budget-wall-seconds 900
# or the same three flags on: keon sandbox run …

Ao romper o limite, o sandbox autotermina: o status torna-se discarded e budget_breached nomeia qual limite estourou — statements, compute_seconds ou wall_clock. A CLI sai com o código de erro sandbox_budget_breached. Um aviso dispara em 80% de qualquer orçamento.

Na transmissão, os orçamentos são um objeto budgets, uma entrada por orçamento:

"budgets": {
  "statements":      { "limit": 500, "used": 218, "warned": false },
  "compute_seconds": { "limit": 120, "used": 41,  "warned": false },
  "wall_clock":      { "used": 63 }
}

Um limit ausente significa ilimitado — não há valor sentinela para interpretar mal.

Configure padrões e limites máximos (proprietário/administrador):

# Per-project defaults double as the create-time cap.
curl -X PATCH .../v1/projects/{id} \
  -d '{"sandbox_budget_defaults": {"statements": 500, "compute_seconds": 120}}'

O padrão é o limite máximo: pedir no momento da criação por mais do que o padrão do projeto retorna 422 sandbox_budget_exceeds_cap. Os padrões por projeto são configuráveis — não há número fixo para citar aqui.

Duas invariantes:

  • Congelado na criação. Os orçamentos são fixados quando o sandbox é criado; mudar os padrões do projeto depois nunca re-restringe um sandbox ativo.
  • Chaves de agente apenas apertam. Uma chave de capacidade de agente pode solicitar orçamentos mais apertados na criação; ela nunca pode definir os padrões do projeto.

POLICY — camadas de política de promoção

Bloqueia: um promote cujas mudanças são mais arriscadas do que o projeto permite — mesmo um que um agente (ou um humano) tente deixar passar.

POLICY é uma sobreposição por projeto que mapeia uma classe de risco para uma ação, disposta em camadas sobre o promote_mode base.

Configure (CLI):

keon projects promote-policy <projectId>                 # show effective policy
keon projects promote-policy <projectId> \
  --set destructive=block \
  --set unbounded=require_human \
  --on-conflicts require_human                           # merge-write
keon projects promote-policy <projectId> --clear         # drop the overlay

--set <class=action> é repetível e mescla na sobreposição existente.

Configure (API): GET / PUT / DELETE /v1/projects/{projectId}/promote-policy:

{ "version": 1,
  "rules": { "destructive": "block", "unbounded": "require_human" },
  "on_conflicts": "require_human" }
CampoValores legais
classe de riscoadditive, mutating, destructive, unbounded, rewrite
açãoauto_promote, require_human, block
on_conflictsignore, require_human, block

ignore é legal apenas para on_conflicts, nunca como uma ação de classe.

A postura base vem do promote_mode, e a sobreposição substitui por classe:

promote_modeToda classe usa como padrão
selfauto_promote
humanrequire_human

Cada regra efetiva reporta de onde veio — source: overlay, promote_mode ou default.

O que o agente vê quando bloqueado: um promote bloqueado retorna 409 promote_blocked_by_policy com os detalhes da decisão anexados.

Regra-chave: um Approve humano nunca pode substituir um block de política. A política é reavaliada no momento do approve, então block se mantém contra o clique. A validação de política é à prova de falhas / estrita — uma política que não pode ser analisada nega, ela nunca degrada para aberta.

SCOPE — autoridade com escopo (faixas)

Bloqueia: qualquer leitura ou gravação fora dos esquemas e tabelas que você declarou — a faixa do agente.

Configure (apenas no momento da criação):

keon sandbox create \
  --schemas app,analytics \
  --tables  app.users,app.orders
# omit both flags → unscoped (full-branch authority)

--tables sem --schemas é rejeitado no cliente como invalid_lane.

Dentro da bifurcação o agente fisicamente não pode gravar fora da faixa — uma gravação fora da faixa falha na bifurcação com o SQLSTATE 42501 (privilégio insuficiente) ou 3F000 (nome de esquema inválido). Leituras fora da faixa também são negadas. E o promote recusa qualquer mudança fora da faixa com 409 sandbox_out_of_lane.

Na transmissão, o sandbox carrega um objeto scope (ausente = sem escopo):

"scope": { "v": 1, "schemas": ["app", "analytics"], "tables": ["app.users", "app.orders"] }

keon sandbox diff ganha um objeto lane que lista quaisquer violations[], cada uma com um motivo — out_of_lane_schema, out_of_lane_relation ou unresolvable_targets.

Dois limites:

  • Imutável após a criação. Ampliar uma faixa significa descartar + recriar — não há ampliação no lugar.
  • Apenas alvos diretos. Uma faixa não governa efeitos colaterais transitivos alcançados por triggers ou funções SECURITY DEFINER.

LINT — classificação estática de risco

Produz: uma classe estática de risco para cada instrução no conjunto de replay — sem execução, sem LLM, sem contagens de linhas. Puramente textual.

Não há novo comando. LINT aparece em três lugares que você já lê:

  • dentro de keon sandbox diff como risk_report,
  • por ação em keon sandbox log,
  • como max_risk_class na transmissão do sandbox.

Classes, da menor à maior severidade:

additive  <  mutating  <  rewrite  <  unbounded  <  destructive

Um conjunto de replay vazio é classificado como none.

Formato do relatório:

{ "statements": [
    { "seq": 4, "risk_class": "unbounded",
      "reasons": ["where_trivially_true: DELETE guarded by WHERE 1=1 matches every row"] }
  ],
  "rollup": { "max_class": "unbounded", "counts": { "additive": 2, "unbounded": 1 } } }

Cada motivo é <snake_case_code>: <sentence>. Por exemplo, WHERE true ou WHERE 1=1 classifica a instrução como unbounded com o motivo where_trivially_true.

Duas coisas a lembrar:

  • LINT é dado, não uma decisão. Ele classifica; POLICY impõe. LINT nunca bloqueia nada por conta própria.
  • Ele falha para cima. Sob incerteza LINT atribui a pior classe plausível. Como a detecção é textual, as contagens de linhas reais não são uma entrada — um DELETE sem WHERE é lido como destructive quer a tabela tenha uma linha ou um bilhão.

BLAST — ensaio do raio de destruição

Produz: evidência medida do que um promote realmente faria — níveis de lock reais, durações de retenção de lock reais, contagens de linhas reais e qualquer divergência desde a bifurcação.

keon sandbox dry-run <id>

Ele reexecuta o conjunto exato de instruções do sandbox contra duas bifurcações descartáveis do pai, mede o custo real, detecta divergência, depois destrói ambas. Ele nunca toca em main, e o agente nunca recebe uma credencial de bifurcação. BLAST é consultivo — ele nunca bloqueia um promote; conflitos são expostos como dados.

A referência completa de campos, a semântica de conflitos e a lista de endpoints vivem em Sandboxes & promote → Ensaio do raio de destruição.

Como eles se encaixam

  • LINT e BLAST produzem dados — um estático, um medido.
  • POLICY transforma esses dados em uma decisão, à prova de falhas, não substituível por um clique humano quando ela diz block.
  • LEASH e SCOPE são limites rígidos que o agente não pode exceder de forma alguma — impostos na bifurcação, congelados na criação.

Veja Armadilhas comuns para as maneiras como esses cinco são rotineiramente mal interpretados.