Skip to main content

tiny_agent/core/
runtime.rs

1use super::{Agent, AgentFailure, AgentRunTime, FailureKind, storage_failure};
2use crate::{
3    error::AgentError,
4    shared::{Message, Role},
5    state::AgentState,
6    trajectory::{TrajectoryEvent, TrajectoryEventKind},
7    transcript::Transcript,
8};
9use tokio_util::sync::CancellationToken;
10use uuid::Uuid;
11
12impl AgentRunTime {
13    /// 驱动一个 `Agent` 跑到停车点(终态或中断),并负责 **checkpoint 生命周期**:
14    ///
15    /// - `Success` / `Fail` 是终态,运行结束时清掉该会话残留的 checkpoint —— 既回收存储,
16    ///   也避免下次 [`resume`](Self::resume) 读到陈旧快照。
17    /// - `Interrupted` 的 checkpoint 由内层在停车时写入,这里**保留**,留给 `resume`。
18    pub async fn run(
19        &self,
20        agent_state: Agent,
21        cancellation_token: CancellationToken,
22    ) -> Result<Agent, AgentError> {
23        let session_id = self.get_session_id(&agent_state);
24        let session_lock = self.session_lock(&session_id).await;
25        let _guard = session_lock.lock().await;
26        self.run_unlocked(agent_state, cancellation_token).await
27    }
28
29    async fn run_unlocked(
30        &self,
31        agent_state: Agent,
32        cancellation_token: CancellationToken,
33    ) -> Result<Agent, AgentError> {
34        let outcome = self.run_inner(agent_state, cancellation_token).await?;
35        if matches!(outcome, Agent::Success(_) | Agent::Fail(_, _)) {
36            let session_id = self.get_session_id(&outcome);
37            if let Err(e) = self.checkpoint_storage.delete_checkpoint(&session_id).await {
38                return Ok(storage_failure(AgentState::new(Some(session_id)), e));
39            }
40        }
41        Ok(outcome)
42    }
43
44    /// 恢复被中断的会话:有 checkpoint 就从中断点接着跑,没有则什么都不做返回 `Ok(None)`。
45    ///
46    /// 这是与 [`submit`](Self::submit) 互补的入口 —— `resume` 续上**被中断的 in-flight 状态**,
47    /// `submit` 表示**用户发了新消息**(会丢弃尚未恢复的中断态)。
48    pub async fn resume(
49        &self,
50        session_id: &str,
51        cancellation_token: CancellationToken,
52    ) -> Result<Option<Agent>, AgentError> {
53        let session_lock = self.session_lock(session_id).await;
54        let _guard = session_lock.lock().await;
55        let checkpoint = match self.checkpoint_storage.get_checkpoint(session_id).await {
56            Ok(Some(checkpoint)) => checkpoint,
57            Ok(None) => return Ok(None),
58            Err(e) => {
59                return Ok(Some(storage_failure(
60                    AgentState::new(Some(session_id.to_string())),
61                    e,
62                )));
63            }
64        };
65        self.run_unlocked(checkpoint, cancellation_token)
66            .await
67            .map(Some)
68    }
69
70    /// 一站式入口:有 checkpoint 则恢复,否则从全新 `Ready` 开始,跑到停车点。
71    /// 适合"按 session_id 拉起一轮"的调用方,无需自己拼装 `Agent`。
72    pub async fn run_session(
73        &self,
74        session_id: &str,
75        cancellation_token: CancellationToken,
76    ) -> Result<Agent, AgentError> {
77        let session_lock = self.session_lock(session_id).await;
78        let _guard = session_lock.lock().await;
79        let start = match self.checkpoint_storage.get_checkpoint(session_id).await {
80            Ok(Some(checkpoint)) => checkpoint,
81            Ok(None) => Agent::Ready(AgentState::new(Some(session_id.to_string()))),
82            Err(e) => {
83                return Ok(storage_failure(
84                    AgentState::new(Some(session_id.to_string())),
85                    e,
86                ));
87            }
88        };
89        self.run_unlocked(start, cancellation_token).await
90    }
91
92    async fn run_inner(
93        &self,
94        mut agent_state: Agent,
95        cancellation_token: CancellationToken,
96    ) -> Result<Agent, AgentError> {
97        loop {
98            // ToolExecuting 仍有未应答的 tool_use,取消时不能在这里直接丢弃 ——
99            // 必须让它流进 execute_pending_tools,由那里为每个 pending call 补占位结果,
100            // 否则 transcript 会留下孤儿 tool_use。其余状态没有这个负担,可就地停车。
101            if cancellation_token.is_cancelled()
102                && !matches!(agent_state, Agent::ToolExecuting(_, _))
103            {
104                let session_id = self.get_session_id(&agent_state);
105                let parked = match agent_state {
106                    Agent::Reasoning(ctx) => Agent::Interrupted(ctx),
107                    other => other,
108                };
109                if let Err(e) = self
110                    .checkpoint_storage
111                    .as_ref()
112                    .save_checkpoint(&session_id, &parked)
113                    .await
114                {
115                    return Ok(storage_failure(AgentState::new(Some(session_id)), e));
116                }
117                self.emit(&session_id, TrajectoryEventKind::Interrupted)
118                    .await;
119                return Ok(parked);
120            }
121
122            agent_state = match agent_state {
123                Agent::Interrupted(ctx) => Agent::Ready(ctx),
124                Agent::Ready(mut ctx) => {
125                    // 迭代上限:每进入一次推理算一步。超限直接终态失败(不重试)
126                    if ctx.steps_count >= self.max_iter {
127                        let failure = AgentFailure::new(
128                            FailureKind::MaxIter,
129                            format!("exceeded max_iter ({})", self.max_iter),
130                        );
131                        self.emit(
132                            &ctx.session_id,
133                            TrajectoryEventKind::Failed {
134                                kind: failure.kind.as_str().to_string(),
135                                message: failure.message.clone(),
136                            },
137                        )
138                        .await;
139                        return Ok(Agent::Fail(ctx, failure));
140                    }
141                    ctx.steps_count += 1;
142                    Agent::Reasoning(ctx)
143                }
144                Agent::Reasoning(ctx) => {
145                    self.handle_reasoning(ctx, cancellation_token.clone())
146                        .await?
147                }
148                Agent::ToolExecuting(ctx, tool_calls) => {
149                    self.execute_pending_tools(&ctx, tool_calls, cancellation_token.clone())
150                        .await?
151                }
152                Agent::Fail(ctx, failure) => match self.handle_fail(ctx, failure).await? {
153                    ready @ Agent::Ready(_) => ready,
154                    terminal => return Ok(terminal),
155                },
156                other => return Ok(other),
157            }
158        }
159    }
160
161    pub async fn create_session(&self) -> Result<String, AgentError> {
162        let new_session_id = Uuid::new_v4().to_string();
163        let transcript = Transcript::new(&new_session_id);
164        self.transcript_storage
165            .save_transcript(&new_session_id, &transcript)
166            .await?;
167        let sandbox = self.sandbox.clone().open(&new_session_id).await?;
168        sandbox.save().await?;
169        Ok(new_session_id)
170    }
171
172    /// 框架对外的"用户说话"入口:把一条用户消息追加进会话历史,
173    /// 返回一个 `Ready` 状态交给 `run` 驱动一轮对话。
174    pub async fn submit(
175        &self,
176        session_id: &str,
177        user_text: impl Into<String>,
178    ) -> Result<Agent, AgentError> {
179        let session_lock = self.session_lock(session_id).await;
180        let _guard = session_lock.lock().await;
181        if let Err(e) = self.checkpoint_storage.delete_checkpoint(session_id).await {
182            return Ok(storage_failure(
183                AgentState::new(Some(session_id.to_string())),
184                e,
185            ));
186        }
187        let user_text = user_text.into();
188        if let Err(e) = self
189            .transcript_storage
190            .append_message(session_id, Message::text(Role::User, user_text.clone()))
191            .await
192        {
193            return Ok(storage_failure(
194                AgentState::new(Some(session_id.to_string())),
195                e,
196            ));
197        }
198        self.emit(
199            session_id,
200            TrajectoryEventKind::UserMessage { text: user_text },
201        )
202        .await;
203        Ok(Agent::Ready(AgentState::new(Some(session_id.to_string()))))
204    }
205
206    async fn session_lock(&self, session_id: &str) -> std::sync::Arc<tokio::sync::Mutex<()>> {
207        let mut locks = self.session_locks.lock().await;
208        locks.retain(|_, lock| lock.strong_count() > 0);
209        if let Some(lock) = locks.get(session_id).and_then(|lock| lock.upgrade()) {
210            return lock;
211        }
212        let lock = std::sync::Arc::new(tokio::sync::Mutex::new(()));
213        locks.insert(session_id.to_string(), std::sync::Arc::downgrade(&lock));
214        lock
215    }
216
217    pub(crate) fn get_session_id(&self, agent: &Agent) -> String {
218        match agent {
219            Agent::Fail(ctx, _)
220            | Agent::Interrupted(ctx)
221            | Agent::Ready(ctx)
222            | Agent::Reasoning(ctx)
223            | Agent::Success(ctx)
224            | Agent::ToolExecuting(ctx, _)
225            | Agent::WaitingForUser(ctx, _) => ctx.session_id.clone(),
226        }
227    }
228
229    /// 存档 checkpoint 并返回 `Interrupted`。调用前须确保所有 tool_use 已被应答,
230    /// 因此 checkpoint 不再携带可重跑的 pending calls,resume 时只会从 `Interrupted`
231    /// 走 `Ready → Reasoning`,不会重复执行工具。
232    pub(crate) async fn interrupt(&self, ctx: &AgentState) -> Result<Agent, AgentError> {
233        let agent = Agent::Interrupted(ctx.clone());
234        if let Err(e) = self
235            .checkpoint_storage
236            .as_ref()
237            .save_checkpoint(&ctx.session_id, &agent)
238            .await
239        {
240            return Ok(storage_failure(ctx.clone(), e));
241        }
242        self.emit(&ctx.session_id, TrajectoryEventKind::Interrupted)
243            .await;
244        Ok(agent)
245    }
246
247    pub(crate) async fn save_interruption(&self, agent: &Agent) -> Result<(), AgentError> {
248        let session_id = self.get_session_id(agent);
249        self.checkpoint_storage
250            .as_ref()
251            .save_checkpoint(&session_id, agent)
252            .await?;
253        self.emit(&session_id, TrajectoryEventKind::Interrupted)
254            .await;
255        Ok(())
256    }
257
258    /// 同步向轨迹 sink 投递一条事件。可观测性不阻断主流程,失败由 sink 内部吞掉。
259    pub(crate) async fn emit(&self, session_id: &str, kind: TrajectoryEventKind) {
260        self.trajectory
261            .emit(TrajectoryEvent::new(session_id, kind))
262            .await;
263    }
264}