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ída | significado |
|---|---|
0 | totalmente verificado — cada linha verificada, cadeia intacta (ok: true) |
1 | quebrado — uma quebra foi encontrada (ok: false), ou uso incorreto |
3 | verificado 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.