0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

長時間AIエージェントを止めるPython軌跡監視

0
Posted at

長時間動くAIエージェントは、どの時点で止めればいいのか?

「危ないコマンドをブロックしているから大丈夫」は、長く動くエージェントには少し楽観的すぎると思う。

7月20日に公開されたOpenAIの長時間モデルの安全性に関する記事では、単発の操作では問題が見えなくても、操作の並びが制約の回避へ向かう例が紹介されていた。実際に監視を止め、軌跡全体を見る仕組みへ切り替えたという話だ。原文を読んでいて、自分はまずローカルの実行ログに同じ発想を入れたくなった。

ここで作るのはモデルの安全性を判定するものではない。エージェントランナーが出すイベントを順番に見て、「ワークスペース外へ出た」「未承認のネットワーク操作を繰り返した」を人に渡す、小さな停止器だ。

Q. 単発の deny では何が足りない?

A. 1回目の未承認ネットワークアクセスは、設定漏れかもしれない。けれど同じ境界をもう一度越えようとしたら、作業の向きが変わったと見た方がいい。

長時間タスクでは、最初の失敗を受けて別の手段を探す。個々の curl やファイル書き込みだけを許可リストと照合しても、「失敗後に何を試し始めたか」が消える。停止判断には、現在の操作と過去の境界越えを一緒に持たせる必要がある。

ただし、何でも即停止にすると運用で使えない。今回のルールは二段階にした。

条件 判定
ワークスペース内の読み書き・コマンド ALLOW
未承認のネットワーク操作を初めて観測 REVIEW
同じ未承認ネットワーク操作が2回目 PAUSE
ワークスペース外への書き込み・コマンド PAUSE

Q. 最小の軌跡監視はどう書く?

A. イベントごとの判定結果ではなく、境界ごとの回数をオブジェクトに残す。これだけで「2回目」を検出できる。

from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Optional

@dataclass(frozen=True)
class Event:
    kind: str
    target: str
    approved: bool = False

class TrajectoryGuard:
    """実行ログを順に受け取り、境界越えを review / pause に分ける。"""

    def __init__(self, workspace: str) -> None:
        self.workspace = Path(workspace).resolve()
        self.counts: Dict[str, int] = {}

    def inspect(self, event: Event) -> str:
        boundary = self._boundary(event)
        if boundary is None or event.approved:
            return "ALLOW"

        self.counts[boundary] = self.counts.get(boundary, 0) + 1
        if boundary == "workspace" or self.counts[boundary] >= 2:
            return "PAUSE: " + boundary
        return "REVIEW: " + boundary

    def _boundary(self, event: Event) -> Optional[str]:
        if event.kind == "network":
            return "network"
        if event.kind in {"write", "command"}:
            target = Path(event.target).resolve()
            try:
                target.relative_to(self.workspace)
            except ValueError:
                return "workspace"
        return None

この関係はこうです。

approved=True は、人がネットワーク利用を明示的に許可したイベントにだけ付ける想定だ。承認済みを回数に数えないので、レビュー後にタスクを再開しても過去の警告で即停止しない。

Q. ちゃんと「軌跡」になっているか確認する

イベントを4つ流す。最初の2つはワークスペース内でのテストとレポート出力。続く2つは未承認の同じ外部アクセスだ。

events = [
    Event("command", "/tmp/agent-demo/tests"),
    Event("write", "/tmp/agent-demo/report.json"),
    Event("network", "https://example.test/api"),
    Event("network", "https://example.test/api"),
]

guard = TrajectoryGuard("/tmp/agent-demo")
result = [guard.inspect(event) for event in events]
print(result)
assert result == ["ALLOW", "ALLOW", "REVIEW: network", "PAUSE: network"]

手元の Python 3.9.6 で実行した出力はこれだった。

['ALLOW', 'ALLOW', 'REVIEW: network', 'PAUSE: network']

昨日この手の検証をしていて、最初は str | None と書いたら Python 3.9 で落ちた。監視役が起動前に落ちるのはかなり寒いので、記事のコードは Optional[str] にしている。ランナーのPythonが混ざる現場では、こういう地味な互換性が先に効く。

Q. 実運用では何をイベントにする?

A. kindtarget は、使うランナーの監査ログから作る。シェルなら cwd と書き込み先、HTTPツールなら接続先、デプロイなら環境名を target に入れる。Event をJSON Linesで保存しておけば、停止後に「どの境界を何回またいだか」もそのまま追える。

このコードはURLの安全性も、コマンドの危険性も理解しない。そこを正規表現で賢くし始めると、すぐにルールの穴埋めゲームになる。まずは作業ディレクトリ、外部通信、公開環境のように、チームが合意しやすい境界を少数だけ置くのがいい。

おわりに

長時間エージェントの監視で見るべきなのは、危険な1操作だけではなく、失敗のあとにどの境界へ寄っていくかだと思う。ローカル実行でも、回数を持つだけで初回の確認と継続的な逸脱を分けられる。次に足すなら、PAUSE 時点の直近イベントをチケットや通知に添える。止めるだけでなく、人が再開を判断できるログまで残して初めて実務のガードになる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?