Skip to content

code-mode: introduce durable session interface#24180

Merged
cconger merged 6 commits into
mainfrom
cconger/code-mode-session-runtime
May 29, 2026
Merged

code-mode: introduce durable session interface#24180
cconger merged 6 commits into
mainfrom
cconger/code-mode-session-runtime

Conversation

@cconger
Copy link
Copy Markdown
Contributor

@cconger cconger commented May 23, 2026

Summary

Introduce a CodeModeSession interface for executing and managing code-mode cells.

This moves cell lifecycle, callback delegation, termination, and shutdown behind a session abstraction, while continuing to use the existing in-process implementation, and the ability to implement an external process one behind this interface.

A Codex session owns one CodeModeSession, which in turn owns its running cells and stored code-mode state. Each cell is represented to the caller as a StartedCell, exposing its cell ID and initial response.

It also introduces a CodeModeSessionDelegate callback interface. A session uses the delegate to invoke nested host tools and emit notifications while a cell is running, allowing the runtime to communicate with its owning Codex session without depending directly on core turn handling.

image

@cconger cconger requested a review from a team as a code owner May 23, 2026 02:55
@cconger cconger force-pushed the cconger/code-mode-session-runtime branch 2 times, most recently from 7823471 to c883312 Compare May 28, 2026 23:25
let (response_tx, response_rx) = oneshot::channel();
self.start_session(
let cell_id = self.allocate_cell_id();
self.start_cell(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think shutdown can hang if an exec comes in?

  T1: execute() checks shutting_down == false
  T2: shutdown() sets shutting_down = true
  T2: shutdown() snapshots current cells; sees none
  T1: start_cell() inserts a new live cell
  T2: shutdown() waits until cells is empty forever

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should reject this now.

pub(super) struct CodeModeDispatchBroker {
dispatch_tx: async_channel::Sender<DispatchMessage>,
dispatch_rx: async_channel::Receiver<DispatchMessage>,
dispatch_gates: Arc<Mutex<HashMap<String, watch::Sender<bool>>>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these dont seem like they ever get cleaned up after cell completion/termination. probably not a biggy but unbounded growth

CodeModeSessionResultFuture<'a, Arc<dyn CodeModeSession>>;
pub type ToolInvocationFuture<'a> =
Pin<Box<dyn Future<Output = Result<JsonValue, String>> + Send + 'a>>;
pub type NotificationFuture<'a> = Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit but maybe just do like:

  pub type CodeModeFuture<'a, T> =
      Pin<Box<dyn Future<Output = Result<T, String>> + Send + 'a>>;

...

  fn invoke_tool<'a>(...) -> CodeModeFuture<'a, JsonValue>;
  fn notify<'a>(...) -> CodeModeFuture<'a, ()>;
  fn create_session<'a>(...) -> CodeModeFuture<'a, Arc<dyn CodeModeSession>>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so we dont need one per type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Going to leave explicit for now the pool isn't massive, can be a follow up once this is stable.

pub type NotificationFuture<'a> = Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>>;

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct CellId(String);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO if we want to introduce this instead of a bare string, lets remove the automatic conversions and make sure its updated everywhere.

session still uses a mix of CellId and String (StartedCell.cell_id vs WaitRequest.cell_id, terminate(String), etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, bit the bullet and threaded all the way through now.

Comment thread codex-rs/code-mode/src/service.rs Outdated
Comment thread codex-rs/code-mode/src/service.rs Outdated
}).await;
let delegate = Arc::clone(&inner.delegate);
let cell_id = cell_id.clone();
tokio::spawn(async move {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we're losing our cancellation token here and in a few other cases

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

its somewhat of a nit, but i dont really like bare tokio::spawn w/o some way to ensure they dont leak indefinitely

let dispatch_broker = Arc::new(CodeModeDispatchBroker::new());
Self {
inner: codex_code_mode::CodeModeService::new(),
session: Some(Arc::new(codex_code_mode::CodeModeService::with_delegate(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we swap to CodeModeSessionProvider?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this will get picked up in the next change where I start dependency injecting this, going to leave as service for now.

@cconger cconger force-pushed the cconger/code-mode-session-runtime branch from fbfc43a to c3c91e9 Compare May 29, 2026 18:19
@cconger cconger merged commit c9dc0f6 into main May 29, 2026
31 checks passed
@cconger cconger deleted the cconger/code-mode-session-runtime branch May 29, 2026 18:42
@github-actions github-actions Bot locked and limited conversation to collaborators May 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants