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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ breaking changes may land in a minor release.

### Added

- **Transport failures pause instead of burning attempts (#194).** A session whose coding CLI
lost its API connection stays alive but idle, printing `API Error: Unable to connect …` while it
idles out the session clock — indistinguishable from a real wall-clock timeout, so two such
outages exhausted `max_dev_attempts` and deferred the story with zero real work attempted.
bmad-loop now classifies these post-mortem and pauses for a human (re-arm restores the budget),
exactly as verify environment faults (`rc 126/127`) already do:
- Adapters classify a non-completed (`timeout`/`stalled`/`crashed`) session as an _environment
fault_ by matching per-profile `env_fault_patterns` regexes against the ANSI-stripped tail of
the pane log. `SessionResult` gains `env_fault` / `env_fault_evidence`; the generic tmux
adapter reads the log tail once after the verdict and reconcile settle (last match wins,
`over_budget`/`completed` excluded), drops an `env-fault-classified` breadcrumb, and the
`claude` profile ships a seed pattern requiring an `API Error` line _and_ a connection-level
cause on the same line (so prose "API Error" test output does not trip it). Patterns must be a
list of strings and are matched with the `regex` module under a per-pattern timeout (a
pathological pattern can't hang a teardown), and the pane log is reset when a re-armed run reuses
a task id so a prior cycle's line can't misclassify a later session.
- The dev, review, and fix phases PAUSE on a classified session (evidence in the reason, worktree
preserved) instead of charging the attempt/cycle; the `dev-decision` / `fix-decision` /
`session-end` journal entries carry the flag and evidence.
- Blocking plugin workflows escalate rather than defer the story on a classified session
(non-blocking workflows keep continuing, journaled only); the sweep migration and triage loops
escalate before charging a retry, and the sweep's existing escalation-resume restores their
budget.

- **Documented the `BMAD_LOOP_*` environment variables (#246).** The three runtime override
vars — `BMAD_LOOP_MUX_BACKEND`, `BMAD_LOOP_PROCESS_HOST`, `BMAD_LOOP_SESSION_TIMEOUT_S` — now
have a reference table in the README. Behavior is unchanged; they are read through a single
Expand Down
3 changes: 2 additions & 1 deletion docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se
- Bounded dev retries (default 2): verify-failures keep the tree and feed the failing output to the next session via `--feedback`; other failures roll back to baseline.
- Plateau-defer: when review won't converge, the story is skipped, the spec stashed into the run dir, deferred-work preserved, the run continues.
- Typed escalations: `CRITICAL` pauses the run + notifies (desktop + `ATTENTION` file); `PREFERENCE` is journaled and continues.
- Environment faults pause without burning budget (#194): a session whose coding CLI never reached the API — a deterministic verify command dying `rc 126/127`, **or** a dev/review/fix/workflow/sweep session whose pane log matches the profile's `env_fault_patterns` (an `API Error … Connection refused`-class transport failure that idled out the session clock) — pauses the run with the matched evidence instead of charging the attempt/cycle and deferring the story as if its code were broken. Re-arm (or a sweep's escalation-resume) restores the budget, so a resume after the outage clears re-drives from a clean slate. Patterns are per-profile (seeded only for `claude`; extend/disable via a project profile overlay).
- CRITICAL resolution: `bmad-loop resolve <run-id>` opens an interactive resolve agent seeded with the escalation + frozen spec; you disambiguate, it re-arms the story (`escalated → pending`, spec reset to `ready-for-dev`) and resumes. `--no-interactive` skips to re-arm if you fixed the spec yourself.
- Intent-gap patch-restore (BMAD-METHOD#2564): when review halts on an `intent gap`, `bmad-dev-auto` saves the attempted change as a patch file (in the implementation-artifacts folder, referenced from the halt output) before reverting the tree. If the attempted reading turns out to be correct, the resolve agent adds `"restore_patch": "<path>"` to its `resolution.json`; the orchestrator then re-arms the spec to `in-review` (not `ready-for-dev`) and re-applies the patch onto the baseline after every reset, so the re-driven session resumes _review_ on the restored diff instead of re-implementing from scratch. A hand-driven `bmad-loop resolve --no-interactive --restore-patch <path>` does the same. A patch that fails to apply escalates rather than dispatching a session onto a half-restored tree. Deferred-work sweep bundles (below) get the same recovery — an escalated bundle re-arms to `in-review` and the re-driven bundle session resumes review on the re-applied patch.

Expand Down Expand Up @@ -92,7 +93,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se

- Every run is a resumable on-disk state machine: `bmad-loop resume <run-id>` continues from a gate, escalation, or interruption.
- A graceful stop (`stop --graceful` / TUI `S`) is resumable too: unlike a hard stop killed mid-item, it lets the in-flight item finish through commit and finalizes cleanly, ending as a `stopped` run that `resume` picks up at the next item.
- All run state in `.bmad-loop/runs/<run-id>/` (gitignored): `state.json`, `journal.jsonl` (every decision), `events/` (hook signals), `tasks/<id>/` (per-session prompt + `result.json` + diagnostic breadcrumbs — `session-lifecycle.jsonl` records timeout-fire and budget-guard trips/terminations (`budget-tripped` / `over-budget-fired`), `heartbeat.json` is the wait loop's proof-of-life, `resultless-stops.jsonl` records give-up Stops), `logs/`, `deferred/`, `resolve/`, `ATTENTION`.
- All run state in `.bmad-loop/runs/<run-id>/` (gitignored): `state.json`, `journal.jsonl` (every decision), `events/` (hook signals), `tasks/<id>/` (per-session prompt + `result.json` + diagnostic breadcrumbs — `session-lifecycle.jsonl` records timeout-fire, budget-guard trips/terminations (`budget-tripped` / `over-budget-fired`), and transport-failure classification (`env-fault-classified`, #194), `heartbeat.json` is the wait loop's proof-of-life, `resultless-stops.jsonl` records give-up Stops), `logs/`, `deferred/`, `resolve/`, `ATTENTION`.
- `journal.jsonl` records `session-end` for every session unconditionally — even a teardown that throws still lands one (status `aborted` when the outcome is unknowable). A timed-out session's entry carries `fired_at` (wall time the deadline was declared), `teardown_s` (wall seconds from that fire to this entry — the teardown gap), and `expired_clock` (`monotonic` / `wall` / `both` — `wall` alone fingerprints a host suspend that froze the monotonic clock). Every entry whose usage was read carries `tokens` (raw) and `tokens_weighted` (cache reads at `limits.cache_read_weight`), keeping per-session spend reconstructible; both are `null` when the usage read failed, and both are absent on an `aborted` end. `tokens_weighted` is the end-of-session total — distinct from a tripped session's `budget_weighted`, the guard's mid-session sample at trip time.

### Hook-based transport (no pane-scraping)
Expand Down
Loading