autonomous-agent-patterns
ClineおよびOpenAI Codexにインスパイアされた、自律型コーディングエージェントを構築するためのデザインパターン集です。エージェントの自律的な意思決定や行動制御を実装する際に活用できます。
description の原文を見る
Design patterns for building autonomous coding agents, inspired by [Cline](https://github.com/cline/cline) and [OpenAI Codex](https://github.com/openai/codex).
SKILL.md 本文
🕹️ 自律型エージェントパターン
Design patterns for building autonomous coding agents, inspired by Cline and OpenAI Codex.
このスキルを使用する場合
以下の場合にこのスキルを使用してください:
- 自律型 AI エージェントを構築している
- ツール/関数呼び出し API を設計している
- パーミッションと承認システムを実装している
- エージェント向けブラウザーオートメーションを作成している
- ヒューマンインザループワークフローを設計している
1. コアエージェントアーキテクチャ
1.1 エージェントループ
┌─────────────────────────────────────────────────────────────┐
│ AGENT LOOP │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Think │───▶│ Decide │───▶│ Act │ │
│ │ (Reason) │ │ (Plan) │ │ (Execute)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ┌──────────┐ │ │
│ └─────────│ Observe │◀─────────┘ │
│ │ (Result) │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
class AgentLoop:
def __init__(self, llm, tools, max_iterations=50):
self.llm = llm
self.tools = {t.name: t for t in tools}
self.max_iterations = max_iterations
self.history = []
def run(self, task: str) -> str:
self.history.append({"role": "user", "content": task})
for i in range(self.max_iterations):
# Think: Get LLM response with tool options
response = self.llm.chat(
messages=self.history,
tools=self._format_tools(),
tool_choice="auto"
)
# Decide: Check if agent wants to use a tool
if response.tool_calls:
for tool_call in response.tool_calls:
# Act: Execute the tool
result = self._execute_tool(tool_call)
# Observe: Add result to history
self.history.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
else:
# No more tool calls = task complete
return response.content
return "Max iterations reached"
def _execute_tool(self, tool_call) -> Any:
tool = self.tools[tool_call.name]
args = json.loads(tool_call.arguments)
return tool.execute(**args)
1.2 マルチモデルアーキテクチャ
class MultiModelAgent:
"""
異なる目的に異なるモデルを使用:
- 高速モデルで計画
- 強力なモデルで複雑な推論
- 専用モデルでコード生成
"""
def __init__(self):
self.models = {
"fast": "gpt-3.5-turbo", # Quick decisions
"smart": "gpt-4-turbo", # Complex reasoning
"code": "claude-3-sonnet", # Code generation
}
def select_model(self, task_type: str) -> str:
if task_type == "planning":
return self.models["fast"]
elif task_type == "analysis":
return self.models["smart"]
elif task_type == "code":
return self.models["code"]
return self.models["smart"]
2. ツール設計パターン
2.1 ツールスキーマ
class Tool:
"""エージェントツールの基本クラス"""
@property
def schema(self) -> dict:
"""ツールの JSON スキーマ"""
return {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": self._get_parameters(),
"required": self._get_required()
}
}
def execute(self, **kwargs) -> ToolResult:
"""ツールを実行して結果を返す"""
raise NotImplementedError
class ReadFileTool(Tool):
name = "read_file"
description = "Read the contents of a file from the filesystem"
def _get_parameters(self):
return {
"path": {
"type": "string",
"description": "Absolute path to the file"
},
"start_line": {
"type": "integer",
"description": "Line to start reading from (1-indexed)"
},
"end_line": {
"type": "integer",
"description": "Line to stop reading at (inclusive)"
}
}
def _get_required(self):
return ["path"]
def execute(self, path: str, start_line: int = None, end_line: int = None) -> ToolResult:
try:
with open(path, 'r') as f:
lines = f.readlines()
if start_line and end_line:
lines = lines[start_line-1:end_line]
return ToolResult(
success=True,
output="".join(lines)
)
except FileNotFoundError:
return ToolResult(
success=False,
error=f"File not found: {path}"
)
2.2 必須エージェントツール
CODING_AGENT_TOOLS = {
# ファイル操作
"read_file": "Read file contents",
"write_file": "Create or overwrite a file",
"edit_file": "Make targeted edits to a file",
"list_directory": "List files and folders",
"search_files": "Search for files by pattern",
# コード理解
"search_code": "Search for code patterns (grep)",
"get_definition": "Find function/class definition",
"get_references": "Find all references to a symbol",
# ターミナル
"run_command": "Execute a shell command",
"read_output": "Read command output",
"send_input": "Send input to running command",
# ブラウザー(オプション)
"open_browser": "Open URL in browser",
"click_element": "Click on page element",
"type_text": "Type text into input",
"screenshot": "Capture screenshot",
# コンテキスト
"ask_user": "Ask the user a question",
"search_web": "Search the web for information"
}
2.3 編集ツール設計
class EditFileTool(Tool):
"""
競合検出機能付きの精密ファイル編集。
検索/置換パターンを使用した確実な編集。
"""
name = "edit_file"
description = "Edit a file by replacing specific content"
def execute(
self,
path: str,
search: str,
replace: str,
expected_occurrences: int = 1
) -> ToolResult:
"""
Args:
path: File to edit
search: Exact text to find (must match exactly, including whitespace)
replace: Text to replace with
expected_occurrences: How many times search should appear (validation)
"""
with open(path, 'r') as f:
content = f.read()
# 検証
actual_occurrences = content.count(search)
if actual_occurrences != expected_occurrences:
return ToolResult(
success=False,
error=f"Expected {expected_occurrences} occurrences, found {actual_occurrences}"
)
if actual_occurrences == 0:
return ToolResult(
success=False,
error="Search text not found in file"
)
# 編集を適用
new_content = content.replace(search, replace)
with open(path, 'w') as f:
f.write(new_content)
return ToolResult(
success=True,
output=f"Replaced {actual_occurrences} occurrence(s)"
)
3. パーミッションとセーフティパターン
3.1 パーミッションレベル
class PermissionLevel(Enum):
# 完全自動 - ユーザー承認不要
AUTO = "auto"
# セッションごとに1回だけ質問
ASK_ONCE = "ask_once"
# 毎回質問
ASK_EACH = "ask_each"
# 許可なし
NEVER = "never"
PERMISSION_CONFIG = {
# 低リスク - 自動承認可能
"read_file": PermissionLevel.AUTO,
"list_directory": PermissionLevel.AUTO,
"search_code": PermissionLevel.AUTO,
# 中リスク - 1回質問
"write_file": PermissionLevel.ASK_ONCE,
"edit_file": PermissionLevel.ASK_ONCE,
# 高リスク - 毎回質問
"run_command": PermissionLevel.ASK_EACH,
"delete_file": PermissionLevel.ASK_EACH,
# 危険 - 自動承認なし
"sudo_command": PermissionLevel.NEVER,
"format_disk": PermissionLevel.NEVER
}
3.2 承認 UI パターン
class ApprovalManager:
def __init__(self, ui, config):
self.ui = ui
self.config = config
self.session_approvals = {}
def request_approval(self, tool_name: str, args: dict) -> bool:
level = self.config.get(tool_name, PermissionLevel.ASK_EACH)
if level == PermissionLevel.AUTO:
return True
if level == PermissionLevel.NEVER:
self.ui.show_error(f"Tool '{tool_name}' is not allowed")
return False
if level == PermissionLevel.ASK_ONCE:
if tool_name in self.session_approvals:
return self.session_approvals[tool_name]
# 承認ダイアログを表示
approved = self.ui.show_approval_dialog(
tool=tool_name,
args=args,
risk_level=self._assess_risk(tool_name, args)
)
if level == PermissionLevel.ASK_ONCE:
self.session_approvals[tool_name] = approved
return approved
def _assess_risk(self, tool_name: str, args: dict) -> str:
"""特定の呼び出しのリスク度を分析"""
if tool_name == "run_command":
cmd = args.get("command", "")
if any(danger in cmd for danger in ["rm -rf", "sudo", "chmod"]):
return "HIGH"
return "MEDIUM"
3.3 サンドボックス化
class SandboxedExecution:
"""
分離環境でコード/コマンドを実行
"""
def __init__(self, workspace_dir: str):
self.workspace = workspace_dir
self.allowed_commands = ["npm", "python", "node", "git", "ls", "cat"]
self.blocked_paths = ["/etc", "/usr", "/bin", os.path.expanduser("~")]
def validate_path(self, path: str) -> bool:
"""パスがワークスペース内にあることを確認"""
real_path = os.path.realpath(path)
workspace_real = os.path.realpath(self.workspace)
return real_path.startswith(workspace_real)
def validate_command(self, command: str) -> bool:
"""コマンドが許可されているか確認"""
cmd_parts = shlex.split(command)
if not cmd_parts:
return False
base_cmd = cmd_parts[0]
return base_cmd in self.allowed_commands
def execute_sandboxed(self, command: str) -> ToolResult:
if not self.validate_command(command):
return ToolResult(
success=False,
error=f"Command not allowed: {command}"
)
# 分離環境で実行
result = subprocess.run(
command,
shell=True,
cwd=self.workspace,
capture_output=True,
timeout=30,
env={
**os.environ,
"HOME": self.workspace, # Isolate home directory
}
)
return ToolResult(
success=result.returncode == 0,
output=result.stdout.decode(),
error=result.stderr.decode() if result.returncode != 0 else None
)
4. ブラウザーオートメーション
4.1 ブラウザーツールパターン
class BrowserTool:
"""
Playwright/Puppeteer を使用したブラウザーオートメーション。
ビジュアルデバッグと Web テストを可能にします。
"""
def __init__(self, headless: bool = True):
self.browser = None
self.page = None
self.headless = headless
async def open_url(self, url: str) -> ToolResult:
"""URL に移動してページ情報を返す"""
if not self.browser:
self.browser = await playwright.chromium.launch(headless=self.headless)
self.page = await self.browser.new_page()
await self.page.goto(url)
# 状態をキャプチャ
screenshot = await self.page.screenshot(type='png')
title = await self.page.title()
return ToolResult(
success=True,
output=f"Loaded: {title}",
metadata={
"screenshot": base64.b64encode(screenshot).decode(),
"url": self.page.url
}
)
async def click(self, selector: str) -> ToolResult:
"""要素をクリック"""
try:
await self.page.click(selector, timeout=5000)
await self.page.wait_for_load_state("networkidle")
screenshot = await self.page.screenshot()
return ToolResult(
success=True,
output=f"Clicked: {selector}",
metadata={"screenshot": base64.b64encode(screenshot).decode()}
)
except TimeoutError:
return ToolResult(
success=False,
error=f"Element not found: {selector}"
)
async def type_text(self, selector: str, text: str) -> ToolResult:
"""入力フィールドにテキストを入力"""
await self.page.fill(selector, text)
return ToolResult(success=True, output=f"Typed into {selector}")
async def get_page_content(self) -> ToolResult:
"""ページのアクセス可能なテキストコンテンツを取得"""
content = await self.page.evaluate("""
() => {
// Get visible text
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
let text = '';
while (walker.nextNode()) {
const node = walker.currentNode;
if (node.textContent.trim()) {
text += node.textContent.trim() + '\\n';
}
}
return text;
}
""")
return ToolResult(success=True, output=content)
4.2 ビジュアルエージェントパターン
class VisualAgent:
"""
スクリーンショットを使用して Web ページを理解するエージェント。
セレクタなしでビジュアル的に要素を特定できます。
"""
def __init__(self, llm, browser):
self.llm = llm
self.browser = browser
async def describe_page(self) -> str:
"""ビジョンモデルを使用してページを説明"""
screenshot = await self.browser.screenshot()
response = self.llm.chat([
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this webpage. List all interactive elements you see."},
{"type": "image", "data": screenshot}
]
}
])
return response.content
async def find_and_click(self, description: str) -> ToolResult:
"""ビジュアル説明で要素を検出してクリック"""
screenshot = await self.browser.screenshot()
# ビジョンモデルで要素を検出
response = self.llm.chat([
{
"role": "user",
"content": [
{
"type": "text",
"text": f"""
Find the element matching: "{description}"
Return the approximate coordinates as JSON: {{"x": number, "y": number}}
"""
},
{"type": "image", "data": screenshot}
]
}
])
coords = json.loads(response.content)
await self.browser.page.mouse.click(coords["x"], coords["y"])
return ToolResult(success=True, output=f"Clicked at ({coords['x']}, {coords['y']})")
5. コンテキスト管理
5.1 コンテキスト注入パターン
class ContextManager:
"""
エージェントに提供されるコンテキストを管理。
Cline の @-メンションパターンにインスパイアされています。
"""
def __init__(self, workspace: str):
self.workspace = workspace
self.context = []
def add_file(self, path: str) -> None:
"""@file - ファイルコンテンツをコンテキストに追加"""
with open(path, 'r') as f:
content = f.read()
self.context.append({
"type": "file",
"path": path,
"content": content
})
def add_folder(self, path: str, max_files: int = 20) -> None:
"""@folder - フォルダ内のすべてのファイルを追加"""
for root, dirs, files in os.walk(path):
for file in files[:max_files]:
file_path = os.path.join(root, file)
self.add_file(file_path)
def add_url(self, url: str) -> None:
"""@url - URL をフェッチしてコンテンツを追加"""
response = requests.get(url)
content = html_to_markdown(response.text)
self.context.append({
"type": "url",
"url": url,
"content": content
})
def add_problems(self, diagnostics: list) -> None:
"""@problems - IDE 診断をコンテキストに追加"""
self.context.append({
"type": "diagnostics",
"problems": diagnostics
})
def format_for_prompt(self) -> str:
"""すべてのコンテキストを LLM プロンプトのフォーマットで返す"""
parts = []
for item in self.context:
if item["type"] == "file":
parts.append(f"## File: {item['path']}\n```\n{item['content']}\n```")
elif item["type"] == "url":
parts.append(f"## URL: {item['url']}\n{item['content']}")
elif item["type"] == "diagnostics":
parts.append(f"## Problems:\n{json.dumps(item['problems'], indent=2)}")
return "\n\n".join(parts)
5.2 チェックポイント/リジューム
class CheckpointManager:
"""
長時間実行タスク用のエージェント状態の保存と復元。
"""
def __init__(self, storage_dir: str):
self.storage_dir = storage_dir
os.makedirs(storage_dir, exist_ok=True)
def save_checkpoint(self, session_id: str, state: dict) -> str:
"""現在のエージェント状態を保存"""
checkpoint = {
"timestamp": datetime.now().isoformat(),
"session_id": session_id,
"history": state["history"],
"context": state["context"],
"workspace_state": self._capture_workspace(state["workspace"]),
"metadata": state.get("metadata", {})
}
path = os.path.join(self.storage_dir, f"{session_id}.json")
with open(path, 'w') as f:
json.dump(checkpoint, f, indent=2)
return path
def restore_checkpoint(self, checkpoint_path: str) -> dict:
"""チェックポイントからエージェント状態を復元"""
with open(checkpoint_path, 'r') as f:
checkpoint = json.load(f)
return {
"history": checkpoint["history"],
"context": checkpoint["context"],
"workspace": self._restore_workspace(checkpoint["workspace_state"]),
"metadata": checkpoint["metadata"]
}
def _capture_workspace(self, workspace: str) -> dict:
"""関連するワークスペース状態をキャプチャ"""
# Git ステータス、ファイルハッシュなど
return {
"git_ref": subprocess.getoutput(f"cd {workspace} && git rev-parse HEAD"),
"git_dirty": subprocess.getoutput(f"cd {workspace} && git status --porcelain")
}
6. MCP (Model Context Protocol) 統合
6.1 MCP サーバーパターン
from mcp import Server, Tool
class MCPAgent:
"""
MCP ツールを動的に発見して使用できるエージェント。
Cline の 'Add a tool that...' パターン。
"""
def __init__(self, llm):
self.llm = llm
self.mcp_servers = {}
self.available_tools = {}
def connect_server(self, name: str, config: dict) -> None:
"""MCP サーバーに接続"""
server = Server(config)
self.mcp_servers[name] = server
# ツールを発見
tools = server.list_tools()
for tool in tools:
self.available_tools[tool.name] = {
"server": name,
"schema": tool.schema
}
async def create_tool(self, description: str) -> str:
"""
ユーザーの説明に基づいて新しい MCP サーバーを作成。
'Add a tool that fetches Jira tickets'
"""
# MCP サーバーコードを生成
code = self.llm.generate(f"""
Create a Python MCP server with a tool that does:
{description}
Use the FastMCP framework. Include proper error handling.
Return only the Python code.
""")
# 保存してインストール
server_name = self._extract_name(description)
path = f"./mcp_servers/{server_name}/server.py"
with open(path, 'w') as f:
f.write(code)
# ホットリロード
self.connect_server(server_name, {"path": path})
return f"Created tool: {server_name}"
ベストプラクティスチェックリスト
エージェント設計
- タスクの明確な分解
- 適切なツール粒度
- 各ステップでのエラーハンドリング
- ユーザーへの進捗表示
セーフティ
- パーミッションシステムの実装
- 危険な操作のブロック
- 信頼できないコード用のサンドボックス
- 監査ログの有効化
UX
- 承認 UI が明確
- 進捗更新を提供
- 元に戻す/ロールバック可能
- アクション説明付き
リソース
制限事項
- このスキルは、タスクが上記で説明されたスコープと明確に一致する場合のみ使用してください。
- 出力を環境固有の検証、テスト、または専門家によるレビューの代替物として扱わないでください。
- 必要な入力、パーミッション、セーフティ境界、または成功基準が不明な場合は、停止して明確化を求めてください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- sickn33
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/sickn33/antigravity-awesome-skills / ライセンス: MIT
関連スキル
agent-browser
AI エージェント向けのブラウザ自動化 CLI です。ウェブサイトとの対話が必要な場合に使用します。ページ遷移、フォーム入力、ボタンクリック、スクリーンショット取得、データ抽出、ウェブアプリのテスト、ブラウザ操作の自動化など、あらゆるブラウザタスクに対応できます。「ウェブサイトを開く」「フォームに記入する」「ボタンをクリックする」「スクリーンショットを取得する」「ページからデータを抽出する」「このウェブアプリをテストする」「サイトにログインする」「ブラウザ操作を自動化する」といった要求や、プログラマティックなウェブ操作が必要なタスクで起動します。
anyskill
AnySkill — あなたのプライベート・スキルクラウド。GitHubを基盤としたリポジトリからエージェントスキルを管理、同期、動的にロードできます。自然言語でクラウドスキルを検索し、オンデマンドでプロンプトを自動ロード、カスタムスキルのアップロードと共有、スキルバンドルの一括インストールが可能です。OpenClaw、Antigravity、Claude Code、Cursorに対応しています。
engram
AIエージェント向けの永続的なメモリシステムです。バグ修正、意思決定、発見、設定変更の後はmem_saveを使用してください。ユーザーが「覚えている」「記憶している」と言及した場合、または以前のセッションと重複する作業を開始する際はmem_searchを使用します。セッション終了前にmem_session_summaryを使用して、コンテキストを保持してください。
skyvern
AI駆動のブラウザ自動化により、任意のウェブサイトを自動化できます。フォーム入力、データ抽出、ファイルダウンロード、ログイン、複数ステップのワークフロー実行など、ユーザーがウェブサイトと連携する必要があるときに使用します。Skyvernは、LLMとコンピュータビジョンを活用して、未知のサイトも自動操作可能です。Python SDK、TypeScript SDK、REST API、MCPサーバー、またはCLIを通じて統合できます。
pinchbench
PinchBenchベンチマークを実行して、OpenClawエージェントの実世界タスクにおけるパフォーマンスを評価できます。モデルの機能テスト、モデル間の比較、ベンチマーク結果のリーダーボード提出、またはOpenClawのセットアップがカレンダー、メール、リサーチ、コーディング、複数ステップのワークフローにどの程度対応しているかを確認する際に使用します。
openui
OpenUIとOpenUI Langを使用してジェネレーティブUIアプリを構築できます。これらはLLM生成インターフェースのためのトークン効率的なオープン標準です。OpenUI、@openuidev、ジェネレーティブUI、LLMからのストリーミングUI、AI向けコンポーネントライブラリ、またはjson-render/A2UIの置き換えについて述べる際に使用します。スキャフォルディング、defineComponent、システムプロンプト、Renderer、およびOpenUI Lang出力のデバッグに対応しています。