mcpField
A
Arcaeon Ledger

Arcaeon Ledger

本地服务
arcaeon.io安全08 周前更新

关于 Arcaeon Ledger

Arcaeon Ledger 是用于记录 Agent 操作的哈希链账本,提供追加和验证能力,适合需要检查记录完整性的开发者。它使用单个 JSONL 文件,并能指出链中断裂的位置。

主要功能

  1. 01追加操作记录
  2. 02验证哈希链完整性
  3. 03定位链断裂行
  4. 04命令行验证与追加
  5. 05记录操作者及权限信息

适用场景

  1. 01验证 Agent 操作日志是否被修改
  2. 02在 CI 或发布前检查账本
  3. 03将操作者和权限信息纳入记录

MCP 相关

README 未说明该项目与 MCP 的连接方式或 MCP 服务所提供的能力。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"io-arcaeon-ledger": {
"command": "uvx",
"args": [
"arcaeon-ledger"
]
}
}
}
启动命令uvx arcaeon-ledger

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiarcaeon-ledger0.5.6

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

arcaeon-ledger

可观测性工具能展示 Agent 做了什么。arcaeon-ledger 能让你_证明_这一点。

每条记录都会与前一条记录进行哈希链式连接。修改一行、删除一行或 调整历史顺序,都会导致之后的每条链接失效——verify 会指出确切行号。 记录由你掌控,你可以证明它未被篡改。零依赖,一个 JSONL 文件,两个动词。

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, ...)

篡改会被发现,而不是寄望于它不会发生:

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

CLI(可将其接入 CI 或发版前检查——篡改过的日志会以非零状态码退出;而只能获得部分可信证明的日志,也不再像完全验证通过的日志那样退出):

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 退出码(0.5.7):

退出码含义
0完全验证通过——已检查每一行,链完整(ok: true)
1已损坏——发现断链(ok: false),或用法错误
3仅在限定范围内验证(所有情况下均为 ok: null):未发现断链,但未链接的 prechain 行被跳过且未验证(verified_scope: "bounded_prechain_skipped");或唯一的断链是已声明的断链("bounded_declared_break");或文件没有任何行("empty")。伪造的“legacy”前置内容会得到此状态码,而不是 0。传入 --strict 可将前两种情况改为硬性错误 1。

CI 检查应仅将 0 视为通过:

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

证明谁执行了操作,而不只是操作顺序

哈希链能证明序列完整性——但无法证明是谁写入了每条记录,也无法证明他们是否有权这样做。附加一个 authority 块,将执行者及其权限范围绑定到链式连接的(防篡改)行中:

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
    ),
)

现在,审计问题从*“这是否被编辑过?”进一步变为“这是否被编辑过,并且写入者是否获得授权?”*——编辑主体、能力或架构哈希,都会像其他篡改一样破坏链。这将防篡改与权限重放结合起来。(根据上线后社区反馈推出。)

为何打造此工具

2026 年 Agent 构建者最迫切却尚未解决的痛点,是可靠性与审计之间的落差: Agent“完成”了一项任务,但结果悄然出错,而你无法重现——或证明——实际发生了什么。可观测性平台会跟踪运行过程;但没有一个能提供防篡改、可移植、由你掌控的记录。监管要求也正在到来:欧盟《人工智能法案》要求高风险系统在其生命周期内具备自动记录事件的技术能力(第 12(1) 条),并要求提供者和部署者在其控制范围内至少保存这些日志六个月(第 19(1) 条、第 26(6) 条)。该法案规定了记录和留存要求——防篡改并非它使用的措辞,而是我们提出的要求:当有人询问留存的日志是否仍是原始日志时,答案必须比信任更有力。arcaeon-ledger 提供了最精简、诚实的方案:一份加密链式操作日志,可直接接入、由你掌控并验证。

链的工作原理

chain = sha256(prev_chain + json.dumps(row_without_chain, sort_keys=True, ensure_ascii=False))[:32]

请注意分隔符:链主体使用 Python 默认的 ", " / ": " 间距,而不是制品摘要采用的紧凑 json-c14n:v1 格式。跨语言验证器必须精确复现这种间距。

