AI-translated from English; not yet reviewed by a fluent editor.
# Transforme a saída de um agente em um registro de ações revisado
> Crie um resultado tipado com OpenAI Agents SDK a partir das suas notas, revise as evidências e o significado e passe o resultado aceito a um registro Markdown local com leitura de confirmação dos hashes.
By BIG CHANGE Editorial
Published: 2026-10-20T12:52:38.610Z
Updated: 2026-10-20T12:52:38.610Z
Canonical: https://bigchange.ai/blog/agents-sdk-reviewed-action-register-guide

AI-generated conceptual illustration by BIG CHANGE.
Um resultado de agente pode ter a estrutura JSON correta e interpretar a fonte incorretamente. Este guia oferece ao desenvolvedor Python um caminho completo de um arquivo de notas permitido a um registro local: resultado tipado do produtor, verificações da fonte e do significado, aprovação humana vinculada aos hashes exatos e consumidor que grava e relê um relatório Markdown agrupado. Use notas próprias com compromissos explícitos. Nenhuma nota real ou resultado de modelo foi usado na preparação.
## A grande mudança
- **O que mudou:** O OpenAI Agents SDK aceita um `output_type` do Pydantic, permitindo que a aplicação peça um resultado definido ao agente e o verifique antes de usar. O exemplo acrescenta citações da fonte e um contrato versionado ao resultado.
- **Por que importa:** Um desenvolvedor conectando um agente a outra etapa pode deter saída malformada, incompleta ou sem respaldo antes de ela virar lista de ações. Um revisor ainda precisa julgar se as notas citadas sustentam cada tarefa, responsável e data e se o modelo omitiu algo.
- **O que observar:** O ponto de decisão é a fronteira do consumidor. Esta receita produz um registro local de triagem só após aprovação da execução, fonte e candidato exatos. Uma importação futura em sistema de tarefas exigiria autoridade própria, política de repetição e leitura de confirmação no destino.
## Prepare uma fonte autorizada
Você precisa de Python 3.12, conta API OpenAI autorizada para as notas, revisor com acesso ao original e um modelo cujo suporte atual a saída estruturada por esquema JSON você confirmou na conta. Verifique os termos de dados e preços atuais antes de executar o produtor. Não ponha credenciais nas notas ou na aprovação. A única chamada ao modelo abaixo é `produce`; o restante é processamento local.
Salve suas notas originais em UTF-8 em `notes.txt`. Elas devem conter compromissos de ação reais e explícitos do seu trabalho. Atribua um `source_id` estável, como o identificador do seu registro, e mantenha os bytes do arquivo intactos até a revisão e o consumo. Crie `candidate/` antes de produzir; o candidato deve ser um arquivo novo. Disponibilize um diretório de saída separado para o relatório local posterior.
Registre o hash dos bytes da fonte antes da execução:
```sh
python -c "from pathlib import Path; import hashlib; print(hashlib.sha256(Path('notes.txt').read_bytes()).hexdigest())"
```
Em um ambiente Python 3.12 isolado, crie `recipe-requirements.txt` com estas linhas exatas e instale:
```text
openai-agents==0.24.0
pydantic==2.14.1
```
```sh
python -m pip install -r recipe-requirements.txt
mkdir candidate
```
Essas versões foram instaladas juntas em um ambiente local Python 3.12.7 em 20 de outubro de 2026. Confirme compatibilidade e disponibilidade do modelo ao executar. Salve o script completo abaixo como `action_register_recipe.py` no mesmo diretório. Ele tem dois comandos: `produce` cria candidato e comprovante da execução; `consume` constrói o relatório somente depois de validar e aprovar.
## Execute o produtor tipado
O contrato `Extraction` exige a versão `action-register/v1`, o status `ready`, `partial` ou `blocked`, ID da fonte e SHA-256 dos bytes exatos, ações e um `gap_note`. Cada ação exige texto, citação exata e intervalo de linhas da fonte contado a partir de 1, responsável e data que podem ser null, e `complete` ou `needs_review` como status de evidência. Responsável ou data null significam que as notas não os informam. O modelo recebe a instrução de não inferir nenhum deles. Todos os campos são obrigatórios e campos extras são rejeitados.
Escolha explicitamente o modelo de saída estruturada. Forneça seu caminho permitido e identificador:
```sh
python action_register_recipe.py produce --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --model YOUR-STRUCTURED-OUTPUT-MODEL
```
Leia `candidate/actions.json` ao lado de `candidate/actions.run.json`. O segundo registra UUID da execução, hashes da fonte e candidato, agente final e modelo solicitado. Uma falha cria em vez disso um registro diagnóstico `.failure.json` com o ID da execução no sufixo. Uma interrupção, `None` ou saída final em texto simples, ou um agente final inesperado, nunca contam como sucesso. Este comando não preserva o estado retomável do SDK, `RunState`; o comprovante de falha não retoma a execução. Examine requisição, candidato e estado posterior antes de decidir se cabe uma nova execução.
## Revise o significado e aprove o candidato exato
A aplicação verifica tipos estritos e campos obrigatórios, identidade da fonte, status pronto, ao menos uma ação completa, intervalos válidos, citação no trecho, tarefa ou responsável não vazios e datas canônicas `YYYY-MM-DD`. Isso comprova estrutura e posição da citação. Não estabelece que a frase seja mesmo um compromisso, que nome ou data tenham o sentido afirmado nem que todos os compromissos foram encontrados.
Leia as notas originais e cada ação proposta. Compare significado, responsável, data, citação e omissões. Resolva ambiguidades por uma nova execução revisada; deixe sem aprovação candidatos sem respaldo, parciais ou bloqueados. Depois de conferir que arquivo intacto e candidato correspondem a `actions.run.json`, escreva `approval.json` com exatamente estas sete chaves. Copie ID e hashes do comprovante verificado. Use a identificação real do revisor e uma descrição substancial do que foi de fato conferido:
```json
{
"approved": true,
"run_id": "COPY-RUN-ID",
"source_id": "OWNED-ID",
"source_sha256": "COPY-SOURCE-SHA256",
"payload_sha256": "COPY-PAYLOAD-SHA256",
"reviewer": "YOUR-REVIEWER-ID",
"review_note": "Describe the source, task, owner, date, quotation and omission checks actually performed."
}
```
`approved` deve ser o booleano JSON `true`, não uma string ou número. Os demais valores devem ser strings; revisor e nota não podem ficar vazios. Campos adicionais falham. A aprovação vincula um candidato e revisão da fonte, não futuras reescritas.
## Consuma e releia o relatório
```sh
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --approval approval.json --out private-output
```
O consumidor agrupa ações aceitas em **Assigned with dates**, **Needs an owner** e **Needs a date**. Cada entrada contém tarefa, estado de responsável e data, citação e linhas de origem. Ele grava `action-register-<payload-hash-prefix>.md` e um arquivo vizinho `.receipt.json` com hashes de execução, fonte, candidato e relatório. Abra ambos, compare as ações citadas com as notas e confira `report_sha256` com os bytes do Markdown. Um comprovante válido só informa que o arquivo local foi criado e relido; não aprova gravar em sistema externo de tarefas.
O comando de consumo imprime os caminhos reais do relatório e comprovante. Use-os nesta verificação de bytes:
```sh
python -c "from pathlib import Path; import hashlib,json,sys; report=Path(sys.argv[1]); receipt=json.loads(Path(sys.argv[2]).read_text(encoding='utf-8')); assert hashlib.sha256(report.read_bytes()).hexdigest()==receipt['report_sha256']; print('report hash matches receipt')" PATH-PRINTED-FOR-REPORT PATH-PRINTED-FOR-RECEIPT
```
Com um candidato disponível, teste a rejeição com um exemplo estrutural claramente sintético. Copie seu JSON para outro arquivo, remova qualquer campo obrigatório da primeira ação e invoque `consume` com o arquivo malformado e um diretório de saída novo e vazio. Ele deve falhar antes de criar o arquivo. Nunca trate o exemplo como extração factual nem o aprove. Por exemplo, a verificação local abaixo remove `evidence_quote` de uma cópia:
```sh
python -c "import json; from pathlib import Path; p=json.loads(Path('candidate/actions.json').read_text(encoding='utf-8')); p['actions'][0].pop('evidence_quote'); Path('candidate/SYNTHETIC-malformed.json').write_text(json.dumps(p), encoding='utf-8')"
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/SYNTHETIC-malformed.json --approval approval.json --out synthetic-rejection-output
```
O segundo comando deve falhar e o diretório de saída continuar ausente. Não use o exemplo se o candidato real não tiver primeira ação; já é incompleto para o consumidor. Em uma gravação incerta, examine relatório determinístico e comprovante antes de repetir. Bytes existentes idênticos podem ser reutilizados; diferentes interrompem a operação para conciliação. Mudança na fonte ou versão do esquema exige nova revisão. Nunca repita uma chamada ao modelo só porque a resposta do consumidor local não ficou clara.
## Receita Python completa
Copie este script exatamente em `action_register_recipe.py`:
```python
"""Private, bounded recipe. No model call occurs unless `produce` is explicitly run."""
import argparse
import hashlib
import json
import uuid
from datetime import date
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, ValidationError
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True)
class Action(StrictModel):
action_text: StrictStr
evidence_quote: StrictStr
line_start: StrictInt
line_end: StrictInt
owner: StrictStr | None
due_date: StrictStr | None
evidence_status: Literal["complete", "needs_review"]
class Extraction(StrictModel):
schema_version: Literal["action-register/v1"]
status: Literal["ready", "partial", "blocked"]
source_id: StrictStr
source_sha256: StrictStr
actions: list[Action]
gap_note: StrictStr | None
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def canonical(payload: Extraction) -> bytes:
return json.dumps(payload.model_dump(mode="json"), sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
def check(payload: Extraction, source_id: str, source: bytes) -> None:
if payload.source_id != source_id or payload.source_sha256 != digest(source):
raise ValueError("source identity or revision mismatch")
lines = source.decode("utf-8").splitlines()
if payload.status == "ready" and (not payload.actions or payload.gap_note is not None):
raise ValueError("ready requires actions and no gap")
if payload.status != "ready":
raise ValueError("partial or blocked result cannot reach consumer")
for index, item in enumerate(payload.actions, 1):
if item.evidence_status != "complete":
raise ValueError(f"action {index}: evidence needs review")
if not item.action_text.strip() or not item.evidence_quote.strip():
raise ValueError(f"action {index}: empty task or evidence")
if not 1 <= item.line_start <= item.line_end <= len(lines):
raise ValueError(f"action {index}: invalid source line range")
segment = "\n".join(lines[item.line_start - 1:item.line_end])
if item.evidence_quote not in segment:
raise ValueError(f"action {index}: quotation absent from source span")
if item.owner is not None and not item.owner.strip():
raise ValueError(f"action {index}: blank owner")
if item.due_date is not None:
try:
if date.fromisoformat(item.due_date).isoformat() != item.due_date:
raise ValueError()
except ValueError as exc:
raise ValueError(f"action {index}: due date must be YYYY-MM-DD") from exc
def report(payload: Extraction) -> str:
"""Application consumer: a triage register grouped by missing ownership/date."""
groups = [
("Assigned with dates", lambda x: x.owner is not None and x.due_date is not None),
("Needs an owner", lambda x: x.owner is None),
("Needs a date", lambda x: x.owner is not None and x.due_date is None),
]
rows = ["# Action register for " + payload.source_id, "", "Review artifact; source meaning was approved separately.", ""]
for heading, predicate in groups:
rows += ["## " + heading, ""]
matches = [(n, a) for n, a in enumerate(payload.actions, 1) if predicate(a)]
for number, action in matches:
rows += [f"{number}. {action.action_text}", f" Owner: {action.owner or 'Unspecified'}; due: {action.due_date or 'Unspecified'}", f" Source lines {action.line_start}-{action.line_end}: {action.evidence_quote}", ""]
if not matches:
rows += ["None.", ""]
return "\n".join(rows)
def load_payload(path: Path) -> Extraction:
return Extraction.model_validate_json(path.read_bytes(), strict=True)
def consume(source_id: str, source_path: Path, payload_path: Path, approval_path: Path, out_dir: Path) -> tuple[Path, Path]:
source = source_path.read_bytes()
payload = load_payload(payload_path)
check(payload, source_id, source)
run = json.loads(payload_path.with_suffix(".run.json").read_text(encoding="utf-8"))
payload_hash = digest(canonical(payload))
if run.get("source_id") != source_id or run.get("source_sha256") != digest(source) or run.get("payload_sha256") != payload_hash or run.get("last_agent") != "Owned-source action extractor" or not str(run.get("run_id", "")).strip():
raise ValueError("producer receipt does not match accepted candidate")
approval = json.loads(approval_path.read_text(encoding="utf-8"))
if (type(approval) is not dict
or set(approval) != {"approved", "run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"}
or approval["approved"] is not True
or any(type(approval[key]) is not str for key in ("run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"))
or not approval["reviewer"].strip() or not approval["review_note"].strip()
or approval["run_id"] != run["run_id"]
or approval["source_id"] != source_id
or approval["source_sha256"] != digest(source)
or approval["payload_sha256"] != payload_hash):
raise ValueError("human source/meaning approval must bind exact source and payload hashes")
body = report(payload).encode("utf-8")
stem = "action-register-" + payload_hash[:16]
out_dir.mkdir(parents=True, exist_ok=True)
target = out_dir / (stem + ".md")
receipt = out_dir / (stem + ".receipt.json")
record = {"run_id": run["run_id"], "source_id": source_id, "source_sha256": digest(source), "payload_sha256": payload_hash, "report_sha256": digest(body), "reviewer": approval["reviewer"], "operation": "local action-triage register"}
encoded = (json.dumps(record, indent=2) + "\n").encode("utf-8")
for path, data in [(target, body), (receipt, encoded)]:
if path.exists():
if path.read_bytes() != data:
raise ValueError(f"uncertain prior output differs: {path}; inspect before retry")
else:
with path.open("xb") as handle:
handle.write(data)
if path.read_bytes() != data:
raise IOError(f"readback failed: {path}")
return target, receipt
def produce(source_id: str, source_path: Path, candidate_path: Path, model: str) -> None:
from agents import Agent, Runner
source = source_path.read_bytes()
source_text = source.decode("utf-8")
if not source_text.strip() or candidate_path.exists() or not candidate_path.parent.is_dir():
raise ValueError("source must contain text; candidate must be new and its parent directory must already exist")
run_id = str(uuid.uuid4())
agent = Agent(
name="Owned-source action extractor",
model=model,
instructions=("Extract only explicit action commitments from the supplied numbered source. "
"Return action-register/v1. Copy an exact quote with matching 1-based line span. "
"Use null for an unstated owner or due date; never infer them. "
"Use partial and needs_review for ambiguous evidence; blocked for unusable input. "
"Do not use tools or hand off."),
output_type=Extraction,
)
numbered = "\n".join(f"{n}: {line}" for n, line in enumerate(source_text.splitlines(), 1))
try:
result = Runner.run_sync(agent, f"Source ID: {source_id}\nSHA-256: {digest(source)}\nNumbered source:\n{numbered}")
if result.interruptions or result.last_agent is not agent or not isinstance(result.final_output, Extraction):
raise ValueError("run interrupted, unfinished, or finished by unexpected agent; this CLI does not persist resumable RunState, so do not resume from its failure receipt")
output = result.final_output
if output.source_id != source_id or output.source_sha256 != digest(source):
raise ValueError("producer returned wrong source identity")
candidate = canonical(output)
with candidate_path.open("xb") as handle:
handle.write(candidate) # Candidate only; consumer still requires review.
receipt = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "payload_sha256": digest(candidate), "last_agent": result.last_agent.name, "model": model}
with candidate_path.with_suffix(".run.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(receipt, indent=2) + "\n")
except Exception as exc:
failure = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "error_type": type(exc).__name__, "error": str(exc), "state": "unknown_or_blocked; inspect run and destination before retry"}
with candidate_path.with_name(candidate_path.stem + "." + run_id + ".failure.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(failure, indent=2) + "\n")
raise
def main() -> None:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
for name in ("produce", "consume"):
p = sub.add_parser(name)
p.add_argument("--source-id", required=True)
p.add_argument("--source", type=Path, required=True)
if name == "produce":
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--model", required=True)
else:
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--approval", type=Path, required=True)
p.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
if args.command == "produce":
produce(args.source_id, args.source, args.candidate, args.model)
else:
print(*consume(args.source_id, args.source, args.candidate, args.approval, args.out), sep="\n")
if __name__ == "__main__":
main()
```
## Limites e fontes
O contrato e consumidor local foram verificados offline com cinco testes e sete casos sintéticos de produtor simulado ou repetição. Cobriram esquema estrito, rejeição antes de criar arquivos, agrupamento, hashes e falhas. Não houve envio de esquema a provedor, chamada a modelo, extração de notas reais, aprovação humana factual, relatório de fonte real ou medição de conta ou qualidade. A API tem cobranças dependentes da conta; verifique compatibilidade, preços e permissão sobre dados antes de enviar a fonte.
Para contexto relacionado, o [guia da BIG CHANGE sobre escolher uma tarefa de IA no próprio trabalho](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) cobre a decisão anterior sobre o que testar. Este guia começa numa extração selecionada e permitida e leva um resultado tipado a um consumidor local verificado.
### Fontes e leitura complementar
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) documenta o `output_type` do Pydantic. Não fornece a aprovação humana ou o consumidor local desta receita.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) documenta `final_output`, `last_agent`, interrupções e `to_state()`. Esta interface de linha de comando não salva estado retomável.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) cobre instalação e chave API. Mantenha a chave fora da fonte e dos comprovantes.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) explica regras de esquema estrito e suporte de modelos. Confirme o suporte do modelo escolhido.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) documenta validação estrita e proibição de campos extras.
- [OpenAI Agents SDK 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) e [Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) identificam as versões fixadas instaladas localmente. A aceitação do provedor não foi testada.
## Sources
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) — Manual oficial do output_type Pydantic. Não implementa a aprovação humana ou o consumidor local do artigo.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) — Comportamento oficial de final_output, last_agent, interrupções e to_state. A receita não preserva RunState nem oferece retomada.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) — Contexto oficial de instalação e chave API; não foram usadas chave ou chamada real ao provedor na preparação.
- [OpenAI Agents SDK: referência de esquema de saída](https://openai.github.io/openai-agents-python/ref/agent_output/) — Referência oficial de geração estrita de esquema; inspeção local não comprova aceitação pelo provedor.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) — Regras estritas de campos obrigatórios e additionalProperties e compatibilidade; o leitor deve verificar seu modelo no momento de usar.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) — Documenta strict e extra-forbid; precisão semântica continua sendo revisão humana da fonte.
- [Versão openai-agents 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) — Versão exata do SDK instalada na verificação offline isolada Python 3.12.7; sem chamada ao modelo ou conta.
- [Versão Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) — Versão exata da biblioteca de validação instalada localmente com SDK 0.24.0.
- [BIG CHANGE: Mapeie seu trabalho e escolha uma primeira tarefa para testar com IA](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) — Guia contextual para escolher a tarefa antes desta integração; não estabelece a correção da receita.
Newsletter da BIG CHANGE
A visão ampla, no seu ritmo.
Matérias recentes sobre IA e robótica, mudanças que merecem atenção e ideias práticas para usar. Escolha um briefing diário, um resumo semanal ou uma perspectiva mensal.
Próximo envio programado (UTC): . Sua primeira edição chegará no próximo envio programado após a confirmação.
Sua privacidade, sua escolha.
O armazenamento necessário ajuda a proteger o site e a lembrar suas escolhas. O Google Analytics opcional permanece desativado até você autorizá-lo. Você pode ler todas as matérias usando apenas o armazenamento necessário. Detalhes de privacidade