feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
|
pub struct Config {
|
|
|
|
|
pub matrix: MatrixConfig,
|
|
|
|
|
pub opensearch: OpenSearchConfig,
|
|
|
|
|
pub mistral: MistralConfig,
|
|
|
|
|
pub behavior: BehaviorConfig,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
|
pub struct MatrixConfig {
|
|
|
|
|
pub homeserver_url: String,
|
|
|
|
|
pub user_id: String,
|
|
|
|
|
pub state_store_path: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
|
pub struct OpenSearchConfig {
|
|
|
|
|
pub url: String,
|
|
|
|
|
pub index: String,
|
|
|
|
|
#[serde(default = "default_batch_size")]
|
|
|
|
|
pub batch_size: usize,
|
|
|
|
|
#[serde(default = "default_flush_interval_ms")]
|
|
|
|
|
pub flush_interval_ms: u64,
|
|
|
|
|
#[serde(default = "default_embedding_pipeline")]
|
|
|
|
|
pub embedding_pipeline: String,
|
2026-03-21 15:51:31 +00:00
|
|
|
#[serde(default = "default_memory_index")]
|
|
|
|
|
pub memory_index: String,
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
|
pub struct MistralConfig {
|
|
|
|
|
#[serde(default = "default_model")]
|
|
|
|
|
pub default_model: String,
|
|
|
|
|
#[serde(default = "default_evaluation_model")]
|
|
|
|
|
pub evaluation_model: String,
|
|
|
|
|
#[serde(default = "default_research_model")]
|
|
|
|
|
pub research_model: String,
|
|
|
|
|
#[serde(default = "default_max_tool_iterations")]
|
|
|
|
|
pub max_tool_iterations: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
|
pub struct BehaviorConfig {
|
|
|
|
|
#[serde(default = "default_response_delay_min_ms")]
|
|
|
|
|
pub response_delay_min_ms: u64,
|
|
|
|
|
#[serde(default = "default_response_delay_max_ms")]
|
|
|
|
|
pub response_delay_max_ms: u64,
|
|
|
|
|
#[serde(default = "default_spontaneous_delay_min_ms")]
|
|
|
|
|
pub spontaneous_delay_min_ms: u64,
|
|
|
|
|
#[serde(default = "default_spontaneous_delay_max_ms")]
|
|
|
|
|
pub spontaneous_delay_max_ms: u64,
|
|
|
|
|
#[serde(default = "default_spontaneous_threshold")]
|
|
|
|
|
pub spontaneous_threshold: f32,
|
|
|
|
|
#[serde(default = "default_room_context_window")]
|
|
|
|
|
pub room_context_window: usize,
|
|
|
|
|
#[serde(default = "default_dm_context_window")]
|
|
|
|
|
pub dm_context_window: usize,
|
|
|
|
|
#[serde(default = "default_backfill_on_join")]
|
|
|
|
|
pub backfill_on_join: bool,
|
|
|
|
|
#[serde(default = "default_backfill_limit")]
|
|
|
|
|
pub backfill_limit: usize,
|
2026-03-21 15:51:31 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub instant_responses: bool,
|
|
|
|
|
#[serde(default = "default_cooldown_after_response_ms")]
|
|
|
|
|
pub cooldown_after_response_ms: u64,
|
|
|
|
|
#[serde(default = "default_evaluation_context_window")]
|
|
|
|
|
pub evaluation_context_window: usize,
|
|
|
|
|
#[serde(default = "default_detect_sol_in_conversation")]
|
|
|
|
|
pub detect_sol_in_conversation: bool,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub evaluation_prompt_active: Option<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub evaluation_prompt_passive: Option<String>,
|
|
|
|
|
#[serde(default = "default_reaction_threshold")]
|
|
|
|
|
pub reaction_threshold: f32,
|
|
|
|
|
#[serde(default = "default_reaction_enabled")]
|
|
|
|
|
pub reaction_enabled: bool,
|
|
|
|
|
#[serde(default = "default_script_timeout_secs")]
|
|
|
|
|
pub script_timeout_secs: u64,
|
|
|
|
|
#[serde(default = "default_script_max_heap_mb")]
|
|
|
|
|
pub script_max_heap_mb: usize,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub script_fetch_allowlist: Vec<String>,
|
|
|
|
|
#[serde(default = "default_memory_extraction_enabled")]
|
|
|
|
|
pub memory_extraction_enabled: bool,
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_batch_size() -> usize { 50 }
|
|
|
|
|
fn default_flush_interval_ms() -> u64 { 2000 }
|
|
|
|
|
fn default_embedding_pipeline() -> String { "tuwunel_embedding_pipeline".into() }
|
|
|
|
|
fn default_model() -> String { "mistral-medium-latest".into() }
|
|
|
|
|
fn default_evaluation_model() -> String { "ministral-3b-latest".into() }
|
|
|
|
|
fn default_research_model() -> String { "mistral-large-latest".into() }
|
|
|
|
|
fn default_max_tool_iterations() -> usize { 5 }
|
2026-03-21 15:51:31 +00:00
|
|
|
fn default_response_delay_min_ms() -> u64 { 100 }
|
|
|
|
|
fn default_response_delay_max_ms() -> u64 { 2300 }
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
fn default_spontaneous_delay_min_ms() -> u64 { 15000 }
|
|
|
|
|
fn default_spontaneous_delay_max_ms() -> u64 { 60000 }
|
2026-03-21 15:51:31 +00:00
|
|
|
fn default_spontaneous_threshold() -> f32 { 0.85 }
|
|
|
|
|
fn default_cooldown_after_response_ms() -> u64 { 15000 }
|
|
|
|
|
fn default_evaluation_context_window() -> usize { 25 }
|
|
|
|
|
fn default_detect_sol_in_conversation() -> bool { true }
|
|
|
|
|
fn default_reaction_threshold() -> f32 { 0.6 }
|
|
|
|
|
fn default_reaction_enabled() -> bool { true }
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
fn default_room_context_window() -> usize { 30 }
|
|
|
|
|
fn default_dm_context_window() -> usize { 100 }
|
|
|
|
|
fn default_backfill_on_join() -> bool { true }
|
|
|
|
|
fn default_backfill_limit() -> usize { 10000 }
|
2026-03-21 15:51:31 +00:00
|
|
|
fn default_script_timeout_secs() -> u64 { 5 }
|
|
|
|
|
fn default_script_max_heap_mb() -> usize { 64 }
|
|
|
|
|
fn default_memory_index() -> String { "sol_user_memory".into() }
|
|
|
|
|
fn default_memory_extraction_enabled() -> bool { true }
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
|
|
|
|
|
impl Config {
|
|
|
|
|
pub fn load(path: &str) -> anyhow::Result<Self> {
|
|
|
|
|
let content = std::fs::read_to_string(path)?;
|
|
|
|
|
let config: Config = toml::from_str(&content)?;
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn from_str(content: &str) -> anyhow::Result<Self> {
|
|
|
|
|
let config: Config = toml::from_str(content)?;
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
const MINIMAL_CONFIG: &str = r#"
|
|
|
|
|
[matrix]
|
|
|
|
|
homeserver_url = "https://chat.sunbeam.pt"
|
|
|
|
|
user_id = "@sol:sunbeam.pt"
|
|
|
|
|
state_store_path = "/data/sol/state"
|
|
|
|
|
|
|
|
|
|
[opensearch]
|
|
|
|
|
url = "http://opensearch:9200"
|
|
|
|
|
index = "sol-archive"
|
|
|
|
|
|
|
|
|
|
[mistral]
|
|
|
|
|
|
|
|
|
|
[behavior]
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
const FULL_CONFIG: &str = r#"
|
|
|
|
|
[matrix]
|
|
|
|
|
homeserver_url = "https://chat.sunbeam.pt"
|
|
|
|
|
user_id = "@sol:sunbeam.pt"
|
|
|
|
|
state_store_path = "/data/sol/state"
|
|
|
|
|
|
|
|
|
|
[opensearch]
|
|
|
|
|
url = "http://opensearch:9200"
|
|
|
|
|
index = "sol-archive"
|
|
|
|
|
batch_size = 100
|
|
|
|
|
flush_interval_ms = 5000
|
|
|
|
|
embedding_pipeline = "my_pipeline"
|
|
|
|
|
|
|
|
|
|
[mistral]
|
|
|
|
|
default_model = "mistral-large-latest"
|
|
|
|
|
evaluation_model = "ministral-8b-latest"
|
|
|
|
|
research_model = "mistral-large-latest"
|
|
|
|
|
max_tool_iterations = 10
|
|
|
|
|
|
|
|
|
|
[behavior]
|
|
|
|
|
response_delay_min_ms = 1000
|
|
|
|
|
response_delay_max_ms = 5000
|
|
|
|
|
spontaneous_delay_min_ms = 10000
|
|
|
|
|
spontaneous_delay_max_ms = 30000
|
|
|
|
|
spontaneous_threshold = 0.8
|
|
|
|
|
room_context_window = 50
|
|
|
|
|
dm_context_window = 200
|
|
|
|
|
backfill_on_join = false
|
|
|
|
|
backfill_limit = 5000
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_minimal_config_with_defaults() {
|
|
|
|
|
let config = Config::from_str(MINIMAL_CONFIG).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(config.matrix.homeserver_url, "https://chat.sunbeam.pt");
|
|
|
|
|
assert_eq!(config.matrix.user_id, "@sol:sunbeam.pt");
|
|
|
|
|
assert_eq!(config.matrix.state_store_path, "/data/sol/state");
|
|
|
|
|
assert_eq!(config.opensearch.url, "http://opensearch:9200");
|
|
|
|
|
assert_eq!(config.opensearch.index, "sol-archive");
|
|
|
|
|
|
|
|
|
|
// Check defaults
|
|
|
|
|
assert_eq!(config.opensearch.batch_size, 50);
|
|
|
|
|
assert_eq!(config.opensearch.flush_interval_ms, 2000);
|
|
|
|
|
assert_eq!(config.opensearch.embedding_pipeline, "tuwunel_embedding_pipeline");
|
2026-03-21 15:51:31 +00:00
|
|
|
assert_eq!(config.opensearch.memory_index, "sol_user_memory");
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
assert_eq!(config.mistral.default_model, "mistral-medium-latest");
|
|
|
|
|
assert_eq!(config.mistral.evaluation_model, "ministral-3b-latest");
|
|
|
|
|
assert_eq!(config.mistral.research_model, "mistral-large-latest");
|
|
|
|
|
assert_eq!(config.mistral.max_tool_iterations, 5);
|
2026-03-21 15:51:31 +00:00
|
|
|
assert_eq!(config.behavior.response_delay_min_ms, 100);
|
|
|
|
|
assert_eq!(config.behavior.response_delay_max_ms, 2300);
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
assert_eq!(config.behavior.spontaneous_delay_min_ms, 15000);
|
|
|
|
|
assert_eq!(config.behavior.spontaneous_delay_max_ms, 60000);
|
2026-03-21 15:51:31 +00:00
|
|
|
assert!((config.behavior.spontaneous_threshold - 0.85).abs() < f32::EPSILON);
|
|
|
|
|
assert!(!config.behavior.instant_responses);
|
|
|
|
|
assert_eq!(config.behavior.cooldown_after_response_ms, 15000);
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
assert_eq!(config.behavior.room_context_window, 30);
|
|
|
|
|
assert_eq!(config.behavior.dm_context_window, 100);
|
|
|
|
|
assert!(config.behavior.backfill_on_join);
|
|
|
|
|
assert_eq!(config.behavior.backfill_limit, 10000);
|
2026-03-21 15:51:31 +00:00
|
|
|
assert!(config.behavior.memory_extraction_enabled);
|
feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all
messages to OpenSearch and responds to queries via Mistral AI with
function calling tools.
Core systems:
- Archive: bulk OpenSearch indexer with batch/flush, edit/redaction
handling, embedding pipeline passthrough
- Brain: rule-based engagement evaluator (mentions, DMs, name
invocations), LLM-powered spontaneous engagement, per-room
conversation context windows, response delay simulation
- Tools: search_archive, get_room_context, list_rooms, get_room_members
registered as Mistral function calling tools with iterative tool loop
- Personality: templated system prompt with Sol's librarian persona
47 unit tests covering config, evaluator, conversation windowing,
personality templates, schema serialization, and search query building.
2026-03-20 21:40:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_full_config_overrides() {
|
|
|
|
|
let config = Config::from_str(FULL_CONFIG).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(config.opensearch.batch_size, 100);
|
|
|
|
|
assert_eq!(config.opensearch.flush_interval_ms, 5000);
|
|
|
|
|
assert_eq!(config.opensearch.embedding_pipeline, "my_pipeline");
|
|
|
|
|
assert_eq!(config.mistral.default_model, "mistral-large-latest");
|
|
|
|
|
assert_eq!(config.mistral.evaluation_model, "ministral-8b-latest");
|
|
|
|
|
assert_eq!(config.mistral.max_tool_iterations, 10);
|
|
|
|
|
assert_eq!(config.behavior.response_delay_min_ms, 1000);
|
|
|
|
|
assert_eq!(config.behavior.response_delay_max_ms, 5000);
|
|
|
|
|
assert!((config.behavior.spontaneous_threshold - 0.8).abs() < f32::EPSILON);
|
|
|
|
|
assert_eq!(config.behavior.room_context_window, 50);
|
|
|
|
|
assert_eq!(config.behavior.dm_context_window, 200);
|
|
|
|
|
assert!(!config.behavior.backfill_on_join);
|
|
|
|
|
assert_eq!(config.behavior.backfill_limit, 5000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_missing_required_section_fails() {
|
|
|
|
|
let bad = r#"
|
|
|
|
|
[matrix]
|
|
|
|
|
homeserver_url = "https://chat.sunbeam.pt"
|
|
|
|
|
user_id = "@sol:sunbeam.pt"
|
|
|
|
|
state_store_path = "/data/sol/state"
|
|
|
|
|
"#;
|
|
|
|
|
assert!(Config::from_str(bad).is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_missing_required_field_fails() {
|
|
|
|
|
let bad = r#"
|
|
|
|
|
[matrix]
|
|
|
|
|
homeserver_url = "https://chat.sunbeam.pt"
|
|
|
|
|
state_store_path = "/data/sol/state"
|
|
|
|
|
|
|
|
|
|
[opensearch]
|
|
|
|
|
url = "http://opensearch:9200"
|
|
|
|
|
index = "sol-archive"
|
|
|
|
|
|
|
|
|
|
[mistral]
|
|
|
|
|
[behavior]
|
|
|
|
|
"#;
|
|
|
|
|
assert!(Config::from_str(bad).is_err());
|
|
|
|
|
}
|
|
|
|
|
}
|