Skip to main content

lean_ctx/core/gain/
reach.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Reach: how much of the agent's *observed* tool traffic went through lean-ctx.
3//!
4//! The denominator is only what lean-ctx can see — calls routed through it plus
5//! native shell calls a hook observed and let pass. Native tool calls that bypass
6//! every hook are invisible, so reach is never a share of all agent activity.
7//! A pure function of observed counts; a host capability model can attach to it.
8
9use serde::Serialize;
10
11/// Observed call counts for one window.
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
13pub struct ReachCounts {
14    pub routed_calls: u64,
15    pub native_passthrough_calls: u64,
16}
17
18/// Native calls no hook saw. Their number cannot be known locally.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
20#[serde(rename_all = "snake_case")]
21pub enum Unobserved {
22    Unknown,
23}
24
25#[derive(Debug, Clone, PartialEq, Serialize)]
26pub struct ReachView {
27    pub routed_calls: u64,
28    pub native_passthrough_calls: u64,
29    /// The denominator: routed + observed native passthrough.
30    pub observed_calls: u64,
31    /// `routed / observed` in percent; `None` when nothing was observed.
32    pub routed_pct_of_observed: Option<f64>,
33    pub unobserved_native_calls: Unobserved,
34}
35
36#[must_use]
37pub fn reach_view(counts: ReachCounts) -> ReachView {
38    let observed_calls = counts
39        .routed_calls
40        .saturating_add(counts.native_passthrough_calls);
41    ReachView {
42        routed_calls: counts.routed_calls,
43        native_passthrough_calls: counts.native_passthrough_calls,
44        observed_calls,
45        routed_pct_of_observed: (observed_calls > 0)
46            .then(|| counts.routed_calls as f64 * 100.0 / observed_calls as f64),
47        unobserved_native_calls: Unobserved::Unknown,
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn share_is_of_observed_calls_and_absent_without_observations() {
57        let none = reach_view(ReachCounts::default());
58        assert_eq!(none.observed_calls, 0);
59        assert_eq!(none.routed_pct_of_observed, None);
60
61        let view = reach_view(ReachCounts {
62            routed_calls: 9,
63            native_passthrough_calls: 1,
64        });
65        assert_eq!(view.observed_calls, 10);
66        assert_eq!(view.routed_pct_of_observed, Some(90.0));
67        let json = serde_json::to_value(&view).unwrap();
68        assert_eq!(json["unobserved_native_calls"], "unknown");
69        assert_eq!(json["routed_pct_of_observed"], 90.0);
70    }
71}