Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions codex-rs/app-server-protocol/schema/json/ClientRequest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2809,6 +2809,13 @@
},
"type": "object"
},
"ThreadMemoryMode": {
"enum": [
"enabled",
"disabled"
],
"type": "string"
},
"ThreadMetadataGitInfoUpdateParams": {
"properties": {
"branch": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13647,6 +13647,13 @@
"title": "ThreadLoadedListResponse",
"type": "object"
},
"ThreadMemoryMode": {
"enum": [
"enabled",
"disabled"
],
"type": "string"
},
"ThreadMetadataGitInfoUpdateParams": {
"properties": {
"branch": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11495,6 +11495,13 @@
"title": "ThreadLoadedListResponse",
"type": "object"
},
"ThreadMemoryMode": {
"enum": [
"enabled",
"disabled"
],
"type": "string"
},
"ThreadMetadataGitInfoUpdateParams": {
"properties": {
"branch": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!

// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type ThreadMemoryMode = "enabled" | "disabled";
1 change: 1 addition & 0 deletions codex-rs/app-server-protocol/schema/typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type { SessionSource } from "./SessionSource";
export type { Settings } from "./Settings";
export type { SubAgentSource } from "./SubAgentSource";
export type { ThreadId } from "./ThreadId";
export type { ThreadMemoryMode } from "./ThreadMemoryMode";
export type { Tool } from "./Tool";
export type { Verbosity } from "./Verbosity";
export type { WebSearchAction } from "./WebSearchAction";
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ client_request_definitions! {
params: v2::ThreadMetadataUpdateParams,
response: v2::ThreadMetadataUpdateResponse,
},
#[experimental("thread/memoryMode/set")]
ThreadMemoryModeSet => "thread/memoryMode/set" {
params: v2::ThreadMemoryModeSetParams,
response: v2::ThreadMemoryModeSetResponse,
},
ThreadUnarchive => "thread/unarchive" {
params: v2::ThreadUnarchiveParams,
response: v2::ThreadUnarchiveResponse,
Expand Down
37 changes: 37 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3049,6 +3049,43 @@ pub struct ThreadMetadataUpdateResponse {
pub thread: Thread,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(rename_all = "lowercase")]
pub enum ThreadMemoryMode {
Enabled,
Disabled,
}

impl ThreadMemoryMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Enabled => "enabled",
Self::Disabled => "disabled",
}
}

pub fn to_core(self) -> codex_protocol::protocol::ThreadMemoryMode {
match self {
Self::Enabled => codex_protocol::protocol::ThreadMemoryMode::Enabled,
Self::Disabled => codex_protocol::protocol::ThreadMemoryMode::Disabled,
}
}
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadMemoryModeSetParams {
pub thread_id: String,
pub mode: ThreadMemoryMode,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadMemoryModeSetResponse {}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
Expand Down
11 changes: 11 additions & 0 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Example with notification opt-out:
- `thread/loaded/list` — list the thread ids currently loaded in memory.
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
- `thread/metadata/update` — patch stored thread metadata in sqlite; currently supports updating persisted `gitInfo` fields and returns the refreshed `thread`.
- `thread/memoryMode/set` — experimental; set a thread’s persisted memory eligibility to `"enabled"` or `"disabled"` for either a loaded thread or a stored rollout; returns `{}` on success.
- `thread/status/changed` — notification emitted when a loaded thread’s status changes (`threadId` + new `status`).
- `thread/archive` — move a thread’s rollout file into the archived directory; returns `{}` on success and emits `thread/archived`.
- `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server shuts down and unloads the thread, then emits `thread/closed`.
Expand Down Expand Up @@ -395,6 +396,16 @@ Use `thread/metadata/update` to patch sqlite-backed metadata for a thread withou
} }
```

Experimental: use `thread/memoryMode/set` to change whether a thread remains eligible for future memory generation.

```json
{ "method": "thread/memoryMode/set", "id": 26, "params": {
"threadId": "thr_123",
"mode": "disabled"
} }
{ "id": 26, "result": {} }
```

### Example: Archive a thread

Use `thread/archive` to move the persisted rollout (stored as a JSONL file on disk) into the archived sessions directory.
Expand Down
118 changes: 118 additions & 0 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadListResponse;
use codex_app_server_protocol::ThreadLoadedListParams;
use codex_app_server_protocol::ThreadLoadedListResponse;
use codex_app_server_protocol::ThreadMemoryModeSetParams;
use codex_app_server_protocol::ThreadMemoryModeSetResponse;
use codex_app_server_protocol::ThreadMetadataGitInfoUpdateParams;
use codex_app_server_protocol::ThreadMetadataUpdateParams;
use codex_app_server_protocol::ThreadMetadataUpdateResponse;
Expand Down Expand Up @@ -772,6 +774,10 @@ impl CodexMessageProcessor {
self.thread_metadata_update(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadMemoryModeSet { request_id, params } => {
self.thread_memory_mode_set(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadUnarchive { request_id, params } => {
self.thread_unarchive(to_connection_request_id(request_id), params)
.await;
Expand Down Expand Up @@ -2772,6 +2778,118 @@ impl CodexMessageProcessor {
.await;
}

async fn thread_memory_mode_set(
&self,
request_id: ConnectionRequestId,
params: ThreadMemoryModeSetParams,
) {
let ThreadMemoryModeSetParams { thread_id, mode } = params;
let thread_id = match ThreadId::from_string(&thread_id) {
Ok(id) => id,
Err(err) => {
self.send_invalid_request_error(request_id, format!("invalid thread id: {err}"))
.await;
return;
}
};

if let Ok(thread) = self.thread_manager.get_thread(thread_id).await {
if thread.config_snapshot().await.ephemeral {
self.send_invalid_request_error(
request_id,
format!("ephemeral thread does not support memory mode updates: {thread_id}"),
)
.await;
return;
}

if let Err(err) = thread.set_thread_memory_mode(mode.to_core()).await {
self.send_internal_error(
request_id,
format!("failed to set thread memory mode: {err}"),
)
.await;
return;
}

self.outgoing
.send_response(request_id, ThreadMemoryModeSetResponse {})
.await;
Comment thread
jif-oai marked this conversation as resolved.
return;
}

let rollout_path =
match find_thread_path_by_id_str(&self.config.codex_home, &thread_id.to_string()).await
{
Comment thread
jif-oai marked this conversation as resolved.
Ok(Some(path)) => Some(path),
Ok(None) => None,
Err(err) => {
self.send_invalid_request_error(
request_id,
format!("failed to locate thread id {thread_id}: {err}"),
)
.await;
return;
}
};

let Some(rollout_path) = rollout_path else {
self.send_invalid_request_error(request_id, format!("thread not found: {thread_id}"))
.await;
return;
};

let mut session_meta = match read_session_meta_line(rollout_path.as_path()).await {
Ok(session_meta) => session_meta,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to set thread memory mode: {err}"),
)
.await;
return;
}
};
if session_meta.meta.id != thread_id {
self.send_internal_error(
request_id,
format!(
"failed to set thread memory mode: rollout session metadata id mismatch: expected {thread_id}, found {}",
session_meta.meta.id
),
)
.await;
return;
}
session_meta.meta.memory_mode = Some(mode.as_str().to_string());
let item = RolloutItem::SessionMeta(session_meta);

if let Err(err) = append_rollout_item_to_path(rollout_path.as_path(), &item).await {
self.send_internal_error(
request_id,
format!("failed to set thread memory mode: {err}"),
)
.await;
return;
}

let state_db_ctx = open_state_db_for_direct_thread_lookup(&self.config).await;
reconcile_rollout(
state_db_ctx.as_deref(),
rollout_path.as_path(),
self.config.model_provider_id.as_str(),
/*builder*/ None,
&[],
/*archived_only*/ None,
/*new_thread_memory_mode*/ None,
)
.await;

self.outgoing
.send_response(request_id, ThreadMemoryModeSetResponse {})
.await;
}

async fn thread_metadata_update(
&self,
request_id: ConnectionRequestId,
Expand Down
10 changes: 10 additions & 0 deletions codex-rs/app-server/tests/common/mcp_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use codex_app_server_protocol::ThreadCompactStartParams;
use codex_app_server_protocol::ThreadForkParams;
use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadLoadedListParams;
use codex_app_server_protocol::ThreadMemoryModeSetParams;
use codex_app_server_protocol::ThreadMetadataUpdateParams;
use codex_app_server_protocol::ThreadReadParams;
use codex_app_server_protocol::ThreadRealtimeAppendAudioParams;
Expand Down Expand Up @@ -583,6 +584,15 @@ impl McpProcess {
self.send_request("mock/experimentalMethod", params).await
}

/// Send a `thread/memoryMode/set` JSON-RPC request (v2, experimental).
pub async fn send_thread_memory_mode_set_request(
&mut self,
params: ThreadMemoryModeSetParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("thread/memoryMode/set", params).await
}

/// Send a `turn/start` JSON-RPC request (v2).
pub async fn send_turn_start_request(
&mut self,
Expand Down
35 changes: 35 additions & 0 deletions codex-rs/app-server/tests/suite/v2/experimental_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::MockExperimentalMethodParams;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ThreadMemoryMode;
use codex_app_server_protocol::ThreadMemoryModeSetParams;
use codex_app_server_protocol::ThreadRealtimeStartParams;
use codex_app_server_protocol::ThreadRealtimeStartTransport;
use codex_app_server_protocol::ThreadStartParams;
Expand Down Expand Up @@ -89,6 +91,39 @@ async fn realtime_conversation_start_requires_experimental_api_capability() -> R
Ok(())
}

#[tokio::test]
async fn thread_memory_mode_set_requires_experimental_api_capability() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path()).await?;

let init = mcp
.initialize_with_capabilities(
default_client_info(),
Some(InitializeCapabilities {
experimental_api: false,
opt_out_notification_methods: None,
}),
)
.await?;
let JSONRPCMessage::Response(_) = init else {
anyhow::bail!("expected initialize response, got {init:?}");
};

let request_id = mcp
.send_thread_memory_mode_set_request(ThreadMemoryModeSetParams {
thread_id: "thr_123".to_string(),
mode: ThreadMemoryMode::Disabled,
})
.await?;
let error = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert_experimental_capability_error(error, "thread/memoryMode/set");
Ok(())
}

#[tokio::test]
async fn realtime_webrtc_start_requires_experimental_api_capability() -> Result<()> {
let codex_home = TempDir::new()?;
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/tests/suite/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mod thread_archive;
mod thread_fork;
mod thread_list;
mod thread_loaded_list;
mod thread_memory_mode_set;
mod thread_metadata_update;
mod thread_name_websocket;
mod thread_read;
Expand Down
Loading
Loading