链值采用**truncated_sha256_128**——SHA-256 的前 32 个十六进制字符(128 位),而非完整摘要。此命名是为了避免有人将它称为完整的 SHA-256:128 位足以检测编辑或意外更改;但如果你希望重写后让链本身难以被暴力破解,它就略显不足(感谢 atomic-raven 的评审)。

每行都会对之前的完整历史作出承诺。第一行从固定的 "genesis" 种子开始链接。只有在第一条链式记录之前,才允许存在没有 chain 字段的行(方便你将其用于现有日志);链开始后出现的未链接行本身会被标记。发生不匹配时,verify 会继续使用记录中声称的值进行验证,从而如实统计之后的损坏,而不是让一个断链级联成一堆噪声。

它能证明什么——以及它不能证明的五件事

在这里,准确表述本身就是产品,而不是免责声明。哈希链能证明每一行记录的内容在写入后没有被就地篡改:修改文件中间部分、删除和重排都会破坏哈希链,而 verify 会指出对应的行。

0.5.8 中,这句话里有一个词发生了变化,原因正是本节存在的意义。以前写的是“记录的字节”,这比哈希链实际能证明的更多。哈希链是基于从文件中解析回来的每一行计算的,而读取器会规范化无法解码的字节序列——因此,此类区域内的两种不同字节串会被读取为相同内容,并产生相同的验证结果。受保护的是每一行的含义,而不是文件的精确字节内容。如果你需要字节级保全,请同时对文件本身进行哈希。

它不能单独证明另外五件事:

1. 截断。 去掉最新的几行,剩余内容仍能通过验证——仅靠只追加的哈希链无法发现这一点。可以通过定期将链头发布到你无法控制的地方来弥补:

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. 真实性。 哈希链会为写入的任何内容作公证——带有防篡改记录的幻觉,仍然是带校验和的幻觉。若要让一行记录能证明现实情况,请对可重新获取的凭证(URL+字节、快照、工具标准输出)进行哈希,并将摘要存入该行,这样第三方就可以重新获取并进行比对。

3. 署名。 authority()(见上文)记录了谁声称了什么,但它只是行中的数据,不是签名——从创世记录重新铸造哈希链的人,也会一并重新铸造它。外部锚定链头(#1)才是重新铸造者无法推进的部分。

4. 伪造的旧记录前置。 第一条带链记录之前没有 chain 字段的行会被容许——这是有意设计的,这样你就能在现有日志之上启用哈希链,而无需改写历史记录。但跳过的行是未经验证的行,验证器无法区分真实的旧记录历史与伪造的前置记录。因此(0.5.7 起),非严格验证如果跳过了任何行,就绝不会给出绿色结果:ok 为 None——表示“未发现破坏,在限定范围内已验证”——这是一个假值;范围会以内嵌字段的形式标明(verified_scope: "bounded_prechain_skipped"),跳过数量则记录在 prechain 中;CLI 的退出码为 3,而不是 0。只有检查了每一行的扫描才会返回 ok=True。如果你的日志从创世记录起就启用了哈希链,且不应存在任何合理的旧记录行,请传入 verify(strict=True) / --strict——这会将任何未链接的行视为破坏,直接标记为红色。(在哈希链开始后插入的未链接行,在任何模式下都会被标记。)

5. 完整性。 这是最重要的一点,而且属于结构性问题:由 Agent 决定调用 append 记录什么。防篡改日志记录的只是 Agent 选择报告的调用,它仍然是自我报告。这个库内的任何机制都无法解决这一点,因为 Agent 可以拒绝调用自己能调用的任何东西。

要解决这个问题,就得把记录权从 Agent 手中移开——改为在交界处记录,由独立的 OS 进程完成;Agent 不拥有该进程,无法跳过它,也看不到它:

pip install arcaeon-adapter

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

arcaeon-adapter 是一个 stdio 代理,在 MCP 客户端与服务端之间逐字节转发 JSON-RPC,并针对每次 tools/call 向自己的账本写入一条带哈希链的记录。将它包裹在这个库自己的 MCP 服务外部进行测试,得到了最能说明问题的数字:服务自己的日志记下了 0 条,而交界处的日志捕获了 5 条。系统自我报告的内容与交界处实际观察到的内容之间的差距,正是值得衡量的东西。

准确限定其作用后,这个基础机制可以表述为*“这个文件没有被就地改写”*——范围有限、真实可信且可测试。更上一层的机制(通过 head() 进行外部锚定、绑定凭证、签署署名、在交界处记录)则能帮助你将其扩展为完整的证据声明。

缺失或为空的账本上的 verify()

这两种情况看起来一样(“没有数据”),而 verify() 会有意将它们区分开来:

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=None, rows=0, chained=0, first_break=None, verified_scope="empty")

