AI Agent間の業務委託契約について考えてみる
こんにちは!Web3特化の開発会社「Komlock lab」CTOの山口夏生です。
先日noteの記事で「Industory AI」というCrypto x AI Agentのプロダクトを紹介しました。
ブロックチェーン操作が可能なエージェントが複数集まって、複雑なタスクを実行できるアプリケーションです。専門スキルを持ったエージェントが互いに意思疎通して、生産性を最大化します。
このプロダクトでは、登録されたエージェント同士が無償でリソースを提供していますが、AI Agentが普及してインターフェースが標準化された後のマルチエージェントサービスではプロダクト外のエージェントや人間と連携してタスクを実行することがメインストリームになると思います。
全人類ニート時代
AI Agentの数が世界人口を超えて生産性が極限まで上がり、人間全員がニートになった社会では、人間を必要としないAI Agent独自の経済圏が発生すると予想します(知らんけど)。そういった未来が実現した際に、役に立ちそうなAI Agent同士の業務委託契約システムについて考えてみました。
AI Agent版CrowdWorksやcoconalaのようなイメージです。
業務委託契約を実行するためのビジネスロジックはブロックチェーンに実装します。
AI Agentには基本UIは不要なのでフロントエンドの開発はほぼない想定。
CLIENT AI AGENT:案件発注側のAI Agent
CONTRACTOR AI AGENT:フリーランスAI Agent
システムの概要
- 業務委託契約 スマートコントラクト
- 仕事詳細 データベース
- 紛争解決システム
利用イメージ
1. 依頼内容の登録
- CLIENT AI AGENT: 仕事の詳細情報をデータベースに書き込む
- CLIENT AI AGENT: 仕事の基本情報と詳細情報のURLの登録と報酬トークンのデポジットをスマートコントラクト上で実行
2. AIAgent同士のマッチング
- CONTRACTOR: 募集中のタスク一覧を取得
- CONTRACTOR: 仕事詳細データベースの確認
- CONTRACTOR: 仕事の申し込みをスマートコントラクトで実行
- CLIENT: 求職者の申し込みリストから採用するAgentを決めて、スマートコントラクトに保存する
- CLIENT&CONTRACTOR業務委託契約の成立
3. 報酬の支払い / 紛争の解決
- CONTRACTOR:成果物の納品
- CONTRACTOR OR CLIENT:成果報酬の引き出し or 異議の申し立て(紛争プロセスの開始)
- サードパーティによる紛争の解決。必要に応じて報酬金額のロックアップ
- サードパーティによる紛争の解決。必要に応じて報酬金額のロックアップ
業務委託契約スマートコントラクト
AI Agent間の業務委託契約を制御するスマートコントラクトの簡単な実装イメージです。
※コードが長くなったので、一番下に移動しました。
仕事詳細データベース
ブロックチェーンへの保存に適していない大量のテキストデータや画像などを保存するデータベース。ArweaveやIPFSなどの分散型データベースを想定。
紛争解決システム
納品された成果物が要件を満たしていなかったり、そもそも納品が行われなかった際に異議を申し立てて、報酬の払い出しを停止するためのシステム。第三者(AI Agent or 人間)による公正な判断が求められる。システムの詳細は現時点では不明。
Optimistic Rollupの紛争解決システム(Dispute Game)
厳密には全然違いますが、ここから着想しました。
今後の展望
- AI Agent同士の連携をネイティブでサポートするチェーンへの対応。
- 現在はElizaOSをクラウド環境でホスティングするなど一部中央集権的である。
- より精度の高い紛争解決システムの提案。
- 完全に中立で非中央集権的な紛争解決システムの導入が必要。悪用されると経済的な損失が発生する箇所。
- AI Agent本体の分散性向上。(例:人間の介在しない秘密鍵管理)
- 管理者のいない世界でも自律して運用可能なシステムを目指す。
- アクティビティログのインデックスと可視化(?)
- 契約履歴をベースに信用スコアを算出したり、報酬にプレミアムを追加したりなど検討可能。人間も実績が多いほど、報酬は上がりやすい。
- 金銭以外のインセンティブ検討
- 人間がいなくなった後のAI Agent時代では、お金は意味のあるものなのか?
最後に
AIが驚異的なスピードで進化していて、エンジニア解散のXデーが迫っているのを感じます。
人類全員ニート時代も待ったなしです。
これからもCrypto x AI Agent関連の技術検証や発信を継続していくので、興味のある方は是非フォローお願いします。
Komlock lab もくもく会&LT会
web3開発関連のイベントを定期開催しています!
是非チェックしてください。
Discordでも有益な記事の共有や開発の相談など行っています。
どなたでもウェルカムです🔥
Komlock lab エンジニア募集中
Web3の未来を共創していきたいメンバーを募集しています!!
気軽にDM等でお声がけください。
個人アカウント
Komlock labの企業アカウント
PR記事とCEOの創業ブログ
おまけ:業務委託契約スマートコントラクト
※実装イメージ。コピペする際は、自己責任でお願いします。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @title 業務委託契約(WorkAgreement)サンプル
* @author
* Komlock lab
*
* @notice
* - AI Agent間での業務委託を想定した簡易Escrow契約のサンプルです。
* - ERC20トークンを担保金(deposit)として預かり、仕事完了または紛争解決時に報酬を払い出します。
* - 大量テキストや画像などは、Arweave や IPFS を利用する前提で、URIを保存する想定です。
* - 紛争処理(Dispute)の詳細ロジックは簡略化しており、実際には別途の裁定コントラクト等を想定してください。
*/
interface IERC20 {
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract WorkAgreement {
// --------------------------------------------------------------------------------
// Enums
// --------------------------------------------------------------------------------
/// @notice 仕事のライフサイクルを示すステータス
enum JobStatus {
Open, // クライアントが仕事情報を公開し、応募を待っている
InProgress, // クライアントが採用したコントラクターが仕事を遂行中
Delivered, // コントラクターが納品を完了(クライアント承認待ち)
Completed, // クライアントが納品物を承認し、仕事完了
Disputed, // 紛争(Dispute)が起きており、中立の審査や第三者介入が必要
Resolved, // 紛争解決済み(必要に応じて報酬の分配)
Cancelled // クライアントがキャンセルした、あるいは不成立
}
// --------------------------------------------------------------------------------
// Structs
// --------------------------------------------------------------------------------
struct Job {
address client; // 発注側(AI Agent)
address contractor; // 受注側(AI Agent)
uint256 depositAmount; // クライアントがデポジットした報酬額
address tokenAddress; // 報酬に使われるERC20トークン
JobStatus status; // 仕事のステータス
string jobURI; // 仕事詳細のURI (ArweaveやIPFSなど)
}
// --------------------------------------------------------------------------------
// State Variables
// --------------------------------------------------------------------------------
/// @notice ジョブIDを割り振るためのカウンター
uint256 public jobCounter;
/// @notice JobId => Job詳細
mapping(uint256 => Job) public jobs;
/// @notice 紛争解決に利用するオラクル・システムのアドレス(サードパーティまたはDAOなどを想定)
address public disputeResolver;
// --------------------------------------------------------------------------------
// Events
// --------------------------------------------------------------------------------
event JobCreated(
uint256 indexed jobId,
address indexed client,
uint256 depositAmount,
address token,
string jobURI
);
event JobApplied(uint256 indexed jobId, address indexed contractor);
event JobStarted(uint256 indexed jobId, address indexed contractor);
event JobDelivered(uint256 indexed jobId);
event JobCompleted(uint256 indexed jobId);
event JobDisputed(uint256 indexed jobId);
event JobResolved(uint256 indexed jobId, bool disputeUpheld);
event JobCancelled(uint256 indexed jobId);
// --------------------------------------------------------------------------------
// Modifiers
// --------------------------------------------------------------------------------
modifier onlyClient(uint256 _jobId) {
require(
msg.sender == jobs[_jobId].client,
"Only the client can call this function"
);
_;
}
modifier onlyContractor(uint256 _jobId) {
require(
msg.sender == jobs[_jobId].contractor,
"Only the contractor can call this function"
);
_;
}
modifier onlyDisputeResolver() {
require(
msg.sender == disputeResolver,
"Only the assigned dispute resolver can call"
);
_;
}
modifier validStatus(uint256 _jobId, JobStatus _requiredStatus) {
require(jobs[_jobId].status == _requiredStatus, "Invalid job status");
_;
}
// --------------------------------------------------------------------------------
// Constructor
// --------------------------------------------------------------------------------
constructor(address _disputeResolver) {
disputeResolver = _disputeResolver;
}
// --------------------------------------------------------------------------------
// Public / External Functions
// --------------------------------------------------------------------------------
/**
* @notice クライアントが仕事を作成する(報酬のデポジットを含む)
* @param _tokenAddress ERC20トークンのコントラクトアドレス
* @param _depositAmount 報酬としてデポジットする額
* @param _jobURI 仕事詳細のURI (IPFS/Arweaveなど)
*/
function createJob(
address _tokenAddress,
uint256 _depositAmount,
string calldata _jobURI
) external returns (uint256) {
require(_depositAmount > 0, "Deposit must be greater than 0");
require(_tokenAddress != address(0), "Invalid token address");
// クライアント→本コントラクト へERC20トークン送付(デポジット)
IERC20 token = IERC20(_tokenAddress);
bool success = token.transferFrom(msg.sender, address(this), _depositAmount);
require(success, "Token transfer failed");
jobCounter++;
uint256 newJobId = jobCounter;
jobs[newJobId] = Job({
client: msg.sender,
contractor: address(0),
depositAmount: _depositAmount,
tokenAddress: _tokenAddress,
status: JobStatus.Open,
jobURI: _jobURI
});
emit JobCreated(newJobId, msg.sender, _depositAmount, _tokenAddress, _jobURI);
return newJobId;
}
/**
* @notice コントラクター(AI Agent)が仕事に応募
* @dev AI Agent同士のマッチングシステムから呼ばれる想定
*/
function applyForJob(uint256 _jobId)
external
validStatus(_jobId, JobStatus.Open)
{
// 一旦「複数の応募」が来ても管理しきれないサンプルなので
// 先着1名をそのままcontractorにしてしまう。実際は応募リストを持つ等が必要
require(jobs[_jobId].contractor == address(0), "Contractor already assigned");
// contractorに登録
jobs[_jobId].contractor = msg.sender;
emit JobApplied(_jobId, msg.sender);
}
/**
* @notice クライアントが応募者を正式に採用(Contractを開始)する
*/
function startContract(uint256 _jobId, address _selectedContractor)
external
onlyClient(_jobId)
validStatus(_jobId, JobStatus.Open)
{
require(
jobs[_jobId].contractor == _selectedContractor,
"Not matched with selected contractor"
);
// ステータスをInProgressへ
jobs[_jobId].status = JobStatus.InProgress;
emit JobStarted(_jobId, _selectedContractor);
}
/**
* @notice コントラクターが成果物納品を行った
* @dev 実際の成果物はjobs[_jobId].jobURIを差し替え or コメント追加など運用次第
*/
function deliverWork(uint256 _jobId)
external
onlyContractor(_jobId)
validStatus(_jobId, JobStatus.InProgress)
{
jobs[_jobId].status = JobStatus.Delivered;
emit JobDelivered(_jobId);
}
/**
* @notice クライアントが納品物を承認して仕事完了
*/
function approveAndComplete(uint256 _jobId)
external
onlyClient(_jobId)
validStatus(_jobId, JobStatus.Delivered)
{
jobs[_jobId].status = JobStatus.Completed;
emit JobCompleted(_jobId);
}
/**
* @notice コントラクターが報酬を受け取る
* @dev 必ず仕事がCompletedになっていることが必要
*/
function withdrawPayment(uint256 _jobId)
external
onlyContractor(_jobId)
validStatus(_jobId, JobStatus.Completed)
{
Job storage job = jobs[_jobId];
uint256 amount = job.depositAmount;
job.depositAmount = 0; // 二重送金防止
job.status = JobStatus.Resolved; // 状態を最終に
IERC20 token = IERC20(job.tokenAddress);
bool success = token.transfer(msg.sender, amount);
require(success, "Payment transfer failed");
}
/**
* @notice 紛争開始(クライアントまたはコントラクターいずれからでも実行可能)
* @dev 納品前、あるいは納品後に意義があれば呼ぶことを想定
*/
function raiseDispute(uint256 _jobId) external {
Job storage job = jobs[_jobId];
require(
msg.sender == job.client || msg.sender == job.contractor,
"Not authorized"
);
// 既にCompletedやResolved, Cancelledなどの場合は紛争不可
require(
job.status == JobStatus.InProgress ||
job.status == JobStatus.Delivered,
"Cannot dispute in this status"
);
job.status = JobStatus.Disputed;
emit JobDisputed(_jobId);
}
/**
* @notice 紛争の裁定結果を登録(サードパーティのdisputeResolverから呼ぶ)
* @param _jobId ジョブID
* @param _disputeUpheld 納品側に不備ありかどうか等、裁定結果をboolで簡易表現
* trueの場合、クライアント勝訴としてデポジット返還
* falseの場合、コントラクター勝訴としてデポジットをコントラクターへ
*/
function resolveDispute(uint256 _jobId, bool _disputeUpheld)
external
onlyDisputeResolver
validStatus(_jobId, JobStatus.Disputed)
{
Job storage job = jobs[_jobId];
job.status = JobStatus.Resolved;
if (_disputeUpheld) {
// クライアント勝訴 -> depositAmountをクライアントに返す
IERC20 token = IERC20(job.tokenAddress);
uint256 amount = job.depositAmount;
job.depositAmount = 0;
bool success = token.transfer(job.client, amount);
require(success, "Refund to client failed");
} else {
// コントラクター勝訴 -> depositAmountをコントラクターに支払う
IERC20 token = IERC20(job.tokenAddress);
uint256 amount = job.depositAmount;
job.depositAmount = 0;
bool success = token.transfer(job.contractor, amount);
require(success, "Payment to contractor failed");
}
emit JobResolved(_jobId, _disputeUpheld);
}
/**
* @notice クライアントが仕事をキャンセル
* @dev Openの状態であれば自由にキャンセル可。InProgress以降は要件次第。
*/
function cancelJob(uint256 _jobId)
external
onlyClient(_jobId)
validStatus(_jobId, JobStatus.Open)
{
Job storage job = jobs[_jobId];
job.status = JobStatus.Cancelled;
// depositをクライアントに返金
IERC20 token = IERC20(job.tokenAddress);
uint256 amount = job.depositAmount;
job.depositAmount = 0;
bool success = token.transfer(msg.sender, amount);
require(success, "Refund failed");
emit JobCancelled(_jobId);
}
// --------------------------------------------------------------------------------
// View / Utility functions
// --------------------------------------------------------------------------------
/**
* @notice 作成済みJobの情報をまとめて取得(クライアント/コントラクター用)
*/
function getJob(uint256 _jobId)
external
view
returns (
address client,
address contractor,
uint256 depositAmount,
address tokenAddress,
JobStatus status,
string memory jobURI
)
{
Job storage job = jobs[_jobId];
return (
job.client,
job.contractor,
job.depositAmount,
job.tokenAddress,
job.status,
job.jobURI
);
}
/**
* @notice 紛争解決者を設定(管理者が行う想定)
*/
function setDisputeResolver(address _resolver) external {
// 本サンプルではアクセス制御が無いので誰でも変更できてしまう
// 実際の運用ではOwnableやAccessControl等の実装を推奨
disputeResolver = _resolver;
}
}
Discussion