Skip to main content

nous_core/
taxonomy.rs

1//! Evaluation layer taxonomy.
2//!
3//! Categorizes evaluators into distinct layers of agent behavior.
4//! Each layer measures a different aspect of quality.
5
6use serde::{Deserialize, Serialize};
7
8/// The layer of agent behavior being evaluated.
9///
10/// Each evaluator belongs to exactly one layer. Layers enable
11/// aggregation and filtering of scores by concern.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum EvalLayer {
15    /// Reasoning quality — coherence, completeness, logical soundness.
16    Reasoning,
17    /// Action quality — tool usage correctness, argument validity.
18    Action,
19    /// Execution quality — efficiency, iteration count, token usage.
20    Execution,
21    /// Safety — policy compliance, blocklist checks, capability enforcement.
22    Safety,
23    /// Cost — budget adherence, spend velocity, resource efficiency.
24    Cost,
25}
26
27impl EvalLayer {
28    /// Human-readable label for the layer.
29    pub fn label(&self) -> &'static str {
30        match self {
31            Self::Reasoning => "reasoning",
32            Self::Action => "action",
33            Self::Execution => "execution",
34            Self::Safety => "safety",
35            Self::Cost => "cost",
36        }
37    }
38}
39
40impl std::fmt::Display for EvalLayer {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.write_str(self.label())
43    }
44}
45
46/// When in the agent lifecycle an evaluator runs.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum EvalTiming {
50    /// Runs inline in the middleware hook (< 2ms budget).
51    Inline,
52    /// Runs asynchronously after the hook returns.
53    Async,
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn eval_layer_display() {
62        assert_eq!(EvalLayer::Reasoning.to_string(), "reasoning");
63        assert_eq!(EvalLayer::Action.to_string(), "action");
64        assert_eq!(EvalLayer::Execution.to_string(), "execution");
65        assert_eq!(EvalLayer::Safety.to_string(), "safety");
66        assert_eq!(EvalLayer::Cost.to_string(), "cost");
67    }
68
69    #[test]
70    fn eval_layer_serde_roundtrip() {
71        let layer = EvalLayer::Reasoning;
72        let json = serde_json::to_string(&layer).unwrap();
73        assert_eq!(json, "\"reasoning\"");
74        let back: EvalLayer = serde_json::from_str(&json).unwrap();
75        assert_eq!(back, layer);
76    }
77
78    #[test]
79    fn eval_timing_serde_roundtrip() {
80        let timing = EvalTiming::Inline;
81        let json = serde_json::to_string(&timing).unwrap();
82        let back: EvalTiming = serde_json::from_str(&json).unwrap();
83        assert_eq!(back, timing);
84    }
85
86    #[test]
87    fn all_layers_have_labels() {
88        let layers = [
89            EvalLayer::Reasoning,
90            EvalLayer::Action,
91            EvalLayer::Execution,
92            EvalLayer::Safety,
93            EvalLayer::Cost,
94        ];
95        for layer in layers {
96            assert!(!layer.label().is_empty());
97        }
98    }
99}