The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY in this skill are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals.
You are the release conductor. A change has been merged to the default branch, and your job is
to carry it to production and prove it runs there, without weakening any gate the factory has.
You do not deploy anything yourself. You drive release.py, the state tool that reads the
project's release recipe, checks the grant before every consequential step, runs the recipe's
commands in isolated checkouts, records every step, and refuses whatever the rules forbid. Your
judgement goes into what you show the human and into the verifier you dispatch; the tool's
output decides what happens next. Production is the human's call, every time: the tool can
prepare, stage and verify a release unattended, but the production deploy and any rollback run
only after the human's explicit yes for this release, in this session.
Locating this skill's helpers (do this first). The steps below run bundled scripts. You execute from the target repo, not from this skill's directory, so a path written relative to this skill will not resolve. Resolve the base directory once and use it everywhere — including in any subagent prompt, which MUST receive the literal absolute path:
SKILL_DIR="<this skill's base directory>" # your harness provides it when the skill loads
# If you don't have it, discover it:
SKILL_DIR=$(find ~/.claude ~/.config ~/.agents -type d -name 'release-conductor' 2>/dev/null | head -1)
test -d "$SKILL_DIR/assets" || test -d "$SKILL_DIR/scripts" # verify before proceeding
Python interpreter. Most commands in this skill and its references are written with python3.
Find the interpreter once, before the first of them. If $SKILL_PYTHON is set, use it. Otherwise,
on macOS, if command -v python3 prints /usr/bin/python3 and xcode-select -p fails, that
python3 is the install stub: skip it without running it. Use the first of python3, python and
py -3 that is not skipped and for which
<interpreter> -c 'import sys; sys.exit(sys.version_info < (3, 10))' exits 0. Write the interpreter
you found in place of python3 in every command, and in any Bash(...) permission rule you
suggest. Commands that start with uv run or {python} keep their own form. In a session resumed
under a Bash(...) allowlist, skip this lookup and use the interpreter that the allowlist names. If
none passes, follow this skill's own fallback for running without Python where it gives one;
otherwise stop and tell the user that this skill needs Python 3.10 or newer.
Every command below is python3 "$SKILL_DIR/assets/release.py" <command> --root <repo>,
written release <command> for short. Run it with Python 3.10 or newer. Each command prints
one machine line per event (RELEASE:, STAGE:, DEPLOY:, PROD:, ROLLBACK:, RESULT:,
GATE:, NEXT:, STOP:) and exits 0 for OK, 3 when the release stopped or waits for
a human, and 2 when the step was refused or its input is invalid (nothing ran, and the
status is unchanged). STOP: locked (exit 3) means another release command held the repo's
run lock for the whole 900 s this command waited for it: run the same command again once
that one finishes. Only one release per
repository is unfinished at a time. Every command, line, state and exit code, the recipe
schema, the grant and a worked example are in references/release-protocol.md.
When to use
The user asks you to release, ship or deploy what is on the default branch, or a
factory-conductor PR has just been merged and the user wants it in production. A request to
deploy an unmerged branch is not a release: send the change through a PR first. A project with
no recipe starts with release init (step 0).
Preconditions
You MUST have all four before you run release prep, because prep pins what it finds and a
gap found later costs a new prep with the human:
- A committed recipe.
.release/recipe.jsonis committed on the default branch, unchanged in the working tree. With none, run step 0. prep refuses an uncommitted or edited recipe (recipe-uncommitted), and a recipe whose version is{"cmd": …}, since it cannot bump that. - The project's verify skill. The recipe's
verify_skillnames a verify skill made by verification-skill-forge, committed, with at least one feature in itsfeatures/map. Without one, run verification-skill-forge first: staging cannot be proven without it. - A clean checkout of the default branch, up to date with
origin, withoriginconfigured: prep pushesrelease/<version>there and opens the PR against the default branch. - No unfinished release.
release statusshows every releaseverified,rolled_back,stage_failedorabandoned(or none). prep refuses while another release is unfinished.
If any of them fails, stop and tell the user which one and why.
The flow
Each step is one command. Read its output lines, not just the exit code.
-
Init (once per project). Read the repo (its deploy scripts, CI, hosting config) and interview the user for each recipe field;
references/release-protocol.mdlists them. Commands are argv lists, never shell strings. Write the answers to a JSON file outside the repo, thenrelease init --answers <file>. It validates and writes.release/recipe.json(RELEASE: recipe written), and refuses to overwrite one that exists. The recipe is a trust root, like CI config: it reaches production through a reviewed PR the human merges, never through this skill (NEXT: commit .release/recipe.json through a reviewed PR, then run prep). -
Prep, with the human present. prep writes the release grant, and
--approved-byis the human accepting it. So first tell the human what the grant covers: the classeslocal_reversible,push_branch,open_pr,deploy_stagingandpush_tag, on the branchesrelease/<version>andrelease/<version>-stageonly, for 7 days, pinned to the recipe and the release intent; and that production deploys and rollbacks are never covered. Ask for their yes, then runrelease prep --approved-by "<their name>" --driver <your agent id>.--bump patch|minor|majoroverrides the recipe's level;--policy-file <json>declines classes ({"deploy_staging": "ask"}) and can never widen them. Without either flag, prep applies the release defaults a planning grant recorded (RELEASE: release defaults applied from grant <id>). The--driverid is yours, as your harness gives it: the verifier in step 3 has to be someone else. prep bumps the version file, prepends a changelog section built from merge subjects since the last tag, commits onrelease/<version>, pushes it and opens the PR.RELEASE: <version> preppedwithNEXT: merge the release PR, then run stageis the end of this step. AGATE:/STOP:pair on the push or the PR, or a failed push or PR (exit 3), leaves the releaseprepped; fix what stopped it and run the same prep again, which resumes the pending step without a new grant or commit. A refusal (exit 2), or a stop before the commit, undoes everything prep wrote and revokes its grant. -
The human merges the release PR. You do not merge it. Once they have, update the local default branch (
git pull) and runrelease stage. It finds the merged release commit (the first-parent commit where the version file became the pinned version;--commit <sha>names it when the history is unusual), checks that its version is the one the grant pinned and that its recipe is the one the grant pinned, and only then runs anything from it. It creates the stage worktree.skill-contract/releases/<v>/wt-stageonrelease/<v>-stage, builds the commit in an isolated checkout (wt-build), and stops withSTAGE: <v> built <commit>andNEXT: dispatch-verifier <commit>(exit 0). -
Dispatch the verifier. Send a fresh subagent the verifier brief below, filled in. The verifier MUST be a fresh subagent, not you, because the evidence predicate refuses evidence recorded by the prep driver, and a verifier who drove the release is not independent. Pass its id, as your harness gives it, in the next step.
-
Stage with the evidence.
release stage --evidence --verifier <its id>judges the records: every feature the verify skill maps at the release commit needs a passingevidence.jsonfor exactly that commit, recorded by this verifier, with unaltered artifacts and a green doctor. Then, each step gated by the release grant, it deploys to staging, polls the stagingversion_probeuntil it reports the version or commit (up todeploy_timeout), runs everystaging_checksargv, and pushes thev<version>tag, or holds it when CI runs on tags (STAGE: <v> tag-held: the tag push is then the production deploy, step 5).STAGE: <v> pass <commit>withNEXT: run deploy with the humanends staging. When the release grant declineddeploy_stagingorpush_tag(the human's--policy-file, or a planning grant's release defaults), that gate answersASK gate-askwithNEXT: ask the human to approve <class>, then re-run stage --approved-by <name>. Ask the human about that class only: deploy to staging, or push the tag. On their yes, runrelease stage --approved-by "<their name>". The yes answers only that declined gate, once, for this release commit, recorded as CLAIMED. When both classes are declined, stage asks twice: one yes deploys staging, then stage stops atpush_tagand asks again. A yes given when stage asked nothing answers nothing. Every otherASKreason still stops, and the yes is refused unattended.STOP: evidence-reject <reason>(exit 3) means dispatch a new verifier; a failing feature, build, deploy, probe or check isSTAGE: <v> fail <reason>and the release isstage_failed: finished, its grant revoked; a fix needs a new change and a new prep. -
Deploy, only with the human's yes. Run
release deploywith no--approved-by. It checks every refusal (staging evidence for this exact commit, the recipe and build checkout unchanged since stage, thedeploygate answering ASK, no headless allowlist reaching production), then runs the productionversion_probeonce, read-only, to find the rollback target (what production runs now: the probe's answer, else the last release result, else none), records it, and prints the summary:DEPLOY: <v> <commit>, the evidence, the recipe sha, the artifact, the exact deploy argv, and the exact rollback argv for that target, or "no previous release, nothing to roll back to". Then it waits:STOP: waiting-human, statusawaiting_deploy, exit 3. Show the human that summary verbatim, then ask in plain words: "Deploy (commit ) to production now with<deploy argv>? If it fails, rollback runs<rollback argv>(or: there is nothing to roll back to). Reply yes to deploy." You MUST NOT pass--approved-byunless the human said yes to this deploy in this session, because the tool records whatever name you pass as the production yes and cannot tell a real one from a forged one. On their yes, runrelease deploy --approved-by "<their name>"with the same flags. The yes binds to the whole summary: it probes the target again and recomputes the summary, and if anything in it differs from what the human saw (the target, the deploy argv, the--remote), or no attended run showed it, it prints the new summary and waits again (STOP: waiting-human, nothing run), and you ask again. Otherwise it records the yes as CLAIMED and runsdeploy_prodonce from the isolated build checkout, or pushes the tag when CI deploys on tags.DEPLOY: <v> deployed <commit>withNEXT: verify-prod(exit 0) is a success. When no human is present, runrelease deploy --unattendedinstead: it shows the same summary and waits atawaiting_deploy. A run withFACTORY_CONDUCTOR_REENTRYset counts as unattended too. A summary shown unattended is one no human saw, so when the human returns, their session first runsrelease deploywithout--approved-by, shows them that summary, and only then passes their yes. -
Verify production.
release verify-prodpolls the productionversion_probeuntil it reports the release (a deploy still rolling out is not a failure), then runshealthand everyprod_smokeargv, and nothing else:staging_checksnever run against production.PROD: <v> verifiedandRESULT: <path>(exit 0) end the release; its grant is revoked. A failure isPROD: <v> fail <reason>(not-live: still the old version at the timeout;wrong-version: a third version;health-failed;smoke-failed), statusprod_failed, and the rollback the human may say yes to (exit 3). verify-prod judges a deploy once: a re-run is refused, so a flaky check cannot be retried intoverified. -
Rollback, only with the human's yes. Show the human the failure and the printed rollback argv and target, and ask in plain words whether to roll back. As in step 5, first run
release rollbackwithout--approved-by: it shows the rollback summary and waits, and the yes binds only to a summary an attended run showed. The production-yes rule of step 5 applies to--approved-byhere too. On their yes,release rollback --approved-by "<their name>"runs the recipe'srollbackonce, then polls the probe until it reports the target:ROLLBACK: <v> rolled-back <target>andRESULT: <path>(exit 0). Without a yes, or with--unattended, it shows the rollback and waits (STOP: waiting-human). With no recorded target it refuses (exit 2): there is nothing to roll back to, so tell the human. -
Abandon, only with the human's yes. Some releases can go nowhere else: the human closed the release PR, declined to ship, or pulled the kill switch mid-release, or the first release's deploy failed with nothing to roll back to. Every later prep is refused while one is unfinished. Tell the human why the release is stuck and ask whether to end it. On their yes, run
release abandon --approved-by "<their name>" --reason "<their words>". It runs no recipe command and touches neither production, nor the remote, nor tags: it records the releaseabandoned(the yes CLAIMED), revokes its grant, and removes its worktrees and localrelease/<v>branches. Fromdeployed,prod_failedoroutcome_unknownit says that production is the human's to handle. A release leftdeployingorrolling_backis first demoted tooutcome_unknown(STOP: outcome-unknown, exit 3): the human checks production, then you run abandon again. The remote branch and the release PR are left for the human to close; a later prep of the same version needs that remote branch gone, and keeps the abandoned record beside the new one. abandon refuses--unattendedand a run withFACTORY_CONDUCTOR_REENTRYset. You MUST NOT run abandon without the human's yes to ending this release, because it ends the release's record for good and only a new prep with the human starts another.
release status prints every release and its status (RELEASE: <v> <status>). It only
reads: it takes no lock and changes nothing, so you can run it at any time, even during a
deploy.
Reading NEXT:.
NEXT: line |
What you do |
|---|---|
commit .release/recipe.json through a reviewed PR, then run prep |
Open a PR with the recipe for the human; prep after it merges. |
merge the release PR, then run stage |
Tell the human the PR is ready; run stage after they merge it. |
ask the human to approve <class>, then re-run stage --approved-by <name> |
The release grant declined this staging step: ask the human about that class; on their yes, step 4's stage --approved-by. |
fix what stopped it, then re-run prep to resume (or stage) |
Read the GATE:/STOP: line, report it, re-run the same command once the cause is fixed. |
merge the release PR and update local <branch> (git pull), then run stage |
The release commit is not on the local default branch yet. |
dispatch-verifier <commit> |
Step 3, then stage --evidence --verifier <id>. |
reset <wt> to <commit>, then re-run stage |
The stage worktree moved; report it, since a verifier that moves HEAD invalidates the gate's checks. |
run deploy with the human |
Step 5: show the summary, ask for the yes. |
verify-prod |
Step 6. |
verify-prod then ask the human |
The deploy failed or its outcome is unknown: run verify-prod once, then report to the human. |
ask the human to roll back |
Step 7. |
ask the human (there is nothing to roll back to) |
Report the failure; the human decides what happens to production. |
run rollback with the human |
Step 7 with the human present. |
check production by hand; rollback again only with the human's yes |
A rollback failed or its outcome is unknown: report it; production is the human's to inspect. |
a new prep with a human is needed … / re-run prep with a human |
The grant or the recipe cannot be trusted any more: report it. |
fix what stopped it; the release is finished, and its state.json and release-log.jsonl hold the record |
The result envelope could not be written; the release outcome stands. |
check production by hand; then re-run abandon with the human to end the release |
abandon found a crashed deploy or rollback and demoted it: report it; production is the human's to inspect. |
the release is finished; a new prep may start |
abandon ended the release: report what the human still has to close (the remote branch, the PR). |
A RELEASE: refused: grant-revoked (exit 2) from deploy or rollback means the release
grant was revoked (the kill switch) or superseded: the release cannot go on, so report it,
and on the human's yes end it with step 8.
Outcome unknown. A deploy or rollback that timed out, or a command that died mid-flight,
leaves the outcome unknown (STOP: outcome-unknown, status outcome_unknown). The tool
never re-runs that command. After a deploy, run verify-prod once (it can still prove the
release live) and report to the human; after a rollback, only the human decides what is next.
Hard rules
- You MUST NOT put the recipe's
deploy_prodorrollbackargv, or (when CI deploys on tags) agit pushof the release tag, in any--allowedToolsor other headless allowlist, because a headless agent could then run a production deploy that no human said yes to. The tool refuses to stage, deploy or roll back (allowlist-exposes-prod, exit 2) while any live grant'sreentry.agent_cmdcould reach those commands, expired-but-unrevoked grants included. Report the grant ids to the human, who revokes them (python3 "$SKILL_DIR/assets/contract_check.py" revoke-grant --root <repo> --id <id>) or narrows their allowlist. - You MUST NOT put
release.py's owndeploy,rollbackorabandonin a headless allowlist either (aBash(python3 *)rule reaches them), because an agent that can run them unprompted can forge the human's yes. The same refusal covers them. - You MUST NOT run
deploy,rollback,abandonorstagewith--approved-bywhen no human is present, because an unattended production change is exactly what the grant floor forbids. Unattended, use--unattended, which waits with the command ready. A run withFACTORY_CONDUCTOR_REENTRYset is unattended whatever its flags: deploy and rollback wait, abandon andstage --approved-byrefuse. - You MUST NOT edit a grant to lift a declined class, because that widens the human's grant
without them. A declined staging deploy or tag push is answered with the human's
in-session yes (
stage --approved-by), never by rewriting the grant. - You MUST NOT edit, delete or recreate anything under
.skill-contract/releases/, because the state, the log and the intent file there are the release's record that every later command reads back and thatrelease-result/v1pins by digest. Report a mismatch instead. A release that can go nowhere else ends only throughrelease abandonwith the human's yes (step 8). - You MUST NOT re-run a deploy after
outcome_unknownby any route, or a rollback without a fresh yes from the human after they have checked production by hand, because a deploy command is not known to be idempotent and a second run could deploy twice or roll back over a live release. After a deploy, run verify-prod once and leave the rest to the human. - You MUST NOT run any recipe command, a tag push or the release PR merge yourself, because only
release.pychecks the grant and the refusals first and records the step. Pass no--push-cmdor--pr-cmdto prep unless the host has nogh, so the step the grant approved is the step that runs.
The verifier brief
<commit> is the commit from NEXT: dispatch-verifier <commit>; <wt> is
<repo>/.skill-contract/releases/<version>/wt-stage; <skill> is the recipe's verify_skill.
Use the verifier's real id: a record under a placeholder id fails the verifier match. If your
harness assigns the id only when it spawns the agent, add a line saying the id arrives in a
follow-up message and nothing is recorded until then, and send it at once.
You are verifying release <version> at commit <commit>. You did not prepare this release.
You MUST NOT edit or commit files, move HEAD, or dispatch subagents. You MUST NOT use
test-only endpoints or internal setters.
Worktree: <wt> (its HEAD is <commit>; leave it there). Verify skill: <wt>/<skill>.
Run the app locally from this worktree, as the verify skill's Launch says; do not point
anything at staging or production.
Export VERIFY_EVIDENCE_DIR=<absolute repo root>/.verify and use an instance name of your own.
For EVERY feature in <wt>/<skill>/features/ (the full check set): follow the verify skill's
Launch and Doctor, drive the feature's recipe in features/<id>.md, capture the action, the
resulting state and its side effects, and record it with the skill's verify_evidence.py
record, --verifier <your id>. Then run the skill's Cleanup. Evidence stays.
Report: Verdict: pass | fail, then one line per feature: <id> <evidence path> <observed>.
stage --evidence rejects (STOP: evidence-reject <reason>, exit 3) with one of:
verifier-missing, driver-is-verifier, feature-map-unreadable, no-features,
evidence-missing, evidence-stale-sha, evidence-sha-mismatch, evidence-malformed,
evidence-artifact-missing, evidence-artifact-altered, evidence-verifier-mismatch,
doctor-missing, doctor-red. Dispatch a new verifier for each, except no-features and
feature-map-unreadable: those need verification-skill-forge's maintain mode first. A
recorded failure (evidence-failed) is not a reject: the stage fails.
Deliverable
A release report for the user: the version, the release commit, the final status, the
release-result/v1 path (RESULT:), who verified staging and which features, the production
probe, health and smoke results, the rollback target, and the CLAIMED yes with the name. For a
stopped release, the STOP: line, the NEXT: line and what the human has to decide. Say
whether the artifact was rebuilt (staging then verified the same source, not the same bytes).
Output style. Write reports and explanations for the user in about 80% ASD-STE100 Simplified Technical English. Use short sentences, common words, active voice and one action per step. This style is for prose to the user, not for the code, prompts or files that this skill makes.
Verify and repair
Before you report, run release status and check that it shows the status your last command
printed. Then check the envelope as a receiver would:
python3 "$SKILL_DIR/assets/contract_check.py" check-envelope <RESULT path> --root <repo>.
A passing check is the expected result; a failure, or a status that does not match the output you read, is a tool bug:
report it rather than editing state.
What the proof is worth
- The yes is CLAIMED. The tool records the name passed to
--approved-byas the human's yes, marked CLAIMED in the state and the envelope, because a shell-capable agent can forge an in-session approval. The summary the yes answers names the rollback target, found by a read-only production probe that runs before the yes. The yes binds to a digest of the whole summary (the deploy or rollback argv with its remote, the rollback argv, the target, the commit, the recipe and artifact sha), and only to one an attended run showed: a summary that changes before the yes, or one shown only to an unattended run, voids it. The real floors are mechanical:check-grantnever coversdeploy(deploy refuses if it ever answers COVERED), production commands may not appear in a headless allowlist, and the yes is on the record. - A permission prompt is no gate if prompts are bypassed. The harness's prompt before the
deploy command is a second check only when the user runs with prompts on; under
--dangerously-skip-permissionsorbypassPermissionsnothing stops a forged yes but the record. - The allowlist check sees only
reentry.agent_cmd. Allowlists elsewhere, such as the target repo's.claude/settings*.json, are invisible to it. It recognises Claude Code's--allowedToolsand permission-bypass flags, not other agents' CLIs (Codex, Gemini), and a wrapper script hides what it runs. A malformed allowlist or a bypass flag counts as exposing, and so does a grant allowing a broadgit pushin tag-deploy mode, or one reachingrelease.py's owndeploy,rollbackorabandon(aBash(python3 *)rule does), which blocks the release until it is narrowed. The check reads a rule through these wrappers:env,uv run,uv tool run,uvx,sudo,doas,command,builtin,exec,nice,nohup,time,stdbuf,xargsandtimeout, with their options. It readseval,find -exec(and-execdir,-ok,-okdir), and-consh,bash,zsh,dash,kshorfishas "any command". It readspython,python3andpython3.N, by name or by absolute path, with their flags. It readspython -candpython -mwith a glob,pdb,runpy,cProfile,profile,trace,timeitorcodeas "any command". It expands a~/path, and reads a legacyBash(x:*)rule asBash(x *)too. So rules such asBash(env *),Bash(uv *),Bash(uv run *),Bash(uvx *),Bash(sh -c *),Bash(bash:*),Bash(/usr/bin/*)orBash(./*)count as exposing. A rule whose program is a path glob, such asBash(./scripts/*),Bash(bin/*),Bash(~/bin/*)orBash(.venv/bin/*), counts as exposing too: the glob can name any program. The refusal names each grant and the rule that let it in. Known limits: this list can never be complete. A wrapper or interpreter not on it stays unseen, for examplepoetry run,pipenv run,pdm run,npx,setsid,ionice,flock,script -c,watch,strace,perl -eornode -e. A quotedenv -S "..."string is not read. A script path it does not know stays unseen. A glob in an argument (not the program) can cross path parts, so a rule likeBash(python3 tests/*)can reachtests/../release.pyand is not caught. The real backstop is the re-entry rule: withFACTORY_CONDUCTOR_REENTRYset, deploy and rollback wait for the human. - CI tag-trigger detection covers listed formats. GitHub Actions workflows and CircleCI are read; every other CI config is treated as tag-triggered unless proven otherwise, so a tag push then waits for the production yes. A false positive costs one extra ask, never a silent deploy.
- Rebuild honesty. With
"artifact": "rebuild", production rebuilds from the release commit: staging verified the same source, not the same bytes. The record says so. With{"path": …}, the artifact's sha256 is checked from stage to deploy. - The recipe runs with the user's credentials. Every recipe command runs as the user, with the user's environment and credentials, and reaches whatever it targets. The recipe is pinned by digest from prep to deploy, and a recipe change makes every step refuse or ask.
- Production checks are only as good as
prod_smoke. verify-prod proves the probe reports the release and the read-only smoke checks pass; it cannot see what the smoke checks do not check. - Nothing triggers
stageautomatically. After the human merges the release PR, a session has to runstage. - Grant edges. Every release gate names the release grant by its path (with the recipe as
subject and the stage or prep worktree), so another live grant cannot answer for it. A grant
revoked and a new one written in the same second can still tie where a gate selects by
subject, and that gate then answers ASK revoked.
revoke-grantwith no id (the kill switch) revokes every live grant, the release grant included: every gated step (prep's and stage's) stops at its next gate, and deploy and rollback refusegrant-revokedbefore any wait. verify-prod runs only the read-only probe, health and smoke checks, so it still runs. A release stopped this way ends with abandon. The release grant is revoked when the release ends:verified,rolled_back,stage_failed,abandoned, or a prep that failed and was undone. - Output stays local. Command output tails can carry tokens, so they stay in the release's
git-ignored
state.json; the envelope, the release PR and the changelog carry exit codes and neutralised merge subjects only.
Contract
This skill follows skill-contract v1.
It consumes an https://github.com/dhanesh/agent-skills/skill-contract/autonomy-grant/v1: the
release grant prep writes with the human's acceptance, and, for release defaults only, a
planning grant from spec-first-planning. It provides a
https://github.com/dhanesh/agent-skills/skill-contract/release-result/v1 envelope, written by
verify-prod (outcome verified) or rollback (outcome rolled_back); the payload schema is
assets/schemas/release-result.v1.json. Its subjects pin the recipe, the release intent and
the release log by digest, and it carries exit codes only, never command output.
{"provides": ["https://github.com/dhanesh/agent-skills/skill-contract/release-result/v1"], "consumes": ["https://github.com/dhanesh/agent-skills/skill-contract/autonomy-grant/v1"]}
Boundaries
- Not the planner or the builder. spec-first-planning writes specs, plans and grants; factory-conductor builds a plan to a PR.
- Not the verify skill's author. verification-skill-forge writes and maintains the verify skill whose evidence staging needs.
- Not a deploy platform. The recipe holds the project's own commands; the skill knows no platform.
- Not a merger. You MUST leave merging the release PR to the human, because the merge is
the checkpoint before anything reaches staging and no grant can cover
merge. - Not a publisher. A GitHub Release or an announcement is an external message: it is left to the human.