1use serde::Serialize;
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
13pub struct RuntimeLimits {
14 pub max_vm_frames: usize,
16 pub max_template_include_depth: usize,
18 pub max_template_parse_cache_entries: usize,
20 pub max_file_text_cache_entries: usize,
22 pub max_json_parse_cache_entries: usize,
24 pub max_std_cache_entries: usize,
26 pub max_regex_cache_entries: usize,
28 pub max_schema_pattern_cache_entries: usize,
30 pub max_schema_guard_cache_entries: usize,
32 pub default_event_log_queue_depth: usize,
34 pub max_agent_sessions: usize,
36 pub max_project_fingerprint_depth: usize,
38 pub max_project_enrich_yaml_depth: usize,
40 pub max_template_ast_depth: usize,
42 pub max_constant_folded_collection_items: usize,
44 pub max_constant_folded_string_bytes: usize,
46 pub max_nested_execution_depth: usize,
48 pub max_schema_nudge_depth: usize,
50 pub max_schema_nudge_lines: usize,
52 pub max_schema_nudge_keys: usize,
54}
55
56impl RuntimeLimits {
57 pub const DEFAULT: Self = Self {
59 max_vm_frames: 512,
60 max_template_include_depth: 32,
61 max_template_parse_cache_entries: 128,
62 max_file_text_cache_entries: 256,
63 max_json_parse_cache_entries: 128,
64 max_std_cache_entries: 256,
65 max_regex_cache_entries: 128,
66 max_schema_pattern_cache_entries: 256,
67 max_schema_guard_cache_entries: 256,
68 default_event_log_queue_depth: 128,
69 max_agent_sessions: 128,
70 max_project_fingerprint_depth: 4,
71 max_project_enrich_yaml_depth: 128,
72 max_template_ast_depth: 128,
73 max_constant_folded_collection_items: 4_096,
74 max_constant_folded_string_bytes: 64 * 1024,
75 max_nested_execution_depth: 8,
76 max_schema_nudge_depth: 3,
77 max_schema_nudge_lines: 8,
78 max_schema_nudge_keys: 16,
79 };
80
81 pub fn value(&self, name: &str) -> Option<usize> {
83 Some(match name {
84 "max_vm_frames" => self.max_vm_frames,
85 "max_template_include_depth" => self.max_template_include_depth,
86 "max_template_parse_cache_entries" => self.max_template_parse_cache_entries,
87 "max_file_text_cache_entries" => self.max_file_text_cache_entries,
88 "max_json_parse_cache_entries" => self.max_json_parse_cache_entries,
89 "max_std_cache_entries" => self.max_std_cache_entries,
90 "max_regex_cache_entries" => self.max_regex_cache_entries,
91 "max_schema_pattern_cache_entries" => self.max_schema_pattern_cache_entries,
92 "max_schema_guard_cache_entries" => self.max_schema_guard_cache_entries,
93 "default_event_log_queue_depth" => self.default_event_log_queue_depth,
94 "max_agent_sessions" => self.max_agent_sessions,
95 "max_project_fingerprint_depth" => self.max_project_fingerprint_depth,
96 "max_project_enrich_yaml_depth" => self.max_project_enrich_yaml_depth,
97 "max_template_ast_depth" => self.max_template_ast_depth,
98 "max_constant_folded_collection_items" => self.max_constant_folded_collection_items,
99 "max_constant_folded_string_bytes" => self.max_constant_folded_string_bytes,
100 "max_nested_execution_depth" => self.max_nested_execution_depth,
101 "max_schema_nudge_depth" => self.max_schema_nudge_depth,
102 "max_schema_nudge_lines" => self.max_schema_nudge_lines,
103 "max_schema_nudge_keys" => self.max_schema_nudge_keys,
104 _ => return None,
105 })
106 }
107
108 pub fn report(&self) -> RuntimeLimitsReport {
110 RuntimeLimitsReport {
111 entries: RUNTIME_LIMIT_DESCRIPTIONS
112 .iter()
113 .map(|description| RuntimeLimitEntry {
114 name: description.name,
115 value: self
116 .value(description.name)
117 .expect("runtime limit description must name a RuntimeLimits field"),
118 user_visible: description.user_visible,
119 host_configurable: description.host_configurable,
120 protects: description.protects,
121 })
122 .collect(),
123 }
124 }
125}
126
127impl Default for RuntimeLimits {
128 fn default() -> Self {
129 Self::DEFAULT
130 }
131}
132
133#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
135pub struct RuntimeLimitsReport {
136 pub entries: Vec<RuntimeLimitEntry>,
137}
138
139#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
141pub struct RuntimeLimitEntry {
142 pub name: &'static str,
143 pub value: usize,
144 pub user_visible: bool,
145 pub host_configurable: bool,
146 pub protects: &'static str,
147}
148
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub struct RuntimeLimitDescription {
152 pub name: &'static str,
153 pub user_visible: bool,
154 pub host_configurable: bool,
155 pub protects: &'static str,
156}
157
158pub const RUNTIME_LIMIT_DESCRIPTIONS: &[RuntimeLimitDescription] = &[
159 RuntimeLimitDescription {
160 name: "max_vm_frames",
161 user_visible: true,
162 host_configurable: false,
163 protects: "prevents unbounded Harn function recursion from exhausting the VM stack",
164 },
165 RuntimeLimitDescription {
166 name: "max_template_include_depth",
167 user_visible: true,
168 host_configurable: false,
169 protects: "bounds recursive prompt-template includes after cycle detection",
170 },
171 RuntimeLimitDescription {
172 name: "max_template_parse_cache_entries",
173 user_visible: false,
174 host_configurable: false,
175 protects: "bounds per-thread memory held by parsed prompt-template assets",
176 },
177 RuntimeLimitDescription {
178 name: "max_file_text_cache_entries",
179 user_visible: false,
180 host_configurable: false,
181 protects: "bounds per-thread memory held by cached UTF-8 file reads",
182 },
183 RuntimeLimitDescription {
184 name: "max_json_parse_cache_entries",
185 user_visible: false,
186 host_configurable: false,
187 protects: "bounds per-thread memory held by memoized JSON parse inputs and values",
188 },
189 RuntimeLimitDescription {
190 name: "max_std_cache_entries",
191 user_visible: true,
192 host_configurable: false,
193 protects: "bounds retained entries in std/cache memory, filesystem, and sqlite backends",
194 },
195 RuntimeLimitDescription {
196 name: "max_regex_cache_entries",
197 user_visible: false,
198 host_configurable: false,
199 protects: "bounds per-thread memory held by compiled stdlib regex patterns",
200 },
201 RuntimeLimitDescription {
202 name: "max_schema_pattern_cache_entries",
203 user_visible: false,
204 host_configurable: false,
205 protects: "bounds process-wide memory held by compiled JSON-schema pattern regexes",
206 },
207 RuntimeLimitDescription {
208 name: "max_schema_guard_cache_entries",
209 user_visible: false,
210 host_configurable: false,
211 protects: "bounds per-thread memory held by canonical runtime parameter schemas",
212 },
213 RuntimeLimitDescription {
214 name: "default_event_log_queue_depth",
215 user_visible: true,
216 host_configurable: true,
217 protects: "bounds queued subscriber notifications for memory, file, and sqlite event logs",
218 },
219 RuntimeLimitDescription {
220 name: "max_agent_sessions",
221 user_visible: true,
222 host_configurable: false,
223 protects: "bounds concurrent first-class agent sessions stored on one VM thread",
224 },
225 RuntimeLimitDescription {
226 name: "max_project_fingerprint_depth",
227 user_visible: false,
228 host_configurable: false,
229 protects: "bounds project fingerprint discovery in large or adversarial directory trees",
230 },
231 RuntimeLimitDescription {
232 name: "max_project_enrich_yaml_depth",
233 user_visible: false,
234 host_configurable: false,
235 protects: "bounds project enrichment traversal of nested hook YAML",
236 },
237 RuntimeLimitDescription {
238 name: "max_template_ast_depth",
239 user_visible: true,
240 host_configurable: false,
241 protects: "bounds nested prompt-template control structures and expressions",
242 },
243 RuntimeLimitDescription {
244 name: "max_constant_folded_collection_items",
245 user_visible: false,
246 host_configurable: false,
247 protects: "prevents compile-time constant folding from materializing huge collections",
248 },
249 RuntimeLimitDescription {
250 name: "max_constant_folded_string_bytes",
251 user_visible: false,
252 host_configurable: false,
253 protects: "prevents compile-time constant folding from materializing huge strings",
254 },
255 RuntimeLimitDescription {
256 name: "max_nested_execution_depth",
257 user_visible: true,
258 host_configurable: false,
259 protects: "bounds nested agent loops, sub-agents, workers, and workflow stages",
260 },
261 RuntimeLimitDescription {
262 name: "max_schema_nudge_depth",
263 user_visible: false,
264 host_configurable: false,
265 protects: "keeps schema-retry correction prompts compact for deeply nested schemas",
266 },
267 RuntimeLimitDescription {
268 name: "max_schema_nudge_lines",
269 user_visible: false,
270 host_configurable: false,
271 protects: "keeps schema-retry correction prompts from growing with wide schemas",
272 },
273 RuntimeLimitDescription {
274 name: "max_schema_nudge_keys",
275 user_visible: false,
276 host_configurable: false,
277 protects: "keeps schema-retry object-key previews compact for wide objects",
278 },
279];
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn default_runtime_limits_match_legacy_values() {
287 let limits = RuntimeLimits::default();
288 assert_eq!(limits.max_vm_frames, 512);
289 assert_eq!(limits.max_template_include_depth, 32);
290 assert_eq!(limits.max_template_parse_cache_entries, 128);
291 assert_eq!(limits.max_file_text_cache_entries, 256);
292 assert_eq!(limits.max_json_parse_cache_entries, 128);
293 assert_eq!(limits.max_std_cache_entries, 256);
294 assert_eq!(limits.max_regex_cache_entries, 128);
295 assert_eq!(limits.max_schema_pattern_cache_entries, 256);
296 assert_eq!(limits.max_schema_guard_cache_entries, 256);
297 assert_eq!(limits.default_event_log_queue_depth, 128);
298 assert_eq!(limits.max_agent_sessions, 128);
299 assert_eq!(limits.max_project_fingerprint_depth, 4);
300 assert_eq!(limits.max_project_enrich_yaml_depth, 128);
301 assert_eq!(limits.max_template_ast_depth, 128);
302 assert_eq!(limits.max_constant_folded_collection_items, 4_096);
303 assert_eq!(limits.max_constant_folded_string_bytes, 64 * 1024);
304 assert_eq!(limits.max_nested_execution_depth, 8);
305 assert_eq!(limits.max_schema_nudge_depth, 3);
306 assert_eq!(limits.max_schema_nudge_lines, 8);
307 assert_eq!(limits.max_schema_nudge_keys, 16);
308 }
309
310 #[test]
311 fn runtime_limit_report_documents_every_field() {
312 let report = RuntimeLimits::default().report();
313 let names = report
314 .entries
315 .iter()
316 .map(|entry| entry.name)
317 .collect::<Vec<_>>();
318 assert_eq!(
319 names,
320 vec![
321 "max_vm_frames",
322 "max_template_include_depth",
323 "max_template_parse_cache_entries",
324 "max_file_text_cache_entries",
325 "max_json_parse_cache_entries",
326 "max_std_cache_entries",
327 "max_regex_cache_entries",
328 "max_schema_pattern_cache_entries",
329 "max_schema_guard_cache_entries",
330 "default_event_log_queue_depth",
331 "max_agent_sessions",
332 "max_project_fingerprint_depth",
333 "max_project_enrich_yaml_depth",
334 "max_template_ast_depth",
335 "max_constant_folded_collection_items",
336 "max_constant_folded_string_bytes",
337 "max_nested_execution_depth",
338 "max_schema_nudge_depth",
339 "max_schema_nudge_lines",
340 "max_schema_nudge_keys",
341 ]
342 );
343 assert!(report.entries.iter().all(|entry| entry.value > 0));
344 assert!(report
345 .entries
346 .iter()
347 .all(|entry| !entry.protects.is_empty()));
348 }
349}