Skip to main content

codetether_agent/session/context/
policy.rs

1//! Policy dispatcher — routes to the chosen [`DerivePolicy`] implementation.
2
3use std::env;
4use std::sync::Arc;
5
6use anyhow::Result;
7use tokio::sync::mpsc;
8
9use crate::provider::ToolDefinition;
10use crate::session::Session;
11use crate::session::SessionEvent;
12use crate::session::derive_policy::DerivePolicy;
13
14use super::derive::derive_context;
15use super::helpers::DerivedContext;
16use super::reset::derive_reset;
17use super::reset_helpers::latest_reset_marker_index;
18
19const DEFAULT_RESET_THRESHOLD_TOKENS: usize = 32_000;
20
21/// Resolve the effective derivation policy for `session`.
22///
23/// The persisted session policy is the baseline. Operators can override it
24/// process-wide via:
25///
26/// - `CODETETHER_CONTEXT_POLICY=legacy`
27/// - `CODETETHER_CONTEXT_POLICY=reset`
28/// - `CODETETHER_CONTEXT_RESET_THRESHOLD_TOKENS=<usize>`
29///
30/// When there is no explicit override and the persisted policy is still
31/// legacy, a recorded `[CONTEXT RESET]` marker auto-promotes the effective
32/// policy to [`DerivePolicy::Reset`] so `context_reset` markers become live
33/// immediately on the next turn.
34pub fn effective_policy(session: &Session) -> DerivePolicy {
35    let persisted = session.metadata.context_policy;
36    let Ok(raw) = env::var("CODETETHER_CONTEXT_POLICY") else {
37        if matches!(persisted, DerivePolicy::Legacy)
38            && latest_reset_marker_index(&session.messages).is_some()
39        {
40            return DerivePolicy::Reset {
41                threshold_tokens: resolve_reset_threshold(persisted),
42            };
43        }
44        return persisted;
45    };
46    let normalized = raw.trim().to_ascii_lowercase();
47    match normalized.as_str() {
48        "" => persisted,
49        "legacy" => DerivePolicy::Legacy,
50        "reset" => DerivePolicy::Reset {
51            threshold_tokens: resolve_reset_threshold(persisted),
52        },
53        _ => {
54            tracing::warn!(raw = %raw, "Unknown CODETETHER_CONTEXT_POLICY override; using persisted session policy");
55            persisted
56        }
57    }
58}
59
60fn resolve_reset_threshold(persisted: DerivePolicy) -> usize {
61    let default_threshold = match persisted {
62        DerivePolicy::Reset { threshold_tokens } => threshold_tokens,
63        DerivePolicy::Legacy => DEFAULT_RESET_THRESHOLD_TOKENS,
64    };
65    env::var("CODETETHER_CONTEXT_RESET_THRESHOLD_TOKENS")
66        .ok()
67        .and_then(|value| value.trim().parse::<usize>().ok())
68        .unwrap_or(default_threshold)
69}
70
71/// Derive an ephemeral [`DerivedContext`] under a chosen [`DerivePolicy`].
72///
73/// Generalisation of [`derive_context`] that accepts a policy selector.
74/// [`DerivePolicy::Legacy`] delegates back to `derive_context` so the
75/// two signatures co-exist without behaviour drift during rollout.
76///
77/// # Arguments
78///
79/// Same as [`derive_context`], plus:
80///
81/// * `policy` — Which derivation strategy to run. See [`DerivePolicy`].
82/// * `force_keep_last` — When `Some(n)`, bypass policy selection and fall
83///   back to the legacy keep-last derivation used for prompt-too-long
84///   recovery.
85///
86/// # Errors
87///
88/// Propagates any error from the underlying pipeline.
89pub async fn derive_with_policy(
90    session: &Session,
91    provider: Arc<dyn crate::provider::Provider>,
92    model: &str,
93    system_prompt: &str,
94    tools: &[ToolDefinition],
95    event_tx: Option<&mpsc::Sender<SessionEvent>>,
96    policy: DerivePolicy,
97    force_keep_last: Option<usize>,
98) -> Result<DerivedContext> {
99    if force_keep_last.is_some() {
100        return derive_context(
101            session,
102            provider,
103            model,
104            system_prompt,
105            tools,
106            event_tx,
107            force_keep_last,
108        )
109        .await;
110    }
111    match policy {
112        DerivePolicy::Legacy => {
113            derive_context(
114                session,
115                provider,
116                model,
117                system_prompt,
118                tools,
119                event_tx,
120                force_keep_last,
121            )
122            .await
123        }
124        DerivePolicy::Reset { threshold_tokens } => {
125            derive_reset(
126                session,
127                provider,
128                model,
129                system_prompt,
130                tools,
131                threshold_tokens,
132            )
133            .await
134        }
135    }
136}