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
19 changes: 10 additions & 9 deletions board/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ community collection. The board does not duplicate their dashboards.
procedures. Keep this the first content section.
2. A shared orchestration panel for an unspecified task (shared layout places
it beside the banner controls). Focus on the user's request and route it.
3. Review overnight work: only failures, blocked gates, pending/missing runs
and unavailable evidence. Successful routine runs remain in machine data.
4. Maintenance: issue/repo cleanup, Hygiene findings and local observations,
3. Maintenance: issue/repo cleanup, Hygiene findings and local observations,
plus an actionable CI-speedup entry linked to Heart's timing evidence.

No morning sync, community section/shortcut, readiness/release, versions,
Expand All @@ -25,11 +23,14 @@ operational evidence lives in another organ.

## Collection and compatibility

`board/_board.py` collects overnight jobs from `config/policy.yaml`, Heart's
performance evidence, open issue counts, Hygiene and explicit dev-box publishes.
`board/_board.py` collects Heart's performance evidence, open issue counts,
Hygiene and explicit dev-box publishes. It does not poll overnight workflows.
Scientist reviews dated owner records and workflow runs on demand for the
requested period; no scheduled digest or duplicate activity ledger is created.
Retired source collectors are not called. Deprecated board.json keys remain
empty for existing readers. state.json retains the shared v1 schema; badge and
state describe Brain's overnight coordination only, never release readiness.
state describe availability of Brain's directory and maintenance evidence,
never overnight outcomes or release readiness.
The internal `degraded` list remains diagnostic metadata and keeps freshness
unknown when a required read fails, but is not a global page section.

Expand Down Expand Up @@ -65,9 +66,9 @@ must agree; escape owner data and preserve exact portable copy payloads.
Without gh, `--github-data <file>` accepts endpoint-keyed JSON supplied by the
agent's GitHub tools. An endpoint absent from that file or explicitly null means
unavailable, never an empty result. Malformed JSON is fatal. Preserve responses
verbatim. Each configured overnight workflow reads
`repos/<org>/<repo>/actions/workflows/<workflow>/runs?per_page=1`, with optional
`repos/<org>/<repo>/actions/runs/<id>/jobs` for blocked-gate evidence.
verbatim. This adapter supports retained sources; it does not re-enable
retired overnight collection. Legacy explicit diagnostic helpers remain for
compatibility, separate from board publication.

`BOARD_GH` and `BOARD_PAGES_BASE` support hermetic fixtures. A failed Pages
read may use an owning repo's local published artifact; it must not manufacture
Expand Down
104 changes: 11 additions & 93 deletions board/_board.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Brain's agents/workflows directory, overnight exceptions and maintenance entry.
"""Brain's agents/workflows directory and maintenance entry.

Read-only collection feeds HTML, Markdown and version-compatible machine feeds.
Other organs own operational evidence; Brain plans and routes work.
Expand Down Expand Up @@ -194,8 +194,10 @@ def age_label(hours):


def collect_overnight(jobs, org, degraded):
"""Latest run of each scheduled workflow (the what-ran-while-I-slept
glance), with the blocked-at-a-gate refinement from overnight_status.sh."""
"""Legacy explicit diagnostic helper; never called by dashboard collection.

This latest-run view cannot establish activity across a requested period.
"""
rows = []
for job in jobs:
repo, _, wf = str(job).partition(":")
Expand Down Expand Up @@ -596,7 +598,6 @@ def collect():
degraded = []
board_family = board_cfg.get("boards") or {}
heart_repo = board_cfg.get("heart_board", "PyAutoHeart")
overnight = collect_overnight(board_cfg.get("overnight_jobs", []), org, degraded)
# Heart owns performance evidence; Brain offers the investigation workflow.
heart_board = fetch_heart_board(pages_base, heart_repo, degraded)
performance = extract_heart_performance(heart_board, f"{pages_base}/{heart_repo}/")
Expand All @@ -608,7 +609,7 @@ def collect():
"generated": now.strftime("%Y-%m-%d %H:%M UTC"),
"org": org,
"repo": board_family.get("brain", "PyAutoBrain"),
"overnight": overnight,
"overnight": [], # Retired compatibility field; Scientist reviews on demand.
"heart": None,
"heart_monitoring": None,
"heart_blockers": [],
Expand Down Expand Up @@ -638,17 +639,6 @@ def collect():
# --------------------------------------------------------------- verdict ----


def verdict(data):
"""Classify overnight exceptions only; other organs own their verdicts."""
blocking, attention = [], []
for r in data["overnight"]:
if r["conclusion"] not in (None, "success"):
blocking.append(f"overnight: {r['repo']}/{r['workflow']} {r['conclusion']}")
elif r["blocked"]:
attention.append(f"overnight: {r['repo']}/{r['workflow']} blocked at a gate")
return blocking, attention


def headline(data):
"""The one line a reader acts on — qualified by how much was actually read.

Expand All @@ -658,23 +648,15 @@ def headline(data):
brightgreen: the strongest possible all-clear, from a board that had asked
nothing. Absence of findings is not a finding.
"""
blocking, attention = verdict(data)
n = len(blocking) + len(attention)
core = "Agents & workflows" if n == 0 else f"{n} overnight item(s) to review"
core = "Agents & workflows"
unread = len(data.get("degraded") or [])
if unread:
core += f" · partial view ({unread} leg{'s' if unread != 1 else ''} unread)"
return core


def badge_color(data):
blocking, attention = verdict(data)
if blocking:
return "red"
if attention:
return "orange"
# Nothing found AND nothing unread is the only green. Nothing found because
# nothing could be asked is grey, not green.
# Directory/evidence availability only, never a workflow or release verdict.
if data.get("degraded"):
return "lightgrey"
return "brightgreen"
Expand Down Expand Up @@ -775,26 +757,11 @@ def _overnight_state_item(r):
return item


def _state_items(data):
"""The rows the board already renders as asking something of a human —
composed, never re-derived: each prompt is the owning organ's own payload
(the Heart's /bug line, the Ears' triage ref), carried verbatim."""
items = []
for r in data.get("overnight") or []:
item = _overnight_state_item(r)
if item is not None:
items.append(item)
order = {"red": 0, "yellow": 1, "info": 2}
items.sort(key=lambda i: order[i["severity"]]) # stable: section order kept
return items[:STATE_ITEM_CAP]


def render_state(data):
"""state.json — the organ cockpit feed (board/_state.py, contract v1).

