Skip to content

STRUCTURE

Mike Crowe edited this page Jul 31, 2026 · 4 revisions

harnessed — Directory Structure

Analysis Date: 2026-07-31

The authoritative vocabulary (agent/recipe/service/stack/catalog) is in ARCHITECTURE.md at the repo root. The data-flow and key abstractions are in docs/codebase/ARCHITECTURE.md. This document covers where things live, naming conventions, and where to add new code.

Directory Layout

harnessed/                              # bare repo: .bare/ + main/ + .claude/worktrees/
├── pyproject.toml                      # Python project config; entry points: harnessed, harnessed-tools
├── src/
│   └── harnessed/                      # the application package
│       ├── __init__.py                 # package version (__version__)
│       ├── launcher.py                 # `harnessed` CLI (Typer) — 7 000+ lines
│       ├── cli.py                      # `harnessed-tools` CLI (argparse) — assembler + test entrypoints
│       ├── schema.py                   # typed models + catalog parsing (Recipe/Stack/ServiceDef/Agent)
│       ├── assemble.py                 # emit-only assembler: stack + recipes → committed profile
│       ├── emit.py                     # file writers (mcp.json, hatago config, Dockerfile, identity)
│       ├── paths.py                    # single source of truth for all path resolution
│       ├── synclinks.py                # LinkSyncer: fan recipe skills/commands/rules into profile
│       ├── persist.py                  # global persist allowlist + ownership guard
│       ├── persist_gc.py               # persist dir listing + prune
│       ├── scan.py                     # supply-chain scan gate (osv-scanner CVSS parsing)
│       ├── capability.py               # capability test: launch stack --fresh, diff oracle
│       ├── report.py                   # render capability test results (rich table or JSON)
│       ├── staleness.py                # build-stamp detection for stale profiles
│       ├── update.py                   # `harnessed update`: find + bump outdated pins
│       └── catalog -> ../../catalog    # symlink; packaged into wheel; real dir in checkout
│
├── catalog/                            # authored content — SHIPPED INSIDE THE WHEEL
│   ├── agents/
│   │   ├── claude/agent.yaml           # Claude Code harness
│   │   ├── omp/agent.yaml              # OpenMachinePro harness
│   │   ├── opencode/agent.yaml
│   │   ├── antigravity/agent.yaml      # agy (Gemini-derived)
│   │   └── codex/agent.yaml
│   ├── base/
│   │   ├── Dockerfile.harnessed-base   # shared base image (hatago baked in-container)
│   │   ├── Dockerfile.harnessed-claude # per-harness agent layers
│   │   ├── Dockerfile.harnessed-omp
│   │   ├── Dockerfile.harnessed-opencode
│   │   ├── Dockerfile.harnessed-antigravity
│   │   ├── Dockerfile.harnessed-codex
│   │   ├── egress-firewall.sh          # egress firewall script (runs in-container post-start)
│   │   ├── extra-tools.default.txt     # seeded into ~/.config/harnessed/extra-tools.txt at first build
│   │   ├── harnessed-scan              # in-container scan entrypoint
│   │   ├── harnessed-start             # container start script (runs hatago, then waits)
│   │   └── pnpm/config.yaml            # pnpm policy config baked into the base image
│   ├── recipes/
│   │   ├── <name>/                     # single recipe
│   │   │   ├── recipe.yaml             # manifest (required)
│   │   │   ├── Dockerfile              # (optional) system-layer steps only
│   │   │   ├── install.sh              # (optional) install script (both modes)
│   │   │   ├── setup.sh                # (optional) setup script (runtime phase)
│   │   │   ├── skills/<skill>/         # (optional) fanned into .claude/skills/<skill>/
│   │   │   ├── commands/<cmd>/         # (optional) fanned into .claude/commands/<cmd>/
│   │   │   └── rules/<rule>/           # (optional) fanned into .claude/rules/<rule>/
│   │   └── <family>/                   # recipe family (no recipe.yaml at this level)
│   │       └── <variety>/              # variety = a complete recipe dir (recipe.yaml etc.)
│   ├── recipes.backlog/                # planned recipes (not yet active; not in the catalog search path)
│   ├── services/
│   │   ├── beads-server/               # dolt sql-server (scope: project, unix socket)
│   │   ├── agentmemory/                # agentmemory MCP service (scope: global)
│   │   ├── gbrain/                     # gbrain MCP service
│   │   └── ping/                       # test service
│   └── stacks/
│       ├── gsd-core_repowise/          # repowise + gsd-core composition
│       ├── hostbeads/                  # beads/team with host-run backend
│       ├── hostbeads_stealth/
│       ├── hostmcp/
│       ├── hostspike/
│       └── openbrain-example/
│
├── catalog-local/                      # gitignored; DX symlinks → ~/.config/harnessed/catalog
│                                       # (never inside catalog/ — setuptools would package them)
│
├── tests/
│   ├── conftest.py                     # FORCE_COLOR pop (must run at module import time)
│   ├── fixtures/
│   │   ├── recipes/                    # minimal recipe dirs for schema/assembly tests
│   │   ├── services/                   # minimal service dirs
│   │   └── stacks/                     # minimal stack dirs
│   └── test_*.py                       # ~75 test modules (see Key Locations below)
│
├── docs/
│   ├── codebase/                       # generated maps (this file; regenerate with /map-codebase)
│   └── (wiki clone, gitignored)        # docs/guides/*, docs/harnessed-design.md live only in main/
│
└── .claude/
    ├── worktrees/                      # task worktrees (created by EnterWorktree)
    ├── rules/                          # project-level Claude rules
    └── settings.json                   # project Claude Code settings

