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エージェントに「責任経路」を実装する――Capability・Authority・Evidence・Resumeを分離する設計

0
Posted at

最近、AIエージェントに外部APIやMCP Tool Callを実行させる仕組みを作っていて、同じところで何度も引っかかりました。

AIがその操作をできることと、その操作をやってよいことは別です。

さらに厄介なのは実行したあとでした。「本当に成功したのか」「誰が確認するのか」「止まったあと、誰が再開してよいのか」まで考えると、単純なtool permissionだけでは足りません。

私はこの問題を追う中で、Capability、Authority、Evidence、実行状態、Return、Repair、Resumeを分けて扱うようになりました。

この記事では、その設計を責任経路(Responsibility Pathway)の最小構成として、Python風のコードで具体化します。

経緯やRPM / RPF / RPD / RPE / RPR / RPOSの全体像は、先に公開した入口記事で整理しています。

なお、この記事のコードは、特に「現行API」と明記した箇所以外は設計を説明するための疑似コードです。現在公開しているRPE / RPRのAPIそのものではありません。

最初に分けたもの

実装ではまず、次を分けます。

Capability  : その主体に何ができるか
Authority   : その行為をしてよいか
Evidence    : 判断・実行・結果を何で確認できるか
State       : 現在、責任経路のどこにいるか
Return      : 続行不能ならどこへ責任を戻すか

文字で見ると少し堅いですが、コードへ落とすと意外と素直です。

Capability != Authority

LLMがtool callを生成できるからといって、そのtoolを実行してよいとは限りません。

capability != authority

たとえば、AIエージェントに次の能力があるとします。

class Agent:
    capabilities = {"judge", "execute", "explain"}

executeできる。でも、それだけで本番の支払いAPIを叩いてよいわけではありません。

そこで私は、actorを能力と権限を分けて持つ対象として考えています。

from dataclasses import dataclass
from typing import FrozenSet

@dataclass(frozen=True)
class ResponsibilityActor:
    actor_id: str
    actor_type: str
    capabilities: FrozenSet[str]
    authority_scope: FrozenSet[str]
    available: bool = True
agent = ResponsibilityActor(
    actor_id="agent-a",
    actor_type="ai_agent",
    capabilities=frozenset({"judge", "execute", "explain"}),
    authority_scope=frozenset({"ticket.create", "ticket.read"}),
)

このagentはticketを扱えても、payment authorityは持っていません。

「賢いから実行できる」と「その実行を許可されている」は別です。

Transition側にも必要条件を持たせる

actorだけでは足りません。責任経路上のtransition側にも、必要な能力、authority、evidenceを持たせます。

@dataclass(frozen=True)
class TransitionRequirement:
    capability: str
    authority: str
    required_evidence: FrozenSet[str]
def can_take_transition(
    actor: ResponsibilityActor,
    req: TransitionRequirement,
    evidence: set[str],
) -> bool:
    return (
        actor.available
        and req.capability in actor.capabilities
        and req.authority in actor.authority_scope
        and req.required_evidence.issubset(evidence)
    )

こうすると、少なくともsoftware上は「humanならOK」「AIならNG」というactor typeだけの条件ではなく、そのtransitionに何が必要なのかを明示できます。

もちろん、法律・制度・組織ポリシーがhumanを要求する場面では、そのHuman Gateを維持します。Capabilityがあることと、制度上そのactorへAuthorityを与えられることは別だからです。

責任機能を一つにまとめない

責任という言葉を一個のboolにすると、実装時に困ります。

私は少なくとも、次を分けて考えています。

judge    判断する
execute  外部作用を起こす
explain  判断理由と証拠を説明する
verify   外部状態を確認する
repair   失敗状態を修復する
resume   停止後の実行を再開する

一つのactorが全部持つ必要はありません。

AI planner  : judge
AI executor : execute
Verifier    : verify
Human       : resume

のように分離できます。

今の社会ではHuman Gateやhuman resumeをdefaultに置くべき場面が多いと思っています。一方で、設計そのものをactor typeへ固定するのではなく、必要なcapability、正当なauthority、必要なevidenceを明示しておけば、どの責任機能を誰へ委ねられるのかを後から検査できます。

ここでやりたいのは「AIに責任を押しつける」ことではありません。責任を担うために必要な条件を、実装できる形へ分解することです。

Human Return Pointから、Return条件そのものを考える

責任経路の初期設計では、重要な判断や結果不明を人間へ返すことを強く置いていました。これは現在も重要な安全設計です。

ただ、実装していくと、return先を単にhumanと書くだけでは足りない場面が見えてきました。

人間へ返したとしても、その人に必要な権限がなければ再開できません。担当者が不在かもしれないし、承認が失効しているかもしれません。

そこで今は、誰へ返すかの前に、そのreturn先に何が必要なのかを記述できないかと考えています。

@dataclass(frozen=True)
class ReturnPoint:
    required_capabilities: FrozenSet[str]
    required_authority: FrozenSet[str]
    reason: str
return_point = ReturnPoint(
    required_capabilities=frozenset({"judge", "resume"}),
    required_authority=frozenset({"payment.resume"}),
    reason="external_effect_uncertain",
)

この条件を使えば、operational layer側で現在利用可能なactorから候補を探す設計も考えられます。

def resolve_return_actor(actors, rp: ReturnPoint):
    for actor in actors:
        if (
            actor.available
            and rp.required_capabilities.issubset(actor.capabilities)
            and rp.required_authority.issubset(actor.authority_scope)
        ):
            return actor
    return None

見つからなければfail closedです。

actor = resolve_return_actor(actors, return_point)
if actor is None:
    raise RuntimeError("No eligible responsibility actor")

私はこの一般化を、現在 Responsibility Return Point として検討しています。

これは現行RPE / RPR公開APIの確定仕様ではありません。また、Human Return Pointを不要にする提案でもありません。むしろHuman Gateを含めて、return先に必要なCapability・Authority・Evidenceを明示するための上位設計になり得るかを検討している段階です。

外部APIは「レスポンスが来た」で完了にしない

ここはRPRを実装していて、特に重要だと感じたところです。

たとえば、

response = payment_api.charge(order)

を実行した直後に通信が切れたとします。

request dispatch
      ↓
network failure
      ↓
外部では成功しているかもしれない

これ、かなり困ります(笑)。

clientには失敗に見える。でもserver側では処理済みかもしれない。ここで単純retryすると二重実行の危険があります。

だから、

receipt / response
!=
verified external effect

を分けます。

RPRの公開alphaでは、曖昧な外部writeをwrite_status_unknownとして保持し、成功に丸めず、独立readbackやreconciliationへ進める構造を実装しています。

概念化すると、

match dispatch_result:
    case "verified_applied":
        state = "completed"
    case "verified_not_applied":
        state = "repair_required"
    case _:
        state = "effect_unknown"

です。

effect_unknownはここでの説明用表現で、RPRの公開実装上の名称はwrite_status_unknownです。

大事なのは名前より、成功か失敗か確定できていない状態を、推測でどちらかへ丸めないことです。

RepairとResumeを同じ処理にしない

もう一つ、実装してみて重要だった分離があります。

repair != resume

障害を修復したからといって、そのまま再実行してよいとは限りません。

repair completed
      ↓
READY_TO_RESUME
      ↓
explicit resume decision
      ↓
new execution attempt
repair readiness != restored authority

修復している間にapprovalが失効したかもしれません。状況が変わったかもしれません。

だから再開時にauthorityをもう一度確認します。

if state == "ready_to_resume":
    if not has_current_resume_authority(actor, action):
        return "hold"

さらにresume後はfresh attempt identityを作ります。

from uuid import uuid4
new_attempt_id = uuid4()

以前のattemptと新しいattemptを混ぜないためです。

「直った」と「もう一度やっていい」は別。ここはRPRを作る中で、責任経路として明示的に分ける必要があると考えるようになりました。

RPEは「何を満たせば進めるか」を外へ出す

runtimeに全条件をhard-codeすると、業界や組織が変わるたびにコードを書き直すことになります。

そこでRPEでは、Responsible AI上の要求をmachine-readableなRequirement Packとして扱います。

イメージは次のようなものです。

requirement_pack:
  id: payment-production-v1
  owner: finance-governance
  status: approved
  requirements:
    - id: qualified-resume
      when:
        action: payment.resume
      require:
        capability: resume
        authority: payment.resume
        evidence:
          - reconciliation_result
          - current_approval

AIからaction requestが来たら、RPE側が評価します。

AI action request
      ↓
RPE
      ↓
allow / hold / human_gate / deny

現在のRPE M1公開実装には、deterministic Python kernel、Requirement Pack evaluation、REST、OpenAPI 3.1、MCP stdio、pack lifecycle、version / compatibilityなどがあります。

