Skip to main content

navi_core/
runtime_components.rs

1use crate::compact::{self, CompactState};
2use crate::config::HarnessConfig;
3use crate::harness::{
4    AgentRunState, HarnessPolicy, ToolLoopDecision, compact_tool_observation, record_tool_call,
5    record_tool_result,
6};
7use crate::model::{ModelMessage, ModelProvider};
8use crate::prompt::{PromptCache, RenderedPrompt, SystemPromptInput, SystemPromptRenderer};
9use crate::security::{SecurityDecision, SecurityPolicy};
10use crate::tool::{ToolDefinition, ToolInvocation, ToolResult};
11use anyhow::Result;
12use async_trait::async_trait;
13use std::sync::Arc;
14
15#[derive(Clone)]
16pub struct RuntimeComponents {
17    pub security: Arc<dyn ToolSecurityPolicy>,
18    pub harness: Arc<dyn HarnessDriver>,
19    pub prompt: Arc<dyn PromptBuilder>,
20    pub compaction: Arc<dyn CompactionStrategy>,
21    pub hooks: Arc<dyn SessionHooks>,
22}
23
24impl Default for RuntimeComponents {
25    fn default() -> Self {
26        Self {
27            security: Arc::new(DefaultToolSecurityPolicy),
28            harness: Arc::new(DefaultHarnessDriver),
29            prompt: Arc::new(DefaultPromptBuilder),
30            compaction: Arc::new(DefaultCompactionStrategy),
31            hooks: Arc::new(NoopSessionHooks),
32        }
33    }
34}
35
36pub trait ToolSecurityPolicy: Send + Sync {
37    fn validate_tool(
38        &self,
39        base_policy: &SecurityPolicy,
40        definition: &ToolDefinition,
41        invocation: &ToolInvocation,
42    ) -> SecurityDecision;
43}
44
45#[derive(Debug, Default)]
46pub struct DefaultToolSecurityPolicy;
47
48impl ToolSecurityPolicy for DefaultToolSecurityPolicy {
49    fn validate_tool(
50        &self,
51        base_policy: &SecurityPolicy,
52        definition: &ToolDefinition,
53        invocation: &ToolInvocation,
54    ) -> SecurityDecision {
55        base_policy.validate_tool_invocation(definition, invocation)
56    }
57}
58
59#[derive(Debug, Default)]
60pub struct PermissiveSecurityPolicy;
61
62impl ToolSecurityPolicy for PermissiveSecurityPolicy {
63    fn validate_tool(
64        &self,
65        _base_policy: &SecurityPolicy,
66        _definition: &ToolDefinition,
67        _invocation: &ToolInvocation,
68    ) -> SecurityDecision {
69        SecurityDecision::Allow
70    }
71}
72
73pub trait HarnessDriver: Send + Sync {
74    fn filter_tools(
75        &self,
76        tools: Vec<ToolDefinition>,
77        allowed_tool_names: Option<&[String]>,
78    ) -> Vec<ToolDefinition>;
79
80    fn record_tool_call(
81        &self,
82        state: &mut AgentRunState,
83        policy: HarnessPolicy,
84        invocation: &ToolInvocation,
85    ) -> ToolLoopDecision;
86
87    fn record_tool_result(
88        &self,
89        state: &mut AgentRunState,
90        policy: HarnessPolicy,
91        invocation: &ToolInvocation,
92        result: &ToolResult,
93    ) -> ToolLoopDecision;
94
95    fn compact_tool_observation(
96        &self,
97        invocation: &ToolInvocation,
98        result: &ToolResult,
99        policy: HarnessPolicy,
100    ) -> String;
101}
102
103#[derive(Debug, Default)]
104pub struct DefaultHarnessDriver;
105
106impl HarnessDriver for DefaultHarnessDriver {
107    fn filter_tools(
108        &self,
109        tools: Vec<ToolDefinition>,
110        allowed_tool_names: Option<&[String]>,
111    ) -> Vec<ToolDefinition> {
112        let Some(whitelist) = allowed_tool_names else {
113            return tools;
114        };
115        tools
116            .into_iter()
117            .filter(|tool| whitelist.contains(&tool.name))
118            .collect()
119    }
120
121    fn record_tool_call(
122        &self,
123        state: &mut AgentRunState,
124        policy: HarnessPolicy,
125        invocation: &ToolInvocation,
126    ) -> ToolLoopDecision {
127        record_tool_call(state, policy, invocation)
128    }
129
130    fn record_tool_result(
131        &self,
132        state: &mut AgentRunState,
133        policy: HarnessPolicy,
134        invocation: &ToolInvocation,
135        result: &ToolResult,
136    ) -> ToolLoopDecision {
137        record_tool_result(state, policy, invocation, result)
138    }
139
140    fn compact_tool_observation(
141        &self,
142        invocation: &ToolInvocation,
143        result: &ToolResult,
144        policy: HarnessPolicy,
145    ) -> String {
146        compact_tool_observation(invocation, result, policy)
147    }
148}
149
150pub trait PromptBuilder: Send + Sync {
151    fn build(&self, input: SystemPromptInput, cache: Arc<PromptCache>) -> RenderedPrompt;
152}
153
154#[derive(Debug, Default)]
155pub struct DefaultPromptBuilder;
156
157impl PromptBuilder for DefaultPromptBuilder {
158    fn build(&self, input: SystemPromptInput, cache: Arc<PromptCache>) -> RenderedPrompt {
159        SystemPromptRenderer::new(cache).render(input)
160    }
161}
162
163#[async_trait]
164pub trait CompactionStrategy: Send + Sync {
165    fn micro_compact(&self, messages: &mut Vec<ModelMessage>, gap_threshold_minutes: u64) -> usize;
166
167    async fn auto_compact(
168        &self,
169        state: &mut CompactState,
170        messages: &mut Vec<ModelMessage>,
171        provider: &dyn ModelProvider,
172        model: &str,
173        config: &HarnessConfig,
174    ) -> Result<Option<compact::CompactOutcome>>;
175
176    /// Force full compaction with the session model (manual Compact).
177    async fn force_compact(
178        &self,
179        state: &mut CompactState,
180        messages: &mut Vec<ModelMessage>,
181        provider: &dyn ModelProvider,
182        model: &str,
183        config: &HarnessConfig,
184    ) -> Result<Option<compact::CompactOutcome>> {
185        // Default: same path as auto with force semantics via state.force_compact.
186        state.force_compact(messages, provider, model, config).await
187    }
188}
189
190#[derive(Debug, Default)]
191pub struct DefaultCompactionStrategy;
192
193#[async_trait]
194impl CompactionStrategy for DefaultCompactionStrategy {
195    fn micro_compact(&self, messages: &mut Vec<ModelMessage>, gap_threshold_minutes: u64) -> usize {
196        compact::micro_compact(messages, gap_threshold_minutes)
197    }
198
199    async fn auto_compact(
200        &self,
201        state: &mut CompactState,
202        messages: &mut Vec<ModelMessage>,
203        provider: &dyn ModelProvider,
204        model: &str,
205        config: &HarnessConfig,
206    ) -> Result<Option<compact::CompactOutcome>> {
207        state.auto_compact(messages, provider, model, config).await
208    }
209
210    async fn force_compact(
211        &self,
212        state: &mut CompactState,
213        messages: &mut Vec<ModelMessage>,
214        provider: &dyn ModelProvider,
215        model: &str,
216        config: &HarnessConfig,
217    ) -> Result<Option<compact::CompactOutcome>> {
218        state.force_compact(messages, provider, model, config).await
219    }
220}
221
222pub trait SessionHooks: Send + Sync {
223    fn on_session_start(&self, _session_id: &str) {}
224    fn on_turn_start(&self, _session_id: &str, _task: &str) {}
225    fn on_tool_call(&self, _invocation: &ToolInvocation) {}
226    fn on_tool_result(&self, _result: &ToolResult) {}
227    fn on_turn_end(&self, _session_id: &str, _output: &str) {}
228    fn on_session_end(&self, _session_id: &str) {}
229}
230
231#[derive(Debug, Default)]
232pub struct NoopSessionHooks;
233
234impl SessionHooks for NoopSessionHooks {}