Files
openfang/crates/openfang-kernel/src/supervisor.rs
T
jaberjaber23 5692c96494 Initial commit — OpenFang Agent Operating System
Open-source Agent OS built in Rust.

- 14 crates, 1,767+ tests, zero clippy warnings
- 7 autonomous Hands (Clip, Lead, Collector, Predictor, Researcher, Twitter, Browser)
- 16 security systems (WASM sandbox, Merkle audit trail, taint tracking, Ed25519 signing, SSRF protection, secret zeroization, HMAC-SHA256 mutual auth, and more)
- 30 pre-built agents across 4 performance tiers
- 40 channel adapters (Telegram, Discord, Slack, WhatsApp, Teams, and 35 more)
- 38 built-in tools + MCP client/server + A2A protocol
- 26 LLM providers with intelligent routing and cost tracking
- 60+ bundled skills with FangHub marketplace
- Tauri 2.0 native desktop app
- 140+ REST/WS/SSE API endpoints with Alpine.js dashboard
- OpenAI-compatible /v1/chat/completions endpoint
- One-command install, production-ready
2026-02-26 01:00:27 +03:00

228 lines
6.8 KiB
Rust

//! Process supervision — graceful shutdown, signal handling, and health monitoring.
use dashmap::DashMap;
use openfang_types::agent::AgentId;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::watch;
use tracing::{info, warn};
/// Shutdown signal manager with health monitoring.
pub struct Supervisor {
/// Send side of the shutdown signal.
shutdown_tx: watch::Sender<bool>,
/// Receive side of the shutdown signal (clonable).
shutdown_rx: watch::Receiver<bool>,
/// Restart count (how many times agents have been restarted).
restart_count: AtomicU64,
/// Total panics caught across all agents.
panic_count: AtomicU64,
/// Per-agent restart counts for enforcing max_restarts.
agent_restarts: DashMap<AgentId, u32>,
}
impl Supervisor {
/// Create a new supervisor.
pub fn new() -> Self {
let (tx, rx) = watch::channel(false);
Self {
shutdown_tx: tx,
shutdown_rx: rx,
restart_count: AtomicU64::new(0),
panic_count: AtomicU64::new(0),
agent_restarts: DashMap::new(),
}
}
/// Get a receiver that will be notified on shutdown.
pub fn subscribe(&self) -> watch::Receiver<bool> {
self.shutdown_rx.clone()
}
/// Trigger a graceful shutdown.
pub fn shutdown(&self) {
info!("Supervisor: initiating graceful shutdown");
let _ = self.shutdown_tx.send(true);
}
/// Check if shutdown has been requested.
pub fn is_shutting_down(&self) -> bool {
*self.shutdown_rx.borrow()
}
/// Record that a panic was caught during agent execution.
pub fn record_panic(&self) {
self.panic_count.fetch_add(1, Ordering::Relaxed);
warn!(
total_panics = self.panic_count.load(Ordering::Relaxed),
"Agent panic recorded"
);
}
/// Record that an agent was restarted.
pub fn record_restart(&self) {
self.restart_count.fetch_add(1, Ordering::Relaxed);
}
/// Get the total number of panics caught.
pub fn panic_count(&self) -> u64 {
self.panic_count.load(Ordering::Relaxed)
}
/// Get the total number of restarts.
pub fn restart_count(&self) -> u64 {
self.restart_count.load(Ordering::Relaxed)
}
/// Record a restart for a specific agent and check if limit is exceeded.
///
/// Returns Ok(restart_count) if within limit, or Err(count) if limit exceeded.
pub fn record_agent_restart(&self, agent_id: AgentId, max_restarts: u32) -> Result<u32, u32> {
let mut count = self.agent_restarts.entry(agent_id).or_insert(0);
*count += 1;
self.record_restart();
if max_restarts > 0 && *count > max_restarts {
warn!(
agent = %agent_id,
restarts = *count,
max = max_restarts,
"Agent exceeded max restart limit"
);
Err(*count)
} else {
Ok(*count)
}
}
/// Get the restart count for a specific agent.
pub fn agent_restart_count(&self, agent_id: AgentId) -> u32 {
self.agent_restarts.get(&agent_id).map(|r| *r).unwrap_or(0)
}
/// Reset restart counter for an agent (e.g., on manual intervention).
pub fn reset_agent_restarts(&self, agent_id: AgentId) {
self.agent_restarts.remove(&agent_id);
}
/// Get a health summary.
pub fn health(&self) -> SupervisorHealth {
SupervisorHealth {
is_shutting_down: self.is_shutting_down(),
panic_count: self.panic_count(),
restart_count: self.restart_count(),
}
}
}
impl Default for Supervisor {
fn default() -> Self {
Self::new()
}
}
/// Health report from the supervisor.
#[derive(Debug, Clone)]
pub struct SupervisorHealth {
pub is_shutting_down: bool,
pub panic_count: u64,
pub restart_count: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shutdown() {
let supervisor = Supervisor::new();
assert!(!supervisor.is_shutting_down());
supervisor.shutdown();
assert!(supervisor.is_shutting_down());
}
#[test]
fn test_subscribe() {
let supervisor = Supervisor::new();
let rx = supervisor.subscribe();
assert!(!*rx.borrow());
supervisor.shutdown();
assert!(rx.has_changed().unwrap());
}
#[test]
fn test_panic_tracking() {
let supervisor = Supervisor::new();
assert_eq!(supervisor.panic_count(), 0);
supervisor.record_panic();
supervisor.record_panic();
assert_eq!(supervisor.panic_count(), 2);
}
#[test]
fn test_restart_tracking() {
let supervisor = Supervisor::new();
assert_eq!(supervisor.restart_count(), 0);
supervisor.record_restart();
assert_eq!(supervisor.restart_count(), 1);
}
#[test]
fn test_health() {
let supervisor = Supervisor::new();
let health = supervisor.health();
assert!(!health.is_shutting_down);
assert_eq!(health.panic_count, 0);
assert_eq!(health.restart_count, 0);
}
#[test]
fn test_agent_restart_within_limit() {
let supervisor = Supervisor::new();
let agent_id = AgentId::new();
// Allow up to 3 restarts
assert!(supervisor.record_agent_restart(agent_id, 3).is_ok());
assert_eq!(supervisor.agent_restart_count(agent_id), 1);
assert!(supervisor.record_agent_restart(agent_id, 3).is_ok());
assert!(supervisor.record_agent_restart(agent_id, 3).is_ok());
assert_eq!(supervisor.agent_restart_count(agent_id), 3);
}
#[test]
fn test_agent_restart_exceeds_limit() {
let supervisor = Supervisor::new();
let agent_id = AgentId::new();
assert!(supervisor.record_agent_restart(agent_id, 2).is_ok());
assert!(supervisor.record_agent_restart(agent_id, 2).is_ok());
// 3rd restart exceeds max_restarts=2
let result = supervisor.record_agent_restart(agent_id, 2);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), 3);
}
#[test]
fn test_agent_restart_zero_limit_unlimited() {
let supervisor = Supervisor::new();
let agent_id = AgentId::new();
// max_restarts=0 means unlimited
for _ in 0..100 {
assert!(supervisor.record_agent_restart(agent_id, 0).is_ok());
}
}
#[test]
fn test_reset_agent_restarts() {
let supervisor = Supervisor::new();
let agent_id = AgentId::new();
supervisor.record_agent_restart(agent_id, 10).unwrap();
supervisor.record_agent_restart(agent_id, 10).unwrap();
assert_eq!(supervisor.agent_restart_count(agent_id), 2);
supervisor.reset_agent_restarts(agent_id);
assert_eq!(supervisor.agent_restart_count(agent_id), 0);
}
}