Skip to main content

vtcode_core/prompts/
harness_limits.rs

1use crate::core::loop_detector::MAX_TOTAL_READONLY_CALLS;
2use crate::prompts::sections::{SectionBoundaryMode, find_prompt_section_bounds};
3use std::fmt::Write as _;
4
5/// Per-file-path read cap advertised in the system prompt. Must match
6/// `guards::MAX_SAME_FILE_PATH_READ_CALLS`. Reading the same file more than
7/// this many times in a turn triggers recovery.
8const MAX_SAME_FILE_PATH_READS: usize = 6;
9
10/// Keep prompt guidance aligned with the selected user-visible harness limits.
11///
12/// This operation is idempotent: existing `[Harness Limits]` sections are removed before
13/// inserting the current values.
14pub fn upsert_harness_limits_section(
15    prompt: &mut String,
16    max_tool_calls_per_turn: usize,
17    max_tool_wall_clock_secs: u64,
18    max_tool_retries: u32,
19) {
20    let max_tool_calls_label = if max_tool_calls_per_turn == 0 {
21        "unlimited".to_string()
22    } else {
23        max_tool_calls_per_turn.to_string()
24    };
25    let max_tool_wall_clock_label = if max_tool_wall_clock_secs == 0 {
26        "unlimited".to_string()
27    } else {
28        format!("{max_tool_wall_clock_secs} seconds")
29    };
30
31    while let Some((section_start, section_end)) =
32        find_prompt_section_bounds(prompt, "[Harness Limits]", SectionBoundaryMode::BracketOnly)
33    {
34        prompt.replace_range(section_start..section_end, "");
35    }
36
37    while prompt.ends_with('\n') {
38        prompt.pop();
39    }
40
41    let section = format!(
42        "[Harness Limits]\n\
43         - max_tool_calls_per_turn: {max_tool_calls_label}\n\
44         - 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\
45         - max_tool_retries: {max_tool_retries}\n\
46         - max_readonly_tool_calls: {MAX_TOTAL_READONLY_CALLS} (global budget across all read-only tools; produce output before exhausting)\n\
47         - 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\
48         - exec wait/inspect are exempt from max_tool_calls_per_turn\n\
49         - 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; wait/inspect stay budget-exempt"
50    );
51
52    if prompt.is_empty() {
53        let _ = writeln!(prompt, "{section}");
54    } else {
55        let _ = writeln!(prompt, "\n{section}");
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::upsert_harness_limits_section;
62
63    #[test]
64    fn upsert_harness_limits_adds_single_section() {
65        let mut prompt = "Base prompt".to_string();
66
67        upsert_harness_limits_section(&mut prompt, 12, 180, 2);
68
69        assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
70        assert!(prompt.contains("- max_tool_calls_per_turn: 12"));
71        assert!(prompt.contains("- max_tool_wall_clock_secs: 180"));
72        assert!(prompt.contains("- max_tool_retries: 2"));
73        assert!(prompt.contains("- max_readonly_tool_calls: 30"));
74        assert!(prompt.contains("- max_same_file_path_reads: 6"));
75        assert!(prompt.contains("exec wait/inspect are exempt from max_tool_calls_per_turn"));
76        assert!(prompt.contains("single-call long run"));
77    }
78
79    #[test]
80    fn upsert_harness_limits_replaces_existing_values() {
81        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();
82
83        upsert_harness_limits_section(&mut prompt, 9, 240, 4);
84
85        assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
86        assert!(prompt.contains("- max_tool_calls_per_turn: 9"));
87        assert!(prompt.contains("- max_tool_wall_clock_secs: 240"));
88        assert!(prompt.contains("- max_tool_retries: 4"));
89        assert!(!prompt.contains("- max_tool_calls_per_turn: 3"));
90    }
91
92    #[test]
93    fn upsert_harness_limits_preserves_trailing_prompt_sections() {
94        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();
95
96        upsert_harness_limits_section(&mut prompt, 11, 90, 3);
97
98        assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
99        assert!(prompt.contains("[Additional Context]\nKeep this section"));
100        assert!(prompt.contains("- max_tool_retries: 3"));
101        assert!(prompt.contains("- max_readonly_tool_calls: 30"));
102    }
103
104    #[test]
105    fn upsert_harness_limits_replaces_indented_section_header() {
106        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();
107
108        upsert_harness_limits_section(&mut prompt, 5, 30, 2);
109
110        assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
111        assert!(prompt.contains("- max_tool_calls_per_turn: 5"));
112        assert!(!prompt.contains("- max_tool_calls_per_turn: 1"));
113    }
114
115    #[test]
116    fn upsert_harness_limits_removes_duplicate_sections() {
117        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();
118
119        upsert_harness_limits_section(&mut prompt, 7, 70, 3);
120
121        assert_eq!(prompt.matches("[Harness Limits]").count(), 1);
122        assert!(prompt.contains("- max_tool_calls_per_turn: 7"));
123        assert!(prompt.contains("[Other]\nkeep"));
124    }
125
126    #[test]
127    fn upsert_harness_limits_renders_unlimited_when_tool_cap_disabled() {
128        let mut prompt = "Base prompt".to_string();
129
130        upsert_harness_limits_section(&mut prompt, 0, 600, 2);
131
132        assert!(prompt.contains("- max_tool_calls_per_turn: unlimited"));
133        assert!(prompt.contains("- max_tool_wall_clock_secs: 600 seconds"));
134        assert!(prompt.contains("- max_tool_retries: 2"));
135        assert!(prompt.contains("- max_readonly_tool_calls: 30"));
136        assert!(prompt.contains("- max_same_file_path_reads: 6"));
137    }
138
139    #[test]
140    fn upsert_harness_limits_renders_unlimited_wall_clock() {
141        let mut prompt = "Base prompt".to_string();
142
143        upsert_harness_limits_section(&mut prompt, 4, 0, 2);
144
145        assert!(prompt.contains("- max_tool_wall_clock_secs: unlimited"));
146        assert!(prompt.contains("write_stdin next_wait_args"));
147    }
148}