feat: surface agent errors to the TUI + progress-aware resume backoff#53
Open
mkonopelski-gd wants to merge 6 commits into
Open
feat: surface agent errors to the TUI + progress-aware resume backoff#53mkonopelski-gd wants to merge 6 commits into
mkonopelski-gd wants to merge 6 commits into
Conversation
Agent/phase-level failures (SDK crashes, resume give-ups, model fallbacks, workspace aborts) existed only in per-phase log files — nothing durable, nothing in /status, nothing in the TUI. Incident est-e2dfad7b42d7/ws-03-1-phase12: three transient socket errors ~2h apart exhausted the fixed 2-resume budget despite each attempt saving real work; the workspace stalled 3 days and a later retry finished the "failed" phase in 4 minutes. Durable event channel: - agent_error_events (bounded, 200) on the session doc, written only via new state-machine methods (Commandment VII); best-effort recorder wired through TelemetryContext like the totals handler, so deep agent code records events with no signature changes and never raises. - /status ships the newest 50 events + per-workspace agent_state badge; TUI renders a yellow "Agent warnings" panel (mirror of the Error panel), bare RETRYING/ABORTED badges + warning markers on workspace rows, a per-workspace warnings block on the drill-in (model switches pinned first), crash/retry lines in the live feed (new "error" stream kind), a full Events screen (e), and a check_status warnings clause. Unified resume loop (retries are the main logic, not an appendix): - one attempt loop (coding + validator per attempt) with a single classify_error-driven except handler; ResumeGeneration is the sole source for loop condition, log lines, events, and the TUI badge. - every crash waits on one schedule (1/3/5/10/30/60 min); saved progress (new git commit OR >=20 tool uses) resets the schedule; six consecutive zero-progress crashes give up; no total cap. Validator budget stays 2. A dead validator now retries instead of silently marking the phase complete, and the give-up warning can no longer fire on success. Minimum-survivors gate (un-drops what #47 deferred, strengthened): - codegen fails the run when survivors < min(2, total) — variance estimates need two samples; a total wipeout previously completed GREEN with an empty estimate. Deploy keeps the all-failed rule. Aggregated message names per-workspace causes and the retry path; model-caused failures recommend changing the model in Settings. Also fixes the Recent-activity panel tailing macOS .DS_Store garbage (logs live nested; now rglob("*.log") only).
The earlier `.DS_Store`/nested-log discovery fix made this panel actually locate the agent log — which exposed that it renders a raw tail of DEBUG-level records (full `UserMessage(content=[ToolResultBlock(...)])` dumps with escaped newlines): a wall of text that isn't useful at a glance. Hide it from the dashboard for now. `_activity_panel` and `tui/activity.py` are kept intact (and still point at the correct nested log dir), so a proper filtered/prettified view can be revived by re-appending the panel in build_dashboard.
The session dashboard should show only the run's outcome — the estimate when complete, or the fatal Error — not a running list of per-workspace warnings. The "Agent warnings" panel violated that: it grew a scrolling log right under Workspaces (and, with Recent activity now hidden, became the prominent panel there). Remove it from build_dashboard. Nothing is lost: the Workspaces panel already carries bare RETRYING/ABORTED/⚠ markers at a glance, and the full per-event detail lives in the workspace drill-in warnings block and the Events screen (`e`). `_event_row_text` stays (shared by both of those).
Manual/auto retry (reset_for_retry) reset retry_count/error but left the transient per-workspace agent_state (RETRYING/ABORTED) untouched. Because that badge is cleared only as a side-effect of the resume loop's crash→backoff path, a retried run left stale ABORTED/RETRYING markers on any workspace that didn't happen to re-enter that path — so after pressing "r", one workspace's badge cleared while its siblings kept a phantom ABORTED. reset_for_retry now strips agent_state from every workspace_phases entry (preserving last_completed_phase / planning_data so the retry still resumes from the right phase), giving all workspaces a clean slate.
agent_query_with_resume derived its final AgentResult from last_returned regardless of stop_reason. Because the crash branch only populates last_returned when it is still None, a single clean-but-incomplete attempt before a sustained outage left last_returned holding that stale success (is_error=False). On a NO_PROGRESS_BUDGET (or TOOL_CALL_FAILURE) give-up, final returned it verbatim, so execute_all_phases took the non-error branch: it checkpointed the incomplete phase as complete and never hit the CONNECTION_ERROR abort path (#47). On retry the phase was silently skipped. Return an is_error=True result carrying last_error for crash-based give-ups; COMPLETED and VALIDATOR_BUDGET (the anti-stuck contract) still return the last coding result as-is. Adds two regression tests covering the mixed clean-incomplete -> outage and clean-incomplete -> tool-call sequences.
The helper runs a coding query then a validator query; the new name says what it does (coding pass + completion check) rather than the generic 'attempt'.
mkonopelski-gd
marked this pull request as ready for review
July 24, 2026 08:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Agent/phase-level failures (SDK crashes, resume give-ups, model fallbacks, workspace aborts) previously existed only in per-phase log files — nothing durable, nothing in
/status, nothing in the TUI. This PR gives them the same treatment app-level errors already get (red badge + Error panel), and reworks the resume counter so only consecutive zero-progress failures exhaust it.Motivating incident (
est-e2dfad7b42d7/ws-03-1-phase12, 2026-07-17): three transient socket errors ~2h apart exhausted the fixed 2-resume budget even though every attempt saved real work (commits, 136 tool uses). The workspace hard-aborted, stalled for 3 days, and a manual retry then finished the "failed" phase in 4 minutes for $0.41. A log-corpus sweep (63 errors, 32 files, 6 generations) showed 81% of crash-affected phases rode transient errors to cap exhaustion.1. Durable agent-error event channel
agent_error_events(last 200) on the session doc + per-workspaceagent_statebadge, written only via newGenerationSessionStateMachinemethods (Commandment VII; grep-guard clean).TelemetryContext(same pattern as the totals handler) — deep agent code records events with zero signature changes and can never raise into the agent loop./statusships the newest 50 events +agent_statepassthrough; key omitted for old docs (no shape change).2. TUI surfacing
press e for all).RETRYING/ABORTEDbadges +⚠markers on workspace rows — details deliberately live in the drill-in: a per-workspace warnings block (model switches pinned first) between Live messages and Stats, plus crash/retry narrative lines published into the live feed itself (new"error"stream kind; forward-compatible for old TUIs).e).check_statuschat message gains a one-line warnings clause while running..DS_Storeas binary garbage (logs live nested; discovery is now recursive*.log-only).3. Unified resume loop with progress-aware backoff
agent_query_with_resumerewritten as one attempt loop (coding + validator per attempt) with a singleclassify_error-driven except handler; all loop state lives in oneResumeGenerationobject (single source for the loop condition, log lines, events, and the TUI badge — they can't disagree).rev-parse HEADbefore/after).model_fallbackevent (old → new model + reason) — a silently swapped model is no longer invisible.4. Minimum-survivors gate (un-drops what #47 deferred, strengthened)
A total codegen wipeout previously ended COMPLETED/green with an empty estimate (aborts are absorbed per-workspace; P10Y deliberately doesn't fail on empty aggregates). PR #47 built
_raise_if_all_workspaces_failedand dropped it as out-of-scope (3e35205). This PR reinstates it, strengthened: codegen fails when survivors< min(2, total)— variance-reduced estimates need ≥2 samples (single-workspace runs still pass). Deploy keeps the all-failed rule. The aggregated message names per-workspace causes and the retry path; workspaces stay ALLOCATED and checkpoints untouched, soretry_generationresumes only the failed workspaces.Behavior changes to review
< min(2, total)codegen survivors → generation FAILS (previously green).phase_incompleteevent.Testing
test_resume_generation.pyencodes the budget table verbatim;test_agent_query_with_resume.pyincludes an incident-regression test; survivors-gate, state-machine, recorder, executor-abort, and /status tests). Also fixes a pre-existing broken test (test_execute_all_phases_connection_errorpassed a non-awaitable mock to the newer cancellation check).agent_query_with_resume19 → 18; MI avg +0.05.RETRYINGbadge + schedule reset on recovery; kill all-but-one workspace → red FAILED with the insufficient-survivors message +rretry resumes from checkpoints.Follow-ups (agreed, not in this PR)
llm_overrideson the retry endpoint (design agreed).subtype='success'+is_error=True; turn-budget exhaustion is not transient).