PT EN
Instalar

Decisões semânticas em pipelines de agentes, com o Jev

O T25 pergunta ao Jev, da TypeSafe, e deixa uma política determinística decidir o que a probabilidade significa.

Read in English Versão em Markdown

Quando o QA fecha um critério, o T25 não pede um parágrafo sobre a prova. Pergunta evidence.relation_to_criterion.v1: a evidência apoia, contradiz, ou não diz nada. A resposta é um Choice com três probabilidades. Esse contrato é o que chamamos de decisões semânticas em pipelines de agentes.

Decisões semânticas em pipelines de agentes: o que perguntamos

O catálogo mora em src/decisions/questions.ts. Cada id tem versão (v1) e um tipo. O Jev, da TypeSafe, é um modelo System One. Um POST em https://api.typesafe.ai/v1/systemone manda um state e várias perguntas; as respostas tipadas voltam na mesma passada. A documentação pública descreve três primitivas. Noul é a probabilidade de um sim, entre 0 e 1. Choice escolhe uma opção da lista que nós enviamos e devolve a distribuição. Score coloca o estado numa rubrica e também devolve confidence.

Cinco famílias cobrem o julgamento que um if não fecha:

Família Pergunta Tipo O que está em jogo
Contexto context.candidate_helps.v1 Noul Este arquivo ajuda os critérios, ou só enche o prompt?
Progresso progress.looks_like_workaround.v1 Noul A mudança corrige o produto, ou enfraquece o teste?
Evidência evidence.relation_to_criterion.v1 Choice A prova apoia, contradiz ou não fala do critério?
Atenção attention.finding_lacks_context.v1 Noul O finding cita critério ou arquivo, ou alguém precisa abrir o resto?
Roteamento routing.adapter.v1 Choice Qual CLI da allowlist deve tentar este papel?

O QA dispara evidence.relation_to_criterion.v1 uma vez por critério (supports, contradicts, says_nothing). Numa troca de estágio, o progresso manda quatro Nouls: se o diff ainda ataca o critério aberto, se a mudança parece contorno de teste, se a impressão digital do erro se repete, e se o QA descreve o mesmo buraco. Contexto olha no máximo os oito primeiros caminhos de relatedFiles. Com mais de três skills, entra context.skill_candidate_helps.v1.

quality.action.v1 e quality.scope.v1 rodam depois. decideQualityAction já escolheu retrabalho, novo QA ou humano. A escolha do código entra em heuristic com probabilidade 1 na ação tomada e 0 nas outras, e esse Choice não vai no state.

Por que probabilidade tipada audita melhor que texto livre

Texto livre carrega a prova e a conclusão no mesmo bloco. No review isso já nos custou: o modelo escreve APPROVE e, linhas abaixo, lista um finding dentro do escopo. Quem decide é evaluateReview(), relendo os findings. O caso está em O APPROVE do modelo não é o merge.

Uma resposta System One não abre esse segundo canal. parseProviderAnswer(), em src/decisions/answers.ts, recusa o que sai do contrato:

  • Noul fora de [0, 1] não entra.
  • Choice fora do enum enviado não entra. Chave de probabilidade que não está no enum também não.
  • As probabilidades do enum têm de somar 1, com folga de 0.02.
  • Score sem distribuição válida não entra. O level gravado é a opção de maior probabilidade, calculada aqui.

Duas linhas com o mesmo questionId e a mesma questionVersion ficam no mesmo eixo. Mudar o enunciado é outra versão, não um ajuste silencioso do histórico.

Shadow, active e o fallback

A seção semantic_decisions da configuração nasce desligada. Provider fake, mode: shadow, roteamento manual, modelo pinado em jev-1.13.0. O fake responde dentro do processo. Teste não chama a TypeSafe.

createSemanticDecisionEngine() calcula a heurística em toda observação. Com provider: jev e os fatos obrigatórios presentes, a chamada cabe em timeout_ms (1500 ms). O relógio cobre a chamada inteira, inclusive até dois retries de HTTP 429 e 529. Sem chave, no timeout, num status inesperado ou num JSON que não parseia, a linha fica com providerError, jev: null e a heurística intacta. observeSemantic() captura a exceção e a tarefa continua.

Fato obrigatório ausente não sai da máquina. projectObservationState() grava { absent: true }, marca factsStatus: insufficient e não chama o Jev. Em composeAttention(), resposta nula ou fato insuficiente vira unknown, e unknown pede uma pessoa.

O YAML aceita mode: active. A linha gravada continua mode: "shadow" e policyAction: "observe". policyActionFor() não devolve outra ação, e assertNoControlFlow() recusa o resto. Promover uma ação para fora de shadow exige assertPromotionAllowed(): comparação pareada, replay de holdout, adjudicação humana, valor incremental e um teste de que o caminho segue sem o Jev.

