Skip to main content

oris_kernel/kernel/
action.rs

1//! Action and ActionExecutor: single channel for tools and external world (governable).
2//!
3//! Axiom: tool/LLM calls are system actions; results are recorded only as events (ActionSucceeded/ActionFailed).
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::kernel::identity::RunId;
9use crate::kernel::KernelError;
10
11/// System action: the only way the kernel interacts with the outside world.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub enum Action {
14    CallTool {
15        tool: String,
16        input: Value,
17    },
18    CallLLM {
19        provider: String,
20        input: Value,
21    },
22    Sleep {
23        millis: u64,
24    },
25    /// Human-in-the-loop or external signal.
26    WaitSignal {
27        name: String,
28    },
29}
30
31/// Result of executing an action (must be turned into events by the driver).
32#[derive(Clone, Debug)]
33pub enum ActionResult {
34    Success(Value),
35    Failure(String),
36}
37
38/// Classifies executor errors for policy (retry vs fail, backoff, rate-limit).
39#[derive(Clone, Debug)]
40pub enum ActionErrorKind {
41    /// Transient (e.g. network blip); policy may retry.
42    Transient,
43    /// Permanent (e.g. validation); do not retry.
44    Permanent,
45    /// Rate-limited (e.g. 429); retry after retry_after_ms if set.
46    RateLimited,
47}
48
49/// Structured error from action execution; used by Policy for retry decisions.
50#[derive(Clone, Debug)]
51pub struct ActionError {
52    pub kind: ActionErrorKind,
53    pub message: String,
54    pub retry_after_ms: Option<u64>,
55}
56
57impl ActionError {
58    pub fn transient(message: impl Into<String>) -> Self {
59        Self {
60            kind: ActionErrorKind::Transient,
61            message: message.into(),
62            retry_after_ms: None,
63        }
64    }
65
66    pub fn permanent(message: impl Into<String>) -> Self {
67        Self {
68            kind: ActionErrorKind::Permanent,
69            message: message.into(),
70            retry_after_ms: None,
71        }
72    }
73
74    pub fn rate_limited(message: impl Into<String>, retry_after_ms: u64) -> Self {
75        Self {
76            kind: ActionErrorKind::RateLimited,
77            message: message.into(),
78            retry_after_ms: Some(retry_after_ms),
79        }
80    }
81
82    /// Convert a generic executor error (KernelError) into an ActionError for policy.
83    /// Used by the driver when the executor returns Err(KernelError).
84    pub fn from_kernel_error(e: &KernelError) -> Self {
85        if let KernelError::Executor(ae) = e {
86            ae.clone()
87        } else {
88            Self::permanent(e.to_string())
89        }
90    }
91}
92
93impl std::fmt::Display for ActionError {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "{}", self.message)
96    }
97}
98
99/// Executes an action. The driver records ActionRequested, then calls this, then records ActionSucceeded/ActionFailed.
100/// Return `Err(KernelError::Executor(ActionError))` for structured retry decisions; other `KernelError` are treated as permanent.
101pub trait ActionExecutor: Send + Sync {
102    fn execute(&self, run_id: &RunId, action: &Action) -> Result<ActionResult, KernelError>;
103}