Skip to content
Mike Crowe edited this page Jul 31, 2026 · 5 revisions

Technology Stack

Analysis Date: 2026-07-31

Languages

Primary:

  • Python 3.12+ — all host CLI logic (src/harnessed/). Minimum enforced via requires-python = ">=3.12" in pyproject.toml.

Secondary:

  • Bash — recipe install scripts (catalog/recipes/*/install.sh), container entrypoints (catalog/base/harnessed-start, catalog/base/egress-firewall.sh), and all setup.script bodies in the catalog. Linted by validate_install_script / validate_setup_script in src/harnessed/schema.py.
  • Dockerfile — agent base images (catalog/base/Dockerfile.harnessed-*) and recipe derived images. Pin-validated by validate_pin in src/harnessed/schema.py.
  • JavaScript/TypeScript — the hatago MCP hub (@drmikecrowe/hatago-mcp-hub) and any recipe that installs an npm: tool. Not authored in this repo; pulled from npm registry at container build time.

Runtime

Host:

  • Python 3.12 (managed via astral-sh/setup-uv in CI, or host Python for local dev)
  • Package manager: uvuv sync --extra dev installs the project + dev deps. uv run pytest executes tests.
  • Lockfile: uv.lock (present, tracks all transitive deps)
  • mise.toml sets UV_PROJECT_ENVIRONMENT to ~/.local/share/harnessed/venvs/<branch>/.venv — one venv per git branch, stored outside the repo to prevent corruption by container bind-mounts.

Container (podman pod, built by harnessed):

  • Base image: ubuntu:24.04
  • Container user: harnessed (UID 1000, renamed from ubuntu; --userns=keep-id maps host user)
  • Tool version manager: mise (baked into the base image)
  • Container runtime tools (installed in catalog/base/Dockerfile.harnessed-base via mise use -g):
    • node@22 (LTS) + pnpm@11 (npm package manager; mise settings set npm.package_manager pnpm routes all npm: installs through pnpm)
    • npm@11.18.0 (self-upgraded from node-bundled 10.9.8 to fix HIGH CVEs in transitive deps)
    • python@3.12
    • bun@1.2
    • rust@1.87
    • go@1.24
    • fd, ripgrep (file search tools)
    • gh@2.96.0 (GitHub CLI)
    • uv, osv-scanner (added in scanner layer)
  • Container runtime: podman — driven from the host via subprocess in src/harnessed/launcher.py. No daemon socket; rootless podman only (--userns=keep-id).

Frameworks

Core:

  • Typer >=0.12,<1.0 — CLI framework for the harnessed and harnessed-tools entrypoints. Defined in pyproject.toml; app objects live in src/harnessed/launcher.py (app) and src/harnessed/cli.py.

Testing:

  • pytest >=8 — test runner. Config: [tool.pytest.ini_options] in pyproject.toml, testpaths = ["tests"]. Run: mise exec -- uv run pytest -q.
  • pytest-cov — coverage plugin (no explicit config block; coverage not enforced in CI).
  • jsonschema >=4 — JSON Schema validation used in tests and catalog scanning.

Build/Dev:

  • setuptools >=68 — build backend (pyproject.toml [build-system]). Ships the catalog/ tree inside the wheel via the src/harnessed/catalog symlink + package-data = ["catalog/**/*"].
  • mise (host) — manages the per-branch .venv, provides mise exec -- wrapper. Tasks defined in mise.toml: docs, docs-check, pull-and-build.
  • uv tool install (host) — installs harnessed as an editable uv tool during dev (mise tasks.pull-and-build runs uv tool install --editable .).

Key Dependencies

Critical (core functionality depends on these):

  • ruamel.yaml>=0.18,<0.19 — YAML parser used throughout for catalog loading (schema.py, assemble.py, update.py). Chosen for round-trip comment preservation and strict typing over PyYAML.
  • rich>=14,<15 — all terminal output, warnings, and the _WarnCountingConsole subclass in launcher.py that counts warning lines before os.execvp hands off the TTY.
  • typer>=0.12,<1.0 — CLI command tree, argument parsing, and help text. Both entrypoints (harnessed, harnessed-tools) are Typer apps.
  • pip-audit==2.10.1pinned exactly (not a range). Baked into the base image via uv tool install pip-audit and invoked by the harnessed-scan script inside every built stack image for credential-free Python supply-chain checks.

Infrastructure:

  • jsonschema>=4 — validates catalog YAML against JSON Schema definitions under schemas/. Used in tests and in cli.py (harnessed-tools scan).

Container-side (baked into harnessed-base, not Python deps):

  • @drmikecrowe/hatago-mcp-hub@0.1.2 — the in-container MCP aggregator hub (fork of @himorishige/hatago-mcp-hub). Installed via pnpm add -g in Dockerfile.harnessed-base. See INTEGRATIONS.md.
  • snyk — token-gated supply-chain scanner, baked via pnpm add -g snyk.
  • socket@1.1.143 — token-gated supply-chain scanner, baked via pnpm add -g socket@1.1.143. Pinned to v1 to lock supply-chain defaults.
  • osv-scanner — credential-free vuln scanner, baked via mise use -g osv-scanner.

Configuration

Environment and secrets:

  • Host-level secrets live in ~/.config/harnessed/.env.schema, resolved via varlock (varlock load --format json) using op:// 1Password references. See .env.schema.example at the repo root for the DSL and all supported keys.
  • Per-project secrets live in <project>/.env.schema (varlock) or <project>/.env (raw key=value). Both are layered by _resolve_launch_secrets() in launcher.py and injected into podman via --env-file (temp files unlinked after launch — secrets never linger on disk).
  • When varlock is absent, harnessed falls back to reading ~/.config/harnessed/.env literally.

Key config files and their roles:

File Role
pyproject.toml Python project metadata, pinned runtime deps, dev extras, setuptools package-data config, pytest testpaths
mise.toml Per-branch venv path (UV_PROJECT_ENVIRONMENT), dev task definitions (docs, pull-and-build)
uv.lock Full dependency lockfile (all transitive deps; committed)
~/.config/harnessed/.env.schema User-global secrets (SNYK_TOKEN, SOCKET_SECURITY_API_KEY, corp proxy CA path, etc.)
~/.config/harnessed/catalog/ User overlay catalog — wins over repo catalog/ on name clash
catalog/base/extra-tools.txt Staged at build time from extra-tools.default.txt + user edits; additional mise tools baked into harnessed-base
catalog/base/pnpm/config.yaml Managed pnpm supply-chain config; COPY'd into the base image before any pnpm invocation
$XDG_DATA_HOME/harnessed/profiles/ Generated (assembled) stack profiles — never in the repo

Catalog search path (two roots, first wins on clash):

  1. ~/.config/harnessed/catalog/ (user overlay)
  2. catalog/ (repo, shipped inside the wheel via src/harnessed/catalog symlink)

paths.harnessed_home() resolves through the symlink to give podman a real directory as the build context — podman rejects context symlinks that escape the context root.

Volume and profile naming:

  • Assembled profiles: $XDG_DATA_HOME/harnessed/profiles/<stack>/<harness>/
  • Tool volumes: harnessed-tools-<harness>-<stack> (~/.local inside container)
  • Config volumes: harnessed-cfg-<harness>-<stack> (~/.claude inside container)
  • pnpm metadata cache: harnessed-pnpm-meta (podman named volume, --sharing=shared)
  • uv cache: harnessed-uv (podman named volume)
  • mise cache: harnessed-mise (podman named volume, --sharing=locked)

Clone this wiki locally