Skip to main content

tiny_agent/tools/default_tools/
spawn_agent.rs

1use crate::{
2    core::{Agent, AgentRunTime, AgentRuntimeConfig},
3    sandbox::Sandbox,
4    tools::{ToolOutcome, ToolRegistry},
5};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::{Value, json};
9use std::sync::Arc;
10use tokio_util::sync::CancellationToken;
11
12#[derive(Debug, Deserialize, JsonSchema)]
13pub struct SpawnAgentArgs {
14    /// 交给子 agent 独立完成的任务。
15    task: String,
16}
17
18#[derive(Debug, Serialize)]
19struct SpawnAgentOutput {
20    status: String,
21    child_session_id: String,
22    final_resp: Option<String>,
23    detail: Value,
24}
25
26pub fn register_spawn_agent(registry: &mut ToolRegistry, child_config: AgentRuntimeConfig) {
27    registry.register(
28        "spawn_agent",
29        "启动一个子 agent runtime 执行独立子任务。子 agent 使用独立会话历史,但共享父 agent 当前 sandbox,返回子会话 id 和最终结果",
30        false,
31        move |sandbox, args: SpawnAgentArgs| {
32            let child_config = child_config.clone();
33            async move { spawn_agent(child_config, sandbox, args).await }
34        },
35    );
36}
37
38async fn spawn_agent(
39    mut child_config: AgentRuntimeConfig,
40    parent_sandbox: Arc<dyn Sandbox>,
41    args: SpawnAgentArgs,
42) -> Result<ToolOutcome, String> {
43    // sharedParent:子 agent 拥有独立 transcript/session,但工具副作用落在父 agent 当前 sandbox。
44    child_config.sandbox = parent_sandbox;
45    let runtime = AgentRunTime::from_config(child_config).map_err(|e| e.to_string())?;
46    let child_session_id = runtime.create_session().await.map_err(|e| e.to_string())?;
47    let ready = runtime
48        .submit(&child_session_id, args.task)
49        .await
50        .map_err(|e| e.to_string())?;
51    let outcome = runtime
52        .run(ready, CancellationToken::new())
53        .await
54        .map_err(|e| e.to_string())?;
55
56    let output = match outcome {
57        Agent::Success(ctx) => SpawnAgentOutput {
58            status: "success".to_string(),
59            child_session_id,
60            final_resp: ctx.final_resp,
61            detail: json!({}),
62        },
63        Agent::Fail(_, failure) => SpawnAgentOutput {
64            status: "failed".to_string(),
65            child_session_id,
66            final_resp: None,
67            detail: json!({
68                "kind": failure.kind.as_str(),
69                "message": failure.message,
70            }),
71        },
72        Agent::WaitingForUser(_, interaction) => SpawnAgentOutput {
73            status: "waiting_for_user".to_string(),
74            child_session_id,
75            final_resp: None,
76            detail: json!({
77                "interaction": interaction,
78            }),
79        },
80        Agent::Interrupted(_) => SpawnAgentOutput {
81            status: "interrupted".to_string(),
82            child_session_id,
83            final_resp: None,
84            detail: json!({}),
85        },
86        other => SpawnAgentOutput {
87            status: "stopped".to_string(),
88            child_session_id,
89            final_resp: None,
90            detail: json!({
91                "state": format!("{other:?}"),
92            }),
93        },
94    };
95    let value = serde_json::to_value(output).map_err(|e| e.to_string())?;
96    Ok(value.into())
97}