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
8 changes: 4 additions & 4 deletions .github/workflows/cortex_check.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Cortex Check

# Guards the run-and-ruling registry: every phase file parses and its state is
# Guards the run-and-ruling registry: every task file parses and its state is
# legal, every ruling id is unique and every Supersedes: resolves, every
# project a phase names is in projects.yaml, the gates/witness invariants hold
# project a task names is in projects.yaml, the gates/witness invariants hold
# (`scripts/cortex.py check`), then the script's own tests. No self-heal here:
# a failing check is a real contradiction and only a human can resolve it. The
# generated pages that DO self-heal (`dashboard.md`, `dashboard.html`) are
Expand All @@ -13,7 +13,7 @@ on:
push:
branches: [main]
paths:
- "phases/**"
- "tasks/**"
- "rulings/**"
- "batches/**"
- "projects.yaml"
Expand All @@ -25,7 +25,7 @@ on:
- ".github/workflows/cortex_check.yml"
pull_request:
paths:
- "phases/**"
- "tasks/**"
- "rulings/**"
- "batches/**"
- "projects.yaml"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/dashboard_refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Dashboard Refresh

# Keeps the generated Cortex pages (`dashboard.md`, linked from the README, and
# `dashboard.html`, the Pages index) in step with the registry they describe.
# The pages are rendered by PyAutoBrain's cortex conductor from `phases/`,
# The pages are rendered by PyAutoBrain's cortex conductor from `tasks/`,
# `rulings/`, `batches/`, `projects.yaml` and `checkin.yaml`, so any push that
# files a phase, moves it, records a run, lands a ruling or stamps a check-in
# files a task, moves it, records a run, lands a ruling or stamps a check-in
# makes them stale.
#
# Same shape as the Mind's dashboard_refresh.yml, and for the same reason: on
Expand All @@ -30,7 +30,7 @@ on:
push:
branches: [main]
paths:
- "phases/**"
- "tasks/**"
- "rulings/**"
- "batches/**"
- "projects.yaml"
Expand All @@ -39,7 +39,7 @@ on:
- "dashboard.html"
pull_request:
paths:
- "phases/**"
- "tasks/**"
- "rulings/**"
- "batches/**"
- "projects.yaml"
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/ledger_merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Cortex Ledger Merge
# Lands PyAutoCortex's own ledger work without anyone having to ask for it.
#
# THE PROBLEM. A branch-scoped session — the phone, claude.ai/code, any
# `claude/**` flow — pushes a phase move, a ruling, a batch record to a feature
# `claude/**` flow — pushes a task move, a ruling, a batch record to a feature
# branch and it stops there: nothing downstream looks at a `claude/**` push.
# The Mind learned this first (mind_ledger_merge.yml); the Cortex is born with
# the answer.
Expand All @@ -15,7 +15,7 @@ name: Cortex Ledger Merge
# pushed it ever comes back.
#
# WHAT IT WILL AND WILL NOT MERGE. Only a branch whose whole diff is *ledger*:
# `phases/`, `rulings/`, `batches/`, `checkin.yaml`. The line is drawn by
# `tasks/`, `rulings/`, `batches/`, `checkin.yaml`. The line is drawn by
# scripts/ledger_merge.py — a script, so it is testable and a session can
# predict the verdict — and it is DEFAULT DENY: anything under `scripts/`,
# `tests/`, `.github/`, `policy/`, `docs/`, `projects.yaml`, the prose pages,
Expand Down Expand Up @@ -153,7 +153,7 @@ jobs:
echo "verdict=ledger" >> "$GITHUB_OUTPUT"
fi

