docs: comprehensive README rewrite — orchestrator, gRPC, code indexing, new tools

Cover the transport-agnostic orchestrator, gRPC CodeAgent service,
tree-sitter code indexing, client/server tool dispatch, research tool,
SearXNG web search, Kratos identity tools, breadcrumbs, and all new
config sections.
This commit is contained in:
2026-03-24 12:58:18 +00:00
parent 4528739a5f
commit 2949ea354f

345
README.md
View File

@@ -1,6 +1,6 @@
# Sol # 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, per-user memory, and a multi-agent architecture. 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, per-user memory, a multi-agent architecture, and a gRPC coding agent service.
Sol is built by [Sunbeam Studios](https://sunbeam.pt) as part of our self-hosted collaboration stack. Sol is built by [Sunbeam Studios](https://sunbeam.pt) as part of our self-hosted collaboration stack.
@@ -8,12 +8,17 @@ Sol is built by [Sunbeam Studios](https://sunbeam.pt) as part of our self-hosted
- **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. - **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. - **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, room info, and a sandboxed TypeScript/JavaScript runtime (deno_core) for computation. - **Tool Use** — Mistral calls tools mid-conversation: archive search, code search, room context retrieval, web search, a sandboxed TypeScript/JavaScript runtime (deno_core), and parallel research agents.
- **Per-User Memory** — Sol remembers things about the people it talks to. Memories are extracted automatically after conversations, injected into the system prompt before responding, and accessible from scripts via `sol.memory.get/set`. - **Per-User Memory** — Sol remembers things about the people it talks to. Memories are extracted automatically after conversations, injected into the system prompt before responding, and accessible from scripts via `sol.memory.get/set`.
- **User Impersonation** — Sol acts on behalf of users when calling external services. PATs are auto-provisioned via admin APIs and stored securely in OpenBao (Vault). OIDC-to-service username mappings handle identity mismatches. - **User Impersonation** — Sol acts on behalf of users when calling external services. PATs are auto-provisioned via admin APIs and stored securely in OpenBao (Vault). OIDC-to-service username mappings handle identity mismatches.
- **Gitea Integration** — First domain agent (sol-devtools): list repos, search issues, create issues, list PRs, get file contents — all as the requesting user. - **Gitea Integration** — Devtools agent: list repos, search issues, create issues, list PRs, get file contents, search code, create pull requests — all as the requesting user.
- **Identity Management** — Kratos integration for user account operations: create, get, list, update, delete users via the admin API.
- **Multi-Agent Architecture** — An orchestrator agent with personality + tools + web search. Domain agent delegation is dynamic — only active agents appear in instructions. Agent state is persisted in SQLite with instructions hash for automatic recreation on prompt changes. - **Multi-Agent Architecture** — An orchestrator agent with personality + tools + web search. Domain agent delegation is dynamic — only active agents appear in instructions. Agent state is persisted in SQLite with instructions hash for automatic recreation on prompt changes.
- **Conversations API** — Persistent conversation state per room via Mistral's Conversations API, with automatic compaction at token thresholds. Per-message context headers inject timestamps, room info, and memory notes. - **Conversations API** — Persistent conversation state per room via Mistral's Conversations API, with automatic compaction at token thresholds. Per-message context headers inject timestamps, room info, and memory notes.
- **Code Indexing** — Tree-sitter symbol extraction from Gitea repos (Rust, Python, TypeScript, Go, Java). Indexed to OpenSearch for hybrid keyword + semantic code search.
- **gRPC Coding Agent** — Bidirectional streaming service for `sunbeam code` TUI sessions. Transport-agnostic orchestrator emits events; gRPC bridge translates to protobuf. JWT auth in production, dev mode for local use.
- **Research Tool** — Spawns parallel micro-agents with depth control for multi-step research tasks.
- **Web Search** — Self-hosted search via SearXNG. No commercial API keys needed.
- **Multimodal** — `m.image` messages are downloaded from Matrix via `mxc://`, converted to base64 data URIs, and sent as `ContentPart::ImageUrl` to Mistral vision models. - **Multimodal** — `m.image` messages are downloaded from Matrix via `mxc://`, converted to base64 data URIs, and sent as `ContentPart::ImageUrl` to Mistral vision models.
- **Reactions** — Sol can react to messages with emoji when it has something to express but not enough to say. - **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. - **E2EE** — Full end-to-end encryption via matrix-sdk with SQLite state store.
@@ -22,84 +27,66 @@ Sol is built by [Sunbeam Studios](https://sunbeam.pt) as part of our self-hosted
```mermaid ```mermaid
flowchart TD flowchart TD
subgraph Matrix subgraph Clients
sync[Matrix Sync Loop] matrix[Matrix Sync Loop]
grpc[gRPC CodeAgent<br/>sunbeam code TUI]
end end
subgraph Engagement subgraph Engagement["Engagement (Matrix only)"]
eval[Evaluator] eval[Evaluator]
rules[Rule Checks] rules[Rule Checks]
llm_eval[LLM Evaluation<br/>ministral-3b] llm_eval[LLM Evaluation<br/>ministral-3b]
end end
subgraph Response subgraph Core
legacy[Legacy Path<br/>Manual messages + chat completions] orchestrator[Orchestrator<br/>Event-driven, transport-agnostic]
convapi[Conversations API Path<br/>ConversationRegistry + agents] tools[Tool Registry]
tools[Tool Execution] agents[Agent Registry<br/>Orchestrator + domain agents]
end
subgraph Tools
server_tools[Server-Side Tools<br/>search_archive, search_code,<br/>list_rooms, get_room_members,<br/>get_room_context, run_script,<br/>search_web, research,<br/>gitea_*, identity_*]
client_tools[Client-Side Tools<br/>file_read, file_write,<br/>search_replace, grep, bash,<br/>list_directory, lsp_*]
end end
subgraph Persistence subgraph Persistence
sqlite[(SQLite<br/>conversations + agents)] sqlite[(SQLite<br/>conversations, agents,<br/>service_users)]
opensearch[(OpenSearch<br/>archive + memory)] opensearch[(OpenSearch<br/>archive, memory, code)]
end end
sync --> |message event| eval subgraph External
mistral[Mistral AI<br/>Agents + Conversations API]
gitea[Gitea<br/>repos, issues, PRs, code]
searxng[SearXNG<br/>web search]
vault[OpenBao<br/>user tokens]
kratos[Kratos<br/>identity management]
end
matrix --> eval
eval --> rules eval --> rules
rules --> |MustRespond| Response rules --> |MustRespond| orchestrator
rules --> |no rule match| llm_eval rules --> |no rule match| llm_eval
llm_eval --> |MaybeRespond| Response llm_eval --> |MaybeRespond| orchestrator
llm_eval --> |React| sync llm_eval --> |React/Ignore| matrix
llm_eval --> |Ignore| sync
legacy --> tools
convapi --> tools
tools --> opensearch
legacy --> |response text| sync
convapi --> |response text| sync
sync --> |archive| opensearch
convapi --> sqlite
sync --> |memory extraction| opensearch
```
## Source Tree grpc --> orchestrator
``` orchestrator --> mistral
src/ orchestrator --> tools
├── main.rs Entrypoint, Matrix client setup, backfill, orchestrator init tools --> server_tools
├── sync.rs Event loop — messages, reactions, redactions, invites tools --> |relay to client| client_tools
├── config.rs TOML config with serde defaults
├── context.rs ResponseContext — per-message sender identity threading server_tools --> opensearch
├── conversations.rs ConversationRegistry — room→conversation mapping, SQLite-backed server_tools --> gitea
├── persistence.rs SQLite store (WAL mode, tables: conversations, agents, service_users) server_tools --> searxng
├── agent_ux.rs AgentProgress — reaction lifecycle (🔍→⚙️→✅) + thread posting server_tools --> kratos
├── matrix_utils.rs Message extraction, reply/edit/thread detection, image download
├── archive/ orchestrator --> |response| matrix
├── schema.rs ArchiveDocument, OpenSearch index mapping orchestrator --> |events| grpc
└── indexer.rs Batched indexing, reactions, edits, redactions orchestrator --> sqlite
├── brain/ matrix --> |archive| opensearch
├── conversation.rs Sliding-window context per room (configurable group/DM windows) matrix --> |memory extraction| opensearch
├── evaluator.rs Engagement decision (MustRespond/MaybeRespond/React/Ignore) agents --> mistral
│ ├── personality.rs System prompt templating ({date}, {room_name}, {members}, etc.)
│ └── responder.rs Both response paths, tool iteration loops, memory loading
├── memory/
│ ├── schema.rs MemoryDocument, index mapping
│ ├── store.rs Query (topical), get_recent, set — OpenSearch operations
│ └── extractor.rs Post-response fact extraction via ministral-3b
├── agents/
│ ├── definitions.rs Orchestrator config + domain agent definitions (dynamic delegation)
│ └── registry.rs Agent lifecycle with instructions hash staleness detection
├── sdk/
│ ├── mod.rs SDK module root
│ ├── vault.rs OpenBao/Vault client (K8s auth, KV v2 read/write/delete)
│ ├── tokens.rs TokenStore — Vault-backed secrets + SQLite username mappings
│ └── gitea.rs GiteaClient — typed Gitea API v1 with PAT auto-provisioning
└── tools/
├── mod.rs ToolRegistry — tool definitions + dispatch (core + gitea)
├── search.rs Archive search (keyword + semantic via embedding pipeline)
├── room_history.rs Context around a timestamp or event
├── room_info.rs Room listing, member queries
├── script.rs deno_core sandbox with sol.* host API, TS transpilation
├── devtools.rs Gitea tool handlers (repos, issues, PRs, files)
└── bridge.rs ToolBridge — generic async handler map for future SDK integration
``` ```
## Engagement Pipeline ## Engagement Pipeline
@@ -110,7 +97,7 @@ sequenceDiagram
participant S as Sync Handler participant S as Sync Handler
participant E as Evaluator participant E as Evaluator
participant LLM as ministral-3b participant LLM as ministral-3b
participant R as Responder participant O as Orchestrator
M->>S: m.room.message M->>S: m.room.message
S->>S: Archive message S->>S: Archive message
@@ -140,55 +127,140 @@ sequenceDiagram
alt MustRespond or MaybeRespond alt MustRespond or MaybeRespond
S->>S: Check in-flight guard S->>S: Check in-flight guard
S->>S: Check cooldown (15s default) S->>S: Check cooldown (15s default)
S->>R: Generate response S->>O: Generate response
end end
``` ```
## Response Generation ## Orchestrator
Sol has two response paths, controlled by `agents.use_conversations_api`: The orchestrator is a transport-agnostic, event-driven pipeline that sits between input sources (Matrix sync, gRPC sessions) and Mistral's Agents API. It has no knowledge of Matrix, gRPC, or any specific UI — transport bridges subscribe to its events and translate to their protocol.
### Legacy Path (`generate_response`) ```mermaid
flowchart LR
subgraph Input
matrix_bridge[Matrix Bridge]
grpc_bridge[gRPC Bridge]
end
1. Apply response delay (random within configured range) subgraph Orchestrator
2. Send typing indicator request[GenerateRequest]
3. Load memory notes (topical query + recent backfill, max 5) engine[Engine<br/>tool loop]
4. Build system prompt via `Personality` (template substitution: `{date}`, `{room_name}`, `{members}`, `{memory_notes}`, `{room_context_rules}`, `{epoch_ms}`) events[OrchestratorEvent<br/>broadcast channel]
5. Assemble message array: system → context messages (with timestamps) → trigger (multimodal if image) end
6. Tool iteration loop (up to `max_tool_iterations`, default 5):
- If `finish_reason == ToolCalls`: execute tools, append results, continue
- If text response: strip "sol:" prefix, return
7. Fire-and-forget memory extraction
### Conversations API Path (`generate_response_conversations`) subgraph Output
matrix_out[Matrix Room]
grpc_out[gRPC Stream]
end
1. Apply response delay matrix_bridge --> request
2. Send typing indicator grpc_bridge --> request
3. Load memory notes for the user request --> engine
4. Build per-message context header (timestamps, room name, memory notes) engine --> events
5. Format input: raw text for DMs, `<@user:server> text` for groups events --> matrix_out
6. Send through `ConversationRegistry.send_message()` (creates or appends to Mistral conversation) events --> grpc_out
7. Function call loop (up to `max_tool_iterations`): ```
- Execute tool calls locally via `ToolRegistry`
- Send `FunctionResultEntry` back to conversation Events emitted during response generation:
8. Extract assistant text, strip prefix, return
| Event | Description |
|-------|-------------|
| `Started` | Generation begun, carries routing metadata |
| `Thinking` | Model is generating |
| `ToolCallDetected` | Tool call found in output (server-side or client-side) |
| `ToolStarted` | Tool execution began |
| `ToolCompleted` | Tool finished with result preview |
| `Done` | Final response text + token usage |
| `Failed` | Generation error |
Tool dispatch routes calls to either the server (Sol executes them) or the client (relayed to the gRPC TUI for local execution). The orchestrator parks on a oneshot channel for client-side results, transparent to the tool loop.
## gRPC Coding Agent
Sol exposes a `CodeAgent` gRPC service for `sunbeam code` TUI sessions. The protocol is defined in `proto/code.proto`.
```mermaid
sequenceDiagram
participant CLI as sunbeam code
participant gRPC as gRPC Service
participant O as Orchestrator
participant M as Mistral
CLI->>gRPC: StartSession (project context, capabilities)
gRPC->>gRPC: Create Matrix room, auth
gRPC-->>CLI: SessionReady (session_id, room_id, model)
CLI->>gRPC: IndexSymbols (tree-sitter symbols)
gRPC->>gRPC: Index to OpenSearch
CLI->>gRPC: UserInput (text)
gRPC->>O: GenerateRequest
O->>M: Conversation append
alt Server-side tool
O->>O: Execute locally
gRPC-->>CLI: Status (TOOL_RUNNING → TOOL_DONE)
else Client-side tool
gRPC-->>CLI: ToolCall (is_local=true)
CLI->>CLI: Execute (file_read, bash, etc.)
CLI->>gRPC: ToolResult
gRPC->>O: Resume tool loop
end
M-->>O: Response text
gRPC-->>CLI: TextDone (full_text, token usage)
```
Two RPC methods:
- **`Session`** — Bidirectional stream for interactive coding sessions
- **`ReindexCode`** — On-demand Gitea repo indexing via tree-sitter
Auth: JWT validation via JWKS in production, fixed dev identity when `grpc.dev_mode = true`.
## Tool System ## Tool System
### Server-Side Tools
| Tool | Parameters | Description | | Tool | Parameters | Description |
|------|-----------|-------------| |------|-----------|-------------|
| `search_archive` | `query` (required), `room`, `sender`, `after`, `before`, `limit`, `semantic` | Search the message archive (keyword or semantic) | | `search_archive` | `query` (required), `room`, `sender`, `after`, `before`, `limit`, `semantic` | Search the message archive (keyword or semantic) |
| `search_code` | `query` (required), `language`, `repo`, `branch`, `limit` | Search the code index for functions, types, patterns |
| `get_room_context` | `room_id` (required), `around_timestamp`, `around_event_id`, `before_count`, `after_count` | Get messages around a point in time or event | | `get_room_context` | `room_id` (required), `around_timestamp`, `around_event_id`, `before_count`, `after_count` | Get messages around a point in time or event |
| `list_rooms` | *(none)* | List all rooms Sol is in with names and member counts | | `list_rooms` | *(none)* | List all rooms Sol is in with names and member counts |
| `get_room_members` | `room_id` (required) | Get members of a specific room | | `get_room_members` | `room_id` (required) | Get members of a specific room |
| `run_script` | `code` (required) | Execute TypeScript/JavaScript in a sandboxed deno_core runtime | | `run_script` | `code` (required) | Execute TypeScript/JavaScript in a sandboxed deno_core runtime |
| `search_web` | `query` (required), `limit` | Search the web via SearXNG |
| `research` | `query` (required), `depth` | Spawn parallel micro-agents for multi-step research |
| `gitea_list_repos` | `query`, `org`, `limit` | List or search repositories on Gitea | | `gitea_list_repos` | `query`, `org`, `limit` | List or search repositories on Gitea |
| `gitea_get_repo` | `owner`, `repo` (required) | Get details about a specific repository | | `gitea_get_repo` | `owner`, `repo` (required) | Get details about a specific repository |
| `gitea_list_issues` | `owner`, `repo` (required), `state`, `labels`, `limit` | List issues in a repository | | `gitea_search_code` | `query` (required), `repo`, `limit` | Search code across Gitea repositories |
| `gitea_get_issue` | `owner`, `repo`, `number` (required) | Get full details of a specific issue |
| `gitea_create_issue` | `owner`, `repo`, `title` (required), `body`, `labels` | Create a new issue as the requesting user | | `gitea_create_issue` | `owner`, `repo`, `title` (required), `body`, `labels` | Create a new issue as the requesting user |
| `gitea_list_pulls` | `owner`, `repo` (required), `state`, `limit` | List pull requests in a repository | | `gitea_create_pr` | `owner`, `repo`, `title`, `head`, `base` (required), `body` | Create a pull request as the requesting user |
| `gitea_get_file` | `owner`, `repo`, `path` (required), `ref` | Get file contents from a repository | | `gitea_get_file` | `owner`, `repo`, `path` (required), `ref` | Get file contents from a repository |
| `identity_create_user` | `email`, `name` (required) | Create a new user account via Kratos |
| `identity_get_user` | `email` (required) | Get user account details |
| `identity_list_users` | `limit` | List all user accounts |
| `identity_update_user` | `email` (required), `name`, `active` | Update a user account |
| `identity_delete_user` | `email` (required) | Delete a user account |
Tools are conditionally registered — Gitea tools only appear when `services.gitea` is configured, Kratos tools when `services.kratos` is configured, LSP tools when the client advertises capabilities.
### Client-Side Tools (gRPC sessions)
| Tool | Permission | Description |
|------|-----------|-------------|
| `file_read` | always | Read file contents with optional line ranges |
| `file_write` | ask | Write or create files |
| `search_replace` | ask | Apply SEARCH/REPLACE diffs to files |
| `grep` | always | Search files with ripgrep |
| `bash` | ask | Execute shell commands |
| `list_directory` | always | List directory tree |
| `lsp_definition` | always | Jump to definition |
| `lsp_references` | always | Find all references |
| `lsp_hover` | always | Type info + docs |
| `lsp_diagnostics` | always | Compiler errors |
| `lsp_symbols` | always | Document/workspace symbols |
### `run_script` Sandbox ### `run_script` Sandbox
@@ -217,6 +289,39 @@ sol.fs.list(path?) // List sandbox directory
All `sol.*` methods are async — use `await`. All `sol.*` methods are async — use `await`.
## Code Indexing
Sol indexes source code from Gitea repositories using tree-sitter for symbol extraction. Symbols are stored in OpenSearch and searchable via the `search_code` tool and adaptive breadcrumbs.
```mermaid
flowchart LR
subgraph Sources
gitea_repos[Gitea Repos<br/>via API]
sidecar[sunbeam code<br/>IndexSymbols]
end
subgraph Extraction
treesitter[Tree-Sitter<br/>Rust, Python, TypeScript,<br/>Go, Java]
end
subgraph Storage
opensearch_code[(OpenSearch<br/>sol_code index)]
end
subgraph Consumers
search_code_tool[search_code tool]
breadcrumbs[Adaptive Breadcrumbs<br/>project outline + hybrid search]
end
gitea_repos --> treesitter
sidecar --> opensearch_code
treesitter --> opensearch_code
opensearch_code --> search_code_tool
opensearch_code --> breadcrumbs
```
Each symbol is a `SymbolDocument` with file path, repo name, language, symbol name/kind, signature, docstring, line numbers, content, branch, and source. The `ReindexCode` gRPC endpoint triggers on-demand indexing for specific orgs, repos, or branches.
## Memory System ## Memory System
### Extraction (Post-Response, Fire-and-Forget) ### Extraction (Post-Response, Fire-and-Forget)
@@ -246,6 +351,7 @@ Every message event is archived as an `ArchiveDocument` in OpenSearch:
- **Redaction** — `m.room.redaction` sets `redacted: true` on the original - **Redaction** — `m.room.redaction` sets `redacted: true` on the original
- **Reactions** — `m.reaction` events append `{sender, emoji, timestamp}` to the document's reactions array - **Reactions** — `m.reaction` events append `{sender, emoji, timestamp}` to the document's reactions array
- **Backfill** — On startup, conversation context is backfilled from the archive; reactions are backfilled from Matrix room timelines (last 500 events per room) - **Backfill** — On startup, conversation context is backfilled from the archive; reactions are backfilled from Matrix room timelines (last 500 events per room)
- **Cross-room visibility** — Search results from other rooms are filtered by member overlap (`behavior.room_overlap_threshold`, default 0.25)
## Agent Architecture ## Agent Architecture
@@ -269,13 +375,15 @@ stateDiagram-v2
### Orchestrator ### Orchestrator
The orchestrator agent carries Sol's full personality (system prompt) plus all tool definitions converted to `AgentTool` format, including Mistral's built-in `web_search`. It's created on startup if `agents.use_conversations_api` is enabled. The orchestrator agent carries Sol's full personality (system prompt) plus all tool definitions converted to `AgentTool` format, including web search. It's created on startup if `agents.use_conversations_api` is enabled.
When the system prompt changes, the instructions hash detects staleness and the agent is automatically recreated. All existing conversations are reset and Sol sneezes into all rooms to signal the context reset. When the system prompt changes, the instructions hash detects staleness and the agent is automatically recreated. All existing conversations are reset and Sol sneezes into all rooms to signal the context reset.
Agent names can be prefixed via `agents.agent_prefix` — set to `"dev"` in local development to avoid colliding with production agents on the same Mistral account.
### Domain Agents ### Domain Agents
Domain agents are defined in `agents/definitions.rs` as `DOMAIN_AGENTS` (name, description, instructions). The delegation section in the orchestrator's instructions is built dynamically — only agents that are actually registered appear. Domain agents are defined in `agents/definitions.rs`. The delegation section in the orchestrator's instructions is built dynamically — only agents that are actually registered appear.
### User Impersonation ### User Impersonation
@@ -335,6 +443,7 @@ Config is loaded from `SOL_CONFIG` (default: `/etc/sol/sol.toml`).
| `evaluation_model` | string | `ministral-3b-latest` | Model for engagement evaluation + memory extraction | | `evaluation_model` | string | `ministral-3b-latest` | Model for engagement evaluation + memory extraction |
| `research_model` | string | `mistral-large-latest` | Model for research tasks | | `research_model` | string | `mistral-large-latest` | Model for research tasks |
| `max_tool_iterations` | usize | `5` | Max tool call rounds per response | | `max_tool_iterations` | usize | `5` | Max tool call rounds per response |
| `tokenizer_path` | string? | *(none)* | Path to local `tokenizer.json` (downloads from HuggingFace Hub if unset) |
### `[behavior]` ### `[behavior]`
@@ -361,6 +470,8 @@ Config is loaded from `SOL_CONFIG` (default: `/etc/sol/sol.toml`).
| `script_max_heap_mb` | usize | `64` | V8 heap limit for scripts | | `script_max_heap_mb` | usize | `64` | V8 heap limit for scripts |
| `script_fetch_allowlist` | string[] | `[]` | Domains allowed for `sol.fetch()` | | `script_fetch_allowlist` | string[] | `[]` | Domains allowed for `sol.fetch()` |
| `memory_extraction_enabled` | bool | `true` | Enable post-response memory extraction | | `memory_extraction_enabled` | bool | `true` | Enable post-response memory extraction |
| `room_overlap_threshold` | f32 | `0.25` | Min member overlap for cross-room search visibility |
| `silence_duration_ms` | u64 | `1800000` | Duration Sol stays quiet when told to (30 minutes) |
### `[agents]` ### `[agents]`
@@ -370,6 +481,20 @@ Config is loaded from `SOL_CONFIG` (default: `/etc/sol/sol.toml`).
| `domain_model` | string | `mistral-medium-latest` | Model for domain agents | | `domain_model` | string | `mistral-medium-latest` | Model for domain agents |
| `compaction_threshold` | u32 | `118000` | Token estimate before conversation reset (~90% of 131K context) | | `compaction_threshold` | u32 | `118000` | Token estimate before conversation reset (~90% of 131K context) |
| `use_conversations_api` | bool | `false` | Enable Conversations API path (vs legacy chat completions) | | `use_conversations_api` | bool | `false` | Enable Conversations API path (vs legacy chat completions) |
| `coding_model` | string | `mistral-medium-latest` | Model for `sunbeam code` sessions |
| `research_model` | string | `ministral-3b-latest` | Model for research micro-agents |
| `research_max_iterations` | usize | `10` | Max tool calls per research micro-agent |
| `research_max_agents` | usize | `25` | Max parallel agents per research wave |
| `research_max_depth` | usize | `4` | Max recursion depth for research agents |
| `agent_prefix` | string | `""` | Name prefix for agents (e.g. `"dev"` to avoid production collisions) |
### `[grpc]`
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `listen_addr` | string | `0.0.0.0:50051` | gRPC server listen address |
| `jwks_url` | string? | *(none)* | JWKS URL for JWT validation (required unless `dev_mode`) |
| `dev_mode` | bool | `false` | Disable JWT auth, use fixed dev identity |
### `[vault]` ### `[vault]`
@@ -385,6 +510,18 @@ Config is loaded from `SOL_CONFIG` (default: `/etc/sol/sol.toml`).
|-------|------|---------|-------------| |-------|------|---------|-------------|
| `url` | string | *required if enabled* | Gitea API base URL | | `url` | string | *required if enabled* | Gitea API base URL |
### `[services.kratos]`
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `admin_url` | string | *required if enabled* | Kratos admin API URL |
### `[services.searxng]`
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `url` | string | *required if enabled* | SearXNG API URL |
## Environment Variables ## Environment Variables
| Variable | Required | Description | | Variable | Required | Description |
@@ -399,15 +536,17 @@ Config is loaded from `SOL_CONFIG` (default: `/etc/sol/sol.toml`).
## Dependencies ## Dependencies
Sol talks to five external services: Sol talks to seven external services:
- **Matrix homeserver** — [Tuwunel](https://github.com/tulir/tuwunel) (or any Matrix server) - **Matrix homeserver** — [Tuwunel](https://github.com/tulir/tuwunel) (or any Matrix server)
- **OpenSearch** — Message archive + user memory indices - **OpenSearch** — Message archive, user memory, and code symbol indices
- **Mistral AI** — Response generation, engagement evaluation, memory extraction, agents + web search - **Mistral AI** — Response generation, engagement evaluation, memory extraction, agents + conversations
- **OpenBao** — Secure token storage for user impersonation PATs (K8s auth, KV v2) - **OpenBao** — Secure token storage for user impersonation PATs (K8s auth, KV v2)
- **Gitea** — Git hosting API for devtools agent (repos, issues, PRs) - **Gitea** — Git hosting API for devtools agent (repos, issues, PRs, code search, indexing)
- **Kratos** — Identity management for user account operations
- **SearXNG** — Self-hosted web search (no API keys required)
Key crates: `matrix-sdk` 0.9 (E2EE + SQLite), `mistralai-client` 1.1.0 (private registry), `opensearch` 2, `deno_core` 0.393, `rusqlite` 0.32 (bundled), `ruma` 0.12. Key crates: `matrix-sdk` 0.9 (E2EE + SQLite), `mistralai-client` 1.1.0 (private registry), `opensearch` 2, `deno_core` 0.393, `tonic` 0.14 (gRPC), `tree-sitter` 0.24, `rusqlite` 0.32 (bundled), `ruma` 0.12, `tokenizers` 0.22.
## Building ## Building
@@ -435,6 +574,12 @@ The Dockerfile uses a two-stage build: deps layer (cached until Cargo.toml/vendo
cargo test cargo test
``` ```
Integration tests against the Mistral API require a `.env` file with `SOL_MISTRAL_API_KEY`:
```sh
cargo test --test integration_test
```
## License ## License
Sol is dual-licensed: Sol is dual-licensed: