code-mode: linearize cell terminal state (#29286)

## Summary

- Introduce a single cell terminal-state machine for completion and
termination.
- Make stored-value commits atomic with the winning terminal outcome.
- Buffer terminal results for later observation and cover
termination-before-commit behavior.

## Why

Completion, termination, observation, and stored-value updates must
agree on one linearized outcome under cancellation races.

## Impact

Terminal delivery becomes deterministic and terminated cells cannot
commit state after termination wins.

## Validation

- Focused terminal-state regression passed.
- Stack-tip validation: `just test -p codex-code-mode -p
codex-code-mode-protocol` (70 passed).
- Parent branch:
`cconger/code-mode-runtime-compact-03b-session-runtime`.
This commit is contained in:
Channing Conger
2026-06-21 12:05:24 -07:00
committed by GitHub
parent 63f009e9da
commit f774455c3a
5 changed files with 640 additions and 193 deletions
+30 -9
View File
@@ -29,7 +29,9 @@ use crate::cell_actor::CellError;
use crate::cell_actor::CellEventFuture;
use crate::cell_actor::CellHandle;
use crate::cell_actor::CellHost;
use crate::cell_actor::CellState;
use crate::cell_actor::CellToolCall;
use crate::cell_actor::CompletionCommit;
type RuntimeEventFuture = Pin<Box<dyn Future<Output = Result<CellEvent, Error>> + Send + 'static>>;
@@ -165,9 +167,15 @@ impl<D: SessionRuntimeDelegate> SessionRuntime<D> {
if cells.contains_key(&cell_id) {
return Err(Error::DuplicateCell(cell_id));
}
let (handle, initial_event, task) =
CellActor::prepare(request, stored_values, host, initial_observe_mode)
.map_err(Error::Runtime)?;
let cell_state = Arc::new(CellState::new(CancellationToken::new()));
let (handle, initial_event, task) = CellActor::prepare(
request,
stored_values,
host,
initial_observe_mode,
cell_state,
)
.map_err(Error::Runtime)?;
cells.insert(cell_id.clone(), handle);
drop(cells);
tokio::spawn(task);
@@ -251,12 +259,21 @@ impl<D: SessionRuntimeDelegate> CellHost for RuntimeCellHost<D> {
.await
}
async fn commit_stored_values(&self, stored_value_writes: HashMap<String, JsonValue>) {
self.inner
.stored_values
.lock()
.await
.extend(stored_value_writes);
async fn commit_completion(
&self,
stored_value_writes: HashMap<String, JsonValue>,
event: CellEvent,
cell_state: Arc<CellState>,
) -> CompletionCommit {
let cancellation_token = cell_state.cancellation_token();
let mut stored_values = tokio::select! {
biased;
_ = cancellation_token.cancelled() => {
return CompletionCommit::Rejected(event);
}
stored_values = self.inner.stored_values.lock() => stored_values,
};
cell_state.commit_completion(event, || stored_values.extend(stored_value_writes))
}
async fn closed(&self) {
@@ -276,3 +293,7 @@ fn actor_error(cell_id: &CellId, error: CellError) -> Error {
CellError::Closed => Error::ClosedCell(cell_id.clone()),
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;