Arcaeon Ledger

Um registro de ações para agentes de IA com encadeamento de hash e evidência de adulteração, além de um proxy stdio que grava chamadas de ferramentas na junção, de modo que o registro não dependa da cooperação do agente.

Documentação

arcaeon-ledger

Ferramentas de observabilidade mostram o que seu agente fez. arcaeon-ledger permite que você prove isso.

Cada registro é encadeado por hash ao anterior. Edite uma linha, exclua uma ou reordene o histórico, e cada elo posterior se quebra — verify nomeia a linha exata. Você é dono do registro e pode provar que ele não foi alterado. Zero dependências, um arquivo JSONL, dois verbos.

pip install arcaeon-ledger      # then:  from arcaeon_ledger import Ledger
from arcaeon_ledger import Ledger

log = Ledger("agent.log.jsonl")
log.append({"tool": "web.search", "query": "weather in LA", "result_ok": True})
log.append({"tool": "payment", "amount": "49.00", "currency": "USD"})

log.verify()          # VerifyResult(ok=True, rows=2, chained=2, ...)

A adulteração é detectada, não apenas esperada:

# someone edits row 2's amount in the file by hand...
log.verify()          # VerifyResult(ok=False, first_break="line 2: chain mismatch")

CLI (conecte-o ao CI ou a um gate de pré-lançamento — um log adulterado sai com código diferente de zero, e um log que só pôde ser parcialmente atestado não sai mais como um totalmente verificado):

python -m arcaeon_ledger.cli append agent.log.jsonl '{"tool":"search","ok":true}'
python -m arcaeon_ledger.cli verify agent.log.jsonl
python -m arcaeon_ledger.cli verify --strict agent.log.jsonl

verify códigos de saída (0.5.7):

saídasignificado
0totalmente verificado — cada linha verificada, cadeia intacta (ok: true)
1quebrado — uma quebra foi encontrada (ok: false), ou uso incorreto
3verificado somente dentro do escopo — nenhuma quebra encontrada, mas linhas prechain sem encadeamento foram puladas sem verificação (ok: null, verified_scope: "bounded_prechain_skipped"). Um prepêndice "legado" fabricado cai aqui, nunca em 0. Passe --strict para torná-lo um 1 rígido.

Um gate de CI deve tratar apenas 0 como verde:

python -m arcaeon_ledger.cli verify agent.log.jsonl
case $? in
  0) echo "fully verified" ;;
  3) echo "chain intact but prechain rows skipped unverified — inspect, or use --strict" ; exit 1 ;;
  *) echo "ledger broken" ; exit 1 ;;
esac

Prove quem agiu, não apenas a ordem

Uma cadeia de hash prova a integridade da sequência — ela não pode provar quem escreveu cada entrada ou se eles tinham permissão para isso. Anexe um bloco authority para vincular o ator e sua superfície de permissão à linha encadeada (à prova de adulteração):

from arcaeon_ledger import Ledger, authority

log = Ledger("agent.log.jsonl")
log.append(
    {"tool": "payment", "amount": "49.00"},
    authority=authority(
        "agent://billing-7",
        capability_version="v3",              # what they were allowed to do
        tool_schema={"name": "payment", "args": ["amount"]},  # hashed, not just named
        time_source="ntp",                    # trust surface of the clock
    ),
)

Agora a pergunta de auditoria se afia de "isso foi editado?" para "isso foi editado e o escritor estava autorizado?" — editar o principal, a capacidade ou o hash do esquema quebra a cadeia como qualquer outra adulteração. Isso compõe prova de adulteração com replay de permissão. (Enviado em resposta ao feedback da comunidade no lançamento.)

Por que isso existe

A dor não atendida mais forte para construtores de agentes em 2026 é a lacuna de confiabilidade/auditoria: um agente "conclui" uma tarefa e o resultado está silenciosamente errado, e você não consegue reconstruir — ou provar — o que realmente aconteceu. Plataformas de observabilidade rastreiam execuções; nenhuma oferece um registro à prova de adulteração, portátil e de sua propriedade. A regulamentação também está chegando: o EU AI Act exige que sistemas de alto risco permitam tecnicamente a gravação automática de eventos ao longo de sua vida útil (Art. 12(1)) e exige que provedores e implantadores mantenham esses logs, na medida sob seu controle, por pelo menos seis meses (Art. 19(1), Art. 26(6)). O Ato exige gravação e retenção — prova de adulteração não é a palavra dele, é nossa: quando alguém pergunta se um log retido ainda é o log, essa pergunta precisa de uma resposta mais forte que confiança. arcaeon-ledger é a menor versão honesta: um log de ações criptograficamente encadeado que você insere, possui e verifica.

