Skip to content

Commit 706aa8c

Browse files
Jammy2211claude
andauthored
feat: Release Agent rehearsal driver for release validation (M2) (#6)
Add the Brain Release Agent's dispatch/poll/ingest driver for a release rehearsal, orchestrating the build+ingest half of the release-validation pipeline while keeping Heart ingest-and-judge only. - agents/release/rehearse.sh: two-phase, MCP-based (no gh). Phase 1 emits the MCP plan (dispatch Build's release.yml rehearsal=true -> poll -> download the testpypi-rehearsal-version artifact -> capture each library main HEAD into commit_shas.json). Phase 2 hands the artifacts to `pyauto-heart validate --ingest`, then consults the read-only Health Agent for the verdict via the existing consult_health_agent_verdict pattern, emitting a ReleaseValidationDecision. - release.sh routes `release rehearse ...` to the driver; the real-release delegation path is unchanged. - AGENTS.md documents the rehearsal chain + the Release-vs-Health boundary (only the Release Agent dispatches; the Health Agent never does). - HEART_CAPABILITIES.md + dispatcher help note the validate/validation_report surface. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8b96727 commit 706aa8c

5 files changed

Lines changed: 299 additions & 2 deletions

File tree

agents/health/HEART_CAPABILITIES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ reimplement inside Brain.
3535
- Cloud-safe health issue maintenance via `.github/workflows/pulse-health.yml`,
3636
displayed as Heart Health during the Pulse→Heart compatibility period.
3737

38+
## Release validation in Heart (ingest-and-judge)
39+
40+
- `pyauto-heart validate --ingest <artifacts>` folds the release-validation
41+
artifacts (the M1 TestPyPI rehearsal, a `{repo: sha}` commit_shas.json, and —
42+
from M3 — the wheel-based integration `report.json`) into a tracked
43+
`validation_report.json` (`~/.pyauto-heart/validation_report.json`).
44+
- The report is a **hard readiness gate**: GREEN-for-release requires a fresh
45+
passing report whose `commit_shas` match the current `main` HEADs under the
46+
`release` profile; absent/stale/SHA-mismatch/wrong-profile → YELLOW; a failed
47+
stage → RED. It is exposed as the `validate` capability + `validation_report`
48+
signal in Heart's `health_agent/capabilities.yaml`.
49+
- **Heart is ingest-and-judge only** — it never dispatches `release.yml` or
50+
`workspace-validation.yml`. Dispatching/polling/downloading the artifacts is the
51+
**Release Agent's** job (`pyauto-brain release rehearse`), via MCP GitHub tools.
52+
The **Health Agent stays read-only**: it reports the resulting verdict, it does
53+
not dispatch.
54+
3855
## Heart implementation assets
3956

4057
- Bash dispatcher and loop: `bin/pyauto-heart`, `heart/daemon.sh`, `heart/tick.sh`.

agents/release/AGENTS.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,57 @@ bin/pyauto-brain release -- 2 # forward `2` (minor_version) to pre_build
3333
Exit codes: `0` released/delegated · `2` yellow (use --force) · `3` red blocked
3434
· `1/4` could not obtain a verdict / unknown verdict.
3535

36+
## Release-validation rehearsal (M2) — `release rehearse`
37+
38+
Beyond gating a real release, the Release Agent **orchestrates release
39+
validation**: it proves the exact source about to ship was built, published to
40+
TestPyPI, installed from the wheel, and exercised at release fidelity — then
41+
consults the Health Agent for the verdict. It does this without ever letting
42+
Heart dispatch a build (Heart is ingest-and-judge only).
43+
44+
The full chain (M2 builds the dispatch+ingest half; M3/M4 add release-fidelity
45+
integration + full orchestration):
46+
47+
```
48+
Mind -> Release Agent (orchestrate) -> Heart (measure) -> Health Agent (judge) -> Hands/Build (promote on GREEN)
49+
```
50+
51+
`rehearse.sh` is a two-phase driver. Cloud/mobile sessions have **no `gh`**, so
52+
GitHub dispatch/poll/download run through Brain's **MCP GitHub tools**; bash
53+
can't call MCP, so the script emits the MCP plan (phase 1) and owns the local
54+
ingest+verdict (phase 2):
55+
56+
```bash
57+
# 1. print the MCP dispatch/poll/download plan (the agent executes it via MCP):
58+
bin/pyauto-brain release rehearse [--ref main] [--minor N] [--json]
59+
60+
# 2. after downloading the artifact + capturing the library main HEADs,
61+
# ingest into Heart and get the verdict:
62+
bin/pyauto-brain release rehearse --ingest <dir> --commit-shas <dir>/commit_shas.json
63+
bin/pyauto-brain release rehearse --ingest <dir> --force # accept a YELLOW
64+
```
65+
66+
Phase 1 emits the steps: `mcp__github__actions_run_trigger` on
67+
`PyAutoLabs/PyAutoBuild` `release.yml` with `{rehearsal: true}` (M1 mode) →
68+
poll `mcp__github__actions_get` to completion → download the
69+
`testpypi-rehearsal-version` artifact → `mcp__github__get_commit` each library's
70+
`main` HEAD into `commit_shas.json`. Phase 2 hands the artifacts to
71+
`pyauto-heart validate --ingest` (Heart writes `validation_report.json`), then
72+
calls `consult_health_agent_verdict --refresh` so the **read-only Health Agent**
73+
reports GREEN/YELLOW/RED from the freshly-ingested report.
74+
75+
Exit codes (phase 2): `0` green (release-ready) · `2` yellow (use --force) ·
76+
`3` red · `4` unknown · `1` could not ingest.
77+
3678
## What this agent must never do
3779

38-
- Re-derive or second-guess the readiness verdict (that is Heart's job).
80+
- Re-derive or second-guess the readiness verdict (that is Heart's / the Health
81+
Agent's job).
3982
- Run any packaging/tagging/publish step itself (that is Build's job).
4083
- Write into PyAutoHeart or PyAutoBuild repos.
84+
85+
## What only this agent does (not the Health Agent)
86+
87+
- Dispatch/poll/download GitHub workflows and artifacts (via MCP). The Health
88+
Agent is strictly read-and-reason — it never dispatches. Heart never dispatches
89+
either. All release-validation dispatching is the Release Agent's job.

agents/release/rehearse.sh

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env bash
2+
# agents/release/rehearse.sh — the release-VALIDATION driver (M2).
3+
#
4+
# This is the Brain Release Agent's dispatch/poll/ingest driver for a release
5+
# *rehearsal*, distinct from `release.sh` (which drives a real release on GREEN).
6+
# It orchestrates the four-stage release-validation pipeline's build+ingest half:
7+
#
8+
# dispatch M1 TestPyPI rehearsal (Build's release.yml, rehearsal=true)
9+
# -> poll to completion
10+
# -> download the `testpypi-rehearsal-version` artifact
11+
# -> capture the current main HEAD sha of each library
12+
# -> hand it all to `pyauto-heart validate --ingest` (Heart measures)
13+
# -> consult the Health Agent for the verdict (Health judges)
14+
#
15+
# BOUNDARY. Dispatch/poll/download are GitHub actions, done via Brain's MCP
16+
# GitHub tools (cloud/mobile sessions have no `gh`). Bash cannot call MCP, so
17+
# this script owns the *local* half — ingest + consult + decision — and EMITS
18+
# the MCP dispatch/poll/download plan for the agent to execute (see AGENTS.md).
19+
# Heart never dispatches and never mutates a repo; all GitHub credentials/actions
20+
# live here in the Release Agent, never in Heart.
21+
#
22+
# Usage:
23+
# # 1. print the MCP dispatch/poll/download plan (the agent executes it):
24+
# rehearse.sh [--ref main] [--minor N] [--json]
25+
#
26+
# # 2. after the artifacts are downloaded, ingest + get the verdict:
27+
# rehearse.sh --ingest <artifacts-dir> [--commit-shas FILE] [--profile P]
28+
# [--force] [--json]
29+
#
30+
# Exit codes (ingest phase): 0 green · 2 yellow (use --force) · 3 red · 4 unknown
31+
# · 1 could not ingest. Plan phase always exits 0.
32+
33+
set -uo pipefail
34+
35+
HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
36+
source "$HERE/../_common.sh"
37+
38+
# The Build repo + workflow the rehearsal is dispatched against (M1).
39+
BUILD_REPO="PyAutoLabs/PyAutoBuild"
40+
RELEASE_WORKFLOW="release.yml"
41+
REHEARSAL_ARTIFACT="testpypi-rehearsal-version"
42+
# The 5 libraries whose main HEADs the rehearsal is built from; readiness
43+
# confirms the ingested report's commit_shas against these.
44+
LIBRARIES=(PyAutoConf PyAutoFit PyAutoArray PyAutoGalaxy PyAutoLens)
45+
46+
ref="main"
47+
minor=""
48+
ingest_dir=""
49+
commit_shas_file=""
50+
profile=""
51+
force=0
52+
json_only=0
53+
54+
while [[ $# -gt 0 ]]; do
55+
case "$1" in
56+
--ref) ref="$2"; shift 2 ;;
57+
--ref=*) ref="${1#*=}"; shift ;;
58+
--minor) minor="$2"; shift 2 ;;
59+
--minor=*) minor="${1#*=}"; shift ;;
60+
--ingest) ingest_dir="$2"; shift 2 ;;
61+
--ingest=*) ingest_dir="${1#*=}"; shift ;;
62+
--commit-shas) commit_shas_file="$2"; shift 2 ;;
63+
--commit-shas=*) commit_shas_file="${1#*=}"; shift ;;
64+
--profile) profile="$2"; shift 2 ;;
65+
--profile=*) profile="${1#*=}"; shift ;;
66+
--force) force=1; shift ;;
67+
--json) json_only=1; shift ;;
68+
-h|--help) sed -n '2,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
69+
*) echo "release rehearse: unknown arg '$1'" >&2; exit 5 ;;
70+
esac
71+
done
72+
73+
# ---------------------------------------------------------------------------
74+
# Phase 1: emit the MCP dispatch/poll/download plan (no --ingest given).
75+
# ---------------------------------------------------------------------------
76+
if [[ -z "$ingest_dir" ]]; then
77+
inputs='{"rehearsal": true}'
78+
[[ -n "$minor" ]] && inputs='{"rehearsal": true, "minor_version": "'"$minor"'"}'
79+
80+
if [[ "$json_only" -eq 1 ]]; then
81+
REF="$ref" INPUTS="$inputs" REPO="$BUILD_REPO" WF="$RELEASE_WORKFLOW" \
82+
ART="$REHEARSAL_ARTIFACT" LIBS="${LIBRARIES[*]}" python3 -c '
83+
import json, os
84+
print(json.dumps({
85+
"agent": "release", "mode": "rehearse", "phase": "dispatch-plan",
86+
"steps": [
87+
{"step": "dispatch", "mcp_tool": "mcp__github__actions_run_trigger",
88+
"repo": os.environ["REPO"], "workflow": os.environ["WF"],
89+
"ref": os.environ["REF"], "inputs": json.loads(os.environ["INPUTS"])},
90+
{"step": "poll", "mcp_tool": "mcp__github__actions_get",
91+
"until": "status == completed", "note": "artifact exists IFF all 5 wheels built+installed"},
92+
{"step": "download", "artifact": os.environ["ART"],
93+
"note": "download into an artifacts dir (rehearsal.json + testpypi_version.txt)"},
94+
{"step": "capture-heads", "mcp_tool": "mcp__github__get_commit",
95+
"repos": os.environ["LIBS"].split(),
96+
"note": "write {repo: sha} of each library main HEAD to commit_shas.json in the dir"},
97+
{"step": "ingest", "cmd": "pyauto-brain release rehearse --ingest <dir> --commit-shas <dir>/commit_shas.json"},
98+
],
99+
}, indent=2))
100+
'
101+
exit 0
102+
fi
103+
104+
cat <<EOF
105+
== release agent: release-validation rehearsal (dispatch plan) ==
106+
107+
Bash cannot call GitHub; execute these steps with Brain's MCP GitHub tools
108+
(cloud/mobile has no gh). Heart never dispatches — this is the Release Agent's job.
109+
110+
1. DISPATCH the M1 TestPyPI rehearsal:
111+
mcp__github__actions_run_trigger
112+
repo: $BUILD_REPO
113+
workflow: $RELEASE_WORKFLOW
114+
ref: $ref
115+
inputs: $inputs
116+
117+
2. POLL the run to completion (mcp__github__actions_get / actions_list).
118+
The '$REHEARSAL_ARTIFACT' artifact is produced IFF all five wheels built,
119+
uploaded, and installed from TestPyPI — its presence IS the success signal.
120+
121+
3. DOWNLOAD the '$REHEARSAL_ARTIFACT' artifact into an artifacts directory
122+
(it contains rehearsal.json + testpypi_version.txt).
123+
124+
4. CAPTURE the current main HEAD sha of each library and write them as
125+
{repo: sha} to <dir>/commit_shas.json — so Heart can confirm the report is
126+
for THIS source (readiness matches them against the live main HEADs):
127+
mcp__github__get_commit for: ${LIBRARIES[*]}
128+
129+
5. INGEST + get the verdict (this script, phase 2):
130+
pyauto-brain release rehearse --ingest <dir> --commit-shas <dir>/commit_shas.json
131+
132+
After ingest, the Health Agent (read-only) reports GREEN/YELLOW/RED from the
133+
freshly-ingested validation_report — it does NOT dispatch anything.
134+
EOF
135+
exit 0
136+
fi
137+
138+
# ---------------------------------------------------------------------------
139+
# Phase 2: ingest the downloaded artifacts, then consult the Health Agent.
140+
# ---------------------------------------------------------------------------
141+
if [[ ! -d "$ingest_dir" && ! -f "$ingest_dir" ]]; then
142+
echo "release rehearse: artifacts path '$ingest_dir' not found" >&2
143+
exit 1
144+
fi
145+
146+
heart="$(resolve_heart)" || exit $?
147+
148+
ingest_args=(validate --ingest "$ingest_dir")
149+
[[ -n "$commit_shas_file" ]] && ingest_args+=(--commit-shas "$commit_shas_file")
150+
[[ -n "$profile" ]] && ingest_args+=(--profile "$profile")
151+
152+
[[ "$json_only" -eq 1 ]] || echo "== release agent: handing artifacts to pyauto-heart validate --ingest =="
153+
if ! "$heart" "${ingest_args[@]}"; then
154+
echo "release rehearse: pyauto-heart validate --ingest failed" >&2
155+
exit 1
156+
fi
157+
158+
# Consult the sibling Health Agent (read-only) for the verdict. --refresh runs a
159+
# fresh Heart tick so state.json re-aggregates the just-written
160+
# validation_report.json before readiness recomputes the gate.
161+
[[ "$json_only" -eq 1 ]] || echo "== release agent: consulting Health Agent for the release-validation verdict =="
162+
verdict="$(consult_health_agent_verdict --refresh)"
163+
164+
eff="$verdict"; [[ "$eff" == "unknown" ]] && eff="yellow"
165+
decision=""; decision_code=0; blockers=(); warnings=(); next=()
166+
[[ "$verdict" == "unknown" ]] && warnings+=("Readiness verdict unknown; treated as YELLOW.")
167+
168+
case "$eff" in
169+
green)
170+
decision="release-ready"; decision_code=0
171+
next+=("Source built, TestPyPI-installed, and validated at release fidelity. A human/Release-Agent may promote to PyPI via Hands/Build.")
172+
;;
173+
yellow)
174+
if [[ "$force" -eq 1 ]]; then
175+
decision="proceed-with-caution"; decision_code=0
176+
warnings+=("Readiness YELLOW; proceeding under --force.")
177+
else
178+
decision="hold"; decision_code=2
179+
blockers+=("Readiness is YELLOW — likely no fresh release-fidelity run yet, stale rehearsal, or source moved. See 'pyauto-brain health'. Re-run with --force to accept the caution.")
180+
fi
181+
;;
182+
red)
183+
decision="blocked"; decision_code=3
184+
blockers+=("Readiness is RED — a validation stage failed or a hard blocker is present. Fix before releasing.")
185+
;;
186+
*)
187+
decision="unknown"; decision_code=4
188+
blockers+=("Could not obtain a readiness verdict ('$verdict').")
189+
;;
190+
esac
191+
192+
emit_decision() {
193+
HEALTH="$verdict" DECISION="$decision" DIR="$ingest_dir" \
194+
WARNINGS="$(printf '%s\n' "${warnings[@]:-}")" \
195+
BLOCKERS="$(printf '%s\n' "${blockers[@]:-}")" \
196+
NEXT="$(printf '%s\n' "${next[@]:-}")" \
197+
python3 -c '
198+
import json, os
199+
def lines(k): return [x for x in os.environ.get(k, "").splitlines() if x.strip()]
200+
print(json.dumps({
201+
"agent": "release", "mode": "rehearse", "phase": "verdict",
202+
"artifacts": os.environ["DIR"],
203+
"health_status": os.environ["HEALTH"],
204+
"decision": os.environ["DECISION"],
205+
"warnings": lines("WARNINGS"),
206+
"blockers": lines("BLOCKERS"),
207+
"next_steps": lines("NEXT"),
208+
}, indent=2))
209+
'
210+
}
211+
212+
if [[ "$json_only" -eq 1 ]]; then
213+
emit_decision
214+
else
215+
echo "-- ReleaseValidationDecision --"
216+
emit_decision
217+
echo
218+
"$heart" readiness || true
219+
fi
220+
221+
exit "$decision_code"

