Quando um agente começa a usar ferramentas, ele deixa de ser só “texto” e vira execução: consulta dados, cria tickets, envia e-mail, abre incidentes, muda status, dispara cobranças. Nesse ponto, o risco mais comum não é o modelo “alucinar”. É o sistema não saber, de forma consistente, quando é permitido agir.
O erro clássico é espalhar regras de permissão pelo código: ifs, allowlists, flags e exceções locais. Isso funciona no início, mas não escala com múltiplos times e múltiplas ferramentas — e vira impossível auditar quando a decisão precisa ser defendida.
OPA (Open Policy Agent) existe para separar política de aplicação: a regra fica central e executável; o serviço pergunta “posso?” com contexto; a resposta volta com decisão e justificativa.
O desafio — Fazer permissão virar um corredor, não um argumento de reunião
Em agentic AI, “permissão” precisa ter três propriedades para ser governável: ser executável em runtime, ser auditável como artefato versionado, e ser consistente em todos os caminhos (inclusive retries, reprocessamento e exceções).
O desafio é tirar a permissão do “costume organizacional” e colocá-la como boundary técnico: se a ação tem efeito colateral, ela só acontece se a política autorizar, e essa autorização precisa virar evidência.
O cenário — O agente tinha acesso “só para ajudar” e virou operador sem querer
Um time libera para o agente uma API interna de “atualizar cadastro” para acelerar atendimento. Outro time libera “enviar e-mail” para follow-up automático. Um terceiro libera “abrir ticket” para incidentes. Cada integração é feita com regras locais: uma checagem aqui, um allowlist ali.
Quando surge um incidente contestado, a empresa descobre que não existe uma resposta única para “por que isso foi permitido”. Há versões diferentes da regra em lugares diferentes. A auditoria vira arqueologia. E, pior: a permissão começa a vazar por exceções acumuladas.
Visão AI — Regras no código, espalhadas e fáceis de contornar por acidente
No caminho “AI”, a abordagem mais rápida é checar permissão dentro do próprio serviço, com regras simples baseadas em papel, ambiente e uma lista de ações “permitidas”. Isso entrega resultado rápido, mas cria um sistema onde a política está duplicada e muda por acidente: um serviço atualiza, outro esquece, um terceiro cria exceção.
O código abaixo representa esse padrão: um gateway de ferramentas decide “pode ou não pode” com ifs locais e regras por convenção. Ele é prático, mas a política não é um artefato auditável nem consistente entre integrações.
"""AI-style: permissão embutida no código (ifs locais).Pros: rápido, simples, sem dependência externa.Cons: política espalhada, difícil de auditar, fácil de divergir entre serviços."""from __future__ import annotationsfrom dataclasses import dataclassfrom typing import Dict, Any, LiteralEffect = Literal["read", "side_effect"]@dataclass(frozen=True)class ToolRequest:actor: str # ex: "agent:support_bot"role: str # ex: "support"tool: str # ex: "send_email"action: str # ex: "send"effect: Effect # read | side_effectenvironment: str # prod | stagingcohort: str # ex: "region:BR"def local_allow(req: ToolRequest) -> bool:# regra local (e incompleta)if req.environment != "prod":return Trueif req.effect == "read":return True# side effects em prod: só alguns papéisif req.role in ("sre", "compliance"):return True# “exceção” para suporte mandar e-mailif req.role == "support" and req.tool == "send_email" and req.action == "send":return Truereturn Falsedef call_tool(req: ToolRequest, payload: Dict[str, Any]) -> Dict[str, Any]:if not local_allow(req):return {"outcome": "deny", "reason": "local_policy_denied"}# placeholder: aqui chamaria a ferramenta realreturn {"outcome": "ok", "tool": req.tool, "action": req.action, "payload": payload}if __name__ == "__main__":req = ToolRequest(actor="agent:support_bot",role="support",tool="send_email",action="send",effect="side_effect",environment="prod",cohort="region:BR",)print(call_tool(req, {"to": "cliente@exemplo.com", "subject": "Atualização", "body": "..." }))
Esse código decide rápido, mas produz um tipo de fragilidade que aparece tarde: a política não tem versionamento central, não retorna “por que” de forma consistente, e a exceção vira hábito. Quando você adiciona mais ferramentas e mais times, o mesmo “pode” passa a significar coisas diferentes em lugares diferentes — e a organização perde a capacidade de provar permissão sob contestação.
Visão WM — OPA como plano de controle: política executável, auditável e consistente
No caminho “WM”, o objetivo é transformar permissão em decisão auditável. A aplicação não “inventa” regra; ela consulta uma política central (OPA) com contexto suficiente para a política ser precisa: ator, ação, escopo, efeito colateral, coorte, ambiente, versão e intenção operacional.
A ideia não é burocratizar. É permitir expansão por corredores: primeiro leitura, depois efeitos colaterais estreitos, depois expansão merecida. E cada decisão de permissão pode virar evidência portátil quando alguém pergunta “por que isso foi permitido?”.
Abaixo, um exemplo mínimo com duas peças: uma política em Rego (OPA) e um gateway em Python consultando OPA via HTTP. A política devolve uma decisão e um motivo; o gateway anexa isso como “recibo” operacional da permissão.
# policy.rego# WM-style: política executável para tool calls# - corredores por efeito (read vs side_effect)# - restrições por ferramenta/ação em produção# - condições por coorte e papelpackage trajecta.toolsdefault decision := {"allow": false, "reason": "default_deny"}# Leitura em geral é permitida (mas ainda auditável)decision := {"allow": true, "reason": "read_allowed"} {input.effect == "read"}# Efeito colateral em produção: só papéis específicos, com exceções estreitasdecision := {"allow": true, "reason": "prod_side_effect_allowed"} {input.environment == "prod"input.effect == "side_effect"input.role == "sre"}decision := {"allow": true, "reason": "support_email_narrow_corridor"} {input.environment == "prod"input.effect == "side_effect"input.role == "support"input.tool == "send_email"input.action == "send"startswith(input.cohort, "region:")}# Bloqueio explícito: certas ações nunca podem ser automáticasdecision := {"allow": false, "reason": "never_auto_charge"} {input.tool == "charge_card"input.action == "charge"}"""WM-style: gateway consulta OPA e registra a decisão como evidência.- política fica versionada e auditável fora do app- app vira um executor: pede decisão, aplica, e guarda motivo"""from __future__ import annotationsimport jsonimport uuidimport requestsfrom dataclasses import dataclass, asdictfrom typing import Dict, Any, LiteralEffect = Literal["read", "side_effect"]@dataclass(frozen=True)class ToolRequest:actor: strrole: strtool: straction: streffect: Effectenvironment: strcohort: strcorrelation_id: strdef opa_decide(req: ToolRequest) -> Dict[str, Any]:payload = {"input": asdict(req)}r = requests.post("http://localhost:8181/v1/data/trajecta/tools/decision",json=payload,timeout=2,)r.raise_for_status()return r.json()["result"]def call_tool(req: ToolRequest, tool_payload: Dict[str, Any]) -> Dict[str, Any]:decision = opa_decide(req)receipt = {"receipt_id": str(uuid.uuid4()),"correlation_id": req.correlation_id,"actor": req.actor,"tool": req.tool,"action": req.action,"effect": req.effect,"environment": req.environment,"cohort": req.cohort,"policy_reason": decision.get("reason"),"policy_allow": decision.get("allow"),}if not decision.get("allow"):return {"outcome": "deny", "receipt": receipt}# placeholder: aqui chamaria a ferramenta realresult = {"status": "executed", "tool": req.tool, "action": req.action}return {"outcome": "ok", "result": result, "receipt": receipt}if __name__ == "__main__":req = ToolRequest(actor="agent:support_bot",role="support",tool="send_email",action="send",effect="side_effect",environment="prod",cohort="region:BR",correlation_id=f"corr-{uuid.uuid4()}",)out = call_tool(req, {"to": "cliente@exemplo.com", "subject": "Atualização", "body": "..."})print(json.dumps(out, indent=2, ensure_ascii=False))
Esse padrão muda o tipo de conversa que a organização consegue ter. Em vez de “temos logs” e “o código faz”, você passa a ter um artefato claro: a política que autorizou ou negou, com motivo, e a decisão anexada ao ato. A permissão deixa de ser “o que o time lembra” e vira “o que o sistema executa”.
Além disso, OPA favorece governança por versões: a política pode ser revisada, testada e promovida com gates, sem precisar redeploy de toda a aplicação. E, quando a autonomia cresce, ela cresce por corredor: você amplia regras de forma explícita, não por exceção silenciosa.
Comparação — Política espalhada vs política executável e auditável
No padrão AI, permissão é um detalhe local. Isso acelera o início, mas cria divergência inevitável: cada serviço cria sua versão da regra, e cada exceção vira precedente. A empresa perde consistência e perde auditabilidade no momento em que mais precisa.
No padrão WM, permissão vira um plano de controle: uma política executável, consultada de forma uniforme, com decisão e motivo anexados à ação. Isso não elimina todos os riscos, mas muda a geometria do sistema: tool access deixa de ser poder implícito e vira autorização explícita — e isso é o que permite escalar autonomia sem perder legitimidade.
O que ainda poderia melhorar — sinais de próxima maturidade
Ainda faltaria tornar os corredores mais expressivos sem virar “política infinita”: separar claramente permissões por tipo de efeito colateral, por classe de usuário e por contexto operacional, e garantir que exceções tenham expiração e dono. Também faltaria integrar a política a evidências mais robustas de versão e de responsabilidade, para que a decisão de permissão viaje junto com o restante do ato (contexto, fontes, checks e trilha de aprovação quando houver).
Por fim, faltaria amadurecer o “comportamento sob negação”: quando a política nega, o sistema deveria entrar em modos previsíveis (abster-se, escalar, pausar, reduzir escopo por coorte) em vez de “tentar contornar”. Esse é o passo em que política deixa de ser só gate e vira parte do design de hesitação.