Skip to main content

runtimo_core/
lib.rs

1//! Runtimo Core — Agent-centric capability runtime.
2//!
3//! Runtimo provides structured execution, resource limits, crash recovery,
4//! and two-layer telemetry (hardware + process tracking) for machines that
5//! cannot be factory-reset. Every capability execution captures before/after
6//! snapshots, enabling full audit trails and undo support.
7//!
8//! # Architecture
9//!
10//! - **Capabilities** — Pluggable operations implementing the [`Capability`] trait
11//! - **Jobs** — Lifecycle-tracked execution units (Job, [`JobState`])
12//! - **Telemetry** — Hardware awareness ([`Telemetry`])
13//! - **Process Snapshot** — Running process awareness ([`ProcessSnapshot`])
14//! - **WAL** — Append-only crash recovery log ([`WalWriter`]/[`WalReader`])
15//! - **Backup** — Undo support via pre-mutation file backups ([`BackupManager`])
16//! - **Resource Guards** — Circuit breaker via [`LlmoSafeGuard`]
17//!
18//! # Quick Start
19//!
20//! ```rust
21//! use runtimo_core::{FileRead, Capability, Context};
22//! use serde_json::json;
23//!
24//! let cap = FileRead;
25//! assert_eq!(cap.name(), "FileRead");
26//! ```
27//!
28//! # Execution with Full Telemetry
29//!
30//! ```rust,ignore
31//! use runtimo_core::{FileRead, execute_with_telemetry};
32//! use serde_json::json;
33//! use std::path::Path;
34//!
35//! let cap = FileRead;
36//! let result = execute_with_telemetry(
37//!     &cap,
38//!     &json!({"path": "/tmp/test.txt"}),
39//!     false,
40//!     Path::new("/tmp/runtimo.wal"),
41//! ).unwrap();
42//! assert!(result.success);
43//! ```
44//!
45//! # Performance (Measured on AMD EPYC 7B13)
46//!
47//! | Operation | Latency | Notes |
48//! |-----------|---------|-------|
49//! | Cold start | <1s | Binary load + init |
50//! | FileRead | <10ms | Small files (<1KB) |
51//! | FileWrite | <50ms | Includes backup copy |
52//! | Telemetry capture | <100ms | 15+ shell subprocesses |
53//! | Process snapshot | <50ms | ps aux parse |
54//! | Memory baseline | <50MB | RSS at idle |
55//!
56//! # Feature Flags
57//!
58//! No optional features currently. All functionality is included by default.
59
60pub mod backup;
61pub mod capabilities;
62pub mod capability;
63pub mod cmd;
64pub mod config;
65pub mod executor;
66pub mod job;
67pub mod llmosafe;
68pub mod monitor;
69pub mod processes;
70pub mod schema;
71pub mod session;
72pub mod telemetry;
73pub mod validation;
74pub mod wal;
75
76pub use backup::BackupManager;
77pub use capabilities::{FileRead, FileWrite, GitExec, Kill, ShellExec, Undo};
78pub use capability::{Capability, CapabilityRegistry, Context, Output};
79pub use config::RuntimoConfig;
80pub use executor::{execute_with_telemetry, execute_with_telemetry_and_session};
81pub use job::{JobId, JobState};
82pub use llmosafe::LlmoSafeGuard;
83pub use monitor::HealthMonitor;
84pub use processes::ProcessSnapshot;
85pub use telemetry::Telemetry;
86pub use wal::{WalEvent, WalEventType, WalReader, WalWriter};
87
88/// Error types for runtimo-core.
89///
90/// Covers all failure modes: state transitions, schema validation,
91/// capability execution, WAL/backup errors, resource limits, and telemetry.
92#[allow(clippy::exhaustive_enums)] // new variants are semver-breaking regardless
93#[derive(Debug, thiserror::Error)]
94pub enum Error {
95    /// Invalid job state transition attempted.
96    #[error("Invalid job state transition: {from:?} -> {to:?}")]
97    InvalidTransition { from: JobState, to: JobState },
98
99    /// JSON schema validation failed for capability arguments.
100    #[error("Schema validation failed: {0}")]
101    SchemaValidationFailed(String),
102
103    /// Requested capability not found in registry.
104    #[error("Capability not found: {0}")]
105    CapabilityNotFound(String),
106
107    /// Capability execution failed.
108    #[error("Execution failed: {0}")]
109    ExecutionFailed(String),
110
111    /// Write-Ahead Log operation failed.
112    #[error("WAL error: {0}")]
113    WalError(String),
114
115    /// Backup/restore operation failed.
116    #[error("Backup error: {0}")]
117    BackupError(String),
118
119    /// Session operation failed (create, load, save, list).
120    #[error("Session error: {0}")]
121    SessionError(String),
122
123    /// System resource limit exceeded (CPU, RAM, or zombie count).
124    #[error("Resource limit exceeded: {0}")]
125    ResourceLimitExceeded(String),
126
127    /// Telemetry capture failed.
128    #[error("Telemetry error: {0}")]
129    TelemetryError(String),
130}
131
132/// Result alias for runtimo-core operations.
133pub type Result<T> = std::result::Result<T, Error>;
134
135/// Utility functions for path management.
136pub mod utils {
137    use std::path::PathBuf;
138
139    /// Returns the data directory following XDG spec.
140    pub fn data_dir() -> PathBuf {
141        std::env::var("XDG_DATA_HOME")
142            .ok()
143            .map(PathBuf::from)
144            .or_else(|| {
145                std::env::var("HOME")
146                    .ok()
147                    .map(|h| PathBuf::from(h).join(".local/share"))
148            })
149            .unwrap_or_else(std::env::temp_dir)
150            .join("runtimo")
151    }
152
153    /// Returns the WAL path (env override or default).
154    pub fn wal_path() -> PathBuf {
155        std::env::var("RUNTIMO_WAL_PATH")
156            .map_or_else(|_| data_dir().join("wal.jsonl"), PathBuf::from)
157    }
158
159    /// Returns the backup directory (env override or default).
160    pub fn backup_dir() -> PathBuf {
161        std::env::var("RUNTIMO_BACKUP_DIR")
162            .map_or_else(|_| data_dir().join("backups"), PathBuf::from)
163    }
164
165    /// Generates a unique ID from 16 random bytes (32 hex chars).
166    ///
167    /// Uses `/dev/urandom` for collision resistance — P(collision) < 10⁻¹⁵
168    /// even at 100 IDs/sec for 1 hour. Falls back to timestamp if urandom
169    /// is unavailable (e.g., non-Linux platforms).
170    #[must_use]
171    pub fn generate_id() -> String {
172        let mut bytes = [0u8; 16];
173        if std::fs::File::open("/dev/urandom")
174            .ok()
175            .and_then(|mut f| std::io::Read::read_exact(&mut f, &mut bytes).ok())
176            .is_some()
177        {
178            #[allow(clippy::format_collect)]
179            bytes.iter().map(|b| format!("{b:02x}")).collect()
180        } else {
181            // Fallback: timestamp-based (collision possible but rare)
182            let ts = std::time::SystemTime::now()
183                .duration_since(std::time::UNIX_EPOCH)
184                .unwrap_or_default()
185                .as_nanos();
186            format!("{:x}", ts)
187        }
188    }
189}