Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/board_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ jobs:
with:
repository: PyAutoLabs/PyAutoBrain
path: PyAutoBrain
# Coordinated presentation changes test the companion Brain branch.
# Main remains the fallback when this is a Scientist-only change.
- name: Prefer matching Brain branch on pull requests
if: github.event_name == 'pull_request'
working-directory: PyAutoBrain
env:
SCIENTIST_TASK_BRANCH: ${{ github.head_ref }}
run: |
if git ls-remote --exit-code --heads origin "refs/heads/$SCIENTIST_TASK_BRANCH" > /dev/null; then
git fetch --depth 1 origin "refs/heads/$SCIENTIST_TASK_BRANCH"
git checkout -q FETCH_HEAD
else
status=$?
if [ "$status" -ne 2 ]; then exit "$status"; fi
echo "No companion Brain branch; testing against main"
fi
# Canonical repository identity input; relationships remain in Scientist.
- uses: actions/checkout@v4
with:
Expand All @@ -25,5 +41,6 @@ jobs:
python-version: '3.12'
- run: python -m pip install pytest pyyaml
- run: python scripts/organism_board.py --repos PyAutoMind/repos.yaml --check-projects
- run: python scripts/organism_board.py --instance examples/starter/instance.json --repos examples/starter/repos.yaml --check-instance
- run: python -m pytest tests -q
- run: node --test tests/overview.cjs
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ feeds without replacing open disclosures or prompt controls.
https://pyautoscientist.readthedocs.io are built from **`PyAutoBrain/docs/`**;
edit the docs there.

## Independent instance preview

`STARTER.md` documents the opt-in `--instance` profile. Keep instance names,
repository identities, project paths and connected-organ locations in configuration.
An empty profile contacts no feeds and imports no live project records. Repo
identities continue to come from Mind. Bootstrap, activation and deployment are
separate phases; do not describe this preview as a full installation.

## Future intent (not now — Phase 3, demand-gated)

Migrating the RTD docs source *into* this repo — so PyAutoScientist becomes the
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ The Cortex keeps scientific experiments on a separate track from software develo

The software developed by the organism lives across the [PyAutoLabs](https://github.com/PyAutoLabs) organisation. See the [PyAutoLabs front door](https://pyautolabs.github.io) for the full repository map.

## Independent starter preview

[STARTER.md](STARTER.md) provides an empty, independently named local dashboard
with explicit optional organ connections. It is the first starter slice; full
bootstrap, organ activation and independent deployment remain in development.

## Project History

In March 2026, following more than a decade of exclusively human-led software development, PyAutoLabs transitioned to a fully natural-language, agentic-AI development ecosystem called `PyAutoScientist`. The software it now develops grew out of that decade of human-led work: [PyAutoLens](https://github.com/PyAutoLabs/PyAutoLens) (strong gravitational lensing), [PyAutoGalaxy](https://github.com/PyAutoLabs/PyAutoGalaxy) (galaxy structure and morphology), [PyAutoFit](https://github.com/PyAutoLabs/PyAutoFit) (Bayesian model fitting), [PyAutoArray](https://github.com/PyAutoLabs/PyAutoArray) (scientific data structures) and [PyAutoCTI](https://github.com/PyAutoLabs/PyAutoCTI) (CCD charge-transfer calibration), together with their workspaces and tutorials.
Expand Down
95 changes: 95 additions & 0 deletions STARTER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Start a Scientist dashboard with your own identity

This is the first slice of the independent starter: an empty dashboard with a
name, its own repository identity and explicit organ connections. It needs
Python, PyYAML and a pinned PyAutoBrain checkout for shared presentation. It
requires no science libraries, assistant repository or organ service.

This local preview does not initialize a working Mind, install an agent harness,
provision repositories, activate organs or publish a site. Those remain later
starter steps. Existing projects keep their own source and records.

## Try the empty preview

From this repository, with Brain beside it (or `PYAUTO_BRAIN` set to its checkout):

```bash
python -m pip install pyyaml
python scripts/organism_board.py --instance examples/starter/instance.json \
--repos examples/starter/repos.yaml --check-instance
python scripts/organism_board.py --instance examples/starter/instance.json \
--repos examples/starter/repos.yaml --html > starter.html
```

Open `starter.html` locally. The fictitious **Mira Scientist** has no projects or
connected organs. Rendering and opening this empty page fetch no organ feeds.
The example GitHub identity and `example.invalid` address are placeholders,
not repositories or a deployed service. This example body map is a fixture,
not a complete Mind installation.

## Configure your instance

Copy the example configuration to a directory you own, outside the framework
checkout. Set `name`, `repo` and `pages_url`. `repo` is a stable ID in your
canonical Mind `repos.yaml`; its `github` field supplies the repository home.
`pages_url` is the explicit HTTPS directory where you intend to publish this
Scientist, including any custom path. Naming is independent of framework organ
keys and repository names.

The required `projects` field names a file relative to the profile directory.
Start with the empty project file. Supplying `--instance` never falls back to
this repository's live project configuration. Use `--projects` only when you
intentionally want to override the profile's file. Keep this configuration and
your records when updating the framework; this change neither migrates nor
rewrites them. A supported upgrade procedure remains future work.

Omitting `--instance` retains the existing PyAutoLabs-compatible dashboard path.
The current Actions publishing workflow still uses that path; this local preview
does not configure independent-instance deployment.

## Connect an existing project

Give your coding assistant this request in the local checkout:

> Read STARTER.md, PROJECTS.md and my instance profile. Help connect one existing
> project. First inspect its current source and any existing Scientist records.
> Ask me for its purpose, canonical repository identity and intended visibility
> if these are not available. Reuse my Mind body map and explicit project/resource
> relationships; do not copy source material or assume an assistant exists.
> Explain the proposed configuration changes before applying them. Validate the
> profile and relationships offline, then show me a local dashboard preview.
> Do not create repositories, publish, change access or run science as part of
> connecting the project. Suggest optional organs only when they serve a concrete
> need; setup and activation remain separate steps.

Add canonical repository identities in your Mind body map, then configure
resources and projects as described in [PROJECTS.md](PROJECTS.md). Projects and
resources are private by default; only explicit `public: true` entries appear in
the publication snapshot. Do not enter private URLs into public connections.
A project may have no assistant and may share dependencies with other projects.

## Connect an already available organ

`organs` maps Brain's stable organ keys to explicit canonical identities and
published board directories. For example, after your own Mind board exists:

```json
"organs": {
"mind": {
"repo": "research-plans",
"pages_url": "https://research.example/boards/plans/"
}
}
```

`research-plans` must be an entry in your body map with its actual GitHub home.
The board's `state.json` must use `organ: "mind"` and the repository name from
that GitHub identity. Connection order follows Brain's shared registry; only
selected organs are collected, displayed and linked in the footer. A connected
organ with a missing or invalid feed remains visible as unavailable. An omitted
organ is unconfigured, not failed. Empty `organs: {}` is supported.

Connections are an explicit choice of public feed locations. Adding a connection
reads that feed during rendering and browser refresh; it never installs or runs
an organ. Grey available-to-add capabilities, resumable AI-led activation and
independent deployment are subsequent epic phases.
8 changes: 8 additions & 0 deletions examples/starter/instance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schema_version": 1,
"name": "Mira Scientist",
"repo": "mira",
"pages_url": "https://example.invalid/research/mira/",
"projects": "projects.json",
"organs": {}
}
5 changes: 5 additions & 0 deletions examples/starter/projects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"schema_version": 1,
"resources": {},
"projects": {}
}
5 changes: 5 additions & 0 deletions examples/starter/repos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Fictitious canonical identity input for the offline preview only.
# Use your own Mind body map when connecting real projects.
repos:
mira:
github: ExampleResearch/MiraScientist
81 changes: 81 additions & 0 deletions scripts/instance_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Opt-in Scientist instance configuration, resolved against the Mind body map.

