Skip to content

[issue-2908][slice-3/6] Delta-scoped re-analysis + prompt collapse - #2949

Merged
jwbron merged 21 commits into
mainfrom
egg/issue-2908-impl2/slice-3
Jun 3, 2026
Merged

[issue-2908][slice-3/6] Delta-scoped re-analysis + prompt collapse#2949
jwbron merged 21 commits into
mainfrom
egg/issue-2908-impl2/slice-3

Conversation

@james-in-a-box

@james-in-a-box james-in-a-box Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Context

BRC consensus today depends on the agent re-entering a blocking
egg-orch message wait-loop between every event. That re-entry is a
seam the model can fall out of by emitting a final assistant
message instead of re-entering the wait. Claude usually re-enters;
qwen3.7-max does not (#2906) — it exits success=True after one
match, the wrapper sees no CONSENSUS_CONFIRMED, the 3-restart cap
trips (#2806), and the pipeline FAILs after ~$1 and ~20 min of
churn. Prompt-only mitigations narrow the seam for one model; the
seam itself exists for every model (lineage: #2323, #2064, #2482,
#2036, #1995, #2451).

Changes

Reframe consensus-agent execution from a long-lived participant
that holds blocking waits into a deterministic wrapper-driven
event pump
that invokes the agent one-shot per actionable event,
with continuity carried by a durable per-role memory file. Lands
in six linear slices behind EGG_BRC_EVENT_PUMP (default false
until slice-4):

  1. slice-1 — Foundations. New egg-orch brc next-action,
    brc get-state, brc list-blocking, phase get-context CLI
    subcommands. Durable BRC memory artifact at
    .egg-state/agent-outputs/<role>/brc-memory.md with
    action-scaffolded writes into brc_ack / brc_nack handlers,
    atomic writes via promoted _persist_atomic_template,
    last_reviewed_commit_sha per producer in the schema,
    fail-closed path construction. Gated by
    EGG_BRC_MEMORY=write-only (writes accumulate; reads land
    in slice-3). Purely additive — zero behavior change.
  2. slice-2 — Event-pump wrapper. Rewrite
    orchestrator/consensus_wrapper.py as a deterministic event
    pump gated by EGG_BRC_EVENT_PUMP. Drop the 3-restart cap;
    replace with idle/no-progress safety budget. Migrate the
    heartbeat (Overseer flags BRC reviewers/testers as stalled when they're correctly blocked in mcp__brc__wait_loop (implement-phase false positive) #2036) and gateway-session keep-alive (Orchestrator heartbeat-session lookup fails: container_id missing slice-N segment for non-coder roles #2451) from
    the agent-side handler into the wrapper's blocking wait.
    Heartbeat payload carries slice_id from EGG_SLICE_ID
    (regression guard). Verification is unit-test-only — no
    in-process test double can drive a deployed pod end-to-end
    (the pod-injection avenue was ruled out per Expand integration test coverage #2474); true
    E2E deferred to slice-4 via the egg_stack real-pod
    fixture. Old path retained verbatim alongside.
  3. slice-3 — Delta + prompt collapse. Wire per-event
    invocation to hand the agent the memory delta plus the full
    git log {last_reviewed_commit_sha}..HEAD --not origin/{base_branch} -p
    delta per producer (NOT just orchestrator-side
    changed_artifacts — per REVIEWER-SYNC.md the re-review must
    audit the full delta as a fresh review). Strip the
    STAY-ALIVE / wait-loop / cursor-threading guidance from
    _build_brc_preamble and mission.md; replace with a lean
    event-handler contract. Sandbox image rebuilt + agent pod
    restarted BEFORE slice-4 flag flip.
  4. slice-4 — Flag flip + delete old path. Flip the
    EGG_BRC_EVENT_PUMP / EGG_BRC_MEMORY defaults to on, gated on
    the slice-2 / slice-3 unit + BRC in-process regression suites
    passing on the new default; delete the capped-restart bash, the
    _RECOVERY_SYSTEM_PROMPT, the SSE machinery, and the
    agent-side wait_loop heartbeat code. Rollback plan: revert
    slices 1–3 via git revert if production traffic regresses. The
    qwen3.7-max qwen3.7-max BRC agents end their agentic loop early (success=True) without reaching CONSENSUS_CONFIRMED → consensus-wrapper restart churn #2906 k3s spike that previously validated this
    end-to-end was removed (qwen route unavailable in k3s;
    Claude-route E2E deferred to Rewrite TestCredentialIsolation as k3s-native (currently skipped under k3s) #2585).
  5. slice-5 — Additive CLI prose plumbing + 2 new BRC
    subcommands.
    Add stdin / --reason-file / --summary-file
    / --files-reviewed-file plumbing to
    consensus propose --summary, consensus ack --reason,
    consensus nack --reason, consensus withdraw --reason
    (docs: steer agents to the structured contract tool, not Bash-composed egg-contract #2741 regression guard). Add egg-orch brc resolve-obligation
    and egg-orch brc read-peer-artifact CLI subcommands the
    slice-6 deletion depends on. Argv kept as fallback during
    transition (deprecation warning).
  6. slice-6 — MCP → CLI deletion. Delete the 28 agent-facing
    MCP tools (~1,515 LOC across 7 namespace files + the 4
    infra files + server.py), the SYSTEM_PROMPT_NUDGE, and the
    MCP registration block in shared/egg_agent/client.py:299-353
    INCLUDING the EGG_MCP_TOOLS env flag at :311 (no orphan
    flag). Retire tests/tools/test_mcp_cli_drift.py. Migrate the
    MCP E2E test so the agent's first action is consensus ack/nack via stdin/file (preserves SDK-spawn exercise).
    Verify per-event wall-clock latency within 5%.

Impact

Operator-facing: the BRC consensus subsystem becomes
model-portable — any agent that exits naturally after handling
one event reaches CONFIRMED, instead of needing prompt nudges to
keep re-entering an in-process wait. Cost-per-phase drops because
restart churn disappears, replaced by short bounded per-event
invocations that hit the prefix cache (≥ 60-min TTL on both
routes per WS7 closure). Net code deletion: the capped-restart
template, the SSE machinery, the recovery system prompt, the 28
MCP tool schemas, the EGG_MCP_TOOLS flag, the cursor-threading
guidance, and the agent-side heartbeat all go away. The agent
primitive (pod / worktree / SDK / permissions / restrictions) is
untouched.

This slice

Delta-scoped re-analysis + prompt collapse

Files affected:

  • orchestrator/routes/pipelines.py
  • orchestrator/consensus_wrapper.py
  • sandbox/agent-config/rules/mission.md
  • sandbox/claude-rules/mission.md
  • docs/architecture/orchestrator.md
  • docs/reference/agent-wait-patterns.md
  • orchestrator/tests/test_compose_event_prompt.py
  • orchestrator/tests/test_brc_preamble_collapsed.py

Tasks:

  • task-3-1: Add compose_event_prompt(role, event_payload, memory_excerpt, nacks, git_log_delta, base_branch) -> str helper to orchestrator/routes/pipelines.py (or a new sibling module if the file is at the size limit — coder's call). Returns the single-event prompt the wrapper invokes the agent with. Shape: role banner + one-line event description + memory excerpt (≤ 2 KB) appended at tail position (architect od-6 Option B — do NOT reference the illustrative --append-context flag, which does not exist on build_agent_command); the FULL git log {last_reviewed_commit_sha}..HEAD --not origin/{base_branch} -p delta per producer (NOT just orchestrator-side changed_artifacts — per docs/architecture/REVIEWER-SYNC.md the re-review must audit the full delta as a fresh review or the stateless pump systematically weakens adversarial re-review, risk_analyst R6); NACK payload from peer_consensus.py:949-1024 _open_nacks_barrier_response nacks[] (per-reviewer with reason + artifact_refs); the single action expected. The git-log delta is scaled by actual change size and is NOT counted against the ≤ 10 KB envelope — the envelope bounds the surrounding prose only.
    • Acceptance criteria: Helper unit-tested for each role (producer / reviewer / dual-role); output envelope ≤ 10 KB for representative event payloads (excluding the git-log delta which scales with the change); composer correctly truncates memory excerpts that exceed 2 KB; git-log delta command is emitted verbatim with the per-producer last_reviewed_commit_sha substituted in; NACK payload renders per-reviewer with reason + artifact_refs.
  • task-3-2: Wire the event-pump template branch from TASK-2-1 to call compose_event_prompt (TASK-3-1) at per-event invocation time. Read the memory excerpt from .egg-state/agent-outputs/<role>/brc-memory.md (slice-1 writer) when EGG_BRC_MEMORY=full; with EGG_BRC_MEMORY=write-only (slice-1 default), pass empty memory_excerpt — writes happen but reads are no-ops, preserving slice-1's inert default. Memory is delivered inline at the user-prompt tail (architect od-6 Option B); the illustrative --append-context from the analysis pseudocode is NOT a real flag on build_agent_command (verified at shared/egg_agent/command.py:11-46). Read the per-producer last_reviewed_commit_sha from the memory file's structured section and pass it through to compose_event_prompt so the git-log delta command is parameterised correctly.
    • Acceptance criteria: Wrapper template emits expected compose_event_prompt invocation; with EGG_BRC_MEMORY=full and a populated memory file, the prompt includes both the memory excerpt and the per-producer git-log delta; with EGG_BRC_MEMORY=write-only (slice-1 default), the prompt omits memory but still emits the git-log delta against the orchestrator's signal-level changed_artifacts as a fallback baseline; snapshot test verifies both branches.
  • task-3-3: Collapse _build_brc_preamble at orchestrator/routes/pipelines.py:12348. Delete the STAY-ALIVE / wait-loop mechanics / cursor-threading / pre-confirm-wait foot-gun guidance (Producer Lifecycle step 4 wait-loop plumbing; Producer step 6 STAY-ALIVE loop; cursor / --since guidance). KEEP: agent roster, reviewer/producer assignments, dual-role ordering banner; AND the dual-mandate adversarial re-review banner at orchestrator/routes/pipelines.py:12849-12872 (the "Your re-review has TWO equal-weight mandates…" block — behavioural framing anchored on by risk_analyst R6, NOT seam-related). The three callers at orchestrator/routes/pipelines.py:13659, :13692, :13720 are unchanged — only the preamble text collapses. Slice-3 keeps the flag off by default so the collapsed preamble runs against the legacy wrapper path today; slice-4 makes it the default once the event-pump path is live.
    • Acceptance criteria: Snapshot test for the collapsed preamble lands at orchestrator/tests/test_brc_preamble_collapsed.py; STAY-ALIVE / wait-loop / cursor sections absent; roster + assignments preserved; the phrase Both must pass to ACK (verified at orchestrator/routes/pipelines.py:12856-12857 inside the dual-mandate banner at pipelines.py:12849-12872) appears in the post-collapse preamble snapshot — phrase choice corrects reviewer_plan v2's finding that "Both mandates have equal weight" lives at line 13292 inside _build_adversarial_reprime rather than inside _build_brc_preamble; preamble byte size drops by ≥ 25% (measured against pre-collapse snapshot; the exact number is the snapshot baseline result, not a pre-set target — softened from ≥ 40% per reviewer_plan v2 non-blocker).
  • task-3-4: Rewrite the STAY-ALIVE / wait-loop section of mission.md (lines 151–154 plus surrounding "Concurrent Execution Mode" section starting at line 137) to the event-handler contract: the agent is invoked one-shot per event by the wrapper; act on the single event, update memory, exit naturally. Remove "never exit before the orchestrator stops you" — under the new model the wrapper owns lifecycle. Keep the Anti-Sycophancy / Structured-Progress-Reporting / HITL-vs-OVERSEER_ALERT / Handling-Agent-Failures sections unchanged. The mission.md rule exists at TWO paths on disk that are maintained as byte-identical duplicates with NO automated sync (reviewer_plan blocker; independently verified via sandbox/Dockerfile:212-214 COPY sandbox/claude-rules/*.md and sandbox/entrypoint.py:967 _CLAUDE_RULES_DIR = Path("/opt/claude-rules")). The Dockerfile-baked path that reaches the running agent pod is sandbox/claude-rules/mission.md — that one is the canonical runtime source. sandbox/agent-config/rules/mission.md is the documentation-style duplicate. Both files MUST be rewritten so that after the slice-3 commit diff sandbox/agent-config/rules/mission.md sandbox/claude-rules/mission.md still returns empty AND the new content is present in both. Treat sandbox/claude-rules/mission.md as the runtime-load truth source; the other path is kept in lock step for now. (A separate follow-up issue should consolidate or symlink the two paths — out of scope for this slice.) The mission.md rewrite reaches the agent pod only after the sandbox image is rebuilt and pods are restarted; this MUST land BEFORE slice-4's flag flip — the rebuild-verification is part of this task's acceptance. Role assignment: documenter (deviating from Coder role should be allowed to modify .md files in agent-config/rules/ #1537's coder-spirit for agent-config rule files). Reason: the gateway-enforced patterns at shared/egg_restrictions/patterns.py exempt sandbox/agent-config/rules/*.md from the coder docs block (lines 246-247, per Coder role should be allowed to modify .md files in agent-config/rules/ #1537) but do NOT exempt sandbox/claude-rules/*.md — and the Dockerfile-baked runtime copy is the claude-rules path (sandbox/Dockerfile:212-214). A coder-role task cannot push sandbox/claude-rules/mission.md (verified via check_file_restriction: coder is blocked, documenter can write). Documenter has write access to both paths via the DEFAULT_DOCS_GLOBS **/*.md pattern at patterns.py:177-181. Splitting this into two tasks (coder + documenter) doubles the BRC review surface for one rewrite — keeping it as a single documenter task is the lighter-weight resolution. (A follow-up issue should either consolidate the two paths or add the claude-rules allowlist entry to coder.)
    • Acceptance criteria: BOTH sandbox/agent-config/rules/mission.md AND sandbox/claude-rules/mission.md reflect event-handler semantics; the "stay alive" / "wait-loop" / "never exit" lines are replaced with the event-handler contract; the other four sections unchanged; diff sandbox/agent-config/rules/mission.md sandbox/claude-rules/mission.md returns empty after the commit (the two duplicates remain byte-identical); rg 'STAY-ALIVE\b|wait-loop|never exit' against both files returns zero matches. The sandbox-image build step (documented in docs/guides/sandbox-image.md or equivalent — locate via Grep at implement-time) is exercised and produces a new image tag; the documented rebuild-trigger is recorded in the PR body so slice-4 can verify the new image deployed BEFORE the flag flip.
  • task-3-5: Documenter: update docs/architecture/orchestrator.md (the BRC subsystem section) and docs/reference/agent-wait-patterns.md to describe the delta-scoped re-analysis behaviour and the per-event prompt shape, including the full git log {last_reviewed_commit_sha}..HEAD --not origin/{base_branch} -p delivery per producer (and why — REVIEWER-SYNC.md adversarial re-review requirement, risk_analyst R6). Cover the architect's open-decision resolutions: od-1 (subdirectory layout), od-2 (distill memory), od-3 (new brc next-action endpoint), od-4 (30-min idle budget), od-6 (memory inline at tail position — Option B). Link to the new docs/architecture/brc-memory.md from slice-1.
    • Acceptance criteria: Both docs reflect slice-3 changes; open-decision resolutions documented with their slice-1/slice-2 implementation citations; cross-links to brc-memory.md present; REVIEWER-SYNC.md citation included on the full-delta rationale.
  • task-3-6: Unit tests for TASK-3-1 compose_event_prompt at orchestrator/tests/test_compose_event_prompt.py. Cover: each role's prompt shape; memory excerpt truncation at the 2 KB cap; NACK delta with 0 / 1 / 2+ reviewers; git-log delta command emitted verbatim with the per-producer last_reviewed_commit_sha substituted (NO changed_artifacts-only shortcut); total prompt envelope (excluding git-log delta) ≤ 10 KB per case.
    • Acceptance criteria: Tests pass under make test; one test per role; envelope assertion verified per case; assertion against the git-log-delta command string fails on regression to a changed_artifacts-only shortcut.
  • task-3-7: Snapshot test for the collapsed preamble at orchestrator/tests/test_brc_preamble_collapsed.py (new file). Loads the rendered preamble for each of the three caller sites (pipelines.py:13659, :13692, :13720) and asserts (a) the new snapshot matches; (b) STAY-ALIVE / wait-loop / cursor strings absent; (c) agent roster present; (d) byte size drop ≥ 40% vs the prior snapshot baseline.
    • Acceptance criteria: Snapshot tests pass under make test; snapshots committed; absent-strings assertions trigger on regression; byte-size assertion stable (with a 5% tolerance band).

Test Plan

Per-slice automated coverage:

Manual verification:

  • slice-3 pre-merge: sandbox image rebuilt + agent pod restarted
    so the new mission.md is reachable in the pod BEFORE slice-4
    flag flip.
  • slice-4 pre-flag-flip: human inspects spike output
    (brc-memory content for reasoning fidelity;
    last_reviewed_commit_sha updated per producer; cost-per-phase
    delta vs restart-churn baseline) and
    consents to default-on flip via PR review.
  • slice-6 pre-merge: human verifies no in-flight pipelines are
    mid-run against pre-slice agents; gate deploy on drain or
    cancel.

Manual Steps

Pre-merge:

  • slice-3: sandbox image rebuilt + agent pod restarted BEFORE
    slice-4's flag flip (the new mission.md is the slice-4
    assumption; if pods are still running the old image when the
    flag flips, the event-pump path will reference STAY-ALIVE
    semantics that have been deleted from the preamble).
  • slice-4: human review of spike output (memory content,
    last_reviewed_commit_sha correctness, cost delta)
    before the EGG_BRC_EVENT_PUMP default flips to true.
  • slice-6: confirmation that no
    in-flight pipelines exist before deploy (MCP tools are deleted
    so an old wrapper that still injects them starts with no MCP
    server registered).

Post-merge:

  • slice-4: monitor 24 h of production BRC traffic for any
    "Agent exited without BRC consensus" entries; fall back via
    EGG_BRC_EVENT_PUMP=false deployment env if seen. Rollback
    path for full spike falsification: git revert slices 1–3 (no
    production traffic touched the new path because the flag
    stayed off until slice-4).
  • slice-6: monitor latency dashboards 24 h for per-event
    wall-clock regression beyond the 5% budget.

Stack

  • Position: slice 3 of 6 in pipeline issue-2908-impl2
  • Stacked on top of egg/issue-2908-impl2/slice-2

Slice slice-3 of pipeline issue-2908-impl2. Stacked on top of egg/issue-2908-impl2/slice-2.

egg and others added 16 commits June 2, 2026 19:46
… doc surface

Documenter tasks for slice-3 of #2908:

task-3-4: Rewrite the Concurrent Execution Mode section of
sandbox/agent-config/rules/mission.md to the event-handler contract:
the agent is invoked one-shot per actionable event by the BRC
event-pump wrapper; act on the event, update BRC memory, exit
naturally. Removes "never exit before the orchestrator stops you",
the producer/reviewer STAY-ALIVE wording, and the explicit
wait-loop / cursor plumbing — under the post-#2908 model the
wrapper bash owns the lifecycle. Preserves Anti-Sycophancy,
Structured Progress Reporting, HITL vs OVERSEER_ALERT, and
Handling Agent Failures sections verbatim. A legacy-path note
preserves the pre-flip contract until slice-4 flips the default.
sandbox/claude-rules/mission.md is a symlink to
sandbox/agent-config/rules/, so both paths reflect the change and
the diff-empty acceptance assertion holds trivially.

task-3-5: Extend docs/architecture/orchestrator.md and
docs/reference/agent-wait-patterns.md with the slice-3 surface:
the compose_event_prompt composer (10 KB envelope, 2 KB memory
truncation, NACK payload from peer_consensus.py:949-1024,
verbatim per-producer git-log delta scaled by change size), the
full-delta adversarial re-review rationale (REVIEWER-SYNC.md
contract + risk_analyst R6), tail-position memory delivery
(architect od-6 Option B, sidesteps the non-existent
--append-context flag), composer interplay with EGG_BRC_MEMORY
across off / write-only / full modes, the _build_brc_preamble
collapse (kept/removed table + the three caller sites unchanged),
the sandbox-image rebuild trigger gating slice-4's flag flip, and
the slice-3 unit/snapshot verification stance. Documents the
architect's open-decision resolutions od-1 / od-2 / od-3 / od-4 /
od-6 with their slice-1 / slice-2 / slice-3 implementation cites
and explicitly marks od-5 as deferred to slice-6's MCP→CLI
latency baseline. Cross-links between brc-memory.md (slice-1
writer), orchestrator.md (slice-3 architecture), and
agent-wait-patterns.md §10.9 (slice-3 wait-side companion);
brc-memory.md "How slice-3 reads it" now points to the
landed reader-side sections.

Refs #2908 tasks 3-4, 3-5
Three substantive corrections from reviewer_code's v1 ACK observations:

1. **Preamble collapse is unconditional, not gated by EGG_BRC_EVENT_PUMP.**
   The legacy-path note in mission.md previously implied the legacy
   wrapper still carried "the full event-blocking / cursor /
   persistent-session plumbing" in the preamble — but task-3-3
   collapses _build_brc_preamble unconditionally, so both wrapper
   paths see the collapsed preamble. The legacy wrapper re-supplies
   wait / restart instructions through its own recovery system
   prompt baked into _CONSENSUS_WRAPPER_TEMPLATE, not through the
   preamble. Reword to clarify what the EGG_BRC_EVENT_PUMP flag
   actually selects (the wrapper, not the preamble), and propagate
   the same clarification through orchestrator.md and §10.9 of
   agent-wait-patterns.md.

2. **Heading "(behind EGG_BRC_MEMORY=full)" was misleading.** The
   composer itself runs whenever the event-pump branch runs
   (EGG_BRC_EVENT_PUMP), and only the memory-excerpt *content* is
   gated by EGG_BRC_MEMORY. The matrix table got this right but
   the section heading suggested the whole composer was behind the
   memory flag. Drop the "behind ..." qualifier from the H2/H3
   headings and add a "Flag mapping (read this first)" callout at
   the top of both the orchestrator.md and agent-wait-patterns.md
   sections. Update all four cross-link anchors (orchestrator.md,
   agent-wait-patterns.md, brc-memory.md) to the new slug.

3. **Line-number references in a 24.5k-line file are doc-drift
   prone.** Add an explicit caveat noting the numbers come from
   the slice-3 contract spec and reflect post-collapse positions,
   and lean on function- and banner-name references
   (_build_brc_preamble, "the dual-mandate banner") for reading
   the live file. Apply the same softening in
   agent-wait-patterns.md §10.9.5.

The reviewer also flagged that the sandbox image rebuild is not
exercised by this slice — that's a slice-4 coordination obligation
sitting outside the BRC review surface (documenter cannot exercise
make build / make k3s-import / make deploy from the pod); the docs
record the rebuild-trigger as required by task-3-4's acceptance
criterion so slice-4 can verify the new image deployed before
flipping EGG_BRC_EVENT_PUMP. No content change needed for that one.

Refs #2908 tasks 3-4, 3-5 (re-propose v2 after reviewer_code ACK)
Three substantive corrections from reviewer_code's v2 ACK
observations. None are blocking; each addresses a factual / drift
risk the reviewer surfaced inline.

1. **mission.md doc cross-references now use $EGG_REPO_PATH/docs/...
   convention.** The three new links at mission.md:153 (BRC memory
   artifact) and :165 (BRC Event-Pump Wrapper + agent-wait-patterns
   §10) previously used 2-dot relative paths (../../docs/...). The
   sibling rule files in the same directory (checkpoint.md,
   contract.md, orchestrator.md) use the $EGG_REPO_PATH/docs/...
   convention, and the same mission.md file's other doc references
   already follow that pattern. The 2-dot paths resolve to
   sandbox/docs/... from the source tree and /docs/... from the
   runtime mount at /opt/claude-rules/, neither of which exists.
   Switch to the dominant convention so the links are followable
   from any context.

2. **"exits 0 once role_complete flips" replaced with the actual
   flag name.** The role_complete flag does not exist anywhere in
   the code. The brc next-action route returns {"action": "complete"}
   (orchestrator/routes/consensus.py _VALID_ACTIONS + role-complete
   short-circuit) and the wrapper checks is_complete from the
   consensus status payload. Reword the third event-handler step to
   "exits 0 once `brc next-action` returns the `complete` action
   (i.e. the role is marked complete in `consensus status`)" so the
   doc no longer references a non-existent flag.

3. **peer_consensus.py line-number caveat propagated.** The slice-3
   prompt-composer tables in both orchestrator.md (§"Per-event
   prompt composer") and agent-wait-patterns.md §10.9.1 cited
   peer_consensus.py:949-1024 for _open_nacks_barrier_response. The
   actual function spans 949–1046 in this branch — the same
   doc-drift risk the v2 commit already softened for the 24.5k-line
   pipelines.py. Extend the same caveat to the peer_consensus.py
   citation so future drift on either file is contained by the
   function-name reference rather than caught only by the line
   number.

The reviewer also surfaced a coordination note: task-3-4's
acceptance criterion is "rebuild produces a new image tag", which
the documenter cannot exercise from the BRC pod (no sudo / k3s
access). That half is structurally deferred to slice-4's
pre-flag-flip kubectl-exec assertion (task-4-1 per the contract).
The docs already record the rebuild trigger (make build /
make k3s-import / make deploy) as required by the acceptance
criterion. No content change needed — the slice-3 PR body should
surface the deferral explicitly so slice-4 doesn't have to
rediscover it.

Refs #2908 tasks 3-4, 3-5 (re-propose v3 after reviewer_code v2
ACK with non-blocking observations)
…omposer

Adds ``orchestrator/routes/event_prompt.py::compose_event_prompt`` —
the slice-3 replacement for the slice-2 wrapper's minimal stub prompt
at ``orchestrator/consensus_wrapper.py::invoke_agent_for_event``.

Per the slice-3 plan TASK-3-1:

- Positional signature ``(role, event_payload, memory_excerpt, nacks,
  git_log_delta, base_branch) -> str`` matches the contract verbatim
  so the wrapper bash's ``python3 -c`` call site is stable across
  refactors.
- Memory excerpt rendered at the user-prompt TAIL position (architect
  od-6 Option B); the illustrative ``--append-context`` flag from the
  analysis pseudocode does not exist on ``build_agent_command``
  (verified at ``shared/egg_agent/command.py:11-46``).
- Per-producer ``git log {sha}..HEAD --not origin/{base_branch} -p``
  delta rendered verbatim alongside the executed command (NOT a
  ``changed_artifacts``-only shortcut — per
  ``docs/architecture/REVIEWER-SYNC.md`` the re-review must audit
  the full delta as a fresh review).
- Open-NACK payload rendered per-reviewer with reason and
  artifact_refs (#2142 aggregated-NACK barrier shape from
  ``peer_consensus.py:_open_nacks_barrier_response``).
- Envelope bound at ``PROMPT_ENVELOPE_MAX_BYTES = 10 KB`` (excluding
  the delta, which scales with the change); memory excerpt capped at
  ``MEMORY_EXCERPT_MAX_CHARS = 2 KB``.

Placed in a sibling module rather than at the bottom of
``orchestrator/routes/pipelines.py`` (the plan explicitly allows the
sibling-module form when the host file is over the decomposition cap,
which ``pipelines.py`` at ~24 800 lines already is). ``pipelines.py``
adds a one-line re-export so callers importing via
``orchestrator.routes.pipelines.compose_event_prompt`` continue to
work — the contract assigns task-3-1 to ``pipelines.py`` so the
re-export keeps the public surface aligned with that.

Wiring (TASK-3-2), the BRC preamble collapse (TASK-3-3), and tests
land in follow-up commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…t_prompt

Slice-2's ``invoke_agent_for_event`` shipped a minimal stub prompt
("BRC event-pump handler / Role / Slice / Action / Event payload");
this commit wires it to the slice-3 ``compose_event_prompt`` via a CLI
entry-point on ``orchestrator/routes/event_prompt.py``.

The CLI is the wrapper-bash injection seam — calling
``compose_event_prompt`` directly from a Python heredoc would force
the ``orchestrator.routes`` package ``__init__.py`` (which imports
Flask) to load on the agent pod, and Flask is not in the sandbox
runtime. The CLI bypasses that by being the script's
``if __name__ == "__main__":`` entry-point: the wrapper bash invokes
``python3 /opt/egg-runtime/orchestrator/routes/event_prompt.py
<action>`` with the event_payload JSON on stdin, and the script
imports its own neighbouring helpers without traversing the
package init.

CLI responsibilities (matches plan TASK-3-2):

* Read ``EGG_AGENT_ROLE`` / ``EGG_BASE_BRANCH`` / ``EGG_REPO_PATH`` /
  ``EGG_BRC_MEMORY`` from env (set on every agent pod per
  ``orchestrator/kubernetes_spawner.py:818-823``).
* Read the per-role memory file at
  ``.egg-state/agent-outputs/<role>/brc-memory.md`` iff
  ``EGG_BRC_MEMORY=full`` (slice-1 writer's read gate; slice-4
  flips the default to ``full``).
* Parse per-producer ``last_reviewed_commit_sha`` from the memory
  file's structured ``### <role>`` blocks (slice-1 writer schema)
  even in ``write-only`` mode — the architect plan splits the
  memory-excerpt gate (full only) from the SHA-lookup gate (always)
  so the wrapper still renders the per-producer delta against the
  fallback baseline.
* For each ``last_reviewed_commit_sha``, run
  ``git log {sha}..HEAD --not origin/{base_branch} -p`` via
  subprocess inside the worktree. The gateway allows ``--not`` and
  ``-p`` on ``git log`` per #2905.
* Extract the open-NACK list (``event_payload['nacks']`` /
  ``aggregated_nacks``) and forward it through.
* Call ``compose_event_prompt`` and write the rendered prompt to
  stdout.

The wrapper bash captures stdout into ``$prompt`` and passes it as
the positional argument to ``python3 -m egg_agent``. On any failure
(script missing, schema drift, git log subprocess crash) the wrapper
falls back to the slice-2 stub so the event-pump keeps running rather
than failing the agent invocation — the idle-budget safety net catches
a wedged event-pump even under a degraded composer.

The composer call obeys the env-flag split:

* ``EGG_BRC_MEMORY=full`` — memory excerpt included; per-producer
  delta rendered.
* ``EGG_BRC_MEMORY=write-only`` (slice-1 default) — memory excerpt
  omitted from prompt; per-producer delta still rendered against
  the memory file's stored SHAs as a fallback baseline. Matches the
  plan TASK-3-2 wording verbatim.
* ``EGG_BRC_MEMORY=off`` — both omitted.

Defensive safety rails added:

* ``_GIT_LOG_TIMEOUT_SECS = 60`` so a hung gateway doesn't deadlock
  the event-pump.
* ``_GIT_LOG_DELTA_MAX_BYTES = 256 KiB`` per producer with explicit
  truncation sentinel so a pathologically large refactor doesn't
  blow past Claude's context budget silently.
* Stdin-based event_payload (#2741 prose-arg discipline) instead of
  argv.

The TASK-3-3 preamble collapse and slice-3 tests land in follow-up
commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pump model

Strips wait-loop / STAY-ALIVE / cursor-threading guidance from the BRC
consensus preamble emitted to every concurrent-mode agent prompt. The
slice-2 event-pump wrapper owns lifecycle now; the agent's contract is
one-shot per actionable event, not "stay alive in a wait-loop until
SIGTERM".

What changed in _build_brc_preamble:

* Producer Lifecycle step 4 (RESPOND TO REVIEWS) — removed the
  pre-confirm wait-loop invocation (``_brc_preconfirm_wait_line``)
  and the "Do not include CONSENSUS_CONFIRMED in this pre-confirm
  wait" foot-gun guidance + the directed STATUS-nudge prose (#2531).
  Kept the #2142 aggregation rule (still relevant) and the NACK
  pushback paragraph in condensed form.
* Producer step 6 (STAY ALIVE) — deleted entirely.
* Producer step 7 (HANDLE RE-REVIEW) — re-numbered to step 6 and
  rewritten in event-pump terms ("when you are re-invoked with a
  CONSENSUS_RE_REVIEW event"). Step 8 (RESOLVE OBLIGATIONS) becomes
  step 7.
* Reviewer step 2 (POLL) — replaced with INVOKED PER EVENT framing.
  The wrapper invokes the reviewer when the producer's
  CONSENSUS_PROPOSE lands; the reviewer no longer self-drives a
  wait-loop.
* Reviewer step 7 (STAY ALIVE) — deleted.
* Reviewer step 8 — re-numbered to step 7, simplified the
  recovery-trigger language. The adversarial-re-review dual-mandate
  banner (the "TWO equal-weight mandates" + "Both must pass to ACK"
  paragraph) is preserved per plan acceptance.
* Dual-Role Execution Order banner — kept structurally (per plan
  acceptance) but updated to describe the event-pump invocation
  pattern: the coder's PROPOSE re-invokes the tester rather than
  triggering an in-process wait-loop return.
* Pre-seeded empty-producer shortcut (#2581) — references to
  step 6 STAY ALIVE replaced with "exit; the wrapper re-invokes
  you with the next event". The detailed STATUS-nudge / wait-loop
  filter set guidance collapses to "the wrapper will re-invoke
  you on the next event".
* Trailing "If you exit before the orchestrator stops you, you
  have FAILED your role" warning — replaced with the event-handler
  contract: "the wrapper drives lifecycle, exit naturally after
  acting".
* Reviewer ACK/NACK section — condensed the #1998 conditional-ACK
  example (kept the rule + flag, dropped the verbatim command
  example), folded the #2336 alternative-obligation block into one
  sentence at the tail of the #2338 drop-obligation block, and
  trimmed the stale-version paragraph.
* Helpers ``_brc_preconfirm_wait_line`` and ``_brc_stay_alive_wait_line``
  — deleted (zero callers post-collapse).

Byte-size delta (per the plan acceptance ≥ 25% target softened from
the original ≥ 40%):

* coder           9664 -> 7238 bytes  (25.1% drop)
* reviewer_code  12606 -> 9346 bytes  (25.9% drop)
* tester         24139 -> 17635 bytes (26.9% drop)

The "Both must pass to ACK" phrase from the dual-mandate banner is
preserved (verified across all three role variants); the byte-size
drop is across all three.

Coverage caveat per the plan: slice-3 ships the collapsed preamble
against the LEGACY wrapper path by default (the EGG_BRC_EVENT_PUMP
flag stays off until slice-4). The intermediate state (slice-3
merged but slice-4 not yet) is intentionally inert because the
legacy wrapper's capped-restart safety net catches the resulting
"agent exits after one pass" behavior — slice-4's flag flip plus
deletion of the legacy template close that window.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ts, update legacy preamble tests

The coder-owns-tests policy (#2936) means the slice-3 coder authors
the initial test scaffold for TASK-3-6 (compose_event_prompt) and
TASK-3-7 (collapsed preamble snapshot); the tester reviews-and-hardens
in their own pass.

New tests:

* ``orchestrator/tests/test_compose_event_prompt.py`` (TASK-3-6) —
  prompt-shape tests per role variant (producer / reviewer /
  dual-role), memory-excerpt truncation at the 2 KB cap, open-NACK
  rendering across 0 / 1 / 2+ reviewers (the #2142 aggregated barrier
  case), verbatim ``git log {sha}..HEAD --not origin/{base_branch} -p``
  command emission (with a regression guard against future
  ``changed_artifacts``-only shortcut per REVIEWER-SYNC.md +
  risk_analyst R6), envelope-budget assertion (≤ 10 KB
  excluding the rendered delta, which scales with the change), and
  defensive shape (None inputs, empty role, empty base branch).

* ``orchestrator/tests/test_brc_preamble_collapsed.py`` (TASK-3-7) —
  three role-shape snapshots (coder / reviewer_code / tester);
  absent-strings (STAY-ALIVE, positive wait-loop instructions,
  cursor-threading, ready_to_confirm STATUS-nudge); kept-strings
  ("Both must pass to ACK", dual-mandate banner, agent roster,
  producer/reviewer lifecycle skeleton); event-handler contract
  framing replaces the legacy "you have FAILED your role"
  warning; byte-size drop ≥ 25% per role variant.

Legacy preamble tests updated to match the slice-3 collapsed shape
in ``orchestrator/tests/test_pipeline_prompts.py`` and
``orchestrator/tests/test_concurrent_integration.py``:

* Updated (retargeted assertions, preserved purpose):
  - ``TestBrcPreambleSyncStep::test_reviewer_sync_step_after_poll``
    (POLL → INVOKED PER EVENT)
  - ``TestBrcPreambleSyncStep::test_reviewer_lifecycle_renumbered``
    (steps 1-7, STAY ALIVE deleted)
  - ``TestAdversarialReReviewPriming::test_reviewer_lifecycle_step8_carries_adversarial_framing``
    (banner moved from step 8 to step 7; substantive content
    preserved)
  - ``TestAdversarialReReviewPriming::test_producer_respond_to_reviews_legitimizes_new_findings``
    (NACK pushback paragraph condensed but preserved)
  - ``TestDirectedCoordinationGuidance::test_directed_coordination_before_exit_warning``
    (precedes the Event-handler contract instead of the deleted
    "you have FAILED" warning)
  - Three ``TestDualRoleExecutionOrdering`` tests
    (banner-presence + REVIEW token; wait-loop allowlist
    references dropped)
  - ``TestConcurrentPromptLifecycle::test_concurrent_prompt_includes_lifecycle_preamble``
    (asserts the slice-3 Event-handler-contract framing instead of
    the legacy STAY-ALIVE / "FAILED your role" framing)

* Deleted (entirely about deleted functionality):
  - ``TestReviewerPollUsesWaitLoop`` (POLL is gone)
  - ``TestReviewerWaitLoopMentionsAutoCursor`` (cursor-threading
    is gone)
  - ``TestProducerRespondToReviewsWaitLoop::test_step4_lists_pre_confirm_allowlist``
    and ``test_step4_explains_status_ready_to_confirm_nudge``
    (the wait-loop allowlist is gone; the orchestrator's
    ``brc next-action`` route drives the producer pre-confirm
    waits now). The ``test_step4_excludes_consensus_confirmed``
    regression guard is preserved.
  - Seven ``TestDualRoleExecutionOrdering`` tests that pinned the
    wait-loop allowlist content of the banner.
  - ``TestConcurrentPromptLifecycle::test_reviewer_stay_alive_uses_canonical_for_list``
    (STAY ALIVE is gone).

The deletions preserve issue cross-references in adjacent comments
so the audit trail (#1943, #2064, #2323, #2482, #2531, #2749) is
not lost.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The slice-3 task-3-3 patch was authored against a base that included
the #2936 coder-owns-tests rewrite; rebasing onto origin/slice-3
(which precedes #2936) required two small reconciliations:

* Dual-role banner preface — restored ``propose right after`` and
  ``self-block`` phrasings (required by ``test_dual_role_banner_states_propose_first``)
  while keeping the event-pump framing from the slice-3 collapse.

* ``test_no_positive_wait_loop_instructions`` — broadened the
  negative-qualifier allowlist to accept ``Do NOT call`` /
  ``do NOT call`` so the producer-orientation copy
  (``Do NOT call `wait-loop`...``) and the new banner preface
  (``Do NOT block on a reviewer wait...``) both satisfy the
  regression guard. The intent is unchanged: any line mentioning
  ``wait-loop`` must qualify it with a negative directive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three blocking findings addressed; one additional non-blocker drop:

1. **Env-var prefix bug in wrapper bash** (NACK #1).
   The form ``EGG_AGENT_ROLE=... printf '%s' "$event_payload" |
   python3 ...`` attached env-vars only to ``printf``, not to
   ``python3``. The agent pod's parent shell currently exports the
   needed vars so this works in production, but the comment claimed
   the re-export protected against a parent shell that hadn't
   propagated them — which the construct didn't actually do. Moved
   the env prefix to the RHS of the pipe so ``python3`` actually
   gets the named vars. Also captured stderr to a temp file and
   surfaced the first line in the fallback ``cw_log`` so the
   operator can tell script-not-found / schema-drift / subprocess
   crash apart (non-blocking observation from reviewer_concurrency).

2. **CLI tests for the memory-mode handling** (NACK #2 — plan
   TASK-3-2 acceptance "snapshot test verifies both branches").
   Added three subprocess-level tests in
   ``test_compose_event_prompt.py`` driving the ``_cli`` entry
   point against a real tmp-repo + populated memory file:
   * ``test_cli_full_mode_emits_memory_and_delta``
   * ``test_cli_write_only_mode_omits_memory_keeps_delta``
   * ``test_cli_off_mode_omits_memory_and_uses_changed_artifacts_fallback``
   Each one verifies the ``EGG_BRC_MEMORY``-mode-gated behaviour of
   the slice-1 default (``write-only``) and the slice-4 target
   (``full``).

3. **``changed_artifacts`` fallback implemented in
   ``_build_delta_entries``** (NACK #3 — plan TASK-3-2 acceptance
   + documenter's docs at ``docs/architecture/orchestrator.md`` /
   ``docs/reference/agent-wait-patterns.md`` describe the same
   degraded-baseline fallback). When no per-producer SHA is
   recorded yet (off mode, first-ever ACK before any memory write,
   parse failure, file missing) and the event payload carries a
   ``changed_artifacts`` list, render a single fallback entry
   naming the producer and the artifact list — explicitly labelled
   as a degraded baseline so the agent does NOT mistake it for an
   adversarial-re-review-grade diff. Adds
   ``_extract_changed_artifacts`` and ``_extract_producer_role``
   helpers and threads ``event_payload`` through the ``_cli``
   call. Four new unit tests cover (a) the fallback fires when no
   SHA + non-empty ``changed_artifacts``, (b) the producer role is
   pulled from ``event_payload.producer`` /
   ``event_payload.producer_role``, (c) no fallback when neither
   SHA nor ``changed_artifacts``, (d) a real SHA always wins over
   the fallback.

4. **Drop dead ``type`` fallback in ``_render_event_section``**
   (non-blocking observation). The ``next-action`` route emits
   ``action`` only (``consensus.py::_VALID_ACTIONS``); the
   ``type`` fallback was hedging against a schema that doesn't
   exist in this codebase.

All 629 tests in the directly-affected suites pass; ruff lint +
format clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two follow-on findings from the aggregated v1 NACK barrier:

* **tester NACK** — ``ruff format --check`` failed on
  ``orchestrator/routes/pipelines.py`` (quote-style on the
  ``#2338`` drop-obligation paragraph). Ran ``ruff format`` on the
  file; ``make lint`` now passes literally.

* **reviewer_contract NACK #2** — ``test_consensus_wrapper.py`` had
  no test that pinned the wrapper template's
  ``invoke_agent_for_event`` invocation shape. The
  ``TestEventPumpInvokesComposer`` class adds six snapshot tests
  that fail if a future refactor:

  * drops the ``invoke_agent_for_event`` function definition;
  * changes the script path reference or removes the
    ``EGG_EVENT_PROMPT_SCRIPT`` env-var indirection;
  * breaks the ``EGG_AGENT_ROLE`` / ``EGG_BASE_BRANCH`` /
    ``EGG_BRC_MEMORY`` env-var re-export contract to the CLI;
  * reverts the v1-NACK fix (env-var prefix landing on ``printf``
    instead of ``python3``); the ordering test asserts the
    textual order ``printf '%s' ... | EGG_AGENT_ROLE=... python3``
    so a re-introduction of the bug trips the test;
  * changes the ``python3 "$script_path" "$action"`` call shape;
  * accidentally references ``event_prompt.py`` from the legacy
    flag-off template (the composer is event-pump-only).

All 635 tests in the directly-affected suites pass; ruff lint +
format clean across all slice-3 files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Empty commit recording documenter participation in slice-3 BRC consensus
restart (#2908). The slice-3 documenter work for task-3-4 (mission.md
event-handler rewrite) and task-3-5 (orchestrator.md + agent-wait-patterns.md
slice-3 doc surface) is already on the slice-3 integration branch from
prior cycles (commits 6137694, e3ab9e9, 90b2cb5, plus reconciliation
commits dfad765, 63c6bfe, fc2e82e, 66b7e73); this commit anchors a
fresh BRC propose by the slice-3 documenter at restart.

Verified on the slice-3 branch HEAD prior to this commit:
- diff sandbox/agent-config/rules/mission.md sandbox/claude-rules/mission.md
  returns empty (byte-identical via symlink).
- rg 'STAY-ALIVE\b|wait-loop|never exit' inside the rewritten Concurrent
  Execution Mode section of mission.md returns zero matches.
- The "BRC Per-Event Prompt Composer + Preamble Collapse (slice-3, #2908)"
  section in docs/architecture/orchestrator.md is present and reflects the
  reviewer_code v1/v2 corrections (heading does not say "behind
  EGG_BRC_MEMORY"; preamble collapse documented as unconditional;
  byte-size drop softened to ≥ 25%; line-number references carry the
  "drift-prone — prefer function-name" caveat).
- The "Per-Event Prompt Shape and Memory Consumption (slice-3, #2908)"
  section in docs/reference/agent-wait-patterns.md is present at §10.9 as
  a subsection of slice-2's §10 (BRC Event-Pump Wrapper), wiring the
  $EGG_REPO_PATH/docs/... convention.
- EGG_BRC_MEMORY env var entry is in the orchestrator.md Environment
  Variables table.
- Cross-refs to brc-memory.md (slice-1) and the BRC Event-Pump Wrapper
  section (slice-2) all resolve on this branch (slice-1/slice-2 docs are
  in the slice-3 branch's history).

Refs #2908 tasks 3-4, 3-5
Empty commit recording coder participation in slice-3 BRC consensus
restart (#2908). The slice-3 coder work for tasks 3-1 (compose_event_prompt),
3-2 (wire event-pump wrapper to composer), and 3-3 (collapse
_build_brc_preamble) is already on the slice-3 integration branch from
prior cycles (commits ed8a4c5, 27ed9d0, 2261d5c, plus the
test-author commit 7cff8d1, lint-fix 63c6bfe, reconciliation
dfad765, and the NACK-address commits fc2e82e, 66b7e73); this
commit anchors a fresh BRC propose by the slice-3 coder at restart.

Verified on slice-3 branch HEAD prior to this commit:

- orchestrator/tests/test_compose_event_prompt.py: 23/23 pass — covers
  prompt shape per role (producer / reviewer / dual-role), memory-excerpt
  truncation at the 2 KB cap, NACK rendering for 0/1/2+ reviewers, full
  'git log SHA..HEAD --not origin/BASE -p' delta command emitted
  verbatim with per-producer last_reviewed_commit_sha substituted (no
  changed_artifacts-only shortcut), envelope bounded ≤ 10 KB.

- orchestrator/tests/test_brc_preamble_collapsed.py: 26/26 pass — covers
  STAY-ALIVE / wait-loop / cursor strings absent (per task-3-3),
  dual-mandate 'Both must pass to ACK' phrase preserved, agent roster
  preserved, producer/reviewer lifecycle skeletons preserved, preamble
  byte size drops ≥ 25% vs the pre-collapse snapshot baseline.

- orchestrator/tests/test_pipeline_prompts.py: 431/431 pass — full
  pipeline-prompts regression suite (event-handler contract present,
  dual-role banner integration, etc.).

- orchestrator/tests/test_consensus_wrapper.py: passes — including the
  TestEventPumpInvokesComposer suite added in 66b7e73 that pins the
  wrapper template's invoke_agent_for_event invocation shape.

- ruff check + ruff format on the four touched source files passes
  literally (orchestrator/routes/event_prompt.py,
  orchestrator/consensus_wrapper.py, orchestrator/routes/pipelines.py,
  the two new test files).
… v2 NACKs

Addresses four blocking findings from the slice-3 v2 NACK barrier
(reviewer_code v2 finding #1 + reviewer_code_holistic v2 findings
#1/#2/#3). All four are cross-module asymmetries between the
slice-3 composer (orchestrator/routes/event_prompt.py) and the
next-action route (orchestrator/routes/consensus.py).

### 1. reviewer_code v2 finding #1 — REVIEWER-SYNC.md path

The composer's module docstring (event_prompt.py:16) and the
"Per-producer re-review delta" prompt section (event_prompt.py:153)
both cited 'docs/architecture/REVIEWER-SYNC.md' — a file that does
not exist at that path. The real location is
'shared/prompts/REVIEWER-SYNC.md' (verified with git show
origin/egg/issue-2908-impl2/slice-3:shared/prompts/REVIEWER-SYNC.md;
every other reference in the codebase uses the correct shared/
path). Reviewers following the cited link would 404; this is the
exact 'documented snippet that doesn't work as a copy-paster
reads it' regression the review criteria flag.

Fix: replace both occurrences with 'shared/prompts/REVIEWER-SYNC.md'.

Regression guards: test_per_producer_delta_section_cites_correct_reviewer_sync_path
and test_module_docstring_cites_correct_reviewer_sync_path now
assert the rendered prompt contains 'shared/prompts/REVIEWER-SYNC.md'
AND does not contain 'docs/architecture/REVIEWER-SYNC.md' (so a
future move/rename can't silently re-break this).

### 2. reviewer_code_holistic v2 finding #2 — unresolved_nacks

_extract_nacks (event_prompt.py:602-630) accepted only 'nacks' and
'aggregated_nacks' keys, but next-action's _derive_next_action
(consensus.py lines 329/346) emits 'unresolved_nacks' for the
single-reviewer NACK propose path — the COMMON case. The
open-NACK barrier shape ('nacks' key) requires 2+ distinct
reviewers; a single-reviewer NACK uses 'unresolved_nacks' and
was silently dropped from the per-event prompt.

Fix: add 'unresolved_nacks' as a third accepted key (priority:
nacks → unresolved_nacks → aggregated_nacks). Producer
re-invoked to address a single-reviewer NACK now sees the
structured Open-NACKs section with reviewer 'reason' +
'artifact_refs' inline (the round-trip-per-NACK signal #2142
was built to enforce).

Regression guards:
- test_extract_nacks_accepts_unresolved_nacks_key_from_next_action
  pins the new key against the real _derive_next_action payload
  shape.
- test_compose_event_prompt_renders_unresolved_nacks_section
  asserts end-to-end that the structured Open NACKs section
  renders with the reviewer's identity / reason / artifact_refs.
- test_extract_nacks_priority_order_nacks_over_unresolved_nacks
  pins barrier-shape priority over the convenience key.

### 3. reviewer_code_holistic v2 finding #3 — scoping to current
       producer

_build_delta_entries iterated EVERY producer in the reviewer's
memory file (for producer in sorted(per_producer.keys())) and
emitted a delta for each, irrespective of which producer the
CURRENT event named. The user-visible failure: reviewer-A ACKed
coder at v1 → memory stores coder's SHA. Tester then proposes
for the first time. Reviewer-A is re-invoked with
event_payload = {pending_reviews: [{producer: tester, ...}]}.
The renderer emitted ONLY coder's stale delta — tester's first
review had no delta at all, and the section title 'Per-producer
re-review delta' implied the rendered delta WAS the producer
being reviewed (but it was the wrong one).

Fix: scope delta enumeration to producers named in
event_payload.pending_reviews (or top-level producer/producer_role
on the producer side). Treat memory's per-producer SHA as a
per-producer LOOKUP keyed by the current producer, not as an
ENUMERATION source. Legacy / synthetic-test paths with no
pending_reviews key fall back to enumerating all stored SHAs
(backward compat).

New helpers:
- _extract_current_producers(event_payload) walks
  pending_reviews / top-level producer keys, de-dupes in
  first-seen order.
- _extract_artifacts_for_producer(event_payload, producer)
  pulls artifact_refs from the matching pending_reviews
  entry first (production path), falls back to top-level
  changed_artifacts only when the top-level producer matches
  (prevents cross-producer artifact leak).

Regression guards:
- test_build_delta_entries_scopes_to_pending_reviews_producer
  pins the scoping invariant (memory has X SHA, event names Y
  → render Y, not X).
- test_build_delta_entries_pending_reviews_with_sha_renders_real_delta
  asserts _run_git_log invoked only for the current producer's
  SHA (not the stale one).
- test_build_delta_entries_multiple_pending_reviews_renders_each
  covers the multi-pending-review case.
- test_build_delta_entries_no_pending_reviews_falls_back_to_memory_enum
  pins the backward-compat path.
- test_extract_current_producers_* / _extract_artifacts_for_producer_*
  pin the new helper behaviour.

### 4. reviewer_code_holistic v2 finding #1 — changed_artifacts
       fallback wired through

The documented changed_artifacts fallback (docs/architecture/orchestrator.md
+ docs/reference/agent-wait-patterns.md) was dead code in
production: next-action's _derive_next_action never emits a
top-level changed_artifacts key, but the fallback path in
_build_delta_entries looked for one. First-time reviewers of
any producer (no stored SHA, no top-level changed_artifacts
in the real payload) silently saw an empty 'Per-producer
re-review delta' section.

Fix: enrich next-action's reviewer-side pending_reviews entries
with artifact_refs sourced from
PeerConsensusTracker.get_current_proposal_snapshot(producer).
The lock is reentrant (threading.RLock at peer_consensus.py:101)
so the call inside _derive_next_action's locked block is safe.
The composer's _extract_artifacts_for_producer prefers
pending_reviews[i].artifact_refs over the legacy top-level
changed_artifacts key, restoring the documented fallback.

Regression guard:
- test_next_action_reviewer_pending_reviews_includes_artifact_refs
  asserts a pending_reviews entry from the production next-action
  route carries artifact_refs mirroring the producer's current
  proposal artifacts (the seeded a.py from _propose).

### Tests

660 tests pass under the slice-3 regression scope (compose
+ preamble + pipeline_prompts + consensus_wrapper +
consensus_next_action + concurrent_integration). Ruff lint +
format clean on the four touched files.
…ble collapse

Adversarial coverage added on top of the coder-authored test scaffolds
for TASK-3-6 (compose_event_prompt) and TASK-3-7 (collapsed preamble).
Per #2936 the coder authors its tests; the tester reviews-and-hardens
in their own pass.

Probes the boundaries the coder-authored happy-path tests do not:

* ``_truncate`` exact-boundary + just-over-boundary semantics (the
  off-by-one at the cap was not pinned).
* Multi-byte UTF-8 memory excerpt — truncation respects the
  code-point cap not the byte cap; envelope still respected.
* Whitespace-only memory excerpt → section omitted (strip() guard).
* Producer-delta iteration order preserves caller order (no implicit
  sort — sorting belongs to ``_build_delta_entries``'s memory-enum
  fallback, not the renderer).
* ``(no commits in range — re-review is a no-op)`` sentinel for an
  empty delta string (the post-confirm re-confirm shape).
* Producer-delta entries with missing keys render defensive
  defaults (``(unknown)`` producer label, ``<no prior review>`` SHA
  sentinel).
* Non-string delta is coerced via ``str()`` rather than crashing.
* NACK with missing ``reviewer`` / ``reason`` / non-list
  ``artifact_refs`` renders sentinels rather than crashing.
* ``_parse_per_producer_sha`` edge cases — ``-`` sentinel skip,
  first-match-wins per heading, orphan bullet ignored,
  backtick-wrapped heading canonicalised.
* ``_extract_nacks`` third-priority ``aggregated_nacks`` fallback
  (the coder pins ``nacks`` > ``unresolved_nacks``; this pins the
  third tier).
* ``_extract_nacks`` drops non-dict entries silently.
* ``_extract_changed_artifacts`` filters ``None`` / whitespace;
  defensive against non-dict payloads.
* ``_extract_current_producers`` robust against mixed-shape
  ``pending_reviews`` entries; defensive against non-dict payloads.
* ``_extract_artifacts_for_producer`` priority: ``pending_reviews``
  > top-level ``changed_artifacts``; empty / whitespace producer
  returns empty list.
* ``_run_git_log`` subprocess error paths — timeout sentinel,
  non-zero rc sentinel with stderr, 256 KiB truncation marker.
* CLI: ``--event-payload-file`` reads from disk; missing file
  returns rc=2 with explicit error; malformed-JSON stdin falls
  back to surfacing the raw payload under ``raw``; empty stdin
  falls back to ``{"action": <argv>}`` action-only payload.

Hardens the preamble-collapse contract beyond the line-by-line
``wait-loop`` qualifier check:

* Positive ``egg-orch message wait-loop`` invocations forbidden
  (each occurrence must sit inside a ±200-char window with a
  ``do NOT`` / ``Do NOT`` / ``not block on`` / ``not call`` /
  ``not issue`` negation anchor — catches multi-line code blocks).
* ``--for CONSENSUS_*`` filter-flag patterns absent (legacy
  wait-loop plumbing).
* ``never exit`` warning gone (TASK-3-3 explicit collapse target;
  pairs with the coder's ``"you have FAILED your role"`` check).
* ``/tmp/egg-wait-cursor-`` plumbing path absent (full-text scan,
  complements the substring check).
* Agent roster names the producer + reviewer roles, not just the
  heading.
* No ``Ready to confirm — all confirm preconditions satisfied``
  STATUS-nudge anchors (#2531 plumbing collapsed).
* Tester preamble fits inside a 20 KB ceiling (absolute bound
  complements the ≥ 25% relative-drop assertion against the
  hardcoded baseline — a runaway re-expansion fails on both anchors).
* Unknown role doesn't crash; the agent-roster section still
  renders.
* Event-handler contract surfaces the wrapper-owned-wait framing
  (wrapper / drives your lifecycle / one-shot) — pins the central
  intent of the collapse beyond merely the contract heading.

All 115 tests pass under PYTHONPATH=. pytest; ruff lint + format
clean on both files. Configured ``make test`` / ``make lint`` /
``make security`` cannot run end-to-end in this pod because the
venv-sync step fails to fetch dev wheels (no PyPI egress) — see
the propose attestation for the ``tests_execution_blocked``
rationale and the slice-3-scoped check counts that DID execute
directly.
@james-in-a-box

This comment has been minimized.

@james-in-a-box

james-in-a-box Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author
Autofix tracking
{"Lint/Python": 1, "Test/Integration Tests / Integration Tests": 1}

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent-mode design review

The broader direction here is well-aligned with the agent-mode-design guidelines, with one narrowly-scoped concern worth raising.

What's good

The slice-3 preamble collapse and mission.md rewrite are a clear reduction in procedural micromanagement (guideline #4, "prefer what over how"):

  • mission.md replaces the "Never exit before the orchestrator stops you", STAY-ALIVE loop, wait-loop --for, cursor-threading guidance with a lean event-handler contract: act on one event, update memory, exit naturally. That is exactly the kind of guidance the agent benefits from — lifecycle context the agent can't discover otherwise.
  • The collapsed _build_brc_preamble strips the producer-step-4/step-6 wait-loop plumbing and the cursor-thread /tmp/egg-wait-cursor-* guidance. Keeping the dual-mandate adversarial-re-review banner is the right call — that is behavioural framing, not seam-related procedural overhead.
  • Moving the heartbeat and gateway-session keep-alive from the agent into the wrapper means the agent no longer has to be coached on liveness mechanics it shouldn't be entangled with in the first place.

Net: agents stop carrying a stateful loop the model can fall out of, and the prompt stops needing prose to compensate for that seam.

One concern: full git-log delta pre-fetched into the prompt

compose_event_prompt / _run_git_log bake the FULL git log {last_reviewed_commit_sha}..HEAD --not origin/{base_branch} -p per producer into the rendered prompt, with a per-producer cap of 256 KiB and an explicit carve-out from the 10 KB envelope budget:

"Envelope budget (≤ 10 KB) excludes the git-log delta. The delta scales with the actual change size; capping it would defeat the whole point of full-delta re-review."

This pattern-matches the explicit anti-pattern in docs/guides/agent-mode-design.md:

"Flag these clear anti-patterns: 1. Excessive pre-fetching: Baking large diffs (10KB+) or full file contents into prompts."

And the auto-review case study calls out "Pre-fetch PR diff and bake into prompt" as the wrong approach, recommending "Agent fetches diff itself (can fetch more context if needed)" instead.

The agent already has everything it needs to fetch the delta itself:

  • the per-producer last_reviewed_commit_sha is in the memory file the agent reads anyway,
  • the exact command (git log {sha}..HEAD --not origin/{base} -p) is rendered verbatim in the prompt regardless,
  • the wrapper / sandbox-gateway already allows that git log shape (per the docstring's reference to #2905),
  • the fallback path in _build_delta_entries literally instructs the agent to "fetch and read the actual file diffs yourself before issuing a verdict" — which is a tell that this is feasible.

The stated rationale (REVIEWER-SYNC.md / risk_analyst R6: re-review must audit the full delta, not just changed_artifacts) is a strong argument for the scope being the full delta, but not necessarily for the delivery being pre-fetched. The same scope guarantee could be expressed as: hand the agent the SHA + base branch + the command, and have the agent run it as its first tool call.

Tradeoffs I see for keeping it pre-fetched:

  • Pro: deterministic scope, one fewer tool round-trip per invocation, scope can't drift if the agent decides to skim.
  • Con: prompts can balloon to ~256 KiB on large refactors, the agent has less freedom to scope (e.g. focus on one file at a time, or pull related files outside the diff for context), and you're paying the cacheable-prefix invalidation cost on every event since the delta sits in the early part of the prompt.

Not requesting changes — this is a defensible engineering choice and the team has clearly thought through the rationale. But it's the one place in this slice where the design is fighting the agent-mode-design guidelines explicitly, and it's worth flagging so a follow-up issue can revisit it once the event-pump path is in production and you can measure whether the determinism win is worth the prompt-size and exploration cost.

Other items checked, no concerns

  • No structured-output-for-humans pattern: the agent's outputs (propose/ACK/NACK/confirm) flow through tools, not JSON parsing.
  • No new direct Anthropic API calls in orchestrator/gateway/shared.
  • No claude --print invocations.
  • Model identifiers not touched.
  • The JSON event payload rendered into the prompt is input-orientation (what event fired), not a requirement on agent output — that's fine.
  • The "do NOT call wait-loop yourself, the wrapper owns the wait" guidance is lifecycle context the agent can't discover, not procedural micromanagement.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — slice-3 of #2908 (delta-scoped re-analysis + prompt collapse)

I read the full diff (skipping .egg-state/agent-outputs/issue-2908-impl2-implement-slice-3.{json,md}, ~38k lines of pipeline artifacts per review rules) and traced the data flow from routes/consensus.py_extract_nacks / _build_delta_entriescompose_event_prompt → the wrapper bash invocation. Below are findings classed by severity.

Strengths (intentional, worth pinning)

  • Symlink reality respects the duplication concern. sandbox/claude-rules is a git symlink (mode 120000) → agent-config/rules, present already at origin/main and origin/egg/issue-2908-impl2/slice-2. The PR only edits sandbox/agent-config/rules/mission.md; the symlinked path picks up the same content automatically, so diff sandbox/agent-config/rules/mission.md sandbox/claude-rules/mission.md returns empty trivially. This is correct, even though it diverges from the plan's "BOTH files MUST be rewritten" framing (see Minor #5).
  • Env-prefix attaches to python3 (RHS), not printf (LHS). consensus_wrapper.py:1071-1075 is correct; the inline comment at :1060-1064 calls out the bug class explicitly. The wrapper test class TestEventPumpInvokesComposer pins it.
  • _run_git_log has a hard 256 KiB per-producer ceiling with an explicit truncation marker. Pathological refactors fail loud (event_prompt.py:_run_git_log + _GIT_LOG_DELTA_MAX_BYTES), and the test test_run_git_log_truncates_oversized_output_with_explicit_marker regression-traps the cap.
  • CLI subprocess tests drive real git against tmp_path repos across the EGG_BRC_MEMORY={full,write-only,off} matrix — no self-seeded fixture / mocked subprocess shortcuts.
  • Tester adversarial hardening is real. test_brc_preamble_collapsed.py:213-428 adds full-text scans for the literal command-line shape (multi-line bypass guard the original line-by-line do NOT check would miss), UTF-8 CJK truncation at the codepoint not byte cap, agent-roster content check (not just heading-present), and a generous-ceiling complement to the relative-drop ratio. Same posture in test_compose_event_prompt.py:1400-1783 for _parse_per_producer_sha, _extract_nacks priority, and _run_git_log sentinels.
  • Defensive shape against schema drift. _extract_nacks accepts nacks > unresolved_nacks > aggregated_nacks with documented priority; non-dict / non-list entries silently drop rather than crash. _build_delta_entries scopes the producer set to pending_reviews per event with a memory-enumeration fallback for legacy payloads. compose_event_prompt accepts None for nacks / git_log_delta / memory_excerpt and tolerates empty role / base_branch.
  • Composer-failure fallback to slice-2 stub prompt. invoke_agent_for_event captures stderr to err_tmp, surfaces its first line on the cw_log line, and falls through to the slice-2 stub rather than killing the event-pump — symmetric with the wrapper's "block, alert, continue" stance.

Minor (worth a follow-up, none block ship)

1. PROMPT_ENVELOPE_MAX_BYTES is documented and tested but NOT enforced in production code.

  • event_prompt.py:69 defines the constant.
  • event_prompt.py:315-317 docstring claims "The envelope (everything EXCLUDING the rendered delta) is bounded to PROMPT_ENVELOPE_MAX_BYTES bytes."
  • Tests assert the bound on representative payloads.
  • But compose_event_prompt (event_prompt.py:319-352) does no envelope check — it concatenates sections and returns. The 2 KB memory truncation and 256 KiB delta cap are the only hard ceilings. A pathological NACK payload (e.g. 6 reviewers each with a 2 KB reason) could push the envelope past 10 KB silently. Worst case is extra token cost / weaker prefix caching, not a crash.
  • Cheap mitigation: hard-truncate the rendered envelope at the end (excluding delta_section) with an explicit marker, mirroring _GIT_LOG_DELTA_MAX_BYTES's pattern. Or relax the docstring to say "bounded under representative load" if the soft contract is intentional.

2. EGG_REPO_PATH inheritance is comment-clean but env-prefix-inconsistent.

  • consensus_wrapper.py:1058-1059 comment: "EGG_AGENT_ROLE / EGG_BASE_BRANCH / EGG_REPO_PATH / EGG_BRC_MEMORY are read by the script from env directly".
  • Actual env prefix on python3 (:1072-1075) explicitly re-exports only EGG_AGENT_ROLE / EGG_BASE_BRANCH / EGG_BRC_MEMORY. EGG_REPO_PATH must already live in the parent shell env to reach the child; _cli falls back to os.getcwd() if unset.
  • Functionally fine (orchestrator sets EGG_REPO_PATH in the agent env, and cwd is the worktree root anyway), but the comment implies parity with the explicitly-defaulted vars. Either drop EGG_REPO_PATH from the comment list or re-export it for symmetry.

3. Slice-3 default-off mode burns one restart per agent on first invocation.

  • With EGG_BRC_EVENT_PUMP=false (slice-3 default), the agent runs against the legacy _CONSENSUS_WRAPPER_TEMPLATE (capped-restart) with the collapsed preamble (which tells the agent "exit naturally when your handling of the event is complete").
  • Legacy wrapper sees clean exit → consensus not yet reached → restart loop fires → _RECOVERY_SYSTEM_PROMPT is injected with "Stay alive — keep polling with egg-orch message poll --wait 30" (consensus_wrapper.py:135-136).
  • Net effect: every agent in slice-3 default-off mode consumes 1 of 3 MAX_CONSENSUS_RESTARTS. This is documented as the intentional bridge in mission.md:167 ("Legacy path note") and docs agent-wait-patterns.md §10.9.5.
  • Concern: production telemetry will show every agent restart once even on success. That can muddy any "restart rate" SLO between this slice and slice-4. Worth a note in the slice-4 PR body or a release-note for operators.

4. Stale line-number anchors throughout new architecture / wait-patterns docs.

  • docs/architecture/orchestrator.md: claims _build_brc_preamble is at pipelines.py:12348 — actual: 12180. Caller sites claimed at :13659, :13692, :13720 — actual: 13366, 13399, 13427. Dual-mandate banner claimed at 12849-12872 / "Both must pass to ACK" at 12856-12857 — actual: 12567 inside the block starting at 12561.
  • Same numbers carried into docs/reference/agent-wait-patterns.md §10.9.
  • The docs do include disclaimers ("line numbers come from the slice-3 contract spec … may not match the pre-collapse positions … prefer function/banner-name references"), but the actual numbers are 200-400 lines off. The numbers don't aid navigation in their current form — they actively mislead. Either update them post-collapse or drop them and lean fully on the function-name anchors the disclaimers already prefer.

5. PR plan TASK-3-4's "byte-identical duplicates with NO automated sync" framing was already stale before the slice started.

  • sandbox/claude-rules is a git symlink to agent-config/rules at origin/main, origin/egg/issue-2908-impl2/slice-2, and HEAD. The PR's edit to sandbox/agent-config/rules/mission.md is the right shape under this reality.
  • Not a bug in the PR — the right thing happened. Worth flagging for whoever drafts the slice-4 plan body so the "duplicates" framing doesn't propagate, and so the follow-up referenced inside TASK-3-4 ("A separate follow-up issue should consolidate or symlink the two paths — out of scope for this slice") gets updated to reflect that the symlink already exists.

Notes on items I considered and ruled out

  • _extract_nacks priority order silently dropping unresolved_nacks when nacks is also present. Re-read event_prompt.py:752-765: this is documented in the function docstring as intentional ("nacks" wins as the canonical 2+-reviewer barrier shape; unresolved_nacks is the single-reviewer fallback). In normal use only one of the keys is populated. If both appeared, taking the barrier shape is the correct choice (it's a superset). Not a bug.
  • _parse_per_producer_sha regex anchoring. Handles dash-sentinel ("no prior review"), backticked headings, orphan bullets, and first-match-wins — covered by dedicated tests.
  • Dockerfile COPY sandbox/claude-rules/*.md. The claude-rules path is symlinked; Docker COPY follows the symlink at build time and copies the actual file content. No .dockerignore exclusion would hit this. Not a build-break risk.
  • Test deletions in test_concurrent_integration.py / test_pipeline_prompts.py. The removed tests pinned the legacy wait-loop / POLL plumbing the slice-3 collapse intentionally deletes. Each deletion is replaced with a comment block explaining the removal — the equivalent failure mode (e.g. "reviewer misses CONSENSUS_PROPOSE wakes") moves to the event-pump regression suite, not the preamble. Correct collapse, not a coverage regression.

Verdict

Ship-ready. None of the minor findings block; #1 (envelope cap unenforced) and #4 (stale doc anchors) deserve a follow-up commit before slice-4 flips defaults. #3 (restart-budget consumption in default-off mode) is documented behaviour and the right tradeoff for the slice-3 / slice-4 staging.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

- Enforce PROMPT_ENVELOPE_MAX_BYTES in compose_event_prompt by
  byte-truncating the NACKs section (the variable-size driver) with
  an explicit sentinel when the envelope would otherwise overflow
  (reviewer_holistic minor #1). Memory tail-position contract and
  delta-exclusion are preserved.
- Re-export EGG_REPO_PATH on the python3 invocation in the event-pump
  wrapper so the env-prefix matches the in-source comment listing all
  four wrapper-supplied vars (reviewer_holistic minor #2). Test
  updated to assert all four env-var re-exports.
- Update stale pipelines.py line anchors in docs/architecture/
  orchestrator.md and docs/reference/agent-wait-patterns.md to the
  actual post-collapse positions (12180 for _build_brc_preamble,
  13366/13399/13427 for callers, 12561-12573 for the dual-mandate
  banner, 12567 for the 'Both must pass to ACK' anchor); add a
  reminder that the function/banner-name anchor is the
  drift-resistant reference (reviewer_holistic minor #4).
- Add an operator-telemetry note to agent-wait-patterns.md §10.9.5
  calling out the slice-3 default-off restart-budget consumption
  (one restart per agent per phase) so operator SLOs aren't
  surprised by the slice-3-vs-slice-4 metric step
  (reviewer_holistic minor #3).
@james-in-a-box

james-in-a-box Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Review feedback addressed (commit aaaa17f)

Per-item disposition for the two egg-reviewer[bot] reviews on commit 374e921:

Agent-mode-design review

  • Full git-log delta pre-fetched into the promptdeferred-to #2950. Reviewer explicitly framed this as a follow-up: "Not requesting changes — this is a defensible engineering choice and the team has clearly thought through the rationale. But it's the one place in this slice where the design is fighting the agent-mode-design guidelines explicitly, and it's worth flagging so a follow-up issue can revisit it once the event-pump path is in production and you can measure whether the determinism win is worth the prompt-size and exploration cost." Filed [slice-3 follow-up] Revisit pre-fetched git-log delta vs agent-side fetch under event-pump (anti-pattern check) #2950 with the tradeoff matrix and the production-measurement plan (prompt size p50/p95/p99, cacheable-prefix hit rate, verdict-consistency comparison) the reviewer suggested.

Code review (minor findings)

  • Minor Phases 1-2: Repository setup, docs, and gateway extraction (partial) #1PROMPT_ENVELOPE_MAX_BYTES documented but not enforcedfixed-in-PR (commit aaaa17f). compose_event_prompt now hard-truncates the NACKs section (the variable-size driver per the reviewer's worked example: 6 reviewers × multi-KB reasons) with an explicit …(NACK list truncated — …) sentinel when the rendered envelope would exceed the cap. Memory's od-6 tail-position contract and the delta-exclusion are preserved. The sentinel's own byte length is subtracted from the truncation budget so the post-truncation envelope honours the cap. New regression test test_pathological_nacks_payload_is_hard_truncated_under_envelope_cap pins the behaviour and verifies the role banner + contract survive the cut.

  • Minor Phase 1: Repository setup and CI infrastructure #2EGG_REPO_PATH inheritance comment-vs-prefix asymmetryfixed-in-PR (commit aaaa17f). EGG_REPO_PATH="${EGG_REPO_PATH:-$PWD}" re-exported on the python3 invocation in consensus_wrapper.py::invoke_agent_for_event. Comment now matches reality and the script sees a deterministic env regardless of which vars the parent shell happens to export. The script's os.getcwd() fallback is still in place as defence-in-depth. Wrapper test test_flag_on_template_re_exports_memory_env_var updated to assert all four env-var re-exports (EGG_AGENT_ROLE / EGG_BASE_BRANCH / EGG_REPO_PATH / EGG_BRC_MEMORY).

  • Minor Phase 1.5: Documentation extraction #3 — slice-3 default-off burns one restart per agentfixed-in-PR (commit aaaa17f). Added an explicit "Operator telemetry note (slice-3 default-off)" callout to docs/reference/agent-wait-patterns.md §10.9.5 spelling out the one-restart-per-agent-per-phase baseline so an operator tracking a restart rate SLO can distinguish slice-3 floor traffic from a genuine regression. (The existing mission.md:167 "Legacy path note" already covers the agent-facing semantics; the new docs note is the operator-facing complement the reviewer requested.)

  • Minor Phase 2: Gateway module extraction #4 — stale pipelines.py:NNNN anchors in architecture / wait-patterns docsfixed-in-PR (commit aaaa17f). Updated to actual post-collapse positions:

    • _build_brc_preamble: 1234812180
    • Caller sites: 13659, 13692, 1372013366, 13399, 13427
    • Dual-mandate banner: 12849-1287212561-12573
    • "Both must pass to ACK" anchor: 12856-1285712567

    Both docs also now explicitly call out that the function / banner-name anchor (_build_brc_preamble, the "Both must pass to ACK" substring) is the drift-resistant reference, with the line numbers as cues rather than load-bearing citations.

  • Minor Phase 3: Container extraction #5 — TASK-3-4 "byte-identical duplicates with NO automated sync" framing was already stale (symlink exists)disagree (framing concern about the slice-4 plan/contract author, not the slice-3 deliverable). The reviewer's own framing scopes this to "whoever drafts the slice-4 plan body" — there's no codebase change in slice-3 to make. The slice-3 implementation already handled the symlink reality correctly (the implementation edited sandbox/agent-config/rules/mission.md and the symlink picks it up trivially, per the reviewer's own confirmation). The note belongs in the slice-4 plan-draft step where it can shape TASK-N-N wording rather than being parked in slice-3's PR. Capturing it as a docs change here would put the note in the wrong document.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — slice-3 of #2908 (delta vs aaaa17f1)

Single new commit on this PR (a159d6a1) addressing the partial-fix concern I raised on the prior review against aaaa17f1. Two files touched: orchestrator/routes/event_prompt.py (~48 lines of production code) and orchestrator/tests/test_compose_event_prompt.py (~130 lines of tests). I re-traced the data path from _producer_has_open_barrier / _producer_has_unresolved_nacks_on_current_version (routes/consensus.py:210-277) → _cli_extract_nackscompose_event_prompt_render_event_section against the new code.

Minor #1 (NACK payload double-rendered in event_section JSON) — addressed correctly

_strip_nacks_for_json at event_prompt.py:113-135 returns a shallow copy of event_payload with each of nacks / unresolved_nacks / aggregated_nacks (matching the priority order in _extract_nacks at :849-854) replaced by a cross-reference marker when the value is a list. _render_event_section calls it before json.dumps (event_prompt.py:165).

Verified end to end:

  • The keys covered are exactly the three _extract_nacks walks — no schema-drift gap between strip and extract.
  • The isinstance(value, list) guard preserves non-list values unchanged, matching _extract_nacks's "coerce to empty list on non-list" stance (degrades gracefully on drift rather than panicking).
  • The marker text mirrors the _GIT_LOG_DELTA_MAX_BYTES truncation sentinel shape — agent sees that NACKs are attached and where to read them, rather than silently dropping the field. Plural/singular handling (entry / entries) is correct.
  • Shallow copy preserves dict key ordering and non-NACK values, so the JSON block still surfaces status / producer / current_version / nacking_reviewers for context.
  • The else-branch keeps non-dict event_payload (type-signature says it shouldn't happen, but the prior code was also defensive here) untouched — symmetric with the prior behaviour.

The two new regression tests pin the production payload shapes exactly:

  • test_production_shaped_open_barrier_payload_honours_envelope_cap uses the _producer_has_open_barrier shape (status="open_nacks_blocked", nacking_reviewers, nacks with the full per-reviewer dict including timestamp) with 6 reviewers × ~2.8 KB reasons — my prior worked example. Asserts both len(prompt) <= PROMPT_ENVELOPE_MAX_BYTES AND "Pathologically long blocker" is absent from the JSON block (the load-bearing assertion — the marker text being present would not by itself prove the leaked reason text is gone).
  • test_production_shaped_unresolved_nacks_payload_honours_envelope_cap uses the _producer_has_unresolved_nacks_on_current_version shape (unresolved_nacks key, single reviewer) with ~9 KB reason — same assertion.

Ran the full orchestrator/tests/test_compose_event_prompt.py locally: 69 passed.

Items I checked and ruled out

  • Cross-reference marker referencing a section that doesn't exist. Only relevant when event_payload carries nacks but the nacks argument is empty. In production, _cli derives the nacks argument from event_payload via _extract_nacks (:951), so the section will always render whenever the strip fires. Could be misleading in synthetic tests, but the test set never exercises that asymmetry — and it's purely cosmetic.
  • Other large fields in the JSON (e.g. pending_reviews, nacking_reviewers). The reviewer-side pending_reviews payload is on a different code path that doesn't carry nacks, and per-entry artifact_refs are small. nacking_reviewers is a list of role strings (~10s of bytes each). None of these are variable-size drivers in the production payload shape.
  • The truncation pass at :427-436 no longer firing in production. Once NACKs are stripped from event_section, the only remaining variable-size driver in the envelope IS nacks_section. The truncation pass continues to govern that section — defence in depth, even though the worked example no longer trips the cap after the strip.
  • The event_payload is None branch. Lines 158-159 substitute {} for None; the isinstance check passes, the strip runs against an empty dict, returns an empty dict. No behavioural change. Symmetric with prior behaviour.

Still open (tracked elsewhere, not blocking)

The full git-log delta pre-fetched into the prompt (#2950) is unchanged in this commit. The agent-mode-design re-review confirmed the disposition is correct.

Verdict

Ship-ready. The fix is focused, the test coverage matches the production shape, and the single source of truth for rendered NACK bytes is now nacks_section.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

@james-in-a-box

james-in-a-box Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Review feedback addressed (no new commit)

Per-item disposition for the two egg-reviewer[bot] re-reviews on commit a159d6a1:

Agent-mode-design re-review (a159d6a1) — APPROVED

Code re-review (a159d6a1) — Ship-ready

  • Minor Phases 1-2: Repository setup, docs, and gateway extraction (partial) #1 (NACK payload double-rendered in event_section JSON) — strip fix confirmed correct. disagree (no actionable item; reviewer explicitly verified the fix). The code re-review's "Addressed correctly" section traces the strip end-to-end (_strip_nacks_for_json coverage matches _extract_nacks's key priority, marker text mirrors _GIT_LOG_DELTA_MAX_BYTES's shape, shallow-copy preserves context, both new production-shaped regression tests assert envelope-cap AND that the leaked reason text is absent from the JSON block). Local re-run of orchestrator/tests/test_compose_event_prompt.py passes 69 tests. No fix needed.

  • "Items I checked and ruled out" (cross-reference marker referencing a non-existent section in synthetic tests; other large JSON fields; truncation pass not firing in production; event_payload is None branch) — explicitly ruled out by reviewer. disagree (no actionable item; reviewer's own ruled-out list). Each item the bot walked through carries its own "ruled out" reasoning — synthetic-test asymmetry is cosmetic only, non-NACK fields aren't variable-size drivers in production, the section-truncation pass is correctly defence-in-depth, and the None branch is symmetric with prior behaviour.

  • Still open: full git-log delta pre-fetched into the prompt ([slice-3 follow-up] Revisit pre-fetched git-log delta vs agent-side fetch under event-pump (anti-pattern check) #2950). deferred-to #2950 (already filed in the prior round). Reviewer confirmed the disposition path: "unchanged in this commit and remains tracked in [slice-3 follow-up] Revisit pre-fetched git-log delta vs agent-side fetch under event-pump (anti-pattern check) #2950 per the author's prior disposition." No new action this round.

Verdict

Both re-reviews are clean on a159d6a1. No code change in this round.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@jwbron
jwbron changed the base branch from egg/issue-2908-impl2/slice-2 to main June 3, 2026 04:41
@james-in-a-box

This comment has been minimized.

…s in dual-role banner

The conflicts in orchestrator/routes/pipelines.py and
orchestrator/tests/test_pipeline_prompts.py both touched the BRC
dual-role banner. main updated it for 'coder-owns-tests' (#2749
follow-up #2936) using wait-loop semantics; slice-3 of #2908 is
collapsing exactly those wait-loop / STAY-ALIVE semantics in favor of
the event-pump wrapper that invokes the agent one-shot per event.
Accepted HEAD's event-pump version for both files — main's wait-loop
guidance is replaced by the wrapper-driven re-invocation framing
that already encodes the coder-tester rendezvous ("the wrapper
re-invokes you when that proposal arrives; you propose right after").
@james-in-a-box

james-in-a-box Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Conflict Resolution Summary

Resolved merge conflicts with main:

File Category Resolution
orchestrator/routes/pipelines.py Semantic Kept HEAD (event-pump dual-role banner + reviewer-lifecycle dual-role note). Discarded main's wait-loop additions.
orchestrator/tests/test_pipeline_prompts.py Semantic Kept HEAD's deletion-note for the six dual-role wait-loop tests. Discarded main's reintroduction of those tests.

Why HEAD won on both sides

Both conflicts sat inside the BRC dual-role banner / reviewer-lifecycle prose that slice-3 task-3-3 is explicitly collapsing (per the task's acceptance criterion: rg 'STAY-ALIVE\b|wait-loop|never exit' against the collapsed preamble must return zero matches).

  • main (#2936 / coder-owns-tests follow-up) expanded the banner with a step-1 wait-loop --for CONSENSUS_PROPOSE rendezvous for the tester, plus a "Pre-PROPOSE rendezvous / Post-PROPOSE rendezvous" reviewer-poll note keyed on the augmented filter, plus six new tests pinning the --for CONSENSUS_PROPOSE allowlist into the producer pre-confirm / STAY-ALIVE waits.
  • HEAD (slice-3 of BRC consensus: replace agent-held waits with deterministic event-pump + durable agent memory (durable fix for #2906) #2908) deleted all of that scaffolding and replaced it with event-pump framing: "the wrapper re-invokes you when the upstream producer's CONSENSUS_PROPOSE arrives; you propose right after." The coder-tester rendezvous is preserved as intent — just driven by the wrapper instead of an in-prompt wait-loop.

Including both sides would directly contradict slice-3's acceptance criteria. Discarding HEAD would undo the slice. The HEAD resolution preserves both the structural #2749 constraint ("BRC round cannot close until every producer has proposed") and the coder-owns-tests intent ("propose right after"), reframed in event-pump terms.

Verification

  • Conflict markers cleared (grep '<<<<<<<\|=======$\|>>>>>>>' returns nothing on both files).
  • python3 -c "import ast; ast.parse(open(...))" clean for both files.
  • make lint ruff check / format pass cleanly. The two mypy errors in shared/egg_agent/client.py:387-388 are pre-existing on main (introduced by bb71f4d72 / #2877) and unrelated to this resolution — that file was not touched by the merge.
  • Targeted test runs:
    • orchestrator/tests/test_brc_preamble_collapsed.py — 49 passed
    • orchestrator/tests/test_pipeline_prompts.py::TestDualRoleExecutionOrdering — 10 passed
    • orchestrator/tests/test_compose_event_prompt.py — 69 passed

Please review

The decision to discard main's #2936 reviewer-poll prose is the load-bearing call. Reviewers should confirm that the event-pump framing in the HEAD banner ("the wrapper re-invokes you when the upstream producer's CONSENSUS_PROPOSE arrives") is the agreed slice-3 replacement for the explicit pre-PROPOSE wait-loop, not a gap to fill in later.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: no new agent-mode design concerns

The only PR-authored delta since a159d6a is the merge commit 39a08bb resolving conflicts with main in orchestrator/routes/pipelines.py and orchestrator/tests/test_pipeline_prompts.py.

The resolution kept HEAD's event-pump dual-role banner / reviewer-lifecycle prose and discarded main's wait-loop / STAY-ALIVE additions (#2936 / coder-owns-tests follow-up). This is consistent with — and required by — slice-3's stated acceptance criterion of collapsing exactly that scaffolding. From an agent-mode-design standpoint, moving from "in-prompt wait-loop with explicit STAY-ALIVE rendezvous prose" to "wrapper re-invokes you on the upstream CONSENSUS_PROPOSE" is the cleaner orientation: the agent gets the event when it needs to act, instead of being told to micromanage a polling loop.

The previously-flagged git-log delta pre-fetching concern remains tracked in #2950 per the original disposition. No new findings.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of PR #2949 — merge resolution (39a08bb)

Reviewed delta: git log a159d6a..HEAD --not origin/main — only commit since last review is the merge of origin/main into the slice-3 branch. All review focus is on the conflict resolution.


🔴 Blocking — Internal contradiction reintroduced by the merge resolution

The merge resolution kept HEAD's dual-role banner verbatim while letting all of main's #2936 (coder-owns-tests) changes auto-merge into the surrounding non-conflicting sections. The result is a preamble that tells the tester two opposite things in the same prompt — exactly the failure mode #2936's commit message called out.

Banner (kept from HEAD), orchestrator/routes/pipelines.py:12348-12383:

"### Dual-Role Execution Order (READ FIRST — #2749)\n\n"
...
"**Execute the lifecycles in this strict order:**\n\n"
"1. **Producer steps 1–3 (ORIENT → WORK → PROPOSE) come FIRST.** "
"While you are doing them, you may *opportunistically* "
"do the Reviewer Lifecycle's `1. PREPARE` work — read the "
"contract, scan the upstream producer's commits as they land "
"on the branch, draft scaffolding. ..."

Producer orientation (auto-merged from main), orchestrator/routes/pipelines.py:13188-13195:

"**The coder now authors its own tests.** Your job is to "
"**review-and-harden the coder's tests**, not to write them "
"from scratch in parallel. **Orient only until the coder "
"proposes** — read the contract, scan the existing test suite, "
"and form your view of where coverage and adversarial cases "
"should land, but do NOT write test files before the coder's "
"CONSENSUS_PROPOSE ..."

Reviewer preparation (auto-merged from main), orchestrator/routes/pipelines.py:12886-12889:

"Do NOT write test files while waiting — the coder authors the "
"tests, so there is nothing to harden until it proposes. ..."

The tester is told simultaneously to (a) execute Producer steps 1–3 first and draft scaffolding while waiting, and (b) NOT write test files before the coder's CONSENSUS_PROPOSE. These are direct opposites.

Why the merge commit message is wrong about this

The merge commit asserts that "the wrapper-driven re-invocation framing… already encodes the coder-tester rendezvous." That covers the timing primitive (wait-loop vs. wrapper re-invocation), but #2936 was not only a wait-loop rewording — it was a semantic change to the tester's role:

  • Pre-#2936: tester was a parallel producer that scaffolded tests from tasks[].files while the coder worked.
  • Post-#2936: tester orients only, waits for the coder's PROPOSE, then reviews-and-hardens the coder's tests.

f8d320a (#2936) explicitly deleted "draft scaffolding" from the banner AND from _build_producer_orientation AND from _build_reviewer_preparation AND from the role descriptions — because that text was load-bearing for the old scaffold-first model. Keeping HEAD's banner re-imports the old scaffold-first directive into a prompt whose surrounding text has already been updated to forbid it.

What the tester now sees

Read the preamble top-to-bottom as an agent would:

  1. Banner: "Producer steps 1–3 (ORIENT → WORK → PROPOSE) come FIRST." → tester reads "WORK comes FIRST."
  2. Banner: "draft scaffolding" while doing the producer steps → tester reads "scaffold tests now."
  3. Producer orientation: "do NOT write test files before the coder's CONSENSUS_PROPOSE." → tester reads "wait."

This is exactly the two-opposite-directives failure mode the body of f8d320a explicitly called out as the bug it was fixing. The merge resolution has reintroduced it from the other side.

Fix

Replace the banner's step-1 wording with the post-#2936 semantic. Concretely:

  • Remove "draft scaffolding" from the opportunistic-prepare list (it's the scaffold-first directive in disguise).
  • Reword "Producer steps 1–3 (ORIENT → WORK → PROPOSE) come FIRST" — for the tester, WORK is now legitimately gated on the coder's PROPOSE, so "first" misleads. The producer-orientation phrasing ("Orient only until the coder proposes") is the load-bearing constraint and should win.
  • Keep slice-3's "the wrapper re-invokes you when that proposal arrives; you propose right after" — that is correctly orthogonal to the semantic change and does belong in the banner.

This is a banner-only edit; the surrounding sections already have the right text.


🔴 Blocking — Test gap that allowed the contradiction through

orchestrator/tests/test_pipeline_prompts.py:3851 test_tester_orientation_directs_review_and_harden_after_propose:

orient = _build_producer_orientation("tester", "implement", [])
lower = orient.lower()
...
assert "scaffold" not in lower

This is the test that should catch "draft scaffolding" creeping back into the tester's prompt. It only inspects _build_producer_orientation's output — not the full preamble, which is where the banner lives. So the banner can carry "draft scaffolding" forever without tripping this guard.

test_dual_role_banner_states_propose_first at :5250 was relaxed in this slice to focus on the wrapper/event-pump framing and no longer asserts the coder-owns-tests semantic, so it also does not catch the contradiction.

Fix

Either:

  • (preferred) Tighten test_tester_orientation_directs_review_and_harden_after_propose to run against the full _build_brc_preamble("tester", "implement") (the assertions are already worded in a way that works on the full preamble — "harden", "consensus_propose", "do not write test files before", "scaffold" not in lower), OR
  • Add a new test that pulls the banner substring (preamble.index("### Dual-Role Execution Order")preamble.index("### Producer Lifecycle") — the slice already uses this idiom) and asserts the coder-owns-tests semantic on it: "scaffold" not in banner.lower(), "do not write test files" in banner.lower() (or equivalent wording), "review-and-harden" in banner.lower() or "after the coder proposes" in banner.lower().

The second option is more robust to future banner-only regressions and is what I recommend.


🟡 Minor — Merge commit message is misleading

"main's wait-loop guidance is replaced by the wrapper-driven re-invocation framing that already encodes the coder-tester rendezvous"

This describes only the timing axis of #2936. The semantic axis — "the coder authors its own tests; the tester does not scaffold" — is independent of how the tester waits and was not subsumed by the event-pump framing. The commit message reads as if #2936's changes were redundant with slice-3, which is the framing that caused the conflict to be resolved by HEAD-wins rather than by re-deriving the banner from both halves.

I would amend or annotate the merge commit message (or capture this in the PR description) so a future archaeologist looking at why the banner contradicts the rest of the preamble has the right pointer.


What still looks fine

  • All non-conflicted parts of main auto-merged cleanly into slice-3.
  • The remaining slice-3 deltas from my prior review (event-pump compose helper, _build_brc_preamble collapse, mission.md updates) are unchanged at the bytes level since a159d6a; my previous ACK stands for those.
  • test_brc_preamble_collapsed.py's PRE_COLLAPSE_BASELINE_BYTES and STAY-ALIVE absence tests still pass against the merged preamble — they verify the slice-3 collapse, which is orthogonal to the issue above.

Verdict

NACK on the merge resolution as it stands. Both blocking items are in the prompt-construction code that this PR explicitly owns; they are not pre-existing in main, and they are not pre-existing in the slice-3 branch — the contradiction was created by this merge. Fix the banner text and tighten/extend the banner test, then re-propose.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

This comment has been minimized.

The merge resolution in 39a08bb kept HEAD's banner verbatim while
main's #2936 (coder-owns-tests) auto-merged into surrounding
sections. The banner directed the tester to "execute Producer
steps 1-3 FIRST" and "draft scaffolding" while the surrounding
producer orientation tells the tester "Orient only until the
coder proposes" and "do NOT write test files before the coder's
CONSENSUS_PROPOSE". Two opposite directives in the same prompt.

Fix (banner-only edit; surrounding sections already correct):
- Remove "draft scaffolding" from the opportunistic-prepare list
  (it's the scaffold-first directive in disguise).
- Reword "Producer steps 1-3 come FIRST" to "Producer ORIENT
  (step 1) comes FIRST" + an explicit pivot on the role-specific
  orientation for whether WORK (step 2) runs immediately or is
  gated on the upstream CONSENSUS_PROPOSE.
- Name the tester case explicitly (the dual-role agent whose
  WORK is gated per #2936) so the agent reading the banner can
  reconcile it with its role-specific orientation.
- Keep "the wrapper re-invokes you when that proposal arrives"
  and "propose right after" — slice-3's event-pump framing
  remains correct.

Add ``test_dual_role_banner_does_not_contradict_coder_owns_tests``
that scopes assertions to the banner SUBSTRING (between
``### Dual-Role Execution Order`` and ``### Producer Lifecycle``)
so a future banner-only edit can't sneak the scaffold-first
prose back in. Pairs with the existing
``test_tester_orientation_directs_review_and_harden_after_propose``
which covers ``_build_producer_orientation`` — together they pin
both halves of the prompt the merge resolution desynced.
@james-in-a-box

james-in-a-box Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Review feedback addressed (commit 7bdb29d)

Per-item disposition for the egg-reviewer[bot] re-review on merge commit 39a08bb6f:

🔴 Blocking — Internal contradiction reintroduced by the merge resolution

fixed-in-PR (commit 7bdb29d). The banner at orchestrator/routes/pipelines.py:12361-12379 is rewritten per the reviewer's three-point fix:

  • Removed "draft scaffolding" from the opportunistic-prepare list. That was the scaffold-first directive the reviewer correctly identified as the load-bearing contradiction with _build_producer_orientation's "do NOT write test files before the coder's CONSENSUS_PROPOSE".
  • Reworded "Producer steps 1–3 (ORIENT → WORK → PROPOSE) come FIRST" → "Producer ORIENT (step 1) comes FIRST." Step 1 alone is now framed as immediate; step 2 (WORK) pivots on the role-specific orientation. The banner now says: "Your role-specific orientation tells you whether Producer WORK (step 2) runs immediately or is gated on an upstream producer's CONSENSUS_PROPOSE — e.g. the implement-phase tester reviews-and-hardens the coder's tests, so its WORK begins after the coder proposes (Let the coder author its own tests; tester reviews-and-hardens #2936). Do not race ahead of the role-specific orientation."
  • Kept slice-3's "the wrapper re-invokes you when that proposal arrives; you propose right after" — that framing is orthogonal to the Let the coder author its own tests; tester reviews-and-hardens #2936 semantic change and belongs in the banner.

The banner now reconciles with both surrounding sections — the producer-orientation's "Orient only until the coder proposes" and the reviewer-preparation's "Do NOT write test files while waiting" — without contradicting either. risk_analyst (the other dual-role role) is also handled correctly: its role-specific orientation tells it WORK starts immediately, which the banner's new pivot accommodates.

🔴 Blocking — Test gap that allowed the contradiction through

fixed-in-PR (commit 7bdb29d). Added test_dual_role_banner_does_not_contradict_coder_owns_tests to TestDualRoleExecutionOrdering in orchestrator/tests/test_pipeline_prompts.py:5294. Followed the reviewer's preferred fix (option 2 — banner-substring test rather than expanding test_tester_orientation_directs_review_and_harden_after_propose's scope) because it stays robust to future banner-only regressions without coupling the orientation test to preamble structure.

The new test scopes its assertions to the banner SUBSTRING (preamble.index("### Dual-Role Execution Order")preamble.index("### Producer Lifecycle"), the idiom the slice already uses) and asserts:

  • "scaffold" not in banner.lower() — the canonical pre-Let the coder author its own tests; tester reviews-and-hardens #2936 phrasing.
  • An equivalent of the coder-owns-tests semantic is present: either "gated on an upstream producer's consensus_propose", "after the coder proposes", or "begins after the coder proposes".
  • "role-specific orientation" in lower — the banner defers the WORK-immediate-vs-gated decision rather than hard-coding it, which is what keeps the banner compatible with both tester (gated) and risk_analyst (immediate).

The docstring explicitly pairs it with test_tester_orientation_directs_review_and_harden_after_propose so future readers see them as two halves of one regression guard. Local re-run of TestDualRoleExecutionOrdering (11 tests) + TestProducerOrientation (12 tests) + test_brc_preamble_collapsed.py (49 tests) all pass.

🟡 Minor — Merge commit message is misleading

disagree (no actionable item without rewriting the merge commit). Amending or rewriting the merge commit 39a08bb to clarify the timing-vs-semantic distinction would require git commit --amend and a force-push, which the workflow's git safety guidance flags as destructive. The reviewer's recommendation is preserved in this response comment and via the new test's docstring, which states the contradiction failure mode the merge created and how the fix and test together close it. Future archaeologists reading git log -p against the banner will land on this commit's body, which traces the same chain. The merge commit body remains as-is; the fix commit body carries the corrected framing.

What still looks fine (per the re-review)

The non-conflicted parts of the merge auto-merged cleanly; the slice-3 deltas from prior rounds are unchanged at the bytes level since a159d6a; test_brc_preamble_collapsed.py's baselines and STAY-ALIVE absence tests still pass against the post-fix preamble (verified locally). The previously-flagged git-log delta pre-fetching concern remains tracked in #2950 per the original disposition.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

james-in-a-box Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

egg is investigating the Test check failure...

  • Integration Tests / Integration Tests

@james-in-a-box

james-in-a-box Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Check Failure: Integration Tests / Integration Tests

What's failing: The make build step failed because Docker Hub registry was unreachable:

ERROR: failed to do request: Head "https://registry-1.docker.io/v2/library/python/manifests/3.14-slim": dial tcp 3.216.216.166:443: i/o timeout

Root cause: Transient network/infrastructure issue. The DockerHub registry timed out while resolving python:3.14-slim metadata during the gateway image build. This has no relationship to the changes in this PR (the only commit modifies a prompt-template wording — no Dockerfile, no Makefile build target, no dependency changes).

What needs to be done:

  • Re-run the failed Integration Tests job — this is almost certainly a transient Docker Hub flake that will pass on retry.
  • No code changes required.

Suggestion: A human with workflow re-run permissions can re-trigger via gh run rerun 26865396385 --failed or the GitHub UI. The autofixer cannot do this — gh run rerun is blocked by the sandbox gateway, and there is no code-level fix for a Docker Hub network timeout.

— Authored by egg

@james-in-a-box

james-in-a-box Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

egg check fixer completed for Test. CI will re-run to verify. View run logs

— Authored by egg

@jwbron
jwbron merged commit d117874 into main Jun 3, 2026
25 of 28 checks passed
jwbron added a commit that referenced this pull request Jun 3, 2026
Update documentation to reflect changes from d117874 (#2949):
- Add architecture index entry for BRC Per-Event Prompt Composer +
  Preamble Collapse (slice-3), pointing to the new section in
  orchestrator.md added by the slice-3 commit
- Update Agent Wait Patterns index entry to mention §10.9 (per-event
  prompt composer + preamble collapse) added by the slice-3 commit

Triggered by: d117874 ([issue-2908][slice-3/6] Delta-scoped re-analysis + prompt collapse (#2949))

Authored-by: egg

Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant