エージェントが自律的に動くようになると、ある朝こう自問する瞬間が来ます。「この変更、誰がやったんだ」。答えは「エージェント」なのですが、問題はその先です。いつ、どの入力を受けて、どんな判断で、何を実行したのか。それを後から正確に辿れなければ、自律運用は安心して任せられません。
私は個人開発で複数のサイトを一人で運用していて、夜間にエージェントが記事を生成し、品質ゲートを通し、push まで自律的に進めます。便利な一方で、何かがおかしくなったとき「いつから、なぜ」を再構成できないと、原因に辿り着けません。普通のアプリログでは足りない理由が、ここにあります。
アプリログと監査ログは目的が違う
アプリログは、開発者が動作を理解するためのものです。デバッグが終われば消えてよく、フォーマットも気軽に変わります。一方で監査ログは、後から「本当にそう動いたか」を第三者の視点で検証するためのものです。
この違いは、要件に直結します。監査ログは追記専用で、過去のエントリを書き換えてはいけません。順序が保証され、欠落が検知でき、改ざんされていないことを後から確かめられる必要があります。エージェントが自分の行動を自分で記録する以上、「その記録が後から都合よく書き換えられていない」ことを技術的に担保したいのです。
ハッシュチェーンで改ざんを検知する
改ざん検知の核は、各エントリに「前のエントリのハッシュ」を織り込むことです。ブロックチェーンと同じ発想ですが、分散合意は要りません。1台のファイルで十分に機能します。
import hashlib
import json
import time
GENESIS = "0" * 64
def make_entry (prev_hash: str , payload: dict ) -> dict :
entry = {
"ts" : time.time(),
"prev_hash" : prev_hash,
"payload" : payload,
}
# エントリ全体(prev_hash を含む)からハッシュを計算する
serialized = json.dumps(entry, sort_keys = True , ensure_ascii = False )
entry[ "hash" ] = hashlib.sha256(serialized.encode( "utf-8" )).hexdigest()
return entry
prev_hash を含めてハッシュを取るのが要点です。途中のエントリを1つでも書き換えると、そのエントリのハッシュが変わり、次のエントリが持つ prev_hash と食い違います。連鎖が切れた地点を見れば、どこが改ざんされたかが分かります。
追記専用で書き出す
書き込みは、ファイルの末尾に1行ずつ JSON Lines で足していくだけです。直前の行のハッシュを読み、それを次のエントリに渡します。
from pathlib import Path
class AuditLog :
def __init__ (self, path: str ):
self .path = Path(path)
self .path.touch( exist_ok = True )
def _last_hash (self) -> str :
last = GENESIS
with self .path.open( "r" , encoding = "utf-8" ) as f:
for line in f:
if line.strip():
last = json.loads(line)[ "hash" ]
return last
def append (self, action: str , ** fields) -> str :
entry = make_entry( self ._last_hash(), { "action" : action, ** fields})
with self .path.open( "a" , encoding = "utf-8" ) as f:
f.write(json.dumps(entry, ensure_ascii = False ) + " \n " )
return entry[ "hash" ]
エージェントの各ステップで append を呼びます。たとえば「トピックを選んだ」「ゲートを通過した」「push した」という節目です。重要なのは、判断の入力と結果をペアで残すことです。「push した」だけでなく「どのコミットSHAを、どのゲート結果を根拠に push したか」まで残すと、後から因果を辿れます。
検証は一本の走査で済む
監査ログの価値は、後から検証できることにあります。検証は、先頭から順にハッシュを再計算して連鎖を確かめるだけです。
def verify (path: str ) -> tuple[ bool , int ]:
"""改ざん・欠落があれば、最初に壊れた行番号を返す"""
prev = GENESIS
with open (path, "r" , encoding = "utf-8" ) as f:
for i, line in enumerate (f, start = 1 ):
if not line.strip():
continue
entry = json.loads(line)
if entry[ "prev_hash" ] != prev:
return False , i # 連鎖が切れている=欠落か並べ替え
stored = entry.pop( "hash" )
recomputed = hashlib.sha256(
json.dumps(entry, sort_keys = True , ensure_ascii = False ).encode( "utf-8" )
).hexdigest()
if stored != recomputed:
return False , i # 内容が書き換えられている
prev = stored
return True , 0
私はこの verify を毎朝の整合性チェックに組み込んでいます。1万行規模でも 1 秒もかかりません。万一どこかが壊れていれば、最初に壊れた行番号が返るので、そこを起点に調べられます。半年運用していて改ざんが起きたことはありませんが、「いつでも検証できる」という事実そのものが、自律運用への信頼を支えています。
何を記録し、何を記録しないか
監査ログで悩ましいのは、記録の範囲です。多すぎればコストと PII リスクが増え、少なすぎれば後から辿れません。
私の基準は次の3つです。
判断の分岐点だけを記録します。ループの全反復ではなく、「どの選択肢を、なぜ選んだか」の節目に絞ります。
本文そのものは残さず、本文のハッシュとメタデータを残します。記事全文を監査ログに入れるとサイズが膨らみ、後からの削除も難しくなります。
メールアドレスやトークンなどの秘匿情報は、ハッシュ化するか、そもそも payload に入れません。
この3点は、本番運用に入る前に決めておくことを推奨します。後から記録範囲を絞るのは、すでに溜まったログとの整合を取る手間がかかり、想像以上の落とし穴になります。
audit.append(
"article_pushed" ,
site = "antigravitylab" ,
commit_sha = "a1b2c3d" ,
gate_results = { "article_gate" : "pass" , "templating_gate" : "pass" },
body_sha256 = hashlib.sha256(body.encode()).hexdigest(), # 本文は残さない
)
body_sha256 だけを残しておけば、「あのとき push した本文は、いまリポジトリにあるこれと同一か」を後から照合できます。全文を抱え込まなくても、同一性は証明できるわけです。
ローテーションと長期保管の落とし穴
追記専用ログは放っておくと際限なく育ちます。ローテーションは必要ですが、素朴に分割すると連鎖が切れます。
そこで私は、ファイルを切り替えるとき、前ファイルの最後のハッシュを新ファイルの最初のエントリの prev_hash に引き継いでいます。月ごとにファイルを分けても、ハッシュの連鎖は月をまたいで一本につながります。検証は各ファイルを順に走査し、ファイル境界でハッシュが連続しているかも確認します。
def rotate (old_path: str , new_path: str ):
last = AuditLog(old_path)._last_hash()
AuditLog(new_path).append( "rotation_marker" , carried_prev_hash = last)
このひと工夫がないと、ローテーションのたびに連鎖が途切れ、改ざん検知の意味が薄れます。私も最初の設計では月初めに連鎖が切れていて、検証が常に「先頭で不一致」を返していました。境界をまたぐ設計まで含めて、初めて監査ログとして完成します。
自律運用の信頼は、検証可能性から生まれる
エージェントにより多くを任せたいなら、任せられるだけの説明責任を、仕組みとして持たせる必要があります。改ざんを検知できる監査ログは、その土台です。何か起きたときに「いつ、何を、なぜ」を一本の連鎖から再構成でき、しかもその記録が後から書き換えられていないと証明できる。この安心感が、より大きな自律性を任せる勇気につながります。
AdMob で配信するアプリの自動更新でも、私は同じ監査ログを使っています。エージェントを賢くする投資と同じくらい、エージェントの行動を後から検証可能にする投資が、長く安心して任せるための条件だと考えています。
最後までお付き合いいただき、ありがとうございました。自律運用に一歩踏み出す方の支えになれば幸いです。