Skip to content

Commit 3aea7a3

Browse files
hikejsclaude
andcommitted
feat: add Agent Orchestrator for main-sub agent coordination
- Implement event-driven orchestrator based on a3s-event - Add real-time monitoring of SubAgent lifecycle, state, and progress - Add dynamic control: pause, resume, cancel, adjust params, inject prompts - Add 11 event types for comprehensive monitoring - Add Python SDK bindings (Orchestrator, SubAgentConfig, SubAgentHandle) - Add 7 unit tests (1480 total tests passing) - Add comprehensive documentation and examples - Bump version to v1.0.4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent dc170dd commit 3aea7a3

24 files changed

Lines changed: 3052 additions & 5 deletions

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
---
99

10+
## [1.0.4] - 2026-03-05
11+
12+
### New Features
13+
14+
- **Agent Orchestrator** — Main-sub agent coordinator with real-time monitoring and dynamic control
15+
- Event-driven architecture based on a3s-event
16+
- Real-time monitoring of all SubAgent behaviors, planning, and execution
17+
- Dynamic control: pause, resume, cancel, adjust parameters, inject prompts
18+
- 11 event types: started, completed, state_changed, progress, tool_execution, control_signal, etc.
19+
- Pluggable communication: default memory-based, supports custom NATS provider
20+
- Concurrency management with configurable limits
21+
- **Python SDK Orchestrator bindings**`Orchestrator`, `SubAgentConfig`, `SubAgentHandle` classes
22+
- Full API support for spawning, monitoring, and controlling SubAgents
23+
- Event subscription and filtering
24+
- State management and result collection
25+
26+
### Improvements
27+
28+
- **Test coverage** — Added 7 orchestrator unit tests (1480 total tests passing)
29+
- **Documentation** — Added comprehensive orchestrator documentation in English and Chinese
30+
- **Examples** — Added Python examples for orchestrator usage and integration testing
31+
32+
---
33+
1034
## [1.0.2] - 2026-03-04
1135

1236
### New Features

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-core"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//! Orchestrator 使用示例
2+
3+
use a3s_code_core::orchestrator::{AgentOrchestrator, SubAgentConfig};
4+
5+
#[tokio::main]
6+
async fn main() -> anyhow::Result<()> {
7+
println!("=== AgentOrchestrator 示例 ===\n");
8+
9+
// 1. 创建 orchestrator (默认使用内存事件通讯)
10+
let orchestrator = AgentOrchestrator::new_memory();
11+
println!("✓ Orchestrator 已创建(使用内存事件通讯)\n");
12+
13+
// 2. 订阅所有事件
14+
let mut events = orchestrator.subscribe_all();
15+
println!("✓ 已订阅所有事件\n");
16+
17+
// 3. 在后台监控事件
18+
tokio::spawn(async move {
19+
println!("--- 事件监控开始 ---");
20+
while let Ok(event) = events.recv().await {
21+
println!("[Event] {} - {:?}", event.event_name(), event.subagent_id());
22+
}
23+
});
24+
25+
// 4. 启动第一个 SubAgent
26+
println!("启动 SubAgent 1...");
27+
let handle1 = orchestrator
28+
.spawn_subagent(
29+
SubAgentConfig::new("general", "Analyze Python files")
30+
.with_description("Count Python files")
31+
.with_permissive(true)
32+
.with_max_steps(5),
33+
)
34+
.await?;
35+
println!("✓ SubAgent 1 已启动: {}\n", handle1.id);
36+
37+
// 5. 启动第二个 SubAgent
38+
println!("启动 SubAgent 2...");
39+
let handle2 = orchestrator
40+
.spawn_subagent(
41+
SubAgentConfig::new("explore", "Find Rust files")
42+
.with_description("Count Rust files")
43+
.with_permissive(true)
44+
.with_max_steps(3),
45+
)
46+
.await?;
47+
println!("✓ SubAgent 2 已启动: {}\n", handle2.id);
48+
49+
// 6. 查询状态
50+
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
51+
println!("--- 当前状态 ---");
52+
let states = orchestrator.get_all_states().await;
53+
for (id, state) in &states {
54+
println!(" {}: {}", id, state);
55+
}
56+
println!();
57+
58+
// 7. 控制 SubAgent
59+
println!("暂停 SubAgent 1...");
60+
orchestrator.pause_subagent(&handle1.id).await?;
61+
println!("✓ SubAgent 1 已暂停\n");
62+
63+
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
64+
65+
println!("恢复 SubAgent 1...");
66+
orchestrator.resume_subagent(&handle1.id).await?;
67+
println!("✓ SubAgent 1 已恢复\n");
68+
69+
// 8. 等待完成
70+
println!("等待所有 SubAgent 完成...");
71+
let result1 = handle1.wait().await?;
72+
let result2 = handle2.wait().await?;
73+
74+
println!("\n--- 执行结果 ---");
75+
println!("SubAgent 1: {}", result1);
76+
println!("SubAgent 2: {}", result2);
77+
78+
// 9. 最终状态
79+
println!("\n--- 最终状态 ---");
80+
let final_states = orchestrator.get_all_states().await;
81+
for (id, state) in &final_states {
82+
println!(" {}: {}", id, state);
83+
}
84+
85+
println!("\n✓ 所有 SubAgent 已完成");
86+
println!("活跃数量: {}", orchestrator.active_count().await);
87+
88+
Ok(())
89+
}

