1use std::fmt::Write as _;
36
37use crate::agent::AgentName;
38
39pub const MAX_MEMORY: usize = 4_096;
49
50#[derive(Debug, Clone, Copy)]
55pub struct Run<'a> {
56 pub agent: &'a AgentName,
58 pub instructions: &'a str,
60 pub memory: Option<&'a str>,
62 pub brief: &'a str,
64 pub handover: Option<&'a str>,
67 pub body: &'a str,
69}
70
71#[must_use]
76pub fn compose(run: &Run<'_>) -> String {
77 let mut out = String::with_capacity(
78 run.instructions.len() + run.brief.len() + run.body.len() + MAX_MEMORY + 256,
79 );
80
81 write_identity(&mut out, run.agent);
82 write_section(&mut out, run.instructions);
83
84 if let Some(memory) = run.memory {
85 write_memory(&mut out, memory);
86 }
87
88 write_section(&mut out, run.brief);
89
90 if let Some(handover) = run.handover {
91 write_section(&mut out, handover);
92 }
93
94 write_body(&mut out, run.body);
95 out
96}
97
98fn write_identity(out: &mut String, agent: &AgentName) {
104 let _ = writeln!(out, "You are `{agent}`.\n");
105}
106
107fn write_section(out: &mut String, text: &str) {
109 let text = text.trim();
110 if text.is_empty() {
111 return;
112 }
113
114 out.push_str(text);
115 out.push_str("\n\n");
116}
117
118fn write_memory(out: &mut String, memory: &str) {
120 let memory = memory.trim();
121 if memory.is_empty() {
122 return;
123 }
124
125 out.push_str("== WHAT YOU WROTE DOWN LAST TIME ==\n");
126 out.push_str(
127 "Runs are a clean slate, so this is everything you remember. It is what earlier runs of \
128 you chose to record, and nothing else carried over.\n\n",
129 );
130
131 if memory.len() <= MAX_MEMORY {
132 out.push_str(memory);
133 out.push_str("\n\n");
134 return;
135 }
136
137 let mut start = memory.len() - MAX_MEMORY;
140 while start < memory.len() && !memory.is_char_boundary(start) {
141 start += 1;
142 }
143
144 out.push_str(
145 "[This is the most recent part of your memory. It is longer than fits here — call \
146 `layover_memory_read` for the whole file.]\n\n",
147 );
148 out.push_str(memory[start..].trim_start());
149 out.push_str("\n\n");
150}
151
152fn write_body(out: &mut String, body: &str) {
154 out.push_str("== WHAT YOU HAVE BEEN ASKED TO DO ==\n\n");
155 out.push_str(body.trim());
156 out.push('\n');
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 fn agent() -> AgentName {
164 AgentName::new("tester")
165 }
166
167 fn minimal<'a>(agent: &'a AgentName, body: &'a str) -> Run<'a> {
168 Run {
169 agent,
170 instructions: "Run the suite and report what failed.",
171 memory: None,
172 brief: "",
173 handover: None,
174 body,
175 }
176 }
177
178 #[test]
180 fn the_sections_arrive_in_the_settled_order() {
181 let name = agent();
182 let run = Run {
183 agent: &name,
184 instructions: "INSTRUCTIONS",
185 memory: Some("MEMORY"),
186 brief: "BRIEF",
187 handover: Some("HANDOVER"),
188 body: "BODY",
189 };
190
191 let text = compose(&run);
192 let at = |needle: &str| {
193 text.find(needle)
194 .unwrap_or_else(|| panic!("missing {needle}"))
195 };
196
197 assert!(at("You are `tester`") < at("INSTRUCTIONS"));
198 assert!(at("INSTRUCTIONS") < at("MEMORY"));
199 assert!(at("MEMORY") < at("BRIEF"));
200 assert!(
201 at("BRIEF") < at("HANDOVER"),
202 "a recovery instruction must not be buried under accumulated advice"
203 );
204 assert!(
205 at("HANDOVER") < at("BODY"),
206 "the message that woke the agent reads as the current instruction, so it goes last"
207 );
208 }
209
210 #[test]
211 fn the_body_is_the_last_thing_in_the_payload() {
212 let name = agent();
213 let text = compose(&minimal(&name, "Fix the retry policy."));
214
215 assert!(text.trim_end().ends_with("Fix the retry policy."), "{text}");
216 }
217
218 #[test]
219 fn identity_comes_from_the_tower_and_is_always_present() {
220 let name = agent();
221 assert!(compose(&minimal(&name, "go")).starts_with("You are `tester`."));
222 }
223
224 #[test]
225 fn absent_sections_leave_no_hole() {
226 let name = agent();
227 let text = compose(&minimal(&name, "go"));
228
229 assert!(!text.contains("WHAT YOU WROTE DOWN"), "{text}");
230 assert!(
231 !text.contains("\n\n\n"),
232 "blank lines should not stack: {text:?}"
233 );
234 }
235
236 #[test]
237 fn memory_shorter_than_the_cap_arrives_whole_and_says_nothing_about_cutting() {
238 let name = agent();
239 let run = Run {
240 memory: Some("The e2e suite needs VPN. Ask before assuming a failure is real."),
241 ..minimal(&name, "go")
242 };
243
244 let text = compose(&run);
245 assert!(text.contains("needs VPN"), "{text}");
246 assert!(!text.contains("most recent part"), "{text}");
247 }
248
249 #[test]
250 fn over_long_memory_keeps_the_end_not_the_beginning() {
251 let name = agent();
254 let memory = format!("OLDEST{}NEWEST", "x".repeat(MAX_MEMORY * 2));
255 let run = Run {
256 memory: Some(&memory),
257 ..minimal(&name, "go")
258 };
259
260 let text = compose(&run);
261 assert!(text.contains("NEWEST"), "the recent end was dropped");
262 assert!(!text.contains("OLDEST"), "the old end was kept");
263 }
264
265 #[test]
266 fn a_cut_memory_says_so_and_says_where_the_rest_is() {
267 let name = agent();
268 let memory = "y".repeat(MAX_MEMORY * 2);
269 let run = Run {
270 memory: Some(&memory),
271 ..minimal(&name, "go")
272 };
273
274 let text = compose(&run);
275 assert!(text.contains("longer than fits here"), "{text}");
276 assert!(
277 text.contains("layover_memory_read"),
278 "an agent told it has more should be told how to get it: {text}"
279 );
280 }
281
282 #[test]
283 fn cutting_memory_never_splits_a_character() {
284 let name = agent();
285 let memory = "é".repeat(MAX_MEMORY);
287 let run = Run {
288 memory: Some(&memory),
289 ..minimal(&name, "go")
290 };
291
292 assert!(compose(&run).contains('é'));
293 }
294
295 #[test]
296 fn an_ordinary_dispatch_carries_no_handover() {
297 let name = agent();
298 let text = compose(&minimal(&name, "go"));
299
300 assert!(!text.contains("continuing"), "{text}");
301 }
302
303 #[test]
304 fn empty_memory_is_the_same_as_no_memory() {
305 let name = agent();
306 let blank = Run {
307 memory: Some(" \n "),
308 ..minimal(&name, "go")
309 };
310
311 assert_eq!(compose(&blank), compose(&minimal(&name, "go")));
312 }
313}