Dois PRs executam a mesma revisão automatizada. O primeiro aponta uma dependência sem responsável; o segundo não. O modelo parece igual, o workflow também, mas ninguém consegue responder qual conjunto de instruções chegou ao agente em cada execução.
Sem essa identidade, “o agente mudou de comportamento” não é um diagnóstico. É só uma observação difícil de reproduzir. O resultado que queremos é mais modesto e mais útil: localizar o prompt no Git, calcular o SHA-256 dos bytes enviados, registrar essa identidade junto da execução e comparar mudanças controladas no CI.
A orientação atual da OpenAI é tratar prompts de produção como código da aplicação: arquivos versionados, revisão por PR, fixtures e verificações no processo de entrega. Isso também evita construir uma solução nova sobre v1/prompts: a criação desses objetos foi despriorizada em 3 de junho de 2026, e o endpoint tem desligamento previsto para 30 de novembro de 2026 (documentação de prompting).
O mínimo que precisa estar no repositório
Para um agente de revisão no GitHub Actions, uma estrutura pequena já resolve:
.github/
codex/
prompts/
review.md
review.schema.json
workflows/
agent-review.yml
.agent/
tools.json
scripts/
record_agent_usage.py
usage.json
AGENTS.md
review.md contém a tarefa e a rubrica daquela revisão. AGENTS.md guarda orientação durável do repositório. No Codex, os arquivos AGENTS.md aplicáveis são concatenados da raiz até o diretório de trabalho; uma orientação mais próxima aparece depois e prevalece. AGENTS.override.md serve para uma substituição temporária.
Há uma diferença operacional importante: ~/.codex/AGENTS.md pertence ao ambiente da máquina e não aparece no PR. Se uma regra é obrigatória no CI, ela precisa estar no repositório. Confiar no arquivo da home do autor é um jeito bastante eficiente de revisar uma configuração que mais ninguém possui.
Os caminhos .agent/tools.json e .agent/prompt.toml são convenções do nosso harness, não APIs ou nomes reservados de fornecedor. O mesmo vale para usage.json: ele é o artefato de auditoria da execução, não o objeto usage retornado por uma API.
Identidade do checkout não é identidade do prompt
Vale registrar duas coisas diferentes:
git describe --tags --always --dirtyidentifica o checkout;- SHA-256 identifica o conteúdo efetivamente carregado.
O primeiro pode produzir algo como v1.4.0-7-g2414721. Ele ajuda a encontrar o commit e denuncia uma árvore suja, mas não substitui o hash do prompt. Para git describe enxergar o histórico e as tags no runner, o checkout não pode ser raso.
Também não misture git hash-object com SHA-256 bruto. O comando do Git calcula a identidade de um objeto blob e pode considerar filtros; hashlib.file_digest(..., "sha256"), com o arquivo aberto em modo binário, calcula o digest dos bytes lidos. CRLF diferente, hash diferente — os bytes não aceitam a justificativa de que “visualmente está igual”.
Este script acrescenta a identidade ao usage.json já existente:
import hashlib
import json
import subprocess
from pathlib import Path
def sha256_file(path: Path) -> str:
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
prompt_path = Path(".github/codex/prompts/review.md")
tools_path = Path(".agent/tools.json")
usage_path = Path("usage.json")
usage = json.loads(usage_path.read_text()) if usage_path.exists() else {}
usage.update({
"prompt_path": str(prompt_path),
"prompt_sha": sha256_file(prompt_path),
"tools_sha": sha256_file(tools_path) if tools_path.exists() else None,
"git_describe": subprocess.check_output(
["git", "describe", "--tags", "--always", "--dirty"],
text=True,
).strip(),
"git_sha": subprocess.check_output(
["git", "rev-parse", "HEAD"], text=True
).strip(),
})
usage_path.write_text(json.dumps(usage, indent=2, sort_keys=True) + "\n")
Esse exemplo pressupõe que review.md é enviado sem transformação. Se o harness renderiza templates, expande includes ou monta mensagens, grave o conteúdo resolvido em um arquivo temporário e calcule o digest desse arquivo. O hash deve representar os bytes enviados, não a boa intenção armazenada no caminho original.
Como observação de produção, publique usage.json como artefato de cada job e faça o CI recalcular o SHA do arquivo comprometido. A verificação deve falhar se o valor registrado divergir. Assim, cada saída carrega uma ligação auditável entre checkout, prompt e ferramentas.
Prompt e ferramentas mudam o agente por canais diferentes
O prompt define política, tarefa e rubrica. A lista tools, por outro lado, expõe funções com nome, descrição e parâmetros em JSON Schema. Ela é um campo separado das instruções tanto em chamadas com function tools quanto em harnesses de agentes.
Por isso, não embuta tools.json no texto apenas para obter um único hash. Faça o contrário: mantenha os dois artefatos separados e registre prompt_sha e tools_sha. Uma ferramenta renomeada ou um parâmetro que passa a ser obrigatório pode alterar o comportamento mesmo quando o prompt permanece byte a byte igual.
A mesma disciplina vale para AGENTS.md: registre ou verifique separadamente a cadeia de instruções carregada pelo runner quando ela fizer parte do contrato. O prompt_sha de review.md não deve fingir que cobre arquivos que não foram seus bytes de entrada.
Também não use seed do Chat Completions como versão do prompt. Seed trata da amostragem e ainda convive com mudanças indicadas por system_fingerprint; ele não identifica instruções, ferramentas nem checkout. Fixá-lo pode ajudar um experimento específico, mas não torna duas revisões bit a bit idênticas.
Um PR que muda uma linha precisa deixar um recibo
Considere duas revisões do mesmo arquivo de prompt. review.v4.md contém tudo que havia em review.v3.md e adiciona uma linha à rubrica:
--- a/.github/codex/prompts/review.v3.md
+++ b/.github/codex/prompts/review.v4.md
@@
- Do not suggest new dependencies.
+- If the diff adds a dependency, require a human reviewer.
- End with a JSON object: {"ok": bool, "findings": [string]}
Os arquivos executados neste exemplo têm estas identidades:
review.v3.md — 216 bytes
sha256: 4d93b509ce99e6e0d210fc96147e9ae13d04a03d3a1a5acdc1f0f29132fa4b9a
review.v4.md — 275 bytes
sha256: d2d3f27d4f42eace3af1ca6f3abdc50593df5588511b3b24342f71083cf92b36
A diferença de hash prova que a entrada mudou; não prova que a revisão ficou melhor. Para avaliar comportamento, congele o diff do PR usado como fixture e execute dois jobs que diferem somente no prompt comprometido. Em ambos, preserve usage.json e a saída do agente. O teste deve afirmar que prompt_sha mudou e então comparar os resultados.
Esse desenho resolve a rastreabilidade: qualquer resultado publicado pelo job aponta para uma revisão exata do prompt. Se também usamos limite de tokens e circuit breaker, os campos de identidade entram no mesmo artefato de execução; não precisam criar uma segunda contabilidade.
Um workflow de CI sem uma segunda fonte escondida
O Codex GitHub Action aceita prompt ou prompt-file, nunca os dois. Para revisão versionada, escolha o arquivo comprometido. O checkout vem antes da action, fetch-depth: 0 permite usar git describe, e codex-version recebe uma versão exata aprovada pelo repositório. O exemplo oficial do Codex Action ainda mostra actions/checkout@v5; este artigo fixa actions/checkout@v7 porque essa é a major atual em 2026-09-05.
name: Agent review
on:
pull_request:
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Run Codex review
id: codex
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
prompt-file: .github/codex/prompts/review.md
output-file: artifacts/review.json
codex-version: ${{ vars.CODEX_VERSION_EXACT }}
codex-args: >-
["--output-schema", ".github/codex/review.schema.json"]
- name: Record prompt identity
run: python scripts/record_agent_usage.py
- name: Verify recorded SHA
run: |
python - <<'PY'
import hashlib, json
from pathlib import Path
prompt = Path(".github/codex/prompts/review.md")
with prompt.open("rb") as stream:
actual = hashlib.file_digest(stream, "sha256").hexdigest()
recorded = json.loads(Path("usage.json").read_text())["prompt_sha"]
assert recorded == actual, (recorded, actual)
PY
Depois, publique artifacts/review.json e usage.json com a etapa de artefatos já fixada pelo repositório.
CODEX_VERSION_EXACT deve conter uma versão completa e imutável aprovada pelo time, não latest nem uma faixa. Mantê-la como variável evita inventar uma versão no exemplo; protegê-la e registrar seu valor em usage.json evita que a conveniência vire outra entrada invisível.
Na prática, o gate principal deve validar o JSON contra review.schema.json: campos obrigatórios, severidade permitida, localização do achado e ausência de estrutura inválida. O Markdown exato não deve ser o único golden obrigatório. Mesmo com modelo e versão fixados, ainda existe não determinismo residual; pontuação, ordem e redação podem variar sem quebrar o contrato.
Você pode guardar saídas representativas e usar git diff --no-index ou diff -u para inspeção humana. Só não transforme pontuação idêntica em critério de saúde do pipeline. A máquina adora falhar por uma vírgula quando recebe essa oportunidade.
Armadilhas que a verificação precisa capturar
Três falhas aparecem com frequência nesse desenho:
- O workflow define
prompt-filee também umprompt:inline. A action rejeita a duplicidade, e a correção é manter uma única fonte versionada. - O hash é calculado antes de renderizar o template. A correção é persistir e hashear o payload resolvido; a observação é comparar esse digest com o registrado no artefato.
- O teste muda prompt, fixture, ferramentas e modelo ao mesmo tempo. A correção é congelar o PR de entrada e alterar somente o arquivo de prompt entre as duas execuções.
Além disso, recalcule tools_sha, registre o commit com git_sha e confira se o runner carregou os AGENTS.md esperados. A orientação durável de projeto merece ficar perto do código; detalhes sobre como montar o restante do contexto pertencem a outra etapa do agente, como discutimos em memória de projeto para coding agents.
Quando adotar e quando recuar
A DevDojo adotaria esse contrato para um agente de revisão que roda em CI, comenta em PR ou pode bloquear merge. Nesse ponto, prompt, ferramentas, versão do runner e saída estruturada já fazem parte do que colocamos em produção: passam por review, recebem hashes e rodam contra fixtures congeladas.
Para prompts exploratórios de uma sessão, isso é peso desnecessário. Registre hipótese, texto e resultado em um diário de laboratório ou nota diária, sem transformar a tentativa em check obrigatório. AGENTS.override.md também pode servir a uma orientação temporária, desde que não seja confundido com o contrato compartilhado do CI.
O próximo passo é pequeno: mova uma revisão real para .github/codex/prompts/review.md, acrescente prompt_sha e tools_sha ao usage.json, e rode a mesma fixture com review.v3.md e review.v4.md. Se o CI consegue provar qual entrada executou e validar o schema da saída, a próxima regressão deixa de ser uma discussão sobre memória e passa a ser um diff verificável.