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}