Tools module:
- Added search_code to tool_definitions() (was in execute but not defined)
- 8 new unit tests: tool definitions (base/gitea/kratos/all), agent tools,
minimal registry, unknown tool dispatch, search_code without OpenSearch
Gitea indexer fix:
- Direct API calls for directory listings (SDK parses as single object)
- PAT-based auth for file fetching
- GiteaClient.base_url made public for direct API access
Integration tests:
- Gitea SDK: list_repos, get_repo, get_file, directory, full code indexing
- gRPC bridge: thinking events, tool call mapping, request ID filtering
- Evaluator: rule matching, conversation registry lifecycle
- Web search: SearXNG round-trip
When client sends IndexSymbols after session start, Sol indexes
the symbols to the sol_code OpenSearch index. Each symbol becomes
a SymbolDocument with file_path, name, kind, signature, docstring,
branch, and source="local".
Code index (sol_code):
- SymbolDocument: file_path, repo_name, language, symbol_name, symbol_kind,
signature, docstring, branch, source, embedding (768-dim knn_vector)
- CodeIndexer: batch symbol indexer with idempotent upserts
- Branch-aware: symbols scoped to branch with mainline fallback
Breadcrumbs:
- build_breadcrumbs(): adaptive context injection for coding prompts
- Default: project outline via aggregation (modules, types, fns)
- Adaptive: hybrid search (_analyze → symbol matching → BM25 + neural)
- Token budget enforcement with priority (outline first, then relevance)
- format_symbol(): signature + first-line docstring + file:line
Query optimization: uses _analyze API to extract key terms from
free-form user text, matches against actual symbol names in the index
before running the hybrid search.
session_chat_via_orchestrator now:
- Spawns generation on a background task
- Reads in_stream for client tool results in foreground
- Forwards results to orchestrator.submit_tool_result()
- Uses tokio::select! to handle both concurrently
- Uses GenerateRequest + Metadata (no transport types in orchestrator)
- Calls grpc::bridge (not orchestrator::grpc_bridge)
ToolRegistry gains execute_with_context(&ToolContext) which bridges
to the existing execute(&ResponseContext) via a shim. The orchestrator
calls only the new method — no ResponseContext in its dependency tree.
grpc_bridge.rs → grpc/bridge.rs. The bridge maps OrchestratorEvents
to protobuf ServerMessages — it imports from orchestrator (downstream)
and grpc protos (sibling). The orchestrator never imports from grpc.
- Single run_tool_loop() replaces chat/code-specific variants
- generate() for ConversationRegistry path
- generate_from_response() for caller-managed conversations
- Engine uses ToolContext via execute_with_context(), no ResponseContext
- No imports from grpc, sync, matrix, context, or agent_ux modules
Replace transport-coupled types with clean abstractions:
- GenerateRequest: single entry point, no room_id/session_id
- Metadata: opaque key-value bag for transport routing data
- ToolContext: replaces ResponseContext in tool execution
- TokenUsage: clean token count struct
- Simplified OrchestratorEvent: remove AgentProgress*, MemoryExtraction*
Removed: ResponseMode, ChatRequest, CodeRequest.
Orchestrator engine:
- engine.rs: unified Mistral Conversations API tool loop that emits
OrchestratorEvent instead of calling Matrix/gRPC directly
- tool_dispatch.rs: ToolSide routing (client vs server tools)
- Memory loading stubbed (migrates in Phase 4)
Server-side tokenizer:
- tokenizer.rs: HuggingFace tokenizers-rs with Mistral's BPE tokenizer
- count_tokens() for accurate usage metrics
- Loads from local tokenizer.json or falls back to bundled vocab
- Config: mistral.tokenizer_path (optional)
No behavior change — engine is wired but not yet called from
sync.rs or session.rs (Phase 2 continuation).
Introduces the orchestrator module with:
- OrchestratorEvent enum: 11 event variants covering lifecycle, tools,
progress, and side effects
- RequestId (UUID per generation), ResponseMode (Chat/Code), ToolSide
- ChatRequest/CodeRequest structs for transport-agnostic request input
- Orchestrator struct with tokio::broadcast channel (capacity 256)
- subscribe() for transport bridges, emit() for the engine
- Client-side tool dispatch: pending_client_tools map with oneshot channels
- submit_tool_result() to unblock engine from gRPC client responses
Additive only — no behavior change. Existing responder + gRPC session
paths are untouched. Phase 2 will migrate the Conversations API path.
- gRPC dev_mode config: disables JWT auth, uses fixed dev identity
- Agent prefix (agents.agent_prefix): dev agents use "dev-sol-orchestrator"
to avoid colliding with production on shared Mistral accounts
- Coding sessions use instructions (system prompt + coding addendum)
with mistral-medium-latest for personality adherence
- Conversations API: don't send both model + agent_id (422 fix)
- GrpcState carries system_prompt + orchestrator_agent_id
- Session.end() keeps session active for reuse (not "ended")
- User messages posted as m.notice, assistant as m.text (role detection)
- History loaded from Matrix room on session resume
- Docker Compose local dev stack: OpenSearch 3 + Tuwunel + SearXNG
- Dev config: localhost URLs, dev_mode, opensearch-init.sh for ML setup
spawns gRPC server alongside Matrix sync loop when [grpc] config
is present. shares ToolRegistry, Store, MistralClient, and Matrix
client with the gRPC CodeSession handler.
when append_conversation fails (422, 404, etc.), the stale mapping
is deleted and a fresh conversation is created automatically.
prevents Sol from being permanently stuck after a hung research
session or Mistral API error.
new search_web tool calls SearXNG (cluster-internal, free, no tracking)
instead of Mistral's built-in web_search ($0.03/query + rate limits).
returns structured results from DuckDuckGo, Wikipedia, StackOverflow,
GitHub, arXiv, and Brave. no API keys, no cost, no rate limits.
removed Mistral AgentTool::web_search() from orchestrator — replaced
by the custom tool which goes through Sol's normal tool dispatch.
research agents now have a 2-minute timeout via tokio::time::timeout.
a hung Mistral API call can no longer block Sol's entire sync loop.
timed-out agents return partial results instead of hanging forever.
on startup, Sol detects research sessions with status='running' from
previous crashes and marks them as failed. 6 new tests covering the
full research session lifecycle: create, append findings, complete,
fail, hung cleanup, and partial findings survival.
main.rs: create KratosClient, pass mistral+store to ToolRegistry,
build active_agents list for dynamic delegation.
conversations.rs: context_hint for new conversations, reset_all.
sdk/mod.rs: added kratos module.
new research tool spawns 3-25 micro-agents (ministral-3b) in
parallel via futures::join_all. each agent gets its own Mistral
conversation with full tool access.
recursive spawning up to depth 4 — agents can spawn sub-agents.
research sessions persisted in SQLite (survive reboots).
thread UX: 🔍 reaction, per-agent progress posts, ✅ when done.
cost: ~$0.03 per research task (20 micro-agents on ministral-3b).
search_archive, get_room_context, and sol.search() (in run_script)
enforce a configurable member overlap threshold. results from a
room are only visible if >=25% of that room's members are also in
the requesting room.
system-level filter applied at the opensearch query layer — sol
never sees results from excluded rooms.
new engagement types: Respond (inline), ThreadReply (threaded),
React, Ignore. LLM returns response_type to decide HOW to engage.
silence mechanic: "shut up"/"be quiet" sets a 30min per-room timer.
only direct @mention breaks through.
structural suppression (A+B):
- reply to non-Sol human → capped at React
- 3+ human messages since Sol → forced passive mode
threads have a lower relevance threshold (70% of spontaneous).
time context injected into evaluator prompt.
midnight-based day boundaries (today, yesterday, 2 days ago),
week/month boundaries, rolling offsets (1h to 30d). injected
into system prompt via {time_block} and per-message via compact
time line. models no longer need to compute epoch timestamps.
list_users, get_user, create_user, recover_user, disable_user,
enable_user, list_sessions. all via kratos admin API (cluster-
internal, no auth needed). email-to-UUID resolution with fallback
search. delete_user and set_password excluded (CLI-only).
repos: create, edit, fork, list org repos
issues: edit, list comments, create comment
PRs: get, create, merge
branches: list, create, delete
orgs: list user orgs, get org
notifications: list
added write:repository and read:notification PAT scopes.
new response types: Comment, Branch, Organization, Notification.
authed_patch and authed_delete helpers with 401 retry.
URL-encoded query params throughout.
relicensed from MIT to AGPL-3.0-or-later. commercial license
available for organizations that need private modifications
(does not permit redistribution).
sol 1.0.0 ships with:
- multi-agent architecture with mistral conversations API
- user impersonation via vault-backed PAT provisioning
- gitea integration (first domain agent: devtools)
- per-user memory system with automatic extraction
- full-context evaluator with system prompt awareness
- agent recreation on prompt changes with conversation reset
- web search, sandboxed deno runtime, archive search
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.
orchestrator instructions hash (FNV-1a, stable across rust versions)
is stored alongside agent ID. on startup, hash mismatch triggers
delete of old agent + creation of new one + conversation reset + sneeze.
delegation section is now dynamic — only lists domain agents that
are actually registered, preventing the model from hallucinating
capabilities for agents that don't exist yet.
web_search added as a built-in tool on the orchestrator.
7 gitea tools: list_repos, get_repo, list_issues, get_issue,
create_issue, list_pulls, get_file. all operate as the requesting
user via PAT impersonation.
tool registry conditionally includes gitea tools when configured.
dispatch uses prefix matching (gitea_*) for clean extension.
fixed search bug: room_name and sender_name filters used .keyword
subfield which doesn't exist on keyword-typed fields — queries
silently returned zero results for all room/sender-filtered searches.
vault.rs — OpenBao client with kubernetes auth, KV v2 operations,
automatic token refresh on 403. proper error handling on all paths.
tokens.rs — vault-backed token storage with expiry validation.
get_valid returns Result<Option> to distinguish vault errors from
missing tokens. username mappings stay in sqlite (not secrets).
gitea.rs — typed gitea API v1 wrapper with per-user PAT
auto-provisioning via admin API. username discovery by direct match
or email search. URL-encoded query params. handles 400 and 422 token
name conflicts with delete+retry.
new SQLite table service_users maps OIDC identities (matrix localpart)
to service-specific usernames, handling auth boundary mismatches.
localpart() extracts the username from a matrix user ID.
delete_all_conversations() added for bulk reset after agent recreation.
all delete_* methods now log failures instead of silently discarding.
removed dead user_tokens table (tokens now live in vault).
the evaluator now receives sol's entire system prompt as a system
message, giving ministral-3b deep context on sol's personality when
scoring relevance. evaluation context window bumped from 25 to 200
messages, room/dm context windows unified at 200.
pre-computed timestamp variables ({ts_yesterday}, {ts_1h_ago},
{ts_last_week}) added to personality template for accurate time
references without LLM math.
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.
Three memory channels: hidden tool (sol.memory.set/get in scripts),
pre-response injection (relevant memories loaded into system prompt),
and post-response extraction (ministral-3b extracts facts after each
response). User isolation enforced at Rust level — user_id derived
from Matrix sender, never from script arguments.
New modules: context (ResponseContext), memory (schema, store, extractor).
ResponseContext threaded through responder → tools → script runtime.
OpenSearch index sol_user_memory created on startup alongside archive.
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.