weavegraph 0.7.0

Graph-driven, concurrent agent workflow framework with versioned state, deterministic barrier merges, and rich diagnostics.
Documentation
//! Core domain types for workflow graphs.
//!
//! Defines [`NodeKind`] (node identity) and [`ChannelType`] (state channel category).
//! For runtime execution types such as session IDs and step numbers, see
//! [`crate::runtimes::types`].

use serde::{Deserialize, Serialize};
use std::fmt;

/// Node identity within a workflow graph.
///
/// `NodeKind` labels each node in the execution graph. `Start` and `End` are virtual
/// bookend nodes with no user implementation; `Custom` covers all application-defined
/// nodes. Supports serde and the [`encode`](Self::encode)/[`decode`](Self::decode)
/// round-trip for persistence.
///
/// ```rust
/// use weavegraph::types::NodeKind;
///
/// let processor = NodeKind::Custom("DataProcessor".to_string());
/// assert_eq!(NodeKind::decode(&processor.encode()), processor);
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NodeKind {
    /// Virtual entry node. Has no incoming edges; seeds the initial frontier.
    Start,
    /// Virtual terminal node. Has no outgoing edges; signals branch completion.
    End,
    /// Application-defined node identified by a user-supplied name.
    Custom(String),
}

impl NodeKind {
    /// Encode into a persisted string: `"Start"`, `"End"`, or `"Custom:X"`.
    ///
    /// ```rust
    /// # use weavegraph::types::NodeKind;
    /// assert_eq!(NodeKind::Start.encode(), "Start");
    /// assert_eq!(NodeKind::Custom("Processor".to_string()).encode(), "Custom:Processor");
    /// ```
    #[must_use]
    pub fn encode(&self) -> String {
        match self {
            Self::Start => "Start".to_owned(),
            Self::End => "End".to_owned(),
            Self::Custom(s) => format!("Custom:{s}"),
        }
    }

    /// Decode a persisted string back into a `NodeKind`.
    ///
    /// Unrecognized strings that lack a `"Custom:"` prefix are treated as
    /// `Custom(s)` for forward compatibility.
    ///
    /// ```rust
    /// # use weavegraph::types::NodeKind;
    /// assert_eq!(NodeKind::decode("Start"), NodeKind::Start);
    /// assert_eq!(NodeKind::decode("Custom:Processor"), NodeKind::Custom("Processor".to_string()));
    /// assert_eq!(NodeKind::decode("Unknown"), NodeKind::Custom("Unknown".to_string()));
    /// ```
    pub fn decode(s: &str) -> Self {
        match s {
            "Start" => Self::Start,
            "End" => Self::End,
            _ => Self::Custom(s.strip_prefix("Custom:").unwrap_or(s).to_owned()),
        }
    }

    /// Return `true` if this is a [`Start`](Self::Start) node.
    #[must_use]
    pub fn is_start(&self) -> bool {
        matches!(self, Self::Start)
    }

    /// Return `true` if this is an [`End`](Self::End) node.
    #[must_use]
    pub fn is_end(&self) -> bool {
        matches!(self, Self::End)
    }

    /// Return `true` if this is a [`Custom`](Self::Custom) node.
    #[must_use]
    pub fn is_custom(&self) -> bool {
        matches!(self, Self::Custom(_))
    }

    /// Return the `Display` string of this node for use as a conditional-edge target.
    #[must_use]
    pub fn as_target(&self) -> String {
        self.to_string()
    }

    /// Return the canonical target string for the `Start` endpoint.
    #[must_use]
    pub fn start_target() -> String {
        "Start".to_owned()
    }

    /// Return the canonical target string for the `End` endpoint.
    #[must_use]
    pub fn end_target() -> String {
        "End".to_owned()
    }
}

impl fmt::Display for NodeKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Start => f.write_str("Start"),
            Self::End => f.write_str("End"),
            Self::Custom(name) => f.write_str(name),
        }
    }
}

impl From<&str> for NodeKind {
    fn from(s: &str) -> Self {
        match s {
            "Start" => Self::Start,
            "End" => Self::End,
            other => Self::Custom(other.to_owned()),
        }
    }
}

/// State channel category.
///
/// Each variant corresponds to an independent channel in [`VersionedState`](crate::state::VersionedState)
/// with its own reducer and versioning semantics.
///
/// ```rust
/// use weavegraph::types::ChannelType;
/// assert_eq!(ChannelType::Message.to_string(), "message");
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ChannelType {
    /// Conversation messages: user inputs, assistant responses, system notifications.
    Message,
    /// Error events: fatal halts and non-fatal diagnostics.
    Error,
    /// Custom key-value metadata and intermediate node outputs.
    Extra,
}

impl fmt::Display for ChannelType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Message => f.write_str("message"),
            Self::Error => f.write_str("error"),
            Self::Extra => f.write_str("extra"),
        }
    }
}