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
10 changes: 10 additions & 0 deletions agents/conductors/intake/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ schema — light structure over free-form prose.
| **census** | `intake census` | inventory every filed prompt (work-type/target/difficulty/status + hygiene flags); always read-only |
| **dashboard** | `intake dashboard` | render the census as the Mind **backlog** page; `--apply` writes `PyAutoMind/dashboard.md` |
| **formalise** | `intake formalise [prefix]` | retroactively header the prompts census flags — derive the missing fields, insert in place, prose untouched; `--apply` writes |
| **reconcile** | `intake reconcile [prefix]` | rank backlog prompts that look already-shipped (vs `complete.md` / `issued/`); always read-only — retiring stays human |

Census/dashboard are the Mind *backlog* view — deliberately distinct from
Heart's `/health status` health view (see "must never do"). The prompt-taxonomy
Expand All @@ -87,6 +88,14 @@ Type/Target from the folder, keeps every existing field value and every line of
prose, and turns work-type disagreements into **re-home suggestions** — it never
moves or deletes a file.

Reconcile exists because a prompt's `Status:` header is **not** a completeness
signal — formalise preserves an existing Status verbatim, so shipped work can
still read `Status: planned`. It cross-references each backlog prompt against
`complete.md` (path references + `## header` topic overlap), `issued/`
basenames, and hand-set Status values, then ranks suspects (high/medium/low)
with the evidence shown. The final verification — the target repo's git log /
merged PRs — and the retirement itself stay human.

## Machine sources (one staging surface)

Conception input increasingly arrives from the organism itself — research
Expand Down Expand Up @@ -120,6 +129,7 @@ bin/pyauto-brain intake dashboard # backlog pag
bin/pyauto-brain intake --apply dashboard # write PyAutoMind/dashboard.md
bin/pyauto-brain intake formalise # propose retroactive headers (dry-run)
bin/pyauto-brain intake --apply formalise bug/ # write them, only under bug/
bin/pyauto-brain intake reconcile # rank shipped-but-stale suspects (read-only)
```

**Writes only under `--apply`; dry-run is the default.** Exit codes: `0` produced
Expand Down
11 changes: 11 additions & 0 deletions agents/conductors/intake/INTAKE_TAXONOMY.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ confidence it emits a *re-home suggestion*; it never moves or deletes a file.
Optional path-prefix argument scopes the run (`intake formalise bug/`); writes
only under `--apply` and re-runs are no-ops (nothing left missing).

**reconcile** audits the opposite failure: prompts whose work shipped but whose
status went stale (a header `Status:` is display metadata — formalise preserves
an existing value verbatim, so `Status: planned` can outlive the work by
months). Per backlog prompt it collects four Mind-local signals — a
`complete.md` line referencing its path (follow-up/restore/parked wording
downgrades it to likely-open), a duplicate basename in `issued/`, token overlap
with a completed task's `## header`, and a hand-set Status — then ranks
suspects high/medium/low with the evidence shown. Always read-only: the final
verification (target repo git log / merged PRs) and the retirement to
`issued/` stay human.

## 7. What intake does NOT own

- The difficulty heuristic, prompt parsing, repo sets → the **sizing faculty**.
Expand Down
131 changes: 131 additions & 0 deletions agents/conductors/intake/_intake.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,124 @@ def emit_formalise(res: dict):
print("\n(dry-run — re-run `intake --apply formalise` to write the headers)")


# --- reconcile (shipped-but-stale audit) ----------------------------------------
# A prompt's Status: header is NOT a completeness signal — formalise preserves an
# existing Status verbatim, so shipped work can still read "Status: planned" (the
# PyAutoHeart M0-M5 cluster sat exactly like that). Reconcile cross-references
# the backlog against the Mind's shipped-state records and RANKS suspects for a
# human to retire. Read-only, always: retiring a prompt (moving it to issued/)
# stays a human act, and the final verification — the target repo's git log /
# merged PRs — stays out of scope by design.
_STOPWORDS = frozenset(
"the a an of to in for and or is are be with on by via from into as at it "
"this that use using make add new fix update support get set can we i you "
"our my need should will when once each all its".split())
# Wording in a complete.md reference line that marks the prompt as a deferred
# follow-up (still open) rather than the shipped task itself.
_FOLLOWUP_WORDS = ("follow", "restore", "parked", "remain", "blocked", "later",
"next step", "next-step", "deferred")


def _tokens(s: str) -> set:
return {w for w in re.findall(r"[a-z0-9]+", s.lower())
if len(w) > 2 and w not in _STOPWORDS}


def reconcile(mind: Path, prefix: str = "") -> dict:
"""Rank backlog prompts that look already-shipped, for a human to retire.

Four Mind-local signals per prompt: a complete.md line referencing its path
(follow-up wording downgrades it), a duplicate basename in issued/, token
overlap with a completed task's `## header`, and a hand-set Status the
formalise pass deliberately preserved. Never writes anything.
"""
c = census(mind)
comp = mind / "complete.md"
comp_lines = (comp.read_text(encoding="utf-8", errors="replace").splitlines()
if comp.is_file() else [])
headers = [(ln[3:].strip(), _tokens(ln[3:].replace("-", " ")))
for ln in comp_lines if ln.startswith("## ")]
issued = mind / "issued"
issued_names = ({p.name for p in issued.glob("*.md")}
if issued.is_dir() else set())

suspects = []
for r in c["records"]:
if prefix and not r["path"].startswith(prefix):
continue
path = r["path"]
base = path.rsplit("/", 1)[-1]
sans_wt = path.split("/", 1)[1] if "/" in path else path
findings = []
score = 0.0

for ln in comp_lines:
if base in ln or sans_wt in ln:
kind = ("referenced-followup"
if any(w in ln.lower() for w in _FOLLOWUP_WORDS)
else "referenced")
findings.append((kind, ln.strip()))

if base in issued_names:
findings.append(("issued-duplicate", f"issued/{base} already exists"))

sig = _tokens(base.replace("_", " ")) | _tokens(r["title"])
best = (0.0, "", set())
for h, ht in headers:
if not sig or not ht:
continue
shared = sig & ht
j = len(shared) / len(sig | ht)
if (j, len(shared)) > (best[0], len(best[2])):
best = (j, h, shared)
if best[0] >= 0.40 or len(best[2]) >= 3:
score = best[0]
findings.append(("topic-overlap",
f"complete.md '## {best[1]}' "
f"(shared: {', '.join(sorted(best[2]))})"))

if r["status"] not in ("-", "formalised"):
findings.append(("stale-status",
f"Status: {r['status']} — hand-set; verify against "
"shipped state"))

if findings:
kinds = {k for k, _ in findings}
if "issued-duplicate" in kinds or "referenced" in kinds:
conf = "high"
elif "topic-overlap" in kinds:
conf = "medium"
else:
conf = "low" # follow-up reference / stale status only
suspects.append({
"path": path, "title": r["title"], "confidence": conf,
"overlap_score": round(score, 2),
"findings": [{"kind": k, "evidence": e} for k, e in findings],
})

order = {"high": 0, "medium": 1, "low": 2}
suspects.sort(key=lambda s: (order[s["confidence"]],
-s["overlap_score"], s["path"]))
return {"generated": _dt.date.today().isoformat(), "scanned": c["total"],
"suspects": suspects}


def emit_reconcile(res: dict):
print(f"== Intake reconcile: {len(res['suspects'])} suspect(s) of "
f"{res['scanned']} scanned ==")
if not res["suspects"]:
print(" backlog reconciles clean against complete.md / issued/.")
for s in res["suspects"]:
print(f"[{s['confidence']:>6}] {s['path']}")
for f in s["findings"]:
ev = f["evidence"]
if len(ev) > 160:
ev = ev[:157] + "…"
print(f" {f['kind']}: {ev}")
print("\nRetiring a prompt stays human: verify against the target repo's "
"git log / merged\nPRs, then move it to issued/ by hand.")


# --- ideas.md scanning --------------------------------------------------------
def scan_ideas(mind: Path):
"""Yield (bullet_text, context_header) for substantive ideas.md lines."""
Expand Down Expand Up @@ -679,6 +797,11 @@ def main(argv=None):
help="only formalise prompts under this path prefix "
"(e.g. bug/)")

rc = sub.add_parser("reconcile", help="rank backlog prompts that look "
"already-shipped (always read-only)")
rc.add_argument("prefix", nargs="?", default="",
help="only reconcile prompts under this path prefix")

a = ap.parse_args(argv)
mind = Path(a.mind)

Expand All @@ -687,6 +810,14 @@ def main(argv=None):
print(json.dumps(res, indent=2)) if a.as_json else emit_formalise(res)
return 0

if a.cmd == "reconcile":
if a.apply:
print("intake reconcile is read-only — retiring prompts stays "
"human (--apply ignored).", file=sys.stderr)
res = reconcile(mind, prefix=a.prefix)
print(json.dumps(res, indent=2)) if a.as_json else emit_reconcile(res)
return 0

if a.cmd == "census":
c = census(mind)
print(json.dumps(c, indent=2)) if a.as_json else emit_census(c)
Expand Down
7 changes: 5 additions & 2 deletions agents/conductors/intake/intake.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
# intake formalise [prefix] retroactively header the backlog prompts the
# census flags (word-vomit is intent, not defect);
# --apply writes the headers in place
# intake reconcile [prefix] rank backlog prompts that look already-shipped
# (vs complete.md / issued/); always read-only —
# retiring a prompt stays human
#
# Flags (place before the subcommand; both default OFF):
# --apply write the formal prompt file(s) / dashboard.md; else dry-run only
Expand All @@ -45,7 +48,7 @@ apply=0
forward=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) sed -n '2,36p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
-h|--help) sed -n '2,39p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
--json) as_json=1; shift ;;
--apply) apply=1; shift ;;
*) forward+=("$1"); shift ;;
Expand All @@ -61,7 +64,7 @@ fi
# A bare first token that is not a known subcommand -> classify mode on the rest,
# so `intake "raw idea"` and `intake --file p.md` both work as the front door.
case "${forward[0]}" in
classify|ideas|census|dashboard|formalise) ;;
classify|ideas|census|dashboard|formalise|reconcile) ;;
*) forward=(classify "${forward[@]}") ;;
esac

Expand Down
5 changes: 5 additions & 0 deletions skills/intake/intake.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ dev workflow (issue, branch, plan). Do not bypass the Brain.
fields, inserts them in place with all prose verbatim, reports re-home
suggestions instead of ever moving files. Dry-run proposes; `--apply` writes
(then regenerate the dashboard).
- `bin/pyauto-brain intake reconcile [prefix]` — ranks backlog prompts that
look already-shipped (cross-referenced against `complete.md` and `issued/`;
a stale hand-set `Status:` is a signal, never proof). Always read-only:
verify each suspect against the target repo's git log / merged PRs, then
retire it to `issued/` by hand.

## Boundary

Expand Down