feat: multi-agent architecture with Conversations API and persistent state

Mistral Agents + Conversations API integration:
- Orchestrator agent created on startup with Sol's personality + tools
- ConversationRegistry routes messages through persistent conversations
- Per-room conversation state (room_id → conversation_id + token counts)
- Function call handling within conversation responses
- Configurable via [agents] section in sol.toml (use_conversations_api flag)

Multimodal support:
- m.image detection and Matrix media download (mxc:// → base64 data URI)
- ContentPart-based messages sent to Mistral vision models
- Archive stores media_urls for image messages

System prompt rewrite:
- 687 → 150 lines — dense, few-shot examples, hard rules
- {room_context_rules} placeholder for group vs DM behavior
- Sender prefixing (<@user:server>) for multi-user turns in group rooms

SQLite persistence (/data/sol.db):
- Conversation mappings and agent IDs survive reboots
- WAL mode for concurrent reads
- Falls back to in-memory on failure (sneezes into all rooms to signal)
- PVC already mounted at /data alongside Matrix SDK state store

New modules:
- src/persistence.rs — SQLite state store
- src/conversations.rs — ConversationRegistry + message merging
- src/agents/{mod,definitions,registry}.rs — agent lifecycle
- src/agent_ux.rs — reaction + thread progress UX
- src/tools/bridge.rs — tool dispatch for domain agents

102 tests passing.
This commit is contained in:
2026-03-21 22:21:14 +00:00
parent 5e2186f324
commit 7580c10dda
20 changed files with 1723 additions and 655 deletions

View File

@@ -6,6 +6,35 @@ pub struct Config {
pub opensearch: OpenSearchConfig,
pub mistral: MistralConfig,
pub behavior: BehaviorConfig,
#[serde(default)]
pub agents: AgentsConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AgentsConfig {
/// Model for the orchestrator agent.
#[serde(default = "default_model")]
pub orchestrator_model: String,
/// Model for domain agents.
#[serde(default = "default_model")]
pub domain_model: String,
/// Token threshold for conversation compaction (~90% of model context window).
#[serde(default = "default_compaction_threshold")]
pub compaction_threshold: u32,
/// Whether to use the Conversations API (vs manual message management).
#[serde(default)]
pub use_conversations_api: bool,
}
impl Default for AgentsConfig {
fn default() -> Self {
Self {
orchestrator_model: default_model(),
domain_model: default_model(),
compaction_threshold: default_compaction_threshold(),
use_conversations_api: false,
}
}
}
#[derive(Debug, Clone, Deserialize)]
@@ -13,6 +42,10 @@ pub struct MatrixConfig {
pub homeserver_url: String,
pub user_id: String,
pub state_store_path: String,
/// Path to the SQLite database for persistent state (conversations, agents).
/// Should be on a persistent volume in K8s (e.g. Longhorn PVC mounted at /data).
#[serde(default = "default_db_path")]
pub db_path: String,
}
#[derive(Debug, Clone, Deserialize)]
@@ -112,6 +145,8 @@ 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 }
fn default_db_path() -> String { "/data/sol.db".into() }
fn default_compaction_threshold() -> u32 { 118000 } // ~90% of 131K context window
impl Config {
pub fn load(path: &str) -> anyhow::Result<Self> {