Como a cadeia funciona

chain = sha256(prev_chain + canonical_json(row_without_chain))[:32]

O valor da cadeia é truncated_sha256_128 — os primeiros 32 caracteres hexadecimais (128 bits) de SHA-256, não o digest completo. Nomeado assim para que ninguém o cite como SHA-256 completo: 128 bits são suficientes para detecção de edição/acidente, mais fino se você quiser que a cadeia em si seja cara de forçar após uma reescrita (crédito: revisão de atomic-raven).

Cada linha compromete todo o histórico anterior a ela. A primeira linha encadeia a partir de uma semente fixa "genesis". Linhas sem um campo chain são toleradas apenas antes da primeira linha encadeada (para que você possa adotá-la em um log existente); uma linha sem encadeamento aparecendo depois que a cadeia começa é sinalizada. Em uma incompatibilidade, a verificação continua a partir do valor reivindicado para contar danos posteriores honestamente, em vez de cascatear uma quebra em ruído.

O que prova — e as cinco coisas que não prova

Ser preciso aqui é o produto, não um aviso. Uma cadeia de hash prova que os bytes registrados não foram alterados no lugar após a escrita: edição no meio do arquivo, exclusão e reordenação quebram tudo e verify nomeia a linha. Ela não prova por si só cinco outras coisas:

1. Truncamento. Corte as linhas mais recentes e o que resta verifica limpo — nenhuma cadeia somente de acréscimo detecta isso sozinha. Feche isso publicando a cabeça em algum lugar fora do seu próprio controle, em uma cadência:

pin = log.head().as_pin()
# -> "arcaeon-ledger head chain=9f3c… rows=204 as_of=2026-08-13T17:40:00Z"
# post `pin` to a git commit / public comment / notarization anchor.
# a reader compares a fresh head() against the last pin; a truncated or
# re-minted history disagrees. the MAX gap between pins is your security
# parameter, not the average — an attacker picks the gap.

2. Verdade. A cadeia notariza o que foi escrito — um registro à prova de adulteração de uma alucinação ainda é uma alucinação com um checksum. Para fazer uma linha falar sobre o mundo, faça hash de um artefato re-buscável (URL+bytes, um snapshot, saída de ferramenta) e armazene esse digest na linha, para que um terceiro possa re-obtê-lo e comparar.

3. Autoria. authority() (acima) registra quem-reivindicou-o-quê, mas são dados na linha, não uma assinatura — um reescritor que re-cunha desde a gênese também o re-cunha. Ancoragem externa de cabeça (#1) é a coisa que um re-cunhador não pode avançar.

4. Prepêndice-legado-fabricado. Linhas sem campo chain são toleradas antes da primeira linha encadeada — isso é deliberado, para que você possa adotar a cadeia sobre um log existente sem reescrever seu histórico. Mas linhas puladas são linhas não verificadas, e o verificador não consegue distinguir histórico legado real de um prepêndice fabricado. Então (0.5.7) uma verificação não estrita que pulou quaisquer linhas nunca cunha verde: ok é None — "nenhuma quebra encontrada, verificado dentro do escopo" — falso, com o escopo em banda (verified_scope: "bounded_prechain_skipped") e a contagem em prechain; o CLI sai com 3, não 0. Somente uma varredura que verificou cada linha retorna ok=True. Se seu log é encadeado desde a gênese e deve não ter linhas legado legítimas, passe verify(strict=True) / --strict — ele trata qualquer linha sem encadeamento como uma quebra, vermelho rígido. (Uma linha sem encadeamento inserida depois que a cadeia começa já é sinalizada em todos os modos.)

5. Completude. Esta é a grande, e é estrutural: o agente decide o que chamar de append. Um log à prova de adulteração das chamadas que um agente escolheu relatar ainda é auto-relato. Nada dentro desta biblioteca pode fechar isso, porque qualquer coisa que o agente invoca, o agente pode se recusar a invocar.

Feche isso movendo a caneta para fora do alcance do agente — registre na costura em vez disso, em um processo de SO separado que o agente não possui, não pode pular e não pode ver:

pip install arcaeon-adapter

python -m arcaeon_adapter --ledger seam.log.jsonl -- <your mcp server command...>

arcaeon-adapter é um proxy stdio que encaminha JSON-RPC byte por byte entre um cliente MCP e servidor, escrevendo uma linha encadeada por hash por tools/call em seu próprio ledger. Envolvê-lo em torno do servidor MCP desta própria biblioteca produziu o número que faz o ponto: o diário do próprio servidor escreveu 0 linhas enquanto o log da costura capturou 5. A lacuna entre o que um sistema relata sobre si mesmo e o que a costura observou é a coisa que vale a pena medir.

Escopado honestamente, o primitivo é "este arquivo não foi reescrito no lugar" — pequeno, verdadeiro e testável. As camadas acima (ancoragem externa via head(), ligação de artefato, autoria assinada, registro de costura) são como você o estende em direção a uma reivindicação de evidência completa.

verify() em ledgers ausentes ou vazios

Os dois parecem a mesma coisa — "sem dados" — e verify() os trata como opostos, de propósito:

Ledger("never/written.jsonl").verify()
# VerifyResult(ok=False, rows=0, first_break="unreadable: [Errno 2] No such file...")

open("touched/empty.jsonl", "w").close()
Ledger("touched/empty.jsonl").verify()
# VerifyResult(ok=True, rows=0, chained=0, first_break=None)

Um caminho que nunca foi criado não pode ser atestado — ok=False, "ilegível," igual a qualquer outra falha de leitura. Um caminho que existe e é genuinamente vazio tem zero linhas para adulterar, então não há nada com que a cadeia discorde — ok=True, rows=0. Automação que ramifica em verify().ok para decidir "este log está intacto" precisa verificar first_break (ou capturar o caso de arquivo ausente a montante) se também precisar distinguir "nunca existiu" de "existe, legitimamente vazio" — ok sozinho colapsa essa distinção em duas respostas diferentes, não uma.

Vincule o que o agente realmente leu (ligação de artefato)

A cadeia prova que uma linha não foi editada. Ela não prova que a linha foi verdadeira — ela notarizará uma alucinação tão fielmente quanto um fato. bind_artefact fecha essa lacuna para os casos em que você pode apontar para uma fonte re-buscável: faça hash dos bytes reais que o agente leu e armazene esse digest na linha, para que um terceiro possa re-obter a fonte e comparar.

from arcaeon_ledger import Ledger, bind_artefact

log = Ledger("agent.log.jsonl")
art = bind_artefact("https://example.com/pricing")   # or bytes, a file path, or a dict
log.append({"tool": "web.read", "url": "https://example.com/pricing", "artefact": art})
# art -> {"subject": {"name": "...", "digest": {"sha256": "..."}},
#         "recipe": "sha256:raw-bytes:v1",
#         "digest": "sha256:raw-bytes:v1:<hex>", "bound_at": "...", "source_meta": {...}}

Digests são autodescritivos — nunca um hash hexadeciminal puro. Cada um é sha256:<recipe>:<version>:<hex>, carregando sua própria receita para que um estranho o reproduza apenas a partir da string: raw-bytes:v1 (bytes opacos como lidos) ou json-c14n:v1 (uma canonicalização JSON fixada e documentada — chaves ordenadas, compacta, UTF-8). Receitas são congeladas e versionadas somente com acréscimo, então linhas antigas mantêm sua receita para sempre e uma regra alterada nunca faz o histórico parecer adulterado.

Verifique honestamente:

from arcaeon_ledger import verify_artefact

verify_artefact(art)                    # recipe reproducible + string self-consistent
verify_artefact(art, refetch=True)      # for a URL: re-fetch and compare
# -> {"verdict": "live_match",          # <- THE answer; read this field
#     "digest_ok": True, "reason": None,
#     "refetch": "match" | "mismatch" | "unavailable" | "skipped", "notes": [...]}

Leia verdict, não apenas digest_ok (0.5.7). digest_ok nomeia apenas a perna offline — receita reproduzível, string autoconsistente — e permanece True mesmo quando uma re-busca ao vivo discorda. A tag de nível superior verdict cunha a resposta inteira em um campo: "digest_consistent" (perna offline passou, nenhuma comparação ao vivo feita), "live_match", "live_mismatch" (conteúdo ao vivo não corresponde mais — alterado ou adulterado, indeterminado), "live_unavailable" (a verificação ao vivo solicitada não pôde ser executada), ou a razão de falha tipada em si quando a perna offline falha. if out["digest_ok"] após refetch=True costumava ler verde através de uma incompatibilidade ao vivo; out["verdict"] == "live_match" não pode.

Um rótulo que esta compilação não pode reproduzir é uma falha tipada, nunca uma aprovação. Se o digest nomeia um algoritmo, receita ou versão de receita fora do registro suportado, verify_artefact retorna digest_ok=False com um reason legível por máquina — um de unknown_algorithm, unknown_recipe, unknown_recipe_version, malformed_digest, subject_digest_mismatch — e nunca alcança o estágio de re-busca, então uma receita não verificável não pode voltar como "match". Um digest que não podemos recalcular é um digest que não verificamos, e "não verificado" não deve ser relatado como "verificado." Versões antigas permanecem verificáveis ao permanecerem listadas em SUPPORTED_RECIPE_VERSIONS quando uma nova é cunhada, então a promessa de receita somente com acréscimo se mantém sem o verificador acenar para rótulos que nunca enviou.

O limite honesto, declarado em voz alta porque é o ponto: uma re-busca mismatch significa que o conteúdo mudou ou foi adulterado — indeterminado. Nunca é relatado como prova de adulteração. A web muta, 404s, paywalls e personaliza; a ligação prova "este é o digest dos bytes que o agente disse que leu no tempo T," nada mais forte. Para uma captura neutra em vez de sua própria busca, roteie a fonte através de um snapshot notarizante; para existiu-antes-de-T, ancore o digest externamente. Cada uma é uma camada que você adiciona — declarada, não implícita.

A verificação externa: uma testemunha externa

A cadeia não pode detectar truncamento sozinha — corte as linhas mais recentes e o que resta verifica limpo (declarado em "o que não prova", acima). A correção é uma testemunha: um mantenedor de registros fora do seu próprio controle que segura sua cabeça (rows, chain) em uma cadência. Uma vez que uma testemunha tem um pin do tempo T, um log truncado tem menos linhas do que a testemunha viu, e um reescrito tem uma cadeia diferente na linha testemunhada. Nenhum dos dois pode se esconder.

from arcaeon_ledger import Ledger, WitnessStore, publish_head, verify_against_witness

log = Ledger("agent.log.jsonl")
witness = WitnessStore("witness_pins.jsonl")   # ideally on a host you don't control

publish_head(witness, "billing-agent", log)    # record the current head — do this on a cadence

# later — did the log survive intact?
v = verify_against_witness(witness, "billing-agent", log)
v.verdict     # "consistent" | "truncated" | "rewritten" | "no_record"
bool(v)       # truthy ONLY on "consistent" — a missing pin is no_record, never a false ok

WitnessStore é a testemunha de referência: um arquivo JSONL somente de acréscimo de pins. Uma testemunha hospedada é um wrapper HTTP fino sobre exatamente este objeto; execute-o localmente e você tem uma testemunha completa, offline e de custo zero que você controla totalmente (com a óbvia ressalva de que uma testemunha que você controla é tão independente quanto seu host).

O que isso prova, exatamente. Uma testemunha prova que seu log não foi truncado ou reescrito apenas em relação ao que a testemunha viu, e apenas tão recentemente quanto o último pin. Linhas acrescentadas após o último pin estão desprotegidas até a próxima — então a lacuna MÁXIMA entre pins é seu parâmetro de segurança real, não a média, porque um atacante escolhe a lacuna. E não diz nada sobre se o conteúdo registrado era verdadeiro — esse é o trabalho da ligação de artefato (acima); a testemunha apenas protege a forma do histórico. O que o witness guarda. Apenas fingerprints — (namespace, rows, chain, time) — nunca o conteúdo do seu log. Sem senha por design: se o witness for comprometido, não há nada sensível para roubar, apenas hashes inúteis sem o log original.

Coloque em qualquer agente MCP

arcaeon-ledger inclui um servidor MCP sem dependências, então qualquer cliente MCP (Claude Code, etc.) pode dar ao seu agente um log com evidência de adulteração sem código. Conecte-o:

{
  "mcpServers": {
    "ledger": {
      "command": "python",
      "args": ["-m", "arcaeon_ledger.mcp_server", "--log", "agent.log.jsonl"]
    }
  }
}

O agente então tem duas ferramentas: ledger_append(record) para registrar uma ação (retorna o hash da cadeia) e ledger_verify(strict?) para provar que o log está intacto (ou obter a linha exata adulterada de volta). O veredito da verificação é tri-valorado, igual à biblioteca: ok: true = cada linha verificada, ok: null = cadeia intacta, mas linhas prechain fora da cadeia foram puladas sem verificação (verified_scope: "bounded_prechain_skipped" — não é um verde), ok: false = quebrado. Passe strict: true para tornar qualquer linha fora da cadeia uma falha grave. MCP é JSON-RPC sobre stdio e este servidor fala isso diretamente — sem SDK, sem instalação extra.

Status

Biblioteca principal, CLI e um servidor MCP plug-and-play, todos testados: a biblioteca contra adulteração por edição / exclusão / reordenação (test_ledger.py), o servidor MCP através de um handshake completo de initialize → tools/list → append → verify incluindo detecção de adulteração pela rede. Extraído de um ledger de ações com hash em cadeia rodando em produção. Ancoragem externa chega via head() (publique o pin você mesmo) e o witness de referência (WitnessStore, acima); um nível de witness hospedado (retenção, cadência automática de pin, exportação de conformidade) é a próxima camada.

MIT.