aiagent-runner 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiagent_runner/__init__.py +9 -0
- aiagent_runner/__main__.py +282 -0
- aiagent_runner/config.py +99 -0
- aiagent_runner/coordinator.py +687 -0
- aiagent_runner/coordinator_config.py +203 -0
- aiagent_runner/executor.py +99 -0
- aiagent_runner/mcp_client.py +698 -0
- aiagent_runner/prompt_builder.py +120 -0
- aiagent_runner/runner.py +236 -0
- aiagent_runner-0.1.3.dist-info/METADATA +185 -0
- aiagent_runner-0.1.3.dist-info/RECORD +13 -0
- aiagent_runner-0.1.3.dist-info/WHEEL +4 -0
- aiagent_runner-0.1.3.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# src/aiagent_runner/prompt_builder.py
|
|
2
|
+
# Build prompts for CLI execution from task information
|
|
3
|
+
# Reference: docs/plan/PHASE3_PULL_ARCHITECTURE.md - Phase 3-5
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from aiagent_runner.mcp_client import TaskInfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PromptBuilder:
|
|
10
|
+
"""Builds prompts for CLI execution from task information."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, agent_id: str, agent_name: Optional[str] = None):
|
|
13
|
+
"""Initialize prompt builder.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
agent_id: Agent ID executing the task
|
|
17
|
+
agent_name: Human-readable agent name (optional)
|
|
18
|
+
"""
|
|
19
|
+
self.agent_id = agent_id
|
|
20
|
+
self.agent_name = agent_name or agent_id
|
|
21
|
+
|
|
22
|
+
def build(
|
|
23
|
+
self,
|
|
24
|
+
task: TaskInfo,
|
|
25
|
+
context: Optional[dict] = None,
|
|
26
|
+
handoff: Optional[dict] = None
|
|
27
|
+
) -> str:
|
|
28
|
+
"""Build a prompt from task information.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
task: Task information
|
|
32
|
+
context: Previous context (optional, overrides task.context)
|
|
33
|
+
handoff: Handoff information (optional, overrides task.handoff)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Complete prompt string for CLI execution
|
|
37
|
+
"""
|
|
38
|
+
# Use provided context/handoff or fall back to task's
|
|
39
|
+
ctx = context or task.context
|
|
40
|
+
ho = handoff or task.handoff
|
|
41
|
+
|
|
42
|
+
sections = [
|
|
43
|
+
self._build_header(task),
|
|
44
|
+
self._build_identification(task),
|
|
45
|
+
self._build_description(task),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
if task.working_directory:
|
|
49
|
+
sections.append(self._build_working_directory(task))
|
|
50
|
+
|
|
51
|
+
if ctx:
|
|
52
|
+
sections.append(self._build_context(ctx))
|
|
53
|
+
|
|
54
|
+
if ho:
|
|
55
|
+
sections.append(self._build_handoff(ho))
|
|
56
|
+
|
|
57
|
+
sections.append(self._build_instructions(task))
|
|
58
|
+
|
|
59
|
+
return "\n\n".join(sections)
|
|
60
|
+
|
|
61
|
+
def _build_header(self, task: TaskInfo) -> str:
|
|
62
|
+
"""Build header section."""
|
|
63
|
+
return f"# Task: {task.title}"
|
|
64
|
+
|
|
65
|
+
def _build_identification(self, task: TaskInfo) -> str:
|
|
66
|
+
"""Build identification section with IDs."""
|
|
67
|
+
return f"""## Identification
|
|
68
|
+
- Task ID: {task.task_id}
|
|
69
|
+
- Project ID: {task.project_id}
|
|
70
|
+
- Agent ID: {self.agent_id}
|
|
71
|
+
- Agent Name: {self.agent_name}
|
|
72
|
+
- Priority: {task.priority}"""
|
|
73
|
+
|
|
74
|
+
def _build_description(self, task: TaskInfo) -> str:
|
|
75
|
+
"""Build description section."""
|
|
76
|
+
return f"""## Description
|
|
77
|
+
{task.description}"""
|
|
78
|
+
|
|
79
|
+
def _build_working_directory(self, task: TaskInfo) -> str:
|
|
80
|
+
"""Build working directory section."""
|
|
81
|
+
return f"""## Working Directory
|
|
82
|
+
Path: {task.working_directory}"""
|
|
83
|
+
|
|
84
|
+
def _build_context(self, context: dict) -> str:
|
|
85
|
+
"""Build previous context section."""
|
|
86
|
+
lines = ["## Previous Context"]
|
|
87
|
+
if context.get("progress"):
|
|
88
|
+
lines.append(f"**Progress**: {context['progress']}")
|
|
89
|
+
if context.get("findings"):
|
|
90
|
+
lines.append(f"**Findings**: {context['findings']}")
|
|
91
|
+
if context.get("blockers"):
|
|
92
|
+
lines.append(f"**Blockers**: {context['blockers']}")
|
|
93
|
+
if context.get("next_steps"):
|
|
94
|
+
lines.append(f"**Next Steps**: {context['next_steps']}")
|
|
95
|
+
return "\n".join(lines)
|
|
96
|
+
|
|
97
|
+
def _build_handoff(self, handoff: dict) -> str:
|
|
98
|
+
"""Build handoff information section."""
|
|
99
|
+
lines = ["## Handoff Information"]
|
|
100
|
+
if handoff.get("from_agent") or handoff.get("from_agent_id"):
|
|
101
|
+
from_agent = handoff.get("from_agent") or handoff.get("from_agent_id")
|
|
102
|
+
lines.append(f"**From Agent**: {from_agent}")
|
|
103
|
+
if handoff.get("summary"):
|
|
104
|
+
lines.append(f"**Summary**: {handoff['summary']}")
|
|
105
|
+
if handoff.get("context"):
|
|
106
|
+
lines.append(f"**Context**: {handoff['context']}")
|
|
107
|
+
if handoff.get("recommendations"):
|
|
108
|
+
lines.append(f"**Recommendations**: {handoff['recommendations']}")
|
|
109
|
+
return "\n".join(lines)
|
|
110
|
+
|
|
111
|
+
def _build_instructions(self, task: TaskInfo) -> str:
|
|
112
|
+
"""Build instructions section."""
|
|
113
|
+
return f"""## Instructions
|
|
114
|
+
1. Complete the task as described above
|
|
115
|
+
2. Save your progress regularly using:
|
|
116
|
+
save_context(task_id="{task.task_id}", progress="...", findings="...", next_steps="...")
|
|
117
|
+
3. When done, update the task status using:
|
|
118
|
+
update_task_status(task_id="{task.task_id}", status="done")
|
|
119
|
+
4. If you need to hand off to another agent, use:
|
|
120
|
+
create_handoff(task_id="{task.task_id}", from_agent_id="{self.agent_id}", summary="...", recommendations="...")"""
|
aiagent_runner/runner.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# src/aiagent_runner/runner.py
|
|
2
|
+
# Main runner loop for AI Agent PM
|
|
3
|
+
# Reference: docs/plan/PHASE3_PULL_ARCHITECTURE.md - Phase 3-5
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from aiagent_runner.config import RunnerConfig
|
|
13
|
+
from aiagent_runner.executor import CLIExecutor, ExecutionResult
|
|
14
|
+
from aiagent_runner.mcp_client import (
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
MCPClient,
|
|
17
|
+
MCPError,
|
|
18
|
+
SessionExpiredError,
|
|
19
|
+
TaskInfo,
|
|
20
|
+
)
|
|
21
|
+
from aiagent_runner.prompt_builder import PromptBuilder
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Runner:
|
|
27
|
+
"""Main runner that polls for tasks and executes them.
|
|
28
|
+
|
|
29
|
+
The runner operates in a polling loop:
|
|
30
|
+
1. Authenticate with MCP server
|
|
31
|
+
2. Poll for pending tasks
|
|
32
|
+
3. Execute tasks using CLI
|
|
33
|
+
4. Report execution results
|
|
34
|
+
5. Wait for polling interval
|
|
35
|
+
6. Repeat
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: RunnerConfig):
|
|
39
|
+
"""Initialize runner.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: Runner configuration
|
|
43
|
+
"""
|
|
44
|
+
self.config = config
|
|
45
|
+
self.mcp_client = MCPClient(config.mcp_socket_path)
|
|
46
|
+
self.executor = CLIExecutor(config.cli_command, config.cli_args)
|
|
47
|
+
self.prompt_builder: Optional[PromptBuilder] = None
|
|
48
|
+
|
|
49
|
+
self._running = False
|
|
50
|
+
self._authenticated = False
|
|
51
|
+
self._agent_name: Optional[str] = None
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def log_directory(self) -> Path:
|
|
55
|
+
"""Get log directory, creating if needed."""
|
|
56
|
+
if self.config.log_directory:
|
|
57
|
+
log_dir = Path(self.config.log_directory)
|
|
58
|
+
else:
|
|
59
|
+
log_dir = Path.home() / ".aiagent-runner" / "logs"
|
|
60
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
return log_dir
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def working_directory(self) -> str:
|
|
65
|
+
"""Get working directory."""
|
|
66
|
+
return self.config.working_directory or os.getcwd()
|
|
67
|
+
|
|
68
|
+
async def start(self) -> None:
|
|
69
|
+
"""Start the runner loop.
|
|
70
|
+
|
|
71
|
+
Runs until stop() is called or an unrecoverable error occurs.
|
|
72
|
+
"""
|
|
73
|
+
logger.info(
|
|
74
|
+
f"Starting runner for agent {self.config.agent_id}, "
|
|
75
|
+
f"polling every {self.config.polling_interval}s"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
self._running = True
|
|
79
|
+
|
|
80
|
+
while self._running:
|
|
81
|
+
try:
|
|
82
|
+
await self._run_once()
|
|
83
|
+
except AuthenticationError as e:
|
|
84
|
+
logger.error(f"Authentication failed: {e}")
|
|
85
|
+
self._authenticated = False
|
|
86
|
+
# Wait before retrying
|
|
87
|
+
await asyncio.sleep(self.config.polling_interval)
|
|
88
|
+
except MCPError as e:
|
|
89
|
+
logger.error(f"MCP error: {e}")
|
|
90
|
+
# Wait before retrying
|
|
91
|
+
await asyncio.sleep(self.config.polling_interval)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.exception(f"Unexpected error: {e}")
|
|
94
|
+
# Wait before retrying
|
|
95
|
+
await asyncio.sleep(self.config.polling_interval)
|
|
96
|
+
|
|
97
|
+
if self._running:
|
|
98
|
+
await asyncio.sleep(self.config.polling_interval)
|
|
99
|
+
|
|
100
|
+
def stop(self) -> None:
|
|
101
|
+
"""Stop the runner loop."""
|
|
102
|
+
logger.info("Stopping runner")
|
|
103
|
+
self._running = False
|
|
104
|
+
|
|
105
|
+
async def _run_once(self) -> None:
|
|
106
|
+
"""Run one iteration of the polling loop."""
|
|
107
|
+
# Ensure authenticated
|
|
108
|
+
await self._ensure_authenticated()
|
|
109
|
+
|
|
110
|
+
# Get pending tasks (agent_id is derived from session token)
|
|
111
|
+
try:
|
|
112
|
+
tasks = await self.mcp_client.get_pending_tasks()
|
|
113
|
+
except SessionExpiredError:
|
|
114
|
+
logger.info("Session expired, re-authenticating")
|
|
115
|
+
self._authenticated = False
|
|
116
|
+
await self._ensure_authenticated()
|
|
117
|
+
tasks = await self.mcp_client.get_pending_tasks()
|
|
118
|
+
|
|
119
|
+
if not tasks:
|
|
120
|
+
logger.debug("No pending tasks")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
logger.info(f"Found {len(tasks)} pending task(s)")
|
|
124
|
+
|
|
125
|
+
# Process first task (one at a time)
|
|
126
|
+
task = tasks[0]
|
|
127
|
+
await self._process_task(task)
|
|
128
|
+
|
|
129
|
+
async def _ensure_authenticated(self) -> None:
|
|
130
|
+
"""Ensure we have a valid session."""
|
|
131
|
+
if self._authenticated:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
logger.info(f"Authenticating agent {self.config.agent_id} for project {self.config.project_id}")
|
|
135
|
+
result = await self.mcp_client.authenticate(
|
|
136
|
+
self.config.agent_id,
|
|
137
|
+
self.config.passkey,
|
|
138
|
+
self.config.project_id
|
|
139
|
+
)
|
|
140
|
+
self._authenticated = True
|
|
141
|
+
self._agent_name = result.agent_name
|
|
142
|
+
self.prompt_builder = PromptBuilder(
|
|
143
|
+
self.config.agent_id,
|
|
144
|
+
self._agent_name
|
|
145
|
+
)
|
|
146
|
+
logger.info(
|
|
147
|
+
f"Authenticated as {self._agent_name or self.config.agent_id}, "
|
|
148
|
+
f"session expires in {result.expires_in}s"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
async def _process_task(self, task: TaskInfo) -> None:
|
|
152
|
+
"""Process a single task.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
task: Task to process
|
|
156
|
+
"""
|
|
157
|
+
logger.info(f"Processing task {task.task_id}: {task.title}")
|
|
158
|
+
|
|
159
|
+
# Report execution start (agent_id is derived from session token)
|
|
160
|
+
try:
|
|
161
|
+
start_result = await self.mcp_client.report_execution_start(
|
|
162
|
+
task.task_id
|
|
163
|
+
)
|
|
164
|
+
execution_id = start_result.execution_id
|
|
165
|
+
except MCPError as e:
|
|
166
|
+
logger.error(f"Failed to report execution start: {e}")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# Build prompt
|
|
170
|
+
prompt = self.prompt_builder.build(task)
|
|
171
|
+
|
|
172
|
+
# Determine working directory
|
|
173
|
+
work_dir = task.working_directory or self.working_directory
|
|
174
|
+
|
|
175
|
+
# Generate log file path
|
|
176
|
+
log_file = self._generate_log_path(task.task_id)
|
|
177
|
+
|
|
178
|
+
# Execute CLI
|
|
179
|
+
logger.info(f"Executing {self.config.cli_command} for task {task.task_id}")
|
|
180
|
+
result = self.executor.execute(prompt, work_dir, log_file)
|
|
181
|
+
|
|
182
|
+
# Report execution complete
|
|
183
|
+
error_message = None
|
|
184
|
+
if result.exit_code != 0:
|
|
185
|
+
error_message = f"CLI exited with code {result.exit_code}"
|
|
186
|
+
logger.warning(
|
|
187
|
+
f"Task {task.task_id} execution failed: {error_message}"
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
logger.info(
|
|
191
|
+
f"Task {task.task_id} execution completed in "
|
|
192
|
+
f"{result.duration_seconds:.1f}s"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
await self.mcp_client.report_execution_complete(
|
|
197
|
+
execution_id,
|
|
198
|
+
result.exit_code,
|
|
199
|
+
result.duration_seconds,
|
|
200
|
+
result.log_file,
|
|
201
|
+
error_message
|
|
202
|
+
)
|
|
203
|
+
except MCPError as e:
|
|
204
|
+
logger.error(f"Failed to report execution complete: {e}")
|
|
205
|
+
|
|
206
|
+
def _generate_log_path(self, task_id: str) -> str:
|
|
207
|
+
"""Generate a log file path for a task execution.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
task_id: Task ID
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Path to log file
|
|
214
|
+
"""
|
|
215
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
216
|
+
filename = f"{task_id}_{timestamp}.log"
|
|
217
|
+
return str(self.log_directory / filename)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def run_async(config: RunnerConfig) -> None:
|
|
221
|
+
"""Run the runner asynchronously.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
config: Runner configuration
|
|
225
|
+
"""
|
|
226
|
+
runner = Runner(config)
|
|
227
|
+
await runner.start()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def run(config: RunnerConfig) -> None:
|
|
231
|
+
"""Run the runner synchronously.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
config: Runner configuration
|
|
235
|
+
"""
|
|
236
|
+
asyncio.run(run_async(config))
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiagent-runner
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Runner for AI Agent PM - executes tasks via MCP and CLI
|
|
5
|
+
Author: AI Agent PM Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: agent,ai,automation,mcp,orchestration,task-management
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Requires-Dist: pyyaml>=6.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: aiohttp>=3.9; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Provides-Extra: http
|
|
25
|
+
Requires-Dist: aiohttp>=3.9; extra == 'http'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# AI Agent PM Coordinator
|
|
29
|
+
|
|
30
|
+
全エージェントのタスク実行を統合管理するオーケストレーションデーモン。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🚨 致命的な設計違反警告 (2026-01-09)
|
|
35
|
+
|
|
36
|
+
**現在の実装には致命的な設計違反があります。修正が必要です。**
|
|
37
|
+
|
|
38
|
+
### 問題点
|
|
39
|
+
|
|
40
|
+
Coordinator/Runnerは**純粋なMCPクライアント**であるべきですが、現在の実装は以下の不正な情報を保持しています:
|
|
41
|
+
|
|
42
|
+
| 設定項目 | 問題 | 対応 |
|
|
43
|
+
|---------|------|------|
|
|
44
|
+
| `mcp_server_command` | サーバー起動コマンドを保持 | **削除必須** |
|
|
45
|
+
| `mcp_database_path` | DBパス(内部実装詳細)を保持 | **削除必須** |
|
|
46
|
+
|
|
47
|
+
### 設計原則
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
✅ 正しい設計: Coordinatorはソケット接続のみ行う純粋なクライアント
|
|
51
|
+
❌ 現在の実装: Coordinatorがサーバー起動やDB接続情報を管理
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 影響
|
|
55
|
+
|
|
56
|
+
- Agent Instanceごとに新しいMCPサーバーをstdio transportで起動
|
|
57
|
+
- 複数のMCPサーバーが同じDBに同時アクセス → データ整合性の問題
|
|
58
|
+
- テストと本番で異なるDB設定が必要 → 設定の複雑化
|
|
59
|
+
|
|
60
|
+
### 正しいアーキテクチャ
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[アプリ] → MCPデーモン起動(唯一のサーバー)
|
|
64
|
+
[Coordinator] → Unix Socketで接続(クライアントのみ)
|
|
65
|
+
[Agent Instance] → 同じUnix Socketで接続(クライアントのみ)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
詳細は `docs/plan/PHASE4_COORDINATOR_ARCHITECTURE.md` の警告セクションを参照。
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## クイックスタート
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd runner
|
|
76
|
+
pip install -e .
|
|
77
|
+
|
|
78
|
+
# デフォルト設定で起動(config/coordinator_default.yaml を使用)
|
|
79
|
+
python -m aiagent_runner --coordinator
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Coordinatorモード(推奨)
|
|
85
|
+
|
|
86
|
+
単一のCoordinatorが全ての(agent_id, project_id)ペアを管理します。
|
|
87
|
+
|
|
88
|
+
### 基本起動
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# デフォルト設定で起動
|
|
92
|
+
python -m aiagent_runner --coordinator
|
|
93
|
+
|
|
94
|
+
# 詳細ログ出力
|
|
95
|
+
python -m aiagent_runner --coordinator -v
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### カスタム設定ファイル
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m aiagent_runner --coordinator -c /path/to/config.yaml
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 設定ファイル例
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
# config/coordinator_default.yaml がデフォルトで読み込まれます
|
|
108
|
+
# カスタム設定で上書き可能
|
|
109
|
+
|
|
110
|
+
polling_interval: 10
|
|
111
|
+
max_concurrent: 3
|
|
112
|
+
|
|
113
|
+
# AI providers
|
|
114
|
+
ai_providers:
|
|
115
|
+
claude:
|
|
116
|
+
cli_command: claude
|
|
117
|
+
cli_args:
|
|
118
|
+
- "--dangerously-skip-permissions"
|
|
119
|
+
- "--max-turns"
|
|
120
|
+
- "50"
|
|
121
|
+
|
|
122
|
+
# Agents (passkeyのみ - ai_type等はMCPから取得)
|
|
123
|
+
agents:
|
|
124
|
+
agt_developer:
|
|
125
|
+
passkey: secret123
|
|
126
|
+
agt_reviewer:
|
|
127
|
+
passkey: ${REVIEWER_PASSKEY} # 環境変数展開対応
|
|
128
|
+
|
|
129
|
+
log_directory: /tmp/coordinator_logs
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### バックグラウンド実行
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
nohup python -m aiagent_runner --coordinator -v > coordinator.log 2>&1 &
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 設定の優先順位
|
|
141
|
+
|
|
142
|
+
1. **コマンドライン引数** (`--polling-interval` 等)
|
|
143
|
+
2. **指定した設定ファイル** (`-c /path/to/config.yaml`)
|
|
144
|
+
3. **デフォルト設定** (`runner/config/coordinator_default.yaml`)
|
|
145
|
+
4. **組み込みデフォルト値**
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 動作フロー
|
|
150
|
+
|
|
151
|
+
1. MCPサーバーに接続(Unixソケット)
|
|
152
|
+
2. `list_active_projects_with_agents()` で全プロジェクト・エージェントを取得
|
|
153
|
+
3. 各(agent_id, project_id)ペアに対して `get_agent_action()` を呼び出し
|
|
154
|
+
4. 作業が必要な場合、Agent Instance(Claude CLI等)をスポーン
|
|
155
|
+
5. Agent Instanceが `authenticate` → `get_my_task` → 実行 → `report_completed`
|
|
156
|
+
6. 待機して2に戻る
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 前提条件
|
|
161
|
+
|
|
162
|
+
- MCPサーバーが起動していること
|
|
163
|
+
- エージェントがアプリで登録済みで、passkeyが設定されていること
|
|
164
|
+
- 該当エージェントがプロジェクトに割り当てられていること
|
|
165
|
+
- タスクが `in_progress` ステータスであること
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Legacy Runnerモード(非推奨)
|
|
170
|
+
|
|
171
|
+
1エージェント = 1デーモン の旧アーキテクチャ。
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# 非推奨: Coordinatorモードを使用してください
|
|
175
|
+
aiagent-runner --agent-id <AGENT_ID> --passkey <PASSKEY> --project-id <PROJECT_ID>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 開発
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install -e ".[dev]"
|
|
184
|
+
pytest
|
|
185
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
aiagent_runner/__init__.py,sha256=dniIduQc_cgSMFCXg283ytOOFUxrlO5imMKOxLkTCT0,314
|
|
2
|
+
aiagent_runner/__main__.py,sha256=xxBGs0gZdMEVSh21UOWVJg6ekud5OcwmCIflH0eSYcI,8711
|
|
3
|
+
aiagent_runner/config.py,sha256=3Wu5XPuKjPpj6SJcGDoycrK7DkA2bWZ_1OiEUJm2l7g,3539
|
|
4
|
+
aiagent_runner/coordinator.py,sha256=Gg3CbWGe2fH1dqVobqSMGGgXHQ1UrOHqcWZuKQHl43E,26338
|
|
5
|
+
aiagent_runner/coordinator_config.py,sha256=DVGleEeGU93xLSSOONSgFLckwV-3bc53WiwWz6GIIAU,7208
|
|
6
|
+
aiagent_runner/executor.py,sha256=kUyXFb0j6Qw7P41S8KkznI3JSYJT0dxL8gGQdk7kkcA,2914
|
|
7
|
+
aiagent_runner/mcp_client.py,sha256=Y70DDzhQXBirui8OQ_MmOTwBZLWlRK7c3TSHHAYsT4o,23360
|
|
8
|
+
aiagent_runner/prompt_builder.py,sha256=IwPOeXDqYED6iRkLETlQg6tarG-U2VUtnaQaiGwgEQk,4402
|
|
9
|
+
aiagent_runner/runner.py,sha256=0re8QZXrbqYLWGo2mTh15R9PSvg5d7xTnHtIuJHu1Rg,7369
|
|
10
|
+
aiagent_runner-0.1.3.dist-info/METADATA,sha256=GHUpqrzBmmm3rIUsgYczgT18uWA1PvcLrd9vv8-S02w,5179
|
|
11
|
+
aiagent_runner-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
aiagent_runner-0.1.3.dist-info/entry_points.txt,sha256=UwbGpHt0NTjnTFlVQhcZLR2GWgG15v2r1n-OYvxHzK4,64
|
|
13
|
+
aiagent_runner-0.1.3.dist-info/RECORD,,
|