Skip to content
Mike Crowe edited this page Aug 15, 2026 · 5 revisions

Technology Stack

Analysis Date: 2026-08-15

Languages

Primary:

  • Python 3.12+ — all host CLI logic under src/harnessed/; tested against 3.12 (floor) and 3.13 in CI
  • Shell — container entrypoints (catalog/base/harnessed-start, egress-firewall.sh), recipe install.sh scripts

Secondary:

  • YAML — declarative manifests for agents, recipes, services, and stacks (catalog/**/*.yaml)
  • Dockerfile — container image definitions (catalog/base/Dockerfile.harnessed-*, catalog/agents/*/, catalog/services/*/)

Runtime

Host (CLI)

  • Python 3.12 (floor of requires-python = ">=3.12")
  • Version pinned via mise.toml: UV_PYTHON = "3.12"; venv located per-branch at ~/.local/share/harnessed/venvs/<branch>/.venv
  • Package manager: uv (all uv run --extra dev … invocations; mise exec -- uv … inside containers)
  • Lockfile: uv.lock committed at the repo root; mise.lock per-recipe for binary tools in catalog/recipes/*/

Container (image)

  • Base: ubuntu:24.04 (catalog/base/Dockerfile.harnessed-base)
  • Runtime manager: mise (installed at image build time; shims on PATH)
  • Python 3.12 — mise-managed in image
  • Node 22 LTS — mise-managed; required by pnpm@11
  • pnpm 11 — mise-managed; governs all npm-registry tool installs in images
  • Container runtime: podman (host-native, no daemon socket, no DooD)

Frameworks

Core CLI:

  • typer >= 0.12, < 1.0 — CLI commands (harnessed and harnessed-tools entrypoints in src/harnessed/launcher.py, src/harnessed/cli.py)
  • ruamel.yaml >= 0.18, < 0.19 — round-trip YAML parsing for manifests (src/harnessed/schema.py)
  • rich >= 14, < 15 — terminal output, progress, and tables (src/harnessed/console.py)

Data modelling:

  • Python dataclasses (stdlib) — typed recipe/stack/agent/service objects (src/harnessed/schema.py)
  • No Pydantic; validation is explicit in schema.py via SchemaError, PinValidationError, RecipeLintError

Testing:

  • pytest >= 8 + pytest-cov — test runner; config in pyproject.toml [tool.pytest.ini_options]
  • pytest-randomly >= 3 — randomised test order; determinism is a requirement for count-based assertions
  • hypothesis >= 6 — property-based tests for invariants
  • mutmut — mutation testing (paths_to_mutate = ["src/harnessed/"]); run with HARNESSED_DIR=$PWD
  • diff-cover — coverage restricted to changed lines

Lint / type / static analysis:

  • ruff >= 0.16 — linter (rules: E9 F B S PLE RUF BLE; formatter deliberately NOT enforced); target-version = "py312"
  • pyright 1.1.411 — type checker (typeCheckingMode = "basic"); installed via mise as npm:pyright; extraPaths = ["tests"] for import support
  • shellcheck 0.11.0 — shell script linter; installed via mise
  • pip-audit 2.10.1 — dependency vulnerability scanning

Build:

  • setuptools >= 68 — wheel build; catalog/ shipped inside wheel via src/harnessed/catalog symlink
  • pip-audit — declared as a runtime dep so it is always available for the audit layer

Key Dependencies

Critical:

  • ruamel.yaml — all manifest parsing; round-trip mode preserves comments (important for emit-only assembler)
  • typer — entire CLI surface; extend-immutable-calls covers typer.Option/typer.Argument in ruff config
  • rich — all user-visible output formatting

Infrastructure (tools in container images):

  • mise — manages all versioned tool installs inside images; MISE_GLOBAL_CONFIG_FILE redirected to ~/.local/share/mise/config.toml so config travels with installs in the volume
  • pnpm@11 — all npm-registry installs in images must go through pnpm (supply-chain policy BLD-01/BLD-03); npx and bare npm install -g are linted away
  • uv + osv-scanner — installed via mise use -g in base image; Python toolchain and vulnerability scanning
  • osv-scanner — supply-chain scan run inside the image at build time

Extra mise-managed tools (container, catalog/base/extra-tools.default.txt):

  • bat@0.26.1, eza@0.23.5, sd@1.1.0, dua@2.41.1, gping@1.20.4 — modern CLI replacements
  • jq@1.8.2, jless@0.9.0, glow@2.1.2, hexyl@0.17.0, yq@4.53.3 — data viewing
  • lazygit@0.64.0 — git TUI
  • ast-grep@0.45.1, ruff@0.16.2, stylua@2.5.2, markdownlint-cli2@0.23.2 — dev/code tools

Configuration

Config layering (catalog resolution, searched in order):

  1. ~/.config/harnessed/catalog — user overlay; wins on name clash
  2. catalog/ — repo root (shipped in wheel); default authoring surface
  3. $XDG_DATA_HOME/harnessed/generated/ — machine-minted stacks (--recipe); included only when present

Profile output (never in repo):

  • $XDG_DATA_HOME/harnessed/profiles/<stack>/<harness>/ — assembled profiles (.claude/, .mcp.json, Dockerfile.harnessed-<stack>)

Environment overrides:

  • HARNESSED_DIR — overrides harnessed_home() resolution (required for mutmut runs; see pyproject.toml)
  • HARNESSED_PODMAN=1 — gates podman integration tests in tests/conftest.py
  • UV_PROJECT_ENVIRONMENT, UV_PYTHON — set by mise.toml for per-branch venvs

Secrets management:

  • Credentials referenced, never replicated (CLAUDE.md constraint)
  • 1Password CLI (op) available in base image for SSH commit signing; socket forwarded from host when present
  • Credential mounts handled by src/harnessed/credmounts.py
  • No .env files; no hardcoded secrets in any catalog asset

Pin policy:

  • Every download pinned: Dockerfile base tags, GitHub Actions to commit SHAs, mise tool versions, recipe install.refs: blocks
  • schema.PinValidationError rejects --branch main/master, :latest, @latest in recipe Dockerfiles at parse time
  • schema.parse_extra_tools rejects unpinned extra-tools.txt entries before podman is invoked

Clone this wiki locally