SULTAANresearch notebook · .io

Vol. 1Phase 3: pretraining infrastructureRepository private

Architecture: a Python and Rust kernel with one seam per concern

Sultaan is a single monorepo. Rust handles the hot paths (the model backend contract, Candle inference and tokenizers); Python handles orchestration (the command line, the agent, retrieval, memory, training and evaluation). Everything ships as one Python wheel.

The stack, top to bottom

Interface

sultaan CLIgenerate · agent · pretrain · train · eval · rag · kg

Python · reasoning

agentloop, circuit breaker, tools
ragBM25 + dense, rank fusion
kgSQLite triples, trust tiers
memorymem0 wrapper, egress guard
verifygrounding, spans, figures

Python · learning

pretraindecoder, trainer, data, gate
trainQLoRA fine-tuning
evalperplexity, MMLU, TruthfulQA, probes

The seam

Backend protocolthe one place where a model is chosen

Backends

LlamaCppBackendGGUF files via llama-cpp-python
CandleBackendsafetensors via the native module

Rust · hot paths

sultaan-pythe single PyO3 wheel
sultaan-coreBackend trait, versioned FFI contract
sultaan-inferenceCandle Qwen2 generation, CPU today
sultaan-tokenizerHugging Face tokenizers
Figure 1.1. Module map of the repository’s main branch. Tooling around it: uv, maturin, cargo xtask, DVC for model and data versions, mlflow for fine-tuning runs, pytest and ruff.

why one wheelSeveral native wheels side by side invite DLL collisions on Windows. One PyO3 wheel re-exports everything and avoids the problem.

Two backends behind one protocol

Every model call goes through a single Backend protocol, mirrored by a Rust trait in sultaan-core that acts as the versioned FFI contract. Two backends exist from day one: llama.cpp for quantised GGUF files and Candle for safetensors. The hello-world base model is Qwen2.5-0.5B-Instruct, and both backends generate text from it.

The Candle backend implements Qwen2 generation on CPU. CUDA and Metal feature flags exist in the crate but are not the default. A third backend would need its own decision record.

uv run sultaan generate --backend llamacpp "Hello, who are you?"
uv run sultaan generate --backend candle   "Hello, who are you?"
From the repository README. Shown for reference; the repository is private.

An agent loop written in-house

The agent is a small loop owned by the project: a system message, the user turn, tool calls and tool results, with tools registered as plain data classes. It deliberately uses no agent framework. Tools today are a Python REPL, retrieval over a document folder and a knowledge-graph lookup. A Docker sandbox with CPU, memory and time quotas is in the codebase for running untrusted code, but no command uses it by default yet; making it the default is planned session S2. A session-scoped circuit breaker stops runaway loops.

uv run sultaan agent --rag ./docs --verify "What does ADR 0017 decide?"
uv run sultaan kg add facts.db Paris capital_of France
uv run sultaan agent --kg facts.db "What is Paris the capital of?"
Retrieval with grounding checks, then a knowledge-graph round trip (README examples).

Retrieval, graph and memory

  • Retrieval: hybrid search that combines BM25 keyword scores with dense embeddings, merges them with reciprocal rank fusion and packs context fairly across sources.
  • Knowledge graph: a SQLite triple store in which every fact carries a trust tier.
  • Memory: a wrapper around mem0 that runs on local storage and refuses known cloud hosts, plus typed facts and a parser for a SOUL.md identity file that shapes the system message.

open itemThe cloud-host guard is a deny-list today. Tightening it to an allow-list of local and private addresses is on the next-sessions list.

Verification without a judge model

Answers can be checked by a model-free grounding verifier, which binds each claim to its best-supporting source, and by span and figure checks written with the standard library only. The evaluation page covers how the verifier is tested.

What is deliberately not here

  • No server. The server package is a placeholder. If a model is ever served, the portfolio plan puts it behind the shared AI gateway as an upstream, with no API, sign-in or metering of its own.
  • No home-grown deep-learning framework, tokenizer library, vector database or database. PyTorch, Candle, Hugging Face tokenizers, Qdrant and SQLite cover those.
  • No hand-written CUDA or Metal kernels. Attention goes through PyTorch’s built-in fused kernels instead.
  • No hosted model provider anywhere in the code path.

The decision log: 26 records on main

Every decision that outlives one change gets an architecture decision record (context, decision, consequences, alternatives). Accepted records are never rewritten; changes arrive as addenda or new records.

Architecture decision records 0001 to 0026, titles paraphrased from the records.
No.DecisionArea
0001Hybrid Python and Rustfoundation
0002Candle and llama.cpp from day oneinference
0003uv and maturin toolchainfoundation
0004DVC for model and data versioningfoundation
0005aim for experiment tracking (later superseded)tracking
0006Candle Qwen2 implementationinference
0007Agent loop architectureagent
0008Move to Phase 1phase
0009Defer aim, prefer mlflowtracking
0010Perplexity baselineevaluation
0011SOUL.md identity-file parsermemory
0012Training stacktraining
0013Evaluation suite expansionevaluation
0014Phase 2 partial override for mem0memory
0015Phase 1 ratified, move to Phase 2; Phase 3 confirmed as north starphase
0016Phase 3 pretraining stacktraining
0017Hybrid retrieval for RAGretrieval
0018Docker sandboxagent
0019Knowledge graph on SQLiteretrieval
0020Grounding verifierverification
0021Training-run hardening, gate and provenancetraining
0022Data mixture bandittraining
0023Span and figure verifier suiteverification
0024Knowledge-graph trust tiersretrieval
0025Typed memory facts and coveragememory
0026First-contact runtraining