Move agent message board cleanup into the app server (#59167)

## What changed

Move permanent agent message board deletion from `codex-core` into the app server's thread-store cleanup callback. Keep board cleanup active when the board feature is disabled, and run it before optional Guardian evidence-board cleanup.

Remove the message board extension dependency from `codex-core` and retain `codex-message-board` only as a development dependency. Update the core test harness to install the app server's cleanup callback.

GitOrigin-RevId: 61813bbdcd8b57073b8c249e1f771e3fb0ea061e
This commit is contained in:
jif
2026-11-03 14:15:56 +00:00
committed by copyberry
parent bfdbf01bfa
commit f7167ac895
7 changed files with 60 additions and 48 deletions
@@ -3,11 +3,13 @@
//! The extension owns backend construction, storage and tool registration. This adapter
//! selects configuration and supplies host capabilities. It never starts or restores
//! recipients during lookup. Notifications use ordinary agent-message scheduling.
//! Permanent board deletion uses the thread store's existing host cleanup callback.
use chrono::DateTime;
use chrono::Utc;
use codex_agent_message_board_client::RemoteAgentMessageBoard;
use codex_agent_message_board_extension::BoardBackend;
use codex_agent_message_board_extension::local_board_options;
use codex_core::CodexThread;
use codex_core::MULTI_AGENT_V2_NAMESPACE_DESCRIPTION;
use codex_core::ThreadManager;
@@ -15,6 +17,7 @@ use codex_core::config::Config;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ThreadStartInput;
use codex_features::Feature;
use codex_message_board::LocalAgentMessageBoard;
use codex_message_board::LocalBoardConnection;
use codex_message_board::MessageBoardHost;
use codex_message_board::NotificationDelivery;
@@ -27,6 +30,9 @@ use codex_protocol::error::CodexErrorDetails;
use codex_protocol::error::Result;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::Op;
use codex_state::SqliteConfig;
use codex_thread_store::ThreadStoreError;
use codex_thread_store::ThreadStoreResult;
use futures::future::BoxFuture;
use std::sync::Arc;
use std::sync::Weak;
@@ -98,6 +104,24 @@ pub fn install_agent_message_board(
);
}
/// Removes persisted agent-board data through the thread store's deletion callback.
/// Cleanup also applies when the board feature is currently disabled.
pub async fn delete_agent_message_boards(
sqlite: &SqliteConfig,
thread_ids: &[ThreadId],
) -> ThreadStoreResult<()> {
let boards = thread_ids
.iter()
.copied()
.map(Into::into)
.collect::<Vec<_>>();
LocalAgentMessageBoard::delete_boards(sqlite, &local_board_options(), &boards)
.await
.map_err(|err| ThreadStoreError::Internal {
message: format!("failed to delete agent message boards: {err}"),
})
}
#[derive(Clone)]
struct LocalBoardHost {
manager: Weak<ThreadManager>,
+1
View File
@@ -96,6 +96,7 @@ fn is_unsupported_untrusted_approval_policy_error(err: &std::io::Error) -> bool
}
mod agent_message_board;
pub use agent_message_board::delete_agent_message_boards;
pub use agent_message_board::install_agent_message_board;
mod analytics_utils;
+18 -13
View File
@@ -319,22 +319,27 @@ impl MessageProcessor {
// The thread store is intentionally process-scoped. Config reloads can
// affect per-thread behavior, but they must not move newly started,
// resumed, or forked threads to a different persistence backend/root.
let mut thread_store = ThreadStoreBuilder::from_config(config.as_ref(), state_db.clone());
if config.features.enabled(Feature::GuardianV3) {
let sqlite = config.sqlite_config().clone();
thread_store = thread_store.with_additional_cleanup(move |thread_ids| {
let sqlite = config.sqlite_config().clone();
let guardian_v3_enabled = config.features.enabled(Feature::GuardianV3);
let thread_store = ThreadStoreBuilder::from_config(config.as_ref(), state_db.clone())
.with_additional_cleanup(move |thread_ids| {
let sqlite = sqlite.clone();
Box::pin(async move {
let boards = thread_ids.into_iter().map(Into::into).collect::<Vec<_>>();
codex_guardian_v3::delete_local_boards(&sqlite, &boards)
.await
.map_err(|err| ThreadStoreError::Internal {
message: format!("failed to delete Guardian evidence boards: {err}"),
})
crate::delete_agent_message_boards(&sqlite, &thread_ids).await?;
if guardian_v3_enabled {
let boards = thread_ids.into_iter().map(Into::into).collect::<Vec<_>>();
codex_guardian_v3::delete_local_boards(&sqlite, &boards)
.await
.map_err(|err| ThreadStoreError::Internal {
message: format!(
"failed to delete Guardian evidence boards: {err}"
),
})?;
}
Ok(())
})
});
}
let thread_store = thread_store.build();
})
.build();
// Queue persistence requires SQLite, so in-memory thread stores and
// app servers without a state database do not have a queue backend.
let queue_store: Option<Arc<dyn QueueStore>> = match &config.experimental_thread_store {