Specter Decision Engine 0.3.0

September 16, 2026 · View on GitHub

Decisões tipadas, não texto. Kernel numérico para avaliação de decisões limitadas e independentes dentro de software e agentes.

Autor do projeto original: Guilherme Peralta Novaes. Esta versão 0.3.0 é um refinamento do desenho 0.1/0.2, feito com assistência de IA, preservando o contrato público e a postura de honestidade do repositório original.

Status honesto. O núcleo é executável, testado e medido. Não há modelo treinado embarcado. O backend local (SystemOneLocalBackend) é um pontuador léxico determinístico — um baseline de regras auditável, não um modelo semântico. Calibração real, avaliação semântica e gates de produção continuam pendentes e dependem do operador. Este projeto não reproduz os pesos nem o treinamento do Jev.


1. Por que este formato existe

Um LLM devolve string. Para o software usar, alguém precisa parsear, validar e torcer para o modelo não sair dos trilhos. A alternativa é inverter o contrato: o programa declara antecipadamente quais respostas são possíveis, e o modelo devolve números sobre esse conjunto fechado.

LLM de chatDecisão tipada
Saídastring arbitráriavalor do conjunto declarado
Erro de tipopossívelimpossível por construção
Incertezaopcional e mal calibradadistribuição + confiança em todo retorno
Falhavira texto plausívelvira erro tipado, nunca uma decisão
Amostragemsequencial por tokenprojeção paralela e independente

Três primitivas, iguais às do desenho original:

Tipovalueprobabilities / legendEvento calibrado da confidence
noulP(sim) em [0,1][P(não), P(sim)] / [false,true]classificação pelo limiar 0.5 correta
choiceopção do argmax; empate resolve pelo menor índiceordem das opções de entradaopção escolhida correta
scoresoma(i × P(i)) em [0, K−1]ordem da legenda de entradaerro absoluto ≤ tolerance

confidence é P(evento acima) — não é entropia, não é max(probabilities) cru e não é declaração do modelo sobre si mesmo.

2. Começo rápido

Requer apenas Python 3.11+. Sem dependências em runtime.

python -m unittest discover -s tests -v     # 126 testes
python -m specter_decision.cli capabilities
python tools/bench.py --iterations 300
import asyncio
from specter_decision import (
    Question, SpecterDecisionEngine, SystemOneLocalBackend, SystemOneCalibration,
)

engine = SpecterDecisionEngine(
    SystemOneLocalBackend(),
    SystemOneCalibration(domain="suporte-v1"),   # troque por artefato real
    domain="suporte-v1",
    concurrency=8,
    timeout=2.0,
)

answers = asyncio.run(engine.decide(
    {"ticket": "A integração de pagamentos falhou e estou perdendo vendas."},
    [
        Question.choice("equipe", "Qual equipe deve atender?", {
            "billing":   "Cobrança, faturas, reembolso",
            "technical": "Erros, integrações, indisponibilidade",
            "sales":     "Preço, upgrade, contrato",
        }),
        Question.score("severidade", "Qual a severidade?",
                       ["cosmético", "falha com alternativa", "bloqueio sem alternativa"]),
        Question.noul("urgente", "O relato indica urgência?"),
    ],
))

rota = answers[0]
if rota["status"] == "ok" and rota["confidence"] >= 0.85:
    fila = rota["value"]        # a decisão NÃO autoriza nem executa ação

Descrever as opções (criteria) melhora materialmente a qualidade: é a diferença entre o modelo ver "alpha" e ver "alpha: cobrança, faturas e reembolso". Há teste cobrindo exatamente esse ganho.

2.1 Exemplos executáveis

Todos rodam sem rede e sem configuração:

ArquivoO que demonstra
examples/01_triagem_suporte.pytriagem completa: rubricas, fan-out especulativo, gate por confiança, índice composto
examples/02_calibracao.pyajustar artefato, ler tabela de confiabilidade, salvar/recarregar, ver acerto e erro confiante
`examples/03_backend_proprio.py$\text{escrever} \text{seu} \text{backend} \text{com} \text{prefill} ú\text{nico} — 8{,}3 \times \text{medido} \text{contra} \text{o} \text{caminho} \text{sem} \text{lote}
$examples/04_servico_http.py`ativar a superfície HTTP sem abrir porta, com erros tipados
python examples/03_backend_proprio.py
# 32 perguntas, prefill de 10 ms
# sem logits_batch :    353.5 ms   prefills = 32
# com logits_batch :     42.5 ms   prefills = 1
# ganho            :      8.3x

3. Arquitetura

state → snapshot canônico (1x) → validação → planejamento
      → prefill único do estado
      → logits por pergunta        ┌ lote: 1 chamada, N cabeças
                                   └ fan-out: N chamadas, semáforo global
      → temperatura versionada → softmax POR SEGMENTO (nunca global)
      → projeção tipada → validação → Decision
  • O mesmo snapshot vai para todas as perguntas. Nenhuma pergunta enxerga a resposta de outra. O id escolhido pela aplicação nunca chega ao backend (é substituído por _).
  • Concorrência de chamadas não é paralelismo de GPU. O backend precisa ser cooperativo; adaptador bloqueante exige executor ou isolamento de processo próprio. O contrato sozinho não impede estado interno compartilhado.
  • Requisição inválida é rejeitada com ValueError('INVALID_INPUT: ...') antes de qualquer inferência. Falha nunca vira decisão.

Módulos

ArquivoResponsabilidade
numerics.pysoftmax estável, temperatura, simplex exato, esperança
canonical.pysnapshot canônico, validação iterativa, fingerprint
types.pyQuestion/Decision, limites, assinatura criptográfica
protocols.pyBackend, BatchBackend, Calibration
engine.pyplanejamento, agendamento com deadline, projeção
calibration.pytemperatura por NLL, isotônica PAV, métricas, artefato
policy.pygate por confiança, score composto, alta cardinalidade
backends/system_one.pybaseline léxico local com prefill cacheado
backends/jev.pyadaptador para a API System One da TypeSafe
service.py / http.pyenvelope, autenticação, rotas, cliente
cli.pyevaluate, capabilities, selftest, bench, calibrate

4. O que mudou em relação a 0.2 — e por quê

  1. Lote nativo (BatchBackend). Se o backend expõe logits_batch, o estado é codificado uma vez para N perguntas. É o ganho estrutural do desenho System One; fan-out virou o caminho de compatibilidade.
  2. Deduplicação por assinatura. Perguntas de conteúdo idêntico (comuns em fan-out especulativo) são computadas uma vez. Medido: 2,2× mais rápido em 64 perguntas repetidas.
  3. Admissão consciente do deadline. Trabalho nascido vencido não é iniciado: devolve TIMEOUT sem custo de backend.
  4. Gate de calibração por pergunta. Uma pergunta descoberta pelo artefato não derruba as outras.
  5. Semáforo por (instância, event loop). Recriado quando o loop muda — elimina a classe de bug de semáforo compartilhado entre loops.
  6. Calibração de verdade. 0.2 definia o protocolo mas não entregava como ajustar. Agora há temperatura por minimização de NLL, isotônica por PAV, Brier/NLL/ECE/confiabilidade com bootstrap e artefato versionado que recusa domínio, modelo ou pergunta fora do registro.
  7. Validação antecipada. Pergunta inválida falha na construção, não na avaliação (mesmo ValueError, mesmo prefixo INVALID_INPUT).
  8. Simplex exato. A soma das probabilidades é 1.0 em ponto flutuante, não "1.0 ± 1e-16".
  9. Abstenção e recibos, opcionais. abstain_below transforma confiança baixa em ABSTAINED; receipts=True anexa hash auditável que vincula a decisão ao estado sem expor o conteúdo do estado.
  10. Políticas explícitas (ConfidenceGate, composite_score, rank_then_choose para mais de 255 opções) — os padrões que todo fluxo real reimplementa errado.
  11. Adaptador Jev. O mesmo contrato tipado, com um modelo treinado real por trás, quando o operador tem credencial.

Detalhes de migração em MIGRATION.md. Medições em BENCHMARKS.md.

5. Calibração

Sem artefato compatível, o kernel devolve UNCALIBRATED sem chamar o modelo. O caminho para um artefato legítimo:

from specter_decision.calibration import CalibrationArtifact

artefato = CalibrationArtifact.fit(
    registros,                    # [(Question, logits, verdade), ...]
    domain="suporte-v1",
    model_id=backend.model_id,
    holdout=0.3,
    bind_signatures=True,         # vincula às perguntas exatas
)
open("suporte-v1.json", "w").write(artefato.to_json())
print(artefato.metrics)           # nll, brier, ece, acurácia com IC95

Regras que o código não tem como impor por você:

  1. Separe treino / calibração / teste por entidade e por tempo, sem duplicatas vazando entre os conjuntos.
  2. Avalie Brier, NLL, diagramas de confiabilidade e ECE com incerteza. ECE isolado não basta.
  3. Modelo novo, rubrica nova ou domínio novo invalidam o artefato.
  4. Drift ou cobertura insuficiente → abstenção ou revisão humana.
  5. Calibração é propriedade estatística no domínio medido, não garantia universal. O kernel não autentica a veracidade de um artefato fornecido pela aplicação.

6. Limites e postura de segurança

  • 128 perguntas por requisição, 255 opções em choice, 10 níveis em score, 256 KiB de estado, profundidade 32.
  • JSON com NaN/Infinity, chave não-string, ciclo, tipo não serializável ou cardinalidade inválida é rejeitado.
  • Sem geração textual. Rótulos vêm da entrada; erros são enums (UNCALIBRATED, TIMEOUT, BACKEND_ERROR, INVALID_OUTPUT, ABSTAINED).
  • status=ok no envelope significa requisição processada — não que todas as perguntas decidiram.
  • A decisão não concede autorização. O engine não executa ações, não importa código recebido e não envia mensagens.
  • Nenhum conteúdo de estado é registrado em log; só fingerprints.
  • Exposição externa exige proxy TLS com allowlist das rotas de decisão, autenticação e rate limit no proxy. Não publique o Core inteiro para lançar esta API.

7. Gates obrigatórios antes de produção

  1. Modelo numérico com licença e dataset definidos e avaliação semântica real (o backend local não atende a este gate).
  2. Calibração fora do treino, artefato vinculado a hash de pergunta, modelo e domínio; Brier/NLL/ECE com incerteza, drift e política de abstenção.
  3. Teste de carga no hardware real: p50/p95, erro, memória, custo e concorrência. Fixtures não medem inteligência nem latência de modelo.
  4. Revisão do backend, do transporte TLS e dos segredos; rotação de token e isolamento entre clientes.
  5. Ativação em janela de manutenção, smoke test autenticado, rollback em caso de regressão.
  6. Licença de distribuição e destino de publicação definidos.

production_ready permanece false nesta entrega, mesmo com fábrica registrada. Não prometemos perfeição estrutural, confiança universal nem desempenho equivalente ao Jev.

8. Fontes

  • TypeSafe: introdução e primitivas — decisões tipadas e perguntas independentes. A interface daqui usa lista
    • id e não pretende compatibilidade binária com o SDK oficial.
  • Jev / System One — arquitetura, amostrador paralelo e RLCD são descritos pelo fabricante; os ganhos publicados não são benchmarks deste projeto nem evidência de pesos disponíveis.
  • Confiança — no Jev é estatística derivada da distribuição, e noul não inclui confiança. O Specter estende esse contrato e define o evento de acerto explicitamente.
  • Guo et al., ICML 2017 — temperature scaling como método pós-treino; não garante calibração em qualquer domínio.
  • OpenAPI 3.1.1 — contrato do transporte.