meerkat-mob 0.5.2

Multi-agent orchestration runtime for Meerkat
Documentation
use crate::roster::{MemberState, MobMemberKickoffSnapshot};
use crate::runtime::handle::{
    HelperResult, MobMemberSnapshot, MobMemberStatus, MobPeerConnectivitySnapshot,
};
use meerkat_core::types::SessionId;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CanonicalMemberStatus {
    Unknown,
    Active,
    Retiring,
    Broken,
    Completed,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CanonicalSessionObservation {
    Active,
    Inactive,
    Missing,
    Unknown,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum MobMemberTerminalClass {
    Running,
    TerminalFailure,
    TerminalUnknown,
    TerminalCompleted,
}

#[derive(Debug, Clone)]
pub(super) struct CanonicalMemberSnapshotMaterial {
    pub(super) member_present: bool,
    pub(super) status: CanonicalMemberStatus,
    pub(super) session_observation: CanonicalSessionObservation,
    pub(super) error: Option<String>,
    pub(super) output_preview: Option<String>,
    pub(super) tokens_used: u64,
    pub(super) current_session_id: Option<SessionId>,
    pub(super) peer_connectivity: Option<MobPeerConnectivitySnapshot>,
    pub(super) kickoff: Option<MobMemberKickoffSnapshot>,
}

impl CanonicalMemberSnapshotMaterial {
    pub(super) fn to_snapshot(&self) -> MobMemberSnapshot {
        let status = match self.status {
            CanonicalMemberStatus::Unknown => MobMemberStatus::Unknown,
            CanonicalMemberStatus::Active => MobMemberStatus::Active,
            CanonicalMemberStatus::Retiring => MobMemberStatus::Retiring,
            CanonicalMemberStatus::Broken => MobMemberStatus::Broken,
            CanonicalMemberStatus::Completed => MobMemberStatus::Completed,
        };
        let is_final = MobMemberLifecycleAuthority::is_terminal(self);
        MobMemberSnapshot {
            status,
            output_preview: self.output_preview.clone(),
            error: self.error.clone(),
            tokens_used: self.tokens_used,
            is_final,
            current_session_id: self.current_session_id.clone(),
            peer_connectivity: self.peer_connectivity.clone(),
            kickoff: self.kickoff.clone(),
        }
    }

    pub(super) fn to_helper_result(&self) -> HelperResult {
        HelperResult {
            output: self.output_preview.clone(),
            tokens_used: self.tokens_used,
            session_id: self.current_session_id.clone(),
        }
    }
}

#[derive(Debug, Clone)]
pub(super) struct MobMemberLifecycleInput {
    pub(super) member_present: bool,
    pub(super) roster_state: Option<MemberState>,
    pub(super) session_observation: CanonicalSessionObservation,
    pub(super) restore_failure: Option<String>,
    pub(super) output_preview: Option<String>,
    pub(super) tokens_used: u64,
    pub(super) current_session_id: Option<SessionId>,
    pub(super) peer_connectivity: Option<MobPeerConnectivitySnapshot>,
    pub(super) kickoff: Option<MobMemberKickoffSnapshot>,
}

pub(super) struct MobMemberLifecycleAuthority;

impl MobMemberLifecycleAuthority {
    pub(super) fn materialize(input: MobMemberLifecycleInput) -> CanonicalMemberSnapshotMaterial {
        if let Some(reason) = input.restore_failure {
            return CanonicalMemberSnapshotMaterial {
                member_present: input.member_present,
                status: if input.member_present {
                    CanonicalMemberStatus::Broken
                } else {
                    CanonicalMemberStatus::Unknown
                },
                session_observation: CanonicalSessionObservation::Missing,
                error: Some(reason),
                output_preview: None,
                tokens_used: 0,
                current_session_id: input.current_session_id,
                peer_connectivity: None,
                kickoff: input.kickoff,
            };
        }

        let status = match (
            input.member_present,
            input.roster_state,
            input.session_observation,
        ) {
            (false, _, _) => CanonicalMemberStatus::Unknown,
            (true, Some(MemberState::Retiring), _) => CanonicalMemberStatus::Retiring,
            (true, Some(MemberState::Active), CanonicalSessionObservation::Missing) => {
                CanonicalMemberStatus::Completed
            }
            (true, Some(MemberState::Active), _) => CanonicalMemberStatus::Active,
            (true, None, _) => CanonicalMemberStatus::Unknown,
        };

        CanonicalMemberSnapshotMaterial {
            member_present: input.member_present,
            status,
            session_observation: input.session_observation,
            error: None,
            output_preview: input.output_preview,
            tokens_used: input.tokens_used,
            current_session_id: input.current_session_id,
            peer_connectivity: input.peer_connectivity,
            kickoff: input.kickoff,
        }
    }

    pub(super) fn classify(material: &CanonicalMemberSnapshotMaterial) -> MobMemberTerminalClass {
        if !material.member_present {
            return MobMemberTerminalClass::TerminalUnknown;
        }
        match material.status {
            CanonicalMemberStatus::Retiring => MobMemberTerminalClass::Running,
            CanonicalMemberStatus::Broken => MobMemberTerminalClass::TerminalFailure,
            CanonicalMemberStatus::Active => match material.session_observation {
                CanonicalSessionObservation::Active
                | CanonicalSessionObservation::Inactive
                | CanonicalSessionObservation::Unknown => MobMemberTerminalClass::Running,
                CanonicalSessionObservation::Missing => MobMemberTerminalClass::TerminalCompleted,
            },
            CanonicalMemberStatus::Completed => MobMemberTerminalClass::TerminalCompleted,
            CanonicalMemberStatus::Unknown => MobMemberTerminalClass::TerminalUnknown,
        }
    }

    pub(super) fn is_terminal(material: &CanonicalMemberSnapshotMaterial) -> bool {
        matches!(
            Self::classify(material),
            MobMemberTerminalClass::TerminalFailure
                | MobMemberTerminalClass::TerminalUnknown
                | MobMemberTerminalClass::TerminalCompleted
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn restore_failure_breaks_present_member() {
        let material = MobMemberLifecycleAuthority::materialize(MobMemberLifecycleInput {
            member_present: true,
            roster_state: Some(MemberState::Active),
            session_observation: CanonicalSessionObservation::Active,
            restore_failure: Some("restore mismatch".into()),
            output_preview: Some("ignored".into()),
            tokens_used: 12,
            current_session_id: None,
            peer_connectivity: None,
            kickoff: None,
        });
        assert_eq!(material.status, CanonicalMemberStatus::Broken);
        assert_eq!(material.error.as_deref(), Some("restore mismatch"));
    }

    #[test]
    fn missing_active_session_means_completed_not_broken() {
        let material = MobMemberLifecycleAuthority::materialize(MobMemberLifecycleInput {
            member_present: true,
            roster_state: Some(MemberState::Active),
            session_observation: CanonicalSessionObservation::Missing,
            restore_failure: None,
            output_preview: None,
            tokens_used: 0,
            current_session_id: None,
            peer_connectivity: None,
            kickoff: None,
        });
        assert_eq!(material.status, CanonicalMemberStatus::Completed);
        assert!(MobMemberLifecycleAuthority::is_terminal(&material));
    }

    #[test]
    fn retiring_member_is_non_terminal_even_if_session_missing() {
        let material = MobMemberLifecycleAuthority::materialize(MobMemberLifecycleInput {
            member_present: true,
            roster_state: Some(MemberState::Retiring),
            session_observation: CanonicalSessionObservation::Missing,
            restore_failure: None,
            output_preview: None,
            tokens_used: 0,
            current_session_id: None,
            peer_connectivity: None,
            kickoff: None,
        });
        assert_eq!(material.status, CanonicalMemberStatus::Retiring);
        assert!(!MobMemberLifecycleAuthority::is_terminal(&material));
    }
}