A bundled copier conductor skill — the bailiff fills in the paperwork; copier makes the copies.
Project description
bailiff
An agentic conductor for copier. The bailiff fills in the paperwork; copier makes the copies.
Status: active development. Design is captured in
docs/decisions/; implementation is spec-driven (SpecKit).
What bailiff is
copier is a superb deterministic scaffolding engine: it renders templates from answers, pins templates to git refs, and reproduces a config-consistent project from a committed answers file. What copier has no concept of is the agentic layer — choosing which templates to apply, authoring the answers for you, and wiring up AI-agent tooling (APM packages, MCP servers, SpecKit, steering/ADR docs).
bailiff is exactly that layer, and nothing more:
- an init-time agent that selects modules from a user-owned catalog and authors the per-module answer files (the inputs);
- a thin orchestrator for multi-module enablement + dependency ordering;
- the agentic-ecosystem wiring (APM / MCP / SpecKit / ADR) that has no off-the-shelf analog.
What bailiff is NOT
- Not a scaffolder. copier renders; bailiff only decides and fills in.
- Not in the reproduce path. Reproduce is agent-free:
bailiff reproducereplays the committed answers at the recorded commit (recopy --vcs-ref=:current: --defaults --overwrite), never a bare recopy that would silently upgrade — run by a human or CI with no agent. Reproduce also works with copier alone (no bailiff):copier recopy --vcs-ref=:current: --defaults --overwriteper answers file. - Not a template hub. The catalog is user-owned; bailiff works against any
copier templates you point it at. See
0002.
How it works (one line)
The agent (skill) inspects a copier template, collects answers from the user,
records trust consent if the template executes code, and hands the frozen answers
to the deterministic CLI — bailiff on PyPI (uvx bailiff) — which drives copier to
render and initialize the project. Any template that takes actions (its _tasks /
migrations / jinja extensions) runs only from a source the user has trusted —
copier's settings.yml trust list, recorded by an explicit consent step, never a
blanket unsafe=True. See 0003
and 0001.
Install
bailiff is distributed as an APM skill package — installable into any
macOS/Linux/WSL project that uses Claude Code or Codex. There is no PyPI
bailiff package; the skill bundles its own Python modules.
Install into a Claude Code project
# Add the bailiff marketplace (one-time per machine / project):
apm marketplace add bailiff-io/bailiff
# Install the bailiff skill:
apm install bailiff
# Check that runtime deps (copier, pyyaml, packaging, tomli-w) are present:
uvx bailiff doctor
Install into a Codex project
apm marketplace add --marketplace=codex bailiff-io/bailiff
apm install --marketplace=codex bailiff
Runtime dependencies
bailiff checks its deps at startup. If any are missing or version-incompatible
(esp. copier>=9.16,<10), it prints an environment-aware install suggestion
and exits cleanly — no traceback. Run bailiff.py doctor for an explicit check.
Quick install (choose one):
uv pip install copier pyyaml packaging tomli-w # uv (recommended)
pip install copier pyyaml packaging tomli-w # pip
brew install copier && pip install pyyaml packaging tomli-w # macOS brew (copier only via brew)
Or use uvx bailiff <verb> — uvx resolves the published wheel and
auto-provisions a hermetic ephemeral environment on every invocation.
Invocation
bailiff ships as a bundled script (not an installed CLI or PyPI package):
uvx bailiff discover <source> [--ref REF] # inspect template (static, safe)
uvx bailiff trust add <prefix> # record consent
uvx bailiff trust add --from-source <src> # record org-level consent
uvx bailiff init --run-spec <file> [--check] # generate (or dry-run)
uvx bailiff reproduce [<dest>] # faithful reproduce (primary)
# Copier-only fallback (no bailiff needed):
cd <project> && copier recopy --vcs-ref=:current: --defaults --overwrite
See specs/010-delivery-reshape/contracts/invocation.md
for the full surface, exit codes, and the copier-only fallback.
Catalog
The catalog is user-owned configuration: a plain TOML file listing the source repos you want to scaffold from. bailiff depends on no first-party hub; you bring your own templates.
uvx bailiff catalog init # create the catalog file if absent
uvx bailiff catalog add gituser/gitrepo # add a source (idempotent)
uvx bailiff catalog add gituser/mod@v2.1.0 # with a display-version override
uvx bailiff catalog remove gituser/gitrepo # remove a source (idempotent)
uvx bailiff catalog list # show usable templates + flag unusable sources
uvx bailiff catalog list --json # machine-readable listing
uvx bailiff catalog validate demo/my-template # gate: exit 0 if valid, non-zero if unknown/ambiguous
Pass --catalog PATH between catalog and the subverb to target a non-default
file (e.g. catalog --catalog PATH list); the default location follows the same
platformdirs/BAILIFF_CATALOG_PATH resolution as trust.py. Templates are identified by a full-id <catalog>/<template>
(where <catalog> is the pointer name, defaulting to a sanitized source
basename). The listing is deterministic — same sources at the same pins produce
identical output. A source that is unusable (no PEP 440 tag, bad copier.yml,
unreachable) is reported per-source with a reason; the rest of the catalog still
lists.
The skill (step 0 of skills/bailiff/SKILL.md) manages the catalog on the user's
behalf: ensure → list → pick → validate → init.
The bailiff-io module catalog
The first-party module family (bailiff-mod-*) is authored in the
bailiff-io/bailiff monorepo and published as a generated JSON index
committed to the (public) monorepo and served directly via raw git at the stable
URL:
https://raw.githubusercontent.com/bailiff-io/bailiff/main/catalog.json
The index is derived from released modules — a module appears only once it has a
published vX.Y.Z tag on its split repo, so the catalog is empty until the first
release fan-out runs. See
docs/runbooks/fanout-release.md for the
release/fan-out pipeline and the one-time maintainer setup it requires.
Multi-template
Select several templates from the catalog and bailiff applies them in dependency
order — no manual sequencing. Each template declares its depends_on/run_after/
run_before edges in its copier.yml; bailiff builds the DAG, validates it (refuses
cycles, dangling edges, and basename collisions before writing anything), and runs
one copier copy per layer in topological order, threading earlier layers' answers
into later ones.
Each layer commits its own .copier-answers.<basename>.yml. At reproduce, bailiff
enumerates those files, fetches each template at its pinned _commit, re-reads the
edges, and recomputes the same order — no frozen recipe file is committed to the
project. Pinned commits → identical edges → identical order, so reproduce is
deterministic and agent-free.
The copier-only-by-hand fallback (copier recopy … -a <each answers file> in
recomputed order) works for any number of layers — nothing about the project requires
bailiff to reproduce.
N=1 is the degenerate case: a single-template project runs the same path with a one-node DAG, no special-casing.
See skills/bailiff/SKILL.md (multi-template section) and
specs/003-multi-template/contracts/ordering.md for the run-spec shape, edge
semantics, and ordering algorithm.
Upgrade
Move a project from one template version to a newer one:
# Upgrade all layers to the latest PEP 440 tag (single-layer or multi-layer):
uvx bailiff update <project-dir>
# Target a specific version:
uvx bailiff update <project-dir> --vcs-ref v1.2.0
# Dry-run preview:
uvx bailiff update <project-dir> --pretend
Upgrade is the only bailiff path that advances a template version — reproduce always stays pinned. copier's 3-way merge handles local edits; bailiff supplies the version announcement, the cross-template ordering, and the conflict report.
- Multi-layer: layers are upgraded in dependency order (DAG re-solved at target versions). New dependencies introduced in a newer template version are detected and refused until the user adds the missing layer.
- Migrations (
_migrationsincopier.yml): version-crossing migration commands run automatically via copier, trust-gated identically to_tasks. The new format is enforced; the deprecatedbefore/afterdict form is refused at discovery. - Conflicts: if copier's 3-way merge leaves conflict markers, bailiff reports the conflicted files and exits 4. Resolve and re-run.
Exit codes: 0 ok, 1 error, 3 untrusted source, 4 merge conflicts.
Design decisions
- 0001 — copier is the engine; bailiff is the conductor
- 0002 — user-owned catalog; copier answers carry the state
- 0003 — selector-template + runtime catalog injection (superseded in part by spec 002 — two-template flow replaced by catalog file + validation gate;
--data catalog=[…]render-scope fact retained) - 0004 — rendering behavior, file handling, and jinja extensions
- 0005 — global per-module defaults via
user_defaults= - 0006 — central authoring monorepo, fan-out to per-template repos
Development
uv sync --dev
uv run pytest # run the hermetic test suite
uv run pytest -m network -v # live smoke test (requires gh auth)
uv run ruff check src/ tests/ scripts/
uv run mypy
bash scripts/try-bailiff.sh # interactive end-to-end walkthrough
License
Apache-2.0
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bailiff-0.5.0.tar.gz.
File metadata
- Download URL: bailiff-0.5.0.tar.gz
- Upload date:
- Size: 2.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89b19187cfc6e9fd27aa776b4dff0c4ff4e414d5afa21c9c5bee04cda812bb58
|
|
| MD5 |
87a22250fbb7234620064eb379532466
|
|
| BLAKE2b-256 |
388e608f6e8e21b691f5ccfe3652f8b112da122a9531243c0067533958188e77
|
File details
Details for the file bailiff-0.5.0-py3-none-any.whl.
File metadata
- Download URL: bailiff-0.5.0-py3-none-any.whl
- Upload date:
- Size: 67.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bebfea890fdf3f6cba9ce2755ebb4876606edcfee75fa13db12c39548370d51f
|
|
| MD5 |
1d3eccd1942314e326fcb942d6452424
|
|
| BLAKE2b-256 |
79d64da3c755320cb3312a99526a7093d42cd818c210a0d2a2847e84acb7f534
|