Skip to content

Conversation

aibrahim-oai
Copy link
Collaborator

@aibrahim-oai aibrahim-oai commented Jul 21, 2025

  • Move rollout persistence and listing into a dedicated module: rollout/{recorder,list}.
  • Expose lightweight conversation listing that returns file paths plus the first 5 JSONL records for preview.

/// Directory layout: `~/.codex/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl`
/// The first JSONL line is a `SessionMeta` object; subsequent lines are response/state records.
///
/// Returned structure (earliest first):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to reverse this? i.e. latest first

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad

.and_then(|s| s.parse::<u8>().ok())
.map(|m| (m, e.path()))
})
.collect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is repeated a few times, do we want to refactor it into a helper function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/// Returned page of sessions.
#[derive(Debug)]
pub struct SessionsPage {
pub sessions: Vec<Value>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why we're using Value here instead of parsing the session files into SavedSession objects?

Copy link
Collaborator Author

@aibrahim-oai aibrahim-oai Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great idea. My PR is just too big. Maybe in the next PR I can do that. I don't see a problem now in returning the value because jsonl is a bunch of json objects.

Copy link
Collaborator

@dylan-hurd-oai dylan-hurd-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - reasonable to follow up on the SavedSession change in a subsequent PR

/// The first JSONL line is a `SessionMeta` object; subsequent lines are response/state records.
///
/// Returned structure (latest (newest) first):
fn traverse_directories(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the complexity of pagination/filtering is worth it here.

We can easily list 1000 sessions in one page and I doubt users ever care about more than 10 last sessions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, but I think it wouldn't hurt to have, right? what if we want to construct a memory or sth that requires looking at a lot of sessions.

.to_string();

match mode {
SessionsMode::Full => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is using SessionsMode::Full?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client when loading one session.

Copy link

Summary

Adds a new session_manager module that lists on-disk rollout sessions with pagination, date-range and ID filtering, and “full / lite” payload modes.
get_sessions, SessionsMode, and SessionsPage are exported; tests and minor tweaks to Cargo.toml, lib.rs, and rollout.rs complete the integration.

Review

Nice addition—clean directory traversal, clear filtering logic, and thorough unit tests.
A few tiny nits for polish:

  • Consider making MAX_SCAN_FILES configurable or at least documenting the rationale.
  • spawn_blocking error is wrapped with io::Error::other; a dedicated error enum could give callers finer insight.
  • collect_* silently ignores parse errors; a debug log might help during triage.
  • In Lite mode the head/tail constants could be parameters to avoid magic numbers.

Overall, the feature is well-structured and ready to merge after any minor tweaks you deem worthwhile.


View workflow run

@aibrahim-oai aibrahim-oai marked this pull request as draft July 27, 2025 19:55
@aibrahim-oai aibrahim-oai marked this pull request as ready for review July 29, 2025 22:10
page_size: usize,
cursor: Option<&str>,
) -> io::Result<ConversationsPage> {
if page_size == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is special casing this? I think page_size == 0 should "just work" and it's not worth optimizing this case since it's not useful from the caller's perspective

/// Pagination cursor token format: "<file_ts>|<uuid>" where `file_ts` matches the
/// filename timestamp portion (YYYY-MM-DDThh-mm-ss) used in rollout filenames.
/// The cursor orders files by timestamp desc, then UUID desc.
fn parse_cursor_token(token: &str) -> Option<(OffsetDateTime, Uuid)> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn parse_cursor_token(token: &str) -> Option<(OffsetDateTime, Uuid)> {
fn parse_cursor(cursor: &str) -> Option<(OffsetDateTime, Uuid)> {

Comment on lines 212 to 218
fn parse_timestamp_uuid_from_filename(name: &str) -> Option<(OffsetDateTime, Uuid)> {
// Format: rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl
let core = name.strip_prefix("rollout-")?.strip_suffix(".jsonl")?;
if core.len() < 37 {
return None;
} // need at least dt + '-' + 36
let uuid_part = &core[core.len() - 36..];
let dt_part = &core[..core.len() - 37]; // strip trailing '-' before uuid
let Ok(uuid) = Uuid::parse_str(uuid_part) else {
return None;
};
let format: &[FormatItem] =
format_description!("[year]-[month]-[day]T[hour]-[minute]-[second]");
let ts = PrimitiveDateTime::parse(dt_part, format).ok()?.assume_utc();
Some((ts, uuid))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work? It doesn't rely on hard coding offsets

use time::{OffsetDateTime, PrimitiveDateTime, macros::format_description};
use uuid::Uuid;

fn parse_timestamp_uuid_from_filename(name: &str) -> Option<(OffsetDateTime, Uuid)> {
    // Expected: rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl
    let core = name.strip_prefix("rollout-")?.strip_suffix(".jsonl")?;

    // Split at the last '-' so the timestamp (which contains '-') stays intact.
    let (ts_str, uuid_str) = core.rsplit_once('-')?;

    let uuid = Uuid::parse_str(uuid_str).ok()?;
    let fmt = format_description!("[year]-[month]-[day]T[hour]-[minute]-[second]");
    let ts = PrimitiveDateTime::parse(ts_str, fmt).ok()?.assume_utc();

    Some((ts, uuid))
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uuid has - in them. However, I did something less card coded. have a look.

Ok(vec)
}

// Helper: collect files in `parent`, parse with `parse(name_str, path)` into arbitrary value.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Helper: collect files in `parent`, parse with `parse(name_str, path)` into arbitrary value.
// Collects files in a directory and parses them with `parse`.

let (anchor_ts, anchor_id) =
anchor.unwrap_or_else(|| (OffsetDateTime::UNIX_EPOCH, Uuid::nil()));

let year_dirs = collect_dirs_desc(&root, |s| s.parse::<i32>().ok())?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let year_dirs = collect_dirs_desc(&root, |s| s.parse::<i32>().ok())?;
let year_dirs = collect_dirs_desc(&root, |s| s.parse::<u16>().ok())?;

Does this work

))
}

// Helper: collect immediate subdirectories of `parent`, parse their (string) names with `parse`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Helper: collect immediate subdirectories of `parent`, parse their (string) names with `parse`,
/// Collect immediate subdirectories of `parent`, parse their (string) names with `parse`,

@aibrahim-oai aibrahim-oai marked this pull request as draft August 13, 2025 20:02
@aibrahim-oai aibrahim-oai marked this pull request as ready for review September 2, 2025 23:16
@aibrahim-oai aibrahim-oai changed the title Core: initialize session manager to search for session data. core(rollout): extract rollout module, add listing API, and return file heads Sep 3, 2025
@aibrahim-oai
Copy link
Collaborator Author

@codex review this in detail.

Copy link
Contributor

You have reached your rate limits for code reviews, please try again later

@aibrahim-oai
Copy link
Collaborator Author

@codex review this in detail.

Copy link
Contributor

You have reached your rate limits for code reviews, please try again later


/// Load conversation file paths from disk using directory traversal.
///
/// Directory layout: `~/.codex/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use just a uuid v7? and you won't need YYYYMMDDs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea.

@aibrahim-oai aibrahim-oai enabled auto-merge (squash) September 3, 2025 07:31
@aibrahim-oai aibrahim-oai merged commit d77b33d into main Sep 3, 2025
18 checks passed
@aibrahim-oai aibrahim-oai deleted the get_sessions branch September 3, 2025 07:39
@github-actions github-actions bot locked and limited conversation to collaborators Sep 3, 2025
@aibrahim-oai
Copy link
Collaborator Author

Work in progress for this issue: #2080

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants