feat(agents): Phase 7-10 hook system, plugins, skills, orchestration
Phase 7: Built-in Hooks (audit_log, dangerous_confirmation, security_scan) Phase 8: Plugin system (PluginManager, PluginSandbox, PluginManifest) Phase 9: Skills registry (SkillRegistry, local/plugin/MCP loaders) Phase 10: TeamLeader, RemoteTransport, BackgroundTaskManager
This commit is contained in:
@@ -0,0 +1,637 @@
|
||||
# Phase 10:高级编排(Advanced Orchestration)
|
||||
|
||||
日期:2026-04-04
|
||||
状态:待开始
|
||||
前置依赖:Phase 6-9(工具系统、Hook、插件、Skills)
|
||||
Demo参考:claw-code-main — assistant/, cli/, structuredIO.ts, remoteIO.ts
|
||||
|
||||
---
|
||||
|
||||
## 1. 阶段目标
|
||||
|
||||
实现**高级 Agent 编排能力**,包括:
|
||||
- Team 多 Agent 协作
|
||||
- 远程/结构化传输
|
||||
- 高级会话管理
|
||||
- 后台任务系统
|
||||
|
||||
这是 claw-code 与 Jarvis 架构差距最大的地方,也是最复杂的功能。
|
||||
|
||||
---
|
||||
|
||||
## 2. Team 多 Agent 协作
|
||||
|
||||
### 2.1 TeamLeader
|
||||
|
||||
```python
|
||||
# backend/app/agents/team/leader.py
|
||||
|
||||
@dataclass
|
||||
class TeamMember:
|
||||
"""团队成员"""
|
||||
agent_id: str
|
||||
role: str
|
||||
capabilities: list[str]
|
||||
status: AgentStatus
|
||||
current_task: str | None = None
|
||||
|
||||
@dataclass
|
||||
class TeamTask:
|
||||
"""团队任务"""
|
||||
task_id: str
|
||||
description: str
|
||||
parent_id: str | None = None
|
||||
assignee: str | None = None
|
||||
status: TaskStatus
|
||||
dependencies: list[str] = field(default_factory=list)
|
||||
result: Any = None
|
||||
|
||||
class TeamLeader:
|
||||
"""
|
||||
团队领导者
|
||||
|
||||
负责协调多个 Agent 的协作
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
team_id: str,
|
||||
tool_registry: ToolRegistry,
|
||||
hook_manager: HookManager
|
||||
):
|
||||
self.team_id = team_id
|
||||
self.tool_registry = tool_registry
|
||||
self.hook_manager = hook_manager
|
||||
|
||||
self.members: dict[str, TeamMember] = {}
|
||||
self.tasks: dict[str, TeamTask] = {}
|
||||
self.task_queue: asyncio.Queue = asyncio.Queue()
|
||||
|
||||
async def create_team(
|
||||
self,
|
||||
config: TeamConfig
|
||||
) -> str:
|
||||
"""创建团队"""
|
||||
self.team_id = config.team_id
|
||||
|
||||
# 创建团队成员
|
||||
for member_config in config.members:
|
||||
member = TeamMember(
|
||||
agent_id=member_config.agent_id,
|
||||
role=member_config.role,
|
||||
capabilities=member_config.capabilities,
|
||||
status=AgentStatus.IDLE
|
||||
)
|
||||
self.members[member.agent_id] = member
|
||||
|
||||
return self.team_id
|
||||
|
||||
async def assign_task(
|
||||
self,
|
||||
task: TeamTask
|
||||
) -> str:
|
||||
"""分配任务"""
|
||||
self.tasks[task.task_id] = task
|
||||
|
||||
# 找到最合适的成员
|
||||
assignee = await self._find_assignee(task)
|
||||
|
||||
if assignee:
|
||||
task.assignee = assignee.agent_id
|
||||
assignee.current_task = task.task_id
|
||||
assignee.status = AgentStatus.WORKING
|
||||
|
||||
return task.task_id
|
||||
|
||||
async def _find_assignee(
|
||||
self,
|
||||
task: TeamTask
|
||||
) -> TeamMember | None:
|
||||
"""找到最适合执行任务的成员"""
|
||||
# 过滤掉忙碌的成员
|
||||
available = [
|
||||
m for m in self.members.values()
|
||||
if m.status == AgentStatus.IDLE
|
||||
]
|
||||
|
||||
if not available:
|
||||
return None
|
||||
|
||||
# 按能力匹配
|
||||
for cap in task.required_capabilities:
|
||||
matches = [m for m in available if cap in m.capabilities]
|
||||
if matches:
|
||||
return matches[0]
|
||||
|
||||
return available[0] if available else None
|
||||
|
||||
async def broadcast_task(
|
||||
self,
|
||||
description: str,
|
||||
required_capabilities: list[str]
|
||||
) -> list[str]:
|
||||
"""广播任务给所有符合条件的成员"""
|
||||
task_ids = []
|
||||
|
||||
for member in self.members.values():
|
||||
if member.status != AgentStatus.IDLE:
|
||||
continue
|
||||
|
||||
# 检查能力匹配
|
||||
if not any(cap in member.capabilities for cap in required_capabilities):
|
||||
continue
|
||||
|
||||
# 创建任务
|
||||
task = TeamTask(
|
||||
task_id=generate_id(),
|
||||
description=description,
|
||||
assignee=member.agent_id,
|
||||
status=TaskStatus.PENDING
|
||||
)
|
||||
|
||||
await self.assign_task(task)
|
||||
task_ids.append(task.task_id)
|
||||
|
||||
return task_ids
|
||||
|
||||
async def collect_results(
|
||||
self,
|
||||
task_ids: list[str],
|
||||
timeout: float = 300
|
||||
) -> dict[str, Any]:
|
||||
"""收集任务结果"""
|
||||
results = {}
|
||||
start_time = time.time()
|
||||
|
||||
while len(results) < len(task_ids):
|
||||
if time.time() - start_time > timeout:
|
||||
break
|
||||
|
||||
for task_id in task_ids:
|
||||
if task_id in results:
|
||||
continue
|
||||
|
||||
task = self.tasks.get(task_id)
|
||||
if task and task.status == TaskStatus.COMPLETED:
|
||||
results[task_id] = task.result
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
return results
|
||||
|
||||
async def get_team_status(self) -> TeamStatus:
|
||||
"""获取团队状态"""
|
||||
return TeamStatus(
|
||||
team_id=self.team_id,
|
||||
members={
|
||||
agent_id: {
|
||||
"role": m.role,
|
||||
"status": m.status.value,
|
||||
"current_task": m.current_task
|
||||
}
|
||||
for agent_id, m in self.members.items()
|
||||
},
|
||||
active_tasks=sum(1 for t in self.tasks.values() if t.status == TaskStatus.PENDING),
|
||||
completed_tasks=sum(1 for t in self.tasks.values() if t.status == TaskStatus.COMPLETED)
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 远程传输层
|
||||
|
||||
### 3.1 StructuredIO
|
||||
|
||||
```python
|
||||
# backend/app/agents/transport/structured_io.py
|
||||
|
||||
class StructuredIO:
|
||||
"""
|
||||
结构化 IO
|
||||
|
||||
支持结构化的输入输出格式
|
||||
"""
|
||||
|
||||
async def send_response(
|
||||
self,
|
||||
channel: str,
|
||||
data: dict
|
||||
):
|
||||
"""发送结构化响应"""
|
||||
message = {
|
||||
"channel": channel,
|
||||
"type": "structured",
|
||||
"data": data,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
await self.transport.send(message)
|
||||
|
||||
async def send_event(
|
||||
self,
|
||||
event_type: str,
|
||||
payload: dict
|
||||
):
|
||||
"""发送事件"""
|
||||
message = {
|
||||
"type": "event",
|
||||
"event": event_type,
|
||||
"payload": payload,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
await self.transport.send(message)
|
||||
|
||||
async def send_tool_call(
|
||||
self,
|
||||
tool_name: str,
|
||||
arguments: dict,
|
||||
call_id: str
|
||||
):
|
||||
"""发送工具调用"""
|
||||
message = {
|
||||
"type": "tool_call",
|
||||
"tool": tool_name,
|
||||
"args": arguments,
|
||||
"call_id": call_id,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
await self.transport.send(message)
|
||||
|
||||
async def receive(self) -> dict:
|
||||
"""接收消息"""
|
||||
raw = await self.transport.receive()
|
||||
return self._parse(raw)
|
||||
|
||||
def _parse(self, raw: bytes) -> dict:
|
||||
"""解析消息"""
|
||||
# 支持 JSON 和流式格式
|
||||
pass
|
||||
```
|
||||
|
||||
### 3.2 RemoteTransport
|
||||
|
||||
```python
|
||||
# backend/app/agents/transport/remote.py
|
||||
|
||||
class RemoteTransport:
|
||||
"""
|
||||
远程传输
|
||||
|
||||
支持远程 Agent 通信
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
endpoint: str,
|
||||
auth_token: str
|
||||
):
|
||||
self.endpoint = endpoint
|
||||
self.auth_token = auth_token
|
||||
self.websocket: WebSocket | None = None
|
||||
|
||||
async def connect(self):
|
||||
"""建立连接"""
|
||||
self.websocket = await websockets.connect(
|
||||
self.endpoint,
|
||||
extra_headers={"Authorization": f"Bearer {self.auth_token}"}
|
||||
)
|
||||
|
||||
async def send(self, message: dict):
|
||||
"""发送消息"""
|
||||
if not self.websocket:
|
||||
await self.connect()
|
||||
|
||||
await self.websocket.send(json.dumps(message))
|
||||
|
||||
async def receive(self) -> dict:
|
||||
"""接收消息"""
|
||||
if not self.websocket:
|
||||
await self.connect()
|
||||
|
||||
raw = await self.websocket.recv()
|
||||
return json.loads(raw)
|
||||
|
||||
async def close(self):
|
||||
"""关闭连接"""
|
||||
if self.websocket:
|
||||
await self.websocket.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 高级会话管理
|
||||
|
||||
### 4.1 AgentSession
|
||||
|
||||
```python
|
||||
# backend/app/agents/session/manager.py
|
||||
|
||||
@dataclass
|
||||
class SessionConfig:
|
||||
"""会话配置"""
|
||||
session_id: str
|
||||
user_id: str
|
||||
parent_session_id: str | None = None
|
||||
|
||||
max_rounds: int = 50
|
||||
max_tokens: int = 100000
|
||||
timeout: float = 3600
|
||||
|
||||
isolation_mode: IsolationMode = IsolationMode.NONE
|
||||
|
||||
capabilities: list[str] = field(default_factory=list)
|
||||
permissions: list[str] = field(default_factory=list)
|
||||
|
||||
class AgentSession:
|
||||
"""
|
||||
Agent 会话
|
||||
|
||||
管理会话的生命周期和状态
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: SessionConfig,
|
||||
agent_runtime: AgentRuntime
|
||||
):
|
||||
self.config = config
|
||||
self.agent_runtime = agent_runtime
|
||||
|
||||
self.state: SessionState = SessionState.INITIALIZING
|
||||
self.round_count: int = 0
|
||||
self.token_count: int = 0
|
||||
|
||||
self.messages: list[Message] = []
|
||||
self.tool_calls: list[ToolCall] = []
|
||||
self.events: list[SessionEvent] = []
|
||||
|
||||
async def initialize(self) -> str:
|
||||
"""初始化会话"""
|
||||
# 创建会话记录
|
||||
await self._create_session_record()
|
||||
|
||||
# 初始化隔离环境
|
||||
if self.config.isolation_mode != IsolationMode.NONE:
|
||||
await self._initialize_isolation()
|
||||
|
||||
self.state = SessionState.ACTIVE
|
||||
return self.config.session_id
|
||||
|
||||
async def process_message(
|
||||
self,
|
||||
message: str
|
||||
) -> Response:
|
||||
"""处理消息"""
|
||||
if self.state != SessionState.ACTIVE:
|
||||
raise SessionStateError(f"Session not active: {self.state}")
|
||||
|
||||
# 检查轮次限制
|
||||
self.round_count += 1
|
||||
if self.round_count > self.config.max_rounds:
|
||||
raise SessionLimitError("Max rounds exceeded")
|
||||
|
||||
# 处理消息
|
||||
response = await self.agent_runtime.process(
|
||||
message,
|
||||
context=self._get_context()
|
||||
)
|
||||
|
||||
# 记录
|
||||
self.messages.append(Message(role="user", content=message))
|
||||
self.messages.append(Message(role="assistant", content=response.content))
|
||||
|
||||
# 更新 token 计数
|
||||
self.token_count += response.usage.total_tokens
|
||||
|
||||
return response
|
||||
|
||||
async def spawn_child_session(
|
||||
self,
|
||||
config: SessionConfig
|
||||
) -> "AgentSession":
|
||||
"""创建子会话"""
|
||||
child_config = SessionConfig(
|
||||
**{
|
||||
**asdict(config),
|
||||
"parent_session_id": self.config.session_id
|
||||
}
|
||||
)
|
||||
|
||||
child_session = AgentSession(
|
||||
config=child_config,
|
||||
agent_runtime=self.agent_runtime
|
||||
)
|
||||
|
||||
await child_session.initialize()
|
||||
|
||||
return child_session
|
||||
|
||||
async def get_session_summary(self) -> SessionSummary:
|
||||
"""获取会话摘要"""
|
||||
return SessionSummary(
|
||||
session_id=self.config.session_id,
|
||||
user_id=self.config.user_id,
|
||||
round_count=self.round_count,
|
||||
token_count=self.token_count,
|
||||
message_count=len(self.messages),
|
||||
tool_call_count=len(self.tool_calls),
|
||||
duration_seconds=(datetime.now() - self.start_time).total_seconds(),
|
||||
state=self.state.value
|
||||
)
|
||||
|
||||
async def persist(self):
|
||||
"""持久化会话"""
|
||||
await self.session_service.save(SessionRecord(
|
||||
session_id=self.config.session_id,
|
||||
user_id=self.config.user_id,
|
||||
state=self.state.value,
|
||||
round_count=self.round_count,
|
||||
token_count=self.token_count,
|
||||
messages=self.messages,
|
||||
tool_calls=self.tool_calls,
|
||||
events=self.events
|
||||
))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 后台任务系统
|
||||
|
||||
### 5.1 BackgroundTaskManager
|
||||
|
||||
```python
|
||||
# backend/app/agents/background/manager.py
|
||||
|
||||
@dataclass
|
||||
class BackgroundTask:
|
||||
"""后台任务"""
|
||||
task_id: str
|
||||
description: str
|
||||
agent_config: dict
|
||||
schedule: str | None = None # cron expression
|
||||
|
||||
status: BackgroundTaskStatus
|
||||
created_at: datetime
|
||||
started_at: datetime | None = None
|
||||
completed_at: datetime | None = None
|
||||
|
||||
result: Any = None
|
||||
error: str | None = None
|
||||
|
||||
class BackgroundTaskManager:
|
||||
"""
|
||||
后台任务管理器
|
||||
|
||||
管理长期运行的 Agent 任务
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
task_store: TaskStore,
|
||||
agent_factory: AgentFactory
|
||||
):
|
||||
self.task_store = task_store
|
||||
self.agent_factory = agent_factory
|
||||
|
||||
self._running_tasks: dict[str, asyncio.Task] = {}
|
||||
self._scheduler = AsyncIOScheduler()
|
||||
|
||||
async def submit_task(
|
||||
self,
|
||||
task: BackgroundTask
|
||||
) -> str:
|
||||
"""提交后台任务"""
|
||||
# 保存任务记录
|
||||
await self.task_store.save(task)
|
||||
|
||||
# 如果有定时计划,调度任务
|
||||
if task.schedule:
|
||||
self._scheduler.add_job(
|
||||
self._execute_task,
|
||||
CronTrigger.from_crontab(task.schedule),
|
||||
args=[task.task_id]
|
||||
)
|
||||
else:
|
||||
# 立即执行
|
||||
asyncio.create_task(self._execute_task(task.task_id))
|
||||
|
||||
return task.task_id
|
||||
|
||||
async def _execute_task(self, task_id: str):
|
||||
"""执行任务"""
|
||||
task = await self.task_store.get(task_id)
|
||||
if not task:
|
||||
return
|
||||
|
||||
self._running_tasks[task_id] = asyncio.current_task()
|
||||
|
||||
task.status = BackgroundTaskStatus.RUNNING
|
||||
task.started_at = datetime.now()
|
||||
await self.task_store.save(task)
|
||||
|
||||
try:
|
||||
# 创建 Agent
|
||||
agent = await self.agent_factory.create(task.agent_config)
|
||||
|
||||
# 执行
|
||||
result = await agent.run()
|
||||
|
||||
task.status = BackgroundTaskStatus.COMPLETED
|
||||
task.result = result
|
||||
|
||||
except Exception as e:
|
||||
task.status = BackgroundTaskStatus.FAILED
|
||||
task.error = str(e)
|
||||
|
||||
finally:
|
||||
task.completed_at = datetime.now()
|
||||
await self.task_store.save(task)
|
||||
|
||||
if task_id in self._running_tasks:
|
||||
del self._running_tasks[task_id]
|
||||
|
||||
async def cancel_task(self, task_id: str) -> bool:
|
||||
"""取消任务"""
|
||||
if task_id in self._running_tasks:
|
||||
self._running_tasks[task_id].cancel()
|
||||
del self._running_tasks[task_id]
|
||||
|
||||
task = await self.task_store.get(task_id)
|
||||
if task:
|
||||
task.status = BackgroundTaskStatus.CANCELLED
|
||||
await self.task_store.save(task)
|
||||
|
||||
return True
|
||||
|
||||
async def get_task_status(self, task_id: str) -> BackgroundTask | None:
|
||||
"""获取任务状态"""
|
||||
return await self.task_store.get(task_id)
|
||||
|
||||
async def list_tasks(
|
||||
self,
|
||||
user_id: str,
|
||||
status: BackgroundTaskStatus | None = None
|
||||
) -> list[BackgroundTask]:
|
||||
"""列出任务"""
|
||||
return await self.task_store.list(user_id, status)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 文件结构
|
||||
|
||||
```
|
||||
backend/app/agents/
|
||||
├── team/ # Team 协作
|
||||
│ ├── __init__.py
|
||||
│ ├── leader.py # 团队领导
|
||||
│ ├── member.py # 团队成员
|
||||
│ └── task.py # 团队任务
|
||||
│
|
||||
├── transport/ # 传输层
|
||||
│ ├── __init__.py
|
||||
│ ├── structured_io.py # 结构化 IO
|
||||
│ ├── remote.py # 远程传输
|
||||
│ └── websocket.py # WebSocket 传输
|
||||
│
|
||||
├── session/ # 会话管理
|
||||
│ ├── __init__.py
|
||||
│ ├── manager.py # 会话管理器
|
||||
│ ├── context.py # 会话上下文
|
||||
│ └── persistence.py # 会话持久化
|
||||
│
|
||||
├── background/ # 后台任务
|
||||
│ ├── __init__.py
|
||||
│ ├── manager.py # 后台任务管理器
|
||||
│ ├── scheduler.py # 任务调度
|
||||
│ └── executor.py # 任务执行器
|
||||
│
|
||||
└── coordinator.py # 协调整合(现有)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 验收标准
|
||||
|
||||
| 检查点 | 标准 |
|
||||
|--------|------|
|
||||
| Team 创建 | 可以创建和管理 Agent 团队 |
|
||||
| Team 任务分配 | 任务能正确分配给合适的成员 |
|
||||
| Team 结果收集 | 能收集和聚合多成员的结果 |
|
||||
| 结构化 IO | 支持结构化的输入输出格式 |
|
||||
| 远程传输 | 支持远程 Agent 通信 |
|
||||
| 会话管理 | 支持复杂的会话层级和状态管理 |
|
||||
| 后台任务 | 支持定时和异步后台任务 |
|
||||
| 子会话 | 支持从父会话创建子会话 |
|
||||
|
||||
---
|
||||
|
||||
## 8. Demo 借鉴
|
||||
|
||||
| claw-code | Jarvis 对应 |
|
||||
|-----------|------------|
|
||||
| `src/assistant/sessionHistory.ts` | `session/manager.py` |
|
||||
| `src/cli/structuredIO.ts` | `transport/structured_io.py` |
|
||||
| `src/cli/remoteIO.ts` | `transport/remote.py` |
|
||||
| `src/cli/transports/*` | `transport/` |
|
||||
| Team/* tools | `team/leader.py` |
|
||||
| Background tasks | `background/manager.py` |
|
||||
Reference in New Issue
Block a user