tiny-agent 0.1.0

一个小而完整的 Rust LLM Agent 运行时:可中断、可恢复、可观测、可插拔的 agent loop / A small but complete LLM agent runtime in Rust — an interruptible, resumable, observable, pluggable agent loop.
Documentation
use crate::{
    core::{Agent, AgentRunTime, AgentRuntimeConfig},
    sandbox::Sandbox,
    tools::{ToolOutcome, ToolRegistry},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::sync::Arc;
use tokio_util::sync::CancellationToken;

#[derive(Debug, Deserialize, JsonSchema)]
pub struct SpawnAgentArgs {
    /// 交给子 agent 独立完成的任务。
    task: String,
}

#[derive(Debug, Serialize)]
struct SpawnAgentOutput {
    status: String,
    child_session_id: String,
    final_resp: Option<String>,
    detail: Value,
}

pub fn register_spawn_agent(registry: &mut ToolRegistry, child_config: AgentRuntimeConfig) {
    registry.register(
        "spawn_agent",
        "启动一个子 agent runtime 执行独立子任务。子 agent 使用独立会话历史,但共享父 agent 当前 sandbox,返回子会话 id 和最终结果",
        false,
        move |sandbox, args: SpawnAgentArgs| {
            let child_config = child_config.clone();
            async move { spawn_agent(child_config, sandbox, args).await }
        },
    );
}

async fn spawn_agent(
    mut child_config: AgentRuntimeConfig,
    parent_sandbox: Arc<dyn Sandbox>,
    args: SpawnAgentArgs,
) -> Result<ToolOutcome, String> {
    // sharedParent:子 agent 拥有独立 transcript/session,但工具副作用落在父 agent 当前 sandbox。
    child_config.sandbox = parent_sandbox;
    let runtime = AgentRunTime::from_config(child_config).map_err(|e| e.to_string())?;
    let child_session_id = runtime.create_session().await.map_err(|e| e.to_string())?;
    let ready = runtime
        .submit(&child_session_id, args.task)
        .await
        .map_err(|e| e.to_string())?;
    let outcome = runtime
        .run(ready, CancellationToken::new())
        .await
        .map_err(|e| e.to_string())?;

    let output = match outcome {
        Agent::Success(ctx) => SpawnAgentOutput {
            status: "success".to_string(),
            child_session_id,
            final_resp: ctx.final_resp,
            detail: json!({}),
        },
        Agent::Fail(_, failure) => SpawnAgentOutput {
            status: "failed".to_string(),
            child_session_id,
            final_resp: None,
            detail: json!({
                "kind": failure.kind.as_str(),
                "message": failure.message,
            }),
        },
        Agent::WaitingForUser(_, interaction) => SpawnAgentOutput {
            status: "waiting_for_user".to_string(),
            child_session_id,
            final_resp: None,
            detail: json!({
                "interaction": interaction,
            }),
        },
        Agent::Interrupted(_) => SpawnAgentOutput {
            status: "interrupted".to_string(),
            child_session_id,
            final_resp: None,
            detail: json!({}),
        },
        other => SpawnAgentOutput {
            status: "stopped".to_string(),
            child_session_id,
            final_resp: None,
            detail: json!({
                "state": format!("{other:?}"),
            }),
        },
    };
    let value = serde_json::to_value(output).map_err(|e| e.to_string())?;
    Ok(value.into())
}