qualia_core_db/inference/inference_agent/
runtime.rs1use std::time::{Duration, Instant};
6
7use crate::modalities::logic::n3_compiler::N3OutputMode;
8
9use super::config::{effective_inference_timeout_ms, LLM_MEMORY_BUDGET_BYTES, MAX_OUTPUT_TOKENS};
10use super::local_agent::LocalLlmAgent;
11use super::types::{
12 AgentBackend, AgentError, AgentIntent, AgentOutput, AgentRuntime, WebizenVerdict,
13 LLM_RULE_PROFILE_VIOLATION, LLM_RULE_PROVENANCE_REQUIRED, LLM_RULE_TOKEN_BUDGET,
14};
15
16impl AgentRuntime for LocalLlmAgent {
17 fn backend(&self) -> &AgentBackend {
18 &self.backend
19 }
20 fn agent_did(&self) -> &str {
21 &self.agent_did
22 }
23
24 fn validate_intent(&self, intent: &AgentIntent) -> WebizenVerdict {
25 let sieve_on = matches!(
26 intent.output_mode,
27 N3OutputMode::GraphMutation | N3OutputMode::N3Assertions
28 );
29 self.use_sieve_output
30 .store(sieve_on, std::sync::atomic::Ordering::Relaxed);
31 if sieve_on {
32 let mut spec = crate::neuro_symbolic_sieve::SieveLexSpec::graph_mutation_default();
33 for &scope_hash in &intent.requested_graph_scope {
34 if scope_hash != 0 {
35 spec.push_predicate(scope_hash);
36 }
37 }
38 for &namespace_hash in &intent.context_namespaces {
39 if namespace_hash != 0 {
40 spec.push_predicate(namespace_hash);
41 }
42 }
43 *self.sieve_spec.lock().unwrap_or_else(|e| e.into_inner()) = spec;
44 }
45
46 let frame = intent.to_frame();
47 let base = Self::evaluate_intent_frame(self, &frame);
48 if !matches!(base, WebizenVerdict::Permit) {
49 return base;
50 }
51
52 if let Some(profile) = &intent.active_profile {
54 if !profile.allows_intent(intent.intent_predicate) {
55 return WebizenVerdict::DenyWithExplanation {
56 rule_violated: LLM_RULE_PROFILE_VIOLATION,
57 reason: "Profile Violation".into(),
58 explanation: "This capability profile explicitly blocks this intent frame."
59 .into(),
60 };
61 }
62 }
63
64 WebizenVerdict::Permit
65 }
66
67 fn infer(&self, prompt: &str, graph_context: &str) -> Result<AgentOutput, AgentError> {
68 let t0 = Instant::now();
69
70 let current = self
72 .memory_used_bytes
73 .load(std::sync::atomic::Ordering::Relaxed);
74 if current > LLM_MEMORY_BUDGET_BYTES {
75 return Err(AgentError::MemoryBudgetExceeded);
76 }
77
78 let deadline = Duration::from_millis(effective_inference_timeout_ms());
80 let (text, provenance, tokens, semantic_quin) =
81 self.infer_local_model(prompt, graph_context);
82 if t0.elapsed() > deadline {
83 return Err(AgentError::Timeout);
84 }
85 if text == "[sieve-misaligned]" && semantic_quin.is_none() {
86 return Err(AgentError::SieveMisaligned);
87 }
88
89 Ok(AgentOutput {
90 text,
91 semantic_quin,
92 provenance_quins: provenance,
93 tokens_generated: tokens,
94 inference_duration_ms: t0.elapsed().as_millis() as u64,
95 peak_memory_bytes: current,
96 })
97 }
98
99 fn validate_output(&self, output: &AgentOutput) -> WebizenVerdict {
100 if output.provenance_quins.is_empty() {
102 return WebizenVerdict::Deny {
103 rule_violated: LLM_RULE_PROVENANCE_REQUIRED,
104 reason: "Output has no provenance citations. Cannot commit ungrounded content to the semantic graph.",
105 conduct_record: None,
106 };
107 }
108 if output.tokens_generated > MAX_OUTPUT_TOKENS {
110 return WebizenVerdict::Deny {
111 rule_violated: LLM_RULE_TOKEN_BUDGET,
112 reason: "Token budget exceeded.",
113 conduct_record: None,
114 };
115 }
116 WebizenVerdict::Permit
117 }
118
119 fn memory_budget_remaining(&self) -> u64 {
120 let used = self
121 .memory_used_bytes
122 .load(std::sync::atomic::Ordering::Relaxed);
123 LLM_MEMORY_BUDGET_BYTES.saturating_sub(used)
124 }
125}