返回更新列表
新发布Sep 9, 2026

halo-record v0.2.42

面向AI智能体的防篡改审计追踪:哈希链式运行时记录,零依赖,任何人可验证。

分享

halo-record

防篡改的 AI 代理审计追踪 —— 哈希链式运行时记录,渲染为运行时报告,你的客户可以自行核查。

你的代理执行的每一个操作(工具调用、模型调用、数据访问、审批)都会成为仅追加、哈希链式日志中的一条 运行时记录;运行时报告 就是将该链渲染为一个可自验证的 HTML 页面。任何持有该链检查点的一方都可以验证其背后的记录从未被篡改,而无需信任生成这些记录的人——该检查点是承重部件:仅凭链本身,除了操作记录器的一方之外,对所有人都是防篡改的(LIMITS.md §1)。当客户的安全团队问“你的代理对我们的数据做了什么?”时,你递给他们一个链接,而不是一段文字。安全审查已经在 SOC 2 检查清单旁边提出 AI 相关问题——而且越来越多的问题来自 ISO 42001、欧盟 AI 法案的记录保存条款以及客户自己的问卷。如今,一份书面保证仍然能通过。这个项目背后的赌注是,这种情况不会持续太久。

入选 Help Net Security(2026 年 8 月)。

记录格式是开放的,可自由实现。本包是参考实现:记录器、验证器、见证客户端和报告服务器。

正在使用 halo-record,或者正在考虑? 告诉我你是谁、用来做什么 → 谁在使用 halo-record?

自己检查

有人要求你在你的代理中放入一个记录器。你不应该盲目相信:

  • 零运行时依赖。 仅使用标准库。pip install halo-record 只安装一个包。
  • 无网络调用,除了三个可选调用——锚定到见证方(发送主体 ID、记录数量和两个链指纹——头部和链根)、读回见证方的检查点(发送主体 ID),以及 RFC 3161 时间戳(仅将检查点的状态哈希发送给时间戳机构)。除非你主动调用,否则全部关闭;记录内容永远不会离开你的基础设施。
  • 原始工具参数被哈希,并附带脱敏摘要。 参数存储为规范哈希加摘要:参数文本中已知的密钥和 PII 模式被掩码,上限为 200 个字符。不匹配任何模式的短输入会完整出现在摘要中;仅哈希模式(summaries=False)完全不保留摘要。脱敏是尽力而为的(对常见密钥和 PII 格式的正则表达式加上熵兜底):将其视为纵深防御,而非保证。你提供的超出 summary 的结果字段按原样封存(LIMITS §13)。
  • 小到足以审计。 约 5,300 行 Python(代码行,不计空行和注释)。一个下午就能全部读完。
  • Apache-2.0。
  • 文档是一等公民。 LIMITS.md(链无法证明什么)、PRIVACY.md(记录包含什么以及什么会离开你的机器)、RETENTION.md(在保留策略下运行),以及 REVIEWERS.md——四命令独立检查以及审查发现的引用格式。

每一层证明什么——本项目中的承重区分(LIMITS.md §1):你自己持有的链证明记录未被编辑,相对于某人已持有的头部;只有操作者外部持有的检查点才能证明没有记录被移除;而没有任何哈希能证明每一个操作都被捕获。

主张自持链+ 外部检查点+ 可信捕获
检测对已建立产物的编辑✔✔✔
检测对已提交历史的改写—✔✔
检测缺失/延迟的检查点—✔(约定节奏)✔
证明每个操作都被记录——取决于捕获边界

安装前先看一个: 一个示例运行时报告——虚构数据、真实链,它会在你观看时在浏览器中自我重新验证。

60 秒演示

无需代理。使用 uv,无需安装任何东西:``` uvx --from halo-record halo demo --serve

或经典方式:```
pip install halo-record
halo demo --serve

要么搭建一个虚构的支持代理供应商,包含两个客户,见证链(用一个本地见证文件代表操作者之外的一方——参见 LIMITS.md §1),提供他们受门控的 Runtime Reports,并在你的浏览器中打开操作员控制台。然后尝试篡改测试:从其中一个 .jsonl 文件中删除一行并重新加载。报告会捕获它。

记录你自己的代理

在边界处一行:```python from halo_record import trace

agent = trace(run_my_agent, profile="my-agent", log="audit.jsonl") # wraps your entrypoint; records the run boundary to ./audit.jsonl — add record_call() or a framework adapter at each tool boundary to capture individual calls

一个 `from halo import ...` 的便捷 shim 也会随附发布——但 PyPI 上的 `halo` 名称属于一个无关的终端 spinner 包,如果该包已安装,它会在导入时胜出。`halo_record` 则没有歧义,因此示例使用它。

在没有 `log=` 的情况下,记录会写入 `~/.halo/my-agent.jsonl`(每个 agent 一条链)。wrapper 封存运行边界;证据存在于每次调用的记录中。使用框架适配器(见下方矩阵)捕获这些记录——或者显式捕获,这同时展示了委托链接的方式:```python
from halo_record import Recorder, record_call

rec = Recorder("audit.jsonl")

with record_call(rec, "crm.lookup", {"account": "acct-9"}) as call:            # one sealed record per tool call
    call.result = crm.lookup("acct-9")

with record_call(rec, "payments.refund", {"amount": 120},
                 parent_id=rec.last_record_id()) as call:                      # child links to the action that spawned it
    call.result = payments.refund(120)

然后渲染报告:``` halo report audit.jsonl -o report.html # one chain -> self-verifying HTML halo serve ./records --port 8721 # all tenants, gated per customer

快速入门在你于浏览器中查看自己 agent 的 Runtime Report 时结束。如果你得到了一个 JSONL 文件却没有报告,说明出了问题:请提交一个 issue。

### 验证块

如果某个护栏或策略层检查了该操作,其裁决结果可以附加在记录上——这是一个可选块,记录门控做出了什么决定,并像其他所有字段一样被封入哈希链:```python
from halo_record import build

build("tool_call", "security", tool="payments.refund",
      verification={"status": "allowed", "verifier": "gate/1.2",
                    "policy_ref": "sha256:1f3a...",
                    "checked_at": "2026-08-01T12:00:00Z"})

一个命名说明:该包导出了一个 record 函数(装饰器),它会遮蔽包对象上的 halo_record.record 模块。当你需要其内部实现时,请直接从模块路径导入——from halo_record.record import build——而不是 import halo_record.record as record。

它会封存到记录中,形式如下:```json "verification": {"status": "allowed", "verifier": "gate/1.2", "policy_ref": "sha256:1f3a...", "checked_at": "2026-08-01T12:00:00Z"}

`record_call(...)` 接受相同的 `verification=` 关键字参数。`status` 在块内是必需的;`verifier`、`policy_ref` 和 `checked_at` 是可选的。每个状态的含义如下:

| 状态 | 门禁报告的内容 | 操作是否执行? |
|---|---|---|
| `allowed` | 它允许了该操作 | 是 — 操作继续执行 |
| `blocked` | 它拒绝了该操作 | 由集成决定,而非此字段 — 记录仍可能携带结果,且阻止本身并不能证明未执行 |
| `modified` | 它在执行前修改了该操作 — `action.input` 描述的是**执行时**的操作,即修改后的状态 | 是,以修改后的形式 |
| `unverified` | 它运行了(或被咨询了)但未做出判定 — 与缺失的块不同,后者意味着完全没有做出验证声明 | 是 — 操作在无裁决的情况下继续执行 |

该块由操作者的集成代码提供,并记录其报告的门禁所述内容 — 与 `principal` 相同的信任姿态(参见 [LIMITS](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#11-verification-status-is-the-gates-report-not-halos-finding))。密封证明状态在事后未被编辑;它并不证明检查确实发生、裁决正确,或已阻止的操作未执行。这不是独立验证。

要使 `policy_ref` 可用作证据,请使用规则集的内容哈希并保留规则集工件 — 无法解析的标签会使该字段沦为装饰。

## 连接到您已运行的内容

| 在边界处捕获 | 从现有遥测中摄取 |
|---|---|
| 原生记录器(`from halo_record import trace`) | OpenTelemetry GenAI spans |
| MCP 拦截器 | LiteLLM 回调 |
| LangChain / LangGraph 回调 | Langfuse 导出 |
| OpenAI Agents SDK 钩子 | 任何网关 / 反向代理日志 |
| Claude Agent SDK 钩子 | Claude Code 和 Codex CLI 的 `PostToolUse` 钩子(在工具运行后触发) |

框架适配器和摄取路径会为每条记录打上 `source` 标签,因此报告会披露每条证据的收集方式。捕获的记录和摄取的记录存在于同一条链中。

对于 LangChain / LangGraph,它是一个回调处理器:```python
from halo_record import Recorder
from halo_record.integrations.langchain import HaloCallbackHandler

