vtcode_core/prompts/
harness_limits.rs1use crate::core::loop_detector::MAX_TOTAL_READONLY_CALLS;
2use crate::prompts::sections::{SectionBoundaryMode, remove_prompt_section, trim_trailing_newlines};
3use std::fmt::Write as _;
4
5const MAX_SAME_FILE_PATH_READS: usize = 6;
10
11pub fn upsert_harness_limits_section(
16 prompt: &mut String,
17 max_tool_calls_per_turn: usize,
18 max_tool_wall_clock_secs: u64,
19 max_tool_retries: u32,
20) {
21 let max_tool_calls_label = if max_tool_calls_per_turn == 0 {
22 "unlimited".to_string()
23 } else {
24 max_tool_calls_per_turn.to_string()
25 };
26 let max_tool_wall_clock_label = if max_tool_wall_clock_secs == 0 {
27 "unlimited".to_string()
28 } else {
29 format!("{max_tool_wall_clock_secs} seconds")
30 };
31
32 remove_prompt_section(prompt, "[Harness Limits]", SectionBoundaryMode::BracketOnly);
33 trim_trailing_newlines(prompt);
34
35 let section = format!(
36 "[Harness Limits]\n\
37 - max_tool_calls_per_turn: {max_tool_calls_label}\n\
38 - max_tool_wall_clock_secs: {max_tool_wall_clock_label} (per turn; run long builds with exec_command, then call write_stdin next_wait_args with action:\"wait\" verbatim)\n\
39 - max_tool_retries: {max_tool_retries}\n\
40 - max_readonly_tool_calls: {MAX_TOTAL_READONLY_CALLS} (global budget across all read-only tools; produce output before exhausting)\n\
41 - max_same_file_path_reads: {MAX_SAME_FILE_PATH_READS} (per file path per turn; read a file once in full rather than paginating)\n\
42 - exec wait/inspect are exempt from max_tool_calls_per_turn\n\
43 - long commands: set yield_time_ms above 10000 on the initial run for a single-call long run (no outer timeout), or reuse next_wait_args in write_stdin to block until exit or deadline"
44 );
45
46 if prompt.is_empty() {
47 let _ = writeln!(prompt, "{section}");
48 } else {
49 let _ = writeln!(prompt, "\n{section}");
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::upsert_harness_limits_section;
56
57 #[test]
58 fn upsert_harness_limits_adds_single_section() {
59 let mut prompt = "Base prompt".to_string();
60
61 upsert_harness_limits_section(&mut prompt, 12, 180, 2);
62
63 assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
64 assert!(prompt.contains("- max_tool_calls_per_turn: 12"));
65 assert!(prompt.contains("- max_tool_wall_clock_secs: 180"));
66 assert!(prompt.contains("- max_tool_retries: 2"));
67 assert!(prompt.contains("- max_readonly_tool_calls: 30"));
68 assert!(prompt.contains("- max_same_file_path_reads: 6"));
69 assert!(prompt.contains("exec wait/inspect are exempt from max_tool_calls_per_turn"));
70 assert!(prompt.contains("single-call long run"));
71 }
72
73 #[test]
74 fn upsert_harness_limits_states_wait_inspect_exemption_once() {
75 let mut prompt = "Base prompt".to_string();
79
80 upsert_harness_limits_section(&mut prompt, 12, 180, 2);
81
82 assert_eq!(prompt.matches("exempt").count(), 1);
83 assert!(prompt.contains("exec wait/inspect are exempt from max_tool_calls_per_turn"));
84 assert!(prompt.contains("single-call long run"));
85 assert!(prompt.contains("write_stdin next_wait_args"));
86 }
87
88 #[test]
89 fn upsert_harness_limits_replaces_existing_values() {
90 let mut prompt = "Base prompt\n[Harness Limits]\n- max_tool_calls_per_turn: 3\n- max_tool_wall_clock_secs: 60\n- max_tool_retries: 1\n".to_string();
91
92 upsert_harness_limits_section(&mut prompt, 9, 240, 4);
93
94 assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
95 assert!(prompt.contains("- max_tool_calls_per_turn: 9"));
96 assert!(prompt.contains("- max_tool_wall_clock_secs: 240"));
97 assert!(prompt.contains("- max_tool_retries: 4"));
98 assert!(!prompt.contains("- max_tool_calls_per_turn: 3"));
99 }
100
101 #[test]
102 fn upsert_harness_limits_preserves_trailing_prompt_sections() {
103 let mut prompt = "Base prompt\n[Harness Limits]\n- max_tool_calls_per_turn: 3\n- max_tool_wall_clock_secs: 60\n- max_tool_retries: 1\n[Additional Context]\nKeep this section".to_string();
104
105 upsert_harness_limits_section(&mut prompt, 11, 90, 3);
106
107 assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
108 assert!(prompt.contains("[Additional Context]\nKeep this section"));
109 assert!(prompt.contains("- max_tool_retries: 3"));
110 assert!(prompt.contains("- max_readonly_tool_calls: 30"));
111 }
112
113 #[test]
114 fn upsert_harness_limits_replaces_indented_section_header() {
115 let mut prompt = "Base prompt\n [Harness Limits]\n- max_tool_calls_per_turn: 1\n- max_tool_wall_clock_secs: 1\n- max_tool_retries: 1\n".to_string();
116
117 upsert_harness_limits_section(&mut prompt, 5, 30, 2);
118
119 assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
120 assert!(prompt.contains("- max_tool_calls_per_turn: 5"));
121 assert!(!prompt.contains("- max_tool_calls_per_turn: 1"));
122 }
123
124 #[test]
125 fn upsert_harness_limits_removes_duplicate_sections() {
126 let mut prompt = "Base prompt\n[Harness Limits]\n- max_tool_calls_per_turn: 2\n- max_tool_wall_clock_secs: 10\n- max_tool_retries: 1\n[Other]\nkeep\n[Harness Limits]\n- max_tool_calls_per_turn: 3\n- max_tool_wall_clock_secs: 20\n- max_tool_retries: 2\n".to_string();
127
128 upsert_harness_limits_section(&mut prompt, 7, 70, 3);
129
130 assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
131 assert!(prompt.contains("- max_tool_calls_per_turn: 7"));
132 assert!(prompt.contains("[Other]\nkeep"));
133 }
134
135 #[test]
136 fn upsert_harness_limits_renders_unlimited_when_tool_cap_disabled() {
137 let mut prompt = "Base prompt".to_string();
138
139 upsert_harness_limits_section(&mut prompt, 0, 600, 2);
140
141 assert!(prompt.contains("- max_tool_calls_per_turn: unlimited"));
142 assert!(prompt.contains("- max_tool_wall_clock_secs: 600 seconds"));
143 assert!(prompt.contains("- max_tool_retries: 2"));
144 assert!(prompt.contains("- max_readonly_tool_calls: 30"));
145 assert!(prompt.contains("- max_same_file_path_reads: 6"));
146 }
147
148 #[test]
149 fn upsert_harness_limits_renders_unlimited_wall_clock() {
150 let mut prompt = "Base prompt".to_string();
151
152 upsert_harness_limits_section(&mut prompt, 4, 0, 2);
153
154 assert!(prompt.contains("- max_tool_wall_clock_secs: unlimited"));
155 assert!(prompt.contains("write_stdin next_wait_args"));
156 }
157}