**PR 2243 Review: Context Messages in Rollouts — DOs and DON’Ts** **DOs** - **Persist Context As Messages**: Store user instructions and environment context as conversation messages in rollouts, not request-only payload. - **Use Helpers**: Create messages via `Prompt::format_user_instructions_message(...)` and `ResponseItem::from(EnvironmentContext::new(...))`. - **Record In One Call**: Batch messages into a single `sess.record_conversation_items(...)` to avoid extra lock/await. - **Keep Order Stable**: Append messages in this order: user_instructions, environment_context, then user turn messages. - **Match Tag Formats**: Use exact wrappers: user_instructions has blank lines; environment_context does not. - **Adopt New Labels**: Print “Sandbox mode” (not “Sandbox policy”) and include “Network access”. - **Rely On Display Derives**: Use derived `Display` + kebab-case for `SandboxMode` and `NetworkAccess`. - **Build Prompt Correctly**: Populate `Prompt { input, store, tools, base_instructions_override }`; omit removed fields. - **Write Tests For Order/Tags**: Assert role “user”, correct tag start/end, and consistent ordering across requests. - **Inline Format Captures**: Use Rust 1.58+ captured identifiers in `format!` (e.g., `format!("{var}")`). ```rust // DO: record both messages in one call (order: UI, then Env). let ui = sess.get_user_instructions(); let env = EnvironmentContext::new( sess.get_cwd().to_path_buf(), sess.get_approval_policy(), sess.get_sandbox_policy().clone(), ); let mut boot_items = Vec::new(); if let Some(ui) = ui { boot_items.push(Prompt::format_user_instructions_message(&ui)); } boot_items.push(ResponseItem::from(env)); sess.record_conversation_items(&boot_items).await; // DO: construct Prompt without removed fields. let prompt = Prompt { input, store: !sess.disable_response_storage, tools, base_instructions_override: sess.base_instructions.clone(), }; // DO: exact tag formats (UI has blank lines; Env does not). let ui_msg = Prompt::format_user_instructions_message("be nice"); let cwd = cwd.path().to_string_lossy(); let ap = AskForApproval::OnRequest; let sm = SandboxMode::ReadOnly; let na = NetworkAccess::Restricted; let expected_env_text = format!( "\nCurrent working directory: {cwd}\nApproval policy: {ap}\nSandbox mode: {sm}\nNetwork access: {na}\n" ); // DO: assert order and tags in tests. assert_message_starts_with(&body["input"][0], ""); assert_message_ends_with(&body["input"][0], ""); assert_message_starts_with(&body["input"][1], ""); assert_message_ends_with(&body["input"][1], ""); ``` **DON’Ts** - **Don’t Re-Inject Per Request**: Avoid request-time concatenation of context; rely on stored rollout messages. - **Don’t Split Recording Calls**: Don’t call `record_conversation_items()` separately for UI and Env. - **Don’t Use Old Field Names**: Don’t refer to “Sandbox policy”; it’s now “Sandbox mode”. - **Don’t Change Tag Whitespace**: Don’t add/remove blank lines; UI has blank lines, Env does not. - **Don’t Reorder Messages**: Don’t put environment_context before user_instructions. - **Don’t Manually Handcraft Env Text**: Don’t format the env block by hand; use `EnvironmentContext::new` + `ResponseItem::from`. - **Don’t Add Back Removed Prompt Fields**: Don’t set `Prompt.user_instructions` or `Prompt.environment_context` (they’re gone). - **Don’t Break Caching Consistency**: Don’t vary tag text, labels, or order across turns; tests depend on stability. ```rust // DON'T: two separate records (extra lock/await). // sess.record_conversation_items(&[ui_msg]).await; // sess.record_conversation_items(&[env_msg]).await; // DON'T: wrong tag whitespace for Env (extra blank lines). let bad_env = format!( "\n\n...details...\n\n" ); // DON'T: wrong message order. let wrong = serde_json::json!([env_msg, ui_msg, user_msg]); // <- breaks tests // DON'T: use removed fields on Prompt. let prompt = Prompt { input, // user_instructions: Some("..."), // ❌ removed // environment_context: Some(env_context), // ❌ removed store: true, tools: vec![], base_instructions_override: None, }; ```