Skip to main content

spaex — reproducible coding harnesses for any repo and development environment

Status: 5.0.0 (external skills remain standard skill content, declared as structured external_skills metadata and installed through an explicit consumer-selected adapter, not a molecule hook; see Spec 018). Portmanteau of spec and haex. See docs/adr/0011-rename-to-spaex.md for the rename decision and specs/014-rename-to-spaex/ for the full spec.

What it is

spaex composes a coding harness for a single repo out of reusable pieces (MCPs, constitutions, slash commands, dev-environment files, and external skill references, collectively "molecules"). You declare which molecules you want in .spaex/manifest.json. All spaex-owned project state lives under .spaex/; spaex install writes participating runtime files deterministically, pinned by SHA. Two consecutive spaex install runs on unchanged inputs produce byte-identical output.

What you can do today

  • spaex add <source-url> <molecule-ids...>: adopt one or more molecules from a publisher repo into .spaex/manifest.json and install them in one invocation.
  • spaex remove <molecule-ids...>: retract one or more molecules from .spaex/manifest.json and re-run install (files that only the retracted molecule contributed are deleted).
  • spaex install: publish adopted molecules atomically into their participating roots. Writes .spaex/install.lock.
  • spaex constitution show: print the effective spaex constitution to stdout, assembled from adopted molecules per install.lock.
  • spaex status: summarize the repository's active composition — every pinned/installed molecule, what its atoms materialized into, the composed constitution, and any drift between the manifest, the install lock, and the constitution. Read-only.
  • spaex trace <path>: print which molecule(s) wrote a given file, or every recorded file under a directory. Read-only.
  • spaex skills install: activate the consumer's skill_installation policy for pending external_skills references, prompting once (interactively) if none is persisted yet, then running the selected adapter. spaex install never does this on its own.
  • spaex skills configure: change the persisted skill_installation policy (mode, adapter, scope, agents) without installing anything or removing already-installed skills.
  • spaex add --local … / spaex remove --local …: adopt or retract molecules for this checkout only (see Local scope).

Local scope

Every adoption above is project scope: recorded in the committed .spaex/manifest.json and shared with the whole team. spaex add --local <source-url> <molecule-ids> adopts molecules for one developer in one checkout instead (Spec 029, ADR 0027):

  • The adoption lives in the uncommitted overlay .spaex/local/manifest.json; .spaex/manifest.json and every project output stay byte-identical.
  • Nothing local reaches git, and no committed ignore file is edited. Files under .spaex/local/ are hidden by a self-ignoring .spaex/local/.gitignore. Local files elsewhere (exclusive atoms, files a local hook or the local Spec Kit run created) are listed in a spaex-owned, per-worktree block in git's info/exclude.
  • Local behavior fragments compose into .spaex/local/constitution.md, which agents read after .spaex/constitution.md (bootstrap block version 3: rerun spaex install --global). Project rules prevail on conflict.
  • Project scope always wins. A molecule already adopted in project scope cannot be adopted locally, and a later project adoption shadows the local entry. A local write to a project-owned or git-tracked path, including one made by a local install hook, refuses the install and is reverted.
  • spaex status, spaex trace and spaex constitution show label every molecule, file and section with its scope (status/trace JSON format_version: 2).

info/exclude is shared by all worktrees of a clone, so a local entry from one worktree also hides an untracked file at the same path in the others. A spaex version without local scope drops .spaex/local/ on its next install; recreate it with spaex add --local.

Molecule install-hooks

A molecule may declare an optional install_hook in its manifest.json. spaex install invokes it as a normal subprocess (arbitrary code, consumer-user permissions, full environment inheritance) after any required atom materialization and before publishing the install.lock generation; hook-only molecules run without atom materialization. Per-molecule on_failure: "abort" | "warn" selects between transaction rollback and continue-with-hook_status-recorded. Consumers can opt out for a single invocation via spaex install --no-install-hooks (or spaex add --no-install-hooks / spaex remove --no-install-hooks; both also accept --speckit-agents and --no-speckit-integrations for their internal install). See docs/install-hooks.md for the full contract: declaration schema, execution semantics, the four failure kinds, idempotency, non-reversibility, and the trust model.

Atom-category conventions

The v4 molecule-manifest schema treats atoms{} as an open Dict[str, List[str]] map. Publishers pick category names by convention. Common categories today: behavior, slash_commands, agents, mcps. The legacy constitution category remains readable for older molecules but is not the canonical way to contribute policy.

Environment-config files (flake.nix, Dockerfile, devcontainer.json, .envrc, shell.nix, etc.) can be declared under any category name a publisher chooses. The retired skill and skills categories are the one exception: a skill may still live in the publisher repository, but is declared as a structured external_skills reference and installed only through an explicit, consumer-selected adapter (spaex skills install), never a molecule install_hook. Spec 014 makes no other naming commitment here; multi-environment vocabulary (dev/staging/prod), consumer-side selection, and orchestration verbs are the scope of Spec 015 (planned; see docs/plans/2026-09-07-slot-015-multi-environment-placeholder.md).

Activating an adopted Nix devShell