从未创建过的路径无法提供可信保证——ok=False,状态为“unreadable”,与其他读取失败相同。路径存在且确实为空时,没有可篡改的行,但检查了 0 行也不代表通过(自 0.5.8 起):ok=None, rows=0, verified_scope="empty",结果为假,CLI 退出码为 3。根据 verify().ok 分支处理的自动化流程,会将缺失文件判为失败,将空文件判为未通过;读取 first_break 和 verified_scope,即可根据名称区分这两种情况。

日志是在程序外写入时:声明断点,不要重新伪造

迟早会有东西绕过 append(),直接向你的 JSONL 写入内容——可能是脚本、事故,或是使用编辑器的人。链条会在此处断裂,并且一直保持断裂,因为这才是真实记录。两个显而易见的做法都不好:要么接受永久的失败状态,让读者无从得知原因;要么重新计算链条,让文件变为通过——这等于伪造记录,而一条可以被悄悄重新伪造的链条不具备任何证明力。

declare_break 是第三种选择。它会追加一行,说明断点:

from arcaeon_ledger import declare_break, verify_file

declare_break("agent.log.jsonl", 25,
              "Written out of band 2026-08-15 by a session hand-appending JSON "
              "instead of calling append(). Content is true and preserved verbatim; "
              "no chain value was ever computed for it, so none can honestly be supplied.")

r = verify_file("agent.log.jsonl")
r.ok               # None  — bounded, NOT True. Falsy.
r.verified_scope   # "bounded_declared_break"
r.breaks           # 0
r.declared         # ["line 25: declared break (Written out of band 2026-08-15 ...)"]

断点会永远保留在 declared 中。变化之处在于,已知且有说明的断点不再伪装成未说明的断点——孤立行的确切字节会由 sha256 固定下来,因此之后编辑这一行会让文件再次判为失败。它永远不会返回 ok=True。 只有检查过每一行的扫描才会如此,而获准免责的行并未经过检查。verify(strict=True) 会完全忽略声明。

直白地说,这项功能并非密码学工具,而是一种记录机制。 任何能写入文件的人都能写入声明,因此对于已经拥有写入权限的攻击者,它不会提高任何门槛。它防范的是遗忘,而不是篡改。它无法区分诚实的程序外追加与恶意追加——why 是未经验证的人类描述。而且它只能声明 verify() 已发现的断点;对于无人察觉的断点,它无能为力。请用它让诚实记录的事故清晰可读,绝不要用它让账本变为通过。

绑定 Agent 实际读取的内容(artefact-binding)

链条能证明某一行没有被编辑。它不能证明该行曾经属实—— 它会和记录事实一样如实地为幻觉做公证。对于能够指向可重新获取来源的情况,bind_artefact 可以弥补这一缺口:对 Agent 实际读取的字节进行哈希,并将摘要存入该行,以便第三方重新获取来源并进行比对。

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": {...}}

摘要是自描述的——绝不能只是裸十六进制哈希。每个摘要都采用 sha256:<recipe>:<version>:<hex> 格式,并携带自身的配方,让陌生人仅凭该字符串就能重现摘要:raw-bytes:v1(按读取时的原样处理不透明字节)或 json-c14n:v1 (固定且有文档说明的 JSON 规范化方式——键排序、紧凑格式、UTF-8)。配方会冻结,并按只追加的方式进行版本管理,因此旧行会永久保留其配方,规则变更也不会让历史记录看起来像被篡改。

如实验证:

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": [...]}

读取 verdict,而不只是 digest_ok(0.5.7)。 digest_ok 仅表示离线环节——配方可重现,字符串自洽;即使实时重新获取的内容不一致,它仍会保持 True。顶层的 verdict 标签会用一个字段给出完整结果:"digest_consistent"(离线环节通过,未进行实时比对)、"live_match"、"live_mismatch"(实时内容不再匹配——可能已更改或遭到篡改,无法判定)、"live_unavailable"(无法执行所请求的实时检查),或者离线环节失败时的具体失败原因。过去,在 refetch=True 后使用 if out["digest_ok"],即使实时比对不一致也会显示通过;而 out["verdict"] == "live_match" 不会如此。

当前构建无法重现的标签属于明确的失败,而不是通过。 如果摘要指定的算法、配方或配方版本不在支持的注册表中,verify_artefact 会返回 digest_ok=False,并给出机器可读的 reason——取值之一为 unknown_algorithm、unknown_recipe、unknown_recipe_version、malformed_digest、subject_digest_mismatch——且绝不会进入重新获取阶段,因此无法验证的配方不会被判为 "match"。无法重新计算的摘要,就意味着我们没有检查该摘要;而“没有检查”绝不能被报告为“已验证”。新增配方版本时,只要将旧版本继续保留在 SUPPORTED_RECIPE_VERSIONS 中,旧版本就仍可验证;这样既能兑现配方只追加的承诺,也能避免验证器放行它从未随版本发布过的标签。

必须明确说明的诚实边界,因为这正是关键所在: 重新获取结果为 mismatch,表示内容已更改或遭到篡改——无法判定。绝不会将其报告为篡改证据。网页内容会变化,也可能返回 404、设置付费墙或因人而异;绑定只能证明*“这是 Agent 声称自己在时间 T 读取的字节的摘要”,不能证明更多。若要使用中立的捕获结果,而非自行获取的内容,请将来源交由公证快照处理;若要证明在 T 之前已存在*,请在外部锚定摘要。每一项都是额外添加的一层——明确说明,而非暗示。

外部校验:一位外部见证者

链本身无法发现截断——只要删掉最新的几行,剩下的内容仍会通过校验(如上文“它无法证明什么”所述)。解决办法是引入一位见证者:由你控制范围之外的记录者,按一定频率保存你的头部 (rows, chain)。见证者从时间 T 起有了一个固定记录后,被截断的日志行数会少于见证者看到的行数;被改写的日志则会在见证行处呈现不同的链。两者都无法隐藏。

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" | "witness_broken" | "local_broken"
bool(v)       # truthy ONLY on "consistent" — a missing pin is no_record, never a false ok

# READ THE VERDICT WITH ITS QUALIFIERS, never the bare string alone:
v.witness_self_integrity   # "verified" | "unestablished" | "broken"

单独一个 "consistent" 并不能说明全部情况。 判定结果还包含 witness_self_integrity:见证存储是否能够证明其自身的固定记录链完整无损。只公开 latest() 的托管客户端无法自我验证,因此其判定结果会显示为 unestablished——比较过程是可信的,但如果该存储提供了伪造的固定记录,比较结果仍会通过。verified 表示已重新计算存储自身的链;broken 表示校验失败。若使用者只根据 v.verdict == "consistent" 分支,而不读取 witness_self_integrity,那么他们对存储是否诚实的信任程度,就和对日志是否诚实的信任程度完全相同——而见证者的存在,正是为了取代这种安排。(此问题发现于 2026-08-23 的邀请前审计,C14;添加该字段是为了确保“未检查”绝不会显示成“已检查且无异常”。)

WitnessStore 是参考见证存储:一个只追加的 JSONL 固定记录文件。托管见证者只是对这个对象的轻量 HTTP 封装;在本地运行它,就能拥有一个完整、离线、零成本且完全由你控制的见证者(显而易见的注意事项是,你控制的见证者,其独立性取决于它的主机)。

这究竟能证明什么。 见证者只能证明你的日志未被截断或改写,且仅相对于见证者看到的内容,并且只截至最近一次固定记录的时间。最近一次固定记录后追加的行,在下一次固定记录之前都不受保护——因此,固定记录之间的最大间隔才是你真正的安全参数,而不是平均间隔,因为攻击者会选择间隔。 而且,这无法证明记录的内容是否属实——那是 artefact-binding 的职责(见上文);见证者只保护历史记录的结构。

见证者保存什么。 只有指纹——(namespace, rows, chain, time)——绝不保存你的日志内容。密码从不存储,这是设计如此:即使见证者遭到入侵,也没有敏感信息可窃取,只有脱离原始日志便毫无用处的哈希。

将其接入任意 MCP Agent

arcaeon-ledger 提供零依赖的 MCP 服务,因此任何 MCP 客户端(Claude Code、 等)都能无需编写代码,就让其 Agent 获得防篡改日志记录能力。接入方法:

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

随后,Agent 就拥有五个工具。其中两个是针对单个文件的操作员工具: ledger_append(record) 用于记录操作(返回其链哈希),ledger_verify(strict?) 用于证明日志完好无损(或返回被篡改行的准确内容)。验证结果有三种,和库中的定义相同: ok: true = 每一行都已验证;ok: null = 链完好,但跳过了未验证的无链 prechain 行 (verified_scope: "bounded_prechain_skipped" —— 这不算通过);ok: false = 链已损坏。传入 strict: true,即可将任何无链行视为硬性失败。

另外三个是Agent 工具(0.7.0),适用于输出将交给他人的场景——无论是想要证据的主体,还是正在判断是否信任你的对等方:

工具用途返回值
prove_my_conduct(namespace, events)批量记录刚完成的操作,并向主体提供一个哈希{rows, head_hash, chain_verified}
verify_peer_ledger(jsonl_text, strict?)仅凭文本判断另一 Agent 导出的日志{ok, rows, first_break, declared_breaks}
declare_break(namespace, reason)日志已损坏时,声明损坏原因,而非重新铸造一条链{declared_line, declared_breaks, ...}

prove_my_conduct 会在追加后重新验证,因此,日志遭到篡改的 Agent 会得到 chain_verified: false,而不是带着绿色通过标记的头哈希。verify_peer_ledger 返回的 first_break 是一个整数行号(或 null),调用它的 Agent 可以据此指出确切的异常行——它绝不会触碰你的日志(导出内容会通过临时文件验证,而调用本身和其他工具调用一样,会在 <log>.calls.jsonl 中留下单独一行);如果导出内容没有可解析的行,则返回 ok: null(verified_scope: "empty"),因为空无一物却声称通过,是成本最低的伪造。declare_break 在没有损坏时会拒绝执行,并且绝不会恢复通过标记。

Agent 日志按命名空间分别存放,每个命名空间一个文件,位于 --ns-dir 下(默认为 --log 旁的 ledgers/)。命名空间是名称,而非路径:仅允许 [A-Za-z0-9._-],遇到路径遍历会直接拒绝,而不会进行清理。

MCP 使用基于 stdio 的 JSON-RPC,本服务直接实现了该协议——无需 SDK,也无需额外安装。

状态

核心库、CLI 和可直接接入的 MCP 服务均已完成测试:库通过了针对编辑、删除和重排篡改的测试(test_ledger.py);MCP 服务通过请求处理程序完成了 tools/list → append → verify 流程测试,包括篡改检测;Agent 工具通过了命名空间路径遍历、按准确行号检测对等方导出内容篡改,以及已声明损坏的测试(test_agent_tools.py)。该项目从一个已在生产环境运行的哈希链式操作日志中提取而来。外部锚定通过 head() 实现(自行发布固定值),也可使用参考见证存储(上文的 WitnessStore);托管见证服务层(保留、自动固定频率、合规导出)是下一阶段。

MIT。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。