Recipe Activation (the Active Set)
Recipes are a two-layer system, the same way installed skills relate to a marketplace:
- The cookbook is every recipe Pretorin can load — built-in, your own
(
~/.pretorin/recipes/), your team’s (./.pretorin/recipes/), and explicit paths. It grows as you author and clone recipes. See Overview and Publishing. - The active set is the curated subset of the cookbook provisioned for one
compliance effort — one
(system, framework)scope. It is stable and confirmed: established once, edited deliberately, not re-derived on every interaction.
Provisioning answers which recipes are in the room for this effort. It does not change how a recipe is chosen for a given task — that is still the agent reasoning over descriptions per task (a menu, not a binding). Once a scope is provisioned, the active set is the confirmed menu everything downstream draws from.
Where the active set lives
On the scope’s preflight artifact, keyed by
(system, framework) under ~/.pretorin/preflight/. Availability is already
host-local and lives there; the active set is a decision layered on the same facts.
Each active recipe records the version, content digest, and loader source pinned
at activation, the ready source kinds it covers, and who activated it.
Provisioning during preflight
The active set is seeded at the end of the preflight workflow (Step 8), from what the host can actually do:
# Direct CLI: seed the active set with every official-tier recipe runnable on
# this host (community/project recipes need --include-unofficial), then trim
# to the logical subset.
pretorin preflight provision # show candidates + coverage gaps
pretorin preflight provision --apply # seed the active set from candidates
pretorin recipe deactivate openscap-baseline # trim what you don't run
An agent does the same over MCP: get_active_recipes returns the candidates
runnable on the host (source-less recipes plus those whose required source kinds
are all ready), any coverage gaps (ready sources that no cookbook recipe
consumes — author or clone one), and any version drift. It then commits a
logical subset with set_active_recipes.
Managing the active set
pretorin recipe active # show the active set + proposal
pretorin recipe activate code-evidence-capture workspace-capture
pretorin recipe deactivate manual-attestation
pretorin recipe list --active # list only the active subset
Over MCP:
get_active_recipes— read the set + proposal (candidates, coverage_gaps, drift).set_active_recipes(recipe_ids=[...], mode="replace" | "add" | "remove")— edit it.list_recipes(system_id=..., active_only=true)— list the active subset; every recipe also carries anactiveflag when a system scope is supplied.
What changes once a scope is provisioned
- Capture plan /
check_sourcesdraw candidate recipes from the active set. Arecipe_gapno longer means “impossible” — it means no active recipe covers this expectation, and it names the host’s ready source kinds and the active recipes that can substitute. Activate or add a recipe, or accept the gap. start_reciperefuses a recipe that is not in the active set. Activate it first (pretorin recipe activate <id>), or passforce=truefor a genuine one-off run. It also refuses active recipes whose pinned version, content digest, or loader source has drifted; review and re-activate, or force the current recipe ad hoc.
Migration
A scope that has never been provisioned behaves exactly as before: downstream tools fall back to the whole cookbook (filtered by preflight availability) and nudge you to provision. Provisioning is opt-in and non-breaking — establish an active set when you want the stability and the confirmed menu.
Version drift
Activation pins the recipe’s version and a content digest (a hash of
recipe.md plus its scripts) and its loader source. Every
get_active_recipes / pretorin recipe active compares them against the
cookbook and flags:
- version moved — the recipe was upgraded/downgraded since activation;
- content changed — a same-version body/script swap (e.g. a shadowing recipe at a higher-precedence loader path replacing the code behind the id);
- source changed — the id now resolves from a different loader path.
Drift is a visible signal, not a silent swap under your audit. On a provisioned
scope it is also an execution gate: start_recipe refuses the drifted recipe
until you re-activate it, or pass force=true for an audited ad-hoc run.
Security & integrity
- The gate fails closed. If a scope’s preflight artifact is present but
unreadable (corrupt, symlinked, or a tampered/unknown
schema_version),start_reciperefuses rather than silently treating the scope as unprovisioned. Rebuild it withpretorin preflight verify, or passforce=truefor an audited ad-hoc run (recorded asforcedon the execution). - Untrusted recipes are activated deliberately.
pretorin preflight provision --applyseeds official-tier only; community/project recipes must be activated explicitly (pretorin recipe activate <id>) or with--include-unofficial. - Provisioning is scope-bound.
set_active_recipesenforces the active CLI context — an agent can’t provision a scope other than the active one. - The artifact is local,
chmod 600, redacted before write, and rejects path-separator scope keys and symlinks. An attacker with write access to your~/.pretorin/preflight/can still deny service (as they could by deleting any config); cryptographic artifact integrity is future work.