Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
06c5a6c
merge(#2908 slice-4): merge slice-4 base (slice-1+2+3 work) into code…
Jun 2, 2026
bf8ddb3
feat(#2908 slice-4 task-4-1): flip EGG_BRC_EVENT_PUMP and EGG_BRC_MEM…
Jun 2, 2026
d78ebe0
merge(#2908 slice-4): merge slice-4 base (slice-1+2+3 work) into docu…
Jun 2, 2026
169417c
docs(#2908 slice-4 task-4-4): post-deletion consensus wrapper docs
Jun 2, 2026
15664e8
feat(#2908 slice-4 task-4-2): delete legacy capped-restart template a…
Jun 2, 2026
09c7ebd
docs(#2908 slice-4 task-4-4 v2): address reviewer_code v1 NACK
Jun 2, 2026
f3fb61e
merge(#2908 slice-4): merge documenter task-4-4 docs commit into code…
Jun 2, 2026
38b08e4
Merge remote-tracking branch 'origin/egg/issue-2908-impl2/slice-4' in…
Jun 2, 2026
eaca39c
docs(#2908 slice-4 task-4-4 v3): address reviewer_code v2 NACK
Jun 2, 2026
3bcf2af
Merge remote-tracking branch 'origin/egg/issue-2908-impl2/slice-4' in…
Jun 2, 2026
d6fc959
docs(#2908 slice-4 task-4-4 v3 follow-up): EGG_BRC_EVENT_PUMP removed…
Jun 2, 2026
04048c3
fix(#2908 slice-4 v2): address reviewer_code_holistic NACK on v1
Jun 2, 2026
66028aa
Merge remote-tracking branch 'origin/egg/issue-2908-impl2/slice-4' in…
Jun 2, 2026
e093f67
fix(#2908 slice-4 v3): address reviewer_code v1 NACK on coder v2
Jun 2, 2026
b63a42b
fix(#2908 slice-4 v3 follow-up): address reviewer_code_holistic v2 bl…
Jun 2, 2026
fd1a8b6
fix(#2908 slice-4 v4): restore _auto_populate_contract + ruff I001 fi…
Jun 3, 2026
4fafcb8
fix(#2908 slice-4 v7): address reviewer_code NACK — 4 ruff failures
Jun 3, 2026
518558c
Persist BRC history for slice-4 (#2548)
Jun 3, 2026
4140fb3
Merge origin/egg/issue-2908-impl2/slice-3 into egg/issue-2908-impl2/s…
jwbron Jun 3, 2026
85660f4
fix(#2908 slice-4): migrate test assertions off deleted capped-restar…
james-in-a-box[bot] Jun 3, 2026
9259b97
Merge origin/egg/issue-2908-impl2/slice-3 into slice-4: resolve dual-…
jwbron Jun 3, 2026
db48fb5
fix(#2908 slice-4): restore _slice_agents_alive, parent-branch probe,…
james-in-a-box[bot] Jun 3, 2026
b2380bb
Merge remote-tracking branch 'origin/egg/issue-2908-impl2/slice-4' in…
james-in-a-box[bot] Jun 3, 2026
2d8fa0f
Merge origin/egg/issue-2908-impl2/slice-3 into slice-4
jwbron Jun 3, 2026
eb8c644
Restore collateral-damage reverts in pipelines.py from slice-3 + audi…
egg-reviewer[bot] Jun 3, 2026
70de132
Fix checks: align rebase_onto agent_role test with restored orchestra…
james-in-a-box[bot] Jun 3, 2026
3a300e7
Update dual-role banner comment to describe event-pump mechanics
egg-reviewer[bot] Jun 3, 2026
136b9ad
Merge remote-tracking branch 'origin/main' into egg/issue-2908-impl2/…
jwbron Jun 3, 2026
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
16,194 changes: 16,194 additions & 0 deletions .egg-state/brc-history/issue-2908-impl2-implement-slice-4.json

Large diffs are not rendered by default.

18,447 changes: 18,447 additions & 0 deletions .egg-state/brc-history/issue-2908-impl2-implement-slice-4.md

Large diffs are not rendered by default.

1,040 changes: 0 additions & 1,040 deletions .egg-state/contracts/issue-2908-impl2.json

This file was deleted.

1,752 changes: 0 additions & 1,752 deletions .egg-state/drafts/issue-2908-impl2-plan.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ The SDLC pipeline orchestrates agent-based development with structurally enforce
- [Logging](logging.md) - Structured JSON logging
- [Integration-Test Trust Boundary](integration-test-trust-boundary.md) - Test execution contexts (in-sandbox-agent / trusted-CI-runner / human-operator), fixture tier table, and hard-NACK rules for plan-phase Primitive-Existence (§9) and Trust-Boundary (§10) audits
- [BRC Memory Artifact](brc-memory.md) - Per-role-per-pipeline distilled memory file (`.egg-state/agent-outputs/<role>/brc-memory.md`) written by `brc_ack`/`brc_nack`; schema, three `EGG_BRC_MEMORY` modes, atomic-write contract, fail-closed path constructor, distill-on-write cap at 20, and the role-allowlist coverage that makes the path writable for every participant role ([#2908](https://github.com/jwbron/egg/issues/2908))
- [BRC Event-Pump Wrapper](orchestrator.md#brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump) - Deterministic wrapper loop, wrapper-side heartbeat + gateway-session keep-alive, and the idle/no-progress safety budget that replaces the 3-restart FAIL cap; gated by `EGG_BRC_EVENT_PUMP` (default off until slice-4 flips it). Wait-side companion in [agent-wait-patterns §10](../reference/agent-wait-patterns.md#10-brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump) ([#2908](https://github.com/jwbron/egg/issues/2908) slice-2)
- [BRC Consensus Wrapper](orchestrator.md#brc-consensus-wrapper) - Deterministic event-pump wrapper loop, wrapper-side heartbeat + gateway-session keep-alive subshells, the idle/no-progress safety budget that replaced the 3-restart FAIL cap, and the `git revert` rollback plan after slice-4 deleted the legacy capped-restart template (`_CONSENSUS_WRAPPER_TEMPLATE` / `_RECOVERY_SYSTEM_PROMPT` / SSE `consensus.reached` / `MAX_CONSENSUS_RESTARTS`) and the agent-side heartbeat path. Wait-side companion in [agent-wait-patterns §10](../reference/agent-wait-patterns.md#10-brc-consensus-wrapper-event-pump-model) ([#2908](https://github.com/jwbron/egg/issues/2908))

## Why Wrappers, Not MCP

Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/brc-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ preserving the inert default.
For the full architecture of the slice-3 reader (composer shape, 10 KB
envelope, tail-position memory delivery per architect od-6 Option B,
preamble collapse, slice-2 wrapper interplay), see
[Orchestrator — BRC Per-Event Prompt Composer + Preamble Collapse](orchestrator.md#brc-per-event-prompt-composer--preamble-collapse-slice-3)
[Orchestrator — BRC Per-Event Prompt Composer + Preamble Collapse](orchestrator.md#brc-per-event-prompt-composer--preamble-collapse)
and its wait-side companion
[agent-wait-patterns §10.9](../reference/agent-wait-patterns.md#109-brc-per-event-prompt-composer--preamble-collapse-slice-3).
[agent-wait-patterns §10.9](../reference/agent-wait-patterns.md#109-brc-per-event-prompt-composer--preamble-collapse).

## Acceptance contract for the writer (slice-1)

Expand Down
416 changes: 233 additions & 183 deletions docs/architecture/orchestrator.md

Large diffs are not rendered by default.

36 changes: 16 additions & 20 deletions docs/guides/concurrent-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,33 @@ Each agent is registered in the peer consensus tracker before spawning begins.

## Consensus Wrapper

> **Two emission paths from slice-2 of [#2908](https://github.com/jwbron/egg/issues/2908):** the section below describes the **legacy template** path emitted when `EGG_BRC_EVENT_PUMP` is unset or `false` — the production default until slice-4 flips it. The opt-in event-pump path replaces the model-driven restart loop with a deterministic wrapper-driven loop, moves the heartbeat and gateway-session keep-alive from `message_wait_loop` to wrapper-side subshells, and swaps the 3-restart FAIL cap for an idle/no-progress overseer-alert budget. See [Orchestrator Architecture — BRC Event-Pump Wrapper](../architecture/orchestrator.md#brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump) and the wait-side companion in [agent-wait-patterns §10](../reference/agent-wait-patterns.md#10-brc-event-pump-wrapper-slice-2-behind-egg_brc_event_pump) for the new path.
> **[#2908](https://github.com/jwbron/egg/issues/2908) replaced the model-driven restart loop with a deterministic event-pump wrapper across slices 1–4.** The wrapper is now the sole loop driver — the agent is one-shot per actionable BRC event, the wrapper holds the wait between events, and heartbeats + gateway-session keep-alive ride on wrapper-side subshells rather than the in-pod `message_wait_loop`. The 3-restart `MAX_CONSENSUS_RESTARTS` FAIL cap was replaced by an idle/no-progress overseer-alert budget that never transitions the pipeline to FAILED. The legacy `_CONSENSUS_WRAPPER_TEMPLATE`, the `_RECOVERY_SYSTEM_PROMPT`, the SSE `consensus.reached` machinery, the `MAX_CONSENSUS_RESTARTS` constant, and the `EGG_BRC_EVENT_PUMP=false` escape hatch were deleted in slice-4 — the supported regression path is `git revert` of the slice-4 / slice-3 / slice-2 / slice-1 merge commits in reverse-merge order (see [Orchestrator Architecture — Rollback plan](../architecture/orchestrator.md#rollback-plan)). See [Orchestrator Architecture — BRC Consensus Wrapper](../architecture/orchestrator.md#brc-consensus-wrapper) for the deterministic-loop semantics, the wait-filter construction, the wrapper-side heartbeat + keep-alive subshells, the idle-budget escalation table, and the rollback plan. The wait-side companion is [agent-wait-patterns §10](../reference/agent-wait-patterns.md#10-brc-consensus-wrapper-event-pump-model).

All concurrent agent containers are wrapped with a shell script defined in `orchestrator/consensus_wrapper.py`. The wrapper detects when Claude exits without the orchestrator confirming consensus and restarts the agent with recovery instructions instead of silently marking it as ready.
All concurrent agent containers are wrapped with a shell script defined in `orchestrator/consensus_wrapper.py`. The wrapper is the deterministic BRC loop driver: it polls `egg-orch brc get-state` / `egg-orch brc next-action` for sequencing, blocks in `egg-orch message wait-loop` between actionable events, and invokes the agent one-shot per `INVOKE` event with a prompt composed by `compose_event_prompt` (slice-3).

**How it works:**

1. Claude runs inside the wrapper script with the original task prompt.
2. If Claude exits non-zero, the wrapper first checks whether consensus is already complete or this agent is already confirmed (see step 6 for details on the confirmed check). If so, it exits cleanly — the non-zero exit is harmless. Otherwise, the wrapper classifies the exit code:
- **Transient crash** (exit codes 134/SIGABRT, 136/SIGFPE, 137/SIGKILL/OOM, 139/SIGSEGV, 255/Bun segfault): The wrapper logs `"Transient crash (code $AGENT_EXIT). Will restart with backoff."` and falls through to the restart loop (step 4) with exponential backoff. The initial backoff is 5 seconds, doubling after each crash restart up to a 30-second cap.
- **Non-transient failure** (all other non-zero codes, e.g., exit 1): The wrapper logs `"Agent failed (code $AGENT_EXIT). NOT restarting."` and exits immediately with the same code, triggering the orchestrator's agent failure path.
3. If Claude exits cleanly (code 0), the wrapper checks whether this agent is already confirmed before restarting. It queries the pipeline status endpoint and checks the tracker's `confirmed` field for this agent. The wrapper falls back to checking the message bus directly for a prior `CONSENSUS_CONFIRMED` message from this agent's role in two scenarios: (a) the consensus tracker state is empty (e.g., because the orchestrator restarted and the in-memory tracker was not yet reconstructed), or (b) the tracker is populated but shows this agent as **not** confirmed — which can happen when a withdrawal/re-proposal cascade leaves the tracker with stale state that doesn't reflect the agent's actual `CONFIRMED` status. If a matching `CONSENSUS_CONFIRMED` message is found in the message bus, the agent is treated as already confirmed and enters the wait-for-consensus poll loop — no restart needed.
4. If not already confirmed, the wrapper restarts Claude with recovery instructions injected as the **system prompt** (not the user prompt). Using the system prompt prevents the Agent SDK from flagging the recovery context as prompt injection. The recovery system prompt explains that the agent was restarted, includes the current BRC state, and (for producers with unresolved NACKs) includes the NACK feedback so the agent knows exactly what to address before re-proposing. A short user prompt ("Continue the BRC consensus protocol…") accompanies it.
5. Restarts are capped at `MAX_CONSENSUS_RESTARTS` (default: 3). After each restart, the wrapper checks if global consensus was reached (exit cleanly) or if this agent individually reached `CONFIRMED` state (enter the wait-for-consensus poll loop). This prevents a confirmed agent from consuming a restart slot while waiting for peers to finish. Each restart also publishes a medium-priority `OVERSEER_ALERT` (anomaly `agent-restart`) so the operator sees recovery attempts in real time (issue #2806).
6. After exhausting all restarts, the wrapper performs a **final consensus check** before giving up. It polls the pipeline status endpoint for `is_complete`; if consensus has been reached (all agents confirmed), it logs "Consensus reached on final check" and exits with code 0 — avoiding a false failure. Only if consensus is genuinely incomplete does it exit with code 1. For **producer** roles, the orchestrator detects the non-clean exit, re-queries consensus once more (race-window guard for a producer that crashed in wrapper cleanup *after* reaching CONFIRMED), and only if consensus is still incomplete does it hard-fail the pipeline (Option A, issue #2806) with a high-priority `OVERSEER_ALERT` (anomaly `producer-permanent-death`); reviewer-only deaths still flow through the existing single-failure HITL path because peer-review redistribution can recover them.
1. The wrapper starts two background subshells: a heartbeat emitter (every 30 s while a `wait-loop` is blocking) and the implicit gateway-session keep-alive that rides on every accepted heartbeat. Both surfaces moved here from the pre-#2908 agent-side `message_wait_loop` (see [Wrapper-side heartbeat (#2036)](../architecture/orchestrator.md#wrapper-side-heartbeat-2036-migration-completed-in-slice-4) and [Wrapper-side gateway-session keep-alive (#2451)](../architecture/orchestrator.md#wrapper-side-gateway-session-keep-alive-2451-migration-completed-in-slice-4)).
2. The wrapper polls `egg-orch brc get-state --json`. If `role_complete` is true, it calls `egg-orch consensus confirmed` and exits with code 0.
3. Otherwise it polls `egg-orch brc next-action --json`. The action is one of `WAIT` (block in `egg-orch message wait-loop` with a conditional filter — see [Wait-filter construction](../architecture/orchestrator.md#wait-filter-construction-pre-confirm-vs-post-confirm)) or `INVOKE` (spawn the agent with a one-shot event prompt).
4. When the agent exits cleanly after an `INVOKE`, the wrapper loops back to step 2 — no restart, no recovery prompt. Clean exit between events is the expected steady state.
5. If the agent exits with a transient-crash signal (134/SIGABRT, 136/SIGFPE, 137/SIGKILL/OOM, 139/SIGSEGV, 255/Bun segfault), the wrapper restarts the pod with exponential backoff (initial 5 s, doubling, capped at 30 s). These signals are infrastructure-level pod failures that the wrapper still distinguishes from clean event-pump exits via the surviving `is_transient_crash` / `is_buffer_overflow` / `is_startup_failure` classifiers.
6. If the agent exits with a non-transient non-zero code, the wrapper exits with the same code, triggering the orchestrator's agent failure path. The pre-#2908 `MAX_CONSENSUS_RESTARTS = 3` cap and the `_RECOVERY_SYSTEM_PROMPT` recovery-restart cycle no longer exist; the surviving liveness guarantee is the **idle / no-progress safety budget** (`EGG_BRC_IDLE_BUDGET_MIN`, default 30 minutes) which emits an `OVERSEER_ALERT` at threshold and `2 ×` threshold but never transitions the pipeline to FAILED.

**Transient crash classification:** The `is_transient_crash()` shell function in the wrapper identifies exit codes caused by signal-based runtime crashes (segfaults, OOM kills, SIGABRT) and Bun's segfault exit code (255). These indicate infrastructure failures, not application-level errors, and are safe to retry. The worst case for treating exit code 255 as transient is one extra restart attempt if 255 was actually a permanent error. Transient crash restarts share the `MAX_CONSENSUS_RESTARTS` cap with clean-exit restarts.
**Key design principle:** Agents must **explicitly** participate in consensus. The wrapper never auto-signals `READY` on behalf of an agent — it dispatches the agent to handle each actionable event and lets the agent issue ACK / NACK / PROPOSE / CONFIRM verdicts itself. Sequencing is the wrapper's job; *judgment* (what to review, what to fix, when to re-propose) stays with the model.

**Key design principle:** Agents must **explicitly** participate in consensus. The wrapper never auto-signals `READY` on behalf of an agent — it restarts the agent so it can assess state and signal for itself.

**Design intent — safety net, not primary mechanism:** The wrapper exists as a fallback for the edge case where an agent exits prematurely (e.g., context exhaustion). The intended lifecycle is for agents to run with enough turns to finish their work *and* complete the full BRC consensus protocol (including stay-alive polling while peers finish). The orchestrator detects consensus and sends SIGTERM to terminate containers — agents should exit because they are told to, not because they exhaust turns. The restart path is expensive (requires reloading context and re-evaluating BRC state) and should be rare.
**Design intent — the wrapper is the loop, not a safety net.** Before #2908 the wrapper existed as a fallback that re-spawned the agent on premature exit (e.g., context exhaustion) and the agent was expected to hold a long-running BRC wait between events. After #2908 the wrapper *is* the BRC loop driver — clean per-event exits are the steady-state contract, not an edge case. The orchestrator detects consensus and sends SIGTERM to terminate containers when the role is complete; the agent only exits because it finished its event handler or because consensus closed. There is no expensive context-reload-on-restart path.

**Configuration:**

| Parameter | Default | Description |
|-----------|---------|-------------|
| `max_turns` | `1000` | Maximum tool-call turns per agent run (set high so agents can complete work and stay alive for the full BRC lifecycle) |
| `max_restarts` | `3` | Maximum restart attempts (passed to `build_consensus_wrapped_command()`). Shared between clean-exit and transient-crash restarts. Bumped from 2 → 3 in issue #2806. |
| `max_ready_polls` | `10` | Maximum poll cycles (each ~30 s) to wait for global consensus when this agent has already reached `CONFIRMED` |
| `TRANSIENT_RESTART_BACKOFF_INITIAL` | `5` | Initial backoff delay (seconds) before restarting after a transient crash. Doubles after each crash restart, capped at 30 s. Clean-exit restarts skip the backoff. |
| `max_turns` | `1000` | Maximum tool-call turns per agent run (set high so per-event agent invocations have headroom; the wrapper-side loop never reaches this cap because per-event exits are short-lived). |
| `max_ready_polls` | `10` | Maximum poll cycles (each ~30 s) for the legacy "already-confirmed" guard preserved for race-window safety when the wrapper observes `role_complete` between polls. |
| `TRANSIENT_RESTART_BACKOFF_INITIAL` | `5` | Initial backoff delay (seconds) before restarting after a transient infrastructure crash (134/136/137/139/255). Doubles after each crash restart, capped at 30 s. Clean per-event exits do not trigger backoff. |
| `STARTUP_FAILURE_WINDOW_SECONDS` | `30` | Window (seconds) during which exit code 1 is classified as a transient startup failure and retried. Set to `0` to disable. |
| `EGG_MESSAGE_POLL_INTERVAL` | `30` | Seconds between message polls during restarts |
| `EGG_BRC_IDLE_BUDGET_MIN` | `30` | Idle / no-progress safety budget in minutes. Replaces the pre-#2908 3-restart FAIL cap; at threshold and `2 ×` threshold the wrapper emits `mcp__progress__overseer_alert` (anomaly `stuck-phase-transition`) without transitioning the pipeline to FAILED. |
| `EGG_MESSAGE_POLL_INTERVAL` | `30` | Seconds between heartbeat emissions and message polls. |

## Message Bus

Expand Down Expand Up @@ -842,7 +838,7 @@ The additional API call in step 5 is negligible — it only runs on the terminal

### Transient Crash Recovery

Before an agent failure reaches the orchestrator's `handle_agent_crash()` path, the consensus wrapper attempts to recover from transient runtime crashes (segfaults, OOM kills, SIGABRT). Exit codes 134, 136, 137, 139, and 255 are classified as transient and trigger a restart with exponential backoff (starting at 5 s, doubling up to 30 s). If the transient crash restart succeeds and the agent reaches `CONFIRMED`, the failure is fully recovered at the wrapper level — the orchestrator never sees a failure event. Only when the agent crashes again after exhausting `MAX_CONSENSUS_RESTARTS` does the failure propagate to the orchestrator. See [Agent Recovery: Consensus Wrapper](../reference/agent-recovery.md#consensus-wrapper-transient-crash-recovery) for the full exit code classification.
Before an agent failure reaches the orchestrator's `handle_agent_crash()` path, the consensus wrapper attempts to recover from transient runtime crashes (segfaults, OOM kills, SIGABRT). Exit codes 134, 136, 137, 139, and 255 are classified as transient and trigger a restart with exponential backoff (starting at 5 s, doubling up to 30 s). If the transient-crash restart succeeds and the agent re-enters the wrapper's event-pump loop (resuming `egg-orch brc get-state` → `next-action`), the failure is fully recovered at the wrapper level — the orchestrator never sees a failure event. Repeated transient crashes inside the same wrapper run are bounded by the **idle / no-progress safety budget** (`EGG_BRC_IDLE_BUDGET_MIN`, default 30 minutes): the wrapper emits an `OVERSEER_ALERT` at threshold and at `2 ×` threshold but does **not** mark the pipeline FAILED. The pre-#2908 `MAX_CONSENSUS_RESTARTS = 3` hard cap was deleted in slice-4 alongside the legacy template. See [Agent Recovery: Consensus Wrapper](../reference/agent-recovery.md#consensus-wrapper-transient-crash-recovery) for the full exit code classification.

### Agent Failure During Consensus

Expand Down
Loading
Loading