
AIエージェント向けの改ざん防止監査証跡:ハッシュチェーン化されたRuntime Records、依存関係なし、誰でも検証可能
改ざん検知可能な AIエージェントの監査証跡 — ハッシュチェーン化された Runtime Record を、顧客自身が検証できる Runtime Report としてレンダリングします。
計装が捕捉するすべてのアクション(ツール呼び出し、モデル呼び出し、データアクセス、承認)は、追記専用のハッシュチェーンログ内の1つの Runtime Record となり、Runtime Report はそのチェーンを HTML ページとしてレンダリングしたものです。このページは、読者のブラウザ内でハッシュチェーンを再計算し、検証したレコードから表示内容をレンダリングします(ページ自身のコードに依存しない検証には、JSONL に対して halo verify を実行してください)。チェーンのチェックポイントを保持する任意の当事者は、そのチェックポイントまでのレコードが取得以降変更されていないことを、そのレコードを生成した相手を信頼することなく検証できます — このチェックポイントが要となる部分です。チェーン単体では、記録者を運用する当事者以外の全員に対して改ざん検知可能です(LIMITS.md §1)。顧客のセキュリティチームが「あなたのエージェントは我々のデータで何をしたのか?」と尋ねてきたとき、長文の説明ではなくリンクを渡せます。セキュリティレビューではすでに SOC 2 チェックリストの隣で AI に関する質問がなされており、そしてますますそれらの質問は ISO 42001、EU AI Act の記録保持条項、顧客自身のアンケートから来ています。今日では、書面による保証でもまだ通ります。このプロジェクトの背後にある賭けは、それが長くは続かないだろうというものです。
Help Net Security で紹介(2026年8月)。
レコード形式はオープンで、自由に実装できます。このパッケージはリファレンス実装です:レコーダー、検証器、ウィットネスクライアント、レポートサーバー。
halo-record を使っていますか、あるいは検討していますか? あなたが誰で、何のために使うのか教えてください → Who's using halo-record?
あなたはエージェント内にレコーダーを組み込むよう求められています。それを鵜呑みにするべきではありません:
pip install halo-record はちょうど1つのパッケージをインストールします。halo serve --verify-email(レポート閲覧者が入力したアドレスへ、$HALO_SMTP_HOST で設定したサーバー経由の SMTP で、ワンタイムコードとレポートのサブジェクト名を送信)。いずれも明示的に呼び出さない限りオフです。その他のレコード内容があなたのインフラから出ることはありません。summaries=False)ではサマリーは一切保持されません。秘匿化はベストエフォートです(一般的なシークレットおよび PII 形式に対する正規表現と、エントロピーによるキャッチオール):多層防御として扱い、保証とは考えないでください。summary 以外にあなたが提供する結果フィールドは、同じパターンでスキャンおよびマスクされますが、200文字に切り詰められません(LIMITS §13)。各レイヤーが何を証明するか — このプロジェクトにおける要となる区別(LIMITS.md §1):自分で保持するチェーンは、すでに誰かが保持しているヘッドに対して、レコードが編集されていないことを証明します。運用者の外部で保持されるチェックポイントのみが、何も削除されていないことを証明します。そして、いかなるハッシュも、すべてのアクションが捕捉されたことを証明しません。
| 主張 | 自己保持チェーン | + 外部チェックポイント | + 信頼された捕捉 |
|---|---|---|---|
| 確立された成果物への編集を検知 | ✔ | ✔ | ✔ |
| コミット済み履歴の書き換えを検知 | — | ✔ | ✔ |
| 欠落/遅延チェックポイントを検知 | — | ✔(合意されたケイデンス) | ✔ |
| すべてのアクションが記録されたことを証明 | — | — | 捕捉境界に依存 |
インストール前に実物を見る: サンプル Runtime Report — 架空のデータ、本物のチェーン。開くと、そのスクリプトが埋め込まれたレコードのハッシュチェーンをブラウザ内で再計算します。
エージェントは不要です。uv があれば、インストールするものはありません:
uvx --from halo-record halo demo --serve
または従来の方法:
pip install halo-record
halo demo --serve
いずれかを実行すると、2社の顧客を持つ架空のサポートエージェントベンダーをスキャフォールドし、チェーンを目撃し(オペレーター外部の1社を代表するローカルwitnessファイルを使用 — LIMITS.md §1 を参照)、ゲート付きのRuntime Reportsを提供し、オペレーターコンソールをブラウザで開きます。次に改ざんテストを試してください:.jsonl ファイルの1つから1行削除してリロードします。レポートがそれを検出します。
境界での1行:
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 ... という便利なシムも同梱されていますが、PyPI 上の halo という名前は無関係なターミナルスピナーパッケージのものであり、そのパッケージがインストールされている場合はそちらがインポートで優先されます。halo_record は曖昧さがないため、例ではこちらを使用しています。
log= を指定しない場合、レコードは ~/.halo/my-agent.jsonl に書き込まれます(エージェントごとに1チェーン)。ラッパーは実行境界を封印し、証拠は呼び出しごとのレコードに残ります。これらはフレームワークアダプター(下のマトリックス)でキャプチャするか、明示的にキャプチャします。後者は委譲リンクの仕組みも示します:
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 -> HTML that re-verifies in the browser
halo serve ./records --port 8721 # all tenants, gated per customer
0.2.45 で最初に配信されたディレクトリをアップグレードしますか? halo serve は起動時に以前の各グラントを再読み込みします。レコードに subject id が含まれていないファイル(agent.jsonl のようなプレーンな Recorder ファイル)に対するグラントは隔離され、起動のたびに、それを再発行する halo grant コマンドとともに出力されます — LIMITS.md §17, "Serving a directory first served by 0.2.45" を参照してください。
クイックスタートは、ブラウザで自分のエージェントの Runtime Report を表示した時点で終了します。JSONL ファイルが得られてレポートがない場合、何かがおかしいです: issue を開いてください。
ガードレールまたはポリシーレイヤーがアクションをチェックした場合、その判定結果はレコードに乗せることができます — ゲートが何を決定したかを記録するオプションのブロックで、他のすべてのフィールドと同様にハッシュチェーンに封印されます:
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"})
1つ命名に関する注意点があります。このパッケージは record 関数(デコレータ)をエクスポートしており、これがパッケージオブジェクト上の halo_record.record モジュールを隠蔽します。その内部が必要な場合は、import halo_record.record as record ではなく、モジュールパスから直接インポートしてください — from halo_record.record import build。
これはレコードに次のように封印されます:
"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 を参照)。シーリングは、ステータスが事後に編集されなかったことを証明しますが、チェックが行われたこと、判定が正しかったこと、またはブロックされたアクションが実行されなかったことを証明するものではありません。これは独立した検証ではありません。
policy_ref を証拠として使用可能にするには、ルールセットのコンテンツハッシュを使用し、ルールセットの成果物を保持してください — 解決できないラベルは、このフィールドを装飾的なものにします。
| 境界でキャプチャ | 既存のテレメトリから取り込み |
|---|---|
ネイティブレコーダー(from halo_record import trace) | OpenTelemetry GenAI スパン |
| MCP インターセプター | LiteLLM コールバック |
| LangChain / LangGraph コールバック | Langfuse エクスポート |
| OpenAI Agents SDK フック | 任意のゲートウェイ / リバースプロキシログ |
| Claude Agent SDK フック | Claude Code および Codex CLI の PostToolUse フック(ツール実行後に発火) |
フレームワークアダプターと取り込みパスは、各レコードに source タグを付与するため、レポートは各証拠がどのように収集されたかを開示します。キャプチャされたレコードと取り込まれたレコードは同じチェーンに存在します。
LangChain / LangGraph の場合、それはコールバックハンドラーです:
from halo_record import Recorder
from halo_record.integrations.langchain import HaloCallbackHandler