Synsemadocsv0.6.xENES

LLM

Provider config

The provider is selected and configured by the runtime, not the program — the .syn never names a host or key, so it can't redirect the call or leak the key. Each knob resolves process environ > .env > default.

.env (gitignored)§

SYNSEMA_LLM_PROVIDER=deepseek
DEEPSEEK_API_KEY=sk-...

The key reaches the runtime without entering the process environment — no child process or program can read it (the program would need require secret, and even then sees it redacted). No export/source needed.

Knobs§

Env var / .env entryPurposeDefault
ANTHROPIC_API_KEY / OPENAI_API_KEY / MINIMAX_API_KEY / DEEPSEEK_API_KEYAPI key; presence auto-selects the provider— (offline if absent)
SYNSEMA_LLM_PROVIDERForce anthropic / openai / minimax / deepseekauto
SYNSEMA_LLM_MODELModel idclaude-sonnet-4-6 / gpt-4o / MiniMax-M3 / deepseek-chat
SYNSEMA_LLM_MAX_TOKENSOutput cap4096
SYNSEMA_LLM_BASE_URLEndpoint base (point at a local/compatible server)official
SYNSEMA_LLM_TIMEOUTHTTP timeout in seconds for the network providers. With the default streaming transport it measures silence between bytes — each chunk renews it — so a minutes-long generation flows and a dead host still fails fast. Invalid/≤0 → default60
SYNSEMA_LLM_HTTP_STREAMInternal SSE transport for the network providers (the language ops still return complete text; llm_stream emits real chunks). 0/false → classic non-stream path, escape hatch for odd proxies1 (on)
SYNSEMA_LLM_BUDGET_PER_IDENTITYid=N,id2=M (engine v0.6.20+, serve): per-identity token ceilings for the identity the auth task returns; one process-wide counter per identity; at the line that identity's ops degrade to [llm budget exceeded for identity …], the rest continue—
SYNSEMA_LLM_BUDGETPer-process LLM token budget (input + output, all ops). At the ceiling, every op degrades to the marker [llm budget exceeded: used N of M tokens] — no error, no network call, one stderr notice. Consumption is readable with llm_usage() (see LLM primitives). Invalid/0 → no budget, with a warning— (unlimited)

You can also force the provider per-run: synsema run app.syn --provider anthropic (flag > env > .env).

The provider is what a --labels ceiling names (engine v0.6.46+). With labels on, private data reaches a model only through a ceiling item llm=<principal>@<host>, and the host is the configured provider's: api.anthropic.com, api.openai.com, api.deepseek.com, api.minimax.io, local for the embedded provider (and for an unconfigured one), or the host of SYNSEMA_LLM_BASE_URL without its port (llm=customers@localhost for the Ollama setup below). Changing these knobs changes which item the host has to write — see LLM primitives § Private data to a model.

Timeouts no longer cap long generations. The network providers request the API response as a stream and reassemble it internally, so the read-timeout detects dead connections (60s of real silence) instead of limiting total generation time. Verified live: a 177s / 68 KB generation (MiniMax-M3, a reasoning model) completes where the old fixed 60s window killed it. With SYNSEMA_LLM_HTTP_STREAM=0 the timeout caps the whole call, as before.

Local / on-prem (100% private)§

Any OpenAI-compatible server (Ollama, LM Studio, vLLM):

SYNSEMA_LLM_PROVIDER=openai
SYNSEMA_LLM_BASE_URL=http://localhost:11434/v1   # Ollama
SYNSEMA_LLM_MODEL=llama3.1
OPENAI_API_KEY=ollama                            # any non-empty value

Embedded local provider (local) — a model in-process, zero network§

The official binaries ship it: the runtime runs a quantized GGUF inside the Synsema process, with no server, no API key and no socket at all — the only provider that works under a total deny net. Always explicit, never auto-selected.

Since v0.6.27, SYNSEMA_LLM_MODEL takes three forms, and none of them downloads anything:

SYNSEMA_LLM_PROVIDER=local

SYNSEMA_LLM_MODEL=/models/qwen2.5-3b-instruct-q4_k_m.gguf   # 1. a path to a .gguf
SYNSEMA_LLM_MODEL=qwen3:0.6b                                # 2. a model:tag already in the Ollama cache
SYNSEMA_LLM_MODEL=org/repo                                  # 3. a repo already in the Hugging Face cache

If you already use Ollama, you are done — nothing to download, nothing to convert. Ollama stores plain GGUF blobs in a content-addressed store, so Synsema reads the file directly and gets the sha256 of the weights for free. ollama does not need to be running. A name that is not there lists the three places it looked, with real paths.

