feat: mem slash commands (#11569)

Add 2 slash commands for memories:
* `/m_drop` delete all the memories
* `/m_update` update the memories with phase 1 and 2
This commit is contained in:
jif-oai
2026-02-12 10:39:43 +00:00
committed by GitHub
Unverified
parent 4027f1f1a4
commit a0dab25c68
6 changed files with 134 additions and 1 deletions
+68
View File
@@ -3033,6 +3033,12 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
Op::Compact => {
handlers::compact(&sess, sub.id.clone()).await;
}
Op::DropMemories => {
handlers::drop_memories(&sess, &config, sub.id.clone()).await;
}
Op::UpdateMemories => {
handlers::update_memories(&sess, &config, sub.id.clone()).await;
}
Op::ThreadRollback { num_turns } => {
handlers::thread_rollback(&sess, sub.id.clone(), num_turns).await;
}
@@ -3582,6 +3588,68 @@ mod handlers {
.await;
}
pub async fn drop_memories(sess: &Arc<Session>, config: &Arc<Config>, sub_id: String) {
let mut errors = Vec::new();
if let Some(state_db) = sess.services.state_db.as_deref() {
if let Err(err) = state_db.clear_memory_data().await {
errors.push(format!("failed clearing memory rows from state db: {err}"));
}
} else {
errors.push("state db unavailable; memory rows were not cleared".to_string());
}
let memory_root = crate::memories::memory_root(&config.codex_home);
if let Err(err) = tokio::fs::remove_dir_all(&memory_root).await
&& err.kind() != std::io::ErrorKind::NotFound
{
errors.push(format!(
"failed removing memory directory {}: {err}",
memory_root.display()
));
}
if errors.is_empty() {
sess.send_event_raw(Event {
id: sub_id,
msg: EventMsg::Warning(WarningEvent {
message: format!(
"Dropped memories at {} and cleared memory rows from state db.",
memory_root.display()
),
}),
})
.await;
return;
}
sess.send_event_raw(Event {
id: sub_id,
msg: EventMsg::Error(ErrorEvent {
message: format!("Memory drop completed with errors: {}", errors.join("; ")),
codex_error_info: Some(CodexErrorInfo::Other),
}),
})
.await;
}
pub async fn update_memories(sess: &Arc<Session>, config: &Arc<Config>, sub_id: String) {
let session_source = {
let state = sess.state.lock().await;
state.session_configuration.session_source.clone()
};
crate::memories::start_memories_startup_task(sess, Arc::clone(config), &session_source);
sess.send_event_raw(Event {
id: sub_id.clone(),
msg: EventMsg::Warning(WarningEvent {
message: "Memory update triggered.".to_string(),
}),
})
.await;
}
pub async fn thread_rollback(sess: &Arc<Session>, sub_id: String, num_turns: u32) {
if num_turns == 0 {
sess.send_event_raw(Event {
+6
View File
@@ -290,6 +290,12 @@ pub enum Op {
/// to generate a summary which will be returned as an AgentMessage event.
Compact,
/// Drop all persisted memory artifacts and memory-tracking DB rows.
DropMemories,
/// Trigger a single pass of the startup memory pipeline.
UpdateMemories,
/// Set a user-facing thread name in the persisted rollout metadata.
/// This is a local-only operation handled by codex-core; it does not
/// involve the model.
+26
View File
@@ -18,6 +18,32 @@ const MEMORY_CONSOLIDATION_JOB_KEY: &str = "global";
const DEFAULT_RETRY_REMAINING: i64 = 3;
impl StateRuntime {
pub async fn clear_memory_data(&self) -> anyhow::Result<()> {
let mut tx = self.pool.begin().await?;
sqlx::query(
r#"
DELETE FROM stage1_outputs
"#,
)
.execute(&mut *tx)
.await?;
sqlx::query(
r#"
DELETE FROM jobs
WHERE kind = ? OR kind = ?
"#,
)
.bind(JOB_KIND_MEMORY_STAGE1)
.bind(JOB_KIND_MEMORY_CONSOLIDATE_GLOBAL)
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}
pub async fn claim_stage1_jobs_for_startup(
&self,
current_thread_id: ThreadId,
+6
View File
@@ -3398,6 +3398,12 @@ impl ChatWidget {
SlashCommand::Clean => {
self.clean_background_terminals();
}
SlashCommand::MemoryDrop => {
self.submit_op(Op::DropMemories);
}
SlashCommand::MemoryUpdate => {
self.submit_op(Op::UpdateMemories);
}
SlashCommand::Mcp => {
self.add_mcp_output();
}
+18
View File
@@ -3380,6 +3380,24 @@ async fn slash_clean_submits_background_terminal_cleanup() {
);
}
#[tokio::test]
async fn slash_memory_drop_submits_drop_memories_op() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await;
chat.dispatch_command(SlashCommand::MemoryDrop);
assert_matches!(op_rx.try_recv(), Ok(Op::DropMemories));
}
#[tokio::test]
async fn slash_memory_update_submits_update_memories_op() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await;
chat.dispatch_command(SlashCommand::MemoryUpdate);
assert_matches!(op_rx.try_recv(), Ok(Op::UpdateMemories));
}
#[tokio::test]
async fn slash_resume_opens_picker() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
+10 -1
View File
@@ -46,6 +46,11 @@ pub enum SlashCommand {
Clean,
Personality,
TestApproval,
// Debugging commands.
#[strum(serialize = "debug-m-drop")]
MemoryDrop,
#[strum(serialize = "debug-m-update")]
MemoryUpdate,
}
impl SlashCommand {
@@ -70,6 +75,8 @@ impl SlashCommand {
SlashCommand::Statusline => "configure which items appear in the status line",
SlashCommand::Ps => "list background terminals",
SlashCommand::Clean => "stop all background terminals",
SlashCommand::MemoryDrop => "DO NOT USE",
SlashCommand::MemoryUpdate => "DO NOT USE",
SlashCommand::Model => "choose what model and reasoning effort to use",
SlashCommand::Personality => "choose a communication style for Codex",
SlashCommand::Plan => "switch to Plan mode",
@@ -118,7 +125,9 @@ impl SlashCommand {
| SlashCommand::Experimental
| SlashCommand::Review
| SlashCommand::Plan
| SlashCommand::Logout => false,
| SlashCommand::Logout
| SlashCommand::MemoryDrop
| SlashCommand::MemoryUpdate => false,
SlashCommand::Diff
| SlashCommand::Rename
| SlashCommand::Mention