Operator Approval Loop (Loop de Aprovação do Operador)
Um agente que se comunica com contrapartes externas nunca deve enviar por conta própria e deve manter o operador informado internamente. Este skill define o contrato: todo rascunho de saída é arquivado como uma obrigação, um operador decide sobre o texto exato, e um ledger de entrega prova o que foi enviado.
Quando Usar
- Um agente redige respostas para clientes, fornecedores, investidores ou parceiros em um canal compartilhado, e-mail ou chat, e um humano deve aprovar antes do envio.
- Você precisa de uma trilha de auditoria que vincule cada mensagem enviada ao texto exato do rascunho, ao operador que aprovou e ao momento da decisão.
- Você já viu uma aprovação desatualizada liberar um rascunho reescrito, ou dois trabalhadores entregarem a mesma mensagem aprovada duas vezes.
- Rascunhos continuam perguntando a contrapartes por fatos que o ledger já possui.
Como Funciona
Objetos
| Objeto | Significado |
|---|---|
| Obrigação | Uma coisa que devemos a uma contraparte. O status move-se de drafted (rascunhado) para approved (aprovado) ou rejected (rejeitado), depois para sent (enviado). Carrega direction (direção), counterparty (contraparte), channel (canal) e um timestamp updated_at (atualizado_em). |
| Rascunho (Draft) | Linha auxiliar que contém o texto exato do rascunho, um sha256 desse texto, coordenadas de origem (plataforma, canal, thread, usuário) e prioridade (P0 a P3). Um por obrigação, substituído no re-arquivamento. |
| Decisão | Uma aprovação ou rejeição de um operador, registrada com o ID do operador, um nonce e a época do rascunho contra a qual foi tomada. |
| Snapshot de aprovação | Texto, hash, época e destino imutáveis registrados pelo escritor de decisão já autorizado. Snapshots ausentes não podem conceder despacho. |
| Reivindicação (Claim) | Reserva durável com um token aleatório e estado; no máximo uma reivindicação ativa por obrigação. |
| Entrega (Delivery) | Linha do ledger provando um envio ou aviso para um par (obrigação, decisão). |
O esquema de referência está em references/approval-ledger.sql.
Arquivando um rascunho
- Limpar entradas. Remover caracteres de controle, colapsar espaços em branco em campos de linha única e aplicar limites de comprimento (rascunho, resumo, contexto, contraparte). Campos vazios ou muito grandes são recusados, não truncados silenciosamente.
- Executar a barreira de linha de base (abaixo). Ela pode recusar o arquivamento.
- Fazer hash do texto do rascunho com sha256. O prefixo do hash vai para o resumo para que o painel de aprovação mostre qual texto está aprovando.
- Upsert. Se uma obrigação rascunhada aberta já existir para o mesmo par (contraparte, canal), substituir o rascunho auxiliar e avançar o
updated_atda obrigação. Esse avanço é a rotação de época: qualquer decisão vinculada à época antiga não pode mais liberar o novo texto. Caso contrário, inserir uma nova obrigação com statusdrafted. - Roteie o recibo de arquivamento apenas para um destino interno de operações configurado e verificado. Se a origem for esse destino interno, reconheça-o lá. Nunca-silencioso significa relatório interno, não uma resposta externa automática. Mantenha hashes de rascunho, status de aprovação, identidade do operador e metadados do fluxo de trabalho fora dos canais visíveis à contraparte. Origens desconhecidas ou não classificadas permanecem silenciosas; uma mensagem direta não é automaticamente interna.
Se um destino interno verificado não estiver disponível, mantenha o resultado do arquivamento no resultado da ferramenta interna ou na superfície do operador. Nunca recaia em uma origem externa ou desconhecida. Um resultado de ferramenta exposto a terceiros não é uma superfície interna.
Arquivar um rascunho não autoriza uma resposta externa. Qualquer pergunta esclarecedora ou resposta neutra permitida por política é uma decisão de saída separada, sujeita às restrições existentes de menção, canal, rascunho, congelado e nunca em disciplina-de-canal-da-contraparte. Não deve divulgar metadados internos de aprovação.
Barreira de linha de base
Antes de qualquer rascunho ser arquivado, consultar a linha de base atual para a contraparte (um ledger temporal, armazenamento de contratos ou CRM):
- Contrato assinado ou entregue registrado: recusar o arquivamento com a evidência e uma recomendação. Perguntar a uma contraparte sobre especificações após a assinatura é a falha exata que esta barreira existe para impedir.
- Substituição pelo operador:
force_despite_signed_contractpermite o arquivamento e carimba[BASELINE_OVERRIDE_SIGNED_CONTRACT]no contexto do rascunho. - Serviço de barreira inacessível: o arquivamento prossegue e o contexto é carimbado com
[BASELINE_CHECK_UNAVAILABLE]. O painel vê que a proteção estava desligada. Falhas nunca desativam silenciosamente a barreira. - Quando os fatos estão disponíveis, anexar os mais recentes ao contexto como um resumo
[BASELINE FACTS: ...]para que o rascunho chegue com a verdade atual.
Decidindo
O painel de aprovação lista obrigações com status drafted e direção we_owe_them (nós devemos a eles). Aprovar ou rejeitar escreve uma linha de decisão carregando a época do rascunho (draft_updated_ts) e altera o status da obrigação na mesma transação. Uma decisão cuja época não corresponda ao updated_at atual está desatualizada e não deve liberar nada.
Para uma decisão de aprovação já autorizada, a mesma transação insere uma linha imutável obligation_approval_snapshots: IDs da decisão e obrigação, época atual do rascunho, texto exato e SHA-256, plataforma/canal/thread e tipo draft_sent (rascunho_enviado). O escritor da decisão deve estabelecer autorização antes de escrever; a referência nunca autentica um operador ou fabrica uma decisão. A política de aprovação automática não é ativada ou expandida pela referência. Decisões legadas sem snapshots exigem reconciliação explícita ou uma nova aprovação; nunca preencher permissão do rascunho mutável atual.
Entregando
A referência SQLite está em references/approval_claims.py. Ela concede permissão de despacho, mas nunca chama o transporte. Use um banco de dados de referência local existente inicializado a partir do fixture SQL; o módulo não aplica esquema ou migrações de produção. Apenas um escritor de decisão confiável pode preencher registros de aprovação. Todos os escritores devem ativar chaves estrangeiras e gatilhos recursivos e honrar as proteções de esquema; adulteração administrativa do banco de dados está fora deste modelo.
- Descobrir rascunhos aprovados vinculados. Descoberta não é permissão.
claim()abre sua própria transaçãoBEGIN IMMEDIATE, valida a época aprovada atual, o texto exato, o SHA-256 computado e o destino completo contra o snapshot, e insere uma reivindicação única antes de retornar seu token. Um conflito para o trabalhador antes do transporte. Recibos concluídos não podem ser reivindicados novamente. begin_dispatch()revalida a vinculação e altera atomicamenteclaimed(reivindicado) paradispatching(despachando) usando o token. Apenas seu chamador vencedor recebe odraft_textexato e o destino após o commit. Nunca regenerar texto, reler um auxiliar mutável para transporte ou reutilizar a carga útil para outra tentativa. Uma transação de chamador aninhada é recusada; a permissão não pode depender de um commit posterior do chamador. Nenhuma transação de banco de dados permanece aberta durante o transporte.- Um resultado confirmado bem-sucedido vai para
complete(), que registra atomicamente a coordenada de entrega, marca a reivindicação como entregue e altera a obrigação parasent(enviada). Conclusão idêntica é um no-op; coordenadas conflitantes falham. A chave UNIQUE do recibo desduplica registros, não efeitos externos anteriores. - Exceções, timeouts, morte do trabalhador após begin-dispatch ou falha na persistência do recibo deixam uma tentativa bloqueada.
mark_unknown()registra incerteza. Reivindicações desconhecidas nunca expiram, reabrem, são repetidas automaticamente ou permitem que outra decisão para essa obrigação as ignore. Um chamador confiável pode usarreconcile()com coordenada bem-sucedida confirmada e evidência; o módulo não verifica essa evidência. A ausência de recibo não é prova de não-entrega.
A garantia é uma tentativa de despacho automático por decisão aprovada, não entrega externa exatamente-uma-vez. Uma falha após begin-dispatch, mas antes do transporte, pode deixar zero envios e uma reivindicação retida. Liberar um resultado desconhecido para uma nova tentativa exigiria isolar o executor original e verificar a semântica do provedor; esta referência deliberadamente não fornece tal operação de repetição.
| Estado da reivindicação | Próximos estados permitidos |
|---|---|
| claimed (reivindicado) | dispatching (despachando) ou cancelled (cancelado) antes do despacho |
| dispatching (despachando) | delivered (entregue) ou unknown (desconhecido) |
| unknown (desconhecido) | delivered (entregue) apenas por reconciliação confiável |
| delivered (entregue), cancelled (cancelado) | terminal; a chave da decisão não pode ser reutilizada |
Enquanto uma reivindicação estiver ativa, as proteções do banco de dados congelam as escritas de obrigação, rascunho e decisão, incluindo substituições. Snapshots e reivindicações não podem ser apagados. Cancele uma operação reivindicada com seu token antes de re-arquivar; o token desatualizado então não concede nada. Após o início do despacho, segure novas edições ou revogação para reconciliação. Isso serializa as alterações em vez de fingir que pode lembrar uma operação em andamento.
Decisões rejeitadas e linhas legadas sem sidecars/snapshots de rascunho nunca entram neste caminho de envio de rascunho externo. Reporte-as na superfície do operador interno para tratamento manual. Os rodapés de recibo interno permanecem internos:
approved by <operator> · receipt <decision_id> · draft sha256 <prefix>.
Nunca altere texto externo já aprovado para anexar metadados do fluxo de trabalho.
Validação local focada usa bancos de dados temporários, conexões separadas e um contador de tentativas simulado, não um provedor ou mensagem real:
python3 -m unittest discover -s tests/skills -p 'test_approval_delivery_claims.py'.
Os testes exigem Python 3.11+ com suporte a serialização SQLite; a referência usa apenas a biblioteca padrão. As verificações de contrato de padrão de mesa existentes permanecem uma verificação de compatibilidade separada.
Aprovação automática com prazo (opcional)
Um rascunho pode conter auto_send_after (segundos de época). Uma varredura aprova rascunhos cujo prazo passou sem decisão, registrando o operador auto-ttl, e então a entrega prossegue pelo caminho normal. As ações do operador sempre vencem: uma decisão altera o status antes que a varredura o veja, e um re-arquivamento gira a época e move ou limpa o prazo. A varredura verifica novamente o status e a época dentro da transação de escrita, de modo que uma condição de corrida se resolve como um no-op. Rascunhos sem prazo permanecem com barreira rígida para sempre.
Vinculação de sinal
Um rascunho pode nomear a obrigação de entrada à qual responde (signal_obligation_id). Este é o único link verdadeiro para medição de latência (sinal de entrada para resposta rascunhada) e permite que a varredura de SLA trate esse item de entrada como respondido. Rejeite o arquivamento se a linha referenciada não existir.
Exemplos
Arquivar um rascunho
file_request(
draft="Obrigado, podemos reservar o horário até sexta. Qual data de início funciona?",
counterparty="acme-fornecedor",
context="resposta à pergunta sobre janela de entrega",
origin_platform="slack", origin_channel="#acme-compartilhado",
origin_thread="1712345678.000100", priority="P1",
signal_obligation_id=412)
-> {obligation_id: 431, draft_sha256: "9f2c...", refiled: false}
O destino interno de operações configurado e verificado vê:
Rascunho arquivado para aprovação (P1, sha 9f2c8a1b). Aguardando operador.
O canal de origem visível à contraparte não recebe aviso de arquivamento. Se nenhum destino interno verificado estiver disponível, o recibo permanece no resultado da ferramenta interna ou na superfície do operador, sem fallback externo.
Re-arquivar após um direcionamento
O operador pede um rascunho mais curto. Arquivar novamente para o mesmo par (contraparte, canal) retorna refiled: true, o texto auxiliar e o hash mudam, e updated_at avança. Uma aprovação clicada na linha antiga do painel carrega a época antiga e é ignorada.
Recusa da barreira
DeskApprovalError: a barreira de linha de base recusou este rascunho: o ledger mostra um
contrato assinado para 'acme-fornecedor'. Evidência: acordo principal executado
em 2026-08-14. Recomendação: não pergunte. Re-arquive com
force_despite_signed_contract=true se isso for genuinamente uma nova thread.
Rodapé de entrega em um canal interno
Confirmado para sexta, data de início 2026-09-08.
aprovado por operador-a · recibo 118 · hash do rascunho 9f2c8a1b2d3e4f50
Invariantes a testar
- Recibos de arquivamento vão apenas para operações internas configuradas e verificadas; a origem recebe um apenas quando é esse destino interno verificado.
- Uma origem desconhecida permanece silenciosa. Um destino interno indisponível usa o resultado da ferramenta interna ou superfície do operador, sem fallback externo.
- Mesmo par (contraparte, canal) arquivado duas vezes gera uma obrigação, duas épocas.
- Uma decisão com época desatualizada nunca resulta em uma linha de entrega.
- Dois reivindicantes concorrentes geram uma permissão de despacho; os perdedores nunca tentam transporte.
- Resultados desconhecidos e falha na persistência do recibo nunca habilitam uma repetição automática.
- Conclusão bem-sucedida registra o recibo e o status enviado em uma transação.
- Uma época, texto, hash ou destino alterado não pode adquirir ou iniciar uma reivindicação.
- Reivindicações ativas bloqueiam re-arquivamento; apenas o cancelamento pré-despacho pode liberar essa retenção.
- Barreira indisponível carimba o marcador; barreira de contrato assinado recusa sem força.
- Auto-ttl nunca dispara contra texto que o operador re-arquivou desde então.