diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 0e6f508c..cfeabb2d 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -417,7 +417,6 @@ impl Agent { hygiene, workspace.clone(), self.cheap_llm().clone(), - self.safety().clone(), Some(notify_tx), self.store().map(Arc::clone), )) diff --git a/src/agent/commands.rs b/src/agent/commands.rs index bf1c7e6c..2c5b96e5 100644 --- a/src/agent/commands.rs +++ b/src/agent/commands.rs @@ -345,7 +345,6 @@ impl Agent { crate::workspace::hygiene::HygieneConfig::default(), workspace.clone(), self.llm().clone(), - self.safety().clone(), ); match runner.check_heartbeat().await { @@ -406,7 +405,7 @@ impl Agent { .with_max_tokens(512) .with_temperature(0.3); - let reasoning = Reasoning::new(self.llm().clone(), self.safety().clone()); + let reasoning = Reasoning::new(self.llm().clone()); match reasoning.complete(request).await { Ok((text, _usage)) => Ok(SubmissionResult::response(format!( "Thread Summary:\n\n{}", @@ -454,7 +453,7 @@ impl Agent { .with_max_tokens(512) .with_temperature(0.5); - let reasoning = Reasoning::new(self.llm().clone(), self.safety().clone()); + let reasoning = Reasoning::new(self.llm().clone()); match reasoning.complete(request).await { Ok((text, _usage)) => Ok(SubmissionResult::response(format!( "Suggested Next Steps:\n\n{}", diff --git a/src/agent/compaction.rs b/src/agent/compaction.rs index cf8f1903..46980c79 100644 --- a/src/agent/compaction.rs +++ b/src/agent/compaction.rs @@ -13,7 +13,6 @@ use crate::agent::context_monitor::{CompactionStrategy, ContextBreakdown}; use crate::agent::session::Thread; use crate::error::Error; use crate::llm::{ChatMessage, CompletionRequest, LlmProvider, Reasoning}; -use crate::safety::SafetyLayer; use crate::workspace::Workspace; /// Result of a compaction operation. @@ -34,13 +33,12 @@ pub struct CompactionResult { /// Compacts conversation context to stay within limits. pub struct ContextCompactor { llm: Arc, - safety: Arc, } impl ContextCompactor { /// Create a new context compactor. - pub fn new(llm: Arc, safety: Arc) -> Self { - Self { llm, safety } + pub fn new(llm: Arc) -> Self { + Self { llm } } /// Compact a thread's context using the given strategy. @@ -233,7 +231,7 @@ Be brief but capture all important details. Use bullet points."#, .with_max_tokens(1024) .with_temperature(0.3); - let reasoning = Reasoning::new(self.llm.clone(), self.safety.clone()); + let reasoning = Reasoning::new(self.llm.clone()); let (text, _) = reasoning.complete(request).await?; Ok(text) } @@ -346,17 +344,11 @@ mod tests { // === QA Plan - Compaction strategy tests === use crate::agent::context_monitor::CompactionStrategy; - use crate::config::SafetyConfig; - use crate::safety::SafetyLayer; use crate::testing::StubLlm; /// Helper: build a `ContextCompactor` with the given `StubLlm`. fn make_compactor(llm: Arc) -> ContextCompactor { - let safety = Arc::new(SafetyLayer::new(&SafetyConfig { - max_output_length: 100_000, - injection_check_enabled: false, - })); - ContextCompactor::new(llm, safety) + ContextCompactor::new(llm) } /// Helper: build a thread with `n` completed turns. diff --git a/src/agent/dispatcher.rs b/src/agent/dispatcher.rs index 85c24763..2754f4d6 100644 --- a/src/agent/dispatcher.rs +++ b/src/agent/dispatcher.rs @@ -113,7 +113,7 @@ impl Agent { None }; - let mut reasoning = Reasoning::new(self.llm().clone(), self.safety().clone()) + let mut reasoning = Reasoning::new(self.llm().clone()) .with_channel(message.channel.clone()) .with_model_name(self.llm().active_model_name()) .with_group_chat(is_group_chat); @@ -1609,12 +1609,8 @@ mod tests { use crate::testing::StubLlm; let stub = Arc::new(StubLlm::failing_non_transient("ctx-bomb")); - let safety = Arc::new(SafetyLayer::new(&SafetyConfig { - max_output_length: 100_000, - injection_check_enabled: false, - })); - let reasoning = Reasoning::new(stub.clone(), safety); + let reasoning = Reasoning::new(stub.clone()); // Build a fat context with lots of history. let messages = vec![ @@ -1724,11 +1720,7 @@ mod tests { use crate::llm::{Reasoning, ReasoningContext, RespondResult, ToolDefinition}; let provider = Arc::new(AlwaysToolCallProvider); - let safety = Arc::new(SafetyLayer::new(&SafetyConfig { - max_output_length: 100_000, - injection_check_enabled: false, - })); - let reasoning = Reasoning::new(provider, safety); + let reasoning = Reasoning::new(provider); let tool_def = ToolDefinition { name: "echo".to_string(), diff --git a/src/agent/heartbeat.rs b/src/agent/heartbeat.rs index 5c99d01e..4c05c1d5 100644 --- a/src/agent/heartbeat.rs +++ b/src/agent/heartbeat.rs @@ -31,7 +31,6 @@ use tokio::sync::mpsc; use crate::channels::OutgoingResponse; use crate::db::Database; use crate::llm::{ChatMessage, CompletionRequest, LlmProvider, Reasoning}; -use crate::safety::SafetyLayer; use crate::workspace::Workspace; use crate::workspace::hygiene::HygieneConfig; @@ -131,7 +130,6 @@ pub struct HeartbeatRunner { hygiene_config: HygieneConfig, workspace: Arc, llm: Arc, - safety: Arc, response_tx: Option>, store: Option>, consecutive_failures: u32, @@ -144,14 +142,12 @@ impl HeartbeatRunner { hygiene_config: HygieneConfig, workspace: Arc, llm: Arc, - safety: Arc, ) -> Self { Self { config, hygiene_config, workspace, llm, - safety, response_tx: None, store: None, consecutive_failures: 0, @@ -307,7 +303,7 @@ impl HeartbeatRunner { .with_max_tokens(max_tokens) .with_temperature(0.3); - let reasoning = Reasoning::new(self.llm.clone(), self.safety.clone()); + let reasoning = Reasoning::new(self.llm.clone()); let (content, _usage) = match reasoning.complete(request).await { Ok(r) => r, Err(e) => return HeartbeatResult::Failed(format!("LLM call failed: {}", e)), @@ -421,11 +417,10 @@ pub fn spawn_heartbeat( hygiene_config: HygieneConfig, workspace: Arc, llm: Arc, - safety: Arc, response_tx: Option>, store: Option>, ) -> tokio::task::JoinHandle<()> { - let mut runner = HeartbeatRunner::new(config, hygiene_config, workspace, llm, safety); + let mut runner = HeartbeatRunner::new(config, hygiene_config, workspace, llm); if let Some(tx) = response_tx { runner = runner.with_response_channel(tx); } @@ -655,7 +650,6 @@ mod tests { HygieneConfig, Arc, Arc, - Arc, Option>, Option>, ) -> tokio::task::JoinHandle<()> = spawn_heartbeat; diff --git a/src/agent/thread_ops.rs b/src/agent/thread_ops.rs index a2001e4c..4dc3ff17 100644 --- a/src/agent/thread_ops.rs +++ b/src/agent/thread_ops.rs @@ -230,7 +230,7 @@ impl Agent { ) .await; - let compactor = ContextCompactor::new(self.llm().clone(), self.safety().clone()); + let compactor = ContextCompactor::new(self.llm().clone()); if let Err(e) = compactor .compact(thread, strategy, self.workspace().map(|w| w.as_ref())) .await @@ -627,7 +627,7 @@ impl Agent { crate::agent::context_monitor::CompactionStrategy::Summarize { keep_recent: 5 }, ); - let compactor = ContextCompactor::new(self.llm().clone(), self.safety().clone()); + let compactor = ContextCompactor::new(self.llm().clone()); match compactor .compact(thread, strategy, self.workspace().map(|w| w.as_ref())) .await diff --git a/src/agent/worker.rs b/src/agent/worker.rs index 7d50952c..3604cea9 100644 --- a/src/agent/worker.rs +++ b/src/agent/worker.rs @@ -212,7 +212,7 @@ impl Worker { let job_ctx = self.context_manager().get_context(self.job_id).await?; // Create reasoning engine - let reasoning = Reasoning::new(self.llm().clone(), self.safety().clone()); + let reasoning = Reasoning::new(self.llm().clone()); // Build initial reasoning context (tool definitions refreshed each iteration in execution_loop) let mut reason_ctx = ReasoningContext::new().with_job(&job_ctx.description); diff --git a/src/app.rs b/src/app.rs index 766aff30..e89b7e79 100644 --- a/src/app.rs +++ b/src/app.rs @@ -403,11 +403,7 @@ impl AppBuilder { && (self.config.agent.allow_local_tools || !self.config.sandbox.enabled) { tools - .register_builder_tool( - llm.clone(), - safety.clone(), - Some(self.config.builder.to_builder_config()), - ) + .register_builder_tool(llm.clone(), Some(self.config.builder.to_builder_config())) .await; tracing::info!("Builder mode enabled"); } diff --git a/src/evaluation/success.rs b/src/evaluation/success.rs index 2d1e4470..717f3dc0 100644 --- a/src/evaluation/success.rs +++ b/src/evaluation/success.rs @@ -1,13 +1,10 @@ //! Success evaluation for jobs. -use std::sync::Arc; - use async_trait::async_trait; use serde::{Deserialize, Serialize}; use crate::context::{ActionRecord, JobContext}; use crate::error::EvaluationError; -use crate::llm::LlmProvider; /// Result of evaluating job success. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -64,233 +61,132 @@ pub trait SuccessEvaluator: Send + Sync { ) -> Result; } -/// Rule-based success evaluator. -pub struct RuleBasedEvaluator { - /// Minimum success rate for actions. - min_action_success_rate: f64, - /// Maximum allowed failures. - max_failures: u32, -} - -impl RuleBasedEvaluator { - /// Create a new rule-based evaluator. - pub fn new() -> Self { - Self { - min_action_success_rate: 0.8, - max_failures: 3, - } - } - - /// Set minimum action success rate. - #[allow(dead_code)] // Public API for configuring evaluation threshold - pub fn with_min_success_rate(mut self, rate: f64) -> Self { - self.min_action_success_rate = rate; - self - } - - /// Set maximum failures. - #[allow(dead_code)] // Public API for configuring failure tolerance - pub fn with_max_failures(mut self, max: u32) -> Self { - self.max_failures = max; - self - } -} - -impl Default for RuleBasedEvaluator { - fn default() -> Self { - Self::new() - } -} - -#[async_trait] -impl SuccessEvaluator for RuleBasedEvaluator { - async fn evaluate( - &self, - job: &JobContext, - actions: &[ActionRecord], - _output: Option<&str>, - ) -> Result { - let mut issues = Vec::new(); - - // Check if there were any actions - if actions.is_empty() { - return Ok(EvaluationResult::failure( - "No actions were taken", - vec!["No actions recorded".to_string()], - )); - } - - // Calculate action success rate - let successful = actions.iter().filter(|a| a.success).count(); - let total = actions.len(); - let success_rate = successful as f64 / total as f64; - - if success_rate < self.min_action_success_rate { - issues.push(format!( - "Action success rate {:.1}% below threshold {:.1}%", - success_rate * 100.0, - self.min_action_success_rate * 100.0 - )); - } - - // Count failures - let failures = actions.iter().filter(|a| !a.success).count() as u32; - if failures > self.max_failures { - issues.push(format!( - "Too many failures: {} (max {})", - failures, self.max_failures - )); - } - - // Check for critical errors - for action in actions.iter().filter(|a| !a.success) { - if let Some(ref error) = action.error - && (error.to_lowercase().contains("critical") - || error.to_lowercase().contains("fatal")) - { - issues.push(format!("Critical error in {}: {}", action.tool_name, error)); - } - } - - // Check job state - if job.state != crate::context::JobState::Completed - && job.state != crate::context::JobState::Submitted - { - issues.push(format!("Job not in completed state: {:?}", job.state)); - } - - // Calculate quality score - let quality_score = if issues.is_empty() { - let base_score = (success_rate * 80.0) as u32; - let completion_bonus = if job.state == crate::context::JobState::Completed { - 20 - } else { - 0 - }; - (base_score + completion_bonus).min(100) - } else { - ((success_rate * 50.0) as u32).min(50) - }; - - if issues.is_empty() { - Ok(EvaluationResult::success( - format!( - "Job completed successfully with {}/{} actions succeeding ({:.1}%)", - successful, - total, - success_rate * 100.0 - ), - quality_score, - )) - } else { - Ok(EvaluationResult { - success: false, - confidence: 0.85, - reasoning: format!("Job had {} issues", issues.len()), - issues, - suggestions: vec![ - "Review failed actions for common patterns".to_string(), - "Consider adjusting retry logic".to_string(), - ], - quality_score, - }) - } - } -} - -/// LLM-based success evaluator for more nuanced evaluation. -pub struct LlmEvaluator { - llm: Arc, -} - -impl LlmEvaluator { - /// Create a new LLM-based evaluator. - #[allow(dead_code)] // Public API for LLM-based evaluation - pub fn new(llm: Arc) -> Self { - Self { llm } - } -} - -#[async_trait] -impl SuccessEvaluator for LlmEvaluator { - async fn evaluate( - &self, - job: &JobContext, - actions: &[ActionRecord], - output: Option<&str>, - ) -> Result { - // Build evaluation prompt - let actions_summary: Vec = actions - .iter() - .map(|a| { - format!( - "- {}: {} ({})", - a.tool_name, - if a.success { "success" } else { "failed" }, - a.error.as_deref().unwrap_or("ok") - ) - }) - .collect(); - - let prompt = format!( - r#"Evaluate if this job was completed successfully. - -Job: {} -Description: {} -State: {:?} - -Actions taken: -{} - -{} - -Respond in JSON format: -{{ - "success": true/false, - "confidence": 0.0-1.0, - "reasoning": "...", - "issues": ["..."], - "suggestions": ["..."], - "quality_score": 0-100 -}}"#, - job.title, - job.description, - job.state, - actions_summary.join("\n"), - output - .map(|o| format!("Output:\n{}", o)) - .unwrap_or_default() - ); - - let request = - crate::llm::CompletionRequest::new(vec![crate::llm::ChatMessage::user(prompt)]) - .with_max_tokens(1024) - .with_temperature(0.1); - - let response = self - .llm - .complete(request) - .await - .map_err(|e| EvaluationError::Failed { - job_id: job.job_id, - reason: e.to_string(), - })?; - - // Parse the response - let result: EvaluationResult = - serde_json::from_str(&response.content).map_err(|e| EvaluationError::Failed { - job_id: job.job_id, - reason: format!("Failed to parse LLM evaluation: {}", e), - })?; - - Ok(result) - } -} - #[cfg(test)] mod tests { use super::*; - use crate::context::JobContext; + use crate::context::{ActionRecord, JobContext}; + use crate::error::EvaluationError; + + /// Rule-based success evaluator (test-only; no production callers). + struct RuleBasedEvaluator { + min_action_success_rate: f64, + max_failures: u32, + } + + impl RuleBasedEvaluator { + fn new() -> Self { + Self { + min_action_success_rate: 0.8, + max_failures: 3, + } + } + + fn with_min_success_rate(mut self, rate: f64) -> Self { + self.min_action_success_rate = rate; + self + } + + fn with_max_failures(mut self, max: u32) -> Self { + self.max_failures = max; + self + } + } + + impl Default for RuleBasedEvaluator { + fn default() -> Self { + Self::new() + } + } + + #[async_trait::async_trait] + impl SuccessEvaluator for RuleBasedEvaluator { + async fn evaluate( + &self, + job: &JobContext, + actions: &[ActionRecord], + _output: Option<&str>, + ) -> Result { + let mut issues = Vec::new(); + + if actions.is_empty() { + return Ok(EvaluationResult::failure( + "No actions were taken", + vec!["No actions recorded".to_string()], + )); + } + + let successful = actions.iter().filter(|a| a.success).count(); + let total = actions.len(); + let success_rate = successful as f64 / total as f64; + + if success_rate < self.min_action_success_rate { + issues.push(format!( + "Action success rate {:.1}% below threshold {:.1}%", + success_rate * 100.0, + self.min_action_success_rate * 100.0 + )); + } + + let failures = actions.iter().filter(|a| !a.success).count() as u32; + if failures > self.max_failures { + issues.push(format!( + "Too many failures: {} (max {})", + failures, self.max_failures + )); + } + + for action in actions.iter().filter(|a| !a.success) { + if let Some(ref error) = action.error + && (error.to_lowercase().contains("critical") + || error.to_lowercase().contains("fatal")) + { + issues.push(format!("Critical error in {}: {}", action.tool_name, error)); + } + } + + if job.state != crate::context::JobState::Completed + && job.state != crate::context::JobState::Submitted + { + issues.push(format!("Job not in completed state: {:?}", job.state)); + } + + let quality_score = if issues.is_empty() { + let base_score = (success_rate * 80.0) as u32; + let completion_bonus = if job.state == crate::context::JobState::Completed { + 20 + } else { + 0 + }; + (base_score + completion_bonus).min(100) + } else { + ((success_rate * 50.0) as u32).min(50) + }; + + if issues.is_empty() { + Ok(EvaluationResult::success( + format!( + "Job completed successfully with {}/{} actions succeeding ({:.1}%)", + successful, + total, + success_rate * 100.0 + ), + quality_score, + )) + } else { + Ok(EvaluationResult { + success: false, + confidence: 0.85, + reasoning: format!("Job had {} issues", issues.len()), + issues, + suggestions: vec![ + "Review failed actions for common patterns".to_string(), + "Consider adjusting retry logic".to_string(), + ], + quality_score, + }) + } + } + } #[tokio::test] async fn test_rule_based_evaluator_success() { diff --git a/src/extensions/manager.rs b/src/extensions/manager.rs index 6ef47d22..193ec56e 100644 --- a/src/extensions/manager.rs +++ b/src/extensions/manager.rs @@ -1405,38 +1405,6 @@ impl ExtensionManager { Ok(()) } - #[allow(dead_code)] // Used by upcoming hot-activation flow - async fn install_bundled_channel_from_artifacts( - &self, - name: &str, - ) -> Result { - // Check if already installed - let channel_wasm = self.wasm_channels_dir.join(format!("{}.wasm", name)); - if channel_wasm.exists() { - return Err(ExtensionError::AlreadyInstalled(name.to_string())); - } - - crate::channels::wasm::install_bundled_channel(name, &self.wasm_channels_dir, false) - .await - .map_err(ExtensionError::InstallFailed)?; - - tracing::info!( - "Installed bundled channel '{}' to {}", - name, - self.wasm_channels_dir.display() - ); - - Ok(InstallResult { - name: name.to_string(), - kind: ExtensionKind::WasmChannel, - message: format!( - "Channel '{}' installed. \ - Run tool_auth('{}') to configure authentication, then activate.", - name, name, - ), - }) - } - /// Install a WASM extension from local build artifacts (WasmBuildable source). /// /// Resolves the build directory (relative to `CARGO_MANIFEST_DIR` or absolute), diff --git a/src/llm/reasoning.rs b/src/llm/reasoning.rs index 79b0dcb2..c2d2462c 100644 --- a/src/llm/reasoning.rs +++ b/src/llm/reasoning.rs @@ -11,7 +11,6 @@ use crate::llm::{ ChatMessage, CompletionRequest, LlmProvider, Role, ToolCall, ToolCompletionRequest, ToolDefinition, }; -use crate::safety::SafetyLayer; /// Token the agent returns when it has nothing to say (e.g. in group chats). /// The dispatcher should check for this and suppress the message. @@ -343,8 +342,6 @@ pub struct RespondOutput { /// Reasoning engine for the agent. pub struct Reasoning { llm: Arc, - #[allow(dead_code)] // Will be used for sanitizing tool outputs - safety: Arc, /// Optional workspace for loading identity/system prompts. workspace_system_prompt: Option, /// Optional skill context block to inject into system prompt. @@ -362,10 +359,9 @@ pub struct Reasoning { impl Reasoning { /// Create a new reasoning engine. - pub fn new(llm: Arc, safety: Arc) -> Self { + pub fn new(llm: Arc) -> Self { Self { llm, - safety, workspace_system_prompt: None, skill_context: None, channel: None, @@ -2117,15 +2113,9 @@ That's my plan."#; // ---- System prompt building tests (issue #565) ---- fn make_test_reasoning() -> Reasoning { - use crate::config::SafetyConfig; - use crate::safety::SafetyLayer; use crate::testing::StubLlm; let llm = Arc::new(StubLlm::new("test")); - let safety = Arc::new(SafetyLayer::new(&SafetyConfig { - max_output_length: 100_000, - injection_check_enabled: false, - })); - Reasoning::new(llm, safety) + Reasoning::new(llm) } #[test] diff --git a/src/tools/builder/core.rs b/src/tools/builder/core.rs index 54a179a7..0400d24d 100644 --- a/src/tools/builder/core.rs +++ b/src/tools/builder/core.rs @@ -43,7 +43,6 @@ use crate::error::ToolError as AgentToolError; use crate::llm::{ ChatMessage, LlmProvider, Reasoning, ReasoningContext, RespondResult, ToolDefinition, }; -use crate::safety::SafetyLayer; use crate::tools::ToolRegistry; use crate::tools::tool::{ApprovalRequirement, Tool, ToolError, ToolOutput}; @@ -251,29 +250,18 @@ pub trait SoftwareBuilder: Send + Sync { pub struct LlmSoftwareBuilder { config: BuilderConfig, llm: Arc, - safety: Arc, tools: Arc, } impl LlmSoftwareBuilder { /// Create a new LLM-based software builder. - pub fn new( - config: BuilderConfig, - llm: Arc, - safety: Arc, - tools: Arc, - ) -> Self { + pub fn new(config: BuilderConfig, llm: Arc, tools: Arc) -> Self { // Ensure build directory exists if let Err(e) = std::fs::create_dir_all(&config.build_dir) { tracing::warn!("Failed to create build directory: {}", e); } - Self { - config, - llm, - safety, - tools, - } + Self { config, llm, tools } } /// Get the build tools available for the build loop. @@ -521,7 +509,7 @@ Create alongside the .wasm file to grant capabilities: let mut iteration = 0; // Create reasoning engine - let reasoning = Reasoning::new(self.llm.clone(), self.safety.clone()); + let reasoning = Reasoning::new(self.llm.clone()); // Build initial context let tool_defs = self.get_build_tools().await; @@ -822,7 +810,7 @@ Create alongside the .wasm file to grant capabilities: impl SoftwareBuilder for LlmSoftwareBuilder { async fn analyze(&self, description: &str) -> Result { // Use LLM to parse the description - let reasoning = Reasoning::new(self.llm.clone(), self.safety.clone()); + let reasoning = Reasoning::new(self.llm.clone()); let prompt = format!( r#"Analyze this software requirement and extract structured information. diff --git a/src/tools/registry.rs b/src/tools/registry.rs index 498d1d58..44552541 100644 --- a/src/tools/registry.rs +++ b/src/tools/registry.rs @@ -10,7 +10,6 @@ use crate::db::Database; use crate::extensions::ExtensionManager; use crate::llm::{LlmProvider, ToolDefinition}; use crate::orchestrator::job_manager::ContainerJobManager; -use crate::safety::SafetyLayer; use crate::secrets::SecretsStore; use crate::skills::catalog::SkillCatalog; use crate::skills::registry::SkillRegistry; @@ -485,17 +484,15 @@ impl ToolRegistry { pub async fn register_builder_tool( self: &Arc, llm: Arc, - safety: Arc, config: Option, ) { // First register dev tools needed by the builder self.register_dev_tools(); - // Create the builder (arg order: config, llm, safety, tools) + // Create the builder (arg order: config, llm, tools) let builder = Arc::new(LlmSoftwareBuilder::new( config.unwrap_or_default(), llm, - safety, Arc::clone(self), )); diff --git a/src/worker/runtime.rs b/src/worker/runtime.rs index 3f284db5..5dd00e5a 100644 --- a/src/worker/runtime.rs +++ b/src/worker/runtime.rs @@ -133,7 +133,7 @@ impl WorkerRuntime { .await?; // Create reasoning engine - let reasoning = Reasoning::new(self.llm.clone(), self.safety.clone()); + let reasoning = Reasoning::new(self.llm.clone()); // Build initial context let mut reason_ctx = ReasoningContext::new().with_job(&job.description); diff --git a/src/workspace/chunker.rs b/src/workspace/chunker.rs index 7ab6f5b5..c71a4f3f 100644 --- a/src/workspace/chunker.rs +++ b/src/workspace/chunker.rs @@ -113,79 +113,6 @@ pub fn chunk_document(content: &str, config: ChunkConfig) -> Vec { chunks } -/// Split content by paragraphs first, then chunk. -/// -/// This is better for preserving semantic boundaries. -#[allow(dead_code)] // Alternative chunking strategy for paragraph-aware indexing -pub fn chunk_by_paragraphs(content: &str, config: ChunkConfig) -> Vec { - if content.is_empty() { - return Vec::new(); - } - - // Split by double newlines (paragraphs) - let paragraphs: Vec<&str> = content - .split("\n\n") - .map(|p| p.trim()) - .filter(|p| !p.is_empty()) - .collect(); - - if paragraphs.is_empty() { - return chunk_document(content, config); - } - - let mut chunks = Vec::new(); - let mut current_chunk = String::new(); - let mut current_word_count = 0; - - for paragraph in paragraphs { - let para_words = paragraph.split_whitespace().count(); - - // If this paragraph alone exceeds chunk size, chunk it separately - if para_words > config.chunk_size { - // Flush current chunk first - if !current_chunk.is_empty() { - chunks.push(current_chunk.trim().to_string()); - current_chunk = String::new(); - current_word_count = 0; - } - // Chunk the large paragraph - let para_chunks = chunk_document(paragraph, config.clone()); - chunks.extend(para_chunks); - continue; - } - - // Check if adding this paragraph would exceed chunk size - if current_word_count + para_words > config.chunk_size { - // Flush current chunk - if !current_chunk.is_empty() { - chunks.push(current_chunk.trim().to_string()); - } - current_chunk = paragraph.to_string(); - current_word_count = para_words; - } else { - // Add paragraph to current chunk - if !current_chunk.is_empty() { - current_chunk.push_str("\n\n"); - } - current_chunk.push_str(paragraph); - current_word_count += para_words; - } - } - - // Flush remaining content - if !current_chunk.is_empty() { - // If too small, merge with previous chunk if possible - if current_word_count < config.min_chunk_size && !chunks.is_empty() { - let last = chunks.pop().unwrap(); - chunks.push(format!("{}\n\n{}", last, current_chunk.trim())); - } else { - chunks.push(current_chunk.trim().to_string()); - } - } - - chunks -} - #[cfg(test)] mod tests { use super::*; @@ -253,49 +180,6 @@ mod tests { assert_eq!(config.step_size(), 85); } - #[test] - fn test_paragraph_chunking() { - let config = ChunkConfig::default().with_chunk_size(20); - - let content = "First paragraph with some words.\n\nSecond paragraph with different content.\n\nThird paragraph here."; - let chunks = chunk_by_paragraphs(content, config); - - // Should preserve paragraph boundaries - assert!(!chunks.is_empty()); - for chunk in &chunks { - // No chunk should start or end with \n\n - assert!(!chunk.starts_with("\n")); - assert!(!chunk.ends_with("\n")); - } - } - - #[test] - fn test_large_paragraph_handling() { - let config = ChunkConfig { - chunk_size: 10, - overlap_percent: 0.15, - min_chunk_size: 3, // Low threshold for test - }; - - // Create a paragraph with 30 words - let large_para = (1..=30) - .map(|i| format!("word{}", i)) - .collect::>() - .join(" "); - let content = format!("Short intro.\n\n{}\n\nShort outro.", large_para); - - let chunks = chunk_by_paragraphs(&content, config); - - // Should have multiple chunks due to large paragraph - // 30 words + 2 intro + 2 outro = 34 words, chunk_size=10 - // Expect at least 3 chunks - assert!( - chunks.len() >= 3, - "Expected at least 3 chunks for 34 words with chunk_size=10, got {}", - chunks.len() - ); - } - #[test] fn test_min_chunk_size_merging() { let config = ChunkConfig { diff --git a/tests/e2e_routine_heartbeat.rs b/tests/e2e_routine_heartbeat.rs index 2e124816..92141bac 100644 --- a/tests/e2e_routine_heartbeat.rs +++ b/tests/e2e_routine_heartbeat.rs @@ -20,9 +20,8 @@ mod tests { use ironclaw::agent::routine_engine::RoutineEngine; use ironclaw::agent::{HeartbeatConfig, HeartbeatRunner}; use ironclaw::channels::IncomingMessage; - use ironclaw::config::{RoutineConfig, SafetyConfig}; + use ironclaw::config::RoutineConfig; use ironclaw::db::Database; - use ironclaw::safety::SafetyLayer; use ironclaw::workspace::Workspace; use ironclaw::workspace::hygiene::HygieneConfig; @@ -346,10 +345,6 @@ mod tests { }], ); let llm = Arc::new(TraceLlm::from_trace(trace)); - let safety = Arc::new(SafetyLayer::new(&SafetyConfig { - max_output_length: 100_000, - injection_check_enabled: false, - })); let (tx, mut rx) = tokio::sync::mpsc::channel(16); @@ -361,9 +356,8 @@ mod tests { state_dir: _tmp.path().to_path_buf(), }; - let runner = - HeartbeatRunner::new(HeartbeatConfig::default(), hygiene_config, ws, llm, safety) - .with_response_channel(tx); + let runner = HeartbeatRunner::new(HeartbeatConfig::default(), hygiene_config, ws, llm) + .with_response_channel(tx); let result = runner.check_heartbeat().await; match result { @@ -400,10 +394,6 @@ mod tests { // LLM should NOT be called, so provide a trace that would panic if called. let trace = LlmTrace::single_turn("test-heartbeat-skip", "skip", vec![]); let llm = Arc::new(TraceLlm::from_trace(trace)); - let safety = Arc::new(SafetyLayer::new(&SafetyConfig { - max_output_length: 100_000, - injection_check_enabled: false, - })); let hygiene_config = HygieneConfig { enabled: false, @@ -413,8 +403,7 @@ mod tests { state_dir: _tmp.path().to_path_buf(), }; - let runner = - HeartbeatRunner::new(HeartbeatConfig::default(), hygiene_config, ws, llm, safety); + let runner = HeartbeatRunner::new(HeartbeatConfig::default(), hygiene_config, ws, llm); let result = runner.check_heartbeat().await; assert!( diff --git a/tests/heartbeat_integration.rs b/tests/heartbeat_integration.rs index 227f59f9..917e20b4 100644 --- a/tests/heartbeat_integration.rs +++ b/tests/heartbeat_integration.rs @@ -15,7 +15,6 @@ use ironclaw::{ config::Config, history::Store, llm::{create_llm_provider, create_session_manager}, - safety::SafetyLayer, workspace::Workspace, }; @@ -93,8 +92,7 @@ async fn test_heartbeat_end_to_end() { let hb_config = ironclaw::agent::HeartbeatConfig::default(); let hygiene_config = ironclaw::workspace::hygiene::HygieneConfig::default(); - let safety = Arc::new(SafetyLayer::new(&config.safety)); - let runner = HeartbeatRunner::new(hb_config, hygiene_config, workspace, llm, safety); + let runner = HeartbeatRunner::new(hb_config, hygiene_config, workspace, llm); let result = runner.check_heartbeat().await;