Loading is read-only and offline. Only explicitly selected organs enter the
public snapshot; local configuration paths never enter it.
"""

import json
import re
from pathlib import Path
from urllib.parse import urlsplit

import yaml


def _text(value, label):
if not isinstance(value, str) or not value.strip() or any(ord(c) < 32 for c in value):
raise ValueError(f"{label} must be nonempty single-line text")
return value


def _pages(value):
value = _text(value, "pages_url")
url = urlsplit(value)
if (url.scheme != "https" or not url.hostname or url.username or url.password
or url.query or url.fragment or "\\" in value or any(c.isspace() for c in value)):
raise ValueError("pages_url must be an HTTPS directory URL without credentials, query or fragment")
return value.rstrip("/") + "/"


def _identity(key, repos):
_text(key, "repo identity")
identity = repos.get(key)
github = identity.get("github") if isinstance(identity, dict) else None
if not isinstance(github, str) or not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", github):
raise ValueError(f"repo {key}: missing or invalid canonical GitHub identity")
return github


def load(path, repos_path, theme):
"""Return (public resolved profile, local project configuration path)."""
path = Path(path)
config = json.loads(path.read_text(encoding="utf-8"))
required = {"schema_version", "name", "repo", "pages_url", "projects", "organs"}
if not isinstance(config, dict) or set(config) != required:
raise ValueError("instance profile must declare schema_version, name, repo, pages_url, projects and organs")
if type(config["schema_version"]) is not int or config["schema_version"] != 1:
raise ValueError("unsupported instance schema_version")
body = yaml.safe_load(Path(repos_path).read_text(encoding="utf-8"))
repos = body.get("repos") if isinstance(body, dict) else None
if not isinstance(repos, dict):
raise ValueError("canonical identity map must contain repos")
name = _text(config["name"], "instance name")
github = _identity(config["repo"], repos)
pages_url = _pages(config["pages_url"])
relative = Path(_text(config["projects"], "projects"))
if relative.is_absolute() or ".." in relative.parts:
raise ValueError("projects must be a relative path within the instance configuration directory")
projects = path.parent / relative
if not projects.resolve().is_relative_to(path.parent.resolve()):
raise ValueError("projects must stay within the instance configuration directory")
selected = config["organs"]
family = theme.board_links(pages_url, current="organism")
if not family:
raise ValueError("Brain board registry unavailable")
if not isinstance(selected, dict) or set(selected) - set(family):
raise ValueError("organs must map canonical Brain organ keys to explicit connections")
boards = []
for key in family:
if key not in selected:
continue
connection = selected[key]
if not isinstance(connection, dict) or set(connection) != {"repo", "pages_url"}:
raise ValueError(f"organ {key}: declare only repo and pages_url")
organ_github = _identity(connection["repo"], repos)
boards.append({"key": key, "name": theme.organ(key)["organ"],
"role": theme.organ(key)["function"],
"repo": organ_github.split("/")[1],
"url": _pages(connection["pages_url"])})
return {"schema_version": 1, "name": name, "repo": github.split("/")[1],
"source_url": "https://github.com/" + github, "pages_url": pages_url,
"boards": boards}, projects
Loading
Loading