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.jsonand install them in one invocation.spaex remove <molecule-ids...>: retract one or more molecules from.spaex/manifest.jsonand 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 perinstall.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'sskill_installationpolicy for pendingexternal_skillsreferences, prompting once (interactively) if none is persisted yet, then running the selected adapter.spaex installnever does this on its own.spaex skills configure: change the persistedskill_installationpolicy (mode, adapter, scope, agents) without installing anything or removing already-installed skills.
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). 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:
-
Flakes must be enabled. A default Nix install has
nix-commandandflakesbehind theexperimental-featuresflag — without it,nix develop/direnv'suse flakefail withexperimental 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
-
direnv itself must be installed (Nix does not bring it in):
sudo pacman -S direnv,sudo apt install direnv,brew install direnv, ornix profile install nixpkgs#direnv. -
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 adoptedmolecules[]. The consumer's allowlist. - Molecule: a published, reverse-DNS-identified bundle that a publisher declares in its root
manifest.jsonundermolecules{}. - Atom: a single delivered file, grouped under a category key in a molecule's
manifest.jsonatoms{}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.
Release files for spaex 5.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| spaex-5.2.0.tar.gz | 161.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spaex-5.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 359.6 kB
Release files / spaex-5.2.0.tar.gz
| Download URL | spaex-5.2.0.tar.gz |
|---|---|
| Size | 161.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0ce0cee3275e2c0546409e2f79ee6405b07af43c2f14f7ad6e2a278a5fc25176
|
|
BLAKE2b-256 checksum How to use checksums |
d0dc175bff388f0d914f996d4f771d3e0badaeae7a114a7adc694f4d01820a66
|
| 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 Sep 24, 2026.
Transparency logRelease files / spaex-5.2.0-py3-none-any.whl
| Download URL | spaex-5.2.0-py3-none-any.whl |
|---|---|
| Size | 198.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5b957d2b61bc1b6395988be7580d13a4e19789479571081ac84ce284496a9863
|
|
BLAKE2b-256 checksum How to use checksums |
31c8170cade786d3bcb96017fd7c37995f44c7e568521c82bdb55c700e92d62d
|
| 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 Sep 24, 2026.
Transparency log