Skip to content

Commit e5dc7c5

Browse files
Smrutiranjan Patriclaude
andcommitted
fix(tui): consolidation review no longer returns silently in the run dashboard
A book run in manual mode that stalled on a continuity-check contradiction (pending_review with review_kind=consolidation) fell through the post-run router (which only handled chapter/section escalations) and dropped to the prompt with no card. Add _consolidation_card (resume with 'run --force') and a catch-all final branch so no not-done terminal state ever returns silently. Autonomous runs auto-repair contradictions and were unaffected. Tests: consolidation card renders its resume command; explain_error now covers context-overflow + token-budget. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 96f89c0 commit e5dc7c5

5 files changed

Lines changed: 67 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6464
`_isolated_settings` fixture points `config._SETTINGS` at a tmp path, so the suite always runs against
6565
shipped dataclass defaults; a local `config/settings.yaml` (e.g. `agentic=true`) can no longer turn a
6666
local run red while CI (which has no such file, it's gitignored) stays green.
67+
- **Consolidation review no longer returns silently (`dashboard.py`).** A book run in manual mode that
68+
stalled on a continuity-check contradiction (`pending_review` with `review_kind="consolidation"`) fell
69+
through the post-run router — which only handled `chapter`/`section` escalations — and dropped back to
70+
the prompt with no card (only a `[!]` line buried in the event log). It now shows a `_consolidation_card`
71+
with the resume command (`run --force`), and a catch-all final branch guarantees no not-done terminal
72+
state ever returns silently. (Autonomous runs auto-repair contradictions, so they were unaffected.)
73+
Also added `explain_error` coverage for **context-window overflow** and **token-budget** failures.
6774

6875
### Added (earlier this cycle)
6976
- **The compositor — personas, emotions, and layer composition (plan §23, 2026-06-17).** Built the

resume.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,20 @@
5252
Emotions **8→12** (`emotions.py`): `disgust`, `surprise`, `jealousy`, `pride` - completes Ekman's six
5353
+ the two top dramatic drivers; each adds an anti-cliché deny-list + show-don't-name cue + aliases.
5454
Counts refreshed in README, plan.md §23, and learning.md.
55-
- **NEXT STEP:** the bigger UX gap from the audit is **failure feedback in full-auto (autonomous)
56-
runs** - context-overflow/budget errors aren't mapped in `ui.explain_error()`, and a non-escalation
57-
pause may show no recovery card. Verify the control flow in `dashboard.run_with_dashboard` and
58-
`explain_error`, then make those failures actionable. (Audit also flagged discoverability wins:
59-
`/agentic` policy explainer, `/praise`/`/path` help, missing-dep hints.)
55+
- **Failure feedback in full-auto - VERIFIED + closed.** Traced the real control flow (not the audit's
56+
guess): in **autonomous mode the path was already covered** - contradictions auto-repair
57+
(`escalate_on_contradiction=False`), budget is caught -> `_paused_card`, Ctrl-C prints a saved line,
58+
raised errors now hit the actionable `explain_error` (context-overflow + budget cases added). The one
59+
real gap was a **book + manual-mode consolidation stall** (`review_kind="consolidation"`): it fell
60+
through the post-run router (which only handled `chapter`/`section`) and returned **silently**. Fixed
61+
with `_consolidation_card` (`run --force` resume) + a catch-all so no not-done state is ever silent.
62+
Tests: consolidation card + `explain_error` overflow/budget coverage.
63+
- **NEXT STEP (toward "fully agentic"):** the remaining caveats are (1) the **`trace` (learned) policy
64+
is under-trained** - turn accumulated `agent_trace.jsonl` into the first real training set; (2) the
65+
**agentic branch is lightly validated** - add agentic-branch test coverage to match the fixed
66+
pipeline + log a few real agentic runs; (3) minor discoverability wins (`/agentic` policy explainer,
67+
`/praise`/`/path` help, missing-dep hints). See the "fully agentic?" assessment - these three are
68+
what keep it from a no-caveats claim.
6069

6170
- **New (2026-06-17 - the compositor: personas, emotions, layer composition - DONE, branch
6271
`feat/compositor-personas-emotions`, stacked on the craft-engine branch):** built the §22.6 deferral

src/writingagent/shell/dashboard.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,26 @@ def _paused_card(console, book_id: str) -> None:
462462
console.print(Panel(body, title=title, title_align="left", border_style=border, padding=(1, 2)))
463463

464464

465+
def _consolidation_card(console) -> None:
466+
"""The continuity check (consolidation) found possible contradictions and paused for a
467+
look - a book, manual-mode review. Without this the run returned SILENTLY (the only hint
468+
was a `[!]` line buried in the event log). Say what happened, where to see it, and the one
469+
command to proceed."""
470+
from rich.panel import Panel
471+
from rich.text import Text
472+
console.print()
473+
body = Text()
474+
body.append("The continuity check flagged possible contradictions across chapters.\n",
475+
style=f"bold {GOLD}")
476+
body.append("Everything committed so far is saved.\n\n", style=DIM)
477+
body.append("see them: status", style=f"bold {GOLD}")
478+
body.append(" then either —\n", style=DIM)
479+
body.append("accept & finish: run --force", style=f"bold {GOLD}")
480+
body.append(" or fix one: revise --chapter N --instruction \"\"", style=DIM)
481+
console.print(Panel(body, title=f"[{GOLD}]⏸ consolidation review[/]", title_align="left",
482+
border_style=GOLD, padding=(1, 2)))
483+
484+
465485
def _escalation_picker(console, cfg, uid: str, book_id: str, state: dict) -> str:
466486
"""Interactive resolution of a stalled chapter/section - one keypress instead of
467487
a two-flag command. Returns 'rerun' (resume the pipeline) or 'stop'."""
@@ -632,14 +652,19 @@ def _ask(prompt: str) -> str:
632652
_ring()
633653
_summary_card(console, dash, state, uid, book_id)
634654
return
635-
if (interactive and state.get("pending_review")
636-
and state.get("review_kind") in ("chapter", "section")):
655+
pending = state.get("pending_review")
656+
kind = state.get("review_kind")
657+
if interactive and pending and kind in ("chapter", "section"):
637658
_ring()
638659
if _escalation_picker(console, cfg, uid, book_id, state) == "rerun":
639660
continue
640-
elif console and not state.get("pending_review"):
641-
# Not done, not a unit escalation → paused (budget cap / interrupt). Make it
642-
# a clear recovery moment rather than a silent return to the prompt.
661+
elif console and pending and kind == "consolidation":
662+
# Continuity-check stall (book, manual mode): used to return silently.
663+
_ring()
664+
_consolidation_card(console)
665+
elif console:
666+
# Anything else not-done (budget cap / interrupt / a pending review we don't
667+
# drive interactively here): never return silently — leave a recovery card.
643668
_paused_card(console, book_id)
644669
return
645670

tests/test_themes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@ def test_explain_error_maps_known_failures():
106106
assert "key" in ui.explain_error(RuntimeError("401 Unauthorized")).lower()
107107
assert "rate" in ui.explain_error(RuntimeError("429 Too Many Requests")).lower()
108108
assert ui.explain_error(RuntimeError("Connection timed out")) is not None
109+
# context-window overflow + token-budget map to an actionable next step (not a traceback)
110+
assert "context" in ui.explain_error(
111+
RuntimeError("This model's maximum context length is 8192 tokens")).lower()
112+
assert "max_run_tokens" in ui.explain_error(
113+
RuntimeError("run token budget reached (500000 >= 500000 tokens)"))
109114
assert ui.explain_error(ValueError("totally unknown thing")) is None

tests/test_ui.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@ def test_paused_card_renders(tmp_brain):
256256
assert "paused" in console.file.getvalue().lower()
257257

258258

259+
def test_consolidation_card_renders_recovery(tmp_brain):
260+
"""A continuity-check stall must show a recovery card (it used to return silently):
261+
say what happened and give the one resume command."""
262+
from writingagent.shell.dashboard import _consolidation_card
263+
console = _record_console()
264+
_consolidation_card(console)
265+
out = console.file.getvalue()
266+
assert "contradiction" in out.lower()
267+
assert "run --force" in out # the documented resume command
268+
269+
259270
def test_narrow_banner_drops_figlet():
260271
import io
261272

0 commit comments

Comments
 (0)