O opt-in que já pode mudar uma escolha de execução é routing: automatic, com a camada ligada e provider jev. selectExperimentAdapter() troca o CLI se a confidence de routing.adapter.v1 é pelo menos 0.7 e a opção está na allowlist do papel. Resposta incompleta, confiança baixa, opção fora da lista ou um único CLI autorizado: fica o primeiro adapter configurado, com fallbackReason (manual, incomplete_answers, low_confidence, not_authorized ou kept_default). O Score routing.role_difficulty.v1 só desce faixa no mesmo CLI (dois degraus em trivial, um em easy, zero em moderate ou hard), com o mesmo piso. SKILL_SUGGESTION_PROTOCOL.ready é false, então a pergunta sobre skills pode ser registrada e a lista do prompt continua inteira (fallbackReason: protocol_not_ready).

O que cada linha guarda

Cada linha de semantic-decisions.jsonl é um semantic-decision.v1: tarefa, questionId versionado, provider, digest SHA-256 do estado, heurística, resposta ou null, policyAction, erro, responseModel, factsStatus e chaves ausentes. O log guarda os últimos 4000 registros. Linha inválida vai para semantic-decisions.invalid.jsonl.

Em DONE, FAILED ou CANCELLED, stampLaterOutcome() preenche o que ainda estava vazio (accepted, failed, cancelled). A resposta original fica. Dá para ver depois se um Noul alto coincidiu com aceite ou com falha.

O estado enviado é uma projeção. send_code: true quebra o YAML; o único valor aceito é false. A allowlist descarta diff, patch, content, source, body e prompt. Linha com cara de código vira [CODE_OMITTED]. Segredo passa pelo redator. Payload ainda bloqueado não é perguntado. A chave fica fora do YAML, em T25_JEV_API_KEY ou em jev-secret.json modo 0600. t25 jev show e GET /api/v1/jev dizem se há chave, nunca o valor.

O modelo padrão é jev-1.13.0. A página pública de modelos avisa que o alias jev-latest passa a apontar para outro id quando sai um release, e que as respostas podem mudar junto. Limiar calibrado numa versão fica pinado nela. responseModel guarda o id que respondeu.

Onde o limiar mora, e por que paramos nele

A documentação pública do Noul deixa o limiar no programa e manda o valor do meio para uma pessoa. noulBand() faz isso: 0.7 ou mais é positivo, 0.3 ou menos é negativo, o meio é incerto. composeAttention() lê a relação evidência/critério, se o finding tem contexto, e se a impressão digital repete um incidente do projeto. Um incerto devolve unknown. Contradição, finding opaco ou incidente repetido devolve needs_human. skip_human só sai com o conjunto inteiro decisivo e sem esses sinais. Esse veredito não abre pull request e não faz merge.

O merge na main continua com quem responde pelo repositório. O veredito de review continua em evaluateReview(). A spec só vira plano com aprovação humana. A fábrica roda na máquina de quem a instalou e chama os CLIs que essa pessoa já assina. Enquanto uma prova do gate falta, a heurística é a decisão.

Como usar o mesmo contrato em outro pipeline

  1. Uma pergunta por julgamento, com o enum que o seu código já sabe tratar.
  2. Mande só as chaves que a pergunta lista. O repositório fica onde está.
  3. Falhe fechado no parse. Corpo inválido é erro, guardado à parte do registro válido.
  4. Persista a heurística e a probabilidade. Não devolva a heurística dentro do state.
  5. Escreva o limiar no código, com uma faixa do meio que chama uma pessoa. Estoure o prazo e siga na heurística.

Perguntas frequentes

O Jev aprova o pull request ou faz o merge?

A resposta entra como sinal. O review passa por evaluateReview(), que recalcula o veredito a partir dos findings. O merge na main continua uma ação humana, depois dos checks obrigatórios do repositório.

O que acontece se o Jev expirar no meio da tarefa?

A heurística fica. A linha ganha providerError e jev: null. A tarefa segue. Na atenção, resposta ausente conta como unknown e pede uma pessoa.

Por que não pedir um veredito em texto e fazer parse?

O texto junta a prova e a conclusão, e o parse vira outro lugar para a conclusão se esconder. Choice, Score e Noul devolvem um valor no enum que nós definimos, mais a distribuição. Dá para rejeitar a linha, comparar tarefas e mover o limiar sem reinterpretar um parágrafo.

Dá para rodar o T25 sem chave do Jev?

Sim. O padrão é a camada desligada e provider: fake. t25 jev show mostra provider, modo e se existe chave. Apontar o provider para jev é opt-in, pela CLI, por PUT /api/v1/jev com admin, ou pelo YAML, e o modo de partida continua shadow.

Teste o T25 na sua máquina.

O acesso é por convite: o link pessoal de download chega por e-mail, o instalador verifica o checksum do pacote e o doctor --evaluation valida o ambiente. Gratuito durante os 30 dias do programa de early evaluators — o código e as credenciais não saem da sua máquina.

t25 doctor --evaluation
t25 create "Add retry with backoff to the HTTP client"

Solicitar convite

O APPROVE do modelo não é o merge

Um agente de code review escreve APPROVE, mas quem decide o merge? Veja como o T25 recalcula o veredito a partir dos findings, o bug que nos ensinou isso e como montar um gate humano que não depende do modelo.