Skip to content

Commit 8db0c8e

Browse files
authored
Merge pull request #137 from PyAutoLabs/feature/autocti-assistant
feat(clone): classify the reference boundary and guard it per-PR
2 parents 010122d + 46cea93 commit 8db0c8e

10 files changed

Lines changed: 213 additions & 34 deletions

File tree

AGENTS.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,15 @@ humans invoke identically, so behaviour isn't re-derived from prose each time.
182182
leg → re-judge — until Heart goes GREEN. Delegates all dispatch to the
183183
release conductor. Current scope is *validate + recommend*, checkpointing
184184
every dispatch; edit-in fixes are an explicit follow-up. (Skeleton.)
185-
- **`agents/conductors/clone/`** — the *Mitosis Agent* (**analysis-only v0**
186-
see its `DESIGN.md`): analyzes how to reproduce a mature domain assistant
187-
(reference: `autolens_assistant`) into a new specialised assistant cell —
188-
domain analysis, template-boundary partition, a `CloneDecision` with an
185+
- **`agents/conductors/clone/`** — the *Mitosis Agent* (see its `DESIGN.md`):
186+
analyzes how to reproduce a mature domain assistant (reference:
187+
`autolens_assistant`) into a new specialised assistant cell — domain analysis,
188+
template-boundary partition, a `CloneDecision` with an
189189
exact-clone/sibling/seed question, generation delegated to Build, newborn
190-
validation by Heart. Its current CLI emits the decision and writes nothing.
190+
validation by Heart. Bare `clone` decides and writes nothing;
191+
`--apply --mode lightweight-seed` hands the plan to Build's `clone_seed.py`,
192+
which gives birth (private until Heart's publish gate). The other two clone
193+
modes are v2 and refused.
191194

192195
### Faculties
193196

@@ -262,7 +265,7 @@ the `/<verb>` slash commands.
262265
| `eyes` | The perceptive function — the organism's sense of its own appearance: survey/review a visualization workspace's figure surface, critiques route to intake/start_dev — EyesSurvey/EyesReviewSurface (never renders, never edits) | `bin/pyauto-brain eyes` |
263266
| `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` |
264267
| `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` |
265-
| `clone` | The Mitosis Agent (v0: decision only): partition the reference assistant, analyze the domain, emit the CloneDecision — never writes | `bin/pyauto-brain clone` |
268+
| `clone` | The Mitosis Agent: partition the reference assistant, analyze the domain, emit the CloneDecision; --apply --mode lightweight-seed delegates the seed birth to Build | `bin/pyauto-brain clone` |
266269
| `build` | Coordinate execution: consult the vitals faculty, then delegate to PyAutoBuild | `bin/pyauto-brain build` |
267270
| `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` |
268271
| `health` | The organism's clinician: run the health loop with a human, dispatch by dispatch, toward green | `bin/pyauto-brain health` |

agents/conductors/clone/AGENTS.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Clone Agent (the Mitosis Agent)
22

33
> **Tier: conductor** — a front-door agent you *drive*. It decides **and**
4-
> (from v1) delegates execution to PyAutoBuild. **v0 — the current state —
5-
> is decision only**: `analyze` mode emits a CloneDecision and writes
6-
> nothing. The agreed design, phasing and boundary rules live in
4+
> delegates execution to PyAutoBuild. Bare `clone` is decision only: it emits
5+
> a CloneDecision and writes nothing. **`--apply --mode lightweight-seed` is
6+
> live** — it hands a generation plan to Build's `clone_seed.py`, which gives
7+
> birth; `exact-clone` and `differentiated-sibling` remain v2 and are refused.
8+
> The agreed design, phasing and boundary rules live in
79
> [`DESIGN.md`](DESIGN.md) — this file is the operating summary.
810
911
Reproduces a mature domain assistant (reference: `autolens_assistant`) into
@@ -12,6 +14,9 @@ a new specialised assistant cell for another library + workspace [+ HowTo].
1214
```bash
1315
bin/pyauto-brain clone PyAutoFit --workspace autofit_workspace --howto HowToFit
1416
bin/pyauto-brain clone <library> --workspace <repo> [--reference <repo>] [--json]
17+
# give birth (the mode is the human's answer to the clone-mode question):
18+
bin/pyauto-brain clone <library> --workspace <repo> --apply --mode lightweight-seed
19+
# ... add --no-push to build the seed tree in scratch without creating a repo
1520
```
1621

1722
What analyze does: domain analysis (library public API via `ast`, workspace
@@ -24,9 +29,18 @@ per set; the Heart validation legs a newborn must pass; risks; and the
2429
**mandatory clone-mode question** (exact-clone | differentiated-sibling |
2530
lightweight-seed) a human answers before any `--apply`.
2631

32+
An unclassified file **blocks `--apply`** (exit 4) — deliberate pressure that
33+
keeps the boundary complete. The reference's own CI runs that check per-PR via
34+
`check_boundary.py`, so the author who adds a file classifies it rather than
35+
whoever next tries to give birth.
36+
2737
Hard rules (from DESIGN.md): never writes repos/files/GitHub state itself
28-
(execution is Build's, v1+); never copies domain content across domains;
29-
never modifies the reference; never embeds PyAutoMemory content in a public
30-
assistant; a newborn is not announced before its Heart validation legs pass.
38+
(birth is Build's — this agent hands over a plan); never copies domain content
39+
across domains; never modifies the reference; never embeds PyAutoMemory content
40+
in a public assistant; a newborn is born private and is not flipped public or
41+
announced before its Heart validation legs pass
42+
(`PyAutoHeart/docs/newborn_validation.md`).
3143

32-
Exit codes: `0` decision · `4` inputs unresolvable · `5` bad usage/--apply-in-v0.
44+
Exit codes: `0` decision · `4` inputs unresolvable (incl. an unclassified
45+
boundary, or Build's birth failing) · `5` bad usage (`--apply` without
46+
`--mode lightweight-seed` — the v2 modes are refused here).

agents/conductors/clone/DESIGN.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# Clone agent — design (not yet implemented)
2-
3-
> **Status: v0 implemented** (analyze — decision only; PyAutoBrain#73). v1
4-
> (lightweight-seed births via Build) and v2 (differentiated siblings) remain
5-
> unimplemented; this document stays the agreed design they will be built
6-
> against. The operating summary is [`AGENTS.md`](AGENTS.md). Filed from
1+
# Clone agent — design
2+
3+
> **Status: v0 + v1 implemented.** v0 = `analyze` (decision only;
4+
> PyAutoBrain#73). v1 = `--apply --mode lightweight-seed`, which hands the
5+
> generation plan to Build's `clone_seed.py` for the birth, gated by Heart's
6+
> newborn-validation publish checklist. v2 (differentiated siblings, `audit`
7+
> mode) remains unimplemented; this document stays the agreed design it will be
8+
> built against. The operating summary is [`AGENTS.md`](AGENTS.md). Filed from
79
> `PyAutoMind/issued/clone_mitosis_agent.md` (PyAutoBrain #59).
810
911
> **Tier: conductor** — a front-door agent you *drive*. Engineering name:
@@ -115,12 +117,13 @@ bad usage).
115117

116118
## Phased implementation (each phase = one future Mind prompt, filed when its predecessor nears shipping — never bulk-issued)
117119

118-
1. **v0 — decision only.** `analyze` mode end-to-end: domain analysis,
120+
1. **v0 — decision only. SHIPPED.** `analyze` mode end-to-end: domain analysis,
119121
partition, generation plan, CloneDecision. No writer at all. Proves the
120122
reasoning on `PyAutoFit → autofit_assistant` as the dry-run case study.
121-
2. **v1 — seed births.** `--apply` for **lightweight-seed** via a PyAutoBuild
122-
primitive (repo creation + generic-set copy + scaffolds). Heart gains the
123-
newborn-validation checklist. First real birth: `autofit_assistant` seed.
123+
2. **v1 — seed births. SHIPPED.** `--apply` for **lightweight-seed** via a
124+
PyAutoBuild primitive (`autobuild/clone_seed.py`: repo creation + generic-set
125+
copy + scaffolds + the `PENDING.md` growth queue). Heart gained the
126+
newborn-validation checklist (`PyAutoHeart/docs/newborn_validation.md`).
124127
3. **v2 — differentiated siblings.** Skill/wiki regeneration from the domain
125128
corpus (model-assisted, curated per the reference's `al_update_wiki`
126129
philosophy: generated drafts, human-reviewed PRs). `audit` mode lands here.

agents/conductors/clone/_clone.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
"skills/start-new-project*", "skills/contribute-upstream*",
5959
"sources.yaml", "sources/*", # the source-registry pattern
6060
"autoassistant/*", # API gate + wiki-currency + benchmark tooling
61+
".mcp.json", # wires the results-inspector MCP, which is
62+
# `autoassistant.mcp` — generic tooling above,
63+
# so the wiring carries no domain either
6164
"benchmarks/AGENTS.md", # benchmark run/record contract
6265
".github/*", # wiki-currency / citation workflows
6366
"wiki/README.md", "wiki/project/*", # project wiki rules + profile template
@@ -102,8 +105,15 @@
102105
"skills/al_*.md", # every al_* skill body
103106
".claude/skills/al_*.md", # ... and their harness mirrors
104107
"skills/init-slam.md", ".claude/skills/init-slam.md", # SLAM = lensing
108+
# The euclid mode: a survey-specific pipeline register (its skills
109+
# + its own sub-wiki). Lensing science throughout — a newborn grows
110+
# whatever survey modes its own domain has, if any.
111+
"skills/euclid_*.md", ".claude/skills/euclid_*.md",
112+
"wiki/euclid/*",
105113
"wiki/core/*", # lensing-API reference
106114
"wiki/literature/*", # a shipped lensing paper corpus
115+
"paper/*", # this assistant's own JOSS paper
116+
"scripts/*.py", # bundled science scripts (a named lens)
107117
*_SHARED_DOMAIN,
108118
],
109119
"mixed": _SHARED_MIXED,
@@ -319,8 +329,10 @@ def build_decision(args):
319329
"validation_plan": VALIDATION_PLAN,
320330
"risks": risks or ["none identified"],
321331
"next_action": (
322-
"human confirms the clone mode → v1 hands the plan to PyAutoBuild "
323-
"(lightweight-seed first); v0 writes nothing"
332+
"human confirms the clone mode + the repo-creation gate (name / "
333+
"owner / visibility), then re-run with --apply --mode "
334+
"lightweight-seed to hand the plan to Build (--no-push builds the "
335+
"seed tree only); this run wrote nothing"
324336
),
325337
}
326338

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python
2+
"""Assert every tracked file in a reference assistant falls on one side of the
3+
template boundary.
4+
5+
`_clone.py`'s partition already refuses to birth a newborn while any reference
6+
file is unclassified — deliberate pressure that keeps the boundary notes
7+
complete. But that pressure only lands when someone tries to give birth, which
8+
is rare, and by then the person blocked is not the person who added the file:
9+
they must go and classify someone else's work in a repo they may not know.
10+
11+
This runs the same check at PR time in the reference's own CI, so the author who
12+
adds a file classifies it while they still remember whether it is framework or
13+
science. Three features (the euclid mode, the JOSS paper, the results-inspector
14+
MCP wiring) each landed unclassified and silently blocked every future birth
15+
before this existed.
16+
17+
Usage:
18+
python check_boundary.py <reference-checkout> [--reference <name>]
19+
20+
`--reference` defaults to the checkout's directory name, which is how the
21+
profiles are keyed. A checkout with no profile is not a clone reference and has
22+
no boundary to grade, so it *skips* — this matters because a newborn inherits
23+
this check in its `.github/` and is not itself a reference until someone adds a
24+
profile for it. An explicitly-passed `--reference` that has no profile is still
25+
an error, so a typo cannot pass silently.
26+
27+
Exit codes: `0` boundary complete (or not a reference) · `1` unclassified
28+
files · `4` bad inputs.
29+
"""
30+
31+
import argparse
32+
import importlib.util
33+
import sys
34+
from pathlib import Path
35+
36+
37+
def _load_clone():
38+
spec = importlib.util.spec_from_file_location(
39+
"_clone_boundary", Path(__file__).resolve().parent / "_clone.py"
40+
)
41+
mod = importlib.util.module_from_spec(spec)
42+
spec.loader.exec_module(mod)
43+
return mod
44+
45+
46+
def main():
47+
parser = argparse.ArgumentParser(description=__doc__)
48+
parser.add_argument("checkout", help="path to the reference assistant checkout")
49+
parser.add_argument(
50+
"--reference",
51+
default=None,
52+
help="profile name to grade against (default: the checkout's dir name)",
53+
)
54+
args = parser.parse_args()
55+
56+
root = Path(args.checkout).resolve()
57+
if not (root / ".git").exists():
58+
print(f"check_boundary: not a git checkout: {root}", file=sys.stderr)
59+
return 4
60+
61+
clone = _load_clone()
62+
name = args.reference or root.name
63+
profile = clone.REFERENCE_PROFILES.get(name)
64+
if profile is None:
65+
if args.reference is not None:
66+
# Explicitly named: a typo must not pass silently.
67+
print(
68+
f"check_boundary: no clone profile for '{name}' — supported: "
69+
f"{', '.join(sorted(clone.REFERENCE_PROFILES))}",
70+
file=sys.stderr,
71+
)
72+
return 4
73+
# Inferred: this repo is not a clone reference, so it has no boundary to
74+
# grade. A newborn inherits this check and lands here until (and unless)
75+
# it is promoted to a reference with a profile of its own.
76+
print(f"check_boundary: {name} is not a clone reference — nothing to grade")
77+
return 0
78+
79+
sets = clone.partition(root, profile)
80+
unclassified = sets["unclassified"]
81+
if not unclassified:
82+
print(
83+
f"check_boundary: {name} boundary complete — "
84+
f"generic {len(sets['generic'])} · domain {len(sets['domain'])} · "
85+
f"mixed {len(sets['mixed'])}"
86+
)
87+
return 0
88+
89+
print(
90+
f"check_boundary: {len(unclassified)} file(s) in {name} fall on neither "
91+
f"side of the template boundary:\n",
92+
file=sys.stderr,
93+
)
94+
for path in unclassified:
95+
print(f" ✗ {path}", file=sys.stderr)
96+
print(
97+
"\nEvery tracked file must be classified, because the Clone Agent "
98+
"refuses to birth a\nnewborn while any is unclassified — leaving these "
99+
"blocks every future assistant birth.\nDecide what each file is, then "
100+
"record it in BOTH places, which must agree:\n\n"
101+
" 1. modes/maintainer.md, '## Assistant-as-template' — the prose that "
102+
"OWNS the boundary\n"
103+
" 2. PyAutoBrain agents/conductors/clone/_clone.py — REFERENCE_PROFILES"
104+
f"['{name}']\n\n"
105+
" generic — framework that clones to any domain near-verbatim\n"
106+
" domain — this field's science; a newborn regrows its own\n"
107+
" mixed — generic structure, domain-specific values\n",
108+
file=sys.stderr,
109+
)
110+
return 1
111+
112+
113+
if __name__ == "__main__":
114+
sys.exit(main())

agents/conductors/workspace/AGENTS.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ an organism function.
3838

3939
## Modes
4040

41-
Both modes are read-only; the agent writes nothing (the Clone Agent's v0
42-
boundary).
41+
Both modes are read-only; the agent writes nothing.
4342

4443
- **plan** (default) — `pyauto-brain workspace "<raw text>"` or
4544
`pyauto-brain workspace <PyAutoMind prompt path>`: classifies the intent and

bin/pyauto-brain

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ declare -A AGENT_DESC=(
7676
[eyes]="The perceptive function — the organism's sense of its own appearance: survey/review a visualization workspace's figure surface, critiques route to intake/start_dev — EyesSurvey/EyesReviewSurface (never renders, never edits)"
7777
[profiling]="The proprioceptive function — the organism's sense of its own effort: campaign/ingest/triage plans over the autolens_profiling workspace — ProfilingDecision"
7878
[hygiene]="The maintenance function — the organism's sense of its own upkeep: code-quality debt (dev-loop cost + tidiness), delegating fixes — HygieneDecision"
79-
[clone]="The Mitosis Agent (v0: decision only): partition the reference assistant, analyze the domain, emit the CloneDecision — never writes"
79+
[clone]="The Mitosis Agent: partition the reference assistant, analyze the domain, emit the CloneDecision; --apply --mode lightweight-seed delegates the seed birth to Build"
8080
[build]="Coordinate execution: consult the vitals faculty, then delegate to PyAutoBuild"
8181
[release]="Release door → the Build Agent release mode (single gate); 'release rehearse'/'release validate' drive release validation; 'release nightly' is the scheduled-nightly driver"
8282
[health]="The organism's clinician: run the health loop with a human, dispatch by dispatch, toward green"

skills/COMMANDS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ call, so the Brain is not bypassed):
8585
local-only steps. Interactive/terminal only (the automated morning webhooks are
8686
separate).
8787

88-
Codex skills also expose the remaining public CLI agents directly: the
89-
analysis-only `clone` conductor, the `release` conductor, and the read-only
90-
`vitals`, `review`, `memory`, and `samplers` faculties. They do not gain new
88+
Codex skills also expose the remaining public CLI agents directly: the `clone`
89+
conductor, the `release` conductor, and the read-only `vitals`, `review`,
90+
`memory`, and `samplers` faculties. They do not gain new
9191
slash commands; `brain` remains Claude's low-level passthrough.
9292

9393
## How these are installed

skills/clone/SKILL.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
---
22
name: clone
3-
description: Analyze how a mature PyAuto domain assistant should be reproduced as an exact clone, sibling, or seed through the PyAutoBrain Clone Agent. Use for assistant-cell cloning decisions; the current agent is analysis-only and must not generate files.
3+
description: Analyze how a mature PyAuto domain assistant should be reproduced as an exact clone, sibling, or seed through the PyAutoBrain Clone Agent, and birth a lightweight seed once a human has answered the clone-mode question. Use for assistant-cell cloning decisions and seed births.
44
---
55

66
# Clone
77

88
Read [`../../agents/conductors/clone/AGENTS.md`](../../agents/conductors/clone/AGENTS.md)
9-
completely, then run `bin/pyauto-brain clone` with the documented arguments.
10-
Return its `CloneDecision` and preserve the agent's no-write boundary.
9+
completely, then run `bin/pyauto-brain clone` with the documented arguments and
10+
return its `CloneDecision`.
11+
12+
Bare `clone` writes nothing. A birth happens only under
13+
`--apply --mode lightweight-seed`, and only after a human has answered both the
14+
clone-mode question and the repo-creation gate (name / owner / visibility) —
15+
never unprompted. The agent hands the plan to Build; it never writes repos or
16+
files itself, and never modifies the reference.

tests/test_clone_conductor.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,34 @@ def test_autofit_profile_classification(path, expected):
7979
assert _classify(path, clone.REFERENCE_PROFILES["autofit_assistant"]) == expected
8080

8181

82+
@pytest.mark.parametrize(
83+
"path,expected",
84+
[
85+
# The euclid mode is a survey-specific *lensing* register — its skills
86+
# and its own sub-wiki. A newborn grows whatever survey modes its own
87+
# domain has, if any.
88+
("skills/euclid_model_lens.md", "domain"),
89+
(".claude/skills/euclid_hpc_runs.md", "domain"),
90+
("wiki/euclid/index.md", "domain"),
91+
("wiki/euclid/entities/vis.md", "domain"),
92+
("wiki/euclid/bibliography/euclid.bib", "domain"),
93+
# This assistant's own JOSS paper; a newborn writes its own.
94+
("paper/paper.md", "domain"),
95+
("paper/.gitignore", "domain"),
96+
# Bundled science scripts are tied to a named lens...
97+
("scripts/model_cosmos_web_ring.py", "domain"),
98+
("scripts/prepare_cosmos_web_ring.py", "domain"),
99+
# ...but scripts/'s own docs are framework, not science.
100+
("scripts/AGENTS.md", "generic"),
101+
# .mcp.json only wires `autoassistant.mcp` — generic tooling, so the
102+
# wiring carries no domain either and clones verbatim.
103+
(".mcp.json", "generic"),
104+
],
105+
)
106+
def test_autolens_profile_classification(path, expected):
107+
assert _classify(path, clone.REFERENCE_PROFILES["autolens_assistant"]) == expected
108+
109+
82110
def test_wiki_core_and_skills_flip_between_references():
83111
"""The seam is reference-owned: wiki/core/ and the domain skills sit on
84112
opposite sides for the two references."""

0 commit comments

Comments
 (0)