RPE自身はactionを実行しません。policy decisionとexternal executionを分けるためです。

RPRは「実際に何が起きたか」を保持する

RPEが「進めてよいか」を評価し、RPRが「実際の実行状態」を保持する。

簡略化すると、

Requirement / Policy
       ↓
      RPE
       ↓ gate decision
      RPR
       ↓
External Tool / API / MCP
       ↓
Readback / Evidence
       ↓
RPR state update

RPR 0.1.0a4では、actor / authority declaration、persistent execution attempts、readback、ambiguous writeのfail-closed、repair、resume、reconciliation、crash / restart continuityなどをPublic Alphaの範囲として実装しています。

ここでsoftware側の境界も明確にしておきます。RPRが扱うのは実行状態と責任経路の証拠です。法律上の責任を自動決定するengineではなく、任意のremote systemに対してexactly-once effectを保証するものでもありません。

RPOSで残っている問題

RPEとRPRを作ると、今度はruntimeの外側が見えてきました。

現実の組織ではactorも条件も変化します。

  • 担当者が不在になる
  • AI modelが更新される
  • capability評価が失効する
  • authorityの期限が切れる
  • Requirement Packがsupersedeされる
  • incident中だけ権限を縮小する

Runtimeが正しく止まっても、次に適格なactorが自動的に現れるわけではありません。

required role
     ↓
capability check
     ↓
authority check
     ↓
evidence / validity check
     ↓
eligible actor

この問題を、私はRPOSで扱う設計対象の一つとして考えています。

まだ完成済みのactor-resolution製品機能ではありません。RPE / RPRを実装して残った組織運用上の課題を、RPOS側でどう閉じるかをこれから検証する段階です。

数理や形式検証も、責任経路ではEvidenceとして扱える

形式検証は強力です。証明できるpropertyを増やすことには大きな価値があります。

一方で、形式的にpropertyが成立したことと、その結果をどの組織がどの目的で採用し、誰のauthorityで実行したかは別の問いです。

Formal Correctness != Responsibility Completion

だから私は、形式検証の結果も責任を終わらせるものとしてではなく、責任経路を支えるEvidenceの一つとして接続するのが自然ではないかと考えています。

proofが対象にしたproperty、assumption、modelをEvidenceとして残し、それを誰が採用し、どのactionへ適用したかを経路側で保持する、という考え方です。

最小構成へ縮めると

ここまでを実装上の最小構成へ縮めると、私は次の3つから始めるのが分かりやすいと思っています。

1. Actor Contract
   capability / authority / availability

2. Pathway State Machine
   proposed / gated / executing / effect_unknown /
   repair_required / ready_to_resume / completed

3. Evidence Model
   approval / receipt / readback / verification /
   reconciliation / repair evidence

さらにdomain ruleをRequirement Packとして外出しすれば、業界固有の規則と共通runtimeを分ける余地ができます。

Common Responsibility Runtime
          +
Domain Requirement Pack / Industry Profile

これは「医療も金融も行政も同じ責任ルールでよい」という意味ではありません。共通化できる責任経路の骨格と、domain固有で専門家が決めるべきruleを分けられないかという設計提案です。

まとめ

私が責任経路を実装してきて、少なくとも分けておいた方がよいと考えているのは次です。

Capability != Authority
Receipt != Verified Effect
Repair Readiness != Restored Authority
Resume != Retry
Formal Correctness != Responsibility Completion

最初は「重要なところでは人間へ返す」から始めました。それは今も重要です。

そのうえで実装を進めると、「人間へ返す」という一文だけでは、誰に必要なauthorityがあるのか、何を確認してから再開するのか、結果不明を誰が引き取るのかまでは決まりませんでした。

だから今は、必要なCapability・Authority・Evidenceと状態遷移を明示し、責任を扱うための経路そのものをsoftwareへ持たせる方向へ進めています。

RPEとRPRはその一部をすでに公開実装しています。Responsibility Return PointやRPOSのactor resolutionは、その実装から見えてきた次の検証対象です。

試してみる / 関連実装

RPRの現在の公開alphaは 0.1.0a4 です。

python -m pip install responsibility-pathway-runtime==0.1.0a4

この記事の疑似コードは考え方を説明するための例です。RPRはPublic Alphaであり、production-readyやcustomer-environment verifiedを意味しません。

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?