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
33 changes: 23 additions & 10 deletions agents/conductors/intake/_intake.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
RISK_KEYWORDS, AMBIGUITY_KEYWORDS, normalise_repo, declared_header,
declared_inline, effective_difficulty, strip_declarations, _hits,
policy as _sizing_policy, BODY_MAP_PATH,
_body_map_specs as _sizing_specs,
)

# The shared board theme: the one place that answers "what does a one-tap board
Expand Down Expand Up @@ -95,16 +96,28 @@
TARGET_SIGNALS = _sizing_policy()["target_signals"]

# Human-readable display name for the header's `Target:` line.
REPO_DISPLAY = {
"autonerves": "PyAutoNerves", "autoconf": "PyAutoNerves", # autoconf = legacy alias
"autofit": "PyAutoFit", "autoarray": "PyAutoArray",
"autogalaxy": "PyAutoGalaxy", "autolens": "PyAutoLens",
"pyautomind": "PyAutoMind", "pyautobrain": "PyAutoBrain",
"pyautoheart": "PyAutoHeart", "pyautobuild": "PyAutoHands",
"pyautomemory": "PyAutoMemory", "autohands": "PyAutoHands",
"autobuild": "PyAutoHands", # back-compat: the package was renamed autobuild -> autohands
"workspaces": "workspaces",
}
#
# Derived from the body map, which already holds every repo's name in its real
# capitalisation — a hand-kept copy here would be the same drift #287 closed in
# the alias table one map over, and it already had the beginnings of it: keys the
# router could reach (`pyautohands`, and the CTI/Reduce libraries) had no row, so
# a header came out as `Target: pyautohands`. Only the rows a body map cannot
# know are written out: the pre-rename spellings and the workspace bucket.
def _repo_display() -> dict:
out = {
normalise_repo(name): name
for name in _sizing_specs()
}
out.update({
"autoconf": out.get("autonerves", "autonerves"), # pre-rename spelling
"pyautobuild": out.get("pyautohands", "pyautohands"), # pre-rename spelling
"autobuild": out.get("pyautohands", "pyautohands"), # pre-rename package
"workspaces": "workspaces", # a bucket, not a repo
})
return out


