//! Session and thread model for turn-based agent interactions.
//!
//! A Session contains one or more Threads. Each Thread represents a
//! conversation/interaction sequence with the agent. Threads contain
//! Turns, which are request/response pairs.
//!
//! This model supports:
//! - Undo: Roll back to a previous turn
//! - Interrupt: Cancel the current turn mid-execution
//! - Compaction: Summarize old turns to save context
//! - Resume: Continue from a saved checkpoint
/// A session containing one or more threads.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
/// Unique session ID.
pub id: Uuid,
/// User ID that owns this session.
pub user_id: String,
/// Active thread ID.
pub active_thread: Option<Uuid>,
▎ active_thread 是 SessionManager 不维护的、"用户当前焦点"这个用户态概念的内存表示。 它不是路由优化,是用户隐式语义的承载——"用户没指定 thread_id 时该往哪发"这个问题的答案。SessionManager 的
▎ thread_map 处理显式路由,Session 内部的 active_thread 处理隐式焦点。
/// All threads in this session.
pub threads: HashMap<Uuid, Thread>,//threadId对应的具体会话内容
/// When the session was created.
pub created_at: DateTime<Utc>,
/// When the session was last active.
pub last_active_at: DateTime<Utc>,
/// Session metadata.
pub metadata: serde_json::Value,
/// Tools that have been auto-approved for this session ("always approve").
#[serde(default)]
pub auto_approved_tools: HashSet<String>,
}
3. thread(每个用户session可能会有多个会话)
/// A conversation thread within a session.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thread {
/// Unique thread ID.
pub id: Uuid,
/// Parent session ID.
pub session_id: Uuid,
/// Current state.
pub state: ThreadState,
/// Turns in this thread.
pub turns: Vec<Turn>,//所有消息的存储,有多轮
/// When the thread was created.
pub created_at: DateTime<Utc>,
/// When the thread was last updated.
pub updated_at: DateTime<Utc>,
/// Thread metadata (e.g., title, tags).
pub metadata: serde_json::Value,
/// Pending approval request (when state is AwaitingApproval).
#[serde(default)]
pub pending_approval: Option<PendingApproval>,
/// Pending auth token request (thread is in auth mode).
#[serde(default)]
pub pending_auth: Option<PendingAuth>,
/// Messages queued while the thread was processing a turn.
#[serde(default, skip_serializing_if = "VecDeque::is_empty")]
pub pending_messages: VecDeque<String>,pending_messages 是 agent 正在处理 turn 时新到达消息的 FIFO 队列,
避免用户在思考期间发的消息被丢弃——按到达顺序在当前 turn 完成后依次启动新 turn。
/// Channel that created this thread (for approval authorization).
#[serde(default)]
pub source_channel: Option<String>,
}
/// State of a thread.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ThreadState {
/// Thread is idle, waiting for input.
Idle,
/// Thread is processing a turn.
Processing,
/// Thread is waiting for user approval.
AwaitingApproval,
/// Thread has completed (no more turns expected).
Completed,
/// Thread was interrupted.
Interrupted,
}
/// Pending tool approval request stored on a thread.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingApproval {
/// Unique request ID.
pub request_id: Uuid,
/// Tool name requiring approval.
pub tool_name: String,
/// Tool parameters (original values, used for execution).
pub parameters: serde_json::Value,
/// Redacted tool parameters (sensitive values replaced with `[REDACTED]`).
/// Used for display in approval UI, logs, and SSE broadcasts.
#[serde(default)]
pub display_parameters: serde_json::Value,
/// Description of what the tool will do.
pub description: String,
/// Tool call ID from LLM (for proper context continuation).
pub tool_call_id: String,
/// Context messages at the time of the request (to resume from).
pub context_messages: Vec<ChatMessage>,
/// Remaining tool calls from the same assistant message that were not
/// executed yet when approval was requested.
#[serde(default)]
pub deferred_tool_calls: Vec<ToolCall>,
/// First actionable auth prompt already discovered in this turn. Persisted
/// so approval pauses do not drop the prompt before it can be surfaced.
#[serde(default)]
pub selected_auth_prompt: Option<PendingAuthPrompt>,
/// User timezone at the time the approval was requested, so it persists
/// through the approval flow even if the approval message lacks timezone.
#[serde(default)]
pub user_timezone: Option<String>,
/// Whether the "always" auto-approve option should be offered to the user.
/// `false` when the tool returned `ApprovalRequirement::Always` (e.g.
/// destructive shell commands), meaning every invocation must be confirmed.
#[serde(default = "default_true")]
pub allow_always: bool,
}
/// When `tool_auth` returns `awaiting_token`, the thread enters auth mode.
/// The next user message is intercepted before entering the normal pipeline
/// (no logging, no turn creation, no history) and routed directly to the
/// credential store.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingAuth {
/// Extension name to authenticate.
pub extension_name: ExtensionName,
/// When this auth mode was entered. Used for TTL expiry.
#[serde(default = "Utc::now")]
pub created_at: DateTime<Utc>,
}
4. turn
/// A single turn (request/response pair) in a thread.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Turn {
/// Turn number (0-indexed).
pub turn_number: usize,
/// Persisted user message ID when this turn has been written to the DB.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_message_id: Option<Uuid>,
/// User input that started this turn.
pub user_input: String,
/// Agent response (if completed).
pub response: Option<String>,
/// Tool calls made during this turn.
pub tool_calls: Vec<TurnToolCall>,
/// Turn state.
pub state: TurnState,Thread.state(不是 turn)是另一组状态:
Idle | Processing | AwaitingApproval | Completed | Interrupted——管的是整个 thread 当前能不能接受新 turn。
/// When the turn started.
pub started_at: DateTime<Utc>,
/// When the turn completed.
pub completed_at: Option<DateTime<Utc>>,
/// Error message (if failed).
pub error: Option<String>,
/// Agent's reasoning narrative for this turn.
/// Cleaned via `clean_response` and sanitized through `SafetyLayer` before storage.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub narrative: Option<String>,
/// Transient image content parts for multimodal LLM input.
/// Not serialized — images are only needed for the current LLM call.
/// The text description in `user_input` persists for compaction/context.
#[serde(skip)]
pub image_content_parts: Vec<ironclaw_llm::ContentPart>,
}
一个 Turn = 一轮完整的人机交互,从用户输入到 agent 给出最终回复。
turns[0] = Turn {
user_input: "帮我查东京天气",
response: Some("东京明天 25°C,晴..."),
tool_calls: vec![{tool: weather_lookup, args: {city: "tokyo"}, result: Ok(...)}],
state: TurnState::Complete,
}
turns[1] = Turn {
user_input: "还有大阪",
response: Some("大阪明天 22°C..."),
tool_calls: vec![{tool: weather_lookup, args: {city: "osaka"}, result: Ok(...)}],
state: TurnState::Complete,
}
/// State of a turn.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TurnState {
/// Turn is being processed.
Processing,
/// Turn completed successfully.
Completed,
/// Turn failed with an error.
Failed,
/// Turn was interrupted.
Interrupted,
}