KnobPurposeDefault
SYNSEMA_LLM_MODELPath, model:tag, or org/repo (required for local)—
SYNSEMA_INFER_BACKENDrust = the engine written by us; anything else = candlecandle
SYNSEMA_INFER_ARCHDEFDirectory of <arch>.archdef files — architectures without recompiling (Local inference)—
SYNSEMA_LLM_CTXContext window (capped to the GGUF's own limit)4096
SYNSEMA_LLM_THREADSCPU threads for inferenceall cores
SYNSEMA_LLM_TEMPERATURE0 = greedy/deterministic; >0 = sampling (fixed seed)0
SYNSEMA_LLM_MAX_CONCURRENTMax model instances; 1 serializes concurrent calls under serve1
SYNSEMA_LLM_STREAM_BUFFERChunks in flight between generation and llm_stream emission32

SYNSEMA_LLM_MAX_TOKENS applies as usual.

Which architectures run§

The architecture is the general.architecture string in the GGUF header, not the brand name. Which ones run depends on the engine — see Local inference and architectures for the difference and for how to add one yourself:

EngineArchitectures
candle (default)llama, qwen2, qwen3 — compiled into the binary
SYNSEMA_INFER_BACKEND=rustllama, qwen2, qwen3, gemma3 — each one a file you can read, replace or add to

Anything else fails with a clear [local error: …] that names the engine which can run it. Popular families convert to GGUF declaring one of those names, so real coverage is wider than the list suggests. Families verified live (probe = load + question + coherent answer + clean stop):

Model (GGUF probed)declaresVerified
Qwen2.5 Instruct 0.5B/3Bqwen2✅
Qwen3 0.6Bqwen3✅ thinking model — emits raw <think>…</think>; budget SYNSEMA_LLM_MAX_TOKENS for it
Qwen3 4B Instruct-2507qwen3✅ (2.5 GB GGUF, ran in 8 GB RAM). The 4B Thinking-2507 also runs but thinks for thousands of tokens per answer — impractical on CPU; prefer the Instruct variant
Gemma 3 270Mgemma3✅ rust engine only — token for token what Ollama generates for the same prompt
Mistral 7B Instruct v0.3llama✅ its [INST] template is auto-detected
Llama 3.2 1B Instructllama✅ llama3 template auto-detected
SmolLM2 135M Instructllama✅ chatml
DeepSeek-R1-Distill-Qwen 1.5Bqwen2✅ runs in plain mode; strip the <think> tags in your code
TinyLlama 1.1B Chatllama⚠️ loads and runs, but its zephyr template isn't recognized → plain fallback, behaves like a BASE model

A supported arch loads and runs; chat usability also needs a recognized chat template (chatml / llama3 / [INST] / gemma; otherwise plain fallback). phi and glm GGUFs are still rejected.

If you ran a llama, Mistral or Gemma GGUF before v0.6.27, its answers were wrong. Those files store token ranks, not log-probabilities, and the tokenizer was segmenting them as if they were log-probabilities: The capital of France is entered the model as eleven fragments instead of five words. Nothing failed — the model just answered badly. Fixed in v0.6.27. The BPE family (qwen, llama 3) was never affected.

What is actually configured — synsema llm status§

It prints the models already on disk and the architectures of the engine that will actually run, each with its origin and its sha. --json carries the same under an inference key, with the full sha, so a script can compare two runs instead of reading prose:

Modelos locales ya descargados (2):
  gemma3:270m       ollama:gemma3:270m  sha256:735af2139dc6
  qwen3:0.6b        ollama:qwen3:0.6b   sha256:7f4030143c1c
  Usá SYNSEMA_LLM_MODEL=<nombre> — no se descarga nada.

Arquitecturas que corre el backend `rust` (4):
  gemma3 (20 pasos por capa, en el binario, sha b935fcfab2d6)
  llama (16 pasos por capa, en el binario, sha a580fa45fdb6)
  qwen2 (19 pasos por capa, en el binario, sha fbba11641175)
  qwen3 (18 pasos por capa, ./archdefs/qwen3.archdef, sha 5b8d9db76a68)
  Definiciones del operador: ./archdefs

On a binary without the feature, SYNSEMA_LLM_PROVIDER=local prints a clear stderr notice and stays offline — it never silently falls back to another provider.

Honest limits, and building for speed§

Measured (Qwen2.5 Q4_K_M, no rebuild): generation ~13 tok/s (0.5B) / ~5 tok/s (3B); prefill ~12 tok/s — built for short prompts (hundreds of tokens, not thousands). Model load is paid once per process (~6s / ~19s): under serve, the first request loads and the rest reuse. RAM with candle: ~1GB (0.5B) / ~2.4GB (3B); with the rust engine, roughly the size of the .gguf (the file is memory-mapped and the big weights stay quantized).

Rebuilding for speed is only worth it on candle. candle picks its AVX2 quantized kernels at compile time (#[cfg(target_feature = "avx2")]), and Rust's default x86-64 target does not enable AVX2 — so a plain build runs the scalar path. Measured gain: prefill ~3.1× on a 0.5B (~11 → ~35 tok/s). The rust engine does not need this: it dispatches on the CPU it finds (AVX / AVX2+FMA / AVX-512 / NEON), so the binary you downloaded already uses your machine's instructions.

# only if you stay on candle:
RUSTFLAGS="-C target-cpu=native" cargo install --path crates/synsema-cli --features llm-local --force

native ties the binary to that machine's CPU — right for your own VPS; for a binary you distribute, use -C target-cpu=x86-64-v3 (AVX2+FMA, x86 CPUs from ~2015).

---

The egress to the configured host is part of require llm — not a separate net grant (the embedded provider needs no egress at all). Offline (no key), the ops return placeholders; branch on llm_available(). To bypass the built-in ops entirely and hit the API yourself, see Provider API directly.