REPO_DISPLAY = _repo_display()
PRIORITY_HIGH = ["urgent", "asap", "blocker", "blocking", "critical", "important",
"high priority", "must fix", "regression"]
PRIORITY_LOW = ["someday", "nice to have", "eventually", "low priority", "minor",
Expand Down
176 changes: 164 additions & 12 deletions agents/faculties/sizing/_sizing.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,178 @@ def policy() -> dict:
return _POLICY_CACHE


_BODY_MAP_CACHE: dict = {}


def _body_map_specs() -> dict:
"""repo name -> its full body-map spec (the single source of repo identity).

Cached: every call site below reads it, and it is a file the process never
writes.
"""
if not _BODY_MAP_CACHE:
import yaml

_BODY_MAP_CACHE.update(yaml.safe_load(BODY_MAP_PATH.read_text())["repos"])
return _BODY_MAP_CACHE


def _body_map_categories() -> dict:
"""repo name -> category, from the body map (the single source of repo
identity)."""
import yaml
return {name: spec["category"] for name, spec in _body_map_specs().items()}


# --- the canonical-key rule (PyAutoBrain#287) --------------------------------
# One repo, one key. A prompt may spell a repo three ways — `@PyAutoFit`,
# `@autofit`, `PyAutoFit/` — and every one of them has to reach the SAME key, or
# whichever spelling the author happened to type silently decides whether the
# policy maps (test_witness, target_default_wiki, ...) resolve. Seven repos hit
# that split before it was closed here — one at #267, two more at #269, and the
# bare organ spellings plus one project repo at #287.
#
# THE RULE (first written down at #269, now executable): the canonical key is
# the package the repo SHIPS where it ships one, and the repo name where it does
# not. That asymmetry is not arbitrary — it is what prompts actually write. A
# library is named by its import (`@autofit`); an organ ships no package, so the
# only name it has is the repo's (`@PyAutoBrain`).
#
# The authority for "does it ship a package" is the body map's `package:` field —
# repo identity, declared once, where identity lives.


def _hand_aliases() -> dict:
"""The alias rows a body map cannot derive.

Two kinds only: the short forms prompts use for the libraries (`aa`, `af`),
and the pre-rename spellings that keep ~150 archived Mind prompts routing to
the repo they now name (`pyautobuild` -> the Hands, and the Nerves repo's
former name). Neither is inferable from a body map that records only what
the organism is called TODAY.
"""
return policy()["repo_aliases"]


def canonical_key(name: str, spec: dict | None = None) -> str:
"""The one key every spelling of body-map repo `name` must reach."""
if spec is None:
spec = _body_map_specs().get(name, {})
package = spec.get("package")
if package:
return package.lower()
# Fallback for a body map that predates `package:` (an adopting fork, or
# this repo's own CI, which pins the sibling Mind checkout to `main`): the
# hand table still carries the library rows, so the answer is the same one
# `package:` gives. Kept deliberately — it is what lets the Brain half of
# #287 stand alone instead of going red until the Mind half merges.
low = name.lower()
return _hand_aliases().get(low, low)


def spellings_of(name: str, spec: dict | None = None) -> set:
"""Every form of `name` that `_target_sets` registers as a known target.

The repo name, the `PyAuto`-stripped bare form, and the package it ships.
These are the spellings a guard must prove all reach one key; they are NOT
every string that could mention the repo (an org-qualified path like
`@<org>/<repo>` is handled by `normalise_repo`'s truncation).
"""
if spec is None:
spec = _body_map_specs().get(name, {})
low = name.lower()
out = {low}
if low.startswith("pyauto"):
out.add(low[2:])
if spec.get("package"):
out.add(spec["package"].lower())
return out


data = yaml.safe_load(BODY_MAP_PATH.read_text())
return {name: spec["category"] for name, spec in data["repos"].items()}
def unreachable_repos() -> dict:
"""Body-map repos an @-mention can never name -> why.

``normalise_repo`` truncates at the first ``.`` or ``/`` (so `@aa.decorators`
and an org-qualified `@<org>/<repo>` path both resolve to their head token).
A repo whose NAME contains one of those separators therefore cannot survive
normalisation, and registering it as a known target would be a lie: nothing
could ever resolve to it. Aliasing the truncated head instead would be worse
than the lie — where the head happens to be the ORG's own name, every
org-qualified mention would start resolving to that one repo.

Derived from the names themselves, so it stays right for any body map rather
than being a hand-kept exclusion list (PyAutoBrain#287).
"""
return {
name: "name contains a '.' or '/', which normalise_repo truncates — "
"no @-mention can reach it"
for name in _body_map_specs()
if re.split(r"[./]", name, maxsplit=1)[0] != name
}


def _derived_aliases() -> dict:
"""Every registered spelling of every body-map repo -> its canonical key.

This is the half of ``repo_aliases`` that must NOT be typed by hand. The
known-target set was always derived from the body map while the alias table
was maintained by hand, so the two drifted silently and the gap surfaced only
as a wrong-but-plausible conductor message — "strengthen tests first" for a
repo with a full suite (PyAutoBrain#267, #269, #287). Deriving the join means
a repo added to the body map arrives with its spellings already joined.
"""
grouping = policy()["sizing_categories"]
registered = {cat for kinds in grouping.values() for cat in kinds}
unreachable = unreachable_repos()
out = {}
for name, spec in _body_map_specs().items():
if spec["category"] not in registered or name in unreachable:
continue
canonical = canonical_key(name, spec)
for spelling in spellings_of(name, spec):
out[spelling] = canonical
return out


def _repo_aliases() -> dict:
"""The effective alias table: derived join + the rows only a human can know.

A hand row that CONTRADICTS the derivation is drift, and drift in this table
is exactly what #287 is about — so it raises here rather than quietly
winning. A hand row the derivation does not cover (a short form, a rename)
passes through untouched.
"""
derived = _derived_aliases()
hand = _hand_aliases()
conflicts = {
alias: (derived[alias], hand[alias])
for alias in hand
if alias in derived and hand[alias] != derived[alias]
}
if conflicts:
raise ValueError(
"config/policy.yaml repo_aliases contradicts the body map "
"(alias -> (derived, hand)): "
f"{conflicts}. The body map's `package:` field is the authority for "
"a repo's canonical key; fix the hand row or the package name."
)
return {**derived, **hand}


def _target_sets() -> tuple[set, set, set]:
cats = _body_map_categories()
specs = _body_map_specs()
pol = policy()
grouping = pol["sizing_categories"]
unreachable = unreachable_repos()

def names_for(kind):
wanted = set(grouping[kind])
out = set()
for name, cat in cats.items():
if cat in wanted:
out.add(name.lower())
if name.lower().startswith("pyauto"):
out.add(name.lower()[2:]) # PyAutoFit -> autofit package form
for name, spec in specs.items():
# An unreachable repo is deliberately NOT registered: a known target
# nothing can resolve to is the same silent lie as a split spelling.
if spec["category"] in wanted and name not in unreachable:
out |= spellings_of(name, spec)
out.add(canonical_key(name, spec))
return out

libraries = names_for("library")
Expand All @@ -108,14 +258,16 @@ def names_for(kind):
return libraries, workspaces, organism


# Normalise an @-mention or folder name to a canonical key. Built before the
# target sets because `canonical_key`'s pre-`package:` fallback reads the hand
# table, and the sets register the canonical key it returns.
REPO_ALIASES = _repo_aliases()

# Targets that are source *libraries* (work classifies as library vs workspace),
# workspaces/tutorials/example repos, and the organism's own organs — all
# derived from the body map's categories per the policy's grouping.
LIBRARY_REPOS, WORKSPACE_REPOS, ORGANISM_REPOS = _target_sets()

# Normalise an @-mention or folder name to a canonical key.
REPO_ALIASES = policy()["repo_aliases"]

# --- PyAutoMemory sub-wiki routing (shared science vocabulary) ----------------
# Map keywords -> the PyAutoMemory sub-wiki that holds relevant context. This is
# also the canonical *science vocabulary* difficulty scoring keys off (see
Expand Down
39 changes: 34 additions & 5 deletions config/policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,47 @@
# categories at runtime; nothing here duplicates the body map).

# Normalise an @-mention or folder name to a canonical target key.
#
# THE CANONICAL-KEY RULE: one repo, one key — the package the repo SHIPS where it
# ships one, the repo name where it does not. So the libraries key bare
# (`autofit`), and the organs, which ship no package, key by repo name
# (`pyautobrain`); Nerves is the organ that ships one, so it keys `autonerves`.
# The asymmetry is not arbitrary: it is what prompts actually write.
#
# MOST OF THIS TABLE IS NO LONGER WRITTEN HERE. The bare/prefixed/package join is
# DERIVED from the body map at runtime (`_sizing._derived_aliases`), keyed off the
# `package:` field in PyAutoMind/repos.yaml. It had to be: this table was hand-kept
# while the known-target set was derived, so the two drifted silently and the gap
# surfaced only as a wrong-but-plausible conductor message — four repos in a row
# (PyAutoBrain#267, #269, #287). A row below that CONTRADICTS the derivation now
# raises rather than quietly winning.
#
# What is left is what a body map cannot know:
repo_aliases:
# 1. Short forms. Prompts write `@aa.decorators.to_vector_yx`, and no field in
# the body map says so.
aa: autoarray
af: autofit
ag: autogalaxy
al: autolens
# 2. Pre-rename spellings. The body map records what the organism is called
# TODAY; these keep the archived Mind prompts routing to the repo they now
# name.
autoconf: autonerves # back-compat: the Nerves package was renamed
pyautoconf: autonerves # back-compat: the Nerves repo was renamed
pyautobuild: pyautohands # back-compat: the Hands repo was renamed PyAutoBuild → PyAutoHands
# 3. The library rows the derivation would otherwise produce, kept as the
# fallback for a body map that predates `package:` — an adopting fork, or
# this repo's own CI, which pins the sibling Mind checkout to `main`.
# `canonical_key` reads them only when no `package:` is declared, so they
# are dead weight the moment one is, and can be deleted then.
pyautoarray: autoarray
pyautofit: autofit
pyautogalaxy: autogalaxy
pyautolens: autolens
pyautonerves: autonerves
pyautocti: autocti
pyautoreduce: autoreduce
autohands: pyautohands # organs have no package, so the repo name is canonical
autoconf: autonerves # back-compat: the Nerves package was renamed autoconf → autonerves
pyautoconf: autonerves # back-compat: the Nerves repo was renamed PyAutoConf → PyAutoNerves
pyautobuild: pyautohands # back-compat: the Hands repo was renamed PyAutoBuild → PyAutoHands

# Which body-map categories mean "library-flow" / "workspace-flow" /
# "organism-infrastructure" to the sizing faculty; extra literal targets
Expand All @@ -29,7 +54,11 @@ sizing_categories:
workspace: [workspace, workspace_test, workspace_developer, howto, assistant, pipeline, project]
organism: [organ]
extra_workspace_targets: [workspaces]
extra_organism_targets: [autohands]
# Empty since PyAutoBrain#287: `autohands` was declared here because the bare
# organ spellings were not derived. They are now (every spelling of every
# body-map repo is), so a literal here would be redundant — the block stays as
# the seam a fork uses for a target its body map does not carry.
extra_organism_targets: []

# Keyword -> PyAutoMemory sub-wiki (the wiki/<domain>/ layout; also the science
# vocabulary the difficulty scoring keys off). Source of truth for the wiki
Expand Down
Loading
Loading