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 {
35 /// Process an event, returning an error if something goes wrong.
36 fn try_handle_event(&mut self, event: &AgentEvent) -> Result<(), ReportError>;
37}
38
39/// Adapter that wraps a [`FallibleObserver`] and applies a [`FailurePolicy`].
40///
41/// This turns any fallible reporter into a [`LoopObserver`] suitable for
42/// passing to the agent loop.
43///
44/// # Example
45///
46/// ```rust
47/// use agentkit_reporting::{ChannelReporter, FailurePolicy, PolicyReporter};
48///
49/// let (reporter, rx) = ChannelReporter::pair();
50/// let reporter = PolicyReporter::new(reporter, FailurePolicy::Log);
51/// // `reporter` now implements `LoopObserver` and logs send failures to stderr.
52/// ```
53pub struct PolicyReporter<T> {
54 inner: T,
55 policy: FailurePolicy,
56 errors: Vec<ReportError>,
57}
58
59impl<T: FallibleObserver> PolicyReporter<T> {
60 /// Creates a new `PolicyReporter` wrapping the given observer with the
61 /// specified failure policy.
62 pub fn new(inner: T, policy: FailurePolicy) -> Self {
63 Self {
64 inner,
65 policy,
66 errors: Vec::new(),
67 }
68 }
69
70 /// Returns a reference to the inner observer.
71 pub fn inner(&self) -> &T {
72 &self.inner
73 }
74
75 /// Returns a mutable reference to the inner observer.
76 pub fn inner_mut(&mut self) -> &mut T {
77 &mut self.inner
78 }
79
80 /// Returns the configured failure policy.
81 pub fn policy(&self) -> FailurePolicy {
82 self.policy
83 }
84
85 /// Drains and returns all accumulated errors.
86 ///
87 /// Only meaningful when the policy is [`FailurePolicy::Accumulate`].
88 /// Subsequent calls return an empty `Vec` until new errors occur.
89 pub fn take_errors(&mut self) -> Vec<ReportError> {
90 std::mem::take(&mut self.errors)
91 }
92}
93
94impl<T: FallibleObserver> LoopObserver for PolicyReporter<T> {
95 fn handle_event(&mut self, event: AgentEvent) {
96 if let Err(e) = self.inner.try_handle_event(&event) {
97 match self.policy {
98 FailurePolicy::Ignore => {}
99 FailurePolicy::Log => {
100 eprintln!("reporter error: {e}");
101 }
102 FailurePolicy::Accumulate => {
103 self.errors.push(e);
104 }
105 FailurePolicy::FailFast => {
106 panic!("reporter error: {e}");
107 }
108 }
109 }
110 }
111}