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:
parent
f4f180ac41
commit
ec2d5cc93d
29 changed files with 3600 additions and 116 deletions
|
|
@ -25,6 +25,9 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub runtime: RuntimeConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub reliability: ReliabilityConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub heartbeat: HeartbeatConfig,
|
||||
|
||||
|
|
@ -143,6 +146,18 @@ pub struct MemoryConfig {
|
|||
pub backend: String,
|
||||
/// Auto-save conversation context to memory
|
||||
pub auto_save: bool,
|
||||
/// Run memory/session hygiene (archiving + retention cleanup)
|
||||
#[serde(default = "default_hygiene_enabled")]
|
||||
pub hygiene_enabled: bool,
|
||||
/// Archive daily/session files older than this many days
|
||||
#[serde(default = "default_archive_after_days")]
|
||||
pub archive_after_days: u32,
|
||||
/// Purge archived files older than this many days
|
||||
#[serde(default = "default_purge_after_days")]
|
||||
pub purge_after_days: u32,
|
||||
/// For sqlite backend: prune conversation rows older than this many days
|
||||
#[serde(default = "default_conversation_retention_days")]
|
||||
pub conversation_retention_days: u32,
|
||||
/// Embedding provider: "none" | "openai" | "custom:URL"
|
||||
#[serde(default = "default_embedding_provider")]
|
||||
pub embedding_provider: String,
|
||||
|
|
@ -169,6 +184,18 @@ pub struct MemoryConfig {
|
|||
fn default_embedding_provider() -> String {
|
||||
"none".into()
|
||||
}
|
||||
fn default_hygiene_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_archive_after_days() -> u32 {
|
||||
7
|
||||
}
|
||||
fn default_purge_after_days() -> u32 {
|
||||
30
|
||||
}
|
||||
fn default_conversation_retention_days() -> u32 {
|
||||
30
|
||||
}
|
||||
fn default_embedding_model() -> String {
|
||||
"text-embedding-3-small".into()
|
||||
}
|
||||
|
|
@ -193,6 +220,10 @@ impl Default for MemoryConfig {
|
|||
Self {
|
||||
backend: "sqlite".into(),
|
||||
auto_save: true,
|
||||
hygiene_enabled: default_hygiene_enabled(),
|
||||
archive_after_days: default_archive_after_days(),
|
||||
purge_after_days: default_purge_after_days(),
|
||||
conversation_retention_days: default_conversation_retention_days(),
|
||||
embedding_provider: default_embedding_provider(),
|
||||
embedding_model: default_embedding_model(),
|
||||
embedding_dimensions: default_embedding_dims(),
|
||||
|
|
@ -281,7 +312,9 @@ impl Default for AutonomyConfig {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RuntimeConfig {
|
||||
/// "native" | "docker" | "cloudflare"
|
||||
/// Runtime kind (currently supported: "native").
|
||||
///
|
||||
/// Reserved values (not implemented yet): "docker", "cloudflare".
|
||||
pub kind: String,
|
||||
}
|
||||
|
||||
|
|
@ -293,6 +326,71 @@ impl Default for RuntimeConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Reliability / supervision ────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReliabilityConfig {
|
||||
/// Retries per provider before failing over.
|
||||
#[serde(default = "default_provider_retries")]
|
||||
pub provider_retries: u32,
|
||||
/// Base backoff (ms) for provider retry delay.
|
||||
#[serde(default = "default_provider_backoff_ms")]
|
||||
pub provider_backoff_ms: u64,
|
||||
/// Fallback provider chain (e.g. `["anthropic", "openai"]`).
|
||||
#[serde(default)]
|
||||
pub fallback_providers: Vec<String>,
|
||||
/// Initial backoff for channel/daemon restarts.
|
||||
#[serde(default = "default_channel_backoff_secs")]
|
||||
pub channel_initial_backoff_secs: u64,
|
||||
/// Max backoff for channel/daemon restarts.
|
||||
#[serde(default = "default_channel_backoff_max_secs")]
|
||||
pub channel_max_backoff_secs: u64,
|
||||
/// Scheduler polling cadence in seconds.
|
||||
#[serde(default = "default_scheduler_poll_secs")]
|
||||
pub scheduler_poll_secs: u64,
|
||||
/// Max retries for cron job execution attempts.
|
||||
#[serde(default = "default_scheduler_retries")]
|
||||
pub scheduler_retries: u32,
|
||||
}
|
||||
|
||||
fn default_provider_retries() -> u32 {
|
||||
2
|
||||
}
|
||||
|
||||
fn default_provider_backoff_ms() -> u64 {
|
||||
500
|
||||
}
|
||||
|
||||
fn default_channel_backoff_secs() -> u64 {
|
||||
2
|
||||
}
|
||||
|
||||
fn default_channel_backoff_max_secs() -> u64 {
|
||||
60
|
||||
}
|
||||
|
||||
fn default_scheduler_poll_secs() -> u64 {
|
||||
15
|
||||
}
|
||||
|
||||
fn default_scheduler_retries() -> u32 {
|
||||
2
|
||||
}
|
||||
|
||||
impl Default for ReliabilityConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider_retries: default_provider_retries(),
|
||||
provider_backoff_ms: default_provider_backoff_ms(),
|
||||
fallback_providers: Vec::new(),
|
||||
channel_initial_backoff_secs: default_channel_backoff_secs(),
|
||||
channel_max_backoff_secs: default_channel_backoff_max_secs(),
|
||||
scheduler_poll_secs: default_scheduler_poll_secs(),
|
||||
scheduler_retries: default_scheduler_retries(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Heartbeat ────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -463,6 +561,7 @@ impl Default for Config {
|
|||
observability: ObservabilityConfig::default(),
|
||||
autonomy: AutonomyConfig::default(),
|
||||
runtime: RuntimeConfig::default(),
|
||||
reliability: ReliabilityConfig::default(),
|
||||
heartbeat: HeartbeatConfig::default(),
|
||||
channels_config: ChannelsConfig::default(),
|
||||
memory: MemoryConfig::default(),
|
||||
|
|
@ -558,6 +657,17 @@ mod tests {
|
|||
assert_eq!(h.interval_minutes, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_config_default_hygiene_settings() {
|
||||
let m = MemoryConfig::default();
|
||||
assert_eq!(m.backend, "sqlite");
|
||||
assert!(m.auto_save);
|
||||
assert!(m.hygiene_enabled);
|
||||
assert_eq!(m.archive_after_days, 7);
|
||||
assert_eq!(m.purge_after_days, 30);
|
||||
assert_eq!(m.conversation_retention_days, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn channels_config_default() {
|
||||
let c = ChannelsConfig::default();
|
||||
|
|
@ -591,6 +701,7 @@ mod tests {
|
|||
runtime: RuntimeConfig {
|
||||
kind: "docker".into(),
|
||||
},
|
||||
reliability: ReliabilityConfig::default(),
|
||||
heartbeat: HeartbeatConfig {
|
||||
enabled: true,
|
||||
interval_minutes: 15,
|
||||
|
|
@ -650,6 +761,10 @@ default_temperature = 0.7
|
|||
assert_eq!(parsed.runtime.kind, "native");
|
||||
assert!(!parsed.heartbeat.enabled);
|
||||
assert!(parsed.channels_config.cli);
|
||||
assert!(parsed.memory.hygiene_enabled);
|
||||
assert_eq!(parsed.memory.archive_after_days, 7);
|
||||
assert_eq!(parsed.memory.purge_after_days, 30);
|
||||
assert_eq!(parsed.memory.conversation_retention_days, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -669,6 +784,7 @@ default_temperature = 0.7
|
|||
observability: ObservabilityConfig::default(),
|
||||
autonomy: AutonomyConfig::default(),
|
||||
runtime: RuntimeConfig::default(),
|
||||
reliability: ReliabilityConfig::default(),
|
||||
heartbeat: HeartbeatConfig::default(),
|
||||
channels_config: ChannelsConfig::default(),
|
||||
memory: MemoryConfig::default(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue