Skip to content

feat(auto): route Ralph oscillation_detected through UNSTUCK_LATERAL (L5-a)#1175

Merged
shaun0927 merged 2 commits into
Q00:mainfrom
shaun0927:feat/prL5a
May 22, 2026
Merged

feat(auto): route Ralph oscillation_detected through UNSTUCK_LATERAL (L5-a)#1175
shaun0927 merged 2 commits into
Q00:mainfrom
shaun0927:feat/prL5a

Conversation

@shaun0927
Copy link
Copy Markdown
Collaborator

Summary

L5-a slice of #1157's L5 lane — the minimal redesign of long-running resilience that plumbs the existing Ralph oscillation_detected signal into the existing ooo unstuck machinery instead of building a new escalation-ladder state machine on top.

When Ralph terminates with stop_reason == \"oscillation_detected\" in complete-product mode AND a lateral_thinker is wired on the pipeline, the auto pipeline now transitions to UNSTUCK_LATERAL and invokes _run_lateral (the lateral-persona advisor already used by the EVALUATE → UNSTUCK_LATERAL path for QA failures) before landing in BLOCKED. The persona output is persisted on AutoPipelineState and surfaced through AutoPipelineResult just like the QA-failure path.

Other Ralph _RALPH_BLOCKED_STOP_REASONS (iteration_timeout, wall_clock_exhausted, grade_regressing, max_generations reached) are budget-exhaustion terminals rather than spec-reframe candidates and continue to BLOCKED unchanged. Without a wired lateral_thinker, oscillation_detected also continues to BLOCKED unchanged — the L5-a path is purely additive.

Why this is small

