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