From b652c0b2cf0f854010682a3e62008ca318c6ab1a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 16:43:59 +0000 Subject: [PATCH 1/2] infra: generate the command surface into AGENTS.md (agent-agnostic discovery) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec 2 (command_surface_in_agents_md), agent-agnostic layer only: - bin/install.sh: add --write-agents-surface / --check-agents-surface modes that generate a drift-checked command-surface block (verb · purpose · bin/pyauto-brain ) into each organ's auto-loaded AGENTS.md. Unified block (all verbs, hosted in Brain), so a single-repo web session still discovers every verb; skips absent repos / repos without markers. - bin/pyauto-brain: guard `main "$@"` on direct execution so the installer can source the agent registry as the single source of truth (no second list). - AGENTS.md: replace the hand-maintained `## Running` list with the generated block. - tests/test_skill_install.py: assert the committed block is current and covers every public agent — wires the drift check into Brain CI. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KUGujq6jAgU3CExnLkkELe --- AGENTS.md | 51 ++++++++++---- bin/install.sh | 132 +++++++++++++++++++++++++++++++++++- bin/pyauto-brain | 8 ++- tests/test_skill_install.py | 28 ++++++++ 4 files changed, 202 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3423b1d..b294797 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -202,21 +202,42 @@ only reasons. ## Running -```bash -bin/pyauto-brain help # list agents -bin/pyauto-brain intake "add data cube modelling to autolens" # conceive: raw text -> a formal Mind prompt (dry-run) -bin/pyauto-brain intake --apply ideas # sweep ideas.md into formal prompts -bin/pyauto-brain feature # select the best next PyAutoMind feature task -bin/pyauto-brain feature feature/autofit/sbi.md # plan a specific feature task -bin/pyauto-brain refactor # select + plan the best next behaviour-preserving refactor -bin/pyauto-brain refactor candidates # mine the refactor backlog + ideas.md (read-only) -bin/pyauto-brain build # consult vitals, then delegate execution to Build -bin/pyauto-brain build --dry-run # reason + plan only (emit the BuildDecision) -bin/pyauto-brain release # reason about readiness, then release on green -bin/pyauto-brain health # (conductor) run the health loop with a human, toward green -bin/pyauto-brain vitals # (faculty) one tick + the unified dashboard card (raw read) -bin/pyauto-brain review --task # (faculty) a branch's ReviewSurface for the ship gate's review leg -``` + + + +The PyAuto **command surface** — every agent verb, runnable on any tool (Claude, +Codex, Cursor; CLI or web) as `bin/pyauto-brain `. Discovery is universal +— this block travels in every organ's auto-loaded AGENTS.md — but *invocation* +needs **PyAutoBrain** checked out, since that organ hosts the entrypoint. On +Claude Code the same verbs are also the `/` slash commands. + +**Conductors** — front doors you drive (decide *and* act): + +| Verb | Purpose | Entrypoint | +|------|---------|------------| +| `intake` | Conceive a task: turn raw input into a formal, headed PyAutoMind prompt (files it; never starts dev) | `bin/pyauto-brain intake` | +| `feature` | Reason over PyAutoMind feature tasks: select, size, phase, plan for start_dev | `bin/pyauto-brain feature` | +| `bug` | The immune system: classify a bug/regression/Heart finding, locate the fix, plan the repair | `bin/pyauto-brain bug` | +| `refactor` | The renewal function: plan behaviour-preserving restructuring — RefactorDecision; default-safe under --auto | `bin/pyauto-brain refactor` | +| `profiling` | The proprioceptive function — the organism's sense of its own effort: campaign/ingest/triage plans over the autolens_profiling workspace — ProfilingDecision | `bin/pyauto-brain profiling` | +| `hygiene` | The maintenance function — the organism's sense of its own upkeep: code-quality debt (dev-loop cost + tidiness), delegating fixes — HygieneDecision | `bin/pyauto-brain hygiene` | +| `clone` | The Mitosis Agent (v0: decision only): partition the reference assistant, analyze the domain, emit the CloneDecision — never writes | `bin/pyauto-brain clone` | +| `build` | Coordinate execution: consult the vitals faculty, then delegate to PyAutoBuild | `bin/pyauto-brain build` | +| `release` | Release door → the Build Agent release mode (single gate); 'release rehearse'/'release validate' drive release validation; 'release nightly' is the scheduled-nightly driver | `bin/pyauto-brain release` | +| `health` | The organism's clinician: run the health loop with a human, dispatch by dispatch, toward green | `bin/pyauto-brain health` | + +**Faculties** — read-only opinions the conductors consult (also runnable): + +| Verb | Purpose | Entrypoint | +|------|---------|------------| +| `vitals` | Read-only: read the Heart's pulse — the PyAutoHeart readiness verdict (consulted by the conductors) | `bin/pyauto-brain vitals` | +| `review` | Read-only: prepare the branch ReviewSurface — the reviewing agent maps it to CLEAN/FINDINGS/BLOCKED (the ship gate's review leg) | `bin/pyauto-brain review` | +| `memory` | Read-only: recall what the organism knows — a cited digest over PyAutoMemory, autolens_assistant and Mind history | `bin/pyauto-brain memory` | +| `samplers` | Read-only: the motor faculty — SamplerSurface digest over the sampler script tiers, the PyAutoFit search catalogue and the benchmark record | `bin/pyauto-brain samplers` | + Like the other PyAuto repos, PyAutoBrain runs from its checkout (no pip install); it resolves the sibling `pyauto-heart` and `autobuild` binaries from PATH or the diff --git a/bin/install.sh b/bin/install.sh index 74c8c61..3f31bd8 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -28,7 +28,16 @@ # Safe to re-run — existing symlinks are replaced, non-symlink files are skipped. # # Usage: -# bash PyAutoBrain/bin/install.sh +# bash PyAutoBrain/bin/install.sh # install skills/commands +# bash PyAutoBrain/bin/install.sh --write-agents-surface # (re)generate the command +# # surface block in each organ AGENTS.md +# bash PyAutoBrain/bin/install.sh --check-agents-surface # drift-check that block (exit 1 on drift) +# +# The command-surface modes are the agent-agnostic half of command discovery: +# per-tool symlinks (above) are absent in cloud/web sessions, which load only +# committed repo files — so the verb → purpose → `bin/pyauto-brain ` index +# is generated into each organ's auto-loaded AGENTS.md, sourced from the single +# agent registry in `bin/pyauto-brain`. set -euo pipefail @@ -44,6 +53,127 @@ PROFILING_SKILLS_DIR="$PYAUTO_ROOT/autolens_profiling/skills" CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}" CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" +# ---------- Command surface (agent-agnostic discovery) ---------- +# +# The organ repos whose auto-loaded AGENTS.md carries the generated command +# surface. Framework identity (a fork keeps the five organs), so it is fixed +# here — like the skills dirs above — rather than parsed out of repos.yaml. A +# repo opts in by adding the marker pair; absent repos / repos without markers +# are skipped, so this runs in a partial/web checkout. +COMMANDS_BEGIN="" +COMMANDS_END="" +ORGAN_REPOS=(PyAutoMind PyAutoBrain PyAutoHeart PyAutoBuild PyAutoMemory) + +# Emit the canonical command-surface block (identical in every organ) to stdout, +# sourced from the agent registry in bin/pyauto-brain — the single source of +# truth for `bin/pyauto-brain `. Sourcing defines the arrays without +# running the dispatcher (its main is guarded on direct execution). +agents_surface_block() { + # shellcheck disable=SC1090 + source "$SCRIPT_DIR/pyauto-brain" + echo "$COMMANDS_BEGIN" + cat <<'EOF' + + +The PyAuto **command surface** — every agent verb, runnable on any tool (Claude, +Codex, Cursor; CLI or web) as `bin/pyauto-brain `. Discovery is universal +— this block travels in every organ's auto-loaded AGENTS.md — but *invocation* +needs **PyAutoBrain** checked out, since that organ hosts the entrypoint. On +Claude Code the same verbs are also the `/` slash commands. + +**Conductors** — front doors you drive (decide *and* act): + +| Verb | Purpose | Entrypoint | +|------|---------|------------| +EOF + local name desc + for name in "${CONDUCTOR_ORDER[@]}"; do + desc="${AGENT_DESC[$name]//|/\\|}" + printf '| `%s` | %s | `bin/pyauto-brain %s` |\n' "$name" "$desc" "$name" + done + cat <<'EOF' + +**Faculties** — read-only opinions the conductors consult (also runnable): + +| Verb | Purpose | Entrypoint | +|------|---------|------------| +EOF + for name in "${FACULTY_ORDER[@]}"; do + desc="${AGENT_DESC[$name]//|/\\|}" + printf '| `%s` | %s | `bin/pyauto-brain %s` |\n' "$name" "$desc" "$name" + done + echo "$COMMANDS_END" +} + +# Splice the block (read from a file, so no awk -v escape processing) between the +# markers in one AGENTS.md, replacing whatever is there. +_splice_surface() { + local file="$1" blockfile="$2" tmp + tmp="$(mktemp)" + awk -v begin="$COMMANDS_BEGIN" -v end="$COMMANDS_END" -v bf="$blockfile" ' + $0 == begin { while ((getline line < bf) > 0) print line; close(bf); skip=1; next } + $0 == end { skip=0; next } + !skip { print } + ' "$file" > "$tmp" + if cmp -s "$file" "$tmp"; then + echo "unchanged: $file"; rm -f "$tmp" + else + mv "$tmp" "$file"; echo "updated: $file" + fi +} + +write_agents_surface() { + local blockfile repo agents + blockfile="$(mktemp)"; agents_surface_block > "$blockfile" + for repo in "${ORGAN_REPOS[@]}"; do + agents="$PYAUTO_ROOT/$repo/AGENTS.md" + if [ ! -f "$agents" ]; then + echo "skipped (absent): $agents"; continue + fi + if ! grep -qF -- "$COMMANDS_BEGIN" "$agents" || ! grep -qF -- "$COMMANDS_END" "$agents"; then + echo "skipped (no command markers): $agents"; continue + fi + _splice_surface "$agents" "$blockfile" + done + rm -f "$blockfile" +} + +check_agents_surface() { + local blockfile repo agents cur drift=0 + blockfile="$(mktemp)"; agents_surface_block > "$blockfile" + for repo in "${ORGAN_REPOS[@]}"; do + agents="$PYAUTO_ROOT/$repo/AGENTS.md" + if [ ! -f "$agents" ]; then + echo "skipped (absent): $agents"; continue + fi + if ! grep -qF -- "$COMMANDS_BEGIN" "$agents"; then + echo "skipped (no command markers): $agents"; continue + fi + cur="$(mktemp)" + awk -v begin="$COMMANDS_BEGIN" -v end="$COMMANDS_END" ' + $0 == begin { grab=1 } grab { print } $0 == end { grab=0 }' "$agents" > "$cur" + if cmp -s "$cur" "$blockfile"; then + echo "OK: $agents" + else + echo "DRIFT: $agents — run: bash PyAutoBrain/bin/install.sh --write-agents-surface" + drift=1 + fi + rm -f "$cur" + done + rm -f "$blockfile" + return "$drift" +} + +case "${1:-}" in + --write-agents-surface) write_agents_surface; exit 0 ;; + --check-agents-surface) check_agents_surface; exit $? ;; + --help|-h) + sed -n '2,20p' "$0"; exit 0 ;; +esac + # ---------- Execution-environment note ---------- # # Skill discovery is identical in every execution environment (local-dev, diff --git a/bin/pyauto-brain b/bin/pyauto-brain index 5c9d222..6c6e8c2 100755 --- a/bin/pyauto-brain +++ b/bin/pyauto-brain @@ -140,4 +140,10 @@ main() { exec bash "$script" "$@" } -main "$@" +# Only dispatch when executed directly. When sourced (e.g. by +# bin/install.sh --write-agents-surface, which reuses the AGENT_DESC registry +# below as the single source of the command surface) the arrays are defined but +# main is not run. +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/tests/test_skill_install.py b/tests/test_skill_install.py index 899fa3e..8f31ea6 100644 --- a/tests/test_skill_install.py +++ b/tests/test_skill_install.py @@ -132,3 +132,31 @@ def test_invalid_codex_name_does_not_suppress_claude_surfaces(tmp_path): assert (claude_home / "commands" / "legacy_skill.md").is_symlink() assert not (codex_home / "skills" / "legacy_skill").exists() assert "Codex skill name invalid" in result.stdout + + +def test_command_surface_block_is_current(): + """This repo's committed command-surface block matches the agent registry. + Guards against editing bin/pyauto-brain without regenerating the block + (bash PyAutoBrain/bin/install.sh --write-agents-surface).""" + result = subprocess.run( + ["bash", str(INSTALLER), "--check-agents-surface"], + cwd=BRAIN_HOME, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert "DRIFT" not in result.stdout + + +def test_command_surface_covers_every_public_agent(): + agents = public_agents() + assert agents + text = (BRAIN_HOME / "AGENTS.md").read_text() + begin = text.index("") + end = text.index("") + surface = text[begin:end] + missing = [ + name for name in sorted(agents) + if f"`bin/pyauto-brain {name}`" not in surface + ] + assert missing == [] From b4f9450811ed5b58e9a87a6596d20b1e8d0d3b6b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 17:08:13 +0000 Subject: [PATCH 2/2] docs: command surface lives once in Brain (always loaded), not per-organ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflect the Brain-only design in the generated block's intro and the generator comments: PyAutoBrain is loaded in every session, so its auto-loaded AGENTS.md carries the command surface everywhere — no per-organ copy. Regenerate Brain's block with the corrected wording. No change to the verb data. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KUGujq6jAgU3CExnLkkELe --- AGENTS.md | 9 +++++---- bin/install.sh | 15 +++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b294797..b38f33f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -209,10 +209,11 @@ only reasons. `PyAutoBrain/bin/install.sh --check-agents-surface`. --> The PyAuto **command surface** — every agent verb, runnable on any tool (Claude, -Codex, Cursor; CLI or web) as `bin/pyauto-brain `. Discovery is universal -— this block travels in every organ's auto-loaded AGENTS.md — but *invocation* -needs **PyAutoBrain** checked out, since that organ hosts the entrypoint. On -Claude Code the same verbs are also the `/` slash commands. +Codex, Cursor; CLI or web) as `bin/pyauto-brain `. This block lives once in +**PyAutoBrain**'s auto-loaded AGENTS.md, which is present in every session, so the +full verb set is always in context — no per-organ copy needed. Invoking a verb +runs its entrypoint here in PyAutoBrain. On Claude Code the same verbs are also +the `/` slash commands. **Conductors** — front doors you drive (decide *and* act): diff --git a/bin/install.sh b/bin/install.sh index 3f31bd8..e87c769 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -36,8 +36,10 @@ # The command-surface modes are the agent-agnostic half of command discovery: # per-tool symlinks (above) are absent in cloud/web sessions, which load only # committed repo files — so the verb → purpose → `bin/pyauto-brain ` index -# is generated into each organ's auto-loaded AGENTS.md, sourced from the single -# agent registry in `bin/pyauto-brain`. +# is generated into PyAutoBrain's auto-loaded AGENTS.md (Brain is loaded in every +# session, so one copy reaches everywhere), sourced from the single agent registry +# in `bin/pyauto-brain`. Other organs opt in via the markers but need not — Brain's +# copy already travels with every session. set -euo pipefail @@ -79,10 +81,11 @@ agents_surface_block() { `PyAutoBrain/bin/install.sh --check-agents-surface`. --> The PyAuto **command surface** — every agent verb, runnable on any tool (Claude, -Codex, Cursor; CLI or web) as `bin/pyauto-brain `. Discovery is universal -— this block travels in every organ's auto-loaded AGENTS.md — but *invocation* -needs **PyAutoBrain** checked out, since that organ hosts the entrypoint. On -Claude Code the same verbs are also the `/` slash commands. +Codex, Cursor; CLI or web) as `bin/pyauto-brain `. This block lives once in +**PyAutoBrain**'s auto-loaded AGENTS.md, which is present in every session, so the +full verb set is always in context — no per-organ copy needed. Invoking a verb +runs its entrypoint here in PyAutoBrain. On Claude Code the same verbs are also +the `/` slash commands. **Conductors** — front doors you drive (decide *and* act):