Skip to main content

agentkit_reporting/
policy.rs

1//! Configurable failure policies for reporters.
2//!
3//! The [`FailurePolicy`] enum controls what happens when a reporter encounters
4//! an error. Wrap any [`FallibleObserver`] in a [`PolicyReporter`] to get a
5//! [`LoopObserver`] that applies the chosen policy automatically.
6
7use crate::ReportError;
8use agentkit_loop::{AgentEvent, LoopObserver};
9
10/// Policy that determines how reporter errors are handled.
11///
12/// Reporter failures are non-fatal by default — a broken log writer shouldn't
13/// crash the agent. Hosts can configure stricter behaviour by choosing a
14/// different policy.
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub enum FailurePolicy {
17    /// Silently discard errors.
18    #[default]
19    Ignore,
20    /// Log errors to stderr via `eprintln!`.
21    Log,
22    /// Collect errors for later inspection via
23    /// [`PolicyReporter::take_errors`].
24    Accumulate,
25    /// Panic on the first error.
26    FailFast,
27}
28
29/// A reporter whose event handling can fail.
30///
31/// Implement this trait for reporters that perform I/O or other fallible
32/// operations. Wrap the implementation in [`PolicyReporter`] to obtain a
33/// [`LoopObserver`] with configurable error handling.
34pub trait FallibleObserver: Send + Sync {
35    /// Process an event, returning an error if something goes wrong.
36    /// Implementations store mutable state behind interior mutability so the
37    /// wrapper can be shared as `Arc<dyn LoopObserver>`.
38    fn try_handle_event(&self, event: &AgentEvent) -> Result<(), ReportError>;
39}
40
41/// Adapter that wraps a [`FallibleObserver`] and applies a [`FailurePolicy`].
42///
43/// This turns any fallible reporter into a [`LoopObserver`] suitable for
44/// passing to the agent loop.
45///
46/// # Example
47///
48/// ```rust
49/// use agentkit_reporting::{ChannelReporter, FailurePolicy, PolicyReporter};
50///
51/// let (reporter, rx) = ChannelReporter::pair();
52/// let reporter = PolicyReporter::new(reporter, FailurePolicy::Log);
53/// // `reporter` now implements `LoopObserver` and logs send failures to stderr.
54/// ```
55pub struct PolicyReporter<T> {
56    inner: T,
57    policy: FailurePolicy,
58    errors: std::sync::Mutex<Vec<ReportError>>,
59}
60
61impl<T: FallibleObserver> PolicyReporter<T> {
62    /// Creates a new `PolicyReporter` wrapping the given observer with the
63    /// specified failure policy.
64    pub fn new(inner: T, policy: FailurePolicy) -> Self {
65        Self {
66            inner,
67            policy,
68            errors: std::sync::Mutex::new(Vec::new()),
69        }
70    }
71
72    /// Returns a reference to the inner observer.
73    pub fn inner(&self) -> &T {
74        &self.inner
75    }
76
77    /// Returns the configured failure policy.
78    pub fn policy(&self) -> FailurePolicy {
79        self.policy
80    }
81
82    /// Drains and returns all accumulated errors.
83    ///
84    /// Only meaningful when the policy is [`FailurePolicy::Accumulate`].
85    pub fn take_errors(&self) -> Vec<ReportError> {
86        std::mem::take(&mut *self.errors.lock().unwrap_or_else(|e| e.into_inner()))
87    }
88}
89
90impl<T: FallibleObserver> LoopObserver for PolicyReporter<T> {
91    fn handle_event(&self, event: AgentEvent) {
92        if let Err(e) = self.inner.try_handle_event(&event) {
93            match self.policy {
94                FailurePolicy::Ignore => {}
95                FailurePolicy::Log => {
96                    eprintln!("reporter error: {e}");
97                }
98                FailurePolicy::Accumulate => {
99                    self.errors
100                        .lock()
101                        .unwrap_or_else(|e| e.into_inner())
102                        .push(e);
103                }
104                FailurePolicy::FailFast => {
105                    panic!("reporter error: {e}");
106                }
107            }
108        }
109    }
110}