feat: enhance agent personality, tool guidance, and memory hygiene

- Expand communication style presets (professional, expressive, custom)
- Enrich SOUL.md with human-like tone and emoji-awareness guidance
- Add crash recovery and sub-task scoping guidance to AGENTS.md scaffold
- Add 'Use when / Don't use when' guidance to TOOLS.md and runtime prompts
- Implement memory hygiene system with configurable archiving and retention
- Add MemoryConfig options: hygiene_enabled, archive_after_days, purge_after_days, conversation_retention_days
- Archive old daily memory and session files to archive subdirectories
- Purge old archives and prune stale SQLite conversation rows
- Add comprehensive tests for new features
This commit is contained in:
argenis de la rosa 2026-02-14 11:28:39 -05:00
parent f4f180ac41
commit ec2d5cc93d
29 changed files with 3600 additions and 116 deletions

View file

@ -3,11 +3,13 @@ pub mod compatible;
pub mod ollama;
pub mod openai;
pub mod openrouter;
pub mod reliable;
pub mod traits;
pub use traits::Provider;
use compatible::{AuthStyle, OpenAiCompatibleProvider};
use reliable::ReliableProvider;
/// Factory: create the right provider from config
#[allow(clippy::too_many_lines)]
@ -110,6 +112,42 @@ pub fn create_provider(name: &str, api_key: Option<&str>) -> anyhow::Result<Box<
}
}
/// Create provider chain with retry and fallback behavior.
pub fn create_resilient_provider(
primary_name: &str,
api_key: Option<&str>,
reliability: &crate::config::ReliabilityConfig,
) -> anyhow::Result<Box<dyn Provider>> {
let mut providers: Vec<(String, Box<dyn Provider>)> = Vec::new();
providers.push((
primary_name.to_string(),
create_provider(primary_name, api_key)?,
));
for fallback in &reliability.fallback_providers {
if fallback == primary_name || providers.iter().any(|(name, _)| name == fallback) {
continue;
}
match create_provider(fallback, api_key) {
Ok(provider) => providers.push((fallback.clone(), provider)),
Err(e) => {
tracing::warn!(
fallback_provider = fallback,
"Ignoring invalid fallback provider: {e}"
);
}
}
}
Ok(Box::new(ReliableProvider::new(
providers,
reliability.provider_retries,
reliability.provider_backoff_ms,
)))
}
#[cfg(test)]
mod tests {
use super::*;
@ -294,6 +332,34 @@ mod tests {
assert!(create_provider("", None).is_err());
}
#[test]
fn resilient_provider_ignores_duplicate_and_invalid_fallbacks() {
let reliability = crate::config::ReliabilityConfig {
provider_retries: 1,
provider_backoff_ms: 100,
fallback_providers: vec![
"openrouter".into(),
"nonexistent-provider".into(),
"openai".into(),
"openai".into(),
],
channel_initial_backoff_secs: 2,
channel_max_backoff_secs: 60,
scheduler_poll_secs: 15,
scheduler_retries: 2,
};
let provider = create_resilient_provider("openrouter", Some("sk-test"), &reliability);
assert!(provider.is_ok());
}
#[test]
fn resilient_provider_errors_for_invalid_primary() {
let reliability = crate::config::ReliabilityConfig::default();
let provider = create_resilient_provider("totally-invalid", Some("sk-test"), &reliability);
assert!(provider.is_err());
}
#[test]
fn factory_all_providers_create_successfully() {
let providers = [