Per the 2026-05-22 minimal-substrate audit (#1157), L5 was scoped down from a new escalation-ladder state machine + new oscillation detector + new budget unification substrate to plumbing the existing detection signal into the existing recovery substrate. This PR is ~25 LoC of pipeline code + 1 new line in the auto state machine + 3 unit tests + 1 pinning-test update.

What lands

  • src/ouroboros/auto/pipeline.py: at the _RALPH_BLOCKED_STOP_REASONS branch in _handoff_to_ralph, intercept oscillation_detected when self.lateral_thinker is not None and state.complete_product is true; transition to UNSTUCK_LATERAL and call _run_lateral with a synthetic QA-style payload carrying the oscillation marker.
  • src/ouroboros/auto/state.py: add AutoPhase.UNSTUCK_LATERAL to the RALPH_HANDOFF _ALLOWED_TRANSITIONS set.
  • tests/unit/auto/test_pipeline_oscillation_lateral.py (new): 3 tests pinning the new behaviour + regression guards.
  • tests/unit/auto/test_pipeline_ralph_handoff.py: extend the pinning test to include the new UNSTUCK_LATERAL successor.

What is NOT in this PR

Test plan

  • uv run pytest tests/unit/auto/test_pipeline_oscillation_lateral.py -v → 3 passed.
  • uv run pytest tests/unit/auto -q → 879 passed (876 baseline + 3 new).
  • uv run pytest tests/integration/auto -q → 36 passed.
  • uv run ruff check on touched files → clean.
  • uv run ruff format on touched files → clean.
  • uv run mypy src/ouroboros/auto/pipeline.py src/ouroboros/auto/state.py → clean.

Refs #1157 (L5 lane, minimal redesign), #1167 (L4 PR-B2 stop_reason taxonomy), RFC #809 Phase 2.2 (the EVALUATE → UNSTUCK_LATERAL path this PR mirrors).

…(L5-a)

L5-a slice of Q00#1157's L5 lane — the minimal redesign of *long-running
resilience* that plumbs the *existing* Ralph ``oscillation_detected``
signal into the *existing* ``ooo unstuck`` machinery instead of
building a new escalation-ladder state machine on top.

## Summary

When Ralph terminates with ``stop_reason == "oscillation_detected"``
in complete-product mode AND a ``lateral_thinker`` is wired on the
pipeline, the auto pipeline now transitions to ``UNSTUCK_LATERAL``
and invokes ``_run_lateral`` (the lateral-persona advisor already
used by the EVALUATE → UNSTUCK_LATERAL path for QA failures) before
landing in BLOCKED. The persona output is persisted on
``AutoPipelineState`` and surfaced through ``AutoPipelineResult``
just like the QA-failure path.

Other Ralph ``_RALPH_BLOCKED_STOP_REASONS`` (``iteration_timeout``,
``wall_clock_exhausted``, ``grade_regressing``,
``max_generations reached``) are budget-exhaustion terminals rather
than spec-reframe candidates and continue to BLOCKED unchanged.
Without a wired ``lateral_thinker``, ``oscillation_detected`` also
continues to BLOCKED unchanged — the L5-a path is purely additive.

## Why this is small

Per the 2026-05-22 minimal-substrate audit (Q00#1157), L5 was scoped
down from a new escalation-ladder state machine + new oscillation
detector + new budget unification substrate to **plumbing the
existing detection signal into the existing recovery substrate**.
This PR is ~25 LoC of pipeline code + 1 new line in the auto state
machine + 3 unit tests + 1 pinning-test update.

## What lands

- ``src/ouroboros/auto/pipeline.py``: at the
  ``_RALPH_BLOCKED_STOP_REASONS`` branch in ``_handoff_to_ralph``,
  intercept ``oscillation_detected`` when ``self.lateral_thinker is
  not None`` and ``state.complete_product`` is true; transition to
  ``UNSTUCK_LATERAL`` and call ``_run_lateral`` with a synthetic
  QA-style payload that carries the oscillation marker.
- ``src/ouroboros/auto/state.py``: add ``AutoPhase.UNSTUCK_LATERAL``
  to the ``RALPH_HANDOFF`` ``_ALLOWED_TRANSITIONS`` set so the new
  transition is permitted.
- ``tests/unit/auto/test_pipeline_oscillation_lateral.py`` (new): 3
  tests pinning the new behaviour + 2 regression guards.
- ``tests/unit/auto/test_pipeline_ralph_handoff.py``: extend the
  pinning test ``test_state_machine_allows_ralph_handoff_terminal_transitions``
  to include the new ``UNSTUCK_LATERAL`` successor.

## Test plan

- [x] ``uv run pytest tests/unit/auto/test_pipeline_oscillation_lateral.py
      -v`` → 3 passed.
- [x] ``uv run pytest tests/unit/auto -q`` → 879 passed (876 baseline
      + 3 new L5-a).
- [x] ``uv run pytest tests/integration/auto -q`` → 36 passed.
- [x] ``uv run ruff check`` on touched files → clean.
- [x] ``uv run ruff format`` on touched files → clean.
- [x] ``uv run mypy src/ouroboros/auto/pipeline.py
      src/ouroboros/auto/state.py`` → clean.

## What is NOT in this PR

- New escalation-ladder state machine (per Q00#1157 L5 minimal redesign,
  out of scope).
- New oscillation-detector substrate (per Q00#1157 L5 minimal redesign,
  Ralph's existing ``oscillation_detected`` is reused).
- New typed ``stop_reason_code = "unstuck_exhausted"`` for the case
  where lateral itself fails after multiple persona rotations —
  follow-up L5-b sub-PR (Q00#1157 L5 lane body).
- Budget unification with the L2 watchdog (Q00#1172) — separate lane.

## References

- Q00#1157 — Meta SSOT for ``ooo auto`` (L5 lane body, minimal
  redesign 2026-05-22).
- Q00#1167 — L4 PR-B2 ``safe_default`` closure mode that L5-a's
  blocker outcome conforms to.
- RFC Q00#809 Phase 2.2 — the EVALUATE → UNSTUCK_LATERAL path that
  this PR mirrors for the Ralph oscillation case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

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

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Branch: feat/prL5a | 4 files, +326/-4 | CI: Ruff Lint pass 15s https://github.com/Q00/ouroboros/actions/runs/26288860565/job/77383393900
Scope: diff-only
HEAD checked: 3806b998de07151186d51375da61a4743af585b2

What Improved

  • The live _handoff_to_ralph path now routes oscillation_detected through UNSTUCK_LATERAL when complete_product and lateral_thinker are wired, and the state machine permits RALPH_HANDOFF -> UNSTUCK_LATERAL.
  • Added focused tests for the live oscillation path, no-lateral fallback, budget-terminal fallback, and the updated transition set.

Issue #1157 Requirements

Requirement Status
L5-a: plumb Ralph’s existing oscillation_detected signal into existing ooo unstuck / UNSTUCK_LATERAL before bailing. NOT MET — live path does this at src/ouroboros/auto/pipeline.py:1360, but resume/re-attach consumers still block directly at src/ouroboros/auto/pipeline.py:2390 and src/ouroboros/auto/pipeline.py:2520.
Preserve other Ralph blocked stop reasons as direct BLOCKED terminals. MET — tests cover iteration_timeout direct blocking in tests/unit/auto/test_pipeline_oscillation_lateral.py:229.
Avoid new escalation-ladder state machine or new oscillation detector substrate. MET — current diff reuses UNSTUCK_LATERAL and _RALPH_BLOCKED_STOP_REASONS; no new detector/state-machine substrate is added.
L5-b typed unstuck_exhausted terminal is out of scope. MET — no new stop reason is introduced in this PR.

Prior Findings Status

Prior Finding Status
Previous bot review reported no blockers but also stated it could not run local read/git commands due sandbox failure. WITHDRAWN — not a verified prior blocker or approval basis; current review re-checked the supplied HEAD/diff artifacts and current files.

Blockers

# File:Line Severity Confidence Finding
1 src/ouroboros/auto/pipeline.py:2390 High 90% The Ralph resume consumer still maps terminal_status == "failed" plus stop_reason == "oscillation_detected" directly to BLOCKED for every _RALPH_BLOCKED_STOP_REASONS value, and the re-attach branch does the same at src/ouroboros/auto/pipeline.py:2520. The live path added at src/ouroboros/auto/pipeline.py:1360 routes the same Ralph terminal contract through UNSTUCK_LATERAL, so an auto session that is resumed or re-attached after Ralph has already reached oscillation_detected skips the L5-a unstuck recovery entirely. This breaks the changed producer/consumer contract for Ralph terminal handling and leaves a persistence/replay path unimplemented.

Follow-ups

# File:Line Priority Confidence Suggestion

Test Coverage

Targeted tests passed with VCS-version override: SETUPTOOLS_SCM_PRETEND_VERSION_FOR_OUROBOROS_AI=0.0.0 SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 uv run pytest tests/unit/auto/test_pipeline_oscillation_lateral.py tests/unit/auto/test_pipeline_ralph_handoff.py -q -> 44 passed. Without that override, uv run pytest ... failed before collection because the build backend timed out on the local git wrapper while resolving the VCS version. Coverage is not adequate for the PR’s new state/terminal mapping because the resume and re-attach Ralph consumers at src/ouroboros/auto/pipeline.py:2390 and src/ouroboros/auto/pipeline.py:2520 are not covered for oscillation_detected.

Design / Roadmap Gate

design_context.md frames L5-a as minimal existing-substrate plumbing: route Ralph oscillation_detected into existing ooo unstuck, with no new state machine or detector. The implementation aligns with that direction in the live path and state transition table, but it does not preserve the same Ralph terminal contract across persisted resume/re-attach boundaries, so the design gate is not satisfied for long-running resilience.

Merge Recommendation

  • Merge after the Ralph resume and re-attach terminal-status paths apply the same oscillation_detected -> UNSTUCK_LATERAL handling, with tests proving the persisted/replay cases.

ouroboros-agent[bot]

Per review feedback on PR Q00#1175, add a one-line breadcrumb at the two
sibling sites (``_resume_ralph_handoff`` and ``_reattach_ralph_job``)
where ``oscillation_detected`` still falls through to BLOCKED rather
than routing through ``UNSTUCK_LATERAL``. Documents that this is
intentional minimum-substrate scoping (lateral recovery on the
resume/re-attach branches is reserved for L5-b) and that
``tool_name="ralph_starter"`` keeps the session resumable via
``_recoverable_phase_for_tool``, so users are not stranded.

No behavior change.
@shaun0927
Copy link
Copy Markdown
Collaborator Author

PR Review (pr-review skill)

Verdict

APPROVE

Scope Reviewed

  • Intent: L5-a slice of Meta SSOT: ooo auto Vision — Autonomous Completion Engine #1157 — plumb the existing Ralph oscillation_detected stop reason into the existing UNSTUCK_LATERAL / _run_lateral recovery substrate, so an oscillation terminal triggers persona reframing before BLOCKED. Explicitly not a new state machine, not a new detector — minimum-substrate per the Meta SSOT: ooo auto Vision — Autonomous Completion Engine #1157 minimal redesign.
  • Main changed areas:
    • src/ouroboros/auto/pipeline.py _handoff_to_ralph (line 1350): intercept terminal_status == "failed" + stop_reason == "oscillation_detected" when self.lateral_thinker is not None and state.complete_product, transition to UNSTUCK_LATERAL and call _run_lateral with a synthetic QA payload. Two L5-b breadcrumb comments at the resume / re-attach sibling sites (lines 2390, 2520) document the intentional gap.
    • src/ouroboros/auto/state.py: add AutoPhase.UNSTUCK_LATERAL to _ALLOWED_TRANSITIONS[RALPH_HANDOFF].
    • tests/unit/auto/test_pipeline_oscillation_lateral.py (new, 3 tests): positive routing, no-lateral-wired regression, iteration_timeout budget-terminal regression.
    • tests/unit/auto/test_pipeline_ralph_handoff.py: extend pinning test to include the new UNSTUCK_LATERAL successor.
  • Tests reviewed: both new and pinning suites — 44/44 pass locally on 3806b998.
  • Checks considered: ruff lint, ruff format, mypy on touched files all clean locally; CI was fully green on 6e425ab; 3806b998 is docs-only.

Blocking Issues

None.

Warnings

None.

Mutation-Test Thinking

  • Mutants killed by current tests:
    • Flip self.lateral_thinker is not None → killed by test_ralph_oscillation_blocks_directly_when_no_lateral_thinker + positive test.
    • Flip stop_reason == "oscillation_detected" to a different reason → killed by test_ralph_iteration_timeout_does_not_invoke_lateral.
    • Drop the synthetic qa_differences marker → killed by the positive test's "oscillat" in differences_text.lower() assertion.
    • Remove the state.transition(AutoPhase.UNSTUCK_LATERAL, …) call → would raise TransitionError, pinned by test_state_machine_allows_ralph_handoff_terminal_transitions.
  • Potential survivor: flipping the state.complete_product gate is not regression-pinned in this file. Mitigated by the broader suite's test_complete_product_off_matches_legacy_shape and the EVALUATE→lateral suite. Acceptable — not blocking.

Complexity / CRAP-style Risk

  • _handoff_to_ralph is already long and branchy, but this PR adds exactly one nested if with an explicit early return. No new helper, no new field, no new side effect beyond the state.transition + _save pair that mirrors the existing EVALUATE→lateral path.
  • Coverage is proportional to the new code surface (3 dedicated tests + 1 pinning update).
  • No refactoring recommended in this PR. If a third interception lands at the resume / re-attach paths (L5-b), a _maybe_route_oscillation_to_lateral helper becomes appropriate; deferring until the second caller appears is the right call.

Test Quality Assessment (6/7)

  • Strong: positive test pins (1) ThinkingPersona invocation, (2) oscillation marker present in the synthetic qa_differences payload, (3) persona output surfaced on last_lateral_persona / last_lateral_approach_summary / result envelope, (4) terminal lands BLOCKED with tool_name="lateral_thinker" so re-resume re-enters lateral via _recoverable_phase_for_tool.
  • No over-mocking: fixtures use minimal stubs; the lateral thinker is an inline async closure returning a realistic LateralResult. Asserts probe real AutoPipelineState fields, not the mocks.
  • Missing edge case (non-blocking): explicit complete_product=False + oscillation regression.

Security / Operational Risk

None. No new I/O, no new external surface, no secret handling, no migration. The tool_name="lateral_thinker" carried by the BLOCKED envelope preserves the existing resume contract — --resume re-enters UNSTUCK_LATERAL, not a new state. The resume / re-attach sibling sites continue to set tool_name="ralph_starter", which _recoverable_phase_for_tool maps back to RALPH_HANDOFF, so user sessions are never stranded.

Looks Good

  • Strict minimum-substrate adherence: ~35 LoC in pipeline.py + 1 line in the transition table + 3 tests + 1 pinning extension.
  • Synthetic QA payload (qa_score=0.0, qa_verdict="oscillation_detected", single-element differences / suggestions, cache_suffix="") matches the call shape used by the two existing _run_lateral sites (line 717 resume, line 1819 EVALUATE-fail). cache_suffix="" is correct — oscillation is not a QA-cache replay.
  • Gate is structurally identical to the EVALUATE→lateral gate, keeping the two recovery on-ramps observably symmetric.
  • Transition-table change is exact and pinned: a future hand-edit cannot drift.
  • iteration_timeout regression test prevents a future broadening of the L5-a route from silently absorbing budget-exhaustion terminals.
  • Aligned with Meta SSOT: AgentOS roadmap sequencing (#920–#960) #961 Track B (already labeled "narrow L5-a Track B follow-up outside Track C tier gates" by the warden) and with Meta SSOT: ooo auto Vision — Autonomous Completion Engine #1157 (exact L5-a definition: "plumb the existing detection signal into the existing recovery substrate"; L5-b explicitly scoped out in the PR body).
  • Inline comments are load-bearing — they explain why oscillation_detected is special-cased while sibling stop reasons (iteration_timeout, wall_clock_exhausted, grade_regressing, max_generations reached) intentionally are not.

Final Recommendation

APPROVE. The PR delivers exactly the L5-a contract from #1157 with minimum-substrate fidelity. The diff is small, surgical, and tested. Sibling paths (resume / re-attach) are now documented as intentionally deferred to L5-b, eliminating the only ambiguity. CI green at the pre-fixup head; the follow-up commit is docs-only.


Posted by the pr-review skill after live read of the worktree on 3806b998.

@shaun0927
Copy link
Copy Markdown
Collaborator Author

Merge-readiness rationale

What this PR is

This PR is L5-a of the ooo auto SSOT (#1157) — the minimal-substrate slice of the long-running-resilience lane. It does one thing: when Ralph terminates with stop_reason == "oscillation_detected" in complete-product mode and a lateral_thinker is wired, the auto pipeline now transitions RALPH_HANDOFF → UNSTUCK_LATERAL and invokes _run_lateral — the same persona-driven advisor already used by the EVALUATE → UNSTUCK_LATERAL path on QA failures — before landing in BLOCKED. Other Ralph blocked terminals (iteration_timeout, wall_clock_exhausted, grade_regressing, max_generations reached) are budget-exhaustion terminals, not spec-reframe candidates, and continue straight to BLOCKED unchanged. Without a wired lateral thinker, oscillation_detected also continues straight to BLOCKED unchanged — the L5-a path is purely additive.

Why this is the right scope

Per the #1157 minimal-substrate audit (2026-05-22), L5 was deliberately scoped down from "new escalation-ladder state machine + new oscillation detector + new budget unification substrate" to plumbing the existing detection signal into the existing recovery substrate. This PR is exactly that:

  • No new state machine — one new entry in _ALLOWED_TRANSITIONS[RALPH_HANDOFF].
  • No new detector — reuses Ralph's existing oscillation_detected stop reason.
  • No new fields, no new event family, no new helper module.
  • ~35 LoC in pipeline.py + 1 line in state.py + 3 dedicated tests + 1 pinning-test extension.

This is also aligned with #961 Track B: the AgentOS roadmap warden has already labeled this PR a "narrow L5-a Track B follow-up outside Track C tier gates" — i.e. it does not contend with any open Track C tier gate.

What changed during review

After the initial review pass two improvements landed:

  1. Inline L5-b breadcrumb comments at _resume_ralph_handoff (pipeline.py:2390) and _reattach_ralph_job (pipeline.py:2520) — the two sibling sites that also see Ralph terminal status but intentionally do not route oscillation_detected through UNSTUCK_LATERAL in this slice. The comments make the deferral explicit, so a future reader does not mistake the gap for an oversight, and they document the user-recovery guarantee: BLOCKED with tool_name="ralph_starter" is mapped by _recoverable_phase_for_tool back to RALPH_HANDOFF, so --resume retries Ralph from scratch and the session is never stranded. Extending lateral recovery into those branches is the L5-b follow-up.

  2. External pr-review skill pass — posted as a separate review comment on this PR — reached the same APPROVE verdict, with mutation-test thinking pinned to the three new tests (positive route, no-lateral-wired regression, iteration_timeout budget-terminal regression) and the transition-table pinning. No blocking or warning findings remain.

Why this is safe to merge

  • Correctness: the gate (stop_reason == "oscillation_detected" AND lateral_thinker is not None AND state.complete_product) is structurally identical to the existing EVALUATE → UNSTUCK_LATERAL gate at pipeline.py:1813. The call shape into _run_lateral matches the existing two call sites (line 717 resume entry, line 1819 EVALUATE-fail entry) — same keyword args, same cache_suffix="" (oscillation is not a QA-cache replay).
  • No behavioural regression: every other Ralph terminal status (completed, cancelled, the four budget-exhaustion stop reasons, the unknown-failure fall-through) takes the exact same path as before. Pinned by test_ralph_iteration_timeout_does_not_invoke_lateral and the pre-existing handoff suite (44/44 tests pass on this head).
  • Resume contract preserved: the new BLOCKED carries tool_name="lateral_thinker", so --resume re-enters UNSTUCK_LATERAL via the existing _recoverable_phase_for_tool mapping, not a new code path. The two sibling sites that still go BLOCKED → ralph_starter continue to map back to RALPH_HANDOFF for retry; users are never stranded on any branch.
  • State-machine integrity: _ALLOWED_TRANSITIONS[RALPH_HANDOFF] gains exactly one new successor (UNSTUCK_LATERAL), and the pinning test test_state_machine_allows_ralph_handoff_terminal_transitions was extended in the same diff so a future hand-edit to that table cannot drift silently. UNSTUCK_LATERAL's own outbound transitions are unchanged.
  • No new external surface: no new I/O, no new event family, no migration, no schema change, no plugin contract change, no secret handling.
  • CI: all checks (Ruff Lint, MyPy Type Check, Test Python 3.12 / 3.13 / 3.14, enforce-envelope, enforce-boundary, Bridge TypeScript) are green on the merge head. Local uv run ruff check, uv run ruff format --check, uv run mypy, and the targeted pytest run (44/44) are all clean.
  • Alignment with both SSOTs: matches the Meta SSOT: ooo auto Vision — Autonomous Completion Engine #1157 L5-a contract verbatim ("plumb the existing detection signal into the existing recovery substrate"), and is already accounted for in the Meta SSOT: AgentOS roadmap sequencing (#920–#960) #961 Track B status row as a narrow follow-up outside Track C gates.

Out-of-scope / explicit follow-ups (L5-b and beyond)

This PR is intentionally additive, intentionally narrow, and ready to merge.

Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

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

Review — ouroboros-agent[bot]

Verdict: APPROVE

Reviewing commit 3806b99 for PR #1175

Review record: 043e1bdc-ab2b-4a40-b88f-fd9d0a709385

Blocking Findings

No in-scope blocking findings remained after policy filtering.

Non-blocking Suggestions

None.

Design Notes

Unable to assess architecture or implementation design because the source snapshot and patch could not be read in this environment.

Policy Notes

  • Omitted 1 finding(s) that referenced files outside the current PR changed-files scope.

Recovery Notes

First recoverable review artifact generated from codex analysis log.


Reviewed by ouroboros-agent[bot] via Codex deep analysis

@shaun0927 shaun0927 merged commit acfba01 into Q00:main May 22, 2026
8 checks passed
Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

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

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

Branch: feat/prL5a | 4 files, +326/-4 | CI: Bridge TypeScript pass 11s https://github.com/Q00/ouroboros/actions/runs/26288860565/job/77383394024
Scope: diff-only
HEAD checked: 3806b998de07151186d51375da61a4743af585b2

What Improved

  • The live _handoff_to_ralph path now routes oscillation_detected through UNSTUCK_LATERAL when complete_product and lateral_thinker are wired, and RALPH_HANDOFF -> UNSTUCK_LATERAL is now allowed in the state machine.
  • Added focused live-path tests for oscillation routing, no-lateral fallback, budget-terminal fallback, and the updated transition set.

Issue #1157 Requirements

Requirement Status
L5-a: plumb Ralph’s existing oscillation_detected signal into existing ooo unstuck / UNSTUCK_LATERAL before bailing. PARTIAL — live _handoff_to_ralph does this at src/ouroboros/auto/pipeline.py:1350, but resume/re-attach terminal consumers still bypass it at src/ouroboros/auto/pipeline.py:2390 and src/ouroboros/auto/pipeline.py:2520.
Preserve other Ralph blocked stop reasons as direct BLOCKED terminals. MET — implementation gates only stop_reason == "oscillation_detected" at src/ouroboros/auto/pipeline.py:1361; iteration_timeout is tested in tests/unit/auto/test_pipeline_oscillation_lateral.py:229.
Avoid new escalation-ladder state machine or new oscillation detector substrate. MET — the diff reuses UNSTUCK_LATERAL, _RALPH_BLOCKED_STOP_REASONS, and the existing lateral path.
Keep L5-b typed unstuck_exhausted terminal out of this PR. MET — no new stop reason is introduced.

Prior Findings Status

Prior Finding Status
First review on this PR WITHDRAWN — no prior bot finding set to carry forward.

Blockers

# File:Line Severity Confidence Finding
1 src/ouroboros/auto/pipeline.py:2390 High 90% The Ralph resume consumer still maps terminal_status == "failed" plus stop_reason == "oscillation_detected" directly to BLOCKED for every _RALPH_BLOCKED_STOP_REASONS value, and the re-attach branch does the same at src/ouroboros/auto/pipeline.py:2520. The live path now routes the same Ralph terminal contract through UNSTUCK_LATERAL at src/ouroboros/auto/pipeline.py:1350, so a long-running auto session that checkpoints Ralph dispatch, times out/disconnects, then resumes after Ralph reaches oscillation_detected skips the L5-a unstuck recovery entirely. This is a persistence/replay contract gap in the same terminal-status boundary, not just an unimplemented enhancement.

Follow-ups

# File:Line Priority Confidence Suggestion

Test Coverage

Targeted verification passed: SETUPTOOLS_SCM_PRETEND_VERSION_FOR_OUROBOROS_AI=0.0.0 SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 uv run pytest tests/unit/auto/test_pipeline_oscillation_lateral.py tests/unit/auto/test_pipeline_ralph_handoff.py -q -> 44 passed. Coverage is not adequate for all newly affected state/terminal handling: the new live path is covered in tests/unit/auto/test_pipeline_oscillation_lateral.py, but the persistence/replay consumers at src/ouroboros/auto/pipeline.py:2390 and src/ouroboros/auto/pipeline.py:2520 have no oscillation_detected lateral-routing tests and still preserve the old direct-BLOCKED behavior.

Design / Roadmap Gate

design_context.md and linked issue #1157 frame L5-a as minimal-substrate plumbing of the existing Ralph oscillation_detected signal into existing unstuck recovery. The live path aligns with that design, but the current HEAD leaves the resume/re-attach terminal-status consumers on the old direct-BLOCKED mapping. Because this PR touches Ralph terminal handling and persistence/replay is an immediate consumer boundary, the design gate is only partially satisfied.

Merge Recommendation

  • Do not merge until oscillation_detected is routed consistently through UNSTUCK_LATERAL for the Ralph resume/re-attach terminal paths, or the PR narrows the contract with tests proving those persistence paths are intentionally and safely outside L5-a.

ouroboros-agent[bot]

shaun0927 added a commit to shaun0927/ouroboros that referenced this pull request May 22, 2026
Request bot and CI re-evaluation for the already-pushed Q00#1175 follow-up correction without changing code.

Constraint: GitHub reported no checks or bot reviews after the initial PR push.\nConfidence: high\nScope-risk: narrow\nDirective: Do not treat this commit as a code change; review the previous commit for implementation substance.\nTested: Not rerun; empty trigger commit only.\nNot-tested: Bot availability.

Co-authored-by: OmX <omx@oh-my-codex.dev>
Copy link
Copy Markdown
Contributor

@ouroboros-agent ouroboros-agent Bot left a comment

Choose a reason for hiding this comment

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

Review — ouroboros-agent[bot]

Verdict: REQUEST_CHANGES

PR #1175
Branch: feat/prL5a | 4 files, +326/-4 | CI: Bridge TypeScript pass 11s https://github.com/Q00/ouroboros/actions/runs/26288860565/job/77383394024
Scope: architecture-level
HEAD checked: 3806b998de07151186d51375da61a4743af585b2

What Improved

  • The live _handoff_to_ralph path now routes oscillation_detected through UNSTUCK_LATERAL when complete_product and lateral_thinker are enabled.
  • Added focused unit coverage for the live path and direct-blocking behavior when lateral recovery is not configured.

Issue #N/A Requirements

Requirement Status
Live oscillation_detected with complete_product and wired lateral_thinker routes through UNSTUCK_LATERAL Met
Same Ralph terminal result behaves consistently after persisted-job resume Not met
Same Ralph terminal result behaves consistently after re-attach to an already-dispatched job Not met
Meaningful tests for newly added state/replay logic Partial

Prior Findings Status

Prior Finding Status
Prior review context MODIFIED — No prior review concerns were available in the provided context; no concerns were withdrawn.

Blockers

# File:Line Severity Confidence Finding
1 src/ouroboros/auto/pipeline.py:2390 High 95% The new oscillation recovery contract is not applied on replay boundaries. A foreground Ralph failure with oscillation_detected can enter UNSTUCK_LATERAL, but a persisted Ralph job reconciled through _poll_ralph_job still hits the generic _RALPH_BLOCKED_STOP_REASONS branch and mark_blocked(stop_reason, tool_name="ralph_starter") without checking lateral_thinker or complete_product. The same split exists on re-attach at src/ouroboros/auto/pipeline.py:2520. Any crash/offline resume after Ralph dispatch therefore bypasses the newly promised lateral persona path and leaves the session as a plain Ralph blocker, so the behavior depends on whether the process stayed alive rather than on the terminal Ralph result.

Follow-ups

# File:Line Priority Confidence Suggestion
None.

Test Coverage

  • Ran uv run pytest tests/unit/auto/test_pipeline_oscillation_lateral.py -q: 3 passed.
  • Coverage is incomplete for the affected persistence/replay boundary: no test exercises oscillation_detected through _poll_ralph_job resume or _reattach_ralph_job.

Design / Roadmap Gate

Affected-boundary audit failed. The change modifies a state-machine contract for Ralph terminal classification, so the boundary is not only the live _handoff_to_ralph branch; it includes persisted job polling, re-attach after interrupted dispatch, resume recovery classification, result surfaces, and tests. Current HEAD keeps the old BLOCKED mapping on the resume and re-attach branches, which makes oscillation_detected recovery non-durable across normal crash/replay paths.

Merge Recommendation

Retrospective audit recommendation: follow up with a blocking fix that applies the oscillation_detectedUNSTUCK_LATERAL routing consistently in _poll_ralph_job and _reattach_ralph_job, with regression tests for both replay paths.

Review-Metadata:
verdict: REQUEST_CHANGES
github_event: COMMENT
review_kind: post_merge_audit
merge_eligible: false
head_sha: 3806b99
source_read_ok: true
diff_read_ok: true
blocking_count: 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant