Status: design decision awaiting grading. No production implementation in this change.
Artifact: .brigade/measurements/issue-635-journal-ceiling.json (local, same shape family as issue-568-slice6.json: environment metadata plus structured aggregates; volume mode adds bounds and scenario blocks).
Current hard bounds: MAX_JOURNAL_EVENTS = 512, MAX_JOURNAL_BYTES = 8 MiB.
| Scenario | Events | Bytes | % event ceiling | % byte ceiling | Hit ceiling? |
|---|---|---|---|---|---|
| Scanned real runs (n=13, single-seat journal-hardening runs) | p50/max 17 | p50 14,279 / max 14,286 | 3.3% | 0.17% | no |
| Synthetic representative (1 seat, 1 attempt, 0 pauses) | 11 | 7,780 | 2.1% | 0.09% | no |
| Synthetic roster-sized (11 seats, 2 attempts, 3 pause/resume cycles) | 155 | 124,777 | 30.3% | 1.5% | no |
| Synthetic worst case (32 seats, 3 attempts, 8 pause cycles) | 512 | 420,683 | 100% | 5.0% | yes, mid-dispatch |
Worst-case stop reason: LifecycleJournalError: bound exceeded: journal event sequence above MAX_JOURNAL_EVENTS at seat-28 attempt 1. Bytes were still ~5% of the 8 MiB cap.
Raise the event bound. Do not segment the journal yet.
Reasoning:
- Event count is the binding constraint. At a full 512-event journal the file is only ~0.4 MiB.
- Realistic and roster-sized load stays far from 512 (3% real, 30% for 11 seats with retries and three approval cycles).
- The ceiling is reachable, but only under a synthetic fleet larger than today's 11-seat roster with three attempts per seat.
- Segmentation (cross-linked
lifecycle.NNNNN.jsonl, reader/projector/doctor/recovery awareness) is a large surface change. The measured gap does not justify that cost before a cheaper bound raise plus a soft warning. - Keep segmentation as the explicit escape hatch if #593 budget events or larger fleets push roster-sized runs past ~50% of the raised ceiling.
Recommended production change (separate implementation issue, after this design is graded):
- Raise
MAX_JOURNAL_EVENTSfrom 512 to 2048 (4x). - Leave
MAX_JOURNAL_BYTESat 8 MiB for now (still ~20x headroom at 2048 median-sized events). - Add a doctor soft threshold at 75% of the event bound (1536 of 2048):
WARN, notFAIL. - Keep the hard refuse-to-append behavior only at the new ceiling, and make that failure visible (already true today via
LifecycleJournalError).
When brigade doctor inspects a run journal and event_count >= ceil(0.75 * MAX_JOURNAL_EVENTS) while still under the hard ceiling:
- status:
WARN - check name:
runs: journal event headroom - detail shape:
lifecycle journal at {event_count}/{MAX_JOURNAL_EVENTS} events ({pct}%); raise or segment before the hard ceiling halts appends
Below 75%: silent on this check. At or above the hard ceiling: keep today's FAIL / bound exceeded path for recovery verdicts that already treat oversize journals as failed.
A run that stopped at sequence 512 under the old bound has a complete, chain-valid journal and paired checkpoints. It does not need rewrite or segmentation to become readable again.
After the bound raise ships:
- Readers (
read_journal/read_journal_bounded) accept the existing 512-event file because it is under the new ceiling. - Doctor recovery checks that previously returned
FAIL/bound exceededsolely for the old event ceiling clear once the constant moves. - If the run is still live or being resumed, the active lock owner may append sequence 513+ again; checkpoint pairing and projection continue from the existing chain tip.
- No migration tool is required for journals that stopped exactly at 512. Journals that somehow exceed the new byte bound remain fail-closed (unchanged).
- No journal segmentation.
- No change to worker/provider event streams (#592 stays separate).
- No silent truncation or dropping of lifecycle facts when the hard ceiling is hit.
python scripts/measure_run_journal.py \
--mode volume \
--scan /path/to/.brigade/runs \
--output .brigade/measurements/issue-635-journal-ceiling.json