Key Locations

Entry Points

Entrypoint File Function
harnessed CLI src/harnessed/launcher.py main()app() (Typer)
harnessed-tools CLI src/harnessed/cli.py main()

CLI Commands (launcher.py — all are @app.command() functions)

Command Function What it does
build build() Assemble + build images + populate volumes + scan
launch launch() Start pod, attach harness (container backend)
host-run host_run() Materialize home, exec harness (host backend)
test test_stack() Run capability test oracle
new new_stack() Scaffold a new stack.yaml
install install_stack() Install a stack from the user overlay
uninstall uninstall_stack() Remove an installed stack
list list_stacks() Enumerate all visible stacks
stop stop() Stop a running instance
rm remove() Remove a stopped instance
prune prune() Remove all stopped instances for a stack
clean clean_profiles() Remove emitted profile dirs
update update_pins() Find + bump outdated pins
scan scan() Supply-chain scan a built stack
rescan rescan() Nightly online re-scan of all built images
svc svc() Service management subcommands
aws-sso aws_sso() AWS SSO ECS credential forwarding
host-gc host_gc() Garbage-collect stale host homes
volume-gc volume_gc() Garbage-collect orphaned named volumes

Catalog Resolution

Function File Purpose
catalog_roots() paths.py:140 Returns [user_overlay, harnessed_home/catalog]
find_in_catalog(kind, name) paths.py:173 Resolve a single entry across roots
list_catalog(kind) paths.py:198 Enumerate all entries across roots (deduped)
catalog_relpath(name) paths.py:154 Map a ref (beads/stealth) to on-disk path

Assembly Pipeline

Function File Purpose
assemble() assemble.py:106 Main entry: parse → validate → emit → stamp
_merge_servers() assemble.py:68 Collect MCP servers; collision detection
_resolve_service_servers() assemble.py:84 Resolve service: refs → URLs
compute_recipe_hash() assemble.py:50 Image label: SHA-256 of stack.yaml + recipe dirs

Schema / Parsing

Function File Purpose
load_stack_with_recipes() schema.py Top-level loader: stack + full recipe closure
load_stack() schema.py Parse a single stack.yaml (with extends: resolution)
load_recipe() schema.py Parse a single recipe.yaml
load_service() schema.py Parse a single service.yaml
load_agent() schema.py Parse a single agent.yaml
expected_capabilities() schema.py Build the capability test oracle
validate_pin() schema.py Reject floating refs in Dockerfiles
validate_no_raw_npm() schema.py Reject raw npm/npx

Emission

Function File Purpose
reset_profile() emit.py:36 Wipe + recreate profile dir
write_mcp_json() emit.py:48 .mcp.json → single hatago endpoint
write_hatago_config() emit.py hatago.config.json with all recipe servers
write_derived_dockerfile() emit.py Concatenated Dockerfile for the stack image
write_settings_json() emit.py Floor settings.json (permissions, hooks)
write_claude_md() emit.py:61 .claude/CLAUDE.md from stack.instructions
LinkSyncer.fan() synclinks.py Copy skills/commands/rules into .claude/

Path Resolution

Function File Purpose
harnessed_home() paths.py:76 The dir containing catalog/ (repo root or wheel)
profile_dir(stack, harness) paths.py:248 $XDG_DATA_HOME/harnessed/profiles/<stack>/<harness>/
host_home(stack, harness) paths.py:263 $XDG_DATA_HOME/harnessed/home/<stack>/<harness>/
instance_name(stack, harness, path) paths.py:313 harnessed-<harness>-<stack>-<hash8>
persist_workspace_dir() paths.py:434 Per-worktree persist dir
persist_project_dir() paths.py:465 Cross-worktree persist dir (keyed by git-common-dir)
persist_in_repo_dir() paths.py:444 In-repo persist anchor (checkout root)
git_common_dir() paths.py:349 git common dir for a project path
bare_worktree_container() paths.py:369 Parent of a bare repo's git-common-dir

Volume and Service Management (launcher.py)

Function Purpose
_ensure_stack_volumes() Create + fingerprint-gate config/tools volumes; run installs
_run_container_installs() Run each recipe's install.sh in a throwaway install container
_ensure_services() Start global/project service containers (idempotent)
_persist_mounts() Build -v args for recipe persist entries
svc_socket_env() Build HARNESSED_<SVC>_SOCKET env vars for a stack
svc_client_env() Build client_env vars from project-scoped services

Host-Run Backend (launcher.py)

Function Purpose
_launch_host() Orchestrate host-run launch
_materialize_host_home() Fingerprint-gated profile → host_home materialization
_host_run_installs() Run install.sh scripts on host filesystem
_host_run_inits() Source init.run snippets; propagate exported env
_host_run_setups() Run setup.sh scripts on host
_share_host_claude_state() Symlink history/sessions/memories to shared store

Tests (tests/)

File(s) What they cover
test_emit.py Profile file emission correctness
test_schema.py, test_schema_thread_safety.py Recipe/stack/service parsing, validation, thread safety
test_launcher_build.py, test_launcher_install.py Build pipeline (mocked podman)
test_launcher_scan.py Scan gate logic
test_launch_host.py, test_launch_parity.py Host-run backend; parity between container and host
test_install_script.py Install env precedence; script validation
test_install_migration_*.py Migration checks for content/node/system/uv tools
test_persist_mounts.py, test_persist_allowlist.py, test_persist_gc.py Persist system
test_project_scoped_services.py, test_service_refs.py Service management
test_folder_env_contract.py Folder-env variable completeness
test_scan.py, test_scan_coverage_reporting.py osv-scanner gate logic
test_staleness.py Build stamp detection
test_stack_extends.py Stack inheritance resolution
test_paths.py, test_harnessed_home.py Path resolution
test_synclinks.py LinkSyncer collision detection + fan behavior
test_capability_tests.py Capability test oracle
test_catalog_json_schemas.py JSON schema validation of catalog entries
test_wheel_packaging.py Wheel contains catalog; no host-local leaks
test_update_*.py Pin-update logic

Naming Conventions

Files

  • Python modules: snake_case.py (all under src/harnessed/)
  • Catalog manifests: recipe.yaml, stack.yaml, service.yaml, agent.yaml
  • Dockerfiles in catalog/base/: Dockerfile.harnessed-<name> (base, per-harness, and derived stacks share this naming)
  • Recipe Dockerfiles: Dockerfile (one per recipe dir, no suffix)
  • Install scripts: install.sh
  • Setup scripts: setup.sh
  • Test files: test_<module_or_feature>.py

Catalog Entries

  • Recipe names: lowercase kebab-case (gsd-core, codebase-memory-mcp)
  • Recipe variety refs: <family>/<variety> (beads/stealth, beads/team)
  • Stack names: <recipes_joined_by_underscore> with hyphens within a recipe name (gsd-core_repowise); stack names must not match any harness name
  • Service names: lowercase kebab-case (beads-server, agentmemory)
  • Agent/harness names: single token (claude, omp, opencode, antigravity, codex)

Runtime Identifiers

  • Podman instance name: harnessed-<harness>-<stack>-<sha1[:8](project_path)> (see paths.instance_name)
  • Config volume: harnessed-cfg-<harness>-<stack>
  • Tools volume: harnessed-tools-<harness>-<stack>
  • Service container: harnessed-svc-<service_name> (global) or harnessed-svc-<service_name>-<project_hash> (project-scoped)
  • Derived image: harnessed-<harness>-<stack>:latest
  • Base image: harnessed-base:latest
  • Agent image: harnessed-<harness>:latest

Where to Add New Code

New Recipe

  1. Create catalog/recipes/<name>/recipe.yaml. Minimum viable manifest:
    name: <name>
  2. Add optional files: Dockerfile, install.sh, setup.sh, skills/, commands/, rules/.
  3. Reference from a stack.yaml: recipes: [<name>].

For a recipe variety: catalog/recipes/<family>/<variety>/recipe.yaml. The family dir must have no recipe.yaml of its own.

New Stack

  1. Create catalog/stacks/<name>/stack.yaml:
    name: <name>
    recipes: [recipe-a, recipe-b]
  2. Optionally use extends: <parent-stack> to inherit another stack's content.

New Agent Harness

  1. catalog/agents/<name>/agent.yamlname: and image:.
  2. catalog/base/Dockerfile.harnessed-<name> — agent-specific image layer.
  3. src/harnessed/launcher.py — add to _HARNESS_ATTACH_CMD dict.
  4. src/harnessed/schema.py — add to HARNESS_CONFIG_DIR dict.
  5. If the harness uses a non-Claude identity format, add an elif harness == "<name>": branch in assemble.assemble() and a writer in emit.py.

New Service

  1. catalog/services/<name>/service.yamlname:, port:, scope:, data:.
  2. catalog/services/<name>/Dockerfile.
  3. Reference in a recipe via mcp.servers[].service: <name> or in a stack via services: [<name>].

New CLI Command

Add @app.command("<name>") + decorated function to src/harnessed/launcher.py for a top-level harnessed command. Add a subparser entry to _build_parser() in src/harnessed/cli.py for a harnessed-tools subcommand.

New Persist Scope or Location

  1. Extend _PERSIST_VALID_SCOPES or _PERSIST_VALID_LOCATIONS in src/harnessed/schema.py.
  2. Add path resolution in src/harnessed/paths.py (a new persist_*_dir() function).
  3. Add mount-building logic in launcher._persist_mounts().

New Test

Add tests/test_<feature>.py. Fixtures shared across tests go in tests/conftest.py. Minimal fake catalog trees (recipe/stack/service dirs) go under tests/fixtures/. Podman-dependent tests are skipped unless HARNESSED_PODMAN=1 is set in the environment.

Clone this wiki locally