conversations API path now injects per-message context headers with live timestamps, room name, and memory notes. this replaces the template variables in agent instructions which were frozen at creation time. memory notes (topical + recent backfill) loaded before each response in the conversations path — was previously only in the legacy path. context hint seeds new conversations with recent room history after resets, so sol doesn't lose conversational continuity on sneeze. tool call results now logged with preview + length for debugging. reset_all() clears both in-memory and sqlite conversation state.
sol
a virtual librarian for Matrix. sol lives in your chat rooms, archives conversations in OpenSearch, and responds with the help of Mistral AI — with end-to-end encryption, tool use, and per-user memory.
sol is built by sunbeam studios as part of our self-hosted collaboration stack.
what sol does
- Matrix presence — joins rooms, reads the vibe, decides when to speak. direct messages always get a response; in group rooms, sol evaluates relevance before jumping in.
- message archive — every message is indexed in OpenSearch with full-text and semantic search. sol can search its own archive via tools.
- tool use — mistral calls tools mid-conversation: archive search, room context retrieval, and a sandboxed TypeScript/JavaScript runtime (deno_core) for computation.
- per-user memory — sol remembers things about the people it talks to. memories are extracted automatically after conversations (via ministral-3b), injected into the system prompt before responding, and accessible from scripts via
sol.memory.get/set. user isolation is enforced at the rust level. - reactions — sol can react to messages with emoji when it has something to express but not enough to say.
- E2EE — full end-to-end encryption via matrix-sdk with sqlite state store.
architecture
src/
├── main.rs entrypoint, Matrix client setup, backfill
├── sync.rs event loop — messages, reactions, redactions, invites
├── config.rs TOML config with serde defaults
├── context.rs ResponseContext — per-message sender identity
├── matrix_utils.rs message extraction, reply detection, room info
├── archive/
│ ├── schema.rs ArchiveDocument, OpenSearch index mapping
│ └── indexer.rs batched indexing, reactions, edits, redactions
├── brain/
│ ├── conversation.rs sliding-window context per room
│ ├── evaluator.rs engagement decision (must/maybe/react/ignore)
│ ├── personality.rs system prompt templating
│ └── responder.rs Mistral chat loop with tool iterations + memory
├── memory/
│ ├── schema.rs MemoryDocument, index mapping
│ ├── store.rs query, get_recent, set — OpenSearch operations
│ └── extractor.rs post-response fact extraction via ministral-3b
└── tools/
├── mod.rs ToolRegistry, tool definitions, dispatch
├── search.rs archive search (keyword + semantic)
├── room_history.rs context around a timestamp or event
├── room_info.rs room listing, member queries
└── script.rs deno_core sandbox with sol.* API
dependencies
sol talks to three external services:
- Matrix homeserver — tuwunel (or any Matrix server)
- OpenSearch — message archive + user memory indices
- Mistral AI — response generation, engagement evaluation, memory extraction
configuration
sol reads config from SOL_CONFIG (default: /etc/sol/sol.toml) and the system prompt from SOL_SYSTEM_PROMPT (default: /etc/sol/system_prompt.md).
secrets via environment:
| Variable | Description |
|---|---|
SOL_MATRIX_ACCESS_TOKEN |
Matrix access token |
SOL_MATRIX_DEVICE_ID |
Matrix device ID (for E2EE) |
SOL_MISTRAL_API_KEY |
Mistral API key |
see config/sol.toml for the full config reference with defaults.
building
cargo build --release
docker (cross-compile to x86_64 linux):
docker build -t sol .
running
export SOL_MATRIX_ACCESS_TOKEN="..."
export SOL_MATRIX_DEVICE_ID="..."
export SOL_MISTRAL_API_KEY="..."
export SOL_CONFIG="config/sol.toml"
export SOL_SYSTEM_PROMPT="config/system_prompt.md"
cargo run --release
tests
cargo test
80 unit tests covering config parsing, conversation windowing, engagement rules, personality templating, memory schema/store/extraction, search query building, TypeScript transpilation, and sandbox path isolation.