Skip to main content

machi_tools/
metadata.rs

1//! Tool behavioral metadata and capability flags.
2
3use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6
7/// How a tool interacts with concurrent execution.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10#[non_exhaustive]
11pub enum ConcurrencyMode {
12    /// Safe to run alongside other non-exclusive tools.
13    ReadOnly,
14    /// May mutate; concurrent with other concurrent/read-only tools.
15    #[default]
16    Concurrent,
17    /// Must run alone.
18    Exclusive,
19}
20
21/// Destructiveness class for policy and approvals.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24#[non_exhaustive]
25pub enum Destructiveness {
26    /// No side effects of consequence.
27    #[default]
28    None,
29    /// Effects can be undone.
30    Reversible,
31    /// Permanent effects.
32    Irreversible,
33}
34
35/// Cancel behavior while a tool is running.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38#[non_exhaustive]
39pub enum InterruptBehavior {
40    /// Drop / cancel immediately.
41    #[default]
42    Cancel,
43    /// Wait for natural completion.
44    WaitComplete,
45}
46
47/// Fine-grained capability flags for filtering.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50#[non_exhaustive]
51pub enum CapabilityFlag {
52    /// Read filesystem or data sources.
53    Read,
54    /// Write filesystem or mutate state.
55    Write,
56    /// Execute shell / process.
57    Execute,
58    /// Network access.
59    Network,
60    /// Spawn nested agents.
61    Spawn,
62}
63
64/// Metadata used by dispatch and capability filters.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[non_exhaustive]
67pub struct ToolMetadata {
68    /// Concurrency class.
69    pub concurrency: ConcurrencyMode,
70    /// Destructiveness.
71    pub destructiveness: Destructiveness,
72    /// Interrupt behavior.
73    pub interrupt: InterruptBehavior,
74    /// Optional execution timeout.
75    pub timeout: Option<Duration>,
76    /// Capability flags required/advertised.
77    pub capabilities: Vec<CapabilityFlag>,
78    /// Optional per-tool concurrency cap (alongside [`ConcurrencyMode`]).
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub max_concurrency: Option<usize>,
81}
82
83impl Default for ToolMetadata {
84    fn default() -> Self {
85        Self {
86            concurrency: ConcurrencyMode::Concurrent,
87            destructiveness: Destructiveness::None,
88            interrupt: InterruptBehavior::Cancel,
89            timeout: None,
90            capabilities: Vec::new(),
91            max_concurrency: None,
92        }
93    }
94}
95
96impl ToolMetadata {
97    /// Read-only tool defaults.
98    #[must_use]
99    pub fn read_only() -> Self {
100        Self {
101            concurrency: ConcurrencyMode::ReadOnly,
102            capabilities: vec![CapabilityFlag::Read],
103            ..Self::default()
104        }
105    }
106
107    /// Exclusive mutating tool defaults.
108    #[must_use]
109    pub fn exclusive_write() -> Self {
110        Self {
111            concurrency: ConcurrencyMode::Exclusive,
112            destructiveness: Destructiveness::Reversible,
113            capabilities: vec![CapabilityFlag::Write],
114            ..Self::default()
115        }
116    }
117
118    /// Nested-agent spawn tool defaults (concurrent, non-destructive).
119    #[must_use]
120    pub fn spawn() -> Self {
121        Self {
122            concurrency: ConcurrencyMode::Concurrent,
123            destructiveness: Destructiveness::None,
124            capabilities: vec![CapabilityFlag::Spawn],
125            ..Self::default()
126        }
127    }
128
129    /// Exclusive shell / process execution defaults.
130    #[must_use]
131    pub fn shell_execute(timeout: Duration) -> Self {
132        Self {
133            concurrency: ConcurrencyMode::Exclusive,
134            destructiveness: Destructiveness::Reversible,
135            interrupt: InterruptBehavior::Cancel,
136            timeout: Some(timeout),
137            capabilities: vec![CapabilityFlag::Execute, CapabilityFlag::Write],
138            max_concurrency: Some(1),
139        }
140    }
141
142    /// Set per-tool concurrency cap.
143    #[must_use]
144    pub const fn with_max_concurrency(mut self, n: Option<usize>) -> Self {
145        self.max_concurrency = n;
146        self
147    }
148
149    /// True when the tool is admissible under a read-only capability mode.
150    #[must_use]
151    pub fn allowed_in_read_only(&self) -> bool {
152        !self.capabilities.iter().any(|c| {
153            matches!(
154                c,
155                CapabilityFlag::Write | CapabilityFlag::Execute | CapabilityFlag::Spawn
156            )
157        }) && self.destructiveness == Destructiveness::None
158            && self.concurrency != ConcurrencyMode::Exclusive
159    }
160}