Skip to main content

orcs_runtime/channel/
channel.rs

1//! Channel - unit of parallel execution.
2//!
3//! A [`BaseChannel`] represents an isolated execution context within the ORCS
4//! system. Channels form a tree structure where child channels inherit
5//! context from their parents.
6//!
7//! # Configuration
8//!
9//! Channel behavior is controlled by [`ChannelConfig`]:
10//!
11//! - **priority**: Scheduling priority (0-255)
12//! - **can_spawn**: Whether child channels can be spawned
13//!
14//! # State Machine
15//!
16//! ```text
17//!                         ┌─────────┐
18//!       ┌─────────────────│ Running │─────────────────┐
19//!       │                 └────┬────┘                 │
20//!       │                      │                      │
21//!       │ pause()              │ await_approval()     │ abort()
22//!       ▼                      ▼                      ▼
23//! ┌──────────┐         ┌────────────────┐      ┌─────────┐
24//! │  Paused  │         │AwaitingApproval│      │ Aborted │
25//! └────┬─────┘         └───────┬────────┘      └─────────┘
26//!      │                       │
27//!      │ resume()              │ approve()/reject()
28//!      │                       │
29//!      └───────►  Running  ◄───┘
30//!                    │
31//!                    │ complete()
32//!                    ▼
33//!              ┌───────────┐
34//!              │ Completed │
35//!              └───────────┘
36//! ```
37//!
38//! # Example
39//!
40//! ```
41//! use orcs_runtime::{BaseChannel, ChannelConfig, ChannelCore, ChannelMut, ChannelState};
42//! use orcs_types::{ChannelId, Principal};
43//!
44//! let id = ChannelId::new();
45//! let principal = Principal::System;
46//! let mut channel = BaseChannel::new(id, None, ChannelConfig::interactive(), principal);
47//!
48//! assert!(channel.is_running());
49//! assert_eq!(channel.priority(), 255);
50//! assert!(channel.complete());
51//! assert_eq!(channel.state(), &ChannelState::Completed);
52//! ```
53
54use super::config::ChannelConfig;
55use super::traits::{ChannelCore, ChannelMut};
56use orcs_types::{ChannelId, Principal};
57use std::collections::HashSet;
58
59/// State of a channel.
60///
61/// Channels start in [`Running`](ChannelState::Running) state and can
62/// transition through various states. Terminal states are
63/// [`Completed`](ChannelState::Completed) and [`Aborted`](ChannelState::Aborted).
64///
65/// # State Transitions
66///
67/// | From | To | Method |
68/// |------|----|--------|
69/// | Running | Completed | [`ChannelMut::complete()`] |
70/// | Running | Aborted | [`ChannelMut::abort()`] |
71/// | Running | Paused | [`ChannelMut::pause()`] |
72/// | Running | AwaitingApproval | [`ChannelMut::await_approval()`] |
73/// | Paused | Running | [`ChannelMut::resume()`] |
74/// | AwaitingApproval | Running | [`ChannelMut::resolve_approval()`] |
75/// | AwaitingApproval | Aborted | [`ChannelMut::abort()`] (rejected) |
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum ChannelState {
78    /// Channel is actively running.
79    Running,
80
81    /// Channel is paused.
82    ///
83    /// Can be resumed with [`ChannelMut::resume()`].
84    Paused,
85
86    /// Channel is waiting for Human approval (HIL).
87    ///
88    /// Approval is a core part of the Channel lifecycle: LLM tool calls
89    /// require human authorization before execution. This is not an external
90    /// concern but an intrinsic state of the orchestration flow.
91    AwaitingApproval {
92        /// ID of the approval request.
93        request_id: String,
94    },
95
96    /// Channel completed successfully.
97    Completed,
98
99    /// Channel was aborted with a reason.
100    ///
101    /// The reason string provides context for debugging and logging.
102    Aborted {
103        /// Explanation of why the channel was aborted.
104        reason: String,
105    },
106}
107
108/// Base channel implementation.
109///
110/// A `BaseChannel` represents an isolated execution context that can:
111/// - Track its current state ([`ChannelState`])
112/// - Maintain parent-child relationships
113/// - Control behavior via [`ChannelConfig`]
114/// - Transition through its lifecycle
115/// - Hold a [`Principal`] for scope resolution
116///
117/// # Parent-Child Relationships
118///
119/// Channels form a tree structure:
120/// - Primary channel has no parent (`parent() == None`)
121/// - Child channels reference their parent
122/// - A channel tracks all its children
123///
124/// # Ancestor Path (DOD Optimization)
125///
126/// Each channel maintains a cached path to all ancestors for O(1) descendant checks.
127/// The path is ordered from immediate parent to root: `[parent, grandparent, ..., root]`.
128///
129/// # Configuration
130///
131/// Channel behavior is controlled by [`ChannelConfig`]:
132/// - **priority**: Determines scheduling order (0-255, higher = more priority)
133/// - **can_spawn**: Whether this channel can create children
134///
135/// # Example
136///
137/// ```
138/// use orcs_runtime::{BaseChannel, ChannelConfig, ChannelCore, ChannelMut, ChannelState};
139/// use orcs_types::{ChannelId, Principal};
140///
141/// // Create a root channel
142/// let root_id = ChannelId::new();
143/// let mut root = BaseChannel::new(root_id, None, ChannelConfig::interactive(), Principal::System);
144/// assert_eq!(root.priority(), 255);
145/// assert!(root.can_spawn());
146///
147/// // Create a background child channel
148/// let child_id = ChannelId::new();
149/// let child = BaseChannel::new(child_id, Some(root_id), ChannelConfig::background(), Principal::System);
150/// assert_eq!(child.priority(), 10);
151/// assert!(!child.can_spawn());
152///
153/// root.add_child(child_id);
154/// assert!(root.has_children());
155/// ```
156#[derive(Debug)]
157pub struct BaseChannel {
158    id: ChannelId,
159    state: ChannelState,
160    parent: Option<ChannelId>,
161    children: HashSet<ChannelId>,
162    config: ChannelConfig,
163    /// Principal for scope resolution.
164    principal: Principal,
165    /// Cached ancestor path for O(1) descendant checks.
166    /// Ordered: [parent, grandparent, ..., root]
167    ancestor_path: Vec<ChannelId>,
168}
169
170impl BaseChannel {
171    /// Creates a new channel in [`Running`](ChannelState::Running) state.
172    ///
173    /// # Arguments
174    ///
175    /// * `id` - Unique identifier for this channel
176    /// * `parent` - Parent channel ID, or `None` for root channels
177    /// * `config` - Configuration controlling channel behavior
178    /// * `principal` - Principal for scope resolution
179    ///
180    /// # Note
181    ///
182    /// The `ancestor_path` is initialized empty. When spawning via [`World`](crate::World),
183    /// use [`new_with_ancestors`](Self::new_with_ancestors) to properly set the ancestor path.
184    ///
185    /// # Example
186    ///
187    /// ```
188    /// use orcs_runtime::{BaseChannel, ChannelConfig, ChannelCore};
189    /// use orcs_types::{ChannelId, Principal};
190    ///
191    /// // Primary channel (no parent, highest priority)
192    /// let root = BaseChannel::new(ChannelId::new(), None, ChannelConfig::interactive(), Principal::System);
193    /// assert_eq!(root.priority(), 255);
194    ///
195    /// // Background child channel
196    /// let parent_id = ChannelId::new();
197    /// let child = BaseChannel::new(ChannelId::new(), Some(parent_id), ChannelConfig::background(), Principal::System);
198    /// assert_eq!(child.priority(), 10);
199    /// ```
200    #[must_use]
201    pub fn new(
202        id: ChannelId,
203        parent: Option<ChannelId>,
204        config: ChannelConfig,
205        principal: Principal,
206    ) -> Self {
207        Self {
208            id,
209            state: ChannelState::Running,
210            parent,
211            children: HashSet::new(),
212            config,
213            principal,
214            ancestor_path: Vec::new(),
215        }
216    }
217
218    /// Creates a new channel with a pre-computed ancestor path.
219    ///
220    /// This constructor is used by [`World`](crate::World) when spawning child channels
221    /// to maintain the ancestor path for O(1) descendant checks.
222    ///
223    /// # Arguments
224    ///
225    /// * `id` - Unique identifier for this channel
226    /// * `parent` - Parent channel ID
227    /// * `config` - Configuration controlling channel behavior
228    /// * `principal` - Principal for scope resolution
229    /// * `ancestor_path` - Pre-computed path: [parent, grandparent, ..., root]
230    #[must_use]
231    pub fn new_with_ancestors(
232        id: ChannelId,
233        parent: ChannelId,
234        config: ChannelConfig,
235        principal: Principal,
236        ancestor_path: Vec<ChannelId>,
237    ) -> Self {
238        Self {
239            id,
240            state: ChannelState::Running,
241            parent: Some(parent),
242            children: HashSet::new(),
243            config,
244            principal,
245            ancestor_path,
246        }
247    }
248}
249
250// === ChannelCore trait implementation ===
251
252impl ChannelCore for BaseChannel {
253    fn id(&self) -> ChannelId {
254        self.id
255    }
256
257    fn principal(&self) -> &Principal {
258        &self.principal
259    }
260
261    fn state(&self) -> &ChannelState {
262        &self.state
263    }
264
265    fn config(&self) -> &ChannelConfig {
266        &self.config
267    }
268
269    fn parent(&self) -> Option<ChannelId> {
270        self.parent
271    }
272
273    fn children(&self) -> &HashSet<ChannelId> {
274        &self.children
275    }
276
277    fn ancestor_path(&self) -> &[ChannelId] {
278        &self.ancestor_path
279    }
280}
281
282// === ChannelMut trait implementation ===
283
284impl ChannelMut for BaseChannel {
285    fn complete(&mut self) -> bool {
286        if matches!(self.state, ChannelState::Running) {
287            self.state = ChannelState::Completed;
288            true
289        } else {
290            false
291        }
292    }
293
294    fn abort(&mut self, reason: String) -> bool {
295        if !self.is_terminal() {
296            self.state = ChannelState::Aborted { reason };
297            true
298        } else {
299            false
300        }
301    }
302
303    fn pause(&mut self) -> bool {
304        if matches!(self.state, ChannelState::Running) {
305            self.state = ChannelState::Paused;
306            true
307        } else {
308            false
309        }
310    }
311
312    fn resume(&mut self) -> bool {
313        if matches!(self.state, ChannelState::Paused) {
314            self.state = ChannelState::Running;
315            true
316        } else {
317            false
318        }
319    }
320
321    fn await_approval(&mut self, request_id: String) -> bool {
322        if matches!(self.state, ChannelState::Running) {
323            self.state = ChannelState::AwaitingApproval { request_id };
324            true
325        } else {
326            false
327        }
328    }
329
330    fn resolve_approval(&mut self, approval_id: &str) -> Option<String> {
331        if let ChannelState::AwaitingApproval { request_id } = &self.state {
332            if request_id == approval_id {
333                let id = request_id.clone();
334                self.state = ChannelState::Running;
335                Some(id)
336            } else {
337                None
338            }
339        } else {
340            None
341        }
342    }
343
344    fn add_child(&mut self, id: ChannelId) {
345        self.children.insert(id);
346    }
347
348    fn remove_child(&mut self, id: &ChannelId) {
349        self.children.remove(id);
350    }
351}
352
353/// Type alias for backward compatibility.
354///
355/// New code should use [`BaseChannel`] directly or work with the
356/// [`ChannelCore`] / [`ChannelMut`] traits.
357pub type Channel = BaseChannel;
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use orcs_types::PrincipalId;
363
364    fn test_principal() -> Principal {
365        Principal::User(PrincipalId::new())
366    }
367
368    #[test]
369    fn channel_creation() {
370        let id = ChannelId::new();
371        let channel = BaseChannel::new(id, None, ChannelConfig::interactive(), test_principal());
372
373        assert_eq!(channel.id(), id);
374        assert!(channel.is_running());
375        assert!(channel.parent().is_none());
376        assert!(!channel.has_children());
377    }
378
379    #[test]
380    fn channel_with_parent() {
381        let parent_id = ChannelId::new();
382        let child_id = ChannelId::new();
383        let channel = BaseChannel::new(
384            child_id,
385            Some(parent_id),
386            ChannelConfig::default(),
387            test_principal(),
388        );
389
390        assert_eq!(channel.parent(), Some(parent_id));
391    }
392
393    #[test]
394    fn channel_principal() {
395        let principal = Principal::System;
396        let channel = BaseChannel::new(
397            ChannelId::new(),
398            None,
399            ChannelConfig::default(),
400            principal.clone(),
401        );
402
403        assert_eq!(channel.principal(), &principal);
404    }
405
406    #[test]
407    fn channel_state_transition() {
408        let id = ChannelId::new();
409        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
410
411        assert!(channel.is_running());
412
413        assert!(channel.complete());
414        assert!(!channel.is_running());
415        assert_eq!(channel.state(), &ChannelState::Completed);
416
417        // Cannot transition from Completed
418        assert!(!channel.complete());
419        assert!(!channel.abort("test".into()));
420    }
421
422    #[test]
423    fn channel_abort_transition() {
424        let id = ChannelId::new();
425        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
426
427        assert!(channel.abort("reason".into()));
428        assert!(!channel.is_running());
429        assert!(matches!(channel.state(), ChannelState::Aborted { .. }));
430
431        // Cannot transition from Aborted
432        assert!(!channel.complete());
433    }
434
435    #[test]
436    fn channel_children() {
437        let parent_id = ChannelId::new();
438        let child1 = ChannelId::new();
439        let child2 = ChannelId::new();
440
441        let mut parent = BaseChannel::new(
442            parent_id,
443            None,
444            ChannelConfig::interactive(),
445            test_principal(),
446        );
447        assert!(!parent.has_children());
448
449        parent.add_child(child1);
450        parent.add_child(child2);
451        assert!(parent.has_children());
452        assert_eq!(parent.children().len(), 2);
453
454        parent.remove_child(&child1);
455        assert_eq!(parent.children().len(), 1);
456    }
457
458    #[test]
459    fn channel_priority() {
460        let id = ChannelId::new();
461
462        let primary = BaseChannel::new(id, None, ChannelConfig::interactive(), test_principal());
463        assert_eq!(primary.priority(), 255);
464
465        let background = BaseChannel::new(
466            ChannelId::new(),
467            None,
468            ChannelConfig::background(),
469            test_principal(),
470        );
471        assert_eq!(background.priority(), 10);
472
473        let tool = BaseChannel::new(
474            ChannelId::new(),
475            None,
476            ChannelConfig::tool(),
477            test_principal(),
478        );
479        assert_eq!(tool.priority(), 100);
480    }
481
482    #[test]
483    fn channel_can_spawn() {
484        let primary = BaseChannel::new(
485            ChannelId::new(),
486            None,
487            ChannelConfig::interactive(),
488            test_principal(),
489        );
490        assert!(primary.can_spawn());
491
492        let background = BaseChannel::new(
493            ChannelId::new(),
494            None,
495            ChannelConfig::background(),
496            test_principal(),
497        );
498        assert!(!background.can_spawn());
499
500        let tool = BaseChannel::new(
501            ChannelId::new(),
502            None,
503            ChannelConfig::tool(),
504            test_principal(),
505        );
506        assert!(tool.can_spawn());
507    }
508
509    #[test]
510    fn channel_config_access() {
511        let config = ChannelConfig::new(200, false);
512        let channel = BaseChannel::new(ChannelId::new(), None, config, test_principal());
513
514        assert_eq!(channel.config().priority(), 200);
515        assert!(!channel.config().can_spawn());
516    }
517
518    // === M2 HIL State Tests ===
519
520    #[test]
521    fn channel_pause_resume() {
522        let id = ChannelId::new();
523        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
524
525        assert!(channel.is_running());
526        assert!(!channel.is_paused());
527
528        assert!(channel.pause());
529        assert!(!channel.is_running());
530        assert!(channel.is_paused());
531
532        // Cannot pause when already paused
533        assert!(!channel.pause());
534
535        assert!(channel.resume());
536        assert!(channel.is_running());
537        assert!(!channel.is_paused());
538
539        // Cannot resume when running
540        assert!(!channel.resume());
541    }
542
543    #[test]
544    fn channel_await_approval() {
545        let id = ChannelId::new();
546        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
547
548        assert!(channel.await_approval("req-123".to_string()));
549        assert!(channel.is_awaiting_approval());
550        assert!(!channel.is_running());
551
552        // Cannot await approval when already awaiting
553        assert!(!channel.await_approval("req-456".to_string()));
554
555        if let ChannelState::AwaitingApproval { request_id } = channel.state() {
556            assert_eq!(request_id, "req-123");
557        } else {
558            panic!("Expected AwaitingApproval state");
559        }
560    }
561
562    #[test]
563    fn channel_resolve_approval() {
564        let id = ChannelId::new();
565        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
566
567        assert!(channel.await_approval("req-123".to_string()));
568
569        // Wrong approval_id → no match
570        assert!(channel.resolve_approval("wrong-id").is_none());
571        assert!(channel.is_awaiting_approval());
572
573        // Correct approval_id → resolves
574        let resolved_id = channel.resolve_approval("req-123");
575        assert_eq!(resolved_id, Some("req-123".to_string()));
576        assert!(channel.is_running());
577
578        // Cannot resolve when running
579        assert!(channel.resolve_approval("req-123").is_none());
580    }
581
582    #[test]
583    fn channel_abort_from_awaiting() {
584        let id = ChannelId::new();
585        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
586
587        assert!(channel.await_approval("req-123".to_string()));
588        assert!(channel.abort("rejected by user".to_string()));
589
590        assert!(channel.is_terminal());
591        if let ChannelState::Aborted { reason } = channel.state() {
592            assert_eq!(reason, "rejected by user");
593        } else {
594            panic!("Expected Aborted state");
595        }
596    }
597
598    #[test]
599    fn channel_terminal_state() {
600        let id1 = ChannelId::new();
601        let mut ch1 = BaseChannel::new(id1, None, ChannelConfig::default(), test_principal());
602        ch1.complete();
603        assert!(ch1.is_terminal());
604
605        let id2 = ChannelId::new();
606        let mut ch2 = BaseChannel::new(id2, None, ChannelConfig::default(), test_principal());
607        ch2.abort("test".into());
608        assert!(ch2.is_terminal());
609
610        let id3 = ChannelId::new();
611        let ch3 = BaseChannel::new(id3, None, ChannelConfig::default(), test_principal());
612        assert!(!ch3.is_terminal());
613    }
614
615    #[test]
616    fn channel_cannot_pause_from_terminal() {
617        let id = ChannelId::new();
618        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
619        channel.complete();
620
621        assert!(!channel.pause());
622        assert!(!channel.await_approval("req".to_string()));
623    }
624
625    #[test]
626    fn channel_cannot_complete_from_paused() {
627        let id = ChannelId::new();
628        let mut channel = BaseChannel::new(id, None, ChannelConfig::default(), test_principal());
629        assert!(channel.pause());
630
631        // Must resume first
632        assert!(!channel.complete());
633        assert!(channel.resume());
634        assert!(channel.complete());
635    }
636
637    // === Ancestor Path Tests (DOD) ===
638
639    #[test]
640    fn channel_root_has_empty_ancestor_path() {
641        let root = BaseChannel::new(
642            ChannelId::new(),
643            None,
644            ChannelConfig::interactive(),
645            test_principal(),
646        );
647        assert!(root.ancestor_path().is_empty());
648        assert_eq!(root.depth(), 0);
649    }
650
651    #[test]
652    fn channel_with_ancestors() {
653        let root_id = ChannelId::new();
654        let parent_id = ChannelId::new();
655
656        let child = BaseChannel::new_with_ancestors(
657            ChannelId::new(),
658            parent_id,
659            ChannelConfig::default(),
660            test_principal(),
661            vec![parent_id, root_id],
662        );
663
664        assert_eq!(child.ancestor_path(), &[parent_id, root_id]);
665        assert_eq!(child.depth(), 2);
666    }
667
668    #[test]
669    fn channel_is_descendant_of() {
670        let root_id = ChannelId::new();
671        let parent_id = ChannelId::new();
672        let other_id = ChannelId::new();
673
674        let child = BaseChannel::new_with_ancestors(
675            ChannelId::new(),
676            parent_id,
677            ChannelConfig::default(),
678            test_principal(),
679            vec![parent_id, root_id],
680        );
681
682        assert!(child.is_descendant_of(parent_id));
683        assert!(child.is_descendant_of(root_id));
684        assert!(!child.is_descendant_of(other_id));
685        assert!(!child.is_descendant_of(child.id())); // not self
686    }
687
688    #[test]
689    fn channel_root_not_descendant_of_anything() {
690        let root = BaseChannel::new(
691            ChannelId::new(),
692            None,
693            ChannelConfig::interactive(),
694            test_principal(),
695        );
696        let other_id = ChannelId::new();
697
698        assert!(!root.is_descendant_of(other_id));
699        assert!(!root.is_descendant_of(root.id()));
700    }
701}