A molecule that delivers flake.nix/.envrc under an environment-config category (e.g. com.github.haexmas.atoms.nix-devshell-base) only writes those files; it does not — and should not — provision Nix or direnv themselves, since that needs interactive root access spaex should not attempt unattended. Three preconditions are easy to miss on a fresh machine after spaex install:

  1. Flakes must be enabled. A default Nix install has nix-command and flakes behind the experimental-features flag — without it, nix develop/direnv's use flake fail with experimental Nix feature 'nix-command' is disabled. Enable it per-user, no root needed:

    mkdir -p ~/.config/nix
    echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
    
  2. direnv itself must be installed (Nix does not bring it in): sudo pacman -S direnv, sudo apt install direnv, brew install direnv, or nix profile install nixpkgs#direnv.

  3. direnv must be hooked into your shell, then open a new shell:

    # bash (~/.bashrc) / zsh (~/.zshrc)
    eval "$(direnv hook bash)"   # or: zsh
    
    # fish (~/.config/fish/config.fish)
    direnv hook fish | source
    

With all three in place, cd into the consumer repo and run direnv allow once; direnv builds the devShell and loads its tools into PATH automatically on every subsequent cd. Without direnv, nix develop (with flakes enabled per step 1) drops into an equivalent shell manually.

Install

Once published to PyPI (upcoming with the v5.0.0 tag):

pipx install spaex

From a local checkout (development):

git clone https://github.com/haexmas/spaex.git
cd spaex
pip install -e '.[dev]'

Requires Python 3.14.x and Git 2.30+ on $PATH. spaex includes uv for isolated, pinned CLI provisioning; jsonschema and pyyaml are the other runtime dependencies.

External skills

Molecules may keep standard SKILL.md directories in the publisher repository, including haexmas/atoms. They are not copied by spaex's atom materializer. A molecule declares them as structured external_skills metadata: a repository, a full 40-character revision SHA, and a repository-relative skill path (co-located with the molecule or elsewhere). No install_hook is required or used for skill installation.

"external_skills": [
  {
    "repository": "https://github.com/haexmas/atoms",
    "revision": "0123456789abcdef0123456789abcdef01234567",
    "path": "skills/example-skill"
  }
]

Normal spaex install never installs or materializes a referenced skill; its structured declaration remains in the pinned molecule manifest as metadata, reported as pending. Installation is a separate, explicit, consumer-selected operation: run spaex skills install to choose a mode, adapter, scope, and agents (persisted under skill_installation in .spaex/manifest.json) and invoke the adapter, or spaex skills configure to change that policy without installing. See the Spec 018 design and its consumer policy contract for the full command and policy semantics.

After spaex install completes, .spaex/install.lock is present and byte-identical across two consecutive runs.

The spaex vocabulary at a glance

  • Compound (.spaex/manifest.json.compounds[]): a (source, revision) pair with a list of adopted molecules[]. The consumer's allowlist.
  • Molecule: a published, reverse-DNS-identified bundle that a publisher declares in its root manifest.json under molecules{}.
  • Atom: a single delivered file, grouped under a category key in a molecule's manifest.json atoms{} map.

This vocabulary (compounds -> molecules -> atoms) is the clean-cut spaex format. There is no migration command or legacy manifest fallback.

Multi-device delegation

Not part of spaex. That is a separate project: holzi (Nostr + iroh + MCP agent plane, single-user first). spaex is deliberately scoped to one repo, one device.

Environment variable

spaex honors $SPAEX_STATE for the per-invocation publisher-clone state directory. Unset falls back to ~/.local/share/spaex/.

Documentation

  • Every spec under specs/ is authoritative for the mechanism it introduces.
  • Design plans under docs/plans/ capture pre-spec requirements.
  • Architecture Decision Records under docs/adr/ record decisions that reshape the system.
  • The constitution at .specify/memory/constitution.md is the non-negotiable invariant set every spec, plan, and implementation MUST respect.

Metadata

Release files for spaex 5.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spaex 5.4.0
File Size Uploaded
spaex-5.4.0.tar.gz 178.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for spaex 5.4.0
File Interpreter ABI Platform
spaex-5.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 399.0 kB

Release files / spaex-5.4.0.tar.gz

Download URL spaex-5.4.0.tar.gz
Size 178.5 kB
Tags Source
SHA-256 checksum
How to use checksums
c63158271602996ef0a91966f03c55db9d31d8ea0ea84033dfc53b100ee886bc
BLAKE2b-256 checksum
How to use checksums
e9f42ab42cb54ef05aaf91c119be581b3547e213e704118bc12688869b32a9e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / spaex-5.4.0-py3-none-any.whl

Download URL spaex-5.4.0-py3-none-any.whl
Size 220.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11cef3210ba9da082d927aa7e6528f45c48bb78faa731e8c1ebcff934bd7cd70
BLAKE2b-256 checksum
How to use checksums
16d7c63e766f69be038a8fa4124e844c11aa9cb6e6e9a2fba3d0f0a8d3d067f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

5.4.1

2 release files

This release

5.4.0 This release

2 release files

5.3.2

2 release files

5.3.1

2 release files

5.3.0

2 release files

5.2.3

2 release files

5.2.2

2 release files

5.2.0

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.3.0

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page