KI_TOOLS_PROFILES.md records what each local GGUF is actually for, based on the model cards: ornith is a purpose-built agentic coding model, Carnice targets agent runtimes, Qwopus is reasoning plus vision, and the two HauhauCS models are abliterated — an axis about refusals, not literary quality. It also maps which models the local mmproj.gguf fits (the Qwen3.6-35B-A3B based ones, not ornith). Capability and benchmark claims are attributed to their authors, not asserted. EVAL_RUBRIC.md separates what a script can measure from what needs reading, and warns that a passing test suite only proves the code satisfies its own tests. CHANGELOG covers the mmproj feature, the --print-effective-config fix, and the new tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
pip install -e ".[dev]" # dev install
scripts/check.sh # full local CI gate (ruff + mypy + pytest) — mirrors .forgejo/workflows/ci.yml
python -m pytest -q # tests
python -m pytest tests/test_prompt_urls.py -q # one file
python -m pytest tests/test_actions.py::test_do_start -q # one test
ruff check src/ tests/ build_archive.py
mypy # config in pyproject.toml (files = src/llamacppctl)
Always use python -m pytest, never the pytest console script: the test modules do
from tests.test_docker_ops import ..., which needs the repo root on sys.path.
git config core.hooksPath .githooks (once per clone) enables the pre-push hook that runs
scripts/check.sh. CI runs on Python 3.10 and 3.12; requires-python = ">=3.10".
Other entry points:
python3 build_archive.py— builds and self-verifiesllamacppctl-installable.tar.gz(has its own required-file manifest; adding a top-level file that must ship means updating it).SMOKE_GPU=1 scripts/smoke.sh— opt-in end-to-end test against a real Docker + GPU + GGUF model. Not part of the pytest suite.
Architecture
A single CLI (llamacppctl) that starts/checks/stops/switches llama.cpp servers running as Docker
containers, plus a security-hardened input layer for system/user prompts from text, file, or URL.
main.py is a thin pipeline: parse (cli.build_parser) → validate (cli.validate_args) →
docker_available() → resolve prompt sources (prompt_io) → resolve config
(config.resolve_effective_config) → dispatch to actions.do_*.
Layering is deliberate and must be preserved — it keeps the security-critical input validation independently testable from orchestration:
prompt_io.pyknows nothing about argparse; it works only withPromptSource+InputPolicy.config.pyknows nothing about Docker or HTTP.docker_ops.py/http_ops.pyknow nothing about CLI semantics.actions.pyorchestrates;schema.pyholds the shared dataclasses (ServerConfig,PromptConfig,ChatReply,CheckResult).
Two cross-module couplings to know about:
main._resolve_prompts()stashes loaded prompts on the argparse namespace asargs._resolved_system_prompt/args._resolved_user_prompt;config.pypicks them up there.main.run()callsdocker_available()before config resolution, so--dry-runstill needs a reachable Docker daemon. Only--print-effective-configskips that check — it is a standalone action in the mutually-exclusive group, never a modifier, so it cannot start anything.
Config resolution
INI file (llama.cpp.config, template in llama.cpp.config.example), lowest to highest priority:
builtin defaults (config.builtin_defaults()) → [default] → [model.<profile>] (via --profile)
→ CLI overrides. [prompt.<name>] sections resolve separately and only supply a fallback system
prompt — an explicit -s/--system-file/--system-url always wins.
container_name is mandatory (empty → ConfigError). It is the only identity anchor: Docker
--name, the --stop/--check target, and the derived lock path
/tmp/llamacppctl.<container_name>.lock. Profiles meant to run in parallel need distinct
container_name and host_port, otherwise a second --start silently replaces the first
container.
max_tokens / chat_temperature / connect_timeout / read_timeout affect only the chat request
(--chat and the reply printed after --start), never the container.
Exit codes (actions.py)
0 ok · 1 general/config/prompt error · 2 argparse validation · 3 model path missing ·
4 docker error · 5 HTTP not ready (also --check failing) · 6 lock busy.
--check is meant to be scriptable; keep those codes stable.
Invariants worth not breaking
- All Docker calls go through
subprocess.run()with argument lists — nevershell=True, never composed shell strings. - The port publishes to
127.0.0.1by default (docker_ops._port_publish()); the llama.cpp OpenAI endpoint is unauthenticated unlessapi_keyis set.--exposebinds all interfaces. - Every HTTP request in
prompt_io— including each redirect hop — passesvalidate_url_target(): HTTPS-only, no IP literals, no embedded credentials, nolocalhost, all resolved addresses classified (private/loopback/link-local/multicast/reserved/unspecified → reject), metadata IP169.254.169.254blocked._pin_dns()then restrictsgetaddrinfoto the already-validated IPs for the duration of the request, closing the TOCTOU/DNS-rebinding window. Size limits are enforced both fromContent-Lengthand while streaming. - Escape hatches (
--allow-private-url,--allow-insecure-http,--allow-ip-host,--allow-symlinks,--follow-redirects,--allow-html-input) are opt-in and off by default.--allow-private-urland--url-allow-hostare mutually exclusive. --startand--changehold an exclusive non-blockingfcntl.flock;--changevalidates the model path before removing the running container.--dry-runtakes no lock and touches nothing.
Tests mock subprocess and requests (and DNS resolution for the SSRF cases); nothing in the
pytest suite requires Docker or the network.
Conventions
- Docs, comments in
docs/, and the example prompts are in German; source code, docstrings, and commit subjects are in English. Commits follow Conventional Commits (feat(chat):,docs(prompts):). - Line length 100,
from __future__ import annotationsin every module,py.typedis shipped — keep annotations complete enough formypyto pass. - Behavior changes should be reflected in
man/llamacppctl.1,CHANGELOG.md, and — for security- relevant ones —docs/SECURITY_AND_OPERATIONS.md.