O MUNDO NÃO ESTÁ PARADO.RSS
BIG CHANGE.
BIG CHANGE, à distância de um toque
BIG CHANGE, à distância de um toque

Abra espaço para ver o panorama geral.

Adicione a BIG CHANGE ao ecrã principal ou ambiente de trabalho. Abre numa janela própria, com as histórias mais recentes quando está online.

iPhone & iPad
Abra bigchange.ai no Safari. Toque em Partilhar e, em seguida, em Adicionar ao ecrã principal. Ative Abrir como aplicação web, se for apresentado, e toque em Adicionar.
Android
Abra bigchange.ai no Chrome. No menu do navegador, escolha «Instalar aplicação» ou «Adicionar ao ecrã principal».
Computador
No Chrome ou Edge, utilize o ícone de instalação na barra de endereço ou o menu de aplicações do navegador. No Safari para Mac, escolha Ficheiro → Adicionar à Dock.

Edição Markdown

AI-translated from English; not yet reviewed by a fluent editor.

# Transforme a saída de um agente num registo de ações revisto

> Crie um resultado tipado com OpenAI Agents SDK a partir das suas notas, reveja as provas e o significado e passe o resultado aceite a um registo Markdown local com releitura e verificação de 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

![Conceptual charcoal illustration of one person holding a larger blank ruled source page and a smaller blank candidate page, with an orange reading strip across the source.](https://bigchange.ai/api/media/file/source-candidate-human-review-hero-v2.png)
AI-generated conceptual illustration by BIG CHANGE.

Um resultado de agente pode ter a estrutura JSON correta e interpretar mal a fonte. Este guia dá a um programador Python o percurso completo de um ficheiro de notas permitido a um registo local: resultado tipado do produtor, verificações da fonte e do significado, aprovação humana ligada aos hashes exatos e um consumidor que escreve e relê um relatório Markdown agrupado. Use notas próprias com compromissos explícitos. Não foram usadas notas reais ou resultados de modelos na preparação.

## A grande mudança

- **O que mudou:** O OpenAI Agents SDK aceita um `output_type` do Pydantic, pelo que uma aplicação pode pedir um resultado definido ao agente e verificá-lo antes de o usar. O exemplo acrescenta citações da fonte e um contrato com versão ao resultado.
- **Porque importa:** Um programador que liga um agente a outro passo pode travar uma saída malformada, incompleta ou sem fundamento antes de se tornar uma lista de ações. Um revisor continua a ter de avaliar 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 está na fronteira do consumidor. Esta receita produz um registo local de triagem apenas após a aprovação da execução, fonte e candidato exatos. Uma importação posterior num sistema de tarefas exigiria autoridade própria, política de repetição e releitura no destino.

## Prepare uma fonte autorizada

Precisa de Python 3.12, uma conta API OpenAI autorizada para as notas, um revisor que leia o original e um modelo cujo suporte atual a saída estruturada por esquema JSON tenha confirmado na conta. Verifique os termos de dados e preços atuais antes de executar o produtor. Não coloque credenciais nas notas ou no ficheiro de aprovação. A única chamada ao modelo abaixo é `produce`; o restante é processamento local.

Guarde as notas originais em UTF-8 em `notes.txt`. Devem conter compromissos de ação explícitos e reais do seu trabalho. Atribua um `source_id` estável, como o identificador do seu registo, e mantenha os bytes do ficheiro inalterados até à revisão e ao consumo. Crie `candidate/` antes de produzir; o ficheiro candidato deve ser novo. Disponibilize um diretório de saída separado para o relatório local posterior.

Registe 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())"
```

Num 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
```

Estas versões foram instaladas em conjunto num ambiente local Python 3.12.7 em 20 de outubro de 2026. Confirme compatibilidade e disponibilidade do modelo quando executar. Guarde o script completo abaixo como `action_register_recipe.py` no mesmo diretório. Tem dois comandos: `produce` cria um candidato e recibo de execução; `consume` constrói o relatório apenas após validação e aprovação.

## Execute o produtor tipado

O contrato `Extraction` exige a versão `action-register/v1`, o estado `ready`, `partial` ou `blocked`, o 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 admitem null, e `complete` ou `needs_review` como estado da prova. Responsável ou data null significam que as notas não os indicam. O modelo é instruído a não inferir nenhum. Todos os campos são obrigatórios e os adicionais são rejeitados.

Escolha explicitamente o modelo de saída estruturada. Indique o 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 regista o UUID da execução, hashes da fonte e candidato, agente final e modelo pedido. Uma falha cria em alternativa um registo de diagnóstico `.failure.json` com o ID de execução como 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 recibo de falha não retoma a execução. Examine pedido, candidato e estado posterior antes de decidir se uma nova execução é adequada.

## Reveja o significado e aprove o candidato exato

A aplicação verifica tipos estritos e campos obrigatórios, identidade da fonte, estado pronto, pelo 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 prova a estrutura e a localização da citação. Não estabelece que a frase seja um compromisso real, que nome ou data tenham o significado afirmado ou que todos os compromissos tenham sido encontrados.

Leia as notas originais e cada ação proposta. Compare significado, responsável, data, citação e omissões. Resolva a ambiguidade através de uma nova execução revista; não aprove candidatos sem fundamento, parciais ou bloqueados. Após verificar que o ficheiro inalterado e o candidato correspondem a `actions.run.json`, escreva `approval.json` com exatamente estas sete chaves. Copie o ID e hashes do recibo verificado. Use a identificação real do revisor e uma descrição substancial do que foi efetivamente verificado:

```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` tem de ser o booleano JSON `true`, não uma cadeia de texto nem número. Os restantes valores são cadeias de texto; revisor e nota não podem estar vazios. Campos adicionais falham. A aprovação liga um candidato e revisão da fonte, não qualquer reescrita futura.

## 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 aceites 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. Escreve `action-register-<payload-hash-prefix>.md` e um ficheiro vizinho `.receipt.json` com hashes da execução, fonte, candidato e relatório. Abra ambos, compare as ações citadas com as notas e verifique `report_sha256` contra os bytes do Markdown. Um recibo válido só diz que o ficheiro local foi criado e relido; não aprova escrever num sistema externo de tarefas.

O comando de consumo imprime os caminhos reais do relatório e recibo. Use-os para esta 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, verifique a rejeição com um exemplo estrutural claramente sintético. Copie o JSON para outro ficheiro, retire qualquer campo obrigatório da primeira ação e invoque `consume` com esse ficheiro malformado e um diretório de saída novo e vazio. Tem de falhar antes de criar o ficheiro. Nunca considere o exemplo uma extração factual nem o aprove. Por exemplo, a verificação local abaixo retira `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 este consumidor. Numa escrita incerta, examine relatório determinístico e recibo antes de repetir. Bytes existentes idênticos podem ser reutilizados; divergentes interrompem para conciliação. Uma fonte ou versão de esquema alterada exige nova revisão. Nunca repita uma chamada ao modelo apenas porque a resposta do consumidor local foi pouco clara.

## Receita Python completa

Copie este script exatamente para `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. Abrangeram esquema estrito, rejeição antes de criar ficheiros, agrupamento, hashes e falhas. Não houve envio de esquema a fornecedor, chamada a modelo, extração de notas reais, aprovação humana factual, relatório com fonte real ou medição de fatura ou qualidade. A API tem encargos 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 para 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 com uma extração selecionada e permitida e leva um resultado tipado a um consumidor local verificado.

### Fontes e leituras adicionais

- [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 nem 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 comandos não guarda estado retomável.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) cobre instalação e configuração da chave API. Mantenha a chave fora da fonte e dos recibos.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) explica regras de esquema estrito e modelos suportados. Confirme o suporte do modelo escolhido.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) documenta validação estrita e proibição de campos adicionais.
- [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 fornecedor não foi testada.

## Sources

- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) — Manual oficial de output_type Pydantic. Não implementa a aprovação humana nem 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 retoma.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) — Contexto oficial de instalação e chave API; não foi usada chave nem chamada real ao fornecedor na preparação.
- [OpenAI Agents SDK: referência do esquema de saída](https://openai.github.io/openai-agents-python/ref/agent_output/) — Referência oficial de geração estrita de esquema; a inspeção local não prova aceitação pelo fornecedor.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) — Regras estritas de campos obrigatórios e additionalProperties, e compatibilidade; o leitor tem de verificar o modelo escolhido no momento da utilização.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) — Documenta strict e extra-forbid; exatidão semântica continua a exigir 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 fatura.
- [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 o 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 uma tarefa antes desta integração; não estabelece a correção da receita.