Skip to main content

tiny_agent/tools/default_tools/
spawn_agent.rs

1use crate::{
2    core::{Agent, AgentRunTime, AgentRuntimeConfig},
3    sandbox::Sandbox,
4    tools::{ToolCtx, 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_with_ctx(
28        "spawn_agent",
29        "启动一个子 agent runtime 执行独立子任务。子 agent 使用独立会话历史,但共享父 agent 当前 sandbox;\
30         子会话会登记为父会话的 subsession,返回子会话 id 和最终结果",
31        false,
32        move |sandbox, args: SpawnAgentArgs, ctx: ToolCtx| {
33            let child_config = child_config.clone();
34            let parent_session_id = ctx.session_id().to_string();
35            // 从父 run 的取消令牌派生子 token:父任务被取消时子 agent 也一并干净取消。
36            let cancellation = ctx.cancellation_token().child_token();
37            async move {
38                spawn_agent(child_config, sandbox, args, parent_session_id, cancellation).await
39            }
40        },
41    );
42}
43
44async fn spawn_agent(
45    mut child_config: AgentRuntimeConfig,
46    parent_sandbox: Arc<dyn Sandbox>,
47    args: SpawnAgentArgs,
48    parent_session_id: String,
49    cancellation: CancellationToken,
50) -> Result<ToolOutcome, String> {
51    // sharedParent:子 agent 拥有独立 transcript/session,但工具副作用落在父 agent 当前 sandbox。
52    child_config.sandbox = parent_sandbox;
53    let runtime = AgentRunTime::from_config(child_config).map_err(|e| e.to_string())?;
54    // 把子会话挂回发起它的父会话(ctx.session_id),便于按 turn 下钻。
55    let child_session_id = runtime
56        .create_subsession(&parent_session_id)
57        .await
58        .map_err(|e| e.to_string())?;
59    let ready = runtime
60        .submit(&child_session_id, args.task)
61        .await
62        .map_err(|e| e.to_string())?;
63    let outcome = runtime
64        .run(ready, cancellation)
65        .await
66        .map_err(|e| e.to_string())?;
67
68    let output = match outcome {
69        Agent::Success(ctx) => SpawnAgentOutput {
70            status: "success".to_string(),
71            child_session_id,
72            final_resp: ctx.final_resp,
73            detail: json!({}),
74        },
75        Agent::Fail(_, failure) => SpawnAgentOutput {
76            status: "failed".to_string(),
77            child_session_id,
78            final_resp: None,
79            detail: json!({
80                "kind": failure.kind.as_str(),
81                "message": failure.message,
82            }),
83        },
84        Agent::WaitingForUser(_, interaction) => SpawnAgentOutput {
85            status: "waiting_for_user".to_string(),
86            child_session_id,
87            final_resp: None,
88            detail: json!({
89                "interaction": interaction,
90            }),
91        },
92        Agent::Interrupted(_) => SpawnAgentOutput {
93            status: "interrupted".to_string(),
94            child_session_id,
95            final_resp: None,
96            detail: json!({}),
97        },
98        other => SpawnAgentOutput {
99            status: "stopped".to_string(),
100            child_session_id,
101            final_resp: None,
102            detail: json!({
103                "state": format!("{other:?}"),
104            }),
105        },
106    };
107    let value = serde_json::to_value(output).map_err(|e| e.to_string())?;
108    Ok(value.into())
109}