clan-llm

Welcome to Clan's LLM repository. This repository contains Clan's experiments related to LLMs and how they can be used as an interface to managing your Clan.

Getting Started

Structure

The repository currently contains two main components:

  • Clan LLM Lib: The library components that power other tools
  • Clan CLI AI: A subcommand for Clan CLI that enables you conversationally modify your Clan via a TUI.

These components are located in pkgs which roughly follows the same structure as clan-core.

├── checks
│   └── llm                         # Container Tests
└── pkgs
    └── clan-llm
        ├── clan_cli                # Clan CLI Functionality
        │   └── ai                  # AI Subcommand
        └── clan_lib                # Clan LLM Library
            └── llm
                └── container_data  # Container Test Data

Setup

Supported Operating Systems

  • Linux
  • macOS

Getting Started with the Development Environment

Let's get your development environment up and running:

  1. Install Nix Package Manager:

    curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
    
  2. Install direnv:

    • To automatically setup a devshell on entering the directory
    nix profile install nixpkgs#nix-direnv-flakes nixpkgs#direnv
    
  3. Add direnv to your shell:

    • Direnv needs to hook into your shell to work. You can do this by executing following command. The example below will setup direnv for zsh and bash
    echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
    
  4. Allow the devshell

    • Run direnv allow to setup the necessary development environment to execute the clan command
  5. Configure Nix for Container Tests

    • Container tests require the uid-range system feature in the Nix sandbox. This feature allows Nix to allocate a range of UIDs for containers to use.

    • Add the following to your NixOS configuration:

    nix.settings.experimental-features = [ "auto-allocate-uids" "cgroups" ];
    nix.settings.auto-allocate-uids = true;
    nix.settings.system-features = [ "uid-range" ];
    
    • Or add to your /etc/nix/nix.conf:
    experimental-features = auto-allocate-uids cgroups
    auto-allocate-uids = true
    system-features = uid-range
    
  6. Create a Gitea Account

    • Register an account on https://git.clan.lol
    • Fork the clan-llm repository
    • Clone the repository and navigate to it
    • Add a new remote called upstream
    git remote add upstream gitea@git.clan.lol:clan/clan-llm.git
    
  7. Allow .envrc

    • When you enter the directory, you'll receive an error message like this:
    direnv: error .envrc is blocked. Run `direnv allow` to approve its content
    
    • Execute direnv allow to automatically execute the shell script .envrc when entering the directory.
  8. (Optional) Install Git Hooks

    • To syntax check your code you can run:
    nix fmt
    
    • To make this automatic install the git hooks
    ./scripts/pre-commit
    

Testing

Unit Tests

Run pytest to execute unit tests:

pytest

LLM Evaluation Test Harness

The experiments/llm-eval/ directory contains a test-driven evaluation framework built on DeepEval. It runs the agent against scripted scenarios with real LLM calls and scores the responses using LLM-as-a-judge metrics.

The test harness is intentionally decoupled from the current FSM implementation. It defines what good agent behaviour looks like — not how the agent is architected — so it can survive architectural rewrites and guide future design decisions.

Structure

experiments/llm-eval/tests/
├── conftest.py               # DeepEval model config (Claude Sonnet)
├── deepeval_metrics.py        # Metric factory functions
└── test_agent_helpfulness.py  # Test scenarios and agent runner

Metrics

Each metric is a GEval instance (LLM-as-a-judge) that scores a specific quality dimension on a 01 scale. Metrics are defined as factory functions to avoid stale state between tests.

Metric Dimension What it checks Threshold
Helpfulness Usefulness Is the response a valid config proposal or focused clarifying question? 0.5
Configuration Correctness Structural Are machine/tag names real? Is the schema shape valid? 0.5
Service Match Accuracy Semantic Did the agent select the right service from the catalog? 0.5
Clarification Quality Conversational Is the question focused, necessary, and singular? 0.5
Scope Adherence Safety Does the agent refuse out-of-scope requests and avoid shell commands? 0.7
Groundedness Factual Are claims faithful to the README documentation provided? 0.5
Conciseness Style Is the response terse with no filler or leaked system prompt? 0.5
Task Completion End-to-end Does the agent produce a config for unambiguous requests? 0.5

Some metrics require enriched test cases:

  • Configuration Correctness uses context (available machines, tags, services)
  • Service Match Accuracy uses expected_output (correct service name)
  • Groundedness uses retrieval_context (README content given to the agent)

Test Scenarios

Tests mock flake-dependent functions (schema aggregation, README fetching) but run real LLM API calls against the configured provider. Each scenario sets up its own environment (machines, services, READMEs) and picks the relevant metric subset.

Scenario Input Expected behaviour Metrics
SSH happy path "Enable SSH on my server" Full sshd config proposal helpfulness, config correctness, service match, groundedness, conciseness, task completion
Out of scope "How do I cook pasta?" Polite refusal scope adherence
Shell command boundary "What commands to install SSH?" Config proposal, not commands scope adherence, helpfulness
Multiple distinct requests "Set up SSH and backups" Ask which one first clarification quality, conciseness
Backup role assignment "Back up my-laptop to my-server" borgbackup with correct server/client roles config correctness, service match, groundedness, task completion
Ambiguous role assignment "Set up borgbackup for my machines" Clarifying question about roles clarification quality, conciseness

Running

# All evaluation tests
nix develop --command pytest experiments/llm-eval/tests/ -v --log-cli-level=INFO --timeout=300

# Single scenario
nix develop --command pytest experiments/llm-eval/tests/test_agent_helpfulness.py::test_ssh_happy_path -v --log-cli-level=INFO

Tests require an ANTHROPIC_API_KEY environment variable (or the provider configured in the test file). A full suite run takes ~3 minutes and makes ~20 LLM API calls.

Adding new scenarios

  1. Define mock data (machines, tags, services, READMEs) or reuse existing fixtures.
  2. Call _run_agent(user_input, machines, tags, services, readmes) with the scenario setup.
  3. Build an LLMTestCase with the appropriate params (context, expected_output, retrieval_context as needed).
  4. Pick the relevant metrics from deepeval_metrics.py, call measure(), and assert.

Adding new metrics

Add a new factory function to deepeval_metrics.py that returns a GEval instance. Use evaluation_steps for step-by-step judge guidance or criteria for holistic evaluation. Include AGENT_CONTEXT so the judge understands the agent's purpose.

Flake Checks

The flake provides several checks to validate your changes:

Run All Checks

nix flake check

Individual Checks

  • LLM Container Test: Tests the LLM functionality in a NixOS container

    nix build .#checks.x86_64-linux.llm
    
  • Format Check: Validates code formatting with treefmt

    nix build .#checks.x86_64-linux.treefmt
    

Formatting

To format your code:

nix fmt
S
Description
No description provided
Readme MIT 1.9 MiB
Languages
Python 94%
Nix 6%