{"id":48768425,"url":"https://github.com/a9lim/saklas","last_synced_at":"2026-04-25T00:03:58.655Z","repository":{"id":350296117,"uuid":"1205500770","full_name":"a9lim/saklas","owner":"a9lim","description":"Activation steering and trait monitoring for HuggingFace transformers","archived":false,"fork":false,"pushed_at":"2026-04-21T18:54:18.000Z","size":37197,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-21T20:49:58.020Z","etag":null,"topics":["activation-steering","ai","interpretability","llm","python","representation-engineering","transformers"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/a9lim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-09T02:44:52.000Z","updated_at":"2026-04-21T18:54:07.000Z","dependencies_parsed_at":"2026-04-18T10:01:26.873Z","dependency_job_id":null,"html_url":"https://github.com/a9lim/saklas","commit_stats":null,"previous_names":["a9lim/steer","a9lim/liahona","a9lim/saklas"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/a9lim/saklas","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a9lim%2Fsaklas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a9lim%2Fsaklas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a9lim%2Fsaklas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a9lim%2Fsaklas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/a9lim","download_url":"https://codeload.github.com/a9lim/saklas/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a9lim%2Fsaklas/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32245157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activation-steering","ai","interpretability","llm","python","representation-engineering","transformers"],"created_at":"2026-04-13T09:00:32.983Z","updated_at":"2026-04-25T00:03:58.639Z","avatar_url":"https://github.com/a9lim.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# saklas\n\n[![CI](https://github.com/a9lim/saklas/actions/workflows/ci.yml/badge.svg)](https://github.com/a9lim/saklas/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/saklas)](https://pypi.org/project/saklas/)\n[![Downloads](https://img.shields.io/pypi/dm/saklas)](https://pypi.org/project/saklas/)\n[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://pypi.org/project/saklas/)\n\nSaklas is a library for activation steering and trait probing on local HuggingFace models. You give it any concept, from \"angry\" to \"bacterium\", and it automatically generates contrastive pairs, extracts a direction from them, and then adds that direction to the model's hidden states when it's time to generate text. The model itself isn't touched, so you can change the steering strength as you go.\n\nSaklas is built on Representation Engineering ([Zou et al., 2023](https://arxiv.org/abs/2310.01405)), the same paper [repeng](https://github.com/vgel/repeng) implements. The main feature is a terminal UI with live steering controls and a built-in trait monitor that scores every generated token against any probe you care about, with live averages and sparklines so you can see where in a response a trait shifts. There's also an HTTP server that supports both OpenAI `/v1/*` and Ollama `/api/*` on the same port so Open WebUI, Enchanted, or any other OpenAI/Ollama client can talk to a steered model without changes. Persona cloning works on any text sample: point it at a corpus and it pulls out a voice/style vector without hand-labeled pairs.\n\nThree ways to use it:\n\n- **`saklas tui \u003cmodel\u003e`**: Terminal UI\n- **`saklas serve \u003cmodel\u003e`**: HTTP server compatible with both OpenAI and Ollama\n- **`SaklasSession`**: Python API\n\nIt runs on CUDA and Apple Silicon MPS. The full TUI has been tested to run comfortably on a MacBook. CPU does work but it's slow. Tested on Qwen, Gemma, Ministral, gpt-oss, Llama, and GLM. A lot more architectures are wired up in `saklas/core/model.py:_LAYER_ACCESSORS` but have not been tested; if you try one, please let me know how it went.\n\n---\n\n## Credits\n\nThe contrastive-PCA approach comes from the Representation Engineering paper ([Zou et al., 2023](https://arxiv.org/abs/2310.01405)). [repeng](https://github.com/vgel/repeng) by Theia Vogel, is the well-known implementation in this space and is what most people might reach for. Saklas implements the same idea from a different angle: repeng is lean and more of a library, saklas is more of a TUI with monitoring and a chat server bundled in. Both are worth your time!\n\n---\n\n## Quick start\n\n```bash\npip install saklas\nsaklas tui google/gemma-3-4b-it\n```\n\nThe first run downloads the model and extracts the 21 bundled probes. Try `/steer 0.4 angry`: that applies the built-in `angry.calm` vector at α = +0.4 and the model leans angry. `/steer 0.4 calm` gives you the same vector at α = −0.4. `Ctrl+Y` colors each generated token by how strongly the selected probe lit up on it. `Ctrl+A` does a direct A/B comparison against the unsteered model.\n\nAs an API server:\n\n```bash\npip install saklas[serve]\nsaklas serve google/gemma-3-4b-it --steer \"0.2 cheerful\"\n```\n\nFrom Python:\n\n```python\nfrom saklas import SaklasSession\n\nwith SaklasSession.from_pretrained(\"google/gemma-3-4b-it\") as s:\n    name, profile = s.extract(\"angry.calm\")          # bundled bipolar pack\n    s.steer(name, profile)                           # register (no alpha yet)\n    print(s.generate(\"What makes a good day?\", steering=f\"0.3 {name}\").text)\n```\n\n---\n\n## Install\n\n```bash\npip install saklas             # library + TUI\npip install saklas[serve]      # + FastAPI/uvicorn for the API server\npip install saklas[gguf]       # + gguf package for llama.cpp interchange\npip install saklas[research]   # + datasets/pandas for dataset loading and DataFrames\npip install saklas[sae]        # + sae-lens for SAE-backed extraction\n```\n\nThis requires Python 3.11+ and PyTorch 2.2+. It should run on Linux, macOS, and Windows. CUDA or Apple Silicon MPS is recommended for anything interactive.\n\nFrom source:\n\n```bash\ngit clone https://github.com/a9lim/saklas\ncd saklas\npip install -e \".[dev]\"        # + pytest\n```\n\n---\n\n## How it works\n\n### Steering vectors\n\nSaklas takes pairs of sentences and runs them through the model, and then subtracts the two sides. Doing an SVD, it takes the largest principal component at each layer and combines them into a steering tensor. When it's time to generate text, it then takes every layer and adds `alpha × direction` to the hidden state, then rescales it back to the original magnitude.\n\nEach layer's PCA share is baked into the tensor magnitudes at extraction, so the same α means approximately the same strength across architectures. Roughly:\n\n- **0.1–0.3**: soft nudge\n- **0.3–0.6**: coherent steered\n- **0.6-0.8**: starting to be incoherent\n- **0.8-1.0**: gibberish\n\nWhen multiple vectors are selected, they are added together in sequence. \n\n### SAE-backed extraction (experimental)\n\n\u003e **Experimental** This pipeline is not as tested as the contrastive-PCA path. α was measured and calibrated on raw PCA and may not cleanly transfer. Quality also depends on which SAE release you pick. I would recommend using a low α (0.1–0.2) and sweeping. For production use the raw pipeline should be the default. \n\nInstall `saklas[sae]` and pass `--sae \u003crelease\u003e` to `vector extract` to run contrastive PCA in sparse-autoencoder feature space. Saklas routes through SAELens, so any published release it covers (GemmaScope, Eleuther Meta-LLaMA-3.1 SAEs, Joseph Bloom's, Apollo/Goodfire) should be supported. The output uses the same backend as raw PCA.\n\n```bash\nsaklas vector extract honest.deceptive -m google/gemma-2-2b-it \\\n  --sae gemma-scope-2b-pt-res-canonical\n```\n\nThen steer the SAE variant:\n\n```python\nwith session.steering(\"0.3 honest:sae\"):\n    session.generate(\"...\")\n```\n\n```yaml\n# ~/.saklas/config.yaml\nvectors: \"0.3 honest:sae\"\n```\n\n```\n/steer 0.3 honest:sae                                   # TUI: unique SAE variant on disk\n/steer 0.3 honest:sae-gemma-scope-2b-pt-res-canonical   # TUI: explicit release\n```\n\nSAE profiles only include the layers the release covers.\n\nBy default steering fires on every token. The grammar's `@trigger` token attaches a per-term trigger override:\n\n```python\n# Steer only the response, never the prompt or the thinking section\nsession.generate(\"...\", steering=\"0.4 warm@after\")\n\n# Mix regimes per concept\nsession.generate(\"...\", steering=\"0.3 honest + 0.4 warm@after\")\n\n# Projection: steer honest with sycophancy removed\nsession.generate(\"...\", steering=\"0.3 honest|sycophantic\")\n```\n\nGrammar triggers map to the preset constants (`BOTH` / `GENERATED_ONLY` / `PROMPT_ONLY` / `AFTER_THINKING` / `THINKING_ONLY`) — `@both`, `@response`, `@before`, `@after`, `@thinking`. `Trigger.first(n)` and `Trigger.after(n)` let you express token-window ranges. If you want arbitrary combinations, you should pass a pre-built `Steering`.\n\n### Custom concepts\n\nWhen you steer on something not in the built-in library, the model writes its own contrastive pairs. It first comes up with 9 domains for the axis (for `deer.wolf`, it comes up with \"predation and threat assessment\", \"territorial defense\", etc.), and then writes 5 contrastive pairs per domain. \n\nThis means `/steer \u003canything\u003e` works: religions, animals, fictional characters, anything you can name.\n\nOne caveat on custom axes: when the two poles are asymmetric — one specific and one generic, or one that reads more naturally in the reversed order than the order you typed — the model sometimes flips A and B during pair generation, so the statements you asked for as the positive pole end up under `negative` and vice versa. The tensor still extracts cleanly, it just points the wrong way, and `+α` steers toward what you called the negative pole. Balanced axes like the bundled ones don't trip this; it shows up mainly on asymmetric pairs like `human.artificial_intelligence`. If a custom axis does the opposite of what you expect, open `~/.saklas/vectors/local/\u003cconcept\u003e/statements.json` and check whether the `positive` entries actually read as the pole you asked for. If they're reversed, swap `positive` and `negative` in the file and re-run extraction, or just flip the pole order in your call.\n\n### Trait monitor\n\nWhile generating, saklas records the hidden state at every probe layer and every step. They are mean-centered against a neutral baseline and then scored by weighted cosine similarity against every active probe. You can see the history as a sparkline in the TUI; in the library you get `result.readings` as a dict of `ProbeReadings`.\n\n### Vector comparison\n\n`Profile.cosine_similarity(other)` gives you weighted cosine similarity between two steering profiles over their shared layers. The CLI has three modes: ranked comparison of one selected vector against all installed profiles, direct pairwise comparison, and N×N similarity matrices. The TUI has `/compare` for interactive use.\n\nThis lets you find concepts that are correlated. For example, `creative.conventional` and `hallucinating.grounded` extract similar directions on some models (+0.78 on gemma-4-e4b-it), which means that the model itself encodes both concepts in similar directions.\n\n### The probe library\n\nThere are 21 default probes across 6 categories, containing 45 contrastive pairs generated using the program's pipeline.\n\n| Category | Probes |\n|---|---|\n| **Affect** | angry.calm, happy.sad |\n| **Epistemic** | confident.uncertain, honest.deceptive, hallucinating.grounded |\n| **Alignment** | agentic, refusal.compliant, sycophantic.blunt, manipulative |\n| **Register** | formal.casual, direct.indirect, verbose.concise, creative.conventional, humorous.serious, warm.clinical, technical.accessible |\n| **Social stance** | authoritative.submissive, high_context.low_context |\n| **Cultural** | masculine.feminine, religious.secular, traditional.progressive |\n\nPoles are aliased: `/steer angry 0.5` → `angry.calm` at α = +0.5. `/steer calm 0.5` → `angry.calm` at α = −0.5. This works for any installed bipolar pack.\n\nProbes extract on first run per model and cache to `~/.saklas/vectors/default/\u003cconcept\u003e/\u003csafe_model_id\u003e.safetensors`.\n\n---\n\n## Terminal UI\n\n```bash\nsaklas tui google/gemma-2-9b-it\nsaklas tui mistralai/Mistral-7B-Instruct-v0.3 -q 4bit\nsaklas tui meta-llama/Llama-3.1-8B-Instruct -p affect register\n```\n\nThere are three panels: a vector registry on the left, chat in the center, and a trait monitor on the right. `Tab` cycles between panels, arrow keys navigate within each panel.\n\n### Flags\n\n| Flag | Description |\n|---|---|\n| `model` | HuggingFace ID or local path (optional if supplied by `-c`) |\n| `-q`, `--quantize` | `4bit` or `8bit` (CUDA only) |\n| `-d`, `--device` | `auto` (default), `cuda`, `mps`, `cpu` |\n| `-p`, `--probes` | Categories: `all`, `none`, `affect`, `epistemic`, `alignment`, `register`, `social_stance`, `cultural` |\n| `-c`, `--config` | Load setup YAML |\n| `-s`, `--strict` | With `-c`: fail on missing vectors |\n\n### Keybindings\n\n| Key | Action |\n|---|---|\n| `Tab` / `Shift+Tab` | Cycle panel focus |\n| `Left` / `Right` | Adjust alpha |\n| `Up` / `Down` | Navigate vectors / probes |\n| `Enter` | Toggle vector on/off |\n| `Backspace` / `Delete` | Remove selected vector or probe |\n| `Ctrl+T` | Toggle thinking mode |\n| `Ctrl+A` | A/B compare (steered vs. unsteered) |\n| `Ctrl+R` | Regenerate last response |\n| `Ctrl+S` | Cycle trait sort mode |\n| `Ctrl+Y` | Per-token probe highlighting |\n| `[` / `]` | Adjust temperature |\n| `{` / `}` | Adjust top-p |\n| `Escape` | Stop generation |\n| `Ctrl+Q` | Quit |\n\n### Chat commands\n\n| Command | Description |\n|---|---|\n| `/steer \u003cexpression\u003e` | Apply a steering expression (grammar: `0.5 honest + 0.3 warm@after`, `0.5 honest:sae`, `0.5 a\\|b`, …) |\n| `/alpha \u003cname\u003e \u003cval\u003e` | Adjust an already-registered vector's alpha |\n| `/unsteer \u003cname\u003e` | Remove a registered vector |\n| `/probe \u003cname\u003e` | Extract and register a probe vector |\n| `/probe \u003cpos\u003e . \u003cneg\u003e` | Same, bipolar form |\n| `/unprobe \u003cname\u003e` | Remove a registered probe vector |\n| `/compare \u003ca\u003e [b]` | Cosine similarity (1-arg: ranked vs all; 2-arg: pairwise) |\n| `/extract \u003cname\u003e` | Extract to disk without registering |\n| `/extract \u003cpos\u003e . \u003cneg\u003e` | Same, bipolar form (only path for new bipolar extraction) |\n| `/regen` | Regenerate the last assistant turn |\n| `/clear` | Clear conversation history |\n| `/rewind` | Undo last exchange |\n| `/sys \u003cprompt\u003e` | Set system prompt |\n| `/temp \u003cv\u003e` / `/top-p \u003cv\u003e` / `/max \u003cn\u003e` | Sampling defaults |\n| `/seed [n\\|clear]` | Default sampling seed |\n| `/save \u003cname\u003e` / `/load \u003cname\u003e` | Snapshot/restore conversation + alphas |\n| `/export \u003cpath\u003e` | JSONL with per-token probe readings |\n| `/model` | Model + device + active state |\n| `/help` | List commands and keybindings |\n\nA footer at the bottom of the trait panel shows the top 5 layers and the live highest and lowest scored tokens for the selected probe.\n\nThe footer in the chat panel shows generation progress, live tok/s, elapsed, and the running perplexity of the token stream (geometric mean of the pre-temperature post-steering next-token distribution).\n\nIf you want to extract a vector for two poles, use `/extract a dog . a pair of cats`. The TUI parses around the space-period-space delimiter. `dog.cat` stays a single name.\n\n---\n\n## Python API\n\n```python\nfrom saklas import SaklasSession, SamplingConfig, Steering, Profile, DataSource, ResultCollector\n\nwith SaklasSession.from_pretrained(\"google/gemma-3-4b-it\", device=\"auto\") as session:\n    name, profile = session.extract(\"angry.calm\")   # bundled bipolar pack; returns Profile\n    session.steer(name, profile)                    # register (no alpha yet)\n\n    result = session.generate(\n        \"What makes a good day?\",\n        steering=f\"0.2 {name}\",\n        sampling=SamplingConfig(temperature=0.7, max_tokens=256, seed=42),\n    )\n    print(result.text)\n    print(result.readings)                          # live probe readings\n    print(result.applied_steering)                  # canonical expression receipt\n\n    # Scoped steering with pole resolution\n    with session.steering(\"0.4 calm\"):              # bare pole → angry.calm @ -0.4\n        print(session.generate(\"Describe a rainy afternoon.\").text)\n\n    # Compare vectors\n    other_name, other_profile = session.extract(\"happy.sad\")\n    print(profile.cosine_similarity(other_profile))                  # aggregate\n    print(profile.cosine_similarity(other_profile, per_layer=True))  # per-layer\n\n    # Alpha sweep\n    collector = ResultCollector()\n    for alpha in [-0.2, -0.1, 0, 0.1, 0.2]:\n        session.clear_history()\n        r = session.generate(\"Describe a sunset.\", steering=f\"{alpha} {name}\")\n        collector.add(r, alpha=alpha)\n    collector.to_csv(\"sweep.csv\")\n```\n\nRegistration is state and steering is per-call. `session.steer(\"name\", profile)` stores the vector; `session.generate(input, steering=\"0.5 name\")` applies it for that generation. Without `steering` you get a clean baseline.\n\nYou can compose concepts with `+` / `-` / `@trigger` / `|` / `~`. Every surface (Python, YAML, HTTP, TUI, `vector merge`) parses the same expression language. Nested `with session.steering(...)` blocks get flattened, inner wins on key collision.\n\nSampling is per-call via `SamplingConfig`: `temperature`, `top_p`, `top_k`, `max_tokens`, `seed`, `stop`, `logit_bias`, `presence_penalty`, `frequency_penalty`, `logprobs`.\n\nThinking mode auto-detects for models that support it (Qwen 3.5, Gemma 4, gpt-oss, etc). The delimiters are detected from the chat template.\n\n`session.events` is a synchronous `EventBus`. Subscribe to `VectorExtracted`, `SteeringApplied`, `SteeringCleared`, `ProbeScored`, `GenerationStarted`, `GenerationFinished`.\n\n### SaklasSession reference\n\n```python\nsession = SaklasSession.from_pretrained(\n    model_id, device=\"auto\", quantize=None, probes=None,\n    system_prompt=None, max_tokens=1024,\n)\n\n# Extraction\nname, profile = session.extract(\"curiosity\")                # fresh monopolar\nname, profile = session.extract(\"angry.calm\")               # bundled bipolar\nname, profile = session.extract(\"happy\", baseline=\"sad\")    # explicit\nname, profile = session.extract(DataSource.csv(\"pairs.csv\"))\n\n# Persona cloning\nname, profile = session.clone_from_corpus(\"transcripts.txt\", \"hunter\", n_pairs=90)\n\n# Registry\nsession.steer(\"name\", profile)\nsession.unsteer(\"name\")\n\n# Generation\nresult = session.generate(\"prompt\", steering=\"0.5 name\",\n                          sampling=SamplingConfig(temperature=0.8))\nfor tok in session.generate_stream(\"prompt\", steering=\"0.5 name\"):\n    print(tok.text, end=\"\", flush=True)\n\n# Scoped steering\nwith session.steering(\"0.5 wolf\"):        # -\u003e deer.wolf @ -0.5\n    session.generate(\"prompt\")\n\n# Vector comparison\nsimilarity = profile.cosine_similarity(other_profile)\nper_layer = profile.cosine_similarity(other_profile, per_layer=True)\n\n# Monitor\nsession.probe(\"honest\")\nsession.unprobe(\"honest\")\n\n# State\nsession.history; session.last_result; session.last_per_token_scores\nsession.stop(); session.rewind(); session.clear_history()\n```\n\n### GenerationResult\n\n```python\nresult.text              # decoded output (thinking is separate)\nresult.tokens            # token IDs\nresult.token_count; result.tok_per_sec; result.elapsed\nresult.finish_reason     # \"stop\" | \"length\" | \"stop_sequence\"\nresult.vectors           # {\"angry.calm\": 0.2} — alphas snapshot\nresult.readings          # {\"probe_name\": ProbeReadings}\nresult.to_dict()\n```\n\n---\n\n## API server\n\n`saklas serve` supports both OpenAI `/v1/*` and Ollama `/api/*` on the same port. It should work with the OpenAI Python/JS SDKs, LangChain, Open WebUI, Enchanted, Msty, `ollama-python`, and anything else that talks either wire format.\n\n```bash\npip install saklas[serve]\nsaklas serve google/gemma-2-9b-it --steer \"0.2 cheerful\" --port 8000\n```\n\n### OpenAI SDK\n\n```python\nfrom openai import OpenAI\nclient = OpenAI(base_url=\"http://localhost:8000/v1\", api_key=\"unused\")\n\nresp = client.chat.completions.create(\n    model=\"google/gemma-2-9b-it\",\n    messages=[{\"role\": \"user\", \"content\": \"Hello!\"}],\n    extra_body={\"steering\": \"0.4 cheerful\"},       # per-request expression\n)\n```\n\n### Ollama\n\nPoint any Ollama client at `http://localhost:8000` and it should work. Steering goes through the `steer` field in `options`:\n\n```bash\ncurl -N http://localhost:8000/api/chat -d '{\n  \"model\": \"gemma2\",\n  \"messages\": [{\"role\": \"user\", \"content\": \"Write me a haiku.\"}],\n  \"options\": {\"steer\": \"0.3 cheerful - 0.2 formal.casual\"}\n}'\n```\n\n### Saklas-native routes\n\n`/saklas/v1/*` is a resource tree with sessions, vector and probe management, one-shot probe scoring, a bidirectional WebSocket for token plus probe co-streaming, and a live traits SSE endpoint (`GET /saklas/v1/sessions/{id}/traits/stream`) that streams per-token probe scores in real time during any active generation. Interactive docs at `http://localhost:8000/docs`.\n\n### Flags\n\n| Flag | Default | Description |\n|---|---|---|\n| `model` | required | HuggingFace ID or local path |\n| `-H`, `--host` | `0.0.0.0` | Bind address |\n| `-P`, `--port` | `8000` | Bind port |\n| `-S`, `--steer` | — | Default steering expression, e.g. `\"0.2 cheerful\"` |\n| `-C`, `--cors` | — | CORS origin, repeatable |\n| `-k`, `--api-key` | None | Bearer auth. Falls back to `$SAKLAS_API_KEY`. |\n\nNot supported: tool calling, strict JSON mode, embeddings. The server is designed for trusted networks, please see [SECURITY.md](SECURITY.md).\n\n---\n\n## Concept packs\n\nAll state lives under `~/.saklas/` (override via `SAKLAS_HOME`). Each concept is a folder with `pack.json`, `statements.json`, and per-model tensors. Packs are distributed as HuggingFace model repos.\n\nPackless install handles repos with no `pack.json`, so repeng-style GGUF-only control-vector repos install cleanly: `saklas pack install jukofyork/creative-writing-control-vectors-v3.0`.\n\n### Pack management\n\n```bash\nsaklas pack install \u003ctarget\u003e [-s] [-a NS/NAME] [-f]\nsaklas pack refresh \u003cselector\u003e [-m MODEL]\nsaklas pack clear \u003cselector\u003e [-m MODEL] [-y]\nsaklas pack rm \u003cselector\u003e [-y]\nsaklas pack ls [selector] [-j] [-v]\nsaklas pack search \u003cquery\u003e [-j] [-v]\nsaklas pack push \u003cselector\u003e [-a OWNER/NAME] [-pm MODEL] [-snt] [-d] [-f]\nsaklas pack export gguf \u003cselector\u003e [-m MODEL] [-o PATH] [--model-hint HINT]\n```\n\n### Vector operations\n\n```bash\nsaklas vector extract \u003cconcept\u003e | \u003cpos\u003e \u003cneg\u003e [-m MODEL] [-f]\nsaklas vector merge \u003cname\u003e \u003cexpression\u003e [-m] [-f] [-s]\nsaklas vector clone \u003ccorpus-file\u003e -N NAME [-m MODEL] [-n N_PAIRS] [--seed S] [-f]\nsaklas vector compare \u003cconcepts...\u003e -m MODEL [-v] [-j]\nsaklas vector why \u003cconcept\u003e -m MODEL [-j]\n```\n\nMerge expressions share the steering grammar. Terms combine with `+` / `-`, coefficients lead each term, and `~` projects one direction's component out of another. For example, `saklas vector merge dehallu \"0.8 default/creative.conventional~default/hallucinating.grounded\"` gives you creative with hallucination projected out.\n\nSelectors: `\u003cname\u003e`, `\u003cns\u003e/\u003cname\u003e`, `tag:\u003ctag\u003e`, `namespace:\u003cns\u003e`, `default`, `all`. Bare names resolve across namespaces and error if ambiguous.\n\n---\n\n## Supported architectures\n\n**Tested**: Qwen, Gemma, Ministral, gpt-oss, Llama, GLM.\n\n**Wired up but untested**: Mistral, Mixtral, Phi 1–3, PhiMoE, Cohere 1–2, DeepSeek V2–V3, StarCoder2, OLMo 1–3 plus OLMoE, Granite plus GraniteMoE, Nemotron, StableLM, GPT-2 / Neo / J / BigCode / NeoX, Bloom, Falcon / Falcon-H1, MPT, DBRX, OPT, Recurrent Gemma.\n\nPlease see [CONTRIBUTING.md](CONTRIBUTING.md) for adding an architecture.\n\n---\n\n## Tests\n\n```bash\npytest tests/                      # everything\npytest tests/test_server.py        # CPU-only\npytest tests/test_smoke.py         # GPU required\n```\n\nGPU tests download `google/gemma-3-4b-it` (~8 GB) on first run. Works on CUDA and Apple Silicon MPS.\n\n---\n\n## Contributing and security\n\nPlease see [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup. For security, please see [SECURITY.md](SECURITY.md).\n\n## License\n\nAGPL-3.0-or-later. See [LICENSE](LICENSE).\n\nIf you use Saklas in published research, please additionally cite the Representation Engineering paper (Zou et al., 2023) and [repeng](https://github.com/vgel/repeng).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa9lim%2Fsaklas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fa9lim%2Fsaklas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa9lim%2Fsaklas/lists"}