microloop 0.2.0

Ultra-fast runtime safety layer for autonomous agents — detects agent tool call loops before they burn API credits
Documentation
extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use regex::Regex;
use serde_json::Value;

use crate::config::RuleConfig;

pub enum CompiledRule {
    Regex(Regex),
    Exact(String),
    JsonSchema {
        required: Vec<String>,
        schema_type: String,
    },
}

impl CompiledRule {
    pub fn matches(&self, args: &str) -> Result<(), &'static str> {
        match self {
            CompiledRule::Regex(regex) => {
                if !regex.is_match(args) {
                    return Err("Failed regex rule");
                }
                Ok(())
            }
            CompiledRule::Exact(val) => {
                if args != val {
                    return Err("Failed exact match rule");
                }
                Ok(())
            }
            CompiledRule::JsonSchema {
                required,
                schema_type,
            } => {
                let val: Value = serde_json::from_str(args).map_err(|_| "Invalid JSON")?;
                if schema_type == "object" && !val.is_object() {
                    return Err("JSON schema type mismatch (expected object)");
                }
                if schema_type == "object"
                    && let Some(obj) = val.as_object()
                    && !required.iter().all(|req| obj.contains_key(req))
                {
                    return Err("Missing required field in JSON");
                }
                Ok(())
            }
        }
    }
}

pub struct RuleEngine {
    pub rules: Vec<CompiledRule>,
}

impl RuleEngine {
    pub fn new(configs: Vec<RuleConfig>) -> Result<Self, String> {
        let mut compiled_rules = Vec::new();
        for rule in configs {
            match rule {
                RuleConfig::Regex { pattern } => {
                    let r =
                        Regex::new(&pattern).map_err(|e| alloc::format!("Regex error: {}", e))?;
                    compiled_rules.push(CompiledRule::Regex(r));
                }
                RuleConfig::Exact { value } => {
                    compiled_rules.push(CompiledRule::Exact(value));
                }
                RuleConfig::JsonSchema(schema) => {
                    let req = schema.required.unwrap_or_default();
                    let t = schema.schema_type.unwrap_or_else(|| String::from("object"));
                    compiled_rules.push(CompiledRule::JsonSchema {
                        required: req,
                        schema_type: t,
                    });
                }
            }
        }
        Ok(Self {
            rules: compiled_rules,
        })
    }

    pub fn validate(&self, args: &str) -> Result<(), &'static str> {
        for rule in &self.rules {
            rule.matches(args)?;
        }
        Ok(())
    }
}