use anyhow::{Context, Result};
use crate::session_manager::StandaloneSession;
use mj_core::config::{Config, HarnessKind};
use mj_core::relay::RelayCommand;
use mj_core::state::SessionRecord;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NativeContinuityAction {
Unchanged,
Adopt,
AdoptAndHandOff,
}
pub(crate) fn native_continuity_action(
harness: HarnessKind,
recorded: Option<&str>,
reported: &str,
continuity_lost: bool,
) -> NativeContinuityAction {
if recorded == Some(reported) || harness.captures_native_session() {
return NativeContinuityAction::Unchanged;
}
if continuity_lost {
NativeContinuityAction::AdoptAndHandOff
} else {
NativeContinuityAction::Adopt
}
}
pub(crate) struct NativeContinuityInputs {
pub harness: HarnessKind,
pub recorded_native_session_id: Option<String>,
pub context_bytes: usize,
}
impl NativeContinuityInputs {
pub(crate) fn from_record(config: &Config, record: &SessionRecord) -> Self {
Self {
harness: record.harness_kind,
recorded_native_session_id: record.native_session_id.clone(),
context_bytes: config
.profiles
.get(&record.last_profile)
.and_then(|profile| profile.context_window_bytes)
.unwrap_or(crate::compaction::DEFAULT_CONTEXT_BYTES),
}
}
pub(crate) fn load(session_id: &str) -> Result<Self> {
let state = crate::database::load_state()
.context("read the durable session before recovering native continuity")?;
let record = state
.sessions
.get(session_id)
.with_context(|| format!("unknown session {session_id}"))?;
let config = Config::load().unwrap_or_default();
Ok(Self::from_record(&config, record))
}
}
pub(crate) async fn recover_native_continuity(
session_id: &str,
inputs: &NativeContinuityInputs,
connection: &mut StandaloneSession,
) -> Result<()> {
let snapshot = connection
.sync()
.await
.context("read the reconnected worker state before checking native continuity")?;
let Some(reported) = snapshot.operational.native_session_id.clone() else {
return Ok(());
};
let action = native_continuity_action(
inputs.harness,
inputs.recorded_native_session_id.as_deref(),
&reported,
snapshot.operational.native_continuity_lost,
);
if action == NativeContinuityAction::Unchanged {
return Ok(());
}
{
let session_id = session_id.to_owned();
let reported = reported.clone();
tokio::task::spawn_blocking(move || {
crate::database::adopt_native_session_id(&session_id, &reported)
})
.await
.context("join the native session id write")?
.context("record the native session the restarted worker opened")?;
}
tracing::warn!(
session_id,
native_session_id = %reported,
recorded = ?inputs.recorded_native_session_id,
handing_over = action == NativeContinuityAction::AdoptAndHandOff,
"the restarted worker opened a different native session"
);
if action == NativeContinuityAction::Adopt {
return Ok(());
}
push_session_notice(
session_id,
connection,
"The agent could not reload its own session, so it was restarted fresh. \
The conversation so far is being handed to it as context.",
)
.await;
let handoff = mj_transcript::projection::canonical_session_from_materialized(
&snapshot.materialized,
)
.map(|canonical| crate::compaction::render_recent_snapshot(&canonical, inputs.context_bytes));
let installed = match handoff {
Ok(text) => connection.install_prompt_context(text).await,
Err(error) => Err(error),
};
if let Err(error) = installed {
tracing::warn!(
session_id,
error = format!("{error:#}"),
"could not hand the conversation to the restarted native session"
);
push_session_notice(
session_id,
connection,
"The conversation could not be handed to the restarted agent; it starts without prior context.",
)
.await;
}
Ok(())
}
async fn push_session_notice(session_id: &str, connection: &mut StandaloneSession, text: &str) {
let submitted = async {
let command_id = crate::session_manager::new_command_id("native-continuity")?;
connection
.submit(
command_id,
RelayCommand::RecordNotice {
text: text.to_owned(),
},
)
.await
}
.await;
if let Err(error) = submitted {
tracing::warn!(
session_id,
error = format!("{error:#}"),
"could not record a native continuity notice in the conversation"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_harness_without_native_state_adopts_a_restarted_workers_session() {
assert_eq!(
native_continuity_action(HarnessKind::Zcode, Some("old"), "new", true),
NativeContinuityAction::AdoptAndHandOff
);
assert_eq!(
native_continuity_action(HarnessKind::Zcode, Some("old"), "new", false),
NativeContinuityAction::Adopt
);
assert_eq!(
native_continuity_action(HarnessKind::Zcode, None, "new", true),
NativeContinuityAction::AdoptAndHandOff
);
assert_eq!(
native_continuity_action(HarnessKind::Zcode, Some("same"), "same", true),
NativeContinuityAction::Unchanged
);
assert_eq!(
native_continuity_action(HarnessKind::Codex, Some("old"), "new", true),
NativeContinuityAction::Unchanged
);
}
}