Files
codex/prs/bolinfest/study/PR-2575-study.md
2025-09-02 15:17:45 -07:00

3.8 KiB
Raw Blame History

DOs

  • Clone Arcs consistently with .clone(): prefer session.clone() over Arc::clone(&session).
// Preferred
tokio::spawn(submission_loop(session.clone(), turn_context, config, rx_sub));
  • Reuse shared spawn flow: factor event validation/registration into a helper (e.g., finalize_spawn) and call it from both regular and forked spawns.
let CodexSpawnOk { codex, session_id } =
    Codex::spawn(config, auth_manager, Some(truncated_history)).await?;
self.finalize_spawn(codex, session_id).await
  • Design clear, intention-revealing APIs: put the main subject first, modifiers second, plumbing last; name intermediates clearly (e.g., truncated_history).
pub async fn fork_conversation(
    &self,
    conversation_history: Vec<ResponseItem>,
    num_messages_to_drop: usize,
    config: Config,
) -> CodexResult<NewConversation> {
    let truncated_history =
        truncate_after_dropping_last_messages(conversation_history, num_messages_to_drop);
    // spawn with truncated_history...
}
  • Use Option::or_else for clean fallbacks when choosing initial history.
let restored_items: Option<Vec<ResponseItem>> = initial_history.or_else(|| {
    maybe_saved.and_then(|saved|
        if saved.items.is_empty() { None } else { Some(saved.items) })
});
  • Keep protocol cohesive and documented: colocate related ops and add docstrings; use consistent payload names.
// Near GetHistoryEntryRequest
/// Request the full in-memory conversation transcript for the current session.
/// Reply is delivered via `EventMsg::ConversationHistory`.
Op::GetHistory,

// Event and payload
pub enum EventMsg {
    // ...
    ConversationHistory(ConversationHistoryResponseEvent),
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ConversationHistoryResponseEvent {
    pub conversation_id: Uuid,
    pub entries: Vec<ResponseItem>,
}
  • Prefer clear control flow and tests for history truncation helpers.
fn truncate_after_dropping_last_messages(items: Vec<ResponseItem>, n: usize) -> Vec<ResponseItem> {
    if n == 0 || items.is_empty() {
        return items;
    }
    // ... compute cut_index and count ...
    if count < n {
        Vec::new()
    } else {
        items.into_iter().take(cut_index).collect()
    }
}

#[test]
fn drops_from_last_user_only() {
    let items = vec![user_msg("u1"), assistant_msg("a1"), user_msg("u2"), assistant_msg("a2")];
    let out = truncate_after_dropping_last_messages(items.clone(), 1);
    assert_eq!(out, vec![items[0].clone(), items[1].clone()]);
}

DON'Ts

  • Dont expand Codexs responsibilities by storing a Session inside it.
// Avoid adding fields like this to Codex:
struct Codex {
    next_id: AtomicU64,
    tx_sub: Sender<Submission>,
    rx_event: Receiver<Event>,
    // session: Arc<Session>, // ❌ Avoid
}
  • Dont use Arc::clone(&x) in this codebase where .clone() is the project norm.
// Avoid
tokio::spawn(submission_loop(Arc::clone(&session), turn_context, config, rx_sub));
  • Dont duplicate “first event must be SessionConfigured” validation across spawn paths—centralize it.
// Anti-pattern: redoing validation inline
let event = codex.next_event().await?;
// ... custom matching logic duplicated here ...
// ✅ Prefer: self.finalize_spawn(codex, session_id).await
  • Dont use vague parameter ordering or names in APIs like fork_conversation.
// Avoid
pub async fn fork_conversation(&self, drop_last_messages: usize, config: Config, items: Vec<ResponseItem>);
  • Dont introduce inconsistent or unclear protocol naming, or undocumented ops.
// Avoid ambiguous/unused name
pub struct ConversationHistoryEvent; // ❌ Use ConversationHistoryResponseEvent

// Avoid undocumented, misplaced op
Op::GetHistory, // ❌ without docstring or near related ops