core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub mod hooks;
6666
pub mod llm;
6767
pub mod mcp;
6868
pub mod memory;
69+
pub mod orchestrator;
6970
pub mod permissions;
7071
pub mod planning;
7172
pub(crate) mod prompts;

core/src/orchestrator/config.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! Orchestrator configuration
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
/// Orchestrator 配置
6+
#[derive(Debug, Clone)]
7+
pub struct OrchestratorConfig {
8+
/// 事件通道缓冲区大小
9+
pub event_buffer_size: usize,
10+
11+
/// 控制信号通道缓冲区大小
12+
pub control_buffer_size: usize,
13+
14+
/// SubAgent 最大并发数
15+
pub max_concurrent_subagents: usize,
16+
17+
/// 事件主题前缀
18+
pub subject_prefix: String,
19+
}
20+
21+
impl Default for OrchestratorConfig {
22+
fn default() -> Self {
23+
Self {
24+
event_buffer_size: 1000,
25+
control_buffer_size: 100,
26+
max_concurrent_subagents: 50,
27+
subject_prefix: "agent".to_string(),
28+
}
29+
}
30+
}
31+
32+
/// SubAgent 配置
33+
#[derive(Debug, Clone, Serialize, Deserialize)]
34+
pub struct SubAgentConfig {
35+
/// Agent 类型 (general, explore, plan, etc.)
36+
pub agent_type: String,
37+
38+
/// 任务描述
39+
pub description: String,
40+
41+
/// 执行提示词
42+
pub prompt: String,
43+
44+
/// 是否启用宽松模式(绕过 HITL)
45+
#[serde(default)]
46+
pub permissive: bool,
47+
48+
/// 最大执行步数
49+
pub max_steps: Option<usize>,
50+
51+
/// 执行超时(毫秒)
52+
pub timeout_ms: Option<u64>,
53+
54+
/// 父 SubAgent ID(用于嵌套)
55+
#[serde(skip_serializing_if = "Option::is_none")]
56+
pub parent_id: Option<String>,
57+
58+
/// 自定义元数据
59+
#[serde(default)]
60+
pub metadata: serde_json::Value,
61+
}
62+
63+
impl SubAgentConfig {
64+
/// 创建新的 SubAgent 配置
65+
pub fn new(agent_type: impl Into<String>, prompt: impl Into<String>) -> Self {
66+
Self {
67+
agent_type: agent_type.into(),
68+
description: String::new(),
69+
prompt: prompt.into(),
70+
permissive: false,
71+
max_steps: None,
72+
timeout_ms: None,
73+
parent_id: None,
74+
metadata: serde_json::Value::Null,
75+
}
76+
}
77+
78+
/// 设置描述
79+
pub fn with_description(mut self, description: impl Into<String>) -> Self {
80+
self.description = description.into();
81+
self
82+
}
83+
84+
/// 设置宽松模式
85+
pub fn with_permissive(mut self, permissive: bool) -> Self {
86+
self.permissive = permissive;
87+
self
88+
}
89+
90+
/// 设置最大步数
91+
pub fn with_max_steps(mut self, max_steps: usize) -> Self {
92+
self.max_steps = Some(max_steps);
93+
self
94+
}
95+
96+
/// 设置超时
97+
pub fn with_timeout_ms(mut self, timeout_ms: u64) -> Self {
98+
self.timeout_ms = Some(timeout_ms);
99+
self
100+
}
101+
102+
/// 设置父 ID
103+
pub fn with_parent_id(mut self, parent_id: impl Into<String>) -> Self {
104+
self.parent_id = Some(parent_id.into());
105+
self
106+
}
107+
108+
/// 设置元数据
109+
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
110+
self.metadata = metadata;
111+
self
112+
}
113+
}

core/src/orchestrator/control.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! 控制信号定义
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
/// 控制信号 - 主智能体发送给子智能体的指令
6+
#[derive(Debug, Clone, Serialize, Deserialize)]
7+
#[serde(tag = "type", rename_all = "snake_case")]
8+
pub enum ControlSignal {
9+
/// 暂停执行
10+
Pause,
11+
12+
/// 恢复执行
13+
Resume,
14+
15+
/// 取消执行
16+
Cancel,
17+
18+
/// 调整参数
19+
AdjustParams {
20+
#[serde(skip_serializing_if = "Option::is_none")]
21+
max_steps: Option<usize>,
22+
#[serde(skip_serializing_if = "Option::is_none")]
23+
timeout_ms: Option<u64>,
24+
},
25+
26+
/// 注入新提示词
27+
InjectPrompt {
28+
prompt: String,
29+
},
30+
}
31+
32+
impl std::fmt::Display for ControlSignal {
33+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34+
match self {
35+
ControlSignal::Pause => write!(f, "pause"),
36+
ControlSignal::Resume => write!(f, "resume"),
37+
ControlSignal::Cancel => write!(f, "cancel"),
38+
ControlSignal::AdjustParams { .. } => write!(f, "adjust_params"),
39+
ControlSignal::InjectPrompt { .. } => write!(f, "inject_prompt"),
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)