Same verdict as the badge (status mirrors badge_color, headline is the
badge message) plus the actionable rows, so the cockpit never disagrees
with the badge beside it.
Directory/evidence availability mirrors the badge. Operational activity
remains with its owners and Scientist reviews it on demand.
"""
data = _portable_board_data(data)
repo = data.get("repo") or "PyAutoBrain"
Expand All @@ -807,7 +774,7 @@ def render_state(data):
headline=headline(data),
updated=_iso_generated(data),
pages_url=pages_url,
items=_state_items(data),
items=(), # No scheduled activity digest; owners keep operational evidence.
)
return json.dumps(state, indent=2) + "\n"

Expand Down Expand Up @@ -850,15 +817,6 @@ def render_md(data):
if door["tier"] == tier:
verb = door["verb"].replace("_", "-")
lines.append(f"- **{verb}** — {door['desc']} · Use the {verb} skill.")
lines += ["", "## Review overnight work"]
runs = review_runs(data)
for run in runs:
lines.append(_overnight_line(run))
item = _overnight_state_item(run)
if item and item.get("prompt"):
lines.append(f" {item['prompt']}")
if not runs:
lines.append("No overnight exceptions reported.")
lines += ["", "## Maintenance"]
lines += [f"- {label}: {prompt}" for label, prompt in MAINTENANCE_ACTIONS]
heart = data.get("boards", {}).get("heart", "")
Expand Down Expand Up @@ -919,7 +877,6 @@ def render_html(data):
f"<style>{_theme_css(THEME_ORGAN)}</style></head><body>",
hero(THEME_ORGAN, "Agents & workflows", navigation=[
{"label": "Agents & workflows", "href": "#agents"},
{"label": "Review overnight work", "href": "#overnight"},
{"label": "Maintenance", "href": "#maintenance"}])]
work_links = ([{"label": "Open work repository", "href":
f"https://github.com/{data['org']}/{data['repo']}"}]
Expand Down Expand Up @@ -952,45 +909,6 @@ def render_html(data):
+ pills((tier, "" if tier == "conductor" else "n")),
f"Use the {verb} skill."))
H.append("</details>")
H.append('<a id="overnight"></a><h2>Review overnight work</h2>')
if not review_runs(data):
H.append(_plain("No overnight exceptions reported."))
for r in review_runs(data):
# The workflow is the row's subject; its repo and its conclusion are
# facets, so they read as pills — the repo in the organ accent
# (identity) and the conclusion in its verdict tone.
link = f' — <a href="{_attr(r["url"])}">run ↗</a>' if r["url"] else ""
age = (f' <span class="muted">({age_label(r["age_h"])})</span>'
if r["conclusion"] is not None else "")
subject = f'<b>{esc(r["workflow"])}</b>{age}{link}'
failed = r["conclusion"] not in (None, "success")
if r.get("unreadable"):
state, tone, reason = "could not read", "y", ""
elif r["conclusion"] is None:
state, tone, reason = "no runs", "n", ""
elif r["blocked"]:
state, tone = "blocked at a gate", "y"
reason = ('<br><span class="muted">no change made — '
f'{esc(r["blocked_reason"])}</span>'
if r.get("blocked_reason") else
'<br><span class="muted">no change made</span>')
elif not failed:
state, tone, reason = "success", "g", ""
else:
state, tone, reason = r["conclusion"], "r", ""
# pills() escapes its own values; only the hand-built html above is
# escaped here.
text = subject + reason + pills((r["repo"], ""), (state, tone))
if failed and not r["blocked"]:
H.append(_row(text, f"Use the bug skill. overnight: {r['repo']}/{r['workflow']} "
f"concluded {r['conclusion']} — "
f"{r['url'] or 'no run url'}"))
else:
if r["blocked"]:
H.append(_row(text, f"Investigate the blocked overnight workflow {r['repo']}/{r['workflow']} using {r['url'] or 'the owning workflow'}. Read the gate reason and route the next action; do not bypass the gate."))
else:
H.append(_plain(text))

H.append('<a id="maintenance"></a><h2>Maintenance</h2>')
for label, prompt in MAINTENANCE_ACTIONS:
H.append(_row(esc(label), prompt))
Expand Down
15 changes: 2 additions & 13 deletions config/policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,9 @@ release:
HowToFit, HowToGalaxy, HowToLens]
tag_repo: PyAutoLabs/PyAutoLens

# The Brain board (board/_board.py): scheduled workflows for overnight review
# and sibling board destinations. Owner/org is derived from the Mind's body map at runtime, so entries
# here are repo-relative unless a home lies outside the primary org.
# overnight_jobs mirrors bin/overnight_status.sh (its CLI complement) — keep
# the two lists in step until the script reads this block too.
# Brain board destinations. Activity review belongs to Scientist on demand;
# there is no scheduled overnight workflow inventory in this board.
board:
overnight_jobs:
- PyAutoBrain:nightly-release.yml
- PyAutoHeart:heart-health.yml
- PyAutoHeart:workspace-smoke.yml
- PyAutoHands:python_matrix.yml
- PyAutoMind:arxiv_papers.yml
- PyAutoMind:spawn_drift.yml
- autolens_assistant:wiki-currency.yml
heart_board: PyAutoHeart
# The full one-tap board family (the cross-board footer nav every board
# carries; each renderer skips its own entry). Parsed by board/_board.py
Expand Down
55 changes: 13 additions & 42 deletions tests/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,27 +396,15 @@ def test_all_green_is_clear_to_work(tmp_path):
"message": "Agents & workflows", "color": "brightgreen"}


def test_overnight_failure_is_blocking_and_red(tmp_path):
def test_overnight_runs_no_longer_change_brain_badge_or_feed(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures(**{
"runs.json": _run_json("failure")}))
r = _run(["--badge"], tmp_path, stub)
badge = json.loads(r.stdout)
assert badge["color"] == "red"
assert badge["message"].endswith("overnight item(s) to review")
md = _run([], tmp_path, stub).stdout
assert "Review overnight work" in md and "failure" in md


def test_blocked_gate_is_attention_not_blocking(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures(**{
"jobs.json": {"jobs": [{"steps": [
{"name": "Blocked at a gate — no release made",
"conclusion": "success"}]}]}}))
r = _run(["--badge"], tmp_path, stub)
badge = json.loads(r.stdout)
assert badge["color"] == "orange"
html_page = _run(["--html"], tmp_path, stub).stdout
assert "blocked at a gate" in html_page
badge = json.loads(_run(["--badge"], tmp_path, stub).stdout)
assert badge["message"] == "Agents & workflows"
assert badge["color"] == "brightgreen"
assert json.loads(_run(["--json"], tmp_path, stub).stdout)["overnight"] == []
assert json.loads(_run(["--state"], tmp_path, stub).stdout)["items"] == []
assert "Review overnight work" not in _run([], tmp_path, stub).stdout


# -------------------------------------------------------------------- html --
Expand Down Expand Up @@ -447,7 +435,7 @@ def test_html_is_self_contained_with_one_tap_payloads(tmp_path):
assert 'data-cmd="Use the community skill."' in page
assert 'data-cmd="Use the issue-cleanup skill."' in page
assert 'data-cmd="Use the prm skill."' in page
assert "Use the bug skill. overnight:" in page # the failing run's payload
assert "Use the bug skill. overnight:" not in page # Scientist owns this entry
# The local morning leg is a TERMINAL chip, not a Claude payload.
assert "morning.sh" not in page
# The doors roster is on the page.
Expand All @@ -469,10 +457,11 @@ def test_navigation_matches_the_reduced_scope(tmp_path):
page = _run(["--html"], tmp_path, stub).stdout
nav = re.search(r'<nav class="board-nav".*?</nav>', page, re.S).group()
targets = re.findall(r'href="#([^"]+)"', nav)
assert targets == ["agents", "overnight", "maintenance"]
assert targets == ["agents", "maintenance"]
for target in targets:
assert f'id="{target}"' in page
assert page.index('<h2>Agents &amp; workflows') < page.index('<h2>Review overnight work') < page.index('<h2>Maintenance')
assert page.index('<h2>Agents &amp; workflows') < page.index('<h2>Maintenance')
assert 'id="overnight"' not in page


def test_unavailable_performance_is_local_to_maintenance(tmp_path):
Expand All @@ -483,24 +472,6 @@ def test_unavailable_performance_is_local_to_maintenance(tmp_path):
assert 'id="readiness"' not in page


def test_an_overnight_row_wears_its_repo_and_its_conclusion(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures(**{
"runs.json": _run_json("failure")}))
page = _run(["--html"], tmp_path, stub).stdout
# The repo is identity, so it takes the accent; the conclusion is a
# judgement, so it takes a tone.
assert '<span class="pill">ExampleOrg/PyAutoBrain</span>' in page
assert '<span class="pill r">failure</span>' in page
green = _run(["--html"], tmp_path,
_fabricate(tmp_path / "green", _default_fixtures())).stdout
assert '<span class="pill g">success</span>' not in green
assert "No overnight exceptions reported" in green






def test_a_door_is_accented_only_when_it_acts(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures())
page = _run(["--html"], tmp_path, stub).stdout
Expand Down Expand Up @@ -585,7 +556,7 @@ def test_boards_footer_lists_the_family_without_self(tmp_path):



def test_blocked_gate_annotation_renders_inline(tmp_path):
def test_blocked_gate_annotation_is_not_aggregated_on_brain(tmp_path):
stub = _fabricate(tmp_path, _default_fixtures(**{
"jobs.json": {"jobs": [{"id": 77, "steps": [
{"name": "Blocked at a gate — no release made",
Expand All @@ -596,7 +567,7 @@ def test_blocked_gate_annotation_renders_inline(tmp_path):
"was made.",
"annotation_level": "warning"}]}))
page = _run(["--html"], tmp_path, stub).stdout
assert "The driver stopped at a gate (exit 3)" in page
assert "The driver stopped at a gate (exit 3)" not in page


def _devbox_payload(ts):
Expand Down
Loading
Loading