diff --git a/agents/conductors/intake/AGENTS.md b/agents/conductors/intake/AGENTS.md index 1ef6dd9..9d9205a 100644 --- a/agents/conductors/intake/AGENTS.md +++ b/agents/conductors/intake/AGENTS.md @@ -89,15 +89,18 @@ schema — light structure over free-form prose. | **reconcile --repo** | `intake reconcile --repo [prefix]` | **also** read the target repo's source for identifiers the prompts name — the one signal that sees a prompt with no Mind-side trace. Opt-in; the default path is offline | **Recent** is the one section laid out by *date* rather than by state: the 20 -newest task events merged across every bucket — issued, parked, filed, -completed — sitting between the Backlog and the Epics. Every other section -answers "what should I do now?"; recency is orthogonal to state, so none of -them can answer "what has been happening?". Dates come from the registry key -that names the event (`issued:` / `parked:` / `filed:`, PyAutoMind REFERENCE.md -"Task dates"), a prompt's own `Issued:` header, or a record's `completed:`. -Live work is selected first and completions fill the rest — on a Mind that -ships two hundred records a month a straight date sort is twenty receipts and -no work. +newest events on the **work in hand** — issued, parked, filed — merged across +the live buckets and sitting between the Backlog and the Epics. Every other +section answers "what should I do now?"; recency is orthogonal to state, so +none of them can answer "what has been happening?". Dates come from the +registry key that names the event (`issued:` / `parked:` / `filed:`, PyAutoMind +REFERENCE.md "Task dates") or a prompt's own `Issued:` header. + +Shipped work is deliberately **not** in the feed and `complete/` is never +opened to render it: the ledger is a thousand records deep and takes ~200 a +month, so including it made the table a list of receipts — twenty things nobody +can act on, on the page whose whole job is work in hand. `complete/index.md` is +where shipped work is read. Census/dashboard are the Mind *backlog* view — deliberately distinct from Heart's `/health status` health view (see "must never do"). The prompt-taxonomy diff --git a/agents/conductors/intake/_intake.py b/agents/conductors/intake/_intake.py index 06b7893..c7bb1b7 100755 --- a/agents/conductors/intake/_intake.py +++ b/agents/conductors/intake/_intake.py @@ -492,33 +492,22 @@ def _clip(text: str, limit: int = 130) -> str: # --- the recent feed ----------------------------------------------------------- # The dashboard answers "what should I do now?" everywhere else on the page. -# This one section answers "what has been happening?" — the single question a -# task page laid out by STATE cannot answer, because a task's recency is -# orthogonal to which bucket it sits in. Merging the buckets by date puts the -# week's issuing, parking and shipping in one place, which is also the fastest -# way to spot a Mind that has drifted (three weeks of `filed` and nothing -# `completed` is visible at a glance and invisible section by section). +# This one section answers "what has been happening to the work in hand?" — the +# single question a task page laid out by STATE cannot answer, because a task's +# recency is orthogonal to which bucket it sits in. Merging the buckets by date +# puts the week's issuing, parking and filing in one place. +# +# Live work ONLY. The `complete/` ledger is not in this feed: it is a thousand +# records deep and ships ~200 a month, so including it made the table a list of +# receipts — twenty things nobody can act on, on the page whose whole job is +# work in hand. `complete/index.md` is where shipped work is read. RECENT_MAX = 20 -# How many month folders of `complete/` to open. The records are the bulk of -# the Mind (1000+ files), and only the newest handful can reach a 20-row feed, -# so the scan is bounded by folder rather than reading the whole ledger on -# every render. Two months is comfortably more than 20 records in practice; -# the scan widens on its own if they are not. -RECENT_MONTHS = 2 - -# Records open with their fields (`- completed: …`) but the ledger is a decade -# of hand-written prose, so a fifth of them say it another way. Read the head -# of the file only: a date deep in a narrative is something the task mentions, -# not when it shipped. -_RECORD_HEAD_BYTES = 4096 -_RECORD_DATE = re.compile(r"^-\s+(completed|shipped):\s*(.*)$", re.MULTILINE) - # The verb each event reads as in the feed. Past tense throughout — every row # is something that already happened. EVENT_LABEL = {"issued": "issued", "registered": "issued", "started": "started", "planned": "planned", "filed": "filed", "parked": "parked", - "found": "found", "completed": "completed", "shipped": "completed"} + "found": "found"} def _anchor(heading: str) -> str: @@ -526,108 +515,38 @@ def _anchor(heading: str) -> str: return re.sub(r"[^a-z0-9-]+", "-", heading.lower()).strip("-") -def _record_date(path: Path, text: str) -> str: - """The day a completion record's task shipped. - - `completed:`/`shipped:` when the record states one; otherwise the record's - own `complete///` folder, which the lifecycle engine files it - into by completion month — coarse, but never wrong about the month, and - the alternative is dropping a fifth of the ledger out of the feed.""" - m = _RECORD_DATE.search(text) - if m: - d = _ISO_DATE.search(m.group(2)) - if d: - return d.group(1) - parts = path.parts - if len(parts) >= 3 and parts[-3].isdigit() and parts[-2].isdigit(): - return f"{parts[-3]}-{parts[-2]}" - return "" - - -def completed_records(mind: Path, months: int = RECENT_MONTHS, - want: int = RECENT_MAX) -> list: - """The newest completion records, newest first — `[{date, slug, path}]`. - - Walks `complete///` newest-first and stops one month after it has - enough, so the whole ledger is never read to render a 20-row table. A month - is opened whole: records inside one are unordered, and a record can carry a - date from the month before it was filed under.""" - root = mind / "complete" - if not root.is_dir(): - return [] - folders = sorted( - (d for y in root.iterdir() if y.is_dir() and y.name.isdigit() - for d in y.iterdir() if d.is_dir() and d.name.isdigit()), - key=lambda d: (d.parent.name, d.name), reverse=True) - out, opened = [], 0 - for folder in folders: - opened += 1 - for f in folder.glob("*.md"): - text = f.read_text(encoding="utf-8", errors="replace")[:_RECORD_HEAD_BYTES] - out.append({"date": _record_date(f, text), - "slug": f.stem, - "path": str(f.relative_to(mind))}) - if opened >= months and len(out) >= want: - break - out.sort(key=lambda r: r["date"], reverse=True) - return out - - def recent_events(c: dict, limit: int = RECENT_MAX) -> list: - """The Mind's newest task events, newest first — `[{date, event, title, …}]`. + """The newest events on the work in hand, newest first. One row per task, not per event: a task that was filed and later issued appears once, on its latest date (which is what `_entry_date` already picks per entry). - **Live work is never crowded out by shipped work.** A straight date sort - would be all completions — this Mind ships ~200 records a month, so the - twenty newest dates in it are twenty records — and the one thing the feed - must show is the work still in hand. So the rows are *selected* live-first - (in flight, parked, planned), completions fill whatever room is left, and - the selected set is then sorted by date like any other feed. On a quiet - Mind that changes nothing; on a busy one it is the difference between a - table of work and a table of receipts. - Undated rows are absent rather than sorted to the bottom: `lifecycle.py dates` is where a missing date gets reported, and padding this table with unknowns would bury the answer it exists to give. """ - live, done = [], [] + events = [] for r in c.get("in_flight") or []: if r.get("date"): - live.append({"date": r["date"], "event": r.get("event") or "issued", - "title": r["title"], "path": r["path"], "live": True, - "payload": f"/start_dev {r['path']}"}) + events.append({"date": r["date"], "event": r.get("event") or "issued", + "title": r["title"], "path": r["path"], + "payload": f"/start_dev {r['path']}"}) for key, verb in (("parked", "resume"), ("planned", "start")): for e in c.get(key) or []: if e.get("date"): - live.append({"date": e["date"], - "event": e.get("event") or key, - "title": e["slug"], - # The entry's own heading anchor — a registry file - # is long enough that landing at its top is not - # the same as landing on the task. - "path": f"{key}.md#{_anchor(e['slug'])}", - "live": True, - "payload": _registry_payload(e, key, verb)}) - for r in c.get("completed") or []: - if r.get("date"): - done.append({"date": r["date"], "event": "completed", - "title": r["slug"], "path": r["path"], "live": False, - "payload": f"/memory recall the completed PyAutoMind " - f"task {r['slug']} — its record is " - f"{r['path']}"}) - - def _newest(rows): - return sorted(rows, key=lambda e: (e["date"], e["title"]), reverse=True) - - chosen = _newest(live)[:limit] - chosen += _newest(done)[:max(0, limit - len(chosen))] - chosen = _newest(chosen) - for e in chosen: + events.append({"date": e["date"], + "event": e.get("event") or key, + "title": e["slug"], + # The entry's own heading anchor — a registry + # file is long enough that landing at its top is + # not the same as landing on the task. + "path": f"{key}.md#{_anchor(e['slug'])}", + "payload": _registry_payload(e, key, verb)}) + events.sort(key=lambda e: (e["date"], e["title"]), reverse=True) + for e in events: e["event"] = EVENT_LABEL.get(e["event"], e["event"]) - return chosen + return events[:limit] def census(mind: Path) -> dict: @@ -749,7 +668,6 @@ def _count(key): "hygiene": hygiene, "drift": drift, } - c["completed"] = completed_records(mind) c["recent"] = recent_events(c) return c @@ -895,11 +813,12 @@ def _epic_members(c: dict) -> dict: RECENT_BLURB = ( - "The Mind's most recent {n} task events, newest first — what was issued, " - "parked, filed and shipped, in one place. Every other section on this page " - "is laid out by state, which is exactly why none of them can answer " - "\u201cwhat has been happening?\u201d. Work still in hand is listed " - "first-class here; completed records fill whatever room is left.") + "The {n} newest things to happen to the work in hand, newest first — " + "issued, parked, filed. Every other section on this page is laid out by " + "state, which is exactly why none of them can answer \u201cwhat has been " + "happening?\u201d. Shipped work is not here: it is read from " + "`complete/index.md`, and a thousand records deep it would crowd out " + "everything anyone can still act on.") def _dated(row: dict) -> str: @@ -1056,8 +975,8 @@ def render_dashboard(c: dict) -> str: "|------|-------|------|"] L += [f"| {r['date']} | {r['event']} | {_cell(_recent_link(r))} |" for r in recent] - L += ["", "_Dates come from each task's registry entry and each record's " - "`completed:` — `lifecycle.py dates` reports anything undated._", ""] + L += ["", "_Dates come from each task's registry entry — " + "`lifecycle.py dates` reports anything undated._", ""] # Epics live at the bottom, whole: each epic's resume prompt sits with its # queued member prompts, grouped and phase-ordered, so nobody picks a diff --git a/tests/test_intake_dashboard.py b/tests/test_intake_dashboard.py index 4781d53..fcd32ea 100644 --- a/tests/test_intake_dashboard.py +++ b/tests/test_intake_dashboard.py @@ -482,17 +482,34 @@ def _recent_mind(root, n_records=0, month="08"): }) -def test_recent_merges_every_state_into_one_dated_feed(tmp_path): +def test_recent_merges_every_live_state_into_one_dated_feed(tmp_path): """Recency is orthogonal to state, so it is the one question no other section on the page can answer.""" - page = _page(_recent_mind(tmp_path, n_records=3)) + page = _page(_recent_mind(tmp_path)) recent = page.split("## Recent")[1] - for slug in ("sprocket-calibration", "flywheel-balance", "gearbox-survey", - "shipped-00"): - assert slug in recent or slug.replace("-", " ") in recent.lower() + for slug in ("Sprocket calibration", "flywheel-balance", "gearbox-survey"): + assert slug in recent assert "| Date | Event | Task |" in recent +def test_shipped_work_is_not_in_the_feed(tmp_path): + """The `complete/` ledger is a thousand records deep and ships ~200 a + month, so including it made this a list of receipts — twenty things nobody + can act on, on the page whose whole job is work in hand.""" + mind = _recent_mind(tmp_path, n_records=40) + rows = _intake.census(mind)["recent"] + assert len(rows) == 3 + assert not any("shipped" in r["title"] for r in rows) + assert "shipped-00" not in _page(mind) + + +def test_the_complete_ledger_is_never_opened(tmp_path): + """Not merely filtered out afterwards — a 20-row table of live work must + not read a thousand records to render.""" + assert not hasattr(_intake, "completed_records") + assert "completed" not in _intake.census(_recent_mind(tmp_path, n_records=5)) + + def test_recent_names_the_event_each_date_records(tmp_path): """A bare date says nothing; `parked` / `issued` / `filed` says what happened — the whole reason the registry key is the event name.""" @@ -517,18 +534,6 @@ def test_recent_sits_after_the_backlog_and_before_the_epics(tmp_path): assert page.index("## Backlog") < page.index("## Recent") < page.index("## Epics") -def test_a_busy_ledger_never_crowds_live_work_out_of_the_feed(tmp_path): - """A straight date sort on a Mind that ships 200 records a month is 20 - receipts and no work. Live tasks are selected first; records fill the - rest — otherwise the dates on active tasks would be invisible on the very - page they were added for.""" - rows = _intake.census(_recent_mind(tmp_path, n_records=40))["recent"] - assert len(rows) == _intake.RECENT_MAX - live = [r for r in rows if r["live"]] - assert {r["title"] for r in live} == {"Sprocket calibration", - "flywheel-balance", "gearbox-survey"} - - def test_an_undated_task_is_absent_rather_than_sorted_to_the_bottom(tmp_path): """`lifecycle.py dates` is where a missing date gets reported; padding the feed with unknowns would bury the answer it exists to give.""" @@ -575,15 +580,6 @@ def test_a_date_in_another_fields_prose_does_not_count_as_a_date(tmp_path): assert _intake.census(mind)["recent"] == [] -def test_a_record_without_a_completed_field_still_dates_by_its_folder(tmp_path): - """A fifth of the ledger states its date in prose. The month folder the - lifecycle engine filed it into is coarse but never wrong about the month, - and the alternative is dropping those records out of the feed.""" - mind = _mind(tmp_path, complete={ - "2026/07/legacy_record.md": "Shipped one July afternoon.\n"}) - assert _intake.census(mind)["recent"][0]["date"] == "2026-07" - - def test_the_html_twin_carries_the_same_feed_with_real_copy_buttons(tmp_path): html = _intake.render_dashboard_html(_intake.census(_recent_mind(tmp_path))) assert "

Recent" in html @@ -593,17 +589,6 @@ def test_the_html_twin_carries_the_same_feed_with_real_copy_buttons(tmp_path): assert 'data-cmd="/start_dev active/sprocket_calibration.md"' in html -def test_the_completed_scan_does_not_read_the_whole_ledger(tmp_path): - """1000+ records is the bulk of the Mind; a 20-row table must not open all - of them on every render.""" - mind = _mind(tmp_path, complete={ - f"2026/{m:02d}/rec-{m}-{i:02d}.md": _record(f"rec-{m}-{i}", f"2026-{m:02d}-01") - for m in range(1, 9) for i in range(30)}) - scanned = _intake.completed_records(mind) - assert len(scanned) < 240 - assert scanned[0]["date"] == "2026-08-01" - - def test_a_live_row_wears_its_date_where_the_task_is(tmp_path): """A status line reads very differently against a row issued yesterday than against one issued in May, so the date rides on the row too — not