board: 11 → review (istos)

This commit is contained in:
istos
2026-07-30 13:10:55 +02:00
parent 3693b66574
commit 55a5d2a781
@@ -1,6 +1,6 @@
# 11 — A failed agent run must leave a visible trace on the card
**Status:** In Progress
**Status:** Review
**Assignee:** istos
**Priority:** High — three agents died today and the board showed nothing a human would notice
**Type:** Feature
@@ -86,3 +86,21 @@ Born from the 2026-07-29 API outage: agents on 06, 07 and 08 died with
(card 10), and every one of them looked identical to "nothing
happening". The empty-branch guard from card 05 fixed the silent
*success* path; this is its sibling for the failure path.
---
## Work report — 2026-07-30 13:10 (Milo)
nded` on every run and, when a run exits non-zero and was not held, calls the new `_record_failure`. That puts a `failure` dict on the run's record (exit code, ended-at, the cleaned tail of the log as the excerpt, the stage the card was in, the log path) and broadcasts a toast. Because it hangs off `_finish`, all four headless kinds get it — work, act-pr, PR review, relevance check. `_agent_public` exposes `failure` and `ended`, so `/api/state` carries them. The failure arm of `_reap_agent` now also calls `_discard_untouched_worktree`: a dead run with zero commits leaves no worktree behind, so **▸ start work** functions without a hand `git worktree remove`; a dead run *with* commits keeps its worktree. All four ticker lines now name what the log ended on instead of saying "see its log".
- `manager/core/watch.py` — the move branch of `narrate` calls `agents.forget_failure(file)`, so a card that changes stage arrives without the alarm. (`agents` sits left of `watch` in the module map, so the import direction holds.)
- `manager/core/board.html``lastRunOn` / `failedRun` / `whyFailed`; a `.card.run-failed` alarm border, a `run failed` pill carrying the excerpt as its tooltip, and an alarm well on the card showing `rc=N · <the line it died on>`. The card sheet shows the whole excerpt in a bounded `<pre>` plus the log path. The failure outranks a PR verdict, and is only worn on the stage the run died in.
- `AGENTS.md` — a new "A run that died" subsection documents the state, its scoping and the worktree cleanup; the stale `.agent/logs/` path in the sentence above it is corrected to `local/state/agent/logs/`.
- `tests/test_failed_run_visible.py` — real launches through a real stub adapter (scripts that print a line and exit 1, that commit then exit 1, and that exit 1 silently), covering the record, the toast, the surviving ticker line, the public payload, the worktree cleanup and the successful relaunch after it, a dead relevance check, excerpt/headline edge cases, `forget_failure` scoping, and source-level invariants for board.html.
**What a reviewer should look at first**
- `manager/core/agents.py:452` (`_finish`) — recording the failure for every kind in one place is the design decision the rest follows from.
- `manager/core/board.html:720` (`failedRun`) — the two-part scoping (most recent run, and only in the stage it failed in) is what keeps the alarm off other cards and out of review/.
**One thing to know:** I verified the frontend logic by extracting `lastRunOn` / `failedRun` / `whyFailed` and running them under `node` against fabricated state (failure shows on its own card in its stage, disappears when the card is in review/, does not appear on another card, is superseded by a newer run, and the headline is the log's last line), plus the source-level invariants in the test file. I did not open the board in a browser to look at the rendered card, so the visual — border, pill and well together — is the one thing worth eyeballing before merge.