feat: sqlite 1 (#10004)

Add a `.sqlite` database to be used to store rollout metatdata (and
later logs)
This PR is phase 1:
* Add the database and the required infrastructure
* Add a backfill of the database
* Persist the newly created rollout both in files and in the DB
* When we need to get metadata or a rollout, consider the `JSONL` as the
source of truth but compare the results with the DB and show any errors
This commit is contained in:
jif-oai authored and GitHub committed 2026-01-28 15:29:14 +01:00
1 parent dabafe204a
commit 3878c3dc7c
30 files changed
+2882 -44

No files matched your search

+33 -7
View File
@@ -154,6 +154,7 @@ use crate::protocol::WarningEvent;
use crate::rollout::RolloutRecorder;
use crate::rollout::RolloutRecorderParams;
use crate::rollout::map_session_init_error;
use crate::rollout::metadata;
use crate::shell;
use crate::shell_snapshot::ShellSnapshot;
use crate::skills::SkillError;
@@ -165,6 +166,7 @@ use crate::skills::collect_explicit_skill_mentions;
use crate::state::ActiveTurn;
use crate::state::SessionServices;
use crate::state::SessionState;
use crate::state_db;
use crate::tasks::GhostSnapshotTask;
use crate::tasks::ReviewTask;
use crate::tasks::SessionTask;
@@ -420,6 +422,10 @@ impl Codex {
let state = self.session.state.lock().await;
state.session_configuration.thread_config_snapshot()
}
pub(crate) fn state_db(&self) -> Option<state_db::StateDbHandle> {
self.session.state_db()
}
}
/// Context for an initialized model agent
@@ -699,6 +705,13 @@ impl Session {
RolloutRecorderParams::resume(resumed_history.rollout_path.clone()),
),
};
let state_builder = match &initial_history {
InitialHistory::Resumed(resumed) => metadata::builder_from_items(
resumed.history.as_slice(),
resumed.rollout_path.as_path(),
),
InitialHistory::New | InitialHistory::Forked(_) => None,
};
// Kick off independent async setup tasks in parallel to reduce startup latency.
//
@@ -707,11 +720,17 @@ impl Session {
// - load history metadata
let rollout_fut = async {
if config.ephemeral {
Ok(None)
Ok::<_, anyhow::Error>((None, None))
} else {
RolloutRecorder::new(&config, rollout_params)
.await
.map(Some)
let state_db_ctx = state_db::init_if_enabled(&config, None).await;
let rollout_recorder = RolloutRecorder::new(
&config,
rollout_params,
state_db_ctx.clone(),
state_builder.clone(),
)
.await?;
Ok((Some(rollout_recorder), state_db_ctx))
}
};
@@ -731,14 +750,14 @@ impl Session {
// Join all independent futures.
let (
rollout_recorder,
rollout_recorder_and_state_db,
(history_log_id, history_entry_count),
(auth, mcp_servers, auth_statuses),
) = tokio::join!(rollout_fut, history_meta_fut, auth_and_mcp_fut);
let rollout_recorder = rollout_recorder.map_err(|e| {
let (rollout_recorder, state_db_ctx) = rollout_recorder_and_state_db.map_err(|e| {
error!("failed to initialize rollout recorder: {e:#}");
anyhow::Error::from(e)
e
})?;
let rollout_path = rollout_recorder
.as_ref()
@@ -842,6 +861,7 @@ impl Session {
tool_approvals: Mutex::new(ApprovalStore::default()),
skills_manager,
agent_control,
state_db: state_db_ctx.clone(),
};
let sess = Arc::new(Session {
@@ -914,6 +934,10 @@ impl Session {
self.tx_event.clone()
}
pub(crate) fn state_db(&self) -> Option<state_db::StateDbHandle> {
self.services.state_db.clone()
}
/// Ensure all rollout writes are durably flushed.
pub(crate) async fn flush_rollout(&self) {
let recorder = {
@@ -4580,6 +4604,7 @@ mod tests {
tool_approvals: Mutex::new(ApprovalStore::default()),
skills_manager,
agent_control,
state_db: None,
};
let turn_context = Session::make_turn_context(
@@ -4691,6 +4716,7 @@ mod tests {
tool_approvals: Mutex::new(ApprovalStore::default()),
skills_manager,
agent_control,
state_db: None,
};
let turn_context = Arc::new(Session::make_turn_context(