# Structural drift on the branch tip BLOCKS. A phase in a state its
# Structural drift on the branch tip BLOCKS. A task in a state its
# header contradicts, a dangling Supersedes:, a run line that does not
# parse — nothing heals that automatically, and merging it would put the
# contradiction on main. Also the only check audit mode gets to report.
Expand Down Expand Up @@ -206,11 +206,11 @@ jobs:
fi
# THE TRIAL-MERGE CHECK. The branch passed alone; does it pass
# merged? This is where two branches that both assigned the same
# ruling id, or both moved the same phase, are caught.
# ruling id, or both moved the same task, are caught.
if ! python3 scripts/cortex.py check; then
git reset --hard origin/main
echo "::error::'$BRANCH' merges cleanly but 'cortex.py check' fails on the merged tree — left for a human"
echo "**Blocked** — \`$BRANCH\` fails \`cortex.py check\` once merged with main (a ruling-id or phase-state race?); left for a human." >> "$GITHUB_STEP_SUMMARY"
echo "**Blocked** — \`$BRANCH\` fails \`cortex.py check\` once merged with main (a ruling-id or task-state race?); left for a human." >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
if [ -n "$PR" ]; then
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pages_dashboard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Pages Dashboard
# Why a twin exists at all: GitHub's markdown rendering strips JavaScript, so
# `dashboard.md` can never write to the clipboard — on the GitHub mobile app its
# fenced blocks have no copy affordance at all, leaving long-press text
# selection as the only way to grab a phase's command. The HTML page carries a
# selection as the only way to grab a task's command. The HTML page carries a
# real copy button instead: one tap and the command is on the clipboard, ready
# to paste into a Claude Code chat. It is also where a review is done — the
# board is read on a phone as often as on the laptop.
Expand All @@ -15,7 +15,7 @@ name: Pages Dashboard
# `dashboard.md` (one census, two pages) and kept fresh by the same self-heal in
# `dashboard_refresh.yml`; this workflow only publishes it.
# Only the uploaded files reach Pages — nothing else in the repo, and in
# particular not `phases/`, `rulings/` or `projects.yaml`, whose absolute laptop
# particular not `tasks/`, `rulings/` or `projects.yaml`, whose absolute laptop
# paths are not for a public site.

on:
Expand Down
80 changes: 48 additions & 32 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,29 @@ Generated from `PyAutoMind/repos.yaml` + `PyAutoBrain/ORGANISM.md`; edit there,

**The Mind decides what to build, the Brain routes the work and executes
nothing, the Cortex learns what is true.** The Cortex is a **run-and-ruling
registry**: `project → phase → runs → rulings`. It owns two pieces of state no
registry**: `project → task → runs → rulings`. It owns two pieces of state no
other organ holds — the **science body map** (`projects.yaml`: every science
project, where it lives on the laptop and on RAL, how it syncs, where its own
commentary ledger is) and the **rulings ledger of record** (`rulings/`: every
verdict a human has passed on a science run, append-only).

It is **not a second PyAutoMind.** The Mind is PR-shaped all the way down —
prompts, issues, branches, completion records, "delivered = a PR with a diff
and checks". The Cortex's unit is a **phase** of a **project**, which spawns
**runs** (SLURM job ids) and ends in a **ruling**. Development tasks a science
phase waits on stay in the Mind and are named here only as **gates** — GitHub
issue/PR refs in a phase's `Gates:` header, one grammar, one direction. The
and checks". The Cortex's unit is a **task** of a **project**, which spawns
**runs** (SLURM job ids) and ends in a **ruling**. Development work a science
task waits on stays in the Mind and is named here only as **gates** — GitHub
issue/PR refs in a task's `Gates:` header, one grammar, one direction. The
Mind learns nothing about the Cortex beyond a render-time badge.

The schemas — phase files, the run-line grammar, rulings, batches, the
**Tasks, not phases** (2026-09-07). A project holds *unordered* tasks — ideas
the human runs in whatever order the results and the priorities dictate — so
there is no number and no sequence. A task's identity is its **slug**, unique
per project by the path itself, and every task carries a required `Summary:`
of at most ten words: the *question* it answers, which is the one line the
board shows. Mind **epics** are still numbered phases; that is development
work, and it really is sequential.

