Skip to main content

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 project byte-for-byte 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: scripts/bailiff.py reproduce replays 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 --overwrite per 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 bundled deterministic script — scripts/bailiff.py — 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:
python "$(apm skill-path bailiff)/scripts/bailiff.py" 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 uv run scripts/bailiff.py <verb>uv reads the PEP 723 header and auto-provisions a hermetic ephemeral environment on every invocation.


Invocation

bailiff ships as a bundled script (not an installed CLI or PyPI package):

uv run scripts/bailiff.py discover <source> [--ref REF]   # inspect template (static, safe)
uv run scripts/bailiff.py trust add <prefix>              # record consent
uv run scripts/bailiff.py trust add --from-source <src>   # record org-level consent
uv run scripts/bailiff.py init --run-spec <file> [--check] # generate (or dry-run)
uv run scripts/bailiff.py 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.

uv run scripts/bailiff.py catalog init                       # create the catalog file if absent
uv run scripts/bailiff.py catalog add gituser/gitrepo        # add a source (idempotent)
uv run scripts/bailiff.py catalog add gituser/mod@v2.1.0     # with a display-version override
uv run scripts/bailiff.py catalog remove gituser/gitrepo     # remove a source (idempotent)
uv run scripts/bailiff.py catalog list                       # show usable templates + flag unusable sources
uv run scripts/bailiff.py catalog list --json                # machine-readable listing
uv run scripts/bailiff.py 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):
uv run scripts/bailiff.py update <project-dir>

# Target a specific version:
uv run scripts/bailiff.py update <project-dir> --vcs-ref v1.2.0

# Dry-run preview:
uv run scripts/bailiff.py 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 (_migrations in copier.yml): version-crossing migration commands run automatically via copier, trust-gated identically to _tasks. The new format is enforced; the deprecated before/after dict 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

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

bailiff-0.1.0.tar.gz (1.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bailiff-0.1.0-py3-none-any.whl (42.9 kB view details)

Uploaded Python 3

File details

Details for the file bailiff-0.1.0.tar.gz.

File metadata

  • Download URL: bailiff-0.1.0.tar.gz
  • Upload date:
  • Size: 1.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","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

Hashes for bailiff-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f570ad8296dfeb7158de5ba2c0962fc436023d994cad4900e7c103a40c697140
MD5 d9dbcd90f4beaa88afb54f1a97088127
BLAKE2b-256 aee8d64d82be8bbf938cb1a11669693749211b386b70434072ae282948fdb620

See more details on using hashes here.

File details

Details for the file bailiff-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bailiff-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 42.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","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

Hashes for bailiff-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dbb0cc8d2296d0b8d0ee0bdd70ef8eeb7e14ffa3fad09e8bfb69800f30462131
MD5 1c219e8d4f431cc93f9727242898a40f
BLAKE2b-256 9d3a4c73e9529418b12faf93f8a25bf23cdf900cea223e913d655c47563fa5dd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page