1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! In-memory retention limits for long-running sessions.
//!
//! The framework's in-memory stores
//! ([`InMemoryRunStore`](crate::run::InMemoryRunStore),
//! [`InMemoryTraceSink`](crate::trace::InMemoryTraceSink),
//! [`InMemorySubagentTaskTracker`](crate::subagent_task_tracker::InMemorySubagentTaskTracker))
//! use conservative finite defaults so sessions that live for hours or days
//! cannot grow these collections without bound.
//!
//! `SessionRetentionLimits` lets the host cap each store with a FIFO
//! policy. `None` for any field keeps that collection unbounded. Hosts that
//! deliberately need the legacy behavior can use
//! [`SessionRetentionLimits::unbounded`].
//!
//! All caps are **soft**: when a store hits its cap, the oldest entry
//! is dropped on insert. The framework never returns errors from cap
//! enforcement.
/// Per-session in-memory retention caps. Built via
/// [`SessionOptions::with_retention_limits`](crate::agent_api::SessionOptions::with_retention_limits)
/// or by constructing the struct directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SessionRetentionLimits {
/// Maximum number of runs retained in
/// [`InMemoryRunStore`](crate::run::InMemoryRunStore).
///
/// When a new run is created past this cap, the **oldest** run
/// (by insertion order) is dropped along with its events.
/// `None` keeps all runs.
pub max_runs_retained: Option<usize>,
/// Maximum number of event records retained per run in
/// [`InMemoryRunStore`](crate::run::InMemoryRunStore).
///
/// When a run accumulates more events than this, the oldest
/// events are FIFO-dropped. The run snapshot's `event_count`
/// is **not** decremented — it remains the total ever recorded.
/// `None` keeps all events.
pub max_events_per_run: Option<usize>,
/// Maximum serialized bytes retained for event records in each run.
///
/// This cap is enforced together with [`Self::max_events_per_run`].
/// Oldest records are FIFO-dropped until both limits are satisfied. A
/// single record larger than the cap is therefore not retained, while
/// the run's cumulative `event_count` and state transition still advance.
/// `None` does not impose a byte limit.
pub max_event_bytes_per_run: Option<usize>,
/// Maximum number of events retained in
/// [`InMemoryTraceSink`](crate::trace::InMemoryTraceSink).
///
/// When the sink reaches this cap, the oldest event is dropped
/// on each new write. `None` keeps all events.
pub max_trace_events: Option<usize>,
/// Maximum number of **terminal** (Completed / Failed / Cancelled)
/// subagent task snapshots retained in
/// [`InMemorySubagentTaskTracker`](crate::subagent_task_tracker::InMemorySubagentTaskTracker).
/// Running tasks are never dropped.
///
/// When the count of terminal entries exceeds this cap, the
/// oldest terminal entry (by completion time) is dropped.
/// `None` keeps all terminal entries.
pub max_terminal_subagent_tasks: Option<usize>,
}
impl SessionRetentionLimits {
/// Convenience builder.
pub fn new() -> Self {
Self::default()
}
/// Explicitly opt into unbounded in-memory retention for every store.
pub const fn unbounded() -> Self {
Self {
max_runs_retained: None,
max_events_per_run: None,
max_event_bytes_per_run: None,
max_trace_events: None,
max_terminal_subagent_tasks: None,
}
}
pub fn with_max_runs(mut self, n: usize) -> Self {
self.max_runs_retained = Some(n);
self
}
pub fn with_max_events_per_run(mut self, n: usize) -> Self {
self.max_events_per_run = Some(n);
self
}
pub fn with_max_event_bytes_per_run(mut self, n: usize) -> Self {
self.max_event_bytes_per_run = Some(n);
self
}
pub fn with_max_trace_events(mut self, n: usize) -> Self {
self.max_trace_events = Some(n);
self
}
pub fn with_max_terminal_subagent_tasks(mut self, n: usize) -> Self {
self.max_terminal_subagent_tasks = Some(n);
self
}
}
impl Default for SessionRetentionLimits {
fn default() -> Self {
Self {
max_runs_retained: Some(64),
max_events_per_run: Some(2_048),
max_event_bytes_per_run: Some(8 * 1024 * 1024),
max_trace_events: Some(8_192),
max_terminal_subagent_tasks: Some(512),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_finite_and_unbounded_is_explicit() {
let defaults = SessionRetentionLimits::default();
assert!(defaults.max_runs_retained.is_some());
assert!(defaults.max_events_per_run.is_some());
assert!(defaults.max_event_bytes_per_run.is_some());
assert!(defaults.max_trace_events.is_some());
assert!(defaults.max_terminal_subagent_tasks.is_some());
assert_eq!(
SessionRetentionLimits::unbounded(),
SessionRetentionLimits {
max_runs_retained: None,
max_events_per_run: None,
max_event_bytes_per_run: None,
max_trace_events: None,
max_terminal_subagent_tasks: None,
}
);
}
}