The schemas — task files, the run-line grammar, rulings, batches, the
restricted `projects.yaml` subset — and the `scripts/cortex.py` verbs are in
[REFERENCE.md](REFERENCE.md). Every choice the birth epic did not fix is dated
in [docs/schema_decisions.md](docs/schema_decisions.md); read decisions there,
Expand All @@ -67,11 +75,13 @@ human's turn. Full rules in [rulings/AGENTS.md](rulings/AGENTS.md).
code, not ledger**: `sync_cli` and `local_path` are paths a conductor will
execute under, so a change to it is always a human's turn. `cortex.py` reads
it with PyYAML and validates the fields (REFERENCE.md "projects.yaml").
- **`phases/<project>/<slug>.md`** — one file per phase, with the Mind's light
header (`Key: value` lines, no YAML frontmatter) and a `## Runs` body in the
SLURM run-line grammar. `State:` is one of `planned | gated | ready |
- **`tasks/<project>/<slug>.md`** — one file per task, with the Mind's light
header (`Key: value` lines, no YAML frontmatter — `Project:`, `Summary:`,
`State:`, …) and a `## Runs` body in the SLURM run-line grammar. There is no
`Phase:` number: the slug is the identity, and `check` names `Phase:` as a
retired key. `State:` is one of `planned | gated | ready |
submitted | running | pulled | awaiting-ruling | accepted | rerun | dropped`.
`legacy` and `legacy_wrong` are states of a **run**, never of a phase.
`legacy` and `legacy_wrong` are states of a **run**, never of a task.
- **`rulings/<YYYY>/<MM>/R-<YYYYMMDD>-<nn>.md`** — the ledger of record.
- **`batches/`** — closed history. Three 2026-08/09 batch records and the
human's verbatim reviews under `reviews/`, kept because 13 rulings cite them
Expand All @@ -85,27 +95,27 @@ human's turn. Full rules in [rulings/AGENTS.md](rulings/AGENTS.md).
- **`scripts/cortex.py`** — the one lifecycle script (stdlib only):
- `check` — every structural rule, hermetic; `cortex check: OK` or `DRIFT`
with one ` - …` line per finding, exit 1.
- `gates` — read-only and offline: every gated phase, its refs and their
- `gates` — read-only and offline: every gated task, its refs and their
URLs. Nothing polls GitHub and nothing flips a state; a human reads the
listing, opens the refs and types `move <phase> ready`.
- `rule <phase> <verb> --body <file> …` — assigns the next ruling id, writes
the ruling file(s), updates the phase's `Ruling:` and `State:`. The only
listing, opens the refs and types `move <task> ready`.
- `rule <task> <verb> --body <file> …` — assigns the next ruling id, writes
the ruling file(s), updates the task's `Ruling:` and `State:`. The only
door to `accepted`, `rerun`, `dropped` and `leave-to-finish`.
- `move <phase> <state> …` — every other transition, per the table in
- `move <task> <state> …` — every other transition, per the table in
REFERENCE.md; refuses the ruling edges.
- `new <project> <slug> --phase <n> …` — writes a phase file from the
template.
- `new <project> <slug> --summary "<≤10 words>" …` — writes a task file
from the template; `--summary` is required and capped, as `check` is.
- `retire <project> --why "<one line>"` — the only verb that writes
`projects.yaml`: the row's `status:` becomes `retired` and its
`note:` records `retired <today>: <why>`. The row, its phases and its
rulings all stay; it refuses while any phase of that project is
`note:` records `retired <today>: <why>`. The row, its tasks and its
rulings all stay; it refuses while any task of that project is
outside `accepted | rerun | dropped | planned`.
- **`dashboard.md` / `dashboard.html`** — GENERATED, never hand-edited: the
board, rendered by the Brain's cortex conductor and self-healed on `main`
(see "Driving the Cortex" below). They are ledger for the merge gate.
- **`scripts/ledger_merge.py`** — the default-deny classifier behind
`.github/workflows/ledger_merge.yml`: a `claude/**` push whose whole diff is
ledger (`phases/`, `rulings/`, `batches/`, `checkin.yaml`, the two generated
ledger (`tasks/`, `rulings/`, `batches/`, `checkin.yaml`, the two generated
dashboards) lands on `main` by itself; anything else waits for a human — and
that "anything else" includes an `AGENTS.md` or `TEMPLATE.md` *inside* a
ledger dir, which is doctrine, not an entry. `python3
Expand All @@ -118,23 +128,26 @@ lives in the Brain's **cortex conductor** — `pyauto-brain cortex <verb>`, or
`python3 PyAutoBrain/agents/conductors/cortex/_cortex.py <verb> --cortex
<checkout>` with no Brain install. It is read-mostly: it renders, it plans and
it scores. The only bytes it writes of its own are the two generated pages;
every change to a *phase* goes through `scripts/cortex.py`, which owns the
every change to a *task* goes through `scripts/cortex.py`, which owns the
state table.

| Verb | What it does |
|------|--------------|
| `checkin [--dry-run \| --apply] [--push \| --no-push] [--project KEY] [--skip-pull]` | **the check-in** — the one door: pull every active project through its own `sync_cli`, score every live phase, move what came back, re-render the board, push the ledger where the rule allows, and summarise **by project**. `--dry-run` is the default and reaches nothing |
| `checkin [--dry-run \| --apply] [--push \| --no-push] [--project KEY] [--skip-pull]` | **the check-in** — the one door: pull every active project through its own `sync_cli`, score every live task, move what came back, re-render the board, push the ledger where the rule allows, and summarise **by project**. `--dry-run` is the default and reaches nothing |
| `census [--json]` | what the Cortex is holding, by state — the one-screen answer |
| `dashboard --check` \| `--apply` | render `dashboard.md` + `dashboard.html`; `--check` exits **1 on drift**, **2 on bad args**, anything else = the renderer could not run |
| `gates` | every gated phase, its refs and their URLs — read-only, offline |
| `collect [--phase REL] [--pull] [--refreshed ISO] [--apply]` | score what came back; with no `--phase` it scopes to **every** `submitted \| running` phase, and `--pull` runs the *project's own* sync CLI |
| `gates` | every gated task, its refs and their URLs — read-only, offline |
| `collect [--task REL] [--pull] [--refreshed ISO] [--apply]` | score what came back; with no `--task` it scopes to **every** `submitted \| running` task, and `--pull` runs the *project's own* sync CLI |

**`--apply` here, `--write` there.** The conductor's verbs spell the writing
flag `--apply` (the Brain's house spelling, as intake does); `scripts/cortex.py`
spells it `--write`.

**Nothing here submits a job.** A human runs the project's own `sync_cli
submit` line and the `cortex.py move <phase> submitted --run <jobid>` follow-up.
**Nothing here submits a job by itself.** A submission is made only on the
human's ask — by the human, or by the agent in the session — with the project's
own `sync_cli submit` line, followed at once by `cortex.py move <task>
submitted --run <jobid>` (ruled 2026-09-05; before that the human typed the
submit).
`checkin` and `collect --pull` are the only legs that shell out, and only to
the project's own CLI — the conductor adds no SSH of its own. Each project
`checkin` pulls gets a `<pull root>/.cortex/pull.json` manifest
Expand Down Expand Up @@ -172,7 +185,7 @@ exception, and it is confined to `projects.yaml`: a project's `local_path` and
`mirror` are absolute laptop paths **outside** the workspace (the Science
folder under `/mnt/c/…`), because that is where the datasets, the `output/`
trees and the pulled results live and the review happens at the laptop. The
exception is stated in the file's header and here, and nowhere else — a phase
exception is stated in the file's header and here, and nowhere else — a task
file or a ruling points into a project through its `projects.yaml` row, never
with a bare absolute path of its own.

Expand All @@ -199,18 +212,21 @@ so nobody re-derives these:
> prompts that they are human-driven and supervised with a judged verdict as the
> deliverable — **no transport was ever going to make those unattended.**

Every Cortex phase therefore runs on the laptop. That was once written into
each phase file as `Lane: local-dev` — 32 of 32 said the same thing, so the
header was deleted on 2026-09-03 and the fact lives here instead.
Every Cortex task therefore runs on the laptop. That was once written into
each task file as `Lane: local-dev` — 32 of 32 said the same thing, so the
header was deleted on 2026-09-03 and the fact lives here instead. (The quote
above is the Mind's, verbatim: the "phases 4, 6a and 6b" it names are phases of
the `euclid-dr1-prep` **Mind epic**, which is development work and genuinely
sequential — not Cortex tasks.)

## Hard rules

1. **Never rewrite history on any branch with a remote** (block below).
2. **Pull before edit.** `git fetch && git status` first, every time.
3. **Never edit a ruling.** Supersede it. `ledger_merge.py` refuses to
auto-merge a modified or deleted file under `rulings/`.
4. **Run `python3 scripts/cortex.py check` before you push** a phase, ruling
or batch change; `ledger_merge.yml` runs it on the trial-merge tree and a
4. **Run `python3 scripts/cortex.py check` before you push** a task, ruling
or batch change (it is what refuses a missing or over-long `Summary:`); `ledger_merge.yml` runs it on the trial-merge tree and a
failing check leaves the branch for a human.
5. **No hand-written HTML.** `dashboard.html` is rendered by the cortex
conductor; a hand edit is drift that `dashboard_refresh.yml` will overwrite
Expand Down
Loading
Loading