agents/release/release.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ set -uo pipefail
2525
HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
2626
source "$HERE/../_common.sh"
2727

28+
# `release rehearse ...` drives the M2 release-VALIDATION rehearsal (dispatch the
29+
# TestPyPI rehearsal, ingest the report into Heart, consult the Health Agent) —
30+
# distinct from the real-release delegation below. Route it to rehearse.sh.
31+
if [[ "${1:-}" == "rehearse" ]]; then
32+
shift
33+
exec bash "$HERE/rehearse.sh" "$@"
34+
fi
35+
2836
force=0
2937
forward=()
3038
while [[ $# -gt 0 ]]; do

bin/pyauto-brain

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# pyauto-brain feature [args] reason over PyAutoMind feature tasks, plan growth
1111
# pyauto-brain build [args] coordinate execution: consult health, run Build
1212
# pyauto-brain release [args] reason about readiness, then release on green
13+
# (release rehearse: drive a TestPyPI rehearsal,
14+
# ingest the validation report, judge via Health)
1315
# pyauto-brain health [args] reason over the PyAutoHeart health surface
1416
# pyauto-brain help [name] list agents or show one agent's docs
1517
#
@@ -31,7 +33,7 @@ declare -A AGENT_SCRIPT=(
3133
declare -A AGENT_DESC=(
3234
[feature]="Reason over PyAutoMind feature tasks: select, size, phase, plan for start_dev"
3335
[build]="Coordinate execution: consult the Health Agent, then delegate to PyAutoBuild"
34-
[release]="Reason about pyauto-heart readiness, then run the Build release on green"
36+
[release]="Reason about readiness + run release on green; 'release rehearse' drives TestPyPI validation"
3537
[health]="Reason over the PyAutoHeart monitoring / readiness surface"
3638
)
3739
AGENT_ORDER=(feature build release health)

0 commit comments

Comments
 (0)