recorder = Recorder("audit.jsonl")
result = my_chain.invoke(inputs, config={"callbacks": [HaloCallbackHandler(recorder)]})   # every tool call becomes a record

对于 MCP,一次调用即可封装客户端会话——随后任何使用 MCP 的 agent 都会为每次工具调用发出记录,无论由哪个框架驱动:```python from halo_record.integrations.mcp import instrument_client_session

instrument_client_session(session, Recorder("audit.jsonl"), server="stripe") # every session.call_tool() is now recorded

对于网关或代理日志(Cloudflare AI Gateway、Portkey、模型前方的 nginx),将日志行映射到链中——明确标记为已摄取,而非边界捕获:```python
from halo_record.integrations.gateway import record_log

record_log(Recorder("audit.jsonl"), {"tool": "gen_ai:gpt-4o", "model": "gpt-4o", "status": 200, "subject": "acme-corp"})

任何发出 OpenTelemetry GenAI span 的内容(CrewAI、LlamaIndex,以及大多数带有 OTel 插桩的 agent 框架)都会通过 OTel 适配器进入链中,而 TypeScript 包 为 Vercel AI SDK 和 JS agent 生态系统提供了原生适配器。缺少适用于你的技术栈的适配器?提交一个 issue。大多数适配器大约只有一百行代码。

记录你的编码 agent(Claude Code 或 Codex)

Claude Code 在每次工具调用后都会触发一个 PostToolUse 钩子。将其指向 halo hook,每个操作——文件写入、shell 命令、MCP 连接器调用——都会成为本地链中的一条记录。无需修改代码;只需一项设置条目:```json { "hooks": { "PostToolUse": [ {"matcher": "*", "hooks": [{"type": "command", "command": "halo hook"}]} ] } }

将其添加到 `~/.claude/settings.json`,记录会写入 `~/.halo/audit.jsonl`(可通过 `$HALO_LOG` 覆盖)。不接触任何数据、网络或外部状态的纯编排工具会被跳过——该链记录的是信任边界操作,而非思考过程。设置 `HALO_HASH_ONLY=1` 可仅记录内容哈希而不记录摘要。设置 `HALO_AGENT_VERSION`(以及可选的 `HALO_AGENT_MODEL`)可将每条记录绑定到产生它的 agent 构建版本——当审计人员询问某个时间窗口内运行的是哪个版本时,导出结果按列作答,而非凭记忆作答。

Codex CLI 提供相同的生命周期钩子,事件结构也相同(钩子默认开启)。将其添加到 `~/.codex/hooks.json`,Codex 的 shell 命令、`apply_patch` 编辑和 MCP 调用就会写入同一条链:```json
{
  "hooks": {
    "PostToolUse": [
      {"matcher": ".*", "hooks": [{"type": "command", "command": "halo hook"}]}
    ]
  }
}

hook 通过事件本身来区分两者(Codex 会添加 turn_id 和 model),并将每条记录标记为 claude-code 或 codex;设置 HALO_HOOK_AGENT 可强制指定其一。两者都属于摄取层:PostToolUse hook 在工具运行后触发,因此记录是根据 harness 所报告的内容构建的。

分类