"""Eval harness for the llmdebug A/B debugging experiment.

Orchestrates case discovery, condition execution (traceback-only vs.
snapshot-enriched), patch proposal via pluggable patchers, pytest
verification, and JSONL result recording.  Supports local and
Docker-based (testcontainers) execution, TOML config files, resume
from partial runs, adaptive gating policies, online contextual-bandit
policy controllers, and budget-aware resource management.
"""

from __future__ import annotations

import argparse
import hashlib
import os
import shutil
import subprocess
import sys
import threading
import time
import uuid
from collections.abc import Callable, Mapping, Sequence
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import replace
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath
from typing import TYPE_CHECKING, Any

from . import _json as json

try:
    import tomllib as _toml_loader
except ModuleNotFoundError:
    _toml_loader = None

from evals.adaptive_budget_online_policy import (
    ACTION_INCREASE_BUDGET,
    ACTION_KEEP_BUDGET,
    BUDGET_POLICY_ACTIONS,
    AdaptiveBudgetPolicyController,
    BudgetPolicyConfig,
    BudgetPolicyDecision,
)
from evals.adaptive_online_policy import (
    ACTION_COMPACT_THEN_PATCH,
    ACTION_INVOKE_HELPER,
    ACTION_RETRY_HELPER_STRICT_JSON,
    ACTION_SKIP_HELPER,
    ACTION_SNAPSHOT_LITE,
    ONLINE_POLICY_ACTIONS,
    AdaptiveOnlinePolicyController,
    OnlinePolicyConfig,
    PolicyDecision,
)
from evals.policy_feature_scaling import (
    DEFAULT_POLICY_FEATURE_SCALING_PROFILE,
    normalize_feature_scaling_profile,
    scale_feature_mapping,
)

if TYPE_CHECKING:
    from evals.patchers.payload_types import ContextRequestPayload
else:
    ContextRequestPayload = dict[str, Any]  # type: ignore[misc,assignment]

_REPO_ROOT = Path(__file__).resolve().parents[1]
_SRC_ROOT = _REPO_ROOT / "src"
if str(_SRC_ROOT) not in sys.path:
    sys.path.insert(0, str(_SRC_ROOT))


from evals.eval_types import (  # noqa: E402
    _ASSERTION_VALUE_PATTERN,
    _DIAGNOSTIC_MESSAGE_PATTERNS,
    _EXCEPTION_TYPE_SPECIFICITY,
    _EXCEPTION_TYPE_SPECIFICITY_DEFAULT,
    _PROGRESS_LEVEL_ORDER,
    _PROGRESS_PRINT_LOCK,
    _PYTEST_SUMMARY_COUNT_RE,
    _STDOUT_PYTEST_ERROR_RE,
    _STDOUT_TRACEBACK_ERROR_RE,
    ADAPTIVE_BUDGET_CONTROL_CHANGE_KEYS,
    ADAPTIVE_BUDGET_POLICY_ALGOS,
    ADAPTIVE_BUDGET_POLICY_MODES,
    ADAPTIVE_BUDGET_REWARD_RHO_BUDGET_EXCEEDED_DEFAULT,
    ADAPTIVE_BUDGET_REWARD_SCOPE,
    ADAPTIVE_DELAYED_RESCUE_HORIZON_ATTEMPTS,
    ADAPTIVE_FEATURE_SCALING_PROFILES,
    ADAPTIVE_GATE_POLICIES,
    ADAPTIVE_HELPER_BUDGET_PROFILES,
    ADAPTIVE_HELPER_OUTCOME_INVALID_PAYLOAD,
    ADAPTIVE_HELPER_OUTCOME_MATERIALIZED,
    ADAPTIVE_HELPER_OUTCOME_NOT_INVOKED,
    ADAPTIVE_HELPER_OUTCOME_REQUEST_BUDGET_EXCEEDED,
    ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT,
    ADAPTIVE_HELPER_OUTCOME_SKIPPED_GUARDRAIL,
    ADAPTIVE_HELPER_OUTCOME_SKIPPED_POLICY,
    ADAPTIVE_HELPER_OUTCOME_SKIPPED_UNSUPPORTED,
    ADAPTIVE_HELPER_OUTCOME_TRANSPORT_FAILED,
    ADAPTIVE_HELPER_OUTCOME_UNAVAILABLE_BACKEND,
    ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_BONUS_BASE,
    ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_BONUS_MAX,
    ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_CONTEXT_MULTIPLIER,
    ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_HORIZON_TWO_FACTOR,
    ADAPTIVE_LOCAL_REWARD_HELPER_MATERIALIZATION_BONUS_SCALE,
    ADAPTIVE_LOCAL_REWARD_PROGRESS_HINT_SCALE,
    ADAPTIVE_LOCAL_REWARD_STAGNATION_PENALTY_SCALE,
    ADAPTIVE_LOCAL_REWARD_SUCCESS_HINT,
    ADAPTIVE_ONLINE_POLICY_ALGOS,
    ADAPTIVE_ONLINE_POLICY_MODES,
    ADAPTIVE_ONLINE_REWARD_SCOPE,
    ADAPTIVE_OPENAI_HELPER_RESPONSE_MODES,
    ADAPTIVE_REWARD_ETA_PROGRESS_DEFAULT,
    ADAPTIVE_REWARD_KAPPA_STAGNATION_DEFAULT,
    ADAPTIVE_REWARD_LAMBDA_TOKENS_DEFAULT,
    ADAPTIVE_REWARD_LATENCY_SCALE_DEFAULT,
    ADAPTIVE_REWARD_MU_LATENCY_DEFAULT,
    ADAPTIVE_REWARD_TOKEN_SCALE_DEFAULT,
    CONTEXT_COMPACTION_MODES,
    CONTEXT_COMPACTION_SCOPE_TOKENS,
    CONTEXT_PROTOCOLS,
    CONTEXT_RECOVERY_FAILURE_KINDS,
    CONTEXT_RECOVERY_PROMPT_BUDGET,
    DEFAULT_ADAPTIVE_BUDGET_TOPUP_MAX_CALLS,
    DEFAULT_ADAPTIVE_NO_PROGRESS_TOKEN_CAP,
    DEFAULT_CONTAINER_CLEANUP_STALE_GRACE_SEC,
    DEFAULT_EXECUTOR,
    DEFAULT_PROGRESS_HEARTBEAT_SEC,
    DEFAULT_PYTEST_OUTPUT_TAIL_LINES,
    DEFAULT_SNAPSHOT_ARTIFACT_MAX_STRING_CHARS,
    DEFAULT_SYNTAX_PREFLIGHT_MAX_CYCLES,
    DEFAULT_TRANSPORT_FAIL_FAST_STREAK,
    EMPTY_ADAPTIVE_POLICY_LOCAL_ACCOUNTING,
    EVAL_CONTAINER_LABEL_KEY,
    EVAL_CONTAINER_LABEL_VALUE,
    EVAL_RECORD_SCHEMA_VERSION,  # noqa: F401  # re-exported
    HELPER_CONTEXT_MATERIALIZING_ACTIONS,
    HELPER_STAGE_A_ACTIONS,
    OPENAI_CONTEXT_WINDOW_CHARS_PER_TOKEN,
    OPENAI_CONTEXT_WINDOW_RESERVE_TOKENS,
    OPENAI_RESPONSE_MODE_AUTOSWITCH_MODES,
    PATCH_APPLY_TIMEOUT_SEC,  # noqa: F401  # re-exported
    PATCH_VERIFIER_MIN_SCORE,
    PATCH_VERIFIER_RECOVERY_RESULT_NO_DIFF,
    PATCH_VERIFIER_RECOVERY_RESULT_NOT_ELIGIBLE,
    PATCH_VERIFIER_RECOVERY_RESULT_PASSED_VERIFIER,
    PATCH_VERIFIER_RECOVERY_RESULT_REQUEST_FAILED,
    PATCH_VERIFIER_RECOVERY_RESULT_SKIPPED_DEADLINE_EXHAUSTED,
    PATCH_VERIFIER_RECOVERY_RESULT_STILL_REJECTED,
    PATCH_VERIFIER_SCOPE_ADAPTIVE_ONLY,
    PATCH_VERIFIER_SCOPE_DISABLED,
    PATCHER_CONTEXT_LIMIT_ERROR_MARKERS,
    PERSISTED_PYTEST_OUTPUT_HARD_CHAR_CAP,
    PROGRESS_VERBOSITIES,
    PYTEST_TIMEOUT_RETURNCODE,
    RECORD_MODES,
    RETRY_CONTEXT_MODES,
    RETRY_PROMPT_MODES,
    RETRYABLE_HTTP_STATUS_CODES,
    RUN_EVAL_LOCKFILE_REDACTED,  # noqa: F401  # re-exported
    VALID_CATEGORIES,
    VALID_DIFFICULTIES,
    VALID_SNAPSHOT_DEPENDENCIES,
    AdaptiveBudgetControlProfile,
    AdaptiveOnlineRewardBreakdown,
    AdaptivePolicyLocalAccounting,
    AdaptiveProgressState,
    CaseSpec,
    Condition,
    ExecutionConfig,
    PendingAdaptiveOnlineCredit,
    PreGateResult,
    RunResult,
    StageAContextData,
    UnitExecutionOutcome,
    _ActiveRunSettings,
    _default_helper_action,
    _empty_stage_a_context,
    _filter_context_request_payload_for_action,
    _helper_action_fallback_reason,
    _TrailingResultsTailIssue,
)

_ACTIVE_RUN_SETTINGS = _ActiveRunSettings()
_ACTIVE_ONLINE_POLICY_CONTROLLER: AdaptiveOnlinePolicyController | None = None
_ACTIVE_BUDGET_POLICY_CONTROLLER: AdaptiveBudgetPolicyController | None = None


import evals.eval_containers as _eval_containers_mod  # noqa: E402
from evals.eval_config import (  # noqa: E402
    _apply_toml_configs_to_args,
    _default_openai_api_key,
    _parse_adaptive_budget_policy_tiers,
    _write_resolved_run_config_lockfile,
    add_execution_arguments,
    build_execution_config_from_args,
    get_active_execution_config,
    set_active_execution_config,
)
from evals.eval_containers import (  # noqa: E402
    _docker_client_from_env as _docker_client_from_env,
)
from evals.eval_containers import (  # noqa: E402
    _EvalContainerPool,
    check_testcontainers_runtime_available,
)
from evals.eval_containers import (  # noqa: E402
    cleanup_stale_eval_containers as _cleanup_stale_eval_containers_impl,
)


def cleanup_stale_eval_containers(
    *,
    grace_sec: int = DEFAULT_CONTAINER_CLEANUP_STALE_GRACE_SEC,
    now_utc: datetime | None = None,
) -> tuple[int, int, str | None]:
    """Remove stopped eval containers older than *grace_sec* seconds.

    Thin wrapper that patches ``eval_containers._docker_client_from_env``
    so that monkeypatching ``run_eval._docker_client_from_env`` in tests
    propagates correctly.
    """
    _eval_containers_mod._docker_client_from_env = _docker_client_from_env  # type: ignore[attr-defined]
    return _cleanup_stale_eval_containers_impl(
        grace_sec=grace_sec,
        now_utc=now_utc,
    )


_ACTIVE_EXECUTION_CONFIG = ExecutionConfig()
_ACTIVE_CONTAINER_POOL: _EvalContainerPool | None = None


def _normalized_progress_verbosity(raw: Any) -> str:
    text = str(raw or "").strip().lower()
    return text if text in PROGRESS_VERBOSITIES else "basic"


def _progress_is_enabled(verbosity: str, level: str) -> bool:
    normalized_verbosity = _normalized_progress_verbosity(verbosity)
    normalized_level = _normalized_progress_verbosity(level)
    return _PROGRESS_LEVEL_ORDER[normalized_verbosity] >= _PROGRESS_LEVEL_ORDER[normalized_level]


def _progress_print(
    *,
    verbosity: str,
    level: str,
    message: str,
    flush: bool = True,
) -> None:
    if not _progress_is_enabled(verbosity, level):
        return
    with _PROGRESS_PRINT_LOCK:
        print(message, flush=flush)


def _format_duration_precise(seconds: float | None) -> str:
    if seconds is None:
        return "unknown"
    normalized = max(0.0, float(seconds))
    if normalized < 60.0:
        return f"{normalized:.2f}s"
    return _format_duration(normalized)


from evals.eval_recording import (  # noqa: E402
    _build_base_record,
    _build_model_metadata,
    _build_patcher_config,
    _build_run_metadata,
    _default_run_metadata,
    _empty_attempt_metadata,
    _materialize_attempt_metadata,
    _RunResultsLock,
    _SynchronizedJsonlWriter,
)


def main(argv: list[str] | None = None) -> int:
    """CLI entry-point for the eval harness.

    Parses arguments, discovers cases, resumes partial runs, and
    dispatches (case, condition) units to a thread-pool executor.

    Returns:
        Exit code: 0 on success, 1 on partial failure, 2 on fatal error.
    """
    global _ACTIVE_RUN_SETTINGS, _ACTIVE_CONTAINER_POOL
    global _ACTIVE_ONLINE_POLICY_CONTROLLER, _ACTIVE_BUDGET_POLICY_CONTROLLER
    argv_tokens = list(sys.argv[1:] if argv is None else argv)
    parser = argparse.ArgumentParser(description="Run llmdebug A/B eval harness.")
    parser.add_argument("--cases-dir", default="evals/cases", help="Cases directory.")
    parser.add_argument("--case", action="append", default=[], help="Case id(s) to run.")
    parser.add_argument(
        "--case-list-file",
        action="append",
        default=[],
        help=(
            "Path to newline-delimited case ids to include (repeatable). "
            "Blank lines and '#' comments are ignored."
        ),
    )
    parser.add_argument(
        "--category", action="append", default=[], help="Category filter (repeatable)."
    )
    parser.add_argument(
        "--difficulty", action="append", default=[], help="Difficulty filter (repeatable)."
    )
    parser.add_argument(
        "--snapshot-dependency",
        action="append",
        default=[],
        help="Snapshot dependency filter (repeatable): required|helpful|none.",
    )
    parser.add_argument(
        "--bug-source", action="append", default=[], help="Bug source filter (repeatable)."
    )
    parser.add_argument(
        "--max-cases", type=int, default=0, help="Max number of cases to run (0 = all)."
    )
    parser.add_argument(
        "--jobs",
        type=int,
        default=1,
        help="Number of case/condition units to execute concurrently.",
    )
    parser.add_argument(
        "--progress-verbosity",
        default="basic",
        help="Progress verbosity level for runtime status output (basic|detailed|trace).",
    )
    parser.add_argument(
        "--progress-heartbeat-sec",
        type=float,
        default=DEFAULT_PROGRESS_HEARTBEAT_SEC,
        help="Heartbeat interval in seconds for in-flight unit progress (0 disables).",
    )
    parser.add_argument(
        "--transport-fail-fast-streak",
        type=int,
        default=DEFAULT_TRANSPORT_FAIL_FAST_STREAK,
        help=(
            "Abort early after this many consecutive terminal transport request failures "
            "(0 disables; default: 5)."
        ),
    )
    parser.add_argument(
        "--case-fail-fast-on-request-budget-exceeded",
        dest="case_fail_fast_on_request_budget_exceeded",
        action="store_true",
        default=True,
        help=(
            "Stop remaining attempts for the current case/condition when an attempt reports "
            "`request_budget_exceeded` (default: enabled)."
        ),
    )
    parser.add_argument(
        "--no-case-fail-fast-on-request-budget-exceeded",
        dest="case_fail_fast_on_request_budget_exceeded",
        action="store_false",
        help=(
            "Disable per-case fail-fast on `request_budget_exceeded` and continue retry attempts."
        ),
    )
    parser.add_argument(
        "--conditions",
        default="traceback_only,with_snapshot",
        help=(
            "Comma-separated list: "
            "traceback_only,with_snapshot,with_snapshot_structured,"
            "adaptive_gated,adaptive_llm_discretion"
        ),
    )
    parser.add_argument(
        "--run-id",
        default="",
        help="Optional run identifier. If the matching results file exists, unfinished units are resumed.",
    )
    parser.add_argument(
        "--config",
        action="append",
        default=[],
        help=(
            "Path to TOML config file with run settings. "
            "Top-level keys or [run_eval] keys map to CLI destinations."
        ),
    )
    parser.add_argument("--k", type=int, default=3, help="Max patch attempts per condition.")
    parser.add_argument(
        "--patcher",
        default="command",
        choices=["command", "heuristic", "null", "openai"],
        help="Patch strategy.",
    )
    parser.add_argument(
        "--patch-cmd",
        default="",
        help="Command to run for command patcher (overrides LLMDEBUG_EVAL_PATCH_CMD).",
    )
    parser.add_argument(
        "--patch-cmd-timeout-sec",
        type=float,
        default=30.0,
        help="Timeout in seconds for command patcher subprocess execution.",
    )
    parser.add_argument(
        "--patch-cmd-shell",
        action="store_true",
        help="Run command patcher with shell=True (default: disabled).",
    )
    parser.add_argument(
        "--output-format", default="json_compact", choices=["json", "json_compact", "toon"]
    )
    parser.add_argument("--keep-workdirs", action="store_true", help="Keep copied case workdirs.")
    parser.add_argument(
        "--openai-base-url", default=os.getenv("OPENAI_BASE_URL", "http://127.0.0.1:1234")
    )
    parser.add_argument("--openai-model", default=os.getenv("OPENAI_MODEL", ""))
    parser.add_argument("--openai-api-key", default=_default_openai_api_key())
    parser.add_argument(
        "--openai-http2",
        dest="openai_http2",
        action="store_true",
        default=True,
        help="Enable HTTP/2 for OpenAI-compatible transport (default: enabled).",
    )
    parser.add_argument(
        "--no-openai-http2",
        dest="openai_http2",
        action="store_false",
        help="Disable HTTP/2 and force HTTP/1.1 for OpenAI-compatible transport.",
    )
    parser.add_argument("--openai-timeout-sec", type=float, default=120.0)
    parser.add_argument(
        "--openai-transport-max-retries",
        type=int,
        default=6,
        help=(
            "Max immediate retries for retryable OpenAI transport failures inside one request "
            "(default: 6)."
        ),
    )
    parser.add_argument(
        "--openai-transport-retry-backoff-base-sec",
        type=float,
        default=1.0,
        help="Base exponential backoff seconds for OpenAI transport retries (default: 1.0).",
    )
    parser.add_argument(
        "--openai-transport-retry-backoff-max-sec",
        type=float,
        default=8.0,
        help="Max backoff seconds for OpenAI transport retries (default: 8.0).",
    )
    parser.add_argument(
        "--openai-max-propose-sec",
        type=float,
        default=300.0,
        help=(
            "Maximum wall-clock budget per patch attempt for OpenAI propose calls "
            "(initial + recovery proposals), including per-call retries/fallback calls "
            "(0 disables cap)."
        ),
    )
    parser.add_argument("--openai-max-tokens", type=int, default=2048)
    parser.add_argument(
        "--openai-context-window-tokens",
        type=int,
        default=0,
        help=(
            "Optional OpenAI-compatible context window for Stage-B prompt preflight capping "
            "(0 disables guard)."
        ),
    )
    parser.add_argument(
        "--openai-auto-context-window",
        action="store_true",
        default=False,
        help=(
            "Probe OpenAI-compatible model metadata for context window tokens before run start "
            "(manual --openai-context-window-tokens overrides)."
        ),
    )
    parser.add_argument("--openai-temperature", type=float, default=0.0)
    parser.add_argument("--openai-response-mode", choices=["edits", "diff"], default="edits")
    parser.add_argument(
        "--openai-response-mode-autoswitch",
        choices=["off", "retry_to_diff"],
        default="off",
        help=(
            "OpenAI retry policy for internal syntax/sanitize retries: off|retry_to_diff "
            "(default: off)."
        ),
    )
    parser.add_argument(
        "--openai-force-patch-attempt",
        action="store_true",
        help="Force a minimal patch attempt when model output cannot be parsed into a diff.",
    )
    parser.add_argument(
        "--force-patch-attempt",
        dest="force_patch_attempt",
        action="store_true",
        default=True,
        help="Force a deterministic runner-side patch attempt when patcher returns no diff (default: enabled).",
    )
    parser.add_argument(
        "--no-force-patch-attempt",
        dest="force_patch_attempt",
        action="store_false",
        help="Disable runner-side forced patch attempts when no diff is proposed.",
    )
    parser.add_argument(
        "--no-files-context", action="store_true", help="Do not include full file text in prompt."
    )
    parser.add_argument(
        "--max-context-chars",
        type=int,
        default=200_000,
        help="Max characters for project file context in prompt (default: 200000).",
    )
    parser.add_argument(
        "--context-protocol",
        choices=["monolithic", "staged"],
        default="monolithic",
        help="Context protocol for prompting: monolithic|staged (default: monolithic).",
    )
    parser.add_argument(
        "--context-stage-a-max-requests",
        type=int,
        default=6,
        help="Max Stage-A context requests when --context-protocol=staged (default: 6).",
    )
    parser.add_argument(
        "--context-stage-a-max-chars",
        type=int,
        default=120_000,
        help="Max characters returned from Stage-A context retrieval (default: 120000).",
    )
    parser.add_argument(
        "--retry-prompt-mode",
        choices=["full", "delta", "auto"],
        default="auto",
        help="Retry prompt protocol: full|delta|auto (default: auto).",
    )
    parser.add_argument(
        "--retry-context-mode",
        choices=["fresh", "history"],
        default="history",
        help="Retry context mode: fresh|history (default: history).",
    )
    parser.add_argument(
        "--retry-history-max-chars",
        type=int,
        default=48_000,
        help="Max characters of retry history transcript to include when --retry-context-mode=history (default: 48000).",
    )
    parser.add_argument(
        "--pytest-output-tail-lines",
        type=int,
        default=DEFAULT_PYTEST_OUTPUT_TAIL_LINES,
        help=(
            "Persist at most the latest N lines in pytest stdout/stderr artifact files "
            f"(default: {DEFAULT_PYTEST_OUTPUT_TAIL_LINES}, 0 disables line truncation)."
        ),
    )
    parser.add_argument(
        "--snapshot-artifact-max-string-chars",
        type=int,
        default=DEFAULT_SNAPSHOT_ARTIFACT_MAX_STRING_CHARS,
        help=(
            "Per-string cap for persisted snapshot.json artifacts "
            f"(default: {DEFAULT_SNAPSHOT_ARTIFACT_MAX_STRING_CHARS}, 0 disables)."
        ),
    )
    parser.add_argument(
        "--prompt-budget-total-chars",
        type=int,
        default=180_000,
        help="Stage-B prompt total character budget (0 disables total budget cap).",
    )
    parser.add_argument(
        "--prompt-budget-retry-delta-chars",
        type=int,
        default=16_000,
        help="Per-section character cap for retry delta packet in Stage-B prompts.",
    )
    parser.add_argument(
        "--prompt-budget-stdout-chars",
        type=int,
        default=32_000,
        help="Per-section character cap for pytest stdout/stderr in Stage-B prompts.",
    )
    parser.add_argument(
        "--prompt-budget-snapshot-chars",
        type=int,
        default=32_000,
        help="Per-section character cap for snapshot summary in Stage-B prompts.",
    )
    parser.add_argument(
        "--prompt-budget-evidence-chars",
        type=int,
        default=64_000,
        help="Per-section character cap for Stage-A evidence blocks in Stage-B prompts.",
    )
    parser.add_argument(
        "--prompt-budget-retry-history-chars",
        type=int,
        default=12_000,
        help="Per-section character cap for retry-history transcript in Stage-B prompts.",
    )
    parser.add_argument(
        "--context-compaction-mode",
        choices=["off", "auto_overflow", "always"],
        default="auto_overflow",
        help=(
            "Delegated context compaction policy for Stage-B prompts: "
            "off|auto_overflow|always (default: auto_overflow)."
        ),
    )
    parser.add_argument(
        "--context-compaction-scope",
        default="snapshot,pytest,files",
        help=(
            "Comma-separated raw context sources eligible for delegated compaction "
            "(snapshot,pytest,files)."
        ),
    )
    parser.add_argument(
        "--context-compaction-max-input-chars",
        type=int,
        default=120_000,
        help="Max characters sent to delegated context compaction calls (default: 120000).",
    )
    parser.add_argument(
        "--context-compaction-target-chars",
        type=int,
        default=18_000,
        help="Target compacted context size in characters (default: 18000).",
    )
    parser.add_argument(
        "--context-compaction-max-calls",
        type=int,
        default=1,
        help="Max delegated context compaction calls per case/condition (default: 1, 0 disables).",
    )
    parser.add_argument(
        "--adaptive-gate-policy",
        choices=["aggressive", "balanced", "conservative"],
        default="balanced",
        help="Gate policy for adaptive_gated helper invocation.",
    )
    parser.add_argument(
        "--adaptive-max-helper-calls",
        type=int,
        default=1,
        help="Max helper invocations per case/condition for adaptive modes (default: 1, 0 = unlimited).",
    )
    parser.add_argument(
        "--adaptive-budget-topup-max-calls",
        type=int,
        default=DEFAULT_ADAPTIVE_BUDGET_TOPUP_MAX_CALLS,
        help=(
            "Extra one-time helper budget grants per case/condition when adaptive policies are "
            "stagnating (default: 1, 0 disables)."
        ),
    )
    parser.add_argument(
        "--adaptive-stagnation-window",
        type=int,
        default=1,
        help="Consecutive repeated-failure window for adaptive gate decisions.",
    )
    parser.add_argument(
        "--adaptive-saturation-threshold",
        type=float,
        default=0.75,
        help="Disable further helper calls when low-yield helper ratio reaches this threshold.",
    )
    parser.add_argument(
        "--adaptive-no-progress-token-cap",
        type=int,
        default=DEFAULT_ADAPTIVE_NO_PROGRESS_TOKEN_CAP,
        help=(
            "Stop adaptive loops when repeated no-progress attempts accumulate at least this many "
            "tokens (default: 150000, 0 disables)."
        ),
    )
    parser.add_argument(
        "--adaptive-helper-budget-profile",
        choices=["default", "compact"],
        default="default",
        help="Prompt-budget profile applied when adaptive helper is invoked.",
    )
    parser.add_argument(
        "--adaptive-openai-helper-response-mode",
        choices=["inherit", "edits", "diff"],
        default="inherit",
        help="OpenAI response-mode override for adaptive helper-invoked attempts (default: inherit).",
    )
    parser.add_argument(
        "--adaptive-informativeness-pregate",
        action="store_true",
        default=False,
        help="Enable rule-based traceback informativeness pre-gate for adaptive conditions.",
    )
    parser.add_argument(
        "--adaptive-informativeness-pregate-threshold",
        type=float,
        default=0.7,
        help="Score threshold to skip Stage-A (0.0-1.0, default: 0.7).",
    )
    parser.add_argument(
        "--adaptive-online-policy-mode",
        choices=["off", "shadow", "on"],
        default="off",
        help="Online adaptive policy mode for adaptive_llm_discretion (default: off).",
    )
    parser.add_argument(
        "--adaptive-online-policy-algo",
        choices=["linucb"],
        default="linucb",
        help="Online adaptive policy algorithm (default: linucb).",
    )
    parser.add_argument(
        "--adaptive-online-alpha",
        type=float,
        default=0.8,
        help="LinUCB exploration coefficient alpha (default: 0.8).",
    )
    parser.add_argument(
        "--adaptive-online-epsilon",
        type=float,
        default=0.05,
        help="Epsilon-greedy exploration probability (default: 0.05).",
    )
    parser.add_argument(
        "--adaptive-online-l2",
        type=float,
        default=1.0,
        help="L2 regularization for LinUCB matrices (default: 1.0).",
    )
    parser.add_argument(
        "--adaptive-online-warmup-decisions",
        type=int,
        default=200,
        help="Uniform warmup decisions before LinUCB greedy/explore policy (default: 200).",
    )
    parser.add_argument(
        "--adaptive-online-save-every",
        type=int,
        default=50,
        help="Persist online policy state every N updates (default: 50).",
    )
    parser.add_argument(
        "--adaptive-online-feature-scaling-profile",
        choices=sorted(ADAPTIVE_FEATURE_SCALING_PROFILES),
        default=DEFAULT_POLICY_FEATURE_SCALING_PROFILE,
        help="Feature scaling profile for adaptive online policy (default: v1).",
    )
    parser.add_argument(
        "--adaptive-online-state-path",
        default="",
        help="Optional path for adaptive online policy state JSON.",
    )
    parser.add_argument(
        "--adaptive-online-events-path",
        default="",
        help="Optional path for adaptive online policy JSONL event log.",
    )
    parser.add_argument(
        "--adaptive-budget-policy-mode",
        choices=["off", "shadow", "on"],
        default="on",
        help="Online adaptive budget policy mode for adaptive conditions (default: on).",
    )
    parser.add_argument(
        "--no-adaptive-budget-policy",
        dest="adaptive_budget_policy_mode",
        action="store_const",
        const="off",
        help="Disable adaptive budget policy.",
    )
    parser.add_argument(
        "--adaptive-budget-policy-algo",
        choices=["linucb"],
        default="linucb",
        help="Online adaptive budget policy algorithm (default: linucb).",
    )
    parser.add_argument(
        "--adaptive-budget-policy-tiers",
        default="1.0,1.5,2.0",
        help=(
            "Comma-separated budget multipliers for attempt propose budget tiers "
            "(default: 1.0,1.5,2.0). Must include 1.0."
        ),
    )
    parser.add_argument(
        "--adaptive-budget-policy-max-ups-per-case",
        type=int,
        default=1,
        help="Max applied budget increases per case/condition (default: 1, 0 disables).",
    )
    parser.add_argument(
        "--adaptive-budget-online-alpha",
        type=float,
        default=0.6,
        help="LinUCB exploration coefficient alpha for adaptive budget policy (default: 0.6).",
    )
    parser.add_argument(
        "--adaptive-budget-online-epsilon",
        type=float,
        default=0.03,
        help="Epsilon-greedy exploration probability for budget policy (default: 0.03).",
    )
    parser.add_argument(
        "--adaptive-budget-online-l2",
        type=float,
        default=1.0,
        help="L2 regularization for adaptive budget LinUCB matrices (default: 1.0).",
    )
    parser.add_argument(
        "--adaptive-budget-online-warmup-decisions",
        type=int,
        default=0,
        help="Uniform warmup decisions before budget policy exploitation (default: 0).",
    )
    parser.add_argument(
        "--adaptive-budget-online-save-every",
        type=int,
        default=50,
        help="Persist adaptive budget policy state every N updates (default: 50).",
    )
    parser.add_argument(
        "--adaptive-budget-feature-scaling-profile",
        choices=sorted(ADAPTIVE_FEATURE_SCALING_PROFILES),
        default=DEFAULT_POLICY_FEATURE_SCALING_PROFILE,
        help="Feature scaling profile for adaptive budget policy (default: v1).",
    )
    parser.add_argument(
        "--adaptive-budget-online-state-path",
        default="",
        help="Optional path for adaptive budget policy state JSON.",
    )
    parser.add_argument(
        "--adaptive-budget-online-events-path",
        default="",
        help="Optional path for adaptive budget policy JSONL event log.",
    )
    parser.add_argument(
        "--adaptive-budget-reward-rho-budget-exceeded",
        type=float,
        default=ADAPTIVE_BUDGET_REWARD_RHO_BUDGET_EXCEEDED_DEFAULT,
        help="Reward penalty coefficient when attempt ends with request_budget_exceeded.",
    )
    parser.add_argument(
        "--adaptive-reward-lambda-tokens",
        type=float,
        default=ADAPTIVE_REWARD_LAMBDA_TOKENS_DEFAULT,
        help=(
            "Reward penalty coefficient for token usage: "
            "reward = success - lambda*(tokens/token_scale) - mu*(latency/latency_scale)."
        ),
    )
    parser.add_argument(
        "--adaptive-reward-mu-latency",
        type=float,
        default=ADAPTIVE_REWARD_MU_LATENCY_DEFAULT,
        help="Reward penalty coefficient for latency term (default: 0.05).",
    )
    parser.add_argument(
        "--adaptive-reward-token-scale",
        type=float,
        default=ADAPTIVE_REWARD_TOKEN_SCALE_DEFAULT,
        help="Token normalization scale in adaptive reward (default: 1000).",
    )
    parser.add_argument(
        "--adaptive-reward-latency-scale",
        type=float,
        default=ADAPTIVE_REWARD_LATENCY_SCALE_DEFAULT,
        help="Latency normalization scale in adaptive reward seconds (default: 10).",
    )
    parser.add_argument(
        "--adaptive-reward-eta-progress",
        type=float,
        default=ADAPTIVE_REWARD_ETA_PROGRESS_DEFAULT,
        help="Reward bonus coefficient for adaptive progress signal (default: 0.15).",
    )
    parser.add_argument(
        "--adaptive-reward-kappa-stagnation",
        type=float,
        default=ADAPTIVE_REWARD_KAPPA_STAGNATION_DEFAULT,
        help="Reward penalty coefficient for adaptive no-progress streak (default: 0.10).",
    )
    parser.add_argument(
        "--syntax-preflight-enabled",
        dest="syntax_preflight_enabled",
        action="store_true",
        default=True,
        help=(
            "Validate patched Python files with py_compile before test execution "
            "(default: enabled)."
        ),
    )
    parser.add_argument(
        "--no-syntax-preflight",
        dest="syntax_preflight_enabled",
        action="store_false",
        help="Disable py_compile syntax preflight before test execution.",
    )
    parser.add_argument(
        "--syntax-preflight-max-cycles",
        type=int,
        default=DEFAULT_SYNTAX_PREFLIGHT_MAX_CYCLES,
        help=(
            "Maximum internal syntax-repair cycles per logical attempt "
            "(default: 2, does not consume additional retry attempts)."
        ),
    )
    parser.add_argument(
        "--patch-sanitize-enabled",
        dest="patch_sanitize_enabled",
        action="store_true",
        default=True,
        help=(
            "Reject malformed patch text patterns before patch apply/syntax preflight "
            "(default: enabled)."
        ),
    )
    parser.add_argument(
        "--no-patch-sanitize",
        dest="patch_sanitize_enabled",
        action="store_false",
        help="Disable patch text sanitation guard before syntax preflight.",
    )
    parser.add_argument(
        "--patch-verifier-infer-test-source-scope",
        dest="patch_verifier_infer_test_source_scope",
        action="store_true",
        default=True,
        help=(
            "Allow patch verifier crash-scope matching via source imports inferred from "
            "failing/crash test files (default: enabled)."
        ),
    )
    parser.add_argument(
        "--no-patch-verifier-infer-test-source-scope",
        dest="patch_verifier_infer_test_source_scope",
        action="store_false",
        help="Disable patch verifier test-to-source crash-scope inference.",
    )
    parser.add_argument(
        "--deterministic",
        action="store_true",
        help="Enable deterministic mode (requires --seed; fails fast if guarantees are missing).",
    )
    parser.add_argument(
        "--seed",
        type=int,
        default=None,
        help="Deterministic seed used for patch generation and subprocess hash randomization.",
    )
    parser.add_argument(
        "--record-prompt",
        choices=["full", "hash", "none"],
        default="hash",
        help="How to persist prompt material into attempt metadata.",
    )
    parser.add_argument(
        "--record-evidence",
        choices=["full", "hash", "none"],
        default="hash",
        help="How to persist evidence material into attempt metadata.",
    )
    parser.add_argument(
        "--record-env",
        action="append",
        default=[],
        help="Environment variable key to include in run_metadata (repeatable).",
    )
    parser.add_argument(
        "--record-deps",
        choices=["none", "minimal", "runtime"],
        default="minimal",
        help="Dependency version capture mode for run_metadata.",
    )
    add_execution_arguments(parser, default_executor=DEFAULT_EXECUTOR)
    args = parser.parse_args(argv_tokens)
    try:
        config_state = _apply_toml_configs_to_args(
            parser=parser, args=args, argv_tokens=argv_tokens
        )
    except ValueError as exc:
        print(f"Invalid --config: {exc}")
        return 2

    if args.deterministic and args.seed is None:
        print("Invalid arguments: --seed is required when --deterministic is enabled.")
        return 2
    if args.record_prompt not in RECORD_MODES:
        print(f"Invalid --record-prompt mode: {args.record_prompt}")
        return 2
    if args.record_evidence not in RECORD_MODES:
        print(f"Invalid --record-evidence mode: {args.record_evidence}")
        return 2
    if args.context_protocol not in CONTEXT_PROTOCOLS:
        print(f"Invalid --context-protocol: {args.context_protocol}")
        return 2
    if int(args.context_stage_a_max_requests) < 1:
        print("Invalid --context-stage-a-max-requests: must be >= 1")
        return 2
    if int(args.context_stage_a_max_chars) < 1:
        print("Invalid --context-stage-a-max-chars: must be >= 1")
        return 2
    if args.retry_prompt_mode not in RETRY_PROMPT_MODES:
        print(f"Invalid --retry-prompt-mode: {args.retry_prompt_mode}")
        return 2
    if args.retry_context_mode not in RETRY_CONTEXT_MODES:
        print(f"Invalid --retry-context-mode: {args.retry_context_mode}")
        return 2
    if int(args.retry_history_max_chars) < 0:
        print("Invalid --retry-history-max-chars: must be >= 0")
        return 2
    if int(args.pytest_output_tail_lines) < 0:
        print("Invalid --pytest-output-tail-lines: must be >= 0")
        return 2
    if int(args.snapshot_artifact_max_string_chars) < 0:
        print("Invalid --snapshot-artifact-max-string-chars: must be >= 0")
        return 2
    if int(args.prompt_budget_total_chars) < 0:
        print("Invalid --prompt-budget-total-chars: must be >= 0")
        return 2
    if int(args.prompt_budget_retry_delta_chars) < 0:
        print("Invalid --prompt-budget-retry-delta-chars: must be >= 0")
        return 2
    if int(args.prompt_budget_stdout_chars) < 0:
        print("Invalid --prompt-budget-stdout-chars: must be >= 0")
        return 2
    if int(args.prompt_budget_snapshot_chars) < 0:
        print("Invalid --prompt-budget-snapshot-chars: must be >= 0")
        return 2
    if int(args.prompt_budget_evidence_chars) < 0:
        print("Invalid --prompt-budget-evidence-chars: must be >= 0")
        return 2
    if int(args.prompt_budget_retry_history_chars) < 0:
        print("Invalid --prompt-budget-retry-history-chars: must be >= 0")
        return 2
    if str(args.context_compaction_mode) not in CONTEXT_COMPACTION_MODES:
        print(f"Invalid --context-compaction-mode: {args.context_compaction_mode}")
        return 2
    context_compaction_scope_tokens = {
        token.strip().lower()
        for token in str(args.context_compaction_scope or "").split(",")
        if token.strip()
    }
    invalid_context_compaction_scope_tokens = sorted(
        token
        for token in context_compaction_scope_tokens
        if token not in CONTEXT_COMPACTION_SCOPE_TOKENS
    )
    if invalid_context_compaction_scope_tokens:
        print(
            "Invalid --context-compaction-scope: unknown token(s): "
            + ", ".join(invalid_context_compaction_scope_tokens)
            + " (allowed: snapshot,pytest,files)"
        )
        return 2
    if int(args.context_compaction_max_input_chars) < 0:
        print("Invalid --context-compaction-max-input-chars: must be >= 0")
        return 2
    if int(args.context_compaction_target_chars) < 1:
        print("Invalid --context-compaction-target-chars: must be >= 1")
        return 2
    if int(args.context_compaction_max_calls) < 0:
        print("Invalid --context-compaction-max-calls: must be >= 0")
        return 2
    if int(args.openai_context_window_tokens) < 0:
        print("Invalid --openai-context-window-tokens: must be >= 0")
        return 2
    if int(args.openai_transport_max_retries) < 0:
        print("Invalid --openai-transport-max-retries: must be >= 0")
        return 2
    if float(args.openai_transport_retry_backoff_base_sec) < 0.0:
        print("Invalid --openai-transport-retry-backoff-base-sec: must be >= 0")
        return 2
    if float(args.openai_transport_retry_backoff_max_sec) < 0.0:
        print("Invalid --openai-transport-retry-backoff-max-sec: must be >= 0")
        return 2
    if float(args.openai_transport_retry_backoff_max_sec) < float(
        args.openai_transport_retry_backoff_base_sec
    ):
        print(
            "Invalid --openai-transport-retry-backoff-max-sec: must be >= "
            "--openai-transport-retry-backoff-base-sec"
        )
        return 2
    if str(args.openai_response_mode_autoswitch) not in OPENAI_RESPONSE_MODE_AUTOSWITCH_MODES:
        print(f"Invalid --openai-response-mode-autoswitch: {args.openai_response_mode_autoswitch}")
        return 2
    if args.adaptive_gate_policy not in ADAPTIVE_GATE_POLICIES:
        print(f"Invalid --adaptive-gate-policy: {args.adaptive_gate_policy}")
        return 2
    if int(args.adaptive_max_helper_calls) < 0:
        print("Invalid --adaptive-max-helper-calls: must be >= 0")
        return 2
    if int(args.adaptive_budget_topup_max_calls) < 0:
        print("Invalid --adaptive-budget-topup-max-calls: must be >= 0")
        return 2
    if int(args.adaptive_stagnation_window) < 1:
        print("Invalid --adaptive-stagnation-window: must be >= 1")
        return 2
    if not (0.0 <= float(args.adaptive_saturation_threshold) <= 1.0):
        print("Invalid --adaptive-saturation-threshold: must be between 0.0 and 1.0")
        return 2
    if int(args.adaptive_no_progress_token_cap) < 0:
        print("Invalid --adaptive-no-progress-token-cap: must be >= 0")
        return 2
    if args.adaptive_helper_budget_profile not in ADAPTIVE_HELPER_BUDGET_PROFILES:
        print(f"Invalid --adaptive-helper-budget-profile: {args.adaptive_helper_budget_profile}")
        return 2
    if args.adaptive_openai_helper_response_mode not in ADAPTIVE_OPENAI_HELPER_RESPONSE_MODES:
        print(
            "Invalid --adaptive-openai-helper-response-mode: "
            f"{args.adaptive_openai_helper_response_mode}"
        )
        return 2
    if not (0.0 <= float(args.adaptive_informativeness_pregate_threshold) <= 1.0):
        print("Invalid --adaptive-informativeness-pregate-threshold: must be between 0.0 and 1.0")
        return 2
    if str(args.adaptive_online_policy_mode) not in ADAPTIVE_ONLINE_POLICY_MODES:
        print(f"Invalid --adaptive-online-policy-mode: {args.adaptive_online_policy_mode}")
        return 2
    if str(args.adaptive_online_policy_algo) not in ADAPTIVE_ONLINE_POLICY_ALGOS:
        print(f"Invalid --adaptive-online-policy-algo: {args.adaptive_online_policy_algo}")
        return 2
    if float(args.adaptive_online_alpha) < 0.0:
        print("Invalid --adaptive-online-alpha: must be >= 0")
        return 2
    if not (0.0 <= float(args.adaptive_online_epsilon) <= 1.0):
        print("Invalid --adaptive-online-epsilon: must be between 0.0 and 1.0")
        return 2
    if float(args.adaptive_online_l2) <= 0.0:
        print("Invalid --adaptive-online-l2: must be > 0")
        return 2
    if int(args.adaptive_online_warmup_decisions) < 0:
        print("Invalid --adaptive-online-warmup-decisions: must be >= 0")
        return 2
    if int(args.adaptive_online_save_every) < 1:
        print("Invalid --adaptive-online-save-every: must be >= 1")
        return 2
    if str(args.adaptive_online_feature_scaling_profile) not in ADAPTIVE_FEATURE_SCALING_PROFILES:
        print(
            "Invalid --adaptive-online-feature-scaling-profile: "
            f"{args.adaptive_online_feature_scaling_profile}"
        )
        return 2
    if str(args.adaptive_budget_policy_mode) not in ADAPTIVE_BUDGET_POLICY_MODES:
        print(f"Invalid --adaptive-budget-policy-mode: {args.adaptive_budget_policy_mode}")
        return 2
    if str(args.adaptive_budget_policy_algo) not in ADAPTIVE_BUDGET_POLICY_ALGOS:
        print(f"Invalid --adaptive-budget-policy-algo: {args.adaptive_budget_policy_algo}")
        return 2
    try:
        _parse_adaptive_budget_policy_tiers(args.adaptive_budget_policy_tiers)
    except (TypeError, ValueError):
        print(
            "Invalid --adaptive-budget-policy-tiers: expected strictly increasing "
            "comma-separated positive floats including 1.0 (for example: 1.0,1.5,2.0)."
        )
        return 2
    if int(args.adaptive_budget_policy_max_ups_per_case) < 0:
        print("Invalid --adaptive-budget-policy-max-ups-per-case: must be >= 0")
        return 2
    if float(args.adaptive_budget_online_alpha) < 0.0:
        print("Invalid --adaptive-budget-online-alpha: must be >= 0")
        return 2
    if not (0.0 <= float(args.adaptive_budget_online_epsilon) <= 1.0):
        print("Invalid --adaptive-budget-online-epsilon: must be between 0.0 and 1.0")
        return 2
    if float(args.adaptive_budget_online_l2) <= 0.0:
        print("Invalid --adaptive-budget-online-l2: must be > 0")
        return 2
    if int(args.adaptive_budget_online_warmup_decisions) < 0:
        print("Invalid --adaptive-budget-online-warmup-decisions: must be >= 0")
        return 2
    if int(args.adaptive_budget_online_save_every) < 1:
        print("Invalid --adaptive-budget-online-save-every: must be >= 1")
        return 2
    if str(args.adaptive_budget_feature_scaling_profile) not in ADAPTIVE_FEATURE_SCALING_PROFILES:
        print(
            "Invalid --adaptive-budget-feature-scaling-profile: "
            f"{args.adaptive_budget_feature_scaling_profile}"
        )
        return 2
    if float(args.adaptive_budget_reward_rho_budget_exceeded) < 0.0:
        print("Invalid --adaptive-budget-reward-rho-budget-exceeded: must be >= 0")
        return 2
    if float(args.adaptive_reward_lambda_tokens) < 0.0:
        print("Invalid --adaptive-reward-lambda-tokens: must be >= 0")
        return 2
    if float(args.adaptive_reward_mu_latency) < 0.0:
        print("Invalid --adaptive-reward-mu-latency: must be >= 0")
        return 2
    if float(args.adaptive_reward_token_scale) <= 0.0:
        print("Invalid --adaptive-reward-token-scale: must be > 0")
        return 2
    if float(args.adaptive_reward_latency_scale) <= 0.0:
        print("Invalid --adaptive-reward-latency-scale: must be > 0")
        return 2
    if float(args.adaptive_reward_eta_progress) < 0.0:
        print("Invalid --adaptive-reward-eta-progress: must be >= 0")
        return 2
    if float(args.adaptive_reward_kappa_stagnation) < 0.0:
        print("Invalid --adaptive-reward-kappa-stagnation: must be >= 0")
        return 2
    if int(args.syntax_preflight_max_cycles) < 1:
        print("Invalid --syntax-preflight-max-cycles: must be >= 1")
        return 2
    if str(args.progress_verbosity) not in PROGRESS_VERBOSITIES:
        print(f"Invalid --progress-verbosity: {args.progress_verbosity}")
        return 2
    if float(args.progress_heartbeat_sec) < 0.0:
        print("Invalid --progress-heartbeat-sec: must be >= 0")
        return 2
    if int(args.jobs) < 1:
        print("Invalid --jobs: must be >= 1")
        return 2
    if int(args.transport_fail_fast_streak) < 0:
        print("Invalid --transport-fail-fast-streak: must be >= 0")
        return 2
    try:
        adaptive_budget_policy_tiers = _parse_adaptive_budget_policy_tiers(
            args.adaptive_budget_policy_tiers
        )
    except (TypeError, ValueError):
        print(
            "Invalid --adaptive-budget-policy-tiers: expected strictly increasing "
            "comma-separated positive floats including 1.0 (for example: 1.0,1.5,2.0)."
        )
        return 2
    args.adaptive_budget_policy_tiers = ",".join(
        f"{value:g}" for value in adaptive_budget_policy_tiers
    )
    try:
        execution_config = build_execution_config_from_args(args)
    except ValueError as exc:
        print(f"Invalid arguments: {exc}")
        return 2
    if args.deterministic and args.patcher == "openai" and float(args.openai_temperature) != 0.0:
        print("Deterministic mode enabled: forcing --openai-temperature=0.0")
        args.openai_temperature = 0.0
    if (
        str(args.openai_response_mode_autoswitch) == "retry_to_diff"
        and int(args.syntax_preflight_max_cycles) < 2
    ):
        print(
            "Warning: --openai-response-mode-autoswitch=retry_to_diff is ineffective when "
            "--syntax-preflight-max-cycles < 2."
        )

    cases_dir = _REPO_ROOT / args.cases_dir
    try:
        cases = load_cases(cases_dir)
    except ValueError as e:
        print(f"Invalid case metadata: {e}")
        return 2

    wanted_case_ids = set(args.case)
    case_list_ids: list[str] = []
    for case_list_file in args.case_list_file:
        path = Path(str(case_list_file)).expanduser()
        if not path.exists():
            print(f"Case-list file not found: {path}")
            return 2
        if not path.is_file():
            print(f"Case-list path is not a file: {path}")
            return 2
        try:
            content = path.read_text(encoding="utf-8")
        except (OSError, UnicodeError) as exc:
            print(f"Failed to read case-list file {path}: {exc}")
            return 2
        for line in content.splitlines():
            normalized = line.strip()
            if not normalized or normalized.startswith("#"):
                continue
            case_list_ids.append(normalized)
    wanted_case_ids.update(case_list_ids)
    if wanted_case_ids:
        available_case_ids = {case.case_id for case in cases}
        unknown_case_ids = sorted(wanted_case_ids.difference(available_case_ids))
        if unknown_case_ids:
            print(
                "Unknown case id(s) requested via --case/--case-list-file: "
                + ", ".join(unknown_case_ids)
            )
            return 2
        cases = [c for c in cases if c.case_id in wanted_case_ids]
    if args.category:
        wanted_categories = {x.strip() for x in args.category if x.strip()}
        cases = [c for c in cases if c.category in wanted_categories]
    if args.difficulty:
        wanted_difficulties = {x.strip() for x in args.difficulty if x.strip()}
        cases = [c for c in cases if c.difficulty in wanted_difficulties]
    if args.snapshot_dependency:
        wanted_snapshot_dependency = {x.strip() for x in args.snapshot_dependency if x.strip()}
        cases = [c for c in cases if c.snapshot_dependency in wanted_snapshot_dependency]
    if args.bug_source:
        wanted_bug_sources = {x.strip() for x in args.bug_source if x.strip()}
        cases = [c for c in cases if (c.bug_source or "hand_crafted") in wanted_bug_sources]
    if args.max_cases > 0:
        cases = cases[: args.max_cases]

    if not cases:
        print("No cases found.")
        return 2

    try:
        conditions = [Condition(c.strip()) for c in args.conditions.split(",") if c.strip()]
    except ValueError as exc:
        print(f"Invalid --conditions value: {exc}")
        return 2
    run_id = str(args.run_id).strip() or (
        time.strftime("%Y%m%dT%H%M%S") + "-" + uuid.uuid4().hex[:8]
    )

    if not str(args.adaptive_online_state_path).strip():
        args.adaptive_online_state_path = str(
            _REPO_ROOT / "evals" / "artifacts" / "policies" / f"{run_id}.json"
        )
        if config_state.value_sources.get("adaptive_online_state_path") == "default":
            config_state.value_sources["adaptive_online_state_path"] = "derived_from_run_id"
    if not str(args.adaptive_online_events_path).strip():
        args.adaptive_online_events_path = str(
            _REPO_ROOT / "evals" / "results" / f"{run_id}.adaptive_policy.jsonl"
        )
        if config_state.value_sources.get("adaptive_online_events_path") == "default":
            config_state.value_sources["adaptive_online_events_path"] = "derived_from_run_id"
    if not str(args.adaptive_budget_online_state_path).strip():
        args.adaptive_budget_online_state_path = str(
            _REPO_ROOT
            / "evals"
            / "artifacts"
            / "policies"
            / f"{run_id}.adaptive_budget_policy.json"
        )
        if config_state.value_sources.get("adaptive_budget_online_state_path") == "default":
            config_state.value_sources["adaptive_budget_online_state_path"] = "derived_from_run_id"
    if not str(args.adaptive_budget_online_events_path).strip():
        args.adaptive_budget_online_events_path = str(
            _REPO_ROOT / "evals" / "results" / f"{run_id}.adaptive_budget_policy.jsonl"
        )
        if config_state.value_sources.get("adaptive_budget_online_events_path") == "default":
            config_state.value_sources["adaptive_budget_online_events_path"] = "derived_from_run_id"

    artifacts_root = _REPO_ROOT / "evals" / "artifacts" / run_id
    results_root = _REPO_ROOT / "evals" / "results"
    results_root.mkdir(parents=True, exist_ok=True)
    results_path = results_root / f"{run_id}.jsonl"
    run_results_lock: _RunResultsLock | None = None
    results_writer: _SynchronizedJsonlWriter | None = None

    def _close_run_results_io() -> None:
        nonlocal run_results_lock, results_writer
        if results_writer is not None:
            results_writer.close()
            results_writer = None
        if run_results_lock is not None:
            run_results_lock.release()
            run_results_lock = None

    execution_units = [(case, condition) for case in cases for condition in conditions]
    total_units = len(execution_units)
    run_settings_fingerprint = _build_run_settings_fingerprint(
        args=args, cases=cases, conditions=conditions
    )
    try:
        resolved_config_lock_path = _write_resolved_run_config_lockfile(
            run_id=run_id,
            args=args,
            argv_tokens=argv_tokens,
            run_settings_fingerprint=run_settings_fingerprint,
            config_state=config_state,
            repo_root=_REPO_ROOT,
        )
    except OSError as exc:
        resolved_config_lock_path = None
        print(f"Warning: failed to write resolved config lock file: {exc}")

    run_results_lock, run_lock_error, run_lock_warning = _RunResultsLock.acquire(
        results_path=results_path,
        run_id=run_id,
    )
    if run_lock_error is not None:
        print(f"Run aborted: {run_lock_error}")
        return 2
    if run_lock_warning is not None:
        print(f"Run lock warning: {run_lock_warning}")

    try:
        (
            existing_completed_units,
            existing_run_metadata,
            existing_run_settings_fingerprint,
            existing_records_count,
            trailing_tail_issue,
        ) = _load_existing_run_state(
            results_path=results_path,
            run_id=run_id,
            max_attempts_per_unit=int(args.k),
        )
    except ValueError as exc:
        _close_run_results_io()
        print(f"Resume aborted: {exc}")
        return 2
    if trailing_tail_issue is not None:
        try:
            sidecar_path, removed_bytes = _repair_trailing_results_tail(
                results_path=results_path,
                run_id=run_id,
                issue=trailing_tail_issue,
            )
        except OSError as exc:
            _close_run_results_io()
            print(
                "Resume aborted: failed to repair malformed trailing JSON record: "
                f"{type(exc).__name__}: {exc}"
            )
            return 2
        print(
            "Resume repair: "
            f"truncated malformed trailing JSON record at line {trailing_tail_issue.line_no} "
            f"(removed_bytes={removed_bytes}, backup={sidecar_path})"
        )
    if existing_records_count > 0:
        if not existing_run_settings_fingerprint:
            _close_run_results_io()
            print(
                "Resume aborted: existing results are missing run_settings_fingerprint; "
                "cannot safely resume."
            )
            return 2
        if existing_run_settings_fingerprint != run_settings_fingerprint:
            print(
                "Resume warning: run settings fingerprint mismatch for existing run_id; "
                "continuing with existing completed-unit state."
            )

    planned_unit_keys = {(case.case_id, condition.value) for case, condition in execution_units}
    resumed_completed_units = {key for key in existing_completed_units if key in planned_unit_keys}
    skipped_units = len(resumed_completed_units)
    remaining_units = total_units - skipped_units

    if remaining_units > 0 and execution_config.executor == "testcontainers":
        ok, message = check_testcontainers_runtime_available()
        if not ok:
            _close_run_results_io()
            print(f"Environment check failed: {message}")
            return 2
        removed, failed, cleanup_error = cleanup_stale_eval_containers(
            grace_sec=DEFAULT_CONTAINER_CLEANUP_STALE_GRACE_SEC
        )
        if cleanup_error is not None:
            print(f"Container cleanup skipped: {cleanup_error}")
        elif removed > 0 or failed > 0:
            print(f"Container cleanup: removed={removed}, failed={failed}")

    print(f"Run: {run_id}")
    if config_state.config_paths:
        print("Config: " + ", ".join(config_state.config_paths))
    if resolved_config_lock_path is not None:
        print(f"Resolved config lock: {resolved_config_lock_path}")
    print(f"Patcher: {args.patcher}")
    print(f"Executor: {execution_config.executor}")
    print(f"Jobs: {int(args.jobs)}")
    print(f"Progress verbosity: {args.progress_verbosity}")
    print(f"Progress heartbeat sec: {float(args.progress_heartbeat_sec):.1f}")
    print(f"Transport fail-fast streak: {int(args.transport_fail_fast_streak)}")
    print(f"Runner force patch attempt: {'on' if args.force_patch_attempt else 'off'}")
    print(
        "Syntax preflight: "
        f"{'on' if args.syntax_preflight_enabled else 'off'} "
        f"(max_cycles={int(args.syntax_preflight_max_cycles)})"
    )
    print(f"Patch sanitize guard: {'on' if args.patch_sanitize_enabled else 'off'}")
    print(
        "Adaptive online policy: "
        f"mode={args.adaptive_online_policy_mode!s}, "
        f"algo={args.adaptive_online_policy_algo!s}, "
        f"alpha={float(args.adaptive_online_alpha):.3f}, "
        f"epsilon={float(args.adaptive_online_epsilon):.3f}"
    )
    if str(args.adaptive_online_policy_mode) != "off":
        print(f"Adaptive online policy state: {args.adaptive_online_state_path}")
        print(f"Adaptive online policy events: {args.adaptive_online_events_path}")
    print(
        "Adaptive budget policy: "
        f"mode={args.adaptive_budget_policy_mode!s}, "
        f"algo={args.adaptive_budget_policy_algo!s}, "
        f"tiers={args.adaptive_budget_policy_tiers!s}, "
        f"max_ups={int(args.adaptive_budget_policy_max_ups_per_case)}"
    )
    if str(args.adaptive_budget_policy_mode) != "off":
        print(f"Adaptive budget policy state: {args.adaptive_budget_online_state_path}")
        print(f"Adaptive budget policy events: {args.adaptive_budget_online_events_path}")
    print(f"Results: {results_path}")
    print(f"Progress: total={total_units}, completed={skipped_units}, remaining={remaining_units}")
    if remaining_units <= 0:
        _close_run_results_io()
        print("Nothing to do: all planned case/condition units are already complete.")
        return 0

    results_writer = _SynchronizedJsonlWriter(results_path)
    jobs = int(args.jobs)
    try:
        patcher = make_patcher(args)
    except BaseException:
        _close_run_results_io()
        raise
    patcher_name = str(getattr(patcher, "name", str(args.patcher)))
    if patcher_name != str(args.patcher):
        print(f"Patcher resolved: {patcher_name}")
    try:
        (
            openai_context_window_tokens_effective,
            openai_context_window_info,
        ) = _resolve_openai_context_window_for_run(
            args=args,
            patcher=patcher,
        )
    except BaseException:
        _close_run_results_io()
        raise
    openai_context_window_source = str(
        openai_context_window_info.get("openai_context_window_source") or "disabled"
    )
    openai_context_window_probe_provider = str(
        openai_context_window_info.get("openai_context_window_probe_provider") or ""
    )
    openai_context_window_probe_error = str(
        openai_context_window_info.get("openai_context_window_probe_error") or ""
    )
    if patcher_name == "openai":
        print(
            "OpenAI context window guard: "
            f"effective={openai_context_window_tokens_effective} "
            f"source={openai_context_window_source}"
        )
        if openai_context_window_probe_provider:
            print(f"OpenAI context probe provider: {openai_context_window_probe_provider}")
        if openai_context_window_probe_error:
            print(f"OpenAI context probe warning: {openai_context_window_probe_error}")
        print(f"OpenAI response autoswitch: {args.openai_response_mode_autoswitch}")

    if isinstance(existing_run_metadata, dict):
        run_metadata = dict(existing_run_metadata)
        run_metadata.setdefault("run_id", run_id)
        run_metadata.setdefault("run_settings_fingerprint", run_settings_fingerprint)
    else:
        started_at_utc = (
            datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
        )
        run_metadata = _build_run_metadata(
            args=args,
            patcher_name=patcher_name,
            run_id=run_id,
            started_at_utc=started_at_utc,
            run_settings_fingerprint=run_settings_fingerprint,
        )
    if config_state.config_paths:
        run_metadata.setdefault("config_files", list(config_state.config_paths))
    if resolved_config_lock_path is not None:
        run_metadata.setdefault("resolved_config_lock_path", str(resolved_config_lock_path))
    patcher_config = run_metadata.get("patcher_config")
    if isinstance(patcher_config, dict):
        patcher_config["openai_auto_context_window"] = bool(
            getattr(args, "openai_auto_context_window", False)
        )
        patcher_config["openai_response_mode_autoswitch"] = str(
            getattr(args, "openai_response_mode_autoswitch", "off")
        )
        patcher_config["openai_context_window_tokens_effective"] = int(
            openai_context_window_tokens_effective
        )
        patcher_config["openai_context_window_source"] = openai_context_window_source
        patcher_config["openai_context_window_probe_provider"] = (
            openai_context_window_probe_provider
        )
        patcher_config["openai_context_window_probe_error"] = openai_context_window_probe_error

    try:
        online_policy_config = OnlinePolicyConfig.normalize(
            mode=str(args.adaptive_online_policy_mode),
            algorithm=str(args.adaptive_online_policy_algo),
            alpha=float(args.adaptive_online_alpha),
            epsilon=float(args.adaptive_online_epsilon),
            l2=float(args.adaptive_online_l2),
            warmup_decisions=int(args.adaptive_online_warmup_decisions),
            save_every=int(args.adaptive_online_save_every),
            feature_scaling_profile=str(args.adaptive_online_feature_scaling_profile),
            state_path=Path(str(args.adaptive_online_state_path)).expanduser().resolve(),
            events_path=Path(str(args.adaptive_online_events_path)).expanduser().resolve(),
            random_seed=(int(args.seed) if args.seed is not None else None),
        )
    except BaseException:
        _close_run_results_io()
        raise
    if online_policy_config.mode == "off":
        online_policy_controller: AdaptiveOnlinePolicyController | None = None
    else:
        try:
            online_policy_controller = AdaptiveOnlinePolicyController(online_policy_config)
        except BaseException:
            _close_run_results_io()
            raise
        if online_policy_controller.load_error:
            print(
                "Online adaptive policy state load warning: "
                f"{online_policy_controller.load_error}; starting with a fresh state."
            )

    _ACTIVE_ONLINE_POLICY_CONTROLLER = online_policy_controller

    try:
        budget_policy_config = BudgetPolicyConfig.normalize(
            mode=str(args.adaptive_budget_policy_mode),
            algorithm=str(args.adaptive_budget_policy_algo),
            alpha=float(args.adaptive_budget_online_alpha),
            epsilon=float(args.adaptive_budget_online_epsilon),
            l2=float(args.adaptive_budget_online_l2),
            warmup_decisions=int(args.adaptive_budget_online_warmup_decisions),
            save_every=int(args.adaptive_budget_online_save_every),
            feature_scaling_profile=str(args.adaptive_budget_feature_scaling_profile),
            state_path=Path(str(args.adaptive_budget_online_state_path)).expanduser().resolve(),
            events_path=Path(str(args.adaptive_budget_online_events_path)).expanduser().resolve(),
            random_seed=(int(args.seed) if args.seed is not None else None),
        )
    except BaseException:
        _close_run_results_io()
        raise
    if budget_policy_config.mode == "off":
        budget_policy_controller: AdaptiveBudgetPolicyController | None = None
    else:
        try:
            budget_policy_controller = AdaptiveBudgetPolicyController(budget_policy_config)
        except BaseException:
            _close_run_results_io()
            raise
        if budget_policy_controller.load_error:
            print(
                "Online adaptive budget policy state load warning: "
                f"{budget_policy_controller.load_error}; starting with a fresh state."
            )

    _ACTIVE_BUDGET_POLICY_CONTROLLER = budget_policy_controller

    previous_run_settings = _ACTIVE_RUN_SETTINGS
    previous_execution_config = set_active_execution_config(execution_config)
    _ACTIVE_RUN_SETTINGS = _ActiveRunSettings(
        deterministic=bool(args.deterministic),
        seed=args.seed if args.deterministic else None,
    )
    run_started = time.monotonic()
    completed_units = 0
    executed_units = 0
    runtime_errors: list[tuple[str, str, str]] = []
    transport_fail_fast_streak = max(0, int(args.transport_fail_fast_streak))
    transport_failure_streak = 0
    transport_fail_fast_triggered = False
    transport_fail_fast_reason = ""
    transport_fail_fast_skipped_units = 0
    transport_fail_fast_skip_reported = False

    def _estimate_remaining_eta_seconds(*, elapsed_sec: float) -> float | None:
        """Estimate wall-clock ETA using observed throughput.

        This is parallel-aware by design: ``executed_units / elapsed`` already
        reflects the effective throughput of the configured worker count, retry
        mix, and runtime overheads.
        """
        remaining_units = total_units - completed_units
        if remaining_units <= 0:
            return 0.0
        if executed_units <= 0 or elapsed_sec <= 0:
            return None
        units_per_sec = executed_units / elapsed_sec
        if units_per_sec <= 0:
            return None
        return remaining_units / units_per_sec

    def _extract_progress_metrics(
        case_records: list[dict[str, Any]],
    ) -> tuple[int, int, float, float]:
        """Return (attempts_used, max_attempts, total_llm_sec, total_test_sec).

        For test time: count the initial ``before_duration_sec`` once, then
        only ``after_duration_sec`` for each attempt (the next attempt's
        "before" is the previous attempt's "after", so this avoids
        double-counting).
        """
        attempts_used = len(case_records)
        max_attempts = 0
        if case_records:
            meta = case_records[0].get("attempt_metadata")
            if isinstance(meta, dict):
                max_attempts = int(meta.get("max_attempts", 0) or 0)

        total_llm_sec = 0.0
        total_test_sec = (
            float(case_records[0].get("before_duration_sec", 0) or 0) if case_records else 0.0
        )
        for r in case_records:
            pm = r.get("patch_meta")
            if isinstance(pm, dict):
                total_llm_sec += float(pm.get("propose_duration_sec", 0) or 0)
            total_test_sec += float(r.get("after_duration_sec", 0) or 0)

        return attempts_used, max_attempts, total_llm_sec, total_test_sec

    def _append_records_safely(case_records: list[dict[str, Any]]) -> None:
        if not case_records:
            return
        if results_writer is None:
            raise RuntimeError("results writer is not initialized")
        for record in case_records:
            results_writer.append_one(record)

    def _handle_outcome(outcome: UnitExecutionOutcome) -> bool:
        nonlocal completed_units
        nonlocal executed_units
        nonlocal transport_failure_streak
        nonlocal transport_fail_fast_triggered
        nonlocal transport_fail_fast_reason
        unit_duration = max(0.0, float(outcome.unit_duration_sec))
        completed_units += 1
        executed_units += 1
        elapsed = time.monotonic() - run_started
        eta = _estimate_remaining_eta_seconds(elapsed_sec=elapsed)
        progress_pct = 100.0 * (completed_units / total_units) if total_units else 100.0
        case_id = outcome.case.case_id
        condition_value = outcome.condition.value
        if outcome.runtime_error is not None:
            reason = _single_line_error(outcome.runtime_error)
            runtime_errors.append((case_id, condition_value, reason))
            print(
                f"[{completed_units}/{total_units} {progress_pct:.1f}%] "
                f"ERROR {case_id}/{condition_value} reason={reason} "
                f"unit={_format_duration(unit_duration)} "
                f"elapsed={_format_duration(elapsed)} "
                f"eta={_format_duration(eta)}"
            )
            if transport_fail_fast_streak > 0:
                if _is_transport_runtime_error(reason):
                    transport_failure_streak += 1
                else:
                    transport_failure_streak = 0
                if (
                    transport_failure_streak >= transport_fail_fast_streak
                    and not transport_fail_fast_triggered
                ):
                    transport_fail_fast_triggered = True
                    transport_fail_fast_reason = (
                        f"{transport_failure_streak} consecutive transport/crash runtime failures"
                    )
                    print(
                        "Fail-fast triggered: "
                        f"{transport_fail_fast_reason} "
                        f"(threshold={transport_fail_fast_streak})."
                    )
                    return True
            return False
        case_records = outcome.case_records or []
        if not outcome.records_streamed:
            _append_records_safely(case_records)
        unit_success = any(bool(rec.get("success")) for rec in case_records)
        status = "success" if unit_success else "failed"
        attempts_used, max_attempts, total_llm_sec, total_test_sec = _extract_progress_metrics(
            case_records
        )
        first_note = str(case_records[0].get("note") or "") if case_records else ""
        k_str = "k=0" if first_note == "no_patch_needed" else f"k={attempts_used}/{max_attempts}"
        print(
            f"[{completed_units}/{total_units} {progress_pct:.1f}%] "
            f"DONE {case_id}/{condition_value} status={status} "
            f"{k_str} llm={_format_duration(total_llm_sec)} test={_format_duration(total_test_sec)} "
            f"unit={_format_duration(unit_duration)} "
            f"elapsed={_format_duration(elapsed)} "
            f"eta={_format_duration(eta)}",
            flush=True,
        )
        # Warn when a failed case used fewer attempts than allowed (early stop).
        if status == "failed" and attempts_used < max_attempts and max_attempts > 0:
            terminal_rec = case_records[-1] if case_records else {}
            terminal_pm = terminal_rec.get("patch_meta") if isinstance(terminal_rec, dict) else None
            early_reason: str | None = None
            if isinstance(terminal_pm, dict):
                if terminal_pm.get("case_fail_fast_triggered"):
                    early_reason = str(terminal_pm.get("case_fail_fast_reason", "fail_fast"))
                elif terminal_pm.get("adaptive_progress_stop_triggered"):
                    early_reason = str(
                        terminal_pm.get(
                            "adaptive_progress_stop_note",
                            terminal_pm.get("adaptive_progress_stop_reason", "adaptive_stop"),
                        )
                    )
            skipped = max_attempts - attempts_used
            reason_str = f": {early_reason}" if early_reason else ""
            print(
                f"    ^ early stop{reason_str} "
                f"(skipped {skipped} remaining attempt{'s' if skipped != 1 else ''})",
                flush=True,
            )
        if transport_fail_fast_streak <= 0:
            return False
        terminal_record = case_records[-1] if case_records else None
        terminal_transport_failure = bool(
            isinstance(terminal_record, dict)
            and _record_is_transport_request_failure(terminal_record)
        )
        if terminal_transport_failure:
            transport_failure_streak += 1
        else:
            transport_failure_streak = 0
        if transport_failure_streak < transport_fail_fast_streak:
            return False
        if not transport_fail_fast_triggered:
            transport_fail_fast_triggered = True
            transport_fail_fast_reason = (
                f"{transport_failure_streak} consecutive transport patcher request failures"
            )
            print(
                "Fail-fast triggered: "
                f"{transport_fail_fast_reason} "
                f"(threshold={transport_fail_fast_streak})."
            )
        return True

    pending_units: list[tuple[CaseSpec, Condition]] = []
    for case, condition in execution_units:
        unit_key = (case.case_id, condition.value)
        if unit_key in resumed_completed_units:
            completed_units += 1
            progress_pct = 100.0 * (completed_units / total_units) if total_units else 100.0
            print(
                f"[{completed_units}/{total_units} {progress_pct:.1f}%] "
                f"SKIP (resumed) {case.case_id}/{condition.value}",
                flush=True,
            )
            continue
        pending_units.append((case, condition))

    def _stream_result_record(record: dict[str, Any]) -> None:
        if results_writer is None:
            raise RuntimeError("results writer is not initialized")
        results_writer.append_one(record)

    shared_unit_kwargs: dict[str, Any] = {
        "artifacts_root": artifacts_root,
        "patcher_args": args,
        "k": int(args.k),
        "output_format": str(args.output_format),
        "run_id": run_id,
        "include_files_context": not bool(args.no_files_context),
        "max_context_chars": int(args.max_context_chars),
        "case_fail_fast_on_request_budget_exceeded": bool(
            args.case_fail_fast_on_request_budget_exceeded
        ),
        "force_patch_attempt": bool(args.force_patch_attempt),
        "context_protocol": str(args.context_protocol),
        "context_stage_a_max_requests": int(args.context_stage_a_max_requests),
        "context_stage_a_max_chars": int(args.context_stage_a_max_chars),
        "retry_prompt_mode": str(args.retry_prompt_mode),
        "retry_context_mode": str(args.retry_context_mode),
        "retry_history_max_chars": int(args.retry_history_max_chars),
        "pytest_output_tail_lines": int(
            getattr(args, "pytest_output_tail_lines", DEFAULT_PYTEST_OUTPUT_TAIL_LINES)
        ),
        "snapshot_artifact_max_string_chars": int(
            getattr(
                args,
                "snapshot_artifact_max_string_chars",
                DEFAULT_SNAPSHOT_ARTIFACT_MAX_STRING_CHARS,
            )
        ),
        "prompt_budget_total_chars": int(args.prompt_budget_total_chars),
        "prompt_budget_retry_delta_chars": int(args.prompt_budget_retry_delta_chars),
        "prompt_budget_stdout_chars": int(args.prompt_budget_stdout_chars),
        "prompt_budget_snapshot_chars": int(args.prompt_budget_snapshot_chars),
        "prompt_budget_evidence_chars": int(args.prompt_budget_evidence_chars),
        "prompt_budget_retry_history_chars": int(args.prompt_budget_retry_history_chars),
        "context_compaction_mode": str(args.context_compaction_mode),
        "context_compaction_scope": str(args.context_compaction_scope),
        "context_compaction_max_input_chars": int(args.context_compaction_max_input_chars),
        "context_compaction_target_chars": int(args.context_compaction_target_chars),
        "context_compaction_max_calls": int(args.context_compaction_max_calls),
        "openai_context_window_tokens": int(openai_context_window_tokens_effective),
        "openai_context_window_source": openai_context_window_source,
        "openai_context_window_probe_provider": openai_context_window_probe_provider,
        "openai_context_window_probe_error": openai_context_window_probe_error,
        "openai_response_mode_autoswitch": str(args.openai_response_mode_autoswitch),
        "adaptive_gate_policy": str(args.adaptive_gate_policy),
        "adaptive_max_helper_calls": int(args.adaptive_max_helper_calls),
        "adaptive_budget_topup_max_calls": int(args.adaptive_budget_topup_max_calls),
        "adaptive_stagnation_window": int(args.adaptive_stagnation_window),
        "adaptive_saturation_threshold": float(args.adaptive_saturation_threshold),
        "adaptive_no_progress_token_cap": int(args.adaptive_no_progress_token_cap),
        "adaptive_helper_budget_profile": str(args.adaptive_helper_budget_profile),
        "adaptive_openai_helper_response_mode": str(args.adaptive_openai_helper_response_mode),
        "adaptive_informativeness_pregate": bool(args.adaptive_informativeness_pregate),
        "adaptive_informativeness_pregate_threshold": float(
            args.adaptive_informativeness_pregate_threshold
        ),
        "syntax_preflight_enabled": bool(args.syntax_preflight_enabled),
        "syntax_preflight_max_cycles": int(args.syntax_preflight_max_cycles),
        "patch_sanitize_enabled": bool(args.patch_sanitize_enabled),
        "patch_verifier_infer_test_source_scope": bool(args.patch_verifier_infer_test_source_scope),
        "adaptive_online_policy_controller": online_policy_controller,
        "adaptive_online_policy_mode": str(args.adaptive_online_policy_mode),
        "adaptive_online_policy_algo": str(args.adaptive_online_policy_algo),
        "adaptive_online_feature_scaling_profile": str(
            args.adaptive_online_feature_scaling_profile
        ),
        "adaptive_budget_policy_controller": budget_policy_controller,
        "adaptive_budget_policy_mode": str(args.adaptive_budget_policy_mode),
        "adaptive_budget_policy_algo": str(args.adaptive_budget_policy_algo),
        "adaptive_budget_feature_scaling_profile": str(
            args.adaptive_budget_feature_scaling_profile
        ),
        "adaptive_budget_policy_tiers": str(args.adaptive_budget_policy_tiers),
        "adaptive_budget_policy_max_ups_per_case": int(
            args.adaptive_budget_policy_max_ups_per_case
        ),
        "adaptive_budget_reward_rho_budget_exceeded": float(
            args.adaptive_budget_reward_rho_budget_exceeded
        ),
        "adaptive_reward_lambda_tokens": float(args.adaptive_reward_lambda_tokens),
        "adaptive_reward_mu_latency": float(args.adaptive_reward_mu_latency),
        "adaptive_reward_token_scale": float(args.adaptive_reward_token_scale),
        "adaptive_reward_latency_scale": float(args.adaptive_reward_latency_scale),
        "adaptive_reward_eta_progress": float(args.adaptive_reward_eta_progress),
        "adaptive_reward_kappa_stagnation": float(args.adaptive_reward_kappa_stagnation),
        "run_metadata": run_metadata,
        "record_prompt": str(args.record_prompt),
        "record_evidence": str(args.record_evidence),
        "deterministic": bool(args.deterministic),
        "seed": args.seed if args.deterministic else None,
        "keep_workdirs": bool(args.keep_workdirs),
        "progress_verbosity": str(args.progress_verbosity),
        "progress_heartbeat_sec": float(args.progress_heartbeat_sec),
        "record_sink": _stream_result_record,
    }

    if execution_config.executor == "testcontainers" and pending_units:
        pool_size = max(1, jobs)
        print(f"Container pool: starting {pool_size} container(s)...", flush=True)
        try:
            _ACTIVE_CONTAINER_POOL = _EvalContainerPool(
                size=pool_size,
                execution=execution_config,
                workdirs_host=artifacts_root / "workdirs",
            )
            print(f"Container pool: {pool_size} container(s) ready", flush=True)
        except Exception as pool_exc:
            print(
                f"Container pool: failed to start ({type(pool_exc).__name__}: {pool_exc}), "
                "falling back to per-call containers",
                flush=True,
            )

    try:
        if jobs == 1:
            for unit_index, (case, condition) in enumerate(pending_units):
                progress_pct = 100.0 * (completed_units / total_units) if total_units else 100.0
                print(
                    f"[{completed_units + 1}/{total_units} {progress_pct:.1f}%] "
                    f"START {case.case_id}/{condition.value}",
                    flush=True,
                )
                outcome = _execute_eval_unit(
                    case=case,
                    condition=condition,
                    patcher_override=patcher,
                    **shared_unit_kwargs,
                )
                should_abort = _handle_outcome(outcome)
                if should_abort:
                    remaining_units = max(0, len(pending_units) - (unit_index + 1))
                    if remaining_units > 0:
                        transport_fail_fast_skipped_units += remaining_units
                        print(f"Fail-fast: skipping {remaining_units} not-yet-started unit(s).")
                    break
        else:
            future_map: dict[Any, tuple[CaseSpec, Condition]] = {}
            pending_iter = iter(pending_units)
            submitted_units = 0

            def _submit_next(executor: ThreadPoolExecutor) -> bool:
                nonlocal submitted_units
                try:
                    case, condition = next(pending_iter)
                except StopIteration:
                    return False
                progress_pct = 100.0 * (completed_units / total_units) if total_units else 100.0
                ordinal = completed_units + len(future_map) + 1
                print(
                    f"[{ordinal}/{total_units} {progress_pct:.1f}%] "
                    f"START {case.case_id}/{condition.value}",
                    flush=True,
                )
                future = executor.submit(
                    _execute_eval_unit,
                    case=case,
                    condition=condition,
                    patcher_override=None,
                    **shared_unit_kwargs,
                )
                future_map[future] = (case, condition)
                submitted_units += 1
                return True

            with ThreadPoolExecutor(max_workers=jobs) as executor:
                while (
                    len(future_map) < jobs
                    and (not transport_fail_fast_triggered)
                    and _submit_next(executor)
                ):
                    pass
                while future_map:
                    future = next(as_completed(tuple(future_map.keys())))
                    case, condition = future_map[future]
                    del future_map[future]
                    try:
                        outcome = future.result()
                    except Exception as exc:
                        outcome = UnitExecutionOutcome(
                            case=case,
                            condition=condition,
                            case_records=None,
                            unit_duration_sec=0.0,
                            runtime_error=f"{type(exc).__name__}: {exc}",
                        )
                    should_abort = _handle_outcome(outcome)
                    if should_abort and (not transport_fail_fast_skip_reported):
                        cancelled = 0
                        for pending_future in list(future_map):
                            if pending_future.cancel():
                                del future_map[pending_future]
                                cancelled += 1
                        unscheduled = max(0, len(pending_units) - submitted_units)
                        skipped_now = cancelled + unscheduled
                        if skipped_now > 0:
                            transport_fail_fast_skipped_units += skipped_now
                            print(f"Fail-fast: skipping {skipped_now} pending/unscheduled unit(s).")
                        transport_fail_fast_skip_reported = True
                    while (
                        len(future_map) < jobs
                        and (not transport_fail_fast_triggered)
                        and _submit_next(executor)
                    ):
                        pass
    finally:
        if _ACTIVE_CONTAINER_POOL is not None:
            _ACTIVE_CONTAINER_POOL.shutdown()
            _ACTIVE_CONTAINER_POOL = None
        if _ACTIVE_ONLINE_POLICY_CONTROLLER is not None:
            try:
                _ACTIVE_ONLINE_POLICY_CONTROLLER.close()
            except Exception as exc:
                print(f"Online adaptive policy close warning: {type(exc).__name__}: {exc}")
            _ACTIVE_ONLINE_POLICY_CONTROLLER = None
        if _ACTIVE_BUDGET_POLICY_CONTROLLER is not None:
            try:
                _ACTIVE_BUDGET_POLICY_CONTROLLER.close()
            except Exception as exc:
                print(f"Online adaptive budget policy close warning: {type(exc).__name__}: {exc}")
            _ACTIVE_BUDGET_POLICY_CONTROLLER = None
        _close_run_results_io()
        _ACTIVE_RUN_SETTINGS = previous_run_settings
        set_active_execution_config(previous_execution_config)

    total_elapsed = time.monotonic() - run_started
    errors_suffix = f", errors={len(runtime_errors)}" if runtime_errors else ""
    print(
        "Progress: finished "
        f"executed={executed_units}, skipped={skipped_units}, total={total_units}, "
        f"elapsed={_format_duration(total_elapsed)}{errors_suffix}"
    )
    if runtime_errors:
        print(
            "Eval finished with "
            f"{len(runtime_errors)} runtime error(s). "
            "Rerun with the same --run-id to retry unfinished units."
        )
    if transport_fail_fast_triggered:
        print(
            "Eval aborted early by transport fail-fast: "
            f"{transport_fail_fast_reason} "
            f"(threshold={transport_fail_fast_streak}, "
            f"skipped_units={transport_fail_fast_skipped_units}). "
            "Rerun with --transport-fail-fast-streak=0 to disable."
        )
        return 2
    if runtime_errors:
        return 2
    return 0


def _single_line_error(raw: str, *, max_chars: int = 240) -> str:
    text = " | ".join(segment.strip() for segment in str(raw).splitlines() if segment.strip())
    if not text:
        text = str(raw).strip() or "unknown runtime error"
    if len(text) <= max_chars:
        return text
    return text[: max_chars - 3] + "..."


def _is_transport_runtime_error(message: str) -> bool:
    text = str(message or "").strip().lower()
    if not text:
        return False
    markers = (
        "segmentation fault",
        "connection refused",
        "connection reset",
        "brokenpipeerror",
        "remotedisconnected",
        "temporarily unavailable",
        "server disconnected",
        "failed to establish a new connection",
    )
    return any(marker in text for marker in markers)


def _record_is_transport_request_failure(record: dict[str, Any]) -> bool:
    note = str(record.get("note") or "")
    if note != "patcher_request_failed":
        return False
    patch_meta = record.get("patch_meta")
    if not isinstance(patch_meta, dict):
        return False
    if bool(patch_meta.get("patcher_request_failed")):
        return str(patch_meta.get("patcher_request_failure_kind") or "") == "transport"
    failed, kind = _classify_patcher_request_failure(patch_meta)
    return bool(failed and kind == "transport")


def _execute_eval_unit(
    *,
    case: CaseSpec,
    condition: Condition,
    artifacts_root: Path,
    patcher_args: argparse.Namespace,
    patcher_override: Any | None,
    k: int,
    output_format: str,
    run_id: str,
    include_files_context: bool,
    max_context_chars: int,
    case_fail_fast_on_request_budget_exceeded: bool,
    force_patch_attempt: bool,
    context_protocol: str,
    context_stage_a_max_requests: int,
    context_stage_a_max_chars: int,
    retry_prompt_mode: str,
    retry_context_mode: str,
    retry_history_max_chars: int,
    pytest_output_tail_lines: int,
    snapshot_artifact_max_string_chars: int,
    prompt_budget_total_chars: int,
    prompt_budget_retry_delta_chars: int,
    prompt_budget_stdout_chars: int,
    prompt_budget_snapshot_chars: int,
    prompt_budget_evidence_chars: int,
    prompt_budget_retry_history_chars: int,
    context_compaction_mode: str,
    context_compaction_scope: str,
    context_compaction_max_input_chars: int,
    context_compaction_target_chars: int,
    context_compaction_max_calls: int,
    openai_context_window_tokens: int,
    openai_context_window_source: str,
    openai_context_window_probe_provider: str,
    openai_context_window_probe_error: str,
    openai_response_mode_autoswitch: str,
    adaptive_gate_policy: str,
    adaptive_max_helper_calls: int,
    adaptive_budget_topup_max_calls: int,
    adaptive_stagnation_window: int,
    adaptive_saturation_threshold: float,
    adaptive_no_progress_token_cap: int,
    adaptive_helper_budget_profile: str,
    adaptive_openai_helper_response_mode: str,
    adaptive_informativeness_pregate: bool,
    adaptive_informativeness_pregate_threshold: float,
    syntax_preflight_enabled: bool,
    syntax_preflight_max_cycles: int,
    patch_sanitize_enabled: bool,
    patch_verifier_infer_test_source_scope: bool,
    adaptive_online_policy_controller: AdaptiveOnlinePolicyController | None,
    adaptive_online_policy_mode: str,
    adaptive_online_policy_algo: str,
    adaptive_online_feature_scaling_profile: str,
    adaptive_budget_policy_controller: AdaptiveBudgetPolicyController | None,
    adaptive_budget_policy_mode: str,
    adaptive_budget_policy_algo: str,
    adaptive_budget_feature_scaling_profile: str,
    adaptive_budget_policy_tiers: str,
    adaptive_budget_policy_max_ups_per_case: int,
    adaptive_budget_reward_rho_budget_exceeded: float,
    adaptive_reward_lambda_tokens: float,
    adaptive_reward_mu_latency: float,
    adaptive_reward_token_scale: float,
    adaptive_reward_latency_scale: float,
    adaptive_reward_eta_progress: float,
    adaptive_reward_kappa_stagnation: float,
    run_metadata: dict[str, Any],
    record_prompt: str,
    record_evidence: str,
    deterministic: bool,
    seed: int | None,
    keep_workdirs: bool,
    progress_verbosity: str,
    progress_heartbeat_sec: float,
    record_sink: Callable[[dict[str, Any]], None] | None,
) -> UnitExecutionOutcome:
    """Execute a single (case, condition) eval unit with *k* attempts.

    Copies the case directory into a workdir, runs baseline pytest,
    manages the propose-verify loop, handles adaptive gating/budget,
    and streams JSONL records.  Called from ``main`` via thread-pool.
    """
    unit_started = time.monotonic()
    normalized_progress_verbosity = _normalized_progress_verbosity(progress_verbosity)
    normalized_progress_heartbeat_sec = max(0.0, float(progress_heartbeat_sec))
    workdir = artifacts_root / "workdirs" / case.case_id / condition.value
    context_dir_base = artifacts_root / "contexts" / case.case_id / condition.value

    runtime_error: str | None = None
    case_records: list[dict[str, Any]] | None = None
    observed_records_count = 0
    persisted_records_count = 0
    progress_state_lock = threading.Lock()
    progress_state: dict[str, Any] = {
        "phase": "initializing",
        "phase_started_monotonic": unit_started,
        "attempt": 0,
        "max_attempts": int(k),
        "syntax_cycle": 0,
        "syntax_max_cycles": 0,
        "last_note": "",
    }
    heartbeat_stop_event = threading.Event()
    heartbeat_thread: threading.Thread | None = None

    def _coerce_nonnegative_float(raw: Any) -> float:
        try:
            return max(0.0, float(raw))
        except (TypeError, ValueError):
            return 0.0

    def _snapshot_progress_state() -> dict[str, Any]:
        with progress_state_lock:
            return dict(progress_state)

    def _update_progress_state(event: str, payload: dict[str, Any] | None = None) -> None:
        now = time.monotonic()
        with progress_state_lock:
            progress_state["phase"] = str(event or "running")
            progress_state["phase_started_monotonic"] = now
            if isinstance(payload, dict):
                for key in ("attempt", "max_attempts", "syntax_cycle", "syntax_max_cycles"):
                    value = payload.get(key)
                    if isinstance(value, int):
                        progress_state[key] = int(value)
                note_value = payload.get("note")
                if note_value is not None:
                    progress_state["last_note"] = str(note_value)

        if _progress_is_enabled(normalized_progress_verbosity, "trace"):
            snapshot = _snapshot_progress_state()
            attempt_value = int(snapshot.get("attempt") or 0)
            max_attempts_value = int(snapshot.get("max_attempts") or 0)
            attempt_fragment = (
                f" attempt={attempt_value}/{max_attempts_value}"
                if attempt_value > 0 and max_attempts_value > 0
                else (f" attempt={attempt_value}" if attempt_value > 0 else "")
            )
            syntax_cycle = int(snapshot.get("syntax_cycle") or 0)
            syntax_max_cycles = int(snapshot.get("syntax_max_cycles") or 0)
            syntax_fragment = (
                f" syntax_cycle={syntax_cycle}/{syntax_max_cycles}"
                if syntax_cycle > 0 and syntax_max_cycles > 0
                else ""
            )
            _progress_print(
                verbosity=normalized_progress_verbosity,
                level="trace",
                message=(
                    f"[TRACE] {case.case_id}/{condition.value} "
                    f"phase={snapshot.get('phase', 'running')}{attempt_fragment}{syntax_fragment}"
                ),
            )

    def _emit_progress_heartbeat() -> None:
        snapshot = _snapshot_progress_state()
        now = time.monotonic()
        unit_elapsed = _format_duration_precise(now - unit_started)
        phase_started = _coerce_nonnegative_float(
            snapshot.get("phase_started_monotonic", unit_started)
        )
        if phase_started <= 0.0:
            phase_started = unit_started
        phase_elapsed = _format_duration_precise(now - phase_started)
        attempt_value = int(snapshot.get("attempt") or 0)
        max_attempts_value = int(snapshot.get("max_attempts") or 0)
        attempt_fragment = (
            f" attempt={attempt_value}/{max_attempts_value}"
            if attempt_value > 0 and max_attempts_value > 0
            else (f" attempt={attempt_value}" if attempt_value > 0 else "")
        )
        note_text = str(snapshot.get("last_note") or "").strip()
        note_fragment = f" note={note_text}" if note_text else ""
        _progress_print(
            verbosity=normalized_progress_verbosity,
            level="detailed",
            message=(
                f"[HEARTBEAT] {case.case_id}/{condition.value}{attempt_fragment} "
                f"phase={snapshot.get('phase', 'running')} "
                f"records={observed_records_count} "
                f"unit={unit_elapsed} phase_elapsed={phase_elapsed}{note_fragment}"
            ),
        )

    def _on_progress_update(event: str, payload: dict[str, Any] | None = None) -> None:
        _update_progress_state(event, payload)

    def _on_record(record: dict[str, Any]) -> None:
        nonlocal observed_records_count
        nonlocal persisted_records_count
        observed_records_count += 1
        should_persist_record = condition != Condition.ADAPTIVE_LLM_DISCRETION
        if record_sink is not None and should_persist_record:
            record_sink(record)
            persisted_records_count += 1
        note = str(record.get("note") or "")
        attempt = int(record.get("attempt") or 0)
        _update_progress_state(
            "record_emitted",
            {
                "attempt": attempt,
                "max_attempts": int(k),
                "note": note,
            },
        )
        if _progress_is_enabled(normalized_progress_verbosity, "detailed"):
            patch_meta = record.get("patch_meta")
            propose_sec = (
                _coerce_nonnegative_float(patch_meta.get("propose_duration_sec"))
                if isinstance(patch_meta, dict)
                else 0.0
            )
            test_after_sec = _coerce_nonnegative_float(record.get("after_duration_sec"))
            attempt_wall_sec = _coerce_nonnegative_float(record.get("attempt_wall_clock_sec"))
            status_label = "success" if bool(record.get("success")) else "failed"
            _progress_print(
                verbosity=normalized_progress_verbosity,
                level="detailed",
                message=(
                    f"[ATTEMPT] {case.case_id}/{condition.value} "
                    f"attempt={attempt}/{int(k)} status={status_label} note={note or 'none'} "
                    f"wall={_format_duration_precise(attempt_wall_sec)} "
                    f"llm={_format_duration_precise(propose_sec)} "
                    f"test_after={_format_duration_precise(test_after_sec)}"
                ),
            )

    if normalized_progress_heartbeat_sec > 0.0 and _progress_is_enabled(
        normalized_progress_verbosity, "detailed"
    ):

        def _heartbeat_loop() -> None:
            while not heartbeat_stop_event.wait(normalized_progress_heartbeat_sec):
                _emit_progress_heartbeat()

        heartbeat_thread = threading.Thread(
            target=_heartbeat_loop,
            name=f"eval-heartbeat-{case.case_id}-{condition.value}",
            daemon=True,
        )
        heartbeat_thread.start()

    try:
        _update_progress_state("setup_workdir")
        workdir.parent.mkdir(parents=True, exist_ok=True)
        context_dir_base.mkdir(parents=True, exist_ok=True)

        if workdir.exists():
            shutil.rmtree(workdir)
        shutil.copytree(
            case.path,
            workdir,
            dirs_exist_ok=True,
            ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"),
        )

        patcher = patcher_override if patcher_override is not None else make_patcher(patcher_args)
        _update_progress_state("run_one_condition")
        case_records = run_one_condition(
            case=case,
            condition=condition,
            workdir=workdir,
            context_dir_base=context_dir_base,
            patcher=patcher,
            k=k,
            output_format=output_format,
            run_id=run_id,
            include_files_context=include_files_context,
            max_context_chars=max_context_chars,
            case_fail_fast_on_request_budget_exceeded=case_fail_fast_on_request_budget_exceeded,
            force_patch_attempt=force_patch_attempt,
            context_protocol=context_protocol,
            context_stage_a_max_requests=context_stage_a_max_requests,
            context_stage_a_max_chars=context_stage_a_max_chars,
            retry_prompt_mode=retry_prompt_mode,
            retry_context_mode=retry_context_mode,
            retry_history_max_chars=retry_history_max_chars,
            pytest_output_tail_lines=pytest_output_tail_lines,
            snapshot_artifact_max_string_chars=snapshot_artifact_max_string_chars,
            prompt_budget_total_chars=prompt_budget_total_chars,
            prompt_budget_retry_delta_chars=prompt_budget_retry_delta_chars,
            prompt_budget_stdout_chars=prompt_budget_stdout_chars,
            prompt_budget_snapshot_chars=prompt_budget_snapshot_chars,
            prompt_budget_evidence_chars=prompt_budget_evidence_chars,
            prompt_budget_retry_history_chars=prompt_budget_retry_history_chars,
            context_compaction_mode=context_compaction_mode,
            context_compaction_scope=context_compaction_scope,
            context_compaction_max_input_chars=context_compaction_max_input_chars,
            context_compaction_target_chars=context_compaction_target_chars,
            context_compaction_max_calls=context_compaction_max_calls,
            openai_context_window_tokens=openai_context_window_tokens,
            openai_context_window_source=openai_context_window_source,
            openai_context_window_probe_provider=openai_context_window_probe_provider,
            openai_context_window_probe_error=openai_context_window_probe_error,
            openai_response_mode_autoswitch=openai_response_mode_autoswitch,
            adaptive_gate_policy=adaptive_gate_policy,
            adaptive_max_helper_calls=adaptive_max_helper_calls,
            adaptive_budget_topup_max_calls=adaptive_budget_topup_max_calls,
            adaptive_stagnation_window=adaptive_stagnation_window,
            adaptive_saturation_threshold=adaptive_saturation_threshold,
            adaptive_no_progress_token_cap=adaptive_no_progress_token_cap,
            adaptive_helper_budget_profile=adaptive_helper_budget_profile,
            adaptive_openai_helper_response_mode=adaptive_openai_helper_response_mode,
            adaptive_informativeness_pregate=adaptive_informativeness_pregate,
            adaptive_informativeness_pregate_threshold=adaptive_informativeness_pregate_threshold,
            syntax_preflight_enabled=syntax_preflight_enabled,
            syntax_preflight_max_cycles=syntax_preflight_max_cycles,
            patch_sanitize_enabled=patch_sanitize_enabled,
            patch_verifier_infer_test_source_scope=patch_verifier_infer_test_source_scope,
            adaptive_online_policy_controller=adaptive_online_policy_controller,
            adaptive_online_policy_mode=adaptive_online_policy_mode,
            adaptive_online_policy_algo=adaptive_online_policy_algo,
            adaptive_online_feature_scaling_profile=adaptive_online_feature_scaling_profile,
            adaptive_budget_policy_controller=adaptive_budget_policy_controller,
            adaptive_budget_policy_mode=adaptive_budget_policy_mode,
            adaptive_budget_policy_algo=adaptive_budget_policy_algo,
            adaptive_budget_feature_scaling_profile=adaptive_budget_feature_scaling_profile,
            adaptive_budget_policy_tiers=adaptive_budget_policy_tiers,
            adaptive_budget_policy_max_ups_per_case=adaptive_budget_policy_max_ups_per_case,
            adaptive_budget_reward_rho_budget_exceeded=adaptive_budget_reward_rho_budget_exceeded,
            adaptive_reward_lambda_tokens=adaptive_reward_lambda_tokens,
            adaptive_reward_mu_latency=adaptive_reward_mu_latency,
            adaptive_reward_token_scale=adaptive_reward_token_scale,
            adaptive_reward_latency_scale=adaptive_reward_latency_scale,
            adaptive_reward_eta_progress=adaptive_reward_eta_progress,
            adaptive_reward_kappa_stagnation=adaptive_reward_kappa_stagnation,
            run_metadata=run_metadata,
            record_prompt=record_prompt,
            record_evidence=record_evidence,
            deterministic=deterministic,
            seed=seed,
            record_sink=_on_record,
            progress_update=_on_progress_update,
        )
        _update_progress_state(
            "unit_completed",
            {
                "note": "completed",
            },
        )
    except RuntimeError as exc:
        runtime_error = str(exc)
    except Exception as exc:
        runtime_error = f"{type(exc).__name__}: {exc}"
    finally:
        heartbeat_stop_event.set()
        if heartbeat_thread is not None:
            heartbeat_thread.join(timeout=0.25)
        if not keep_workdirs:
            shutil.rmtree(workdir, ignore_errors=True)

    return UnitExecutionOutcome(
        case=case,
        condition=condition,
        case_records=case_records,
        unit_duration_sec=time.monotonic() - unit_started,
        runtime_error=runtime_error,
        records_streamed=persisted_records_count > 0,
    )


def _format_duration(seconds: float | None) -> str:
    if seconds is None:
        return "unknown"
    whole_seconds = max(0, round(seconds))
    hours, rem = divmod(whole_seconds, 3600)
    minutes, secs = divmod(rem, 60)
    if hours:
        return f"{hours}h{minutes:02d}m{secs:02d}s"
    if minutes:
        return f"{minutes}m{secs:02d}s"
    return f"{secs}s"


def _build_run_settings_fingerprint(
    *,
    args: argparse.Namespace,
    cases: list[CaseSpec],
    conditions: list[Condition],
) -> str:
    """Compute a SHA-256 fingerprint of the run-level settings."""
    payload = {
        "cases_dir": str(args.cases_dir),
        "selected_case_ids": [case.case_id for case in cases],
        "conditions": [condition.value for condition in conditions],
        "patcher_config": _build_patcher_config(args=args, patcher_name=str(args.patcher)),
        "record_prompt": str(args.record_prompt),
        "record_evidence": str(args.record_evidence),
        "record_env": sorted(key.strip() for key in args.record_env if str(key).strip()),
        "record_deps": str(args.record_deps),
        "retry_context_mode": str(args.retry_context_mode),
        "retry_history_max_chars": int(args.retry_history_max_chars),
        "prompt_budget_retry_history_chars": int(args.prompt_budget_retry_history_chars),
    }
    return _sha256_jsonlike(payload)


def _load_existing_run_state(
    *,
    results_path: Path,
    run_id: str,
    max_attempts_per_unit: int,
) -> tuple[
    set[tuple[str, str]],
    dict[str, Any] | None,
    str | None,
    int,
    _TrailingResultsTailIssue | None,
]:
    """Scan an existing results JSONL to determine completed units.

    Returns:
        Tuple of (completed_unit_keys, first_run_metadata,
        settings_fingerprint, records_count, trailing_tail_issue).
    """
    if not results_path.exists():
        return set(), None, None, 0, None

    per_unit_stats: dict[tuple[str, str], dict[str, Any]] = {}
    first_run_metadata: dict[str, Any] | None = None
    settings_fingerprint: str | None = None
    records_count = 0
    trailing_tail_issue: _TrailingResultsTailIssue | None = None

    def _consume_record(*, record: dict[str, Any], line_no: int) -> None:
        nonlocal first_run_metadata, settings_fingerprint, records_count

        record_run_id = record.get("run_id")
        if not isinstance(record_run_id, str) or not record_run_id:
            raise ValueError(f"{results_path}:{line_no}: run_id must be a non-empty string")
        if record_run_id != run_id:
            raise ValueError(
                f"{results_path}:{line_no}: run_id mismatch ({record_run_id!r} != {run_id!r})"
            )

        case_id = record.get("case_id")
        if not isinstance(case_id, str) or not case_id:
            raise ValueError(f"{results_path}:{line_no}: case_id must be a non-empty string")
        condition = record.get("condition")
        if not isinstance(condition, str) or not condition:
            raise ValueError(f"{results_path}:{line_no}: condition must be a non-empty string")
        attempt = record.get("attempt")
        if not isinstance(attempt, int) or attempt < 0:
            raise ValueError(f"{results_path}:{line_no}: attempt must be a non-negative integer")
        success = record.get("success")
        if not isinstance(success, bool):
            raise ValueError(f"{results_path}:{line_no}: success must be a boolean")

        run_metadata = record.get("run_metadata")
        if not isinstance(run_metadata, dict):
            raise ValueError(f"{results_path}:{line_no}: run_metadata must be an object")
        if run_metadata.get("run_id") != run_id:
            raise ValueError(f"{results_path}:{line_no}: run_metadata.run_id must match run_id")

        current_fingerprint = run_metadata.get("run_settings_fingerprint")
        if current_fingerprint is not None and (
            not isinstance(current_fingerprint, str) or not current_fingerprint
        ):
            raise ValueError(
                f"{results_path}:{line_no}: run_settings_fingerprint must be a non-empty string"
            )
        if isinstance(current_fingerprint, str):
            if settings_fingerprint is None:
                settings_fingerprint = current_fingerprint
            elif settings_fingerprint != current_fingerprint:
                raise ValueError(
                    f"{results_path}:{line_no}: inconsistent run_settings_fingerprint values"
                )

        if first_run_metadata is None:
            first_run_metadata = dict(run_metadata)

        unit_key = (case_id, condition)
        unit_stats = per_unit_stats.setdefault(
            unit_key,
            {"max_attempt": -1, "any_success": False},
        )
        unit_stats["max_attempt"] = max(int(unit_stats["max_attempt"]), attempt)
        if success:
            unit_stats["any_success"] = True
        records_count += 1

    def _parse_record_from_line_bytes(*, line_bytes: bytes, line_no: int) -> dict[str, Any]:
        try:
            line_text = line_bytes.decode("utf-8")
        except UnicodeDecodeError as exc:
            raise ValueError(
                f"{results_path}:{line_no}: invalid UTF-8 ({type(exc).__name__}: {exc})"
            ) from exc
        try:
            record = json.loads(line_text)
        except json.JSONDecodeError as exc:
            raise ValueError(
                f"{results_path}:{line_no}: invalid JSON ({type(exc).__name__}: {exc})"
            ) from exc
        if not isinstance(record, dict):
            raise ValueError(f"{results_path}:{line_no}: record must be a JSON object")
        return record

    pending_line_no: int | None = None
    pending_line_start_offset: int = 0
    pending_line_bytes: bytes | None = None
    offset_bytes = 0
    with results_path.open("rb") as handle:
        for line_no, line_bytes in enumerate(handle, start=1):
            line_start_offset = offset_bytes
            offset_bytes += len(line_bytes)
            if not line_bytes.strip():
                continue
            if pending_line_no is not None and pending_line_bytes is not None:
                record = _parse_record_from_line_bytes(
                    line_bytes=pending_line_bytes,
                    line_no=pending_line_no,
                )
                _consume_record(record=record, line_no=pending_line_no)
            pending_line_no = line_no
            pending_line_start_offset = line_start_offset
            pending_line_bytes = line_bytes

    if pending_line_no is not None and pending_line_bytes is not None:
        try:
            trailing_record = _parse_record_from_line_bytes(
                line_bytes=pending_line_bytes,
                line_no=pending_line_no,
            )
        except ValueError as exc:
            error_text = str(exc)
            is_parse_error = "invalid JSON" in error_text or "invalid UTF-8" in error_text
            if not is_parse_error:
                raise
            trailing_tail_issue = _TrailingResultsTailIssue(
                line_no=pending_line_no,
                truncate_offset_bytes=pending_line_start_offset,
                raw_line_bytes=pending_line_bytes,
                parse_error=error_text,
            )
        else:
            _consume_record(record=trailing_record, line_no=pending_line_no)

    required_attempts = max(1, int(max_attempts_per_unit))
    completed_units = {
        unit_key
        for unit_key, stats in per_unit_stats.items()
        if bool(stats.get("any_success")) or int(stats.get("max_attempt", -1)) >= required_attempts
    }
    return (
        completed_units,
        first_run_metadata,
        settings_fingerprint,
        records_count,
        trailing_tail_issue,
    )


def _repair_trailing_results_tail(
    *,
    results_path: Path,
    run_id: str,
    issue: _TrailingResultsTailIssue,
) -> tuple[Path, int]:
    """Truncate a corrupt trailing record and archive the removed bytes.

    Returns:
        Tuple of (sidecar_backup_path, removed_bytes_count).
    """
    original_size = int(results_path.stat().st_size)
    truncate_offset = int(issue.truncate_offset_bytes)
    if truncate_offset < 0 or truncate_offset > original_size:
        raise OSError(f"invalid truncate offset {truncate_offset} for file size {original_size}")
    with results_path.open("rb") as source:
        source.seek(truncate_offset)
        removed_tail_bytes = source.read()

    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    sidecar_path = results_path.with_name(f"{results_path.name}.corrupt-tail-{timestamp}.log")
    suffix_index = 1
    while sidecar_path.exists():
        sidecar_path = results_path.with_name(
            f"{results_path.name}.corrupt-tail-{timestamp}.{suffix_index}.log"
        )
        suffix_index += 1

    header = (
        "# llmdebug eval trailing-tail repair backup\n"
        f"run_id={run_id}\n"
        f"results_path={results_path}\n"
        f"line_no={issue.line_no}\n"
        f"truncate_offset_bytes={truncate_offset}\n"
        f"original_size_bytes={original_size}\n"
        f"parse_error={issue.parse_error}\n\n"
    ).encode("utf-8", errors="strict")
    with sidecar_path.open("wb") as sidecar:
        sidecar.write(header)
        sidecar.write(removed_tail_bytes)

    with results_path.open("r+b") as handle:
        handle.truncate(truncate_offset)
        handle.flush()

    removed_bytes = original_size - truncate_offset
    return sidecar_path, removed_bytes


def load_cases(cases_dir: Path) -> list[CaseSpec]:
    """Load and validate all eval cases from *cases_dir*.

    Each subdirectory must contain a ``case.json`` with required fields
    (category, difficulty, snapshot_dependency, expected_exception).

    Raises:
        ValueError: If any case metadata is missing or invalid.
    """
    if not cases_dir.exists():
        return []

    out: list[CaseSpec] = []
    for child in sorted(cases_dir.iterdir()):
        if not child.is_dir():
            continue
        case_id = child.name
        meta_path = child / "case.json"
        meta: dict[str, Any] = {}
        if meta_path.exists():
            try:
                meta = json.loads(meta_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError as e:
                raise ValueError(f"{case_id}: invalid JSON in case.json: {e}") from e
            except OSError as e:
                raise ValueError(f"{case_id}: cannot read case.json: {e}") from e

        category = meta.get("category")
        if not isinstance(category, str) or category not in VALID_CATEGORIES:
            raise ValueError(f"{case_id}: category must be one of {sorted(VALID_CATEGORIES)}")

        difficulty = meta.get("difficulty")
        if not isinstance(difficulty, str) or difficulty not in VALID_DIFFICULTIES:
            raise ValueError(f"{case_id}: difficulty must be one of {sorted(VALID_DIFFICULTIES)}")

        snapshot_dependency = meta.get("snapshot_dependency")
        if (
            not isinstance(snapshot_dependency, str)
            or snapshot_dependency not in VALID_SNAPSHOT_DEPENDENCIES
        ):
            raise ValueError(
                f"{case_id}: snapshot_dependency must be one of {sorted(VALID_SNAPSHOT_DEPENDENCIES)}"
            )

        expected_exception = meta.get("expected_exception")
        if not isinstance(expected_exception, str) or not expected_exception:
            raise ValueError(f"{case_id}: expected_exception must be a non-empty string")

        tags_raw = meta.get("tags")
        if tags_raw is None:
            tags = []
        elif isinstance(tags_raw, list) and all(isinstance(x, str) and x for x in tags_raw):
            tags = list(tags_raw)
        else:
            raise ValueError(f"{case_id}: tags must be an array of non-empty strings")

        python_args_raw = meta.get("python_args")
        if (
            not isinstance(python_args_raw, list)
            or not python_args_raw
            or not all(isinstance(x, str) and x for x in python_args_raw)
        ):
            python_args = ["-m", "pytest", "-q"]
        else:
            python_args = list(python_args_raw)

        timeout_sec_raw = meta.get("timeout_sec")
        if not isinstance(timeout_sec_raw, int) or timeout_sec_raw <= 0:
            timeout_sec = 120
        else:
            timeout_sec = timeout_sec_raw

        description = meta.get("description") if isinstance(meta.get("description"), str) else None

        # Extended optional fields for imported cases.
        bug_source = meta.get("bug_source") if isinstance(meta.get("bug_source"), str) else None
        contamination_risk = (
            meta.get("contamination_risk")
            if isinstance(meta.get("contamination_risk"), str)
            else None
        )
        expected_fix_lines_raw = meta.get("expected_fix_lines")
        expected_fix_lines = (
            expected_fix_lines_raw
            if isinstance(expected_fix_lines_raw, int) and expected_fix_lines_raw > 0
            else None
        )
        source_id = meta.get("source_id") if isinstance(meta.get("source_id"), str) else None
        difficulty_rationale = (
            meta.get("difficulty_rationale")
            if isinstance(meta.get("difficulty_rationale"), str)
            else None
        )

        out.append(
            CaseSpec(
                case_id=case_id,
                path=child,
                category=category,
                difficulty=difficulty,
                snapshot_dependency=snapshot_dependency,
                expected_exception=expected_exception,
                tags=tags,
                python_args=python_args,
                timeout_sec=timeout_sec,
                description=description,
                bug_source=bug_source,
                contamination_risk=contamination_risk,
                expected_fix_lines=expected_fix_lines,
                source_id=source_id,
                difficulty_rationale=difficulty_rationale,
            )
        )
    return out


def make_patcher(args: argparse.Namespace):
    """Instantiate the patcher backend selected by ``--patcher``.

    Returns one of ``OpenAICompatPatcher``, ``HeuristicSnapshotPatcher``,
    ``NullPatcher``, or ``CommandPatcher`` depending on *args.patcher*.
    """
    from evals.patchers import CommandPatcher, HeuristicSnapshotPatcher, NullPatcher
    from evals.patchers.command_patcher import patcher_from_env

    if args.patcher == "openai":
        from evals.patchers import OpenAICompatPatcher

        model = (args.openai_model or "").strip()
        if not model:
            raise SystemExit(
                "--openai-model is required for --patcher openai (or set OPENAI_MODEL)."
            )
        api_key = (args.openai_api_key or "").strip() or None
        return OpenAICompatPatcher(
            base_url=str(args.openai_base_url),
            model=model,
            api_key=api_key,
            timeout_sec=float(args.openai_timeout_sec),
            http2=bool(getattr(args, "openai_http2", True)),
            transport_max_retries=int(args.openai_transport_max_retries),
            transport_retry_backoff_base_sec=float(args.openai_transport_retry_backoff_base_sec),
            transport_retry_backoff_max_sec=float(args.openai_transport_retry_backoff_max_sec),
            max_propose_sec=float(args.openai_max_propose_sec),
            max_tokens=int(args.openai_max_tokens),
            temperature=float(args.openai_temperature),
            response_mode=str(args.openai_response_mode),
            force_patch_attempt=bool(args.openai_force_patch_attempt),
            seed=args.seed if bool(args.deterministic) else None,
        )

    if args.patcher == "heuristic":
        return HeuristicSnapshotPatcher()
    if args.patcher == "null":
        return NullPatcher()

    if args.patch_cmd:
        return CommandPatcher(
            cmd=str(args.patch_cmd),
            shell=bool(args.patch_cmd_shell),
            timeout_sec=float(args.patch_cmd_timeout_sec),
        )
    env_patcher = patcher_from_env()
    if env_patcher is None:
        raise SystemExit(
            "Command patcher selected but no command configured. "
            "Set --patch-cmd or LLMDEBUG_EVAL_PATCH_CMD."
        )
    return env_patcher


def _resolve_openai_context_window_for_run(
    *,
    args: argparse.Namespace,
    patcher: Any,
) -> tuple[int, dict[str, Any]]:
    """Resolve the effective OpenAI context-window size for the run.

    Combines manual ``--openai-context-window-tokens`` with optional
    automatic probing.  Returns (effective_tokens, info_dict).
    """
    manual_tokens = max(0, int(getattr(args, "openai_context_window_tokens", 0)))
    info: dict[str, Any] = {
        "openai_context_window_tokens_effective": manual_tokens,
        "openai_context_window_source": "manual_flag" if manual_tokens > 0 else "disabled",
        "openai_context_window_probe_provider": "",
        "openai_context_window_probe_error": "",
    }
    if manual_tokens > 0:
        return manual_tokens, info

    if str(getattr(args, "patcher", "")) != "openai":
        return 0, info
    if str(getattr(patcher, "name", "")) != "openai":
        return 0, info
    if not bool(getattr(args, "openai_auto_context_window", False)):
        return 0, info

    probe_context_window = getattr(patcher, "probe_context_window_tokens", None)
    if not callable(probe_context_window):
        info["openai_context_window_source"] = "auto_probe_unavailable"
        info["openai_context_window_probe_error"] = "patcher_missing_probe_context_window_tokens"
        return 0, info

    try:
        probe_tokens_raw, probe_meta_raw = probe_context_window()
    except Exception as exc:
        info["openai_context_window_source"] = "auto_probe_failed"
        info["openai_context_window_probe_error"] = f"{type(exc).__name__}: {exc}"
        return 0, info

    probe_meta = dict(probe_meta_raw) if isinstance(probe_meta_raw, dict) else {}
    provider = str(
        probe_meta.get("context_window_probe_provider") or probe_meta.get("provider") or ""
    )
    source = str(probe_meta.get("context_window_source") or "auto_probe")
    probe_error = str(probe_meta.get("context_window_probe_error") or probe_meta.get("error") or "")
    info["openai_context_window_probe_provider"] = provider

    try:
        probe_tokens = int(probe_tokens_raw)
    except (TypeError, ValueError):
        probe_tokens = 0
    if probe_tokens > 0:
        info["openai_context_window_tokens_effective"] = probe_tokens
        info["openai_context_window_source"] = source
        info["openai_context_window_probe_error"] = ""
        return probe_tokens, info

    info["openai_context_window_source"] = "auto_probe_failed"
    info["openai_context_window_probe_error"] = probe_error or "probe_returned_no_context_window"
    return 0, info


from evals.eval_evidence import (  # noqa: E402
    _coerce_context_request_payload,
    _collect_file_index_lines,
    _compute_evidence_fingerprint,
    _crash_file_from_snapshot,
    _detect_failing_test_file,
    _evidence_ids_from_items,
    _extract_subprocess_script_neighbors,
    _import_neighbors,
    _merge_evidence_items,
    _render_evidence_index,
    _render_evidence_items,
    _sha256_jsonlike,
    _sha256_text,
    _strip_eval_only_case_artifacts,
    build_fallback_context_bundle,
    collect_text_context,
    execute_context_requests,
)


def _request_context_failure_kind_from_meta(meta: dict[str, Any] | None) -> str:
    if not isinstance(meta, dict):
        return ""
    explicit = str(meta.get("request_context_failure_kind") or "").strip()
    if explicit:
        return explicit
    error = str(meta.get("error") or "").strip()
    if error == "request_budget_exceeded":
        return "request_budget_exceeded"
    if bool(meta.get("request_context_backend_unavailable")):
        return "backend_unavailable"
    if error == "invalid_request_context_payload":
        return "invalid_payload"
    if error == "request_failed":
        return "transport"
    if error == "http_error":
        status = _nonnegative_int(meta.get("status"), default=0)
        if status in {502, 503, 504}:
            return "backend_unavailable"
        return "transport"
    return ""


def _request_context_retry_counts_from_meta(meta: dict[str, Any] | None) -> tuple[int, int]:
    if not isinstance(meta, dict):
        return 0, 0
    retry_count = _nonnegative_int(meta.get("request_context_retry_count"), default=0)
    transport_retry_count = _nonnegative_int(
        meta.get("request_context_transport_retry_count"),
        default=0,
    )
    return retry_count, transport_retry_count


def _adaptive_helper_outcome_for_attempt(
    *,
    helper_invoked: bool,
    helper_materialized_context: bool,
    helper_decision_reason: str,
    stage_a_meta: dict[str, Any] | None,
) -> tuple[str, str]:
    decision_reason = str(helper_decision_reason or "").strip()
    failure_kind = _request_context_failure_kind_from_meta(stage_a_meta)
    if helper_invoked:
        if helper_materialized_context:
            return ADAPTIVE_HELPER_OUTCOME_MATERIALIZED, failure_kind
        if failure_kind == "backend_unavailable":
            return ADAPTIVE_HELPER_OUTCOME_UNAVAILABLE_BACKEND, failure_kind
        if failure_kind == "transport":
            return ADAPTIVE_HELPER_OUTCOME_TRANSPORT_FAILED, failure_kind
        if failure_kind == "request_budget_exceeded":
            return ADAPTIVE_HELPER_OUTCOME_REQUEST_BUDGET_EXCEEDED, failure_kind
        if failure_kind == "invalid_payload" or decision_reason == "llm_invalid_response_no_helper":
            return ADAPTIVE_HELPER_OUTCOME_INVALID_PAYLOAD, failure_kind or "invalid_payload"
        return ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT, failure_kind

    if decision_reason == "request_context_unavailable":
        return ADAPTIVE_HELPER_OUTCOME_SKIPPED_UNSUPPORTED, ""
    if decision_reason in {
        "informativeness_pregate_skip",
        "helper_budget_exhausted",
        "helper_saturation_guard",
    }:
        return ADAPTIVE_HELPER_OUTCOME_SKIPPED_GUARDRAIL, ""
    if decision_reason in {"not_adaptive_condition", ""}:
        return ADAPTIVE_HELPER_OUTCOME_NOT_INVOKED, ""
    return ADAPTIVE_HELPER_OUTCOME_SKIPPED_POLICY, ""


def _nonnegative_int(value: Any, *, default: int = 0) -> int:
    try:
        coerced = int(value)
    except (TypeError, ValueError):
        return int(default)
    return max(0, coerced)


def _usage_token_counts_from_usage_payload(usage: Any) -> tuple[int, int, int]:
    if not isinstance(usage, dict):
        return 0, 0, 0

    prompt_tokens = _nonnegative_int(usage.get("prompt_tokens"), default=0)
    completion_tokens = _nonnegative_int(usage.get("completion_tokens"), default=0)
    # Anthropic-compatible payloads often use input/output token fields.
    if prompt_tokens == 0 and completion_tokens == 0:
        prompt_tokens = _nonnegative_int(usage.get("input_tokens"), default=0)
        completion_tokens = _nonnegative_int(usage.get("output_tokens"), default=0)

    total_tokens_raw = usage.get("total_tokens")
    if total_tokens_raw is None:
        total_tokens = prompt_tokens + completion_tokens
    else:
        total_tokens = _nonnegative_int(total_tokens_raw, default=prompt_tokens + completion_tokens)
    return prompt_tokens, completion_tokens, total_tokens


def _usage_token_counts_from_patch_meta(patch_meta: dict[str, Any] | None) -> tuple[int, int, int]:
    if not isinstance(patch_meta, dict):
        return 0, 0, 0

    usage_prompt, usage_completion, usage_total = _usage_token_counts_from_usage_payload(
        patch_meta.get("usage")
    )
    prompt_tokens = _nonnegative_int(patch_meta.get("tokens_prompt"), default=usage_prompt)
    completion_tokens = _nonnegative_int(
        patch_meta.get("tokens_completion"),
        default=usage_completion,
    )
    total_tokens = _nonnegative_int(
        patch_meta.get("tokens_total"),
        default=usage_total if usage_total > 0 else (prompt_tokens + completion_tokens),
    )
    return prompt_tokens, completion_tokens, total_tokens


def _llm_call_count_from_meta(meta: dict[str, Any] | None, *, default: int = 0) -> int:
    if not isinstance(meta, dict):
        return max(0, int(default))
    value = meta.get("llm_call_count")
    if isinstance(value, int) and value >= 0:
        return value
    return max(0, int(default))


def _aggregate_attempt_llm_usage(
    *,
    stage_a_meta: dict[str, Any] | None,
    patch_meta: dict[str, Any] | None,
    stage_a_default_llm_calls: int,
    patch_default_llm_calls: int,
) -> tuple[int, int, int, int]:
    """Sum LLM call counts and token usage across Stage-A and patch steps."""
    stage_calls = _llm_call_count_from_meta(stage_a_meta, default=stage_a_default_llm_calls)
    patch_calls = _llm_call_count_from_meta(patch_meta, default=patch_default_llm_calls)
    stage_prompt, stage_completion, stage_total = _usage_token_counts_from_patch_meta(stage_a_meta)
    patch_prompt, patch_completion, patch_total = _usage_token_counts_from_patch_meta(patch_meta)
    return (
        stage_calls + patch_calls,
        stage_prompt + patch_prompt,
        stage_completion + patch_completion,
        stage_total + patch_total,
    )


def _apply_usage_totals_to_patch_meta(
    *,
    patch_meta: dict[str, Any],
    llm_call_count: int,
    tokens_prompt: int,
    tokens_completion: int,
    tokens_total: int,
) -> None:
    normalized_calls = _nonnegative_int(llm_call_count, default=0)
    normalized_prompt = _nonnegative_int(tokens_prompt, default=0)
    normalized_completion = _nonnegative_int(tokens_completion, default=0)
    normalized_total = _nonnegative_int(
        tokens_total,
        default=normalized_prompt + normalized_completion,
    )
    patch_meta["llm_call_count"] = normalized_calls
    patch_meta["tokens_prompt"] = normalized_prompt
    patch_meta["tokens_completion"] = normalized_completion
    patch_meta["tokens_total"] = normalized_total
    patch_meta["usage"] = {
        "prompt_tokens": normalized_prompt,
        "completion_tokens": normalized_completion,
        "total_tokens": normalized_total,
    }


def _sync_attempt_usage_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    llm_call_count: int,
) -> None:
    prompt_tokens, completion_tokens, total_tokens = _usage_token_counts_from_patch_meta(patch_meta)
    normalized_calls = _nonnegative_int(llm_call_count, default=0)
    attempt_metadata["llm_call_count"] = normalized_calls
    attempt_metadata["tokens_prompt"] = prompt_tokens
    attempt_metadata["tokens_completion"] = completion_tokens
    attempt_metadata["tokens_total"] = total_tokens
    patch_meta["llm_call_count"] = normalized_calls
    patch_meta["tokens_prompt"] = prompt_tokens
    patch_meta["tokens_completion"] = completion_tokens
    patch_meta["tokens_total"] = total_tokens


def _summarize_context_compaction_meta(meta: dict[str, Any]) -> dict[str, Any]:
    def _clip(value: Any, *, max_chars: int = 320) -> str:
        text = str(value or "").strip()
        if len(text) <= max_chars:
            return text
        return text[: max_chars - 3].rstrip() + "..."

    out: dict[str, Any] = {}
    for key in (
        "llm_call_count",
        "compact_context_duration_sec",
        "compact_context_target_chars",
        "compact_context_input_chars",
        "compact_context_output_chars",
        "compact_context_output_truncated",
        "api_compat_seed_requested",
        "api_compat_seed_acknowledged",
        "api_compat_seed_response",
    ):
        if key in meta:
            out[key] = meta[key]
    status_value = meta.get("status")
    if isinstance(status_value, int):
        out["status"] = status_value
    usage = meta.get("usage")
    if isinstance(usage, dict):
        prompt_tokens, completion_tokens, total_tokens = _usage_token_counts_from_usage_payload(
            usage
        )
        out["usage"] = {
            "prompt_tokens": prompt_tokens,
            "completion_tokens": completion_tokens,
            "total_tokens": total_tokens,
        }
    for key in ("error", "exc", "reason", "fallback_reason"):
        text = _clip(meta.get(key), max_chars=240)
        if text:
            out[key] = text
    body_text = _clip(meta.get("body"), max_chars=320)
    if body_text:
        out["body"] = body_text
    return out


def _sync_syntax_preflight_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    enabled: bool,
    cycles_used: int,
    failures: int,
    last_error_type: str,
    last_error_msg: str,
    events: list[dict[str, Any]],
) -> None:
    patch_meta["syntax_preflight_enabled"] = bool(enabled)
    patch_meta["syntax_preflight_cycles_used"] = int(cycles_used)
    patch_meta["syntax_preflight_failures"] = int(failures)
    patch_meta["syntax_preflight_last_error_type"] = str(last_error_type or "")
    patch_meta["syntax_preflight_last_error_msg"] = str(last_error_msg or "")
    patch_meta["syntax_preflight_events"] = list(events)
    attempt_metadata["syntax_preflight_enabled"] = bool(enabled)
    attempt_metadata["syntax_preflight_cycles_used"] = int(cycles_used)
    attempt_metadata["syntax_preflight_failures"] = int(failures)
    attempt_metadata["syntax_preflight_last_error_type"] = str(last_error_type or "")
    attempt_metadata["syntax_preflight_last_error_msg"] = str(last_error_msg or "")


def _is_context_limit_http_error_message(body: Any) -> bool:
    text = str(body or "").strip().lower()
    if not text:
        return False
    return any(marker in text for marker in PATCHER_CONTEXT_LIMIT_ERROR_MARKERS)


def _is_retryable_transport_http_status(status: Any) -> bool:
    if not isinstance(status, int):
        return False
    return status in RETRYABLE_HTTP_STATUS_CODES or (500 <= status < 600)


def _classify_patcher_request_failure(meta: dict[str, Any]) -> tuple[bool, str]:
    """Classify a patcher request error as (failed, kind).

    Returns ``(True, "transport")`` or ``(True, "context_limit")``
    when the error is a recognized failure mode, else ``(False, "")``.
    """
    error = str(meta.get("error") or "").strip()
    if not error:
        return False, ""
    if error in {"request_failed", "request_budget_exceeded", "bad_request_url"}:
        return True, "transport"
    if error == "http_error":
        if _is_context_limit_http_error_message(meta.get("body")):
            return True, "context_limit"
        if _is_retryable_transport_http_status(meta.get("status")):
            return True, "transport"
    return False, ""


def _sync_patcher_request_failure_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    failed: bool,
    kind: str = "",
    error: str = "",
    status: int | None = None,
    exc: str = "",
    runner_forced_patch_skipped: bool = False,
    runner_forced_patch_skip_reason: str = "",
) -> None:
    values: dict[str, Any] = {
        "patcher_request_failed": bool(failed),
        "patcher_request_failure_kind": str(kind or ""),
        "patcher_request_failure_error": str(error or ""),
        "patcher_request_failure_status": int(status) if isinstance(status, int) else None,
        "patcher_request_failure_exc": str(exc or ""),
        "runner_forced_patch_skipped": bool(runner_forced_patch_skipped),
        "runner_forced_patch_skip_reason": str(runner_forced_patch_skip_reason or ""),
    }
    patch_meta.update(values)
    attempt_metadata.update(values)


def _sync_patch_sanitize_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    enabled: bool,
    passed: bool,
    rejected: bool,
    reasons: list[str],
    error_msg: str,
) -> None:
    values: dict[str, Any] = {
        "patch_sanitize_enabled": bool(enabled),
        "patch_sanitize_passed": bool(passed),
        "patch_sanitize_rejected": bool(rejected),
        "patch_sanitize_reasons": list(reasons),
        "patch_sanitize_error_msg": str(error_msg or ""),
    }
    patch_meta.update(values)
    attempt_metadata.update(values)


def _sync_patch_diff_metadata(*, patch_meta: dict[str, Any], diff: str) -> None:
    patch_meta["patch_diff_sha256"] = _sha256_text(diff)
    patch_meta["patch_diff_chars"] = len(diff)
    patch_meta.update(_compute_patch_path_metrics(diff))


def _sync_patch_verifier_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    enabled: bool,
    scope: str,
    passed: bool | None,
    score: int | None,
    reasons: list[str],
) -> None:
    values: dict[str, Any] = {
        "patch_verifier_enabled": bool(enabled),
        "patch_verifier_scope": str(scope or PATCH_VERIFIER_SCOPE_DISABLED),
        "patch_verifier_passed": passed if isinstance(passed, bool) else None,
        "patch_verifier_score": int(score) if isinstance(score, int) else None,
        "patch_verifier_reasons": list(reasons),
    }
    patch_meta.update(values)
    attempt_metadata.update(values)


def _sync_patch_verifier_recovery_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    eligible: bool,
    invoked: bool,
    result: str,
    prompt_chars_before: int = 0,
    prompt_chars_after: int = 0,
    duration_sec: float = 0.0,
    terminal_note: str = "",
    score_before: int | None = None,
    reasons_before: Sequence[str] | None = None,
    patch_files_before: Sequence[str] | None = None,
    source_scope_matched_before: bool = False,
    raw_meta: Any = None,
) -> None:
    normalized_raw_meta = dict(raw_meta) if isinstance(raw_meta, dict) else raw_meta
    values: dict[str, Any] = {
        "patch_verifier_recovery_eligible": bool(eligible),
        "patch_verifier_recovery_invoked": bool(invoked),
        "patch_verifier_recovery_result": str(
            result or PATCH_VERIFIER_RECOVERY_RESULT_NOT_ELIGIBLE
        ),
        "patch_verifier_recovery_prompt_chars_before": int(prompt_chars_before),
        "patch_verifier_recovery_prompt_chars_after": int(prompt_chars_after),
        "patch_verifier_recovery_duration_sec": max(0.0, float(duration_sec)),
        "patch_verifier_recovery_terminal_note": str(terminal_note or ""),
        "patch_verifier_recovery_score_before": (
            int(score_before) if isinstance(score_before, int) else None
        ),
        "patch_verifier_recovery_reasons_before": [str(item) for item in (reasons_before or [])],
        "patch_verifier_recovery_patch_files_before": [
            str(item) for item in (patch_files_before or [])
        ],
        "patch_verifier_recovery_source_scope_matched_before": bool(source_scope_matched_before),
        "patch_verifier_recovery_raw_meta": normalized_raw_meta,
    }
    patch_meta.update(values)
    attempt_metadata.update(values)


def _sync_openai_response_mode_autoswitch_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    policy: str,
    switched: bool,
    from_mode: str = "",
    to_mode: str = "",
    cycle: int = 0,
    reason: str = "",
) -> None:
    values: dict[str, Any] = {
        "openai_response_mode_autoswitch_policy": str(policy or "off"),
        "openai_response_mode_autoswitched": bool(switched),
        "openai_response_mode_autoswitch_from": str(from_mode or ""),
        "openai_response_mode_autoswitch_to": str(to_mode or ""),
        "openai_response_mode_autoswitch_cycle": int(cycle),
        "openai_response_mode_autoswitch_reason": str(reason or ""),
    }
    patch_meta.update(values)
    attempt_metadata.update(values)


def _maybe_autoswitch_openai_response_mode(
    *,
    patcher_for_attempt: Any,
    policy: str,
    reason: str,
    syntax_cycle: int,
    patch_meta: dict[str, Any],
    attempt_metadata: dict[str, Any],
) -> tuple[Any, bool]:
    if policy != "retry_to_diff":
        return patcher_for_attempt, False
    current_mode = str(getattr(patcher_for_attempt, "response_mode", "")).strip()
    if current_mode != "edits":
        return patcher_for_attempt, False
    try:
        next_patcher = replace(patcher_for_attempt, response_mode="diff")
    except Exception as exc:
        patch_meta["openai_response_mode_autoswitch_error"] = f"{type(exc).__name__}: {exc}"
        return patcher_for_attempt, False
    _sync_openai_response_mode_autoswitch_metadata(
        attempt_metadata=attempt_metadata,
        patch_meta=patch_meta,
        policy=policy,
        switched=True,
        from_mode="edits",
        to_mode="diff",
        cycle=syntax_cycle,
        reason=reason,
    )
    return next_patcher, True


def _append_retry_history_turn_if_enabled(
    *,
    retry_context_mode: str,
    retry_history_turns: list[str],
    attempt: int,
    note: str,
    failure_signature: str,
    patch_meta: dict[str, Any],
) -> None:
    if retry_context_mode != "history":
        return
    retry_history_turns.append(
        _render_retry_history_turn(
            attempt=attempt,
            note=note,
            failure_signature=failure_signature,
            patch_meta=patch_meta,
        )
    )


def _build_attempt_outcome_record(
    *,
    case: CaseSpec,
    condition: Condition,
    run_id: str,
    attempt: int,
    patcher_name: str,
    run_metadata: dict[str, Any],
    attempt_metadata: dict[str, Any],
    patcher: Any,
    patch_meta: dict[str, Any] | None,
    context_protocol: str,
    success: bool,
    note: str,
    before: RunResult,
    cumulative_runtime_sec: float,
    after: RunResult | None = None,
    attempt_wall_clock_sec: float | None = None,
) -> dict[str, Any]:
    patch_meta_dict = patch_meta if isinstance(patch_meta, dict) else None
    if (
        bool(attempt_metadata.get("patch_verifier_recovery_invoked"))
        or bool(attempt_metadata.get("patch_verifier_recovery_eligible"))
        or (
            patch_meta_dict is not None
            and (
                bool(patch_meta_dict.get("patch_verifier_recovery_invoked"))
                or bool(patch_meta_dict.get("patch_verifier_recovery_eligible"))
            )
        )
    ):
        attempt_metadata["patch_verifier_recovery_terminal_note"] = str(note or "")
        if patch_meta_dict is not None:
            patch_meta_dict["patch_verifier_recovery_terminal_note"] = str(note or "")
    record = _build_base_record(
        case=case,
        condition=condition,
        run_id=run_id,
        attempt=attempt,
        patcher_name=patcher_name,
        run_metadata=run_metadata,
        attempt_metadata=attempt_metadata,
        model_metadata=_build_model_metadata(patcher=patcher, patch_meta=patch_meta),
        context_protocol=context_protocol,
    )
    default_attempt_wall_clock_sec = float(before.duration_sec)
    if after is not None:
        default_attempt_wall_clock_sec += float(after.duration_sec)
    measured_attempt_wall_clock_sec = (
        float(attempt_wall_clock_sec)
        if isinstance(attempt_wall_clock_sec, (int, float))
        else default_attempt_wall_clock_sec
    )
    measured_attempt_wall_clock_sec = max(0.0, measured_attempt_wall_clock_sec)
    payload: dict[str, Any] = {
        "success": success,
        "before_returncode": before.returncode,
        "before_duration_sec": before.duration_sec,
        "cumulative_runtime_sec": cumulative_runtime_sec,
        "attempt_wall_clock_sec": measured_attempt_wall_clock_sec,
        "note": note,
    }
    if after is not None:
        payload["after_returncode"] = after.returncode
        payload["after_duration_sec"] = after.duration_sec
    if patch_meta_dict is not None:
        payload["patch_meta"] = patch_meta_dict
        payload["patch_touches_tests"] = bool(patch_meta_dict.get("patch_touches_tests"))
        payload["patch_test_file_count"] = _nonnegative_int(
            patch_meta_dict.get("patch_test_file_count")
        )
        payload["patch_file_count"] = _nonnegative_int(patch_meta_dict.get("patch_file_count"))
    record.update(payload)
    record.update(_timeout_fields(after if after is not None else before))
    return record


def _measure_attempt_wall_clock_sec(attempt_start_monotonic: float) -> float:
    return max(0.0, float(time.perf_counter() - attempt_start_monotonic))


def _ensure_deterministic_seed_ack(
    *,
    deterministic: bool,
    seed: int | None,
    patcher_name: str,
    patch_meta: dict[str, Any],
    case_id: str,
    condition: str,
    attempt: int,
) -> None:
    if not deterministic or patcher_name != "openai":
        return

    requested = patch_meta.get("api_compat_seed_requested")
    acknowledged = patch_meta.get("api_compat_seed_acknowledged")
    if requested != seed:
        raise RuntimeError(
            "deterministic seed mismatch for "
            f"{case_id}/{condition}/attempt={attempt}: requested={requested!r}, expected={seed!r}"
        )
    if acknowledged is not True:
        error = patch_meta.get("error")
        body = patch_meta.get("body")
        exc = patch_meta.get("exc")
        details = error or body or exc or "backend did not acknowledge seed support"
        raise RuntimeError(
            f"deterministic mode failed for {case_id}/{condition}/attempt={attempt}: {details}"
        )


def _adaptive_gate_should_invoke_helper(
    *,
    policy: str,
    attempt: int,
    timed_out: bool,
    signature_repeated: bool,
    stagnation_streak: int,
    stagnation_window: int,
) -> tuple[bool, str]:
    """Decide whether the adaptive gate should invoke the helper.

    Returns (should_invoke, reason_token) based on the gate policy,
    attempt number, timeout state, and stagnation streak.
    """
    if timed_out:
        return True, "gate_timeout"
    if policy == "aggressive":
        return True, "gate_aggressive"

    normalized_window = max(1, int(stagnation_window))
    if policy == "conservative":
        required_repeats = max(normalized_window + 1, 2)
        if signature_repeated and stagnation_streak >= required_repeats:
            return True, "gate_conservative_repeated_signature"
        return False, "gate_conservative_waiting"

    # balanced (default)
    if attempt >= 2 and signature_repeated and stagnation_streak >= normalized_window:
        return True, "gate_balanced_repeated_signature"
    return False, "gate_balanced_waiting"


def _adaptive_progress_signal(
    *,
    note: str = "",
    success: bool = False,
    signature_repeated: bool,
    new_evidence_count: int,
    before_run: RunResult | None = None,
    after_run: RunResult | None = None,
    previous_failure_context: dict[str, Any] | None = None,
    patch_meta: dict[str, Any] | None = None,
) -> AdaptiveProgressState:
    """Compute a multi-signal progress assessment for an attempt.

    Evaluates failure-signature repetition, new-evidence count,
    pytest-failure reduction, pipeline-stage advancement, verifier
    score, and scope alignment to produce an ``AdaptiveProgressState``.
    """
    patch_meta_dict = patch_meta if isinstance(patch_meta, dict) else {}
    previous_context = (
        previous_failure_context if isinstance(previous_failure_context, dict) else {}
    )

    component_signature = 0.0 if signature_repeated else 1.0
    if bool(success):
        component_signature = 1.0
    component_new_evidence = 1.0 if int(new_evidence_count) > 0 else 0.0

    pytest_before_failed_total = _pytest_failed_total(before_run)
    pytest_after_failed_total = _pytest_failed_total(after_run)
    if bool(success) and pytest_after_failed_total is None:
        pytest_after_failed_total = 0

    component_pytest_fail_reduction = 0.0
    if (
        pytest_before_failed_total is not None
        and pytest_after_failed_total is not None
        and pytest_before_failed_total > 0
        and pytest_after_failed_total < pytest_before_failed_total
    ):
        component_pytest_fail_reduction = min(
            1.0,
            max(
                0.0,
                (pytest_before_failed_total - pytest_after_failed_total)
                / float(pytest_before_failed_total),
            ),
        )

    stage_before, stage_before_ord = _adaptive_pipeline_stage_before(
        previous_failure_context=previous_failure_context,
        current_run=before_run,
    )
    stage_after, stage_after_ord = _adaptive_pipeline_stage_for_outcome(
        note=note,
        success=success,
    )
    component_stage_advance = min(
        1.0,
        max(0.0, float(stage_after_ord - stage_before_ord) / 4.0),
    )

    prev_verifier_score = _adaptive_optional_int(previous_context.get("patch_verifier_score"))
    curr_verifier_score = _adaptive_optional_int(patch_meta_dict.get("patch_verifier_score"))
    component_verifier_score = 0.0
    if (
        prev_verifier_score is not None
        and curr_verifier_score is not None
        and curr_verifier_score > prev_verifier_score
    ):
        component_verifier_score = min(
            1.0,
            max(0.0, float(curr_verifier_score - prev_verifier_score) / 4.0),
        )

    prev_scope_matched = _adaptive_optional_bool(
        previous_context.get("patch_verifier_source_scope_matched")
    )
    curr_scope_matched = _adaptive_optional_bool(
        patch_meta_dict.get("patch_verifier_source_scope_matched")
    )
    component_scope_alignment = (
        1.0 if prev_scope_matched is False and curr_scope_matched is True else 0.0
    )

    contributions = {
        "failure_signature_changed": 0.20 * component_signature,
        "new_evidence_added": 0.10 * component_new_evidence,
        "pytest_failure_count_reduction": 0.25 * component_pytest_fail_reduction,
        "pipeline_stage_advance": 0.20 * component_stage_advance,
        "patch_verifier_score_improvement": 0.15 * component_verifier_score,
        "source_scope_alignment_improvement": 0.10 * component_scope_alignment,
    }
    score = min(1.0, max(0.0, sum(contributions.values())))
    reason_order = (
        "failure_signature_changed",
        "pytest_failure_count_reduction",
        "pipeline_stage_advance",
        "patch_verifier_score_improvement",
        "source_scope_alignment_improvement",
        "new_evidence_added",
    )
    reason = "no_progress_v2"
    best_contribution = 0.0
    for reason_key in reason_order:
        contribution = contributions.get(reason_key, 0.0)
        if contribution > best_contribution:
            best_contribution = contribution
            reason = reason_key

    return AdaptiveProgressState(
        made=bool(score > 0.0),
        reason=reason if score > 0.0 else "no_progress_v2",
        score=float(score),
        component_signature=float(component_signature),
        component_new_evidence=float(component_new_evidence),
        component_pytest_fail_reduction=float(component_pytest_fail_reduction),
        component_stage_advance=float(component_stage_advance),
        component_verifier_score=float(component_verifier_score),
        component_scope_alignment=float(component_scope_alignment),
        pytest_before_failed_total=pytest_before_failed_total,
        pytest_after_failed_total=pytest_after_failed_total,
        stage_before=stage_before,
        stage_after=stage_after,
        stage_before_ord=stage_before_ord,
        stage_after_ord=stage_after_ord,
    )


def _adaptive_optional_int(value: Any) -> int | None:
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def _adaptive_optional_bool(value: Any) -> bool | None:
    if isinstance(value, bool):
        return value
    return None


def _pytest_failed_total(run: RunResult | None) -> int | None:
    if run is None:
        return None
    for text in (str(run.stdout or ""), str(run.stderr or "")):
        total = _pytest_failed_total_from_text(text)
        if total is not None:
            return total
    if not run.timed_out and int(run.returncode) == 0:
        return 0
    return None


def _pytest_failed_total_from_text(text: str) -> int | None:
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    for line in reversed(lines):
        lower = line.lower()
        if "short test summary info" in lower:
            continue
        if "no tests ran" in lower:
            return 0
        if "=" not in line and " in " not in lower:
            continue
        if not any(token in lower for token in (" failed", " error", " errors", " passed")):
            continue
        total = 0
        matched = False
        for match in _PYTEST_SUMMARY_COUNT_RE.finditer(lower):
            matched = True
            total += int(match.group(1))
        if matched:
            return total
        if " passed" in lower:
            return 0
    return None


def _adaptive_pipeline_stage_for_outcome(*, note: str, success: bool) -> tuple[str, int]:
    if bool(success) or str(note or "") == "success":
        return "success", 4
    note_norm = str(note or "").strip()
    if note_norm in {"patcher_request_failed", "no_patch_proposed", "retry_gate_no_new_evidence"}:
        return "request_or_no_patch", 0
    if note_norm in {"forced_noop_patch", "patch_apply_failed", "syntax_preflight_failed"}:
        return "pretest_patch_failure", 1
    if note_norm == "patch_verifier_rejected":
        return "verifier_rejected", 2
    return "tests_still_failing", 3


def _adaptive_pipeline_stage_before(
    *,
    previous_failure_context: dict[str, Any] | None,
    current_run: RunResult | None,
) -> tuple[str, int]:
    if isinstance(previous_failure_context, dict):
        previous_note = str(previous_failure_context.get("note") or "")
        if previous_note:
            return _adaptive_pipeline_stage_for_outcome(note=previous_note, success=False)
    if current_run is None:
        return "tests_still_failing", 3
    if current_run.timed_out:
        return "tests_still_failing", 3
    if int(current_run.returncode) == 0:
        return "success", 4
    return "tests_still_failing", 3


def _sync_adaptive_progress_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    progress_state: AdaptiveProgressState,
    no_progress_streak: int,
    recomputed_after_patch: bool = False,
) -> None:
    fields = {
        "adaptive_progress_made": bool(progress_state.made),
        "adaptive_progress_reason": str(progress_state.reason),
        "adaptive_progress_score": float(progress_state.score),
        "adaptive_progress_component_signature": float(progress_state.component_signature),
        "adaptive_progress_component_new_evidence": float(progress_state.component_new_evidence),
        "adaptive_progress_component_pytest_fail_reduction": float(
            progress_state.component_pytest_fail_reduction
        ),
        "adaptive_progress_component_stage_advance": float(progress_state.component_stage_advance),
        "adaptive_progress_component_verifier_score": float(
            progress_state.component_verifier_score
        ),
        "adaptive_progress_component_scope_alignment": float(
            progress_state.component_scope_alignment
        ),
        "adaptive_pytest_before_failed_total": progress_state.pytest_before_failed_total,
        "adaptive_pytest_after_failed_total": progress_state.pytest_after_failed_total,
        "adaptive_pipeline_stage_before": str(progress_state.stage_before),
        "adaptive_pipeline_stage_after": str(progress_state.stage_after),
        "adaptive_pipeline_stage_before_ord": int(progress_state.stage_before_ord),
        "adaptive_pipeline_stage_after_ord": int(progress_state.stage_after_ord),
        "adaptive_no_progress_streak": int(max(0, int(no_progress_streak))),
    }
    attempt_metadata.update(fields)
    patch_meta.update(fields)
    patch_meta["adaptive_progress_recomputed_after_patch"] = bool(recomputed_after_patch)


def _recompute_adaptive_progress_for_attempt_outcome(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    note: str,
    success: bool,
    signature_repeated: bool,
    new_evidence_count: int,
    before_run: RunResult | None,
    after_run: RunResult | None,
    previous_failure_context: dict[str, Any] | None,
    current_no_progress_streak: int,
    recomputed_after_patch: bool,
) -> tuple[AdaptiveProgressState, int]:
    progress_state = _adaptive_progress_signal(
        note=note,
        success=success,
        signature_repeated=signature_repeated,
        new_evidence_count=new_evidence_count,
        before_run=before_run,
        after_run=after_run,
        previous_failure_context=previous_failure_context,
        patch_meta=patch_meta,
    )
    next_no_progress_streak = 0 if progress_state.made else max(0, int(current_no_progress_streak))
    _sync_adaptive_progress_metadata(
        attempt_metadata=attempt_metadata,
        patch_meta=patch_meta,
        progress_state=progress_state,
        no_progress_streak=next_no_progress_streak,
        recomputed_after_patch=recomputed_after_patch,
    )
    return progress_state, next_no_progress_streak


def _adaptive_should_stop_for_no_progress(
    *,
    no_progress_streak: int,
    stop_window: int,
    helper_budget_exhausted: bool,
    helper_saturation_guarded: bool,
    helper_decision_reason: str,
) -> tuple[bool, str]:
    if int(no_progress_streak) < max(1, int(stop_window)):
        return False, "streak_below_window"
    if helper_budget_exhausted:
        return True, "helper_budget_exhausted"
    if helper_saturation_guarded:
        return True, "helper_saturation_guard"
    if helper_decision_reason in {
        "llm_declined_helper",
        "llm_invalid_response_no_helper",
        "llm_snapshot_request_denied_attempt1",
        "request_context_unavailable",
    }:
        return True, helper_decision_reason
    return False, "waiting_for_more_signal"


def _mark_adaptive_progress_stop(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    is_adaptive_condition: bool,
    adaptive_progress_stop_candidate: bool,
    adaptive_progress_stop_reason: str,
    note: str,
) -> bool:
    triggered = bool(
        is_adaptive_condition
        and adaptive_progress_stop_candidate
        and note
        in {
            "hard_stop_no_progress",
            "no_patch_proposed",
            "patcher_request_failed",
            "forced_noop_patch",
            "patch_verifier_rejected",
            "syntax_preflight_failed",
            "patch_apply_failed",
            "tests_still_failing",
        }
    )
    attempt_metadata["adaptive_progress_stop_triggered"] = triggered
    patch_meta["adaptive_progress_stop_triggered"] = triggered
    patch_meta["adaptive_progress_stop_reason"] = adaptive_progress_stop_reason
    if triggered:
        patch_meta["adaptive_progress_stop_note"] = note
    return triggered


def _apply_no_progress_token_cap_candidate(
    *,
    is_adaptive_condition: bool,
    adaptive_progress_made: bool,
    adaptive_no_progress_token_sum: int,
    attempt_tokens_total: int,
    adaptive_no_progress_token_cap: int,
    adaptive_progress_stop_candidate: bool,
    adaptive_progress_stop_reason: str,
) -> tuple[bool, str, int, bool]:
    next_sum = max(0, int(adaptive_no_progress_token_sum))
    if not is_adaptive_condition:
        return adaptive_progress_stop_candidate, adaptive_progress_stop_reason, next_sum, False
    if adaptive_progress_made:
        next_sum = 0
    else:
        next_sum += max(0, int(attempt_tokens_total))

    cap = max(0, int(adaptive_no_progress_token_cap))
    cap_hit = cap > 0 and next_sum >= cap
    if cap_hit:
        return True, "no_progress_token_cap", next_sum, True
    return adaptive_progress_stop_candidate, adaptive_progress_stop_reason, next_sum, False


def _adaptive_online_feature_vector(
    *,
    attempt: int,
    max_attempts: int,
    timed_out: bool,
    failure_signature_repeat: bool,
    stagnation_streak: int,
    helper_calls_used: int,
    helper_calls_without_context: int,
    adaptive_no_progress_streak: int,
    helper_budget_exhausted: bool,
    helper_saturation_guarded: bool,
    supports_context_request: bool,
    snapshot_available: bool,
    pregate_result: PreGateResult,
    effective_helper_budget_limit: int,
    adaptive_budget_unlimited: bool,
    adaptive_budget_topup_calls_used: int,
    adaptive_budget_topup_max_calls: int,
    last_helper_need_more_context: bool | None,
    last_helper_decision_reason: str,
    last_helper_action_executed: str,
    context_compaction_enabled: bool,
    supports_context_compaction: bool,
    context_compaction_calls_used: int,
    context_compaction_max_calls: int,
    last_context_compaction_requested: bool,
    last_context_compaction_invoked: bool,
    last_context_compaction_success: bool,
    last_context_compaction_ratio: float,
    last_context_compaction_trigger: str,
    case_difficulty: str,
    case_snapshot_dependency: str,
    case_bug_source: str | None,
) -> dict[str, float]:
    """Build the feature vector for the online contextual-bandit policy.

    Returns a dict mapping feature names to float values representing
    the current eval state (attempt progress, helper usage, difficulty,
    compaction state, etc.).
    """
    difficulty_ordinal = {
        "easy": 0.0,
        "medium": 0.5,
        "hard": 1.0,
    }.get(str(case_difficulty).strip().lower(), 0.5)
    snapshot_dependency_ordinal = {
        "none": 0.0,
        "helpful": 0.5,
        "required": 1.0,
    }.get(str(case_snapshot_dependency).strip().lower(), 0.5)
    bug_source_normalized = str(case_bug_source or "hand_crafted").strip().lower()
    pregate_exception_type_normalized = str(pregate_result.exception_type).strip().lower()
    max_attempts_safe = max(1, int(max_attempts))
    attempt_safe = max(1, int(attempt))
    helper_calls_used_safe = max(0, int(helper_calls_used))
    helper_budget_limit_safe = max(0, int(effective_helper_budget_limit))
    helper_budget_remaining = max(0, helper_budget_limit_safe - helper_calls_used_safe)
    helper_budget_remaining_frac = (
        1.0
        if adaptive_budget_unlimited
        else (
            helper_budget_remaining / float(helper_budget_limit_safe)
            if helper_budget_limit_safe > 0
            else 0.0
        )
    )
    topup_max_safe = max(0, int(adaptive_budget_topup_max_calls))
    topup_used_safe = max(0, int(adaptive_budget_topup_calls_used))
    helper_topup_used_frac = (
        min(1.0, topup_used_safe / float(topup_max_safe)) if topup_max_safe > 0 else 0.0
    )
    helper_topup_remaining_frac = (
        max(0.0, min(1.0, (topup_max_safe - topup_used_safe) / float(topup_max_safe)))
        if topup_max_safe > 0
        else 0.0
    )
    last_decision_reason = str(last_helper_decision_reason or "").strip()
    last_helper_action_norm = str(last_helper_action_executed or "").strip()
    context_compaction_calls_used_safe = max(0, int(context_compaction_calls_used))
    context_compaction_max_calls_safe = max(0, int(context_compaction_max_calls))
    context_compaction_calls_used_frac = (
        min(1.0, context_compaction_calls_used_safe / float(context_compaction_max_calls_safe))
        if context_compaction_max_calls_safe > 0
        else 0.0
    )
    context_compaction_calls_remaining_frac = (
        max(
            0.0,
            min(
                1.0,
                (context_compaction_max_calls_safe - context_compaction_calls_used_safe)
                / float(context_compaction_max_calls_safe),
            ),
        )
        if context_compaction_max_calls_safe > 0
        else 0.0
    )
    last_compaction_trigger = str(last_context_compaction_trigger or "")
    low_yield_ratio = (
        (max(0, int(helper_calls_without_context)) / float(helper_calls_used_safe))
        if helper_calls_used_safe > 0
        else 0.0
    )
    features = {
        "bias": 1.0,
        "attempt_frac": float(attempt_safe / float(max_attempts_safe)),
        "attempts_remaining_frac": float(
            max(0.0, (max_attempts_safe - attempt_safe) / float(max_attempts_safe))
        ),
        "timed_out": 1.0 if timed_out else 0.0,
        "failure_signature_repeat": 1.0 if failure_signature_repeat else 0.0,
        "stagnation_streak": float(max(0, int(stagnation_streak))),
        "helper_calls_used": float(helper_calls_used_safe),
        "helper_low_yield_ratio": float(low_yield_ratio),
        "helper_budget_remaining_frac": float(helper_budget_remaining_frac),
        "helper_topup_used_frac": float(helper_topup_used_frac),
        "helper_topup_remaining_frac": float(helper_topup_remaining_frac),
        "adaptive_no_progress_streak": float(max(0, int(adaptive_no_progress_streak))),
        "helper_budget_exhausted": 1.0 if helper_budget_exhausted else 0.0,
        "helper_saturation_guarded": 1.0 if helper_saturation_guarded else 0.0,
        "supports_context_request": 1.0 if supports_context_request else 0.0,
        "context_compaction_enabled": 1.0 if context_compaction_enabled else 0.0,
        "supports_context_compaction": 1.0 if supports_context_compaction else 0.0,
        "context_compaction_calls_used_frac": float(context_compaction_calls_used_frac),
        "context_compaction_calls_remaining_frac": float(context_compaction_calls_remaining_frac),
        "last_context_compaction_requested": 1.0 if last_context_compaction_requested else 0.0,
        "last_context_compaction_invoked": 1.0 if last_context_compaction_invoked else 0.0,
        "last_context_compaction_success": 1.0 if last_context_compaction_success else 0.0,
        "last_context_compaction_ratio": float(max(0.0, min(1.0, last_context_compaction_ratio))),
        "last_context_compaction_trigger_stage_a": 1.0
        if last_compaction_trigger == "stage_a_requested"
        else 0.0,
        "last_context_compaction_trigger_overflow": 1.0
        if last_compaction_trigger == "prompt_overflow"
        else 0.0,
        "last_context_compaction_trigger_always": 1.0
        if last_compaction_trigger == "mode_always"
        else 0.0,
        "snapshot_available": 1.0 if snapshot_available else 0.0,
        "last_helper_need_more_context": 1.0 if last_helper_need_more_context is True else 0.0,
        "last_helper_invalid_response": 1.0
        if last_decision_reason == "llm_invalid_response_no_helper"
        else 0.0,
        "last_helper_declined": 1.0 if last_decision_reason == "llm_declined_helper" else 0.0,
        "case_difficulty_ord": float(difficulty_ordinal),
        "case_snapshot_dependency_ord": float(snapshot_dependency_ordinal),
        "case_bug_source_imported": 0.0 if bug_source_normalized == "hand_crafted" else 1.0,
        "pregate_score": float(max(0.0, min(1.0, pregate_result.score))),
        "pregate_fired": 1.0 if pregate_result.should_skip else 0.0,
        "pregate_type_specificity": float(max(0.0, min(1.0, pregate_result.f_type_specificity))),
        "pregate_message_diagnostic": float(
            max(0.0, min(1.0, pregate_result.f_message_diagnostic))
        ),
        "pregate_crash_in_user_code": float(
            max(0.0, min(1.0, pregate_result.f_crash_in_user_code))
        ),
        "pregate_assertion_quality": float(max(0.0, min(1.0, pregate_result.f_assertion_quality))),
        "pregate_frame_depth": float(max(0.0, min(1.0, pregate_result.f_frame_depth))),
        "pregate_exception_is_assertion": 1.0
        if pregate_exception_type_normalized == "assertionerror"
        else 0.0,
        "pregate_exception_is_timeout": 1.0
        if pregate_exception_type_normalized == "timeouterror"
        else 0.0,
        "pregate_exception_is_syntax": 1.0
        if pregate_exception_type_normalized in {"syntaxerror", "indentationerror", "taberror"}
        else 0.0,
    }
    for action in ONLINE_POLICY_ACTIONS:
        features[f"last_helper_action_{action}"] = 1.0 if last_helper_action_norm == action else 0.0
    return features


def _adaptive_budget_control_profile_to_dict(
    profile: AdaptiveBudgetControlProfile,
) -> dict[str, Any]:
    return {
        "propose_budget_multiplier": float(profile.propose_budget_multiplier),
        "propose_budget_sec": float(profile.propose_budget_sec),
        "openai_max_tokens": int(profile.openai_max_tokens),
        "helper_max_calls": int(profile.helper_max_calls),
        "stage_a_max_requests": int(profile.stage_a_max_requests),
        "stage_a_max_chars": int(profile.stage_a_max_chars),
        "retry_history_max_chars": int(profile.retry_history_max_chars),
        "context_compaction_max_calls": int(profile.context_compaction_max_calls),
        "response_mode": str(profile.response_mode or ""),
    }


def _adaptive_budget_control_changes(
    *,
    current: AdaptiveBudgetControlProfile,
    target: AdaptiveBudgetControlProfile,
) -> dict[str, bool]:
    return {
        "propose_budget": (
            float(target.propose_budget_sec) != float(current.propose_budget_sec)
            or float(target.propose_budget_multiplier) != float(current.propose_budget_multiplier)
        ),
        "max_tokens": int(target.openai_max_tokens) != int(current.openai_max_tokens),
        "helper_allowance": int(target.helper_max_calls) != int(current.helper_max_calls),
        "stage_a_requests": int(target.stage_a_max_requests) != int(current.stage_a_max_requests),
        "stage_a_chars": int(target.stage_a_max_chars) != int(current.stage_a_max_chars),
        "retry_history_budget": int(target.retry_history_max_chars)
        != int(current.retry_history_max_chars),
        "compaction_allowance": int(target.context_compaction_max_calls)
        != int(current.context_compaction_max_calls),
        "response_mode": str(target.response_mode or "") != str(current.response_mode or ""),
    }


def _adaptive_budget_controls_changed(change_flags: Mapping[str, bool]) -> bool:
    return any(bool(change_flags.get(key)) for key in ADAPTIVE_BUDGET_CONTROL_CHANGE_KEYS)


def _build_adaptive_budget_control_profile(
    *,
    tier_index: int,
    base_tier_index: int,
    tier_multipliers: Sequence[float],
    base_propose_budget_sec: float,
    base_openai_max_tokens: int,
    base_helper_max_calls: int,
    base_stage_a_max_requests: int,
    base_stage_a_max_chars: int,
    base_retry_history_max_chars: int,
    base_context_compaction_max_calls: int,
    context_compaction_enabled: bool,
    base_response_mode: str,
    is_openai_patcher: bool,
) -> AdaptiveBudgetControlProfile:
    """Compute budget-control knobs for a given tier multiplier.

    Scales propose-budget, max-tokens, helper calls, and compaction
    limits relative to the base-tier values.
    """
    multipliers = [float(value) for value in tier_multipliers] or [1.0]
    max_tier_index = max(0, len(multipliers) - 1)
    normalized_tier_index = min(max(0, int(tier_index)), max_tier_index)
    normalized_base_tier_index = min(max(0, int(base_tier_index)), max_tier_index)
    multiplier = max(0.0, float(multipliers[normalized_tier_index]))
    tier_delta = max(0, normalized_tier_index - normalized_base_tier_index)
    normalized_base_response_mode = str(base_response_mode or "")
    response_mode = normalized_base_response_mode
    if (
        is_openai_patcher
        and normalized_tier_index > normalized_base_tier_index
        and normalized_tier_index == max_tier_index
        and normalized_base_response_mode == "edits"
    ):
        response_mode = "diff"
    return AdaptiveBudgetControlProfile(
        propose_budget_multiplier=multiplier,
        propose_budget_sec=max(0.0, float(base_propose_budget_sec) * multiplier),
        openai_max_tokens=(
            max(1, round(max(0, int(base_openai_max_tokens)) * multiplier))
            if is_openai_patcher and int(base_openai_max_tokens) > 0
            else 0
        ),
        helper_max_calls=(
            0
            if int(base_helper_max_calls) <= 0
            else max(0, int(base_helper_max_calls) + tier_delta)
        ),
        stage_a_max_requests=max(1, int(base_stage_a_max_requests) + tier_delta),
        stage_a_max_chars=max(1, round(max(1, int(base_stage_a_max_chars)) * multiplier)),
        retry_history_max_chars=max(
            0,
            round(max(0, int(base_retry_history_max_chars)) * multiplier),
        ),
        context_compaction_max_calls=(
            max(0, int(base_context_compaction_max_calls) + tier_delta)
            if context_compaction_enabled
            else max(0, int(base_context_compaction_max_calls))
        ),
        response_mode=response_mode,
    )


def _apply_adaptive_budget_profile_to_patcher(
    *,
    patcher: Any,
    applied_profile: AdaptiveBudgetControlProfile,
    base_propose_budget_sec: float,
    base_openai_max_tokens: int,
    base_response_mode: str,
    is_openai_patcher: bool,
) -> tuple[Any, AdaptiveBudgetControlProfile, dict[str, str]]:
    if not is_openai_patcher:
        return patcher, applied_profile, {}

    override_kwargs = {
        "max_propose_sec": float(applied_profile.propose_budget_sec),
        "max_tokens": int(applied_profile.openai_max_tokens),
        "response_mode": str(applied_profile.response_mode or ""),
    }
    try:
        return replace(patcher, **override_kwargs), applied_profile, {}
    except Exception as exc:
        error_text = f"{type(exc).__name__}: {exc}"
        reverted_profile = replace(
            applied_profile,
            propose_budget_sec=max(0.0, float(base_propose_budget_sec)),
            openai_max_tokens=max(0, int(base_openai_max_tokens)),
            response_mode=str(base_response_mode or ""),
        )
        return (
            patcher,
            reverted_profile,
            {
                "max_propose_sec": error_text,
                "max_tokens": error_text,
                "response_mode": error_text,
            },
        )


def _adaptive_policy_local_accounting(
    *,
    llm_call_count: int = 0,
    tokens_prompt: int = 0,
    tokens_completion: int = 0,
    tokens_total: int | None = None,
    wall_sec: float = 0.0,
) -> AdaptivePolicyLocalAccounting:
    normalized_llm_calls = _nonnegative_int(llm_call_count, default=0)
    normalized_tokens_prompt = _nonnegative_int(tokens_prompt, default=0)
    normalized_tokens_completion = _nonnegative_int(tokens_completion, default=0)
    normalized_tokens_total = _nonnegative_int(
        tokens_total,
        default=normalized_tokens_prompt + normalized_tokens_completion,
    )
    return AdaptivePolicyLocalAccounting(
        llm_call_count=normalized_llm_calls,
        tokens_prompt=normalized_tokens_prompt,
        tokens_completion=normalized_tokens_completion,
        tokens_total=normalized_tokens_total,
        wall_sec=max(0.0, float(wall_sec)),
    )


def _adaptive_online_local_reward_components(
    *,
    success: bool,
    helper_materialized_context: bool,
    local_tokens_total: int,
    local_wall_sec: float,
    adaptive_progress_score: float,
    adaptive_no_progress_streak: int,
    lambda_tokens: float,
    mu_latency: float,
    token_scale: float,
    latency_scale: float,
    eta_progress: float,
    kappa_stagnation: float,
) -> tuple[float, float, float, float, float, float, float]:
    success_hint = ADAPTIVE_LOCAL_REWARD_SUCCESS_HINT if bool(success) else 0.0
    progress_score = max(0.0, min(1.0, float(adaptive_progress_score)))
    progress_hint = (
        ADAPTIVE_LOCAL_REWARD_PROGRESS_HINT_SCALE * max(0.0, float(eta_progress)) * progress_score
    )
    helper_materialization_bonus = (
        ADAPTIVE_LOCAL_REWARD_HELPER_MATERIALIZATION_BONUS_SCALE * max(0.0, float(eta_progress))
        if helper_materialized_context
        else 0.0
    )
    token_penalty = max(0.0, float(lambda_tokens)) * (
        max(0.0, float(local_tokens_total)) / max(1.0, float(token_scale))
    )
    latency_penalty = max(0.0, float(mu_latency)) * (
        max(0.0, float(local_wall_sec)) / max(1.0, float(latency_scale))
    )
    stagnation_penalty = (
        ADAPTIVE_LOCAL_REWARD_STAGNATION_PENALTY_SCALE
        * max(0.0, float(kappa_stagnation))
        * min(1.0, max(0.0, float(adaptive_no_progress_streak)) / 3.0)
    )
    reward = (
        success_hint
        + progress_hint
        + helper_materialization_bonus
        - token_penalty
        - latency_penalty
        - stagnation_penalty
    )
    return (
        reward,
        success_hint,
        progress_hint,
        token_penalty,
        latency_penalty,
        stagnation_penalty,
        helper_materialization_bonus,
    )


def _adaptive_budget_feature_vector(
    *,
    attempt: int,
    max_attempts: int,
    timed_out: bool,
    failure_signature_repeat: bool,
    stagnation_streak: int,
    helper_calls_used: int,
    helper_calls_without_context: int,
    adaptive_no_progress_streak: int,
    adaptive_no_progress_token_sum: int,
    helper_budget_exhausted: bool,
    helper_saturation_guarded: bool,
    supports_context_request: bool,
    snapshot_available: bool,
    pregate_result: PreGateResult,
    context_compaction_enabled: bool,
    supports_context_compaction: bool,
    context_compaction_calls_used: int,
    context_compaction_max_calls: int,
    last_context_compaction_requested: bool,
    last_context_compaction_invoked: bool,
    last_context_compaction_success: bool,
    last_context_compaction_ratio: float,
    last_context_compaction_trigger: str,
    current_tier_index: int,
    max_tier_index: int,
    ups_used: int,
    max_ups_per_case: int,
    base_propose_budget_sec: float,
    next_tier_changes: Mapping[str, bool],
    next_tier_multiplier_delta: float,
    last_helper_need_more_context: bool | None,
    last_helper_decision_reason: str,
) -> dict[str, float]:
    """Build the feature vector for the budget contextual-bandit policy."""
    max_attempts_safe = max(1, int(max_attempts))
    attempt_safe = max(1, int(attempt))
    helper_calls_used_safe = max(0, int(helper_calls_used))
    low_yield_ratio = (
        (max(0, int(helper_calls_without_context)) / float(helper_calls_used_safe))
        if helper_calls_used_safe > 0
        else 0.0
    )
    context_compaction_calls_used_safe = max(0, int(context_compaction_calls_used))
    context_compaction_max_calls_safe = max(0, int(context_compaction_max_calls))
    context_compaction_calls_used_frac = (
        min(1.0, context_compaction_calls_used_safe / float(context_compaction_max_calls_safe))
        if context_compaction_max_calls_safe > 0
        else 0.0
    )
    context_compaction_calls_remaining_frac = (
        max(
            0.0,
            min(
                1.0,
                (context_compaction_max_calls_safe - context_compaction_calls_used_safe)
                / float(context_compaction_max_calls_safe),
            ),
        )
        if context_compaction_max_calls_safe > 0
        else 0.0
    )
    max_tier_safe = max(0, int(max_tier_index))
    tier_index_safe = min(max(0, int(current_tier_index)), max_tier_safe)
    tier_frac = (
        tier_index_safe / float(max_tier_safe) if max_tier_safe > 0 else float(tier_index_safe > 0)
    )
    max_ups_safe = max(0, int(max_ups_per_case))
    ups_used_safe = max(0, int(ups_used))
    ups_used_frac = min(1.0, ups_used_safe / float(max_ups_safe)) if max_ups_safe > 0 else 0.0
    ups_remaining_frac = (
        max(0.0, min(1.0, (max_ups_safe - ups_used_safe) / float(max_ups_safe)))
        if max_ups_safe > 0
        else 0.0
    )
    last_decision_reason = str(last_helper_decision_reason or "").strip()
    pregate_exception_type_normalized = str(pregate_result.exception_type).strip().lower()
    last_compaction_trigger = str(last_context_compaction_trigger or "")
    return {
        "bias": 1.0,
        "attempt_frac": float(attempt_safe / float(max_attempts_safe)),
        "attempts_remaining_frac": float(
            max(0.0, (max_attempts_safe - attempt_safe) / float(max_attempts_safe))
        ),
        "timed_out": 1.0 if timed_out else 0.0,
        "failure_signature_repeat": 1.0 if failure_signature_repeat else 0.0,
        "stagnation_streak": float(max(0, int(stagnation_streak))),
        "helper_calls_used": float(helper_calls_used_safe),
        "helper_low_yield_ratio": float(low_yield_ratio),
        "adaptive_no_progress_streak": float(max(0, int(adaptive_no_progress_streak))),
        "adaptive_no_progress_token_sum_k": float(
            max(0, int(adaptive_no_progress_token_sum)) / 1000.0
        ),
        "helper_budget_exhausted": 1.0 if helper_budget_exhausted else 0.0,
        "helper_saturation_guarded": 1.0 if helper_saturation_guarded else 0.0,
        "supports_context_request": 1.0 if supports_context_request else 0.0,
        "snapshot_available": 1.0 if snapshot_available else 0.0,
        "pregate_score": float(max(0.0, min(1.0, pregate_result.score))),
        "pregate_fired": 1.0 if pregate_result.should_skip else 0.0,
        "pregate_exception_is_assertion": 1.0
        if pregate_exception_type_normalized == "assertionerror"
        else 0.0,
        "pregate_exception_is_timeout": 1.0
        if pregate_exception_type_normalized == "timeouterror"
        else 0.0,
        "pregate_exception_is_syntax": 1.0
        if pregate_exception_type_normalized in {"syntaxerror", "indentationerror", "taberror"}
        else 0.0,
        "context_compaction_enabled": 1.0 if context_compaction_enabled else 0.0,
        "supports_context_compaction": 1.0 if supports_context_compaction else 0.0,
        "context_compaction_calls_used_frac": float(context_compaction_calls_used_frac),
        "context_compaction_calls_remaining_frac": float(context_compaction_calls_remaining_frac),
        "last_context_compaction_requested": 1.0 if last_context_compaction_requested else 0.0,
        "last_context_compaction_invoked": 1.0 if last_context_compaction_invoked else 0.0,
        "last_context_compaction_success": 1.0 if last_context_compaction_success else 0.0,
        "last_context_compaction_ratio": float(max(0.0, min(1.0, last_context_compaction_ratio))),
        "last_context_compaction_trigger_stage_a": 1.0
        if last_compaction_trigger == "stage_a_requested"
        else 0.0,
        "last_context_compaction_trigger_overflow": 1.0
        if last_compaction_trigger == "prompt_overflow"
        else 0.0,
        "last_context_compaction_trigger_always": 1.0
        if last_compaction_trigger == "mode_always"
        else 0.0,
        "current_budget_tier_frac": float(max(0.0, min(1.0, tier_frac))),
        "can_increase_budget_tier": 1.0 if tier_index_safe < max_tier_safe else 0.0,
        "budget_ups_used_frac": float(max(0.0, min(1.0, ups_used_frac))),
        "budget_ups_remaining_frac": float(max(0.0, min(1.0, ups_remaining_frac))),
        "base_propose_budget_sec": float(max(0.0, float(base_propose_budget_sec))),
        "budget_can_raise_propose_budget": 1.0
        if bool(next_tier_changes.get("propose_budget"))
        else 0.0,
        "budget_can_raise_max_tokens": 1.0 if bool(next_tier_changes.get("max_tokens")) else 0.0,
        "budget_can_raise_helper_allowance": 1.0
        if bool(next_tier_changes.get("helper_allowance"))
        else 0.0,
        "budget_can_raise_stage_a_requests": 1.0
        if bool(next_tier_changes.get("stage_a_requests"))
        else 0.0,
        "budget_can_raise_stage_a_chars": 1.0
        if bool(next_tier_changes.get("stage_a_chars"))
        else 0.0,
        "budget_can_raise_retry_history_budget": 1.0
        if bool(next_tier_changes.get("retry_history_budget"))
        else 0.0,
        "budget_can_raise_compaction_allowance": 1.0
        if bool(next_tier_changes.get("compaction_allowance"))
        else 0.0,
        "budget_can_switch_response_mode": 1.0
        if bool(next_tier_changes.get("response_mode"))
        else 0.0,
        "budget_next_tier_multiplier_delta": float(max(0.0, float(next_tier_multiplier_delta))),
        "last_helper_need_more_context": 1.0 if last_helper_need_more_context is True else 0.0,
        "last_helper_invalid_response": 1.0
        if last_decision_reason == "llm_invalid_response_no_helper"
        else 0.0,
        "last_helper_declined": 1.0 if last_decision_reason == "llm_declined_helper" else 0.0,
    }


def _adaptive_budget_local_reward_components(
    *,
    success: bool,
    local_tokens_total: int,
    local_wall_sec: float,
    adaptive_progress_score: float,
    adaptive_no_progress_streak: int,
    lambda_tokens: float,
    mu_latency: float,
    token_scale: float,
    latency_scale: float,
    eta_progress: float,
    kappa_stagnation: float,
    rho_budget_exceeded: float,
    request_budget_exceeded: bool,
) -> tuple[float, float, float, float, float, float, float]:
    success_hint = ADAPTIVE_LOCAL_REWARD_SUCCESS_HINT if bool(success) else 0.0
    progress_score = max(0.0, min(1.0, float(adaptive_progress_score)))
    progress_hint = (
        ADAPTIVE_LOCAL_REWARD_PROGRESS_HINT_SCALE * max(0.0, float(eta_progress)) * progress_score
    )
    token_penalty = max(0.0, float(lambda_tokens)) * (
        max(0.0, float(local_tokens_total)) / max(1.0, float(token_scale))
    )
    latency_penalty = max(0.0, float(mu_latency)) * (
        max(0.0, float(local_wall_sec)) / max(1.0, float(latency_scale))
    )
    stagnation_penalty = (
        ADAPTIVE_LOCAL_REWARD_STAGNATION_PENALTY_SCALE
        * max(0.0, float(kappa_stagnation))
        * min(1.0, max(0.0, float(adaptive_no_progress_streak)) / 3.0)
    )
    budget_exceeded_penalty = (
        max(0.0, float(rho_budget_exceeded)) if bool(request_budget_exceeded) else 0.0
    )
    reward = (
        success_hint
        + progress_hint
        - token_penalty
        - latency_penalty
        - stagnation_penalty
        - budget_exceeded_penalty
    )
    return (
        reward,
        success_hint,
        progress_hint,
        token_penalty,
        latency_penalty,
        stagnation_penalty,
        budget_exceeded_penalty,
    )


def _adaptive_online_delayed_rescue_bonus(
    *,
    origin_attempt: int,
    resolved_attempt: int,
    failure_signature_repeat: bool,
    case_difficulty: str,
    case_snapshot_dependency: str,
) -> float:
    horizon_attempts = max(0, int(resolved_attempt) - int(origin_attempt))
    if horizon_attempts <= 0 or horizon_attempts > ADAPTIVE_DELAYED_RESCUE_HORIZON_ATTEMPTS:
        return 0.0
    bonus = ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_BONUS_BASE
    if horizon_attempts == 2:
        bonus *= ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_HORIZON_TWO_FACTOR
    if (
        bool(failure_signature_repeat)
        or str(case_difficulty).strip().lower() == "hard"
        or str(case_snapshot_dependency).strip().lower() in {"helpful", "required"}
    ):
        bonus *= ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_CONTEXT_MULTIPLIER
    return min(ADAPTIVE_LOCAL_REWARD_DELAYED_RESCUE_BONUS_MAX, max(0.0, float(bonus)))


def _sync_adaptive_online_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    mode: str,
    algo: str,
    decision: PolicyDecision | None,
    eligible: bool,
    override_reason: str,
    feature_scaling_profile: str,
    feature_values_raw: dict[str, float],
    feature_values_scaled: dict[str, float],
    reward_lambda_tokens: float,
    reward_mu_latency: float,
    reward_token_scale: float,
    reward_latency_scale: float,
    reward_eta_progress: float,
    reward_kappa_stagnation: float,
    reward_scope: str = ADAPTIVE_ONLINE_REWARD_SCOPE,
    reward: float | None = None,
    reward_success_hint: float = 0.0,
    reward_progress_hint: float = 0.0,
    reward_progress_score: float = 0.0,
    reward_token_penalty: float = 0.0,
    reward_latency_penalty: float = 0.0,
    reward_stagnation_penalty: float = 0.0,
    reward_helper_materialization_bonus: float = 0.0,
    reward_delayed_rescue_bonus: float = 0.0,
    reward_credit_resolved_attempt: int = 0,
    reward_credit_horizon_attempts: int = 0,
    reward_credit_resolution_reason: str = "",
    local_accounting: AdaptivePolicyLocalAccounting = EMPTY_ADAPTIVE_POLICY_LOCAL_ACCOUNTING,
    updated: bool = False,
    update_reason: str = "",
    event_logged: bool = False,
    behavior_action: str = "none",
) -> None:
    selected_action = decision.selected_action if decision is not None else "none"
    applied_action = decision.applied_action if decision is not None else "none"
    decision_id = decision.decision_id if decision is not None else ""
    decision_strategy = decision.decision_strategy if decision is not None else ""
    propensity = float(decision.propensity) if decision is not None else 1.0
    behavior_action, behavior_propensity = _adaptive_online_behavior_fields(
        decision=decision,
        behavior_action=behavior_action,
    )
    fields = {
        "adaptive_online_policy_mode": str(mode),
        "adaptive_online_policy_algo": str(algo),
        "adaptive_online_decision_id": str(decision_id),
        "adaptive_online_decision_strategy": str(decision_strategy),
        "adaptive_online_action_selected": str(selected_action),
        "adaptive_online_action_applied": str(applied_action),
        "adaptive_online_propensity": float(propensity),
        "adaptive_online_behavior_action": behavior_action,
        "adaptive_online_behavior_propensity": float(behavior_propensity),
        "adaptive_online_override_reason": str(override_reason),
        "adaptive_online_feature_scaling_profile": str(feature_scaling_profile),
        "adaptive_online_feature_vector_raw": dict(feature_values_raw),
        "adaptive_online_feature_vector": dict(feature_values_scaled),
        "adaptive_online_eligible": bool(eligible),
        "adaptive_online_reward_lambda_tokens": float(reward_lambda_tokens),
        "adaptive_online_reward_mu_latency": float(reward_mu_latency),
        "adaptive_online_reward_token_scale": float(reward_token_scale),
        "adaptive_online_reward_latency_scale": float(reward_latency_scale),
        "adaptive_online_reward_eta_progress": float(reward_eta_progress),
        "adaptive_online_reward_kappa_stagnation": float(reward_kappa_stagnation),
        "adaptive_online_reward_scope": str(reward_scope),
        "adaptive_online_reward": reward,
        "adaptive_online_reward_success_hint": float(reward_success_hint),
        "adaptive_online_reward_progress_hint": float(reward_progress_hint),
        "adaptive_online_reward_progress_score": float(reward_progress_score),
        "adaptive_online_reward_token_penalty": float(reward_token_penalty),
        "adaptive_online_reward_latency_penalty": float(reward_latency_penalty),
        "adaptive_online_reward_stagnation_penalty": float(reward_stagnation_penalty),
        "adaptive_online_reward_helper_materialization_bonus": float(
            reward_helper_materialization_bonus
        ),
        "adaptive_online_reward_delayed_rescue_bonus": float(reward_delayed_rescue_bonus),
        "adaptive_online_reward_credit_resolved_attempt": int(reward_credit_resolved_attempt),
        "adaptive_online_reward_credit_horizon_attempts": int(reward_credit_horizon_attempts),
        "adaptive_online_reward_credit_resolution_reason": str(reward_credit_resolution_reason),
        "adaptive_online_local_llm_call_count": int(local_accounting.llm_call_count),
        "adaptive_online_local_tokens_prompt": int(local_accounting.tokens_prompt),
        "adaptive_online_local_tokens_completion": int(local_accounting.tokens_completion),
        "adaptive_online_local_tokens_total": int(local_accounting.tokens_total),
        "adaptive_online_local_wall_sec": float(local_accounting.wall_sec),
        "adaptive_online_updated": bool(updated),
        "adaptive_online_update_reason": str(update_reason),
        "adaptive_online_event_logged": bool(event_logged),
    }
    attempt_metadata.update(fields)
    patch_meta.update(fields)


def _adaptive_online_behavior_fields(
    *,
    decision: PolicyDecision | None,
    behavior_action: str,
) -> tuple[str, float]:
    if decision is None:
        return "none", 1.0
    behavior_action_norm = (
        str(behavior_action).strip()
        if str(behavior_action).strip() in ONLINE_POLICY_ACTIONS
        else "none"
    )
    if behavior_action_norm == "none":
        return "none", 1.0
    behavior_propensity = 1.0
    if decision.selected_action == behavior_action_norm:
        behavior_propensity = float(decision.propensity)
    return behavior_action_norm, behavior_propensity


def _adaptive_budget_behavior_fields(
    *,
    decision: BudgetPolicyDecision | None,
) -> tuple[str, float]:
    if decision is None:
        return "none", 1.0
    selected_action = str(decision.selected_action)
    applied_action = str(decision.applied_action)
    behavior_action = applied_action if applied_action in BUDGET_POLICY_ACTIONS else "none"
    behavior_propensity = 1.0
    if selected_action == behavior_action:
        behavior_propensity = float(decision.propensity)
    return behavior_action, behavior_propensity


def _adaptive_online_update_suppression_reason(helper_outcome: str) -> str:
    if helper_outcome == ADAPTIVE_HELPER_OUTCOME_TRANSPORT_FAILED:
        return "helper_transport_no_update"
    if helper_outcome == ADAPTIVE_HELPER_OUTCOME_UNAVAILABLE_BACKEND:
        return "helper_backend_unavailable_no_update"
    if helper_outcome == ADAPTIVE_HELPER_OUTCOME_INVALID_PAYLOAD:
        return "helper_invalid_payload_no_update"
    return ""


def _sync_record_metadata_copies(
    *,
    record: dict[str, Any] | None,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
) -> None:
    if not isinstance(record, dict):
        return
    record["attempt_metadata"] = dict(attempt_metadata)
    if "patch_meta" in record:
        record["patch_meta"] = dict(patch_meta)


def _adaptive_online_reward_breakdown(
    *,
    success: bool,
    helper_materialized_context: bool,
    local_tokens_total: int,
    local_wall_sec: float,
    adaptive_progress_score: float,
    adaptive_no_progress_streak: int,
    lambda_tokens: float,
    mu_latency: float,
    token_scale: float,
    latency_scale: float,
    eta_progress: float,
    kappa_stagnation: float,
    delayed_rescue_bonus: float = 0.0,
    resolved_attempt: int = 0,
    horizon_attempts: int = 0,
    resolution_reason: str = "",
    reward_scope: str = ADAPTIVE_ONLINE_REWARD_SCOPE,
) -> AdaptiveOnlineRewardBreakdown:
    (
        reward,
        reward_success_hint,
        reward_progress_hint,
        reward_token_penalty,
        reward_latency_penalty,
        reward_stagnation_penalty,
        reward_helper_materialization_bonus,
    ) = _adaptive_online_local_reward_components(
        success=success,
        helper_materialized_context=helper_materialized_context,
        local_tokens_total=local_tokens_total,
        local_wall_sec=local_wall_sec,
        adaptive_progress_score=adaptive_progress_score,
        adaptive_no_progress_streak=adaptive_no_progress_streak,
        lambda_tokens=lambda_tokens,
        mu_latency=mu_latency,
        token_scale=token_scale,
        latency_scale=latency_scale,
        eta_progress=eta_progress,
        kappa_stagnation=kappa_stagnation,
    )
    final_reward = reward + max(0.0, float(delayed_rescue_bonus))
    return AdaptiveOnlineRewardBreakdown(
        reward_scope=str(reward_scope),
        reward=float(final_reward),
        reward_success_hint=float(reward_success_hint),
        reward_progress_hint=float(reward_progress_hint),
        reward_progress_score=max(0.0, min(1.0, float(adaptive_progress_score))),
        reward_token_penalty=float(reward_token_penalty),
        reward_latency_penalty=float(reward_latency_penalty),
        reward_stagnation_penalty=float(reward_stagnation_penalty),
        reward_helper_materialization_bonus=float(reward_helper_materialization_bonus),
        reward_delayed_rescue_bonus=max(0.0, float(delayed_rescue_bonus)),
        reward_credit_resolved_attempt=max(0, int(resolved_attempt)),
        reward_credit_horizon_attempts=max(0, int(horizon_attempts)),
        reward_credit_resolution_reason=str(resolution_reason or ""),
    )


def _record_adaptive_online_policy_outcome(
    *,
    adaptive_online_policy_controller: AdaptiveOnlinePolicyController | None,
    decision: PolicyDecision | None,
    run_id: str,
    case_id: str,
    condition_value: str,
    attempt: int,
    note: str,
    success: bool,
    reward_breakdown: AdaptiveOnlineRewardBreakdown,
    local_accounting: AdaptivePolicyLocalAccounting,
    override_reason: str,
    behavior_action: str,
    behavior_propensity: float,
    allow_model_update: bool,
    update_reason_override: str = "",
) -> tuple[bool, bool, str, str]:
    effective_override_reason = str(override_reason or "")
    updated = False
    event_logged = False
    update_reason = "policy_off"
    if decision is None:
        return updated, event_logged, update_reason, effective_override_reason
    if adaptive_online_policy_controller is None:
        return False, False, "policy_controller_unavailable", effective_override_reason
    try:
        policy_event = adaptive_online_policy_controller.record_outcome(
            decision=decision,
            run_id=run_id,
            case_id=case_id,
            condition=condition_value,
            attempt=attempt,
            note=note,
            reward_scope=reward_breakdown.reward_scope,
            reward=reward_breakdown.reward,
            reward_success_hint=reward_breakdown.reward_success_hint,
            reward_progress_hint=reward_breakdown.reward_progress_hint,
            reward_progress_score=reward_breakdown.reward_progress_score,
            reward_token_penalty=reward_breakdown.reward_token_penalty,
            reward_latency_penalty=reward_breakdown.reward_latency_penalty,
            reward_stagnation_penalty=reward_breakdown.reward_stagnation_penalty,
            reward_helper_materialization_bonus=reward_breakdown.reward_helper_materialization_bonus,
            reward_delayed_rescue_bonus=reward_breakdown.reward_delayed_rescue_bonus,
            reward_credit_resolved_attempt=reward_breakdown.reward_credit_resolved_attempt,
            reward_credit_horizon_attempts=reward_breakdown.reward_credit_horizon_attempts,
            reward_credit_resolution_reason=reward_breakdown.reward_credit_resolution_reason,
            success=success,
            local_llm_call_count=local_accounting.llm_call_count,
            local_tokens_prompt=local_accounting.tokens_prompt,
            local_tokens_completion=local_accounting.tokens_completion,
            local_tokens_total=local_accounting.tokens_total,
            local_wall_sec=local_accounting.wall_sec,
            override_reason=effective_override_reason,
            behavior_action=behavior_action,
            behavior_propensity=behavior_propensity,
            allow_model_update=allow_model_update,
            update_reason_override=update_reason_override or None,
        )
        updated = bool(policy_event.model_updated)
        event_logged = True
        update_reason = str(policy_event.update_reason)
        if not effective_override_reason:
            effective_override_reason = str(policy_event.override_reason)
    except Exception as exc:
        update_reason = f"event_logging_error:{type(exc).__name__}"
    return updated, event_logged, update_reason, effective_override_reason


def _adaptive_online_should_defer_credit(
    *,
    is_adaptive_llm_discretion: bool,
    decision: PolicyDecision | None,
    adaptive_online_policy_controller: AdaptiveOnlinePolicyController | None,
    behavior_action: str,
    helper_outcome: str,
    suppression_reason: str,
    success: bool,
) -> bool:
    return bool(
        is_adaptive_llm_discretion
        and decision is not None
        and adaptive_online_policy_controller is not None
        and behavior_action in HELPER_CONTEXT_MATERIALIZING_ACTIONS
        and helper_outcome == ADAPTIVE_HELPER_OUTCOME_MATERIALIZED
        and not suppression_reason
        and not success
    )


def _resolve_pending_adaptive_online_credit(
    *,
    pending: PendingAdaptiveOnlineCredit,
    adaptive_online_policy_controller: AdaptiveOnlinePolicyController | None,
    delayed_rescue_bonus: float,
    resolved_attempt: int,
    resolution_reason: str,
) -> None:
    behavior_action, behavior_propensity = _adaptive_online_behavior_fields(
        decision=pending.decision,
        behavior_action=pending.behavior_action,
    )
    reward_breakdown = _adaptive_online_reward_breakdown(
        success=pending.success,
        helper_materialized_context=(
            behavior_action != ACTION_SKIP_HELPER and bool(pending.context_materialized)
        ),
        local_tokens_total=pending.local_accounting.tokens_total,
        local_wall_sec=pending.local_accounting.wall_sec,
        adaptive_progress_score=pending.progress_state.score,
        adaptive_no_progress_streak=pending.adaptive_no_progress_streak_current,
        lambda_tokens=pending.reward_lambda_tokens,
        mu_latency=pending.reward_mu_latency,
        token_scale=pending.reward_token_scale,
        latency_scale=pending.reward_latency_scale,
        eta_progress=pending.reward_eta_progress,
        kappa_stagnation=pending.reward_kappa_stagnation,
        delayed_rescue_bonus=delayed_rescue_bonus,
        resolved_attempt=resolved_attempt,
        horizon_attempts=max(0, int(resolved_attempt) - int(pending.attempt))
        if resolved_attempt > 0
        else 0,
        resolution_reason=resolution_reason,
    )
    updated, event_logged, update_reason, effective_override_reason = (
        _record_adaptive_online_policy_outcome(
            adaptive_online_policy_controller=adaptive_online_policy_controller,
            decision=pending.decision,
            run_id=pending.run_id,
            case_id=pending.case_id,
            condition_value=pending.condition_value,
            attempt=pending.attempt,
            note=pending.note,
            success=pending.success,
            reward_breakdown=reward_breakdown,
            local_accounting=pending.local_accounting,
            override_reason=pending.online_policy_override_reason or pending.override_reason,
            behavior_action=behavior_action,
            behavior_propensity=behavior_propensity,
            allow_model_update=True,
        )
    )
    _sync_adaptive_online_metadata(
        attempt_metadata=pending.attempt_metadata,
        patch_meta=pending.patch_meta,
        mode=pending.online_policy_mode_for_attempt,
        algo=pending.online_policy_algo_for_attempt,
        decision=pending.decision,
        eligible=pending.online_policy_eligible,
        override_reason=effective_override_reason,
        feature_scaling_profile=pending.online_policy_feature_scaling_profile,
        feature_values_raw=pending.online_policy_features_raw,
        feature_values_scaled=pending.online_policy_features_scaled,
        reward_lambda_tokens=pending.reward_lambda_tokens,
        reward_mu_latency=pending.reward_mu_latency,
        reward_token_scale=pending.reward_token_scale,
        reward_latency_scale=pending.reward_latency_scale,
        reward_eta_progress=pending.reward_eta_progress,
        reward_kappa_stagnation=pending.reward_kappa_stagnation,
        reward_scope=reward_breakdown.reward_scope,
        reward=reward_breakdown.reward,
        reward_success_hint=reward_breakdown.reward_success_hint,
        reward_progress_hint=reward_breakdown.reward_progress_hint,
        reward_progress_score=reward_breakdown.reward_progress_score,
        reward_token_penalty=reward_breakdown.reward_token_penalty,
        reward_latency_penalty=reward_breakdown.reward_latency_penalty,
        reward_stagnation_penalty=reward_breakdown.reward_stagnation_penalty,
        reward_helper_materialization_bonus=reward_breakdown.reward_helper_materialization_bonus,
        reward_delayed_rescue_bonus=reward_breakdown.reward_delayed_rescue_bonus,
        reward_credit_resolved_attempt=reward_breakdown.reward_credit_resolved_attempt,
        reward_credit_horizon_attempts=reward_breakdown.reward_credit_horizon_attempts,
        reward_credit_resolution_reason=reward_breakdown.reward_credit_resolution_reason,
        local_accounting=pending.local_accounting,
        updated=updated,
        update_reason=update_reason,
        event_logged=event_logged,
        behavior_action=behavior_action,
    )
    _sync_record_metadata_copies(
        record=pending.record_ref,
        attempt_metadata=pending.attempt_metadata,
        patch_meta=pending.patch_meta,
    )


def _finalize_adaptive_online_attempt(
    *,
    note: str,
    success: bool,
    attempt_wall_sec: float,
    is_adaptive_llm_discretion: bool,
    online_policy_decision: PolicyDecision | None,
    adaptive_online_policy_controller: AdaptiveOnlinePolicyController | None,
    run_id: str,
    case_id: str,
    condition_value: str,
    attempt: int,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    online_policy_mode_for_attempt: str,
    online_policy_algo_for_attempt: str,
    online_policy_eligible: bool,
    online_policy_override_reason: str,
    online_policy_feature_scaling_profile: str,
    online_policy_features_raw: dict[str, float],
    online_policy_features_scaled: dict[str, float],
    helper_decision_reason: str,
    normalized_adaptive_online_policy_mode: str,
    case_difficulty: str,
    case_snapshot_dependency: str,
    adaptive_progress_made: bool,
    adaptive_progress_score: float,
    adaptive_no_progress_streak_current: int,
    reward_lambda_tokens: float,
    reward_mu_latency: float,
    reward_token_scale: float,
    reward_latency_scale: float,
    reward_eta_progress: float,
    reward_kappa_stagnation: float,
    local_accounting: AdaptivePolicyLocalAccounting = EMPTY_ADAPTIVE_POLICY_LOCAL_ACCOUNTING,
    override_reason: str = "",
    behavior_action: str = "none",
    context_materialized: bool = False,
    helper_outcome: str = ADAPTIVE_HELPER_OUTCOME_NOT_INVOKED,
) -> PendingAdaptiveOnlineCredit | None:
    """Finalize the online-policy bookkeeping after an attempt.

    Either resolves the reward immediately or queues a
    ``PendingAdaptiveOnlineCredit`` for deferred resolution.
    """
    effective_override_reason = (
        online_policy_override_reason
        if online_policy_override_reason
        else str(override_reason or "")
    )
    behavior_action, behavior_propensity = _adaptive_online_behavior_fields(
        decision=online_policy_decision,
        behavior_action=behavior_action,
    )
    charged_local_accounting = (
        local_accounting
        if behavior_action != ACTION_SKIP_HELPER
        else AdaptivePolicyLocalAccounting()
    )
    reward_breakdown = _adaptive_online_reward_breakdown(
        success=success,
        helper_materialized_context=(
            behavior_action != ACTION_SKIP_HELPER and bool(context_materialized)
        ),
        local_tokens_total=charged_local_accounting.tokens_total,
        local_wall_sec=charged_local_accounting.wall_sec,
        adaptive_progress_score=adaptive_progress_score,
        adaptive_no_progress_streak=adaptive_no_progress_streak_current,
        lambda_tokens=reward_lambda_tokens,
        mu_latency=reward_mu_latency,
        token_scale=reward_token_scale,
        latency_scale=reward_latency_scale,
        eta_progress=reward_eta_progress,
        kappa_stagnation=reward_kappa_stagnation,
    )
    updated = False
    event_logged = False
    update_reason = "policy_off"
    suppression_reason = _adaptive_online_update_suppression_reason(helper_outcome)
    allow_model_update = not bool(suppression_reason)
    pending_credit: PendingAdaptiveOnlineCredit | None = None
    if (
        is_adaptive_llm_discretion
        and online_policy_decision is not None
        and adaptive_online_policy_controller is not None
    ):
        if _adaptive_online_should_defer_credit(
            is_adaptive_llm_discretion=is_adaptive_llm_discretion,
            decision=online_policy_decision,
            adaptive_online_policy_controller=adaptive_online_policy_controller,
            behavior_action=behavior_action,
            helper_outcome=helper_outcome,
            suppression_reason=suppression_reason,
            success=success,
        ):
            update_reason = "pending_delayed_credit"
            pending_credit = PendingAdaptiveOnlineCredit(
                decision=online_policy_decision,
                run_id=run_id,
                case_id=case_id,
                condition_value=condition_value,
                attempt=attempt,
                note=note,
                success=success,
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                record_ref=None,
                online_policy_mode_for_attempt=online_policy_mode_for_attempt,
                online_policy_algo_for_attempt=online_policy_algo_for_attempt,
                online_policy_eligible=online_policy_eligible,
                online_policy_override_reason=effective_override_reason,
                online_policy_feature_scaling_profile=online_policy_feature_scaling_profile,
                online_policy_features_raw=dict(online_policy_features_raw),
                online_policy_features_scaled=dict(online_policy_features_scaled),
                helper_decision_reason=helper_decision_reason,
                normalized_adaptive_online_policy_mode=normalized_adaptive_online_policy_mode,
                adaptive_no_progress_streak_current=adaptive_no_progress_streak_current,
                reward_lambda_tokens=reward_lambda_tokens,
                reward_mu_latency=reward_mu_latency,
                reward_token_scale=reward_token_scale,
                reward_latency_scale=reward_latency_scale,
                reward_eta_progress=reward_eta_progress,
                reward_kappa_stagnation=reward_kappa_stagnation,
                local_accounting=local_accounting,
                override_reason=override_reason,
                behavior_action=behavior_action,
                context_materialized=context_materialized,
                helper_outcome=helper_outcome,
                progress_state=AdaptiveProgressState(
                    made=bool(adaptive_progress_made),
                    reason=str(attempt_metadata.get("adaptive_progress_reason") or ""),
                    score=max(0.0, float(adaptive_progress_score)),
                    component_signature=max(
                        0.0,
                        float(attempt_metadata.get("adaptive_progress_component_signature") or 0.0),
                    ),
                    component_new_evidence=max(
                        0.0,
                        float(
                            attempt_metadata.get("adaptive_progress_component_new_evidence") or 0.0
                        ),
                    ),
                    component_pytest_fail_reduction=max(
                        0.0,
                        float(
                            attempt_metadata.get(
                                "adaptive_progress_component_pytest_fail_reduction"
                            )
                            or 0.0
                        ),
                    ),
                    component_stage_advance=max(
                        0.0,
                        float(
                            attempt_metadata.get("adaptive_progress_component_stage_advance") or 0.0
                        ),
                    ),
                    component_verifier_score=max(
                        0.0,
                        float(
                            attempt_metadata.get("adaptive_progress_component_verifier_score")
                            or 0.0
                        ),
                    ),
                    component_scope_alignment=max(
                        0.0,
                        float(
                            attempt_metadata.get("adaptive_progress_component_scope_alignment")
                            or 0.0
                        ),
                    ),
                    pytest_before_failed_total=_adaptive_optional_int(
                        attempt_metadata.get("adaptive_pytest_before_failed_total")
                    ),
                    pytest_after_failed_total=_adaptive_optional_int(
                        attempt_metadata.get("adaptive_pytest_after_failed_total")
                    ),
                    stage_before=str(attempt_metadata.get("adaptive_pipeline_stage_before") or ""),
                    stage_after=str(attempt_metadata.get("adaptive_pipeline_stage_after") or ""),
                    stage_before_ord=_nonnegative_int(
                        attempt_metadata.get("adaptive_pipeline_stage_before_ord"),
                        default=0,
                    ),
                    stage_after_ord=_nonnegative_int(
                        attempt_metadata.get("adaptive_pipeline_stage_after_ord"),
                        default=0,
                    ),
                ),
                failure_signature_repeat=bool(attempt_metadata.get("adaptive_signature_repeat")),
                case_difficulty=str(case_difficulty or ""),
                case_snapshot_dependency=str(case_snapshot_dependency or ""),
            )
        else:
            updated, event_logged, update_reason, effective_override_reason = (
                _record_adaptive_online_policy_outcome(
                    adaptive_online_policy_controller=adaptive_online_policy_controller,
                    decision=online_policy_decision,
                    run_id=run_id,
                    case_id=case_id,
                    condition_value=condition_value,
                    attempt=attempt,
                    note=note,
                    success=success,
                    reward_breakdown=reward_breakdown,
                    local_accounting=local_accounting,
                    override_reason=effective_override_reason,
                    behavior_action=behavior_action,
                    behavior_propensity=behavior_propensity,
                    allow_model_update=allow_model_update,
                    update_reason_override=suppression_reason,
                )
            )
    elif is_adaptive_llm_discretion and online_policy_decision is None:
        if not online_policy_eligible:
            update_reason = f"not_eligible:{helper_decision_reason}"
        elif normalized_adaptive_online_policy_mode == "off":
            update_reason = "policy_off"
        else:
            update_reason = "no_decision_emitted"
    elif is_adaptive_llm_discretion:
        update_reason = "policy_controller_unavailable"
    else:
        update_reason = "non_llm_discretion_condition"

    _sync_adaptive_online_metadata(
        attempt_metadata=attempt_metadata,
        patch_meta=patch_meta,
        mode=online_policy_mode_for_attempt,
        algo=online_policy_algo_for_attempt,
        decision=online_policy_decision,
        eligible=online_policy_eligible,
        override_reason=effective_override_reason,
        feature_scaling_profile=online_policy_feature_scaling_profile,
        feature_values_raw=online_policy_features_raw,
        feature_values_scaled=online_policy_features_scaled,
        reward_lambda_tokens=reward_lambda_tokens,
        reward_mu_latency=reward_mu_latency,
        reward_token_scale=reward_token_scale,
        reward_latency_scale=reward_latency_scale,
        reward_eta_progress=reward_eta_progress,
        reward_kappa_stagnation=reward_kappa_stagnation,
        reward_scope=ADAPTIVE_ONLINE_REWARD_SCOPE,
        reward=reward_breakdown.reward,
        reward_success_hint=reward_breakdown.reward_success_hint,
        reward_progress_hint=reward_breakdown.reward_progress_hint,
        reward_progress_score=reward_breakdown.reward_progress_score,
        reward_token_penalty=reward_breakdown.reward_token_penalty,
        reward_latency_penalty=reward_breakdown.reward_latency_penalty,
        reward_stagnation_penalty=reward_breakdown.reward_stagnation_penalty,
        reward_helper_materialization_bonus=reward_breakdown.reward_helper_materialization_bonus,
        reward_delayed_rescue_bonus=reward_breakdown.reward_delayed_rescue_bonus,
        reward_credit_resolved_attempt=reward_breakdown.reward_credit_resolved_attempt,
        reward_credit_horizon_attempts=reward_breakdown.reward_credit_horizon_attempts,
        reward_credit_resolution_reason=reward_breakdown.reward_credit_resolution_reason,
        local_accounting=local_accounting,
        updated=updated,
        update_reason=update_reason,
        event_logged=event_logged,
        behavior_action=behavior_action,
    )
    return pending_credit


def _sync_adaptive_budget_policy_metadata(
    *,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    mode: str,
    algo: str,
    decision: BudgetPolicyDecision | None,
    eligible: bool,
    override_reason: str,
    feature_scaling_profile: str,
    feature_values_raw: dict[str, float],
    feature_values_scaled: dict[str, float],
    tiers: list[float],
    tier_index_before: int,
    tier_index_after: int,
    selected_multiplier: float,
    applied_multiplier: float,
    default_multiplier: float,
    default_controls: Mapping[str, Any],
    selected_controls: Mapping[str, Any],
    applied_controls: Mapping[str, Any],
    selected_controls_changed: Mapping[str, bool],
    applied_controls_changed: Mapping[str, bool],
    partial_override: bool,
    partial_override_errors: Mapping[str, str],
    max_ups_per_case: int,
    ups_used: int,
    reward_lambda_tokens: float,
    reward_mu_latency: float,
    reward_token_scale: float,
    reward_latency_scale: float,
    reward_rho_budget_exceeded: float,
    reward_eta_progress: float,
    reward_kappa_stagnation: float,
    reward_scope: str = ADAPTIVE_BUDGET_REWARD_SCOPE,
    reward: float | None = None,
    reward_success_hint: float = 0.0,
    reward_progress_hint: float = 0.0,
    reward_progress_score: float = 0.0,
    reward_token_penalty: float = 0.0,
    reward_latency_penalty: float = 0.0,
    reward_stagnation_penalty: float = 0.0,
    reward_budget_exceeded_penalty: float = 0.0,
    request_budget_exceeded: bool = False,
    local_accounting: AdaptivePolicyLocalAccounting = EMPTY_ADAPTIVE_POLICY_LOCAL_ACCOUNTING,
    updated: bool = False,
    update_reason: str = "",
    event_logged: bool = False,
) -> None:
    selected_action = decision.selected_action if decision is not None else "none"
    applied_action = decision.applied_action if decision is not None else "none"
    decision_id = decision.decision_id if decision is not None else ""
    decision_strategy = decision.decision_strategy if decision is not None else ""
    propensity = float(decision.propensity) if decision is not None else 1.0
    behavior_action, behavior_propensity = _adaptive_budget_behavior_fields(decision=decision)
    fields = {
        "adaptive_budget_policy_mode": str(mode),
        "adaptive_budget_policy_algo": str(algo),
        "adaptive_budget_decision_id": str(decision_id),
        "adaptive_budget_decision_strategy": str(decision_strategy),
        "adaptive_budget_action_selected": str(selected_action),
        "adaptive_budget_action_applied": str(applied_action),
        "adaptive_budget_propensity": float(propensity),
        "adaptive_budget_behavior_action": behavior_action,
        "adaptive_budget_behavior_propensity": float(behavior_propensity),
        "adaptive_budget_override_reason": str(override_reason),
        "adaptive_budget_feature_scaling_profile": str(feature_scaling_profile),
        "adaptive_budget_feature_vector_raw": dict(feature_values_raw),
        "adaptive_budget_feature_vector": dict(feature_values_scaled),
        "adaptive_budget_eligible": bool(eligible),
        "adaptive_budget_tiers": [float(value) for value in tiers],
        "adaptive_budget_tier_index_before": int(tier_index_before),
        "adaptive_budget_tier_index_after": int(tier_index_after),
        "adaptive_budget_selected_multiplier": float(selected_multiplier),
        "adaptive_budget_applied_multiplier": float(applied_multiplier),
        "adaptive_budget_default_multiplier": float(default_multiplier),
        "adaptive_budget_default_control_values": dict(default_controls),
        "adaptive_budget_selected_control_values": dict(selected_controls),
        "adaptive_budget_applied_control_values": dict(applied_controls),
        "adaptive_budget_selected_controls_changed": {
            str(key): bool(value) for key, value in selected_controls_changed.items()
        },
        "adaptive_budget_applied_controls_changed": {
            str(key): bool(value) for key, value in applied_controls_changed.items()
        },
        "adaptive_budget_partial_override": bool(partial_override),
        "adaptive_budget_partial_override_errors": {
            str(key): str(value) for key, value in partial_override_errors.items()
        },
        "adaptive_budget_effective_helper_max_calls": int(
            applied_controls.get("helper_max_calls", 0) or 0
        ),
        "adaptive_budget_effective_stage_a_max_requests": int(
            applied_controls.get("stage_a_max_requests", 1) or 1
        ),
        "adaptive_budget_effective_stage_a_max_chars": int(
            applied_controls.get("stage_a_max_chars", 1) or 1
        ),
        "adaptive_budget_effective_retry_history_max_chars": int(
            applied_controls.get("retry_history_max_chars", 0) or 0
        ),
        "adaptive_budget_effective_context_compaction_max_calls": int(
            applied_controls.get("context_compaction_max_calls", 0) or 0
        ),
        "adaptive_budget_effective_openai_max_tokens": int(
            applied_controls.get("openai_max_tokens", 0) or 0
        ),
        "adaptive_budget_effective_response_mode": str(applied_controls.get("response_mode") or ""),
        "adaptive_budget_max_ups_per_case": int(max_ups_per_case),
        "adaptive_budget_ups_used": int(ups_used),
        "adaptive_budget_reward_lambda_tokens": float(reward_lambda_tokens),
        "adaptive_budget_reward_mu_latency": float(reward_mu_latency),
        "adaptive_budget_reward_token_scale": float(reward_token_scale),
        "adaptive_budget_reward_latency_scale": float(reward_latency_scale),
        "adaptive_budget_reward_rho_budget_exceeded": float(reward_rho_budget_exceeded),
        "adaptive_budget_reward_eta_progress": float(reward_eta_progress),
        "adaptive_budget_reward_kappa_stagnation": float(reward_kappa_stagnation),
        "adaptive_budget_reward_scope": str(reward_scope),
        "adaptive_budget_reward": reward,
        "adaptive_budget_reward_success_hint": float(reward_success_hint),
        "adaptive_budget_reward_progress_hint": float(reward_progress_hint),
        "adaptive_budget_reward_progress_score": float(reward_progress_score),
        "adaptive_budget_reward_token_penalty": float(reward_token_penalty),
        "adaptive_budget_reward_latency_penalty": float(reward_latency_penalty),
        "adaptive_budget_reward_stagnation_penalty": float(reward_stagnation_penalty),
        "adaptive_budget_reward_budget_exceeded_penalty": float(reward_budget_exceeded_penalty),
        "adaptive_budget_request_budget_exceeded": bool(request_budget_exceeded),
        "adaptive_budget_local_llm_call_count": int(local_accounting.llm_call_count),
        "adaptive_budget_local_tokens_prompt": int(local_accounting.tokens_prompt),
        "adaptive_budget_local_tokens_completion": int(local_accounting.tokens_completion),
        "adaptive_budget_local_tokens_total": int(local_accounting.tokens_total),
        "adaptive_budget_local_wall_sec": float(local_accounting.wall_sec),
        "adaptive_budget_updated": bool(updated),
        "adaptive_budget_update_reason": str(update_reason),
        "adaptive_budget_event_logged": bool(event_logged),
    }
    attempt_metadata.update(fields)
    patch_meta.update(fields)


def _finalize_adaptive_budget_policy_attempt(
    *,
    note: str,
    success: bool,
    attempt_wall_sec: float,
    is_adaptive_condition: bool,
    budget_policy_decision: BudgetPolicyDecision | None,
    adaptive_budget_policy_controller: AdaptiveBudgetPolicyController | None,
    run_id: str,
    case_id: str,
    condition_value: str,
    attempt: int,
    attempt_metadata: dict[str, Any],
    patch_meta: dict[str, Any],
    budget_policy_mode_for_attempt: str,
    budget_policy_algo_for_attempt: str,
    budget_policy_eligible: bool,
    budget_policy_override_reason: str,
    budget_policy_feature_scaling_profile: str,
    budget_policy_features_raw: dict[str, float],
    budget_policy_features_scaled: dict[str, float],
    normalized_adaptive_budget_policy_mode: str,
    adaptive_progress_made: bool,
    adaptive_progress_score: float,
    adaptive_no_progress_streak_current: int,
    reward_lambda_tokens: float,
    reward_mu_latency: float,
    reward_token_scale: float,
    reward_latency_scale: float,
    reward_eta_progress: float,
    reward_kappa_stagnation: float,
    reward_rho_budget_exceeded: float,
    local_accounting: AdaptivePolicyLocalAccounting = EMPTY_ADAPTIVE_POLICY_LOCAL_ACCOUNTING,
    tiers: list[float],
    tier_index_before: int,
    tier_index_after: int,
    selected_multiplier: float,
    applied_multiplier: float,
    default_multiplier: float,
    default_controls: Mapping[str, Any] | None = None,
    selected_controls: Mapping[str, Any] | None = None,
    applied_controls: Mapping[str, Any] | None = None,
    selected_controls_changed: Mapping[str, bool] | None = None,
    applied_controls_changed: Mapping[str, bool] | None = None,
    partial_override: bool = False,
    partial_override_errors: Mapping[str, str] | None = None,
    max_ups_per_case: int = 0,
    ups_used: int = 0,
    override_reason: str = "",
) -> None:
    """Finalize the budget-policy bookkeeping after an attempt.

    Computes the budget-specific reward, records the outcome, and
    updates the budget-policy controller.
    """
    default_controls = default_controls or {}
    selected_controls = selected_controls or {}
    applied_controls = applied_controls or {}
    selected_controls_changed = selected_controls_changed or {}
    applied_controls_changed = applied_controls_changed or {}
    partial_override_errors = partial_override_errors or {}
    effective_override_reason = (
        budget_policy_override_reason
        if budget_policy_override_reason
        else str(override_reason or "")
    )
    failure_error = str(
        attempt_metadata.get(
            "patcher_request_failure_error",
            patch_meta.get("patcher_request_failure_error", patch_meta.get("error")),
        )
        or ""
    ).strip()
    request_budget_exceeded = failure_error == "request_budget_exceeded"
    behavior_action, _ = _adaptive_budget_behavior_fields(decision=budget_policy_decision)
    charged_local_accounting = (
        local_accounting
        if behavior_action == ACTION_INCREASE_BUDGET
        else AdaptivePolicyLocalAccounting()
    )
    (
        reward,
        reward_success_hint,
        reward_progress_hint,
        reward_token_penalty,
        reward_latency_penalty,
        reward_stagnation_penalty,
        reward_budget_exceeded_penalty,
    ) = _adaptive_budget_local_reward_components(
        success=success,
        local_tokens_total=charged_local_accounting.tokens_total,
        local_wall_sec=charged_local_accounting.wall_sec,
        adaptive_progress_score=adaptive_progress_score,
        adaptive_no_progress_streak=adaptive_no_progress_streak_current,
        lambda_tokens=reward_lambda_tokens,
        mu_latency=reward_mu_latency,
        token_scale=reward_token_scale,
        latency_scale=reward_latency_scale,
        eta_progress=reward_eta_progress,
        kappa_stagnation=reward_kappa_stagnation,
        rho_budget_exceeded=reward_rho_budget_exceeded,
        request_budget_exceeded=request_budget_exceeded,
    )
    updated = False
    event_logged = False
    update_reason = "policy_off"
    if (
        is_adaptive_condition
        and budget_policy_decision is not None
        and adaptive_budget_policy_controller is not None
    ):
        try:
            policy_event = adaptive_budget_policy_controller.record_outcome(
                decision=budget_policy_decision,
                run_id=run_id,
                case_id=case_id,
                condition=condition_value,
                attempt=attempt,
                note=note,
                reward_scope=ADAPTIVE_BUDGET_REWARD_SCOPE,
                reward=reward,
                reward_success_hint=reward_success_hint,
                reward_progress_hint=reward_progress_hint,
                reward_progress_score=max(0.0, min(1.0, float(adaptive_progress_score))),
                reward_token_penalty=reward_token_penalty,
                reward_latency_penalty=reward_latency_penalty,
                reward_stagnation_penalty=reward_stagnation_penalty,
                reward_budget_exceeded_penalty=reward_budget_exceeded_penalty,
                request_budget_exceeded=request_budget_exceeded,
                success=success,
                local_llm_call_count=local_accounting.llm_call_count,
                local_tokens_prompt=local_accounting.tokens_prompt,
                local_tokens_completion=local_accounting.tokens_completion,
                local_tokens_total=local_accounting.tokens_total,
                local_wall_sec=local_accounting.wall_sec,
                selected_multiplier=selected_multiplier,
                applied_multiplier=applied_multiplier,
                default_multiplier=default_multiplier,
                default_controls=default_controls,
                selected_controls=selected_controls,
                applied_controls=applied_controls,
                selected_controls_changed=selected_controls_changed,
                applied_controls_changed=applied_controls_changed,
                partial_override=partial_override,
                partial_override_errors=partial_override_errors,
                tier_index_before=tier_index_before,
                tier_index_after=tier_index_after,
                override_reason=effective_override_reason,
            )
            updated = bool(policy_event.model_updated)
            event_logged = True
            update_reason = str(policy_event.update_reason)
            if not effective_override_reason:
                effective_override_reason = str(policy_event.override_reason)
        except Exception as exc:
            update_reason = f"event_logging_error:{type(exc).__name__}"
    elif is_adaptive_condition and budget_policy_decision is None:
        if not budget_policy_eligible:
            update_reason = "not_eligible"
        elif normalized_adaptive_budget_policy_mode == "off":
            update_reason = "policy_off"
        else:
            update_reason = "no_decision_emitted"
    elif is_adaptive_condition:
        update_reason = "policy_controller_unavailable"
    else:
        update_reason = "non_adaptive_condition"

    _sync_adaptive_budget_policy_metadata(
        attempt_metadata=attempt_metadata,
        patch_meta=patch_meta,
        mode=budget_policy_mode_for_attempt,
        algo=budget_policy_algo_for_attempt,
        decision=budget_policy_decision,
        eligible=budget_policy_eligible,
        override_reason=effective_override_reason,
        feature_scaling_profile=budget_policy_feature_scaling_profile,
        feature_values_raw=budget_policy_features_raw,
        feature_values_scaled=budget_policy_features_scaled,
        tiers=tiers,
        tier_index_before=tier_index_before,
        tier_index_after=tier_index_after,
        selected_multiplier=selected_multiplier,
        applied_multiplier=applied_multiplier,
        default_multiplier=default_multiplier,
        default_controls=default_controls,
        selected_controls=selected_controls,
        applied_controls=applied_controls,
        selected_controls_changed=selected_controls_changed,
        applied_controls_changed=applied_controls_changed,
        partial_override=partial_override,
        partial_override_errors=partial_override_errors,
        max_ups_per_case=max_ups_per_case,
        ups_used=ups_used,
        reward_lambda_tokens=reward_lambda_tokens,
        reward_mu_latency=reward_mu_latency,
        reward_token_scale=reward_token_scale,
        reward_latency_scale=reward_latency_scale,
        reward_rho_budget_exceeded=reward_rho_budget_exceeded,
        reward_eta_progress=reward_eta_progress,
        reward_kappa_stagnation=reward_kappa_stagnation,
        reward_scope=ADAPTIVE_BUDGET_REWARD_SCOPE,
        reward=reward,
        reward_success_hint=reward_success_hint,
        reward_progress_hint=reward_progress_hint,
        reward_progress_score=max(0.0, min(1.0, float(adaptive_progress_score))),
        reward_token_penalty=reward_token_penalty,
        reward_latency_penalty=reward_latency_penalty,
        reward_stagnation_penalty=reward_stagnation_penalty,
        reward_budget_exceeded_penalty=reward_budget_exceeded_penalty,
        request_budget_exceeded=request_budget_exceeded,
        local_accounting=local_accounting,
        updated=updated,
        update_reason=update_reason,
        event_logged=event_logged,
    )


def _finalize_adaptive_policy_attempt(
    *,
    note: str,
    success: bool,
    attempt_wall_sec: float,
    online_finalize_kwargs: dict[str, Any],
    budget_finalize_kwargs: dict[str, Any],
) -> PendingAdaptiveOnlineCredit | None:
    """Finalize both online and budget policy bookkeeping after an attempt."""
    _sync_finalize_progress_kwargs_from_attempt_metadata(
        online_finalize_kwargs=online_finalize_kwargs,
        budget_finalize_kwargs=budget_finalize_kwargs,
    )
    pending_credit = _finalize_adaptive_online_attempt(
        note=note,
        success=success,
        attempt_wall_sec=attempt_wall_sec,
        **online_finalize_kwargs,
    )
    _finalize_adaptive_budget_policy_attempt(
        note=note,
        success=success,
        attempt_wall_sec=attempt_wall_sec,
        **budget_finalize_kwargs,
    )
    return pending_credit


def _sync_finalize_progress_kwargs_from_attempt_metadata(
    *,
    online_finalize_kwargs: dict[str, Any],
    budget_finalize_kwargs: dict[str, Any],
) -> None:
    streak_default = _nonnegative_int(
        online_finalize_kwargs.get("adaptive_no_progress_streak_current"),
        default=0,
    )
    progress_default = bool(online_finalize_kwargs.get("adaptive_progress_made"))
    progress_score_default = max(
        0.0,
        float(online_finalize_kwargs.get("adaptive_progress_score") or 0.0),
    )
    attempt_metadata = online_finalize_kwargs.get("attempt_metadata")
    if isinstance(attempt_metadata, dict):
        progress_default = bool(attempt_metadata.get("adaptive_progress_made", progress_default))
        try:
            progress_score_default = max(
                0.0,
                float(attempt_metadata.get("adaptive_progress_score", progress_score_default)),
            )
        except (TypeError, ValueError):
            pass
        streak_default = _nonnegative_int(
            attempt_metadata.get("adaptive_no_progress_streak"),
            default=streak_default,
        )
    online_finalize_kwargs["adaptive_progress_made"] = progress_default
    online_finalize_kwargs["adaptive_progress_score"] = progress_score_default
    online_finalize_kwargs["adaptive_no_progress_streak_current"] = streak_default
    budget_finalize_kwargs["adaptive_progress_made"] = progress_default
    budget_finalize_kwargs["adaptive_progress_score"] = progress_score_default
    budget_finalize_kwargs["adaptive_no_progress_streak_current"] = streak_default


def _extract_exception_info(
    *,
    snapshot: dict[str, Any] | None,
    stdout: str,
    stderr: str,
) -> tuple[str, str, int, bool]:
    """Extract (exception_type, message, frame_count, crash_in_user_code)."""
    if snapshot and isinstance(snapshot.get("exception"), dict):
        exc_info = snapshot["exception"]
        exc_type = str(exc_info.get("type", "Unknown"))
        exc_msg = str(exc_info.get("message", ""))
        frames = snapshot.get("frames")
        frame_count = len(frames) if isinstance(frames, list) else 0
        crash_in_user_code = False
        if isinstance(frames, list) and len(frames) > 0:
            crash_frame = frames[0]
            if isinstance(crash_frame, dict):
                crash_in_user_code = crash_frame.get("file_rel") is not None
        # Strip module prefix (e.g., "builtins.ValueError" -> "ValueError")
        if "." in exc_type:
            exc_type = exc_type.rsplit(".", 1)[-1]
        return exc_type, exc_msg, frame_count, crash_in_user_code

    # Fallback: parse from stdout/stderr
    for text in (stdout, stderr):
        if not text:
            continue
        m = _STDOUT_PYTEST_ERROR_RE.search(text)
        if m:
            exc_type = m.group(1).rsplit(".", 1)[-1]
            return exc_type, m.group(2).strip(), 0, False
        m = _STDOUT_TRACEBACK_ERROR_RE.search(text)
        if m:
            exc_type = m.group(1).rsplit(".", 1)[-1]
            return exc_type, m.group(2).strip(), 0, False

    return "Unknown", "", 0, False


def _traceback_informativeness_score(
    *,
    exception_type: str,
    exception_message: str,
    frame_count: int,
    crash_in_user_code: bool,
) -> PreGateResult:
    """Score traceback informativeness and return a PreGateResult with all features."""
    # Signal 1: Exception type specificity (weight 0.30)
    f_type_specificity = _EXCEPTION_TYPE_SPECIFICITY.get(
        exception_type, _EXCEPTION_TYPE_SPECIFICITY_DEFAULT
    )

    # Signal 2: Message contains diagnostic values (weight 0.25)
    f_message_diagnostic = 0.0
    if exception_message:
        for pattern in _DIAGNOSTIC_MESSAGE_PATTERNS:
            if pattern.search(exception_message):
                f_message_diagnostic = 1.0
                break
        if f_message_diagnostic == 0.0:
            f_message_diagnostic = 0.3  # Non-empty but generic
    # else: empty message → 0.0

    # Signal 3: Crash frame in user code (weight 0.15)
    f_crash_in_user_code = 1.0 if crash_in_user_code else 0.0

    # Signal 4: Assertion message quality (weight 0.15)
    if exception_type == "AssertionError":
        if exception_message and _ASSERTION_VALUE_PATTERN.search(exception_message):
            f_assertion_quality = 1.0
        elif exception_message:
            f_assertion_quality = 0.5
        else:
            f_assertion_quality = 0.0
    else:
        f_assertion_quality = 0.5  # Neutral for non-assertions

    # Signal 5: Frame count / depth (weight 0.15)
    # frame_count=0 means no structured data (stdout fallback) — treat as neutral.
    if frame_count == 0:
        f_frame_depth = 0.5
    elif frame_count <= 3:
        f_frame_depth = 1.0
    elif frame_count <= 6:
        f_frame_depth = 0.7
    elif frame_count <= 10:
        f_frame_depth = 0.4
    else:
        f_frame_depth = 0.2

    score = (
        0.30 * f_type_specificity
        + 0.25 * f_message_diagnostic
        + 0.15 * f_crash_in_user_code
        + 0.15 * f_assertion_quality
        + 0.15 * f_frame_depth
    )
    score = min(1.0, max(0.0, score))

    tier = (
        "A"
        if f_type_specificity >= 0.8
        else ("B" if f_type_specificity >= 0.5 else ("C" if f_type_specificity >= 0.3 else "D"))
    )
    reason = f"{exception_type}_tier{tier}"

    return PreGateResult(
        should_skip=False,  # Caller decides based on threshold
        score=score,
        exception_type=exception_type,
        reason=reason,
        f_type_specificity=f_type_specificity,
        f_message_diagnostic=f_message_diagnostic,
        f_crash_in_user_code=f_crash_in_user_code,
        f_assertion_quality=f_assertion_quality,
        f_frame_depth=f_frame_depth,
    )


def _traceback_informativeness_pre_gate(
    *,
    snapshot: dict[str, Any] | None,
    stdout: str,
    stderr: str,
    threshold: float,
) -> PreGateResult:
    """Return a PreGateResult with should_skip set based on threshold."""
    exc_type, exc_msg, frame_count, crash_in_user = _extract_exception_info(
        snapshot=snapshot,
        stdout=stdout,
        stderr=stderr,
    )
    result = _traceback_informativeness_score(
        exception_type=exc_type,
        exception_message=exc_msg,
        frame_count=frame_count,
        crash_in_user_code=crash_in_user,
    )
    return result._replace(should_skip=result.score >= threshold)


def _adaptive_prompt_budget_for_attempt(
    *,
    base_budget: dict[str, int],
    helper_materialized_context: bool,
    profile: str,
) -> dict[str, int]:
    out = dict(base_budget)
    if not helper_materialized_context or profile != "compact":
        return out

    def _cap(value: int, limit: int) -> int:
        if value <= 0:
            return value
        return min(value, limit)

    out["total_chars"] = _cap(int(out.get("total_chars", 0)), 120_000)
    out["retry_delta_chars"] = _cap(int(out.get("retry_delta_chars", 0)), 12_000)
    out["stdout_chars"] = _cap(int(out.get("stdout_chars", 0)), 16_000)
    out["snapshot_chars"] = _cap(int(out.get("snapshot_chars", 0)), 20_000)
    out["evidence_chars"] = _cap(int(out.get("evidence_chars", 0)), 32_000)
    out["retry_history_chars"] = _cap(int(out.get("retry_history_chars", 0)), 8_000)
    return out


def _openai_preflight_total_char_cap(
    *,
    context_window_tokens: int,
    max_tokens: int,
    reserve_tokens: int = OPENAI_CONTEXT_WINDOW_RESERVE_TOKENS,
) -> int:
    normalized_window = max(0, int(context_window_tokens))
    if normalized_window <= 0:
        return 0
    usable_tokens = normalized_window - max(0, int(max_tokens)) - max(0, int(reserve_tokens))
    # Guard enabled: never return 0, otherwise budget logic treats it as "uncapped".
    if usable_tokens <= 0:
        return 1
    return max(1, usable_tokens * OPENAI_CONTEXT_WINDOW_CHARS_PER_TOKEN)


def _is_snapshot_condition(condition: Condition) -> bool:
    return condition in {
        Condition.WITH_SNAPSHOT,
        Condition.WITH_SNAPSHOT_STRUCTURED,
        Condition.ADAPTIVE_GATED,
        Condition.ADAPTIVE_LLM_DISCRETION,
    }


def run_one_condition(
    *,
    case: CaseSpec,
    condition: Condition,
    workdir: Path,
    context_dir_base: Path,
    patcher,
    k: int,
    output_format: str,
    run_id: str,
    include_files_context: bool,
    max_context_chars: int = 200_000,
    case_fail_fast_on_request_budget_exceeded: bool = False,
    force_patch_attempt: bool,
    context_protocol: str = "monolithic",
    context_stage_a_max_requests: int = 6,
    context_stage_a_max_chars: int = 120_000,
    retry_prompt_mode: str = "auto",
    retry_context_mode: str = "history",
    retry_history_max_chars: int = 48_000,
    pytest_output_tail_lines: int = DEFAULT_PYTEST_OUTPUT_TAIL_LINES,
    snapshot_artifact_max_string_chars: int = DEFAULT_SNAPSHOT_ARTIFACT_MAX_STRING_CHARS,
    prompt_budget_total_chars: int = 180_000,
    prompt_budget_retry_delta_chars: int = 16_000,
    prompt_budget_stdout_chars: int = 32_000,
    prompt_budget_snapshot_chars: int = 32_000,
    prompt_budget_evidence_chars: int = 64_000,
    prompt_budget_retry_history_chars: int = 12_000,
    context_compaction_mode: str = "auto_overflow",
    context_compaction_scope: str = "snapshot,pytest,files",
    context_compaction_max_input_chars: int = 120_000,
    context_compaction_target_chars: int = 18_000,
    context_compaction_max_calls: int = 1,
    openai_context_window_tokens: int = 0,
    openai_context_window_source: str = "",
    openai_context_window_probe_provider: str = "",
    openai_context_window_probe_error: str = "",
    openai_response_mode_autoswitch: str = "off",
    adaptive_gate_policy: str = "balanced",
    adaptive_max_helper_calls: int = 1,
    adaptive_budget_topup_max_calls: int = DEFAULT_ADAPTIVE_BUDGET_TOPUP_MAX_CALLS,
    adaptive_stagnation_window: int = 1,
    adaptive_saturation_threshold: float = 0.75,
    adaptive_no_progress_token_cap: int = DEFAULT_ADAPTIVE_NO_PROGRESS_TOKEN_CAP,
    adaptive_helper_budget_profile: str = "default",
    adaptive_openai_helper_response_mode: str = "inherit",
    adaptive_informativeness_pregate: bool = False,
    adaptive_informativeness_pregate_threshold: float = 0.7,
    syntax_preflight_enabled: bool = True,
    syntax_preflight_max_cycles: int = DEFAULT_SYNTAX_PREFLIGHT_MAX_CYCLES,
    patch_sanitize_enabled: bool = True,
    patch_verifier_infer_test_source_scope: bool = True,
    adaptive_online_policy_controller: AdaptiveOnlinePolicyController | None = None,
    adaptive_online_policy_mode: str = "off",
    adaptive_online_policy_algo: str = "linucb",
    adaptive_online_feature_scaling_profile: str = DEFAULT_POLICY_FEATURE_SCALING_PROFILE,
    adaptive_budget_policy_controller: AdaptiveBudgetPolicyController | None = None,
    adaptive_budget_policy_mode: str = "on",
    adaptive_budget_policy_algo: str = "linucb",
    adaptive_budget_feature_scaling_profile: str = DEFAULT_POLICY_FEATURE_SCALING_PROFILE,
    adaptive_budget_policy_tiers: str = "1.0,1.5,2.0",
    adaptive_budget_policy_max_ups_per_case: int = 1,
    adaptive_budget_reward_rho_budget_exceeded: float = (
        ADAPTIVE_BUDGET_REWARD_RHO_BUDGET_EXCEEDED_DEFAULT
    ),
    adaptive_reward_lambda_tokens: float = ADAPTIVE_REWARD_LAMBDA_TOKENS_DEFAULT,
    adaptive_reward_mu_latency: float = ADAPTIVE_REWARD_MU_LATENCY_DEFAULT,
    adaptive_reward_token_scale: float = ADAPTIVE_REWARD_TOKEN_SCALE_DEFAULT,
    adaptive_reward_latency_scale: float = ADAPTIVE_REWARD_LATENCY_SCALE_DEFAULT,
    adaptive_reward_eta_progress: float = ADAPTIVE_REWARD_ETA_PROGRESS_DEFAULT,
    adaptive_reward_kappa_stagnation: float = ADAPTIVE_REWARD_KAPPA_STAGNATION_DEFAULT,
    run_metadata: dict[str, Any] | None = None,
    record_prompt: str = "hash",
    record_evidence: str = "hash",
    deterministic: bool = False,
    seed: int | None = None,
    record_sink: Callable[[dict[str, Any]], None] | None = None,
    progress_update: Callable[[str, dict[str, Any] | None], None] | None = None,
) -> list[dict[str, Any]]:
    """Execute up to *k* patch-verify attempts for a single (case, condition).

    Manages the full attempt loop: pytest baseline, context assembly,
    prompt rendering, patch proposal, diff application, re-verification,
    and adaptive policy updates.  Returns a list of JSONL-ready records.
    """
    records: list[dict[str, Any]] = []

    def _emit_progress(event: str, payload: dict[str, Any] | None = None) -> None:
        if progress_update is None:
            return
        try:
            progress_update(event, payload)
        except Exception:
            # Progress callbacks are best-effort and must not break eval execution.
            return

    def _append_record(record: dict[str, Any]) -> None:
        records.append(record)
        _emit_progress(
            "record_emitted",
            {
                "attempt": int(record.get("attempt") or 0),
                "note": str(record.get("note") or ""),
                "success": bool(record.get("success")),
            },
        )
        if record_sink is not None:
            record_sink(record)

    normalized_context_protocol = (
        context_protocol if context_protocol in CONTEXT_PROTOCOLS else "monolithic"
    )
    is_structured_condition = condition == Condition.WITH_SNAPSHOT_STRUCTURED
    is_adaptive_gated = condition == Condition.ADAPTIVE_GATED
    is_adaptive_llm_discretion = condition == Condition.ADAPTIVE_LLM_DISCRETION
    is_adaptive_condition = is_adaptive_gated or is_adaptive_llm_discretion
    pending_online_credits: list[PendingAdaptiveOnlineCredit] = []

    def _queue_pending_online_credit(
        *,
        pending_credit: PendingAdaptiveOnlineCredit | None,
        record: dict[str, Any],
    ) -> None:
        if pending_credit is None:
            return
        pending_credit.record_ref = record
        pending_online_credits.append(pending_credit)

    def _resolve_expired_pending_online_credits(*, current_attempt: int) -> None:
        if not pending_online_credits:
            return
        remaining: list[PendingAdaptiveOnlineCredit] = []
        for pending in pending_online_credits:
            if (
                int(current_attempt) - int(pending.attempt)
                > ADAPTIVE_DELAYED_RESCUE_HORIZON_ATTEMPTS
            ):
                _resolve_pending_adaptive_online_credit(
                    pending=pending,
                    adaptive_online_policy_controller=adaptive_online_policy_controller,
                    delayed_rescue_bonus=0.0,
                    resolved_attempt=0,
                    resolution_reason="local_only_horizon_expired",
                )
            else:
                remaining.append(pending)
        pending_online_credits[:] = remaining

    def _resolve_pending_online_credits_for_success(*, success_attempt: int) -> None:
        if not pending_online_credits:
            return
        credited_index: int | None = None
        for idx in range(len(pending_online_credits) - 1, -1, -1):
            pending = pending_online_credits[idx]
            horizon_attempts = int(success_attempt) - int(pending.attempt)
            if 0 < horizon_attempts <= ADAPTIVE_DELAYED_RESCUE_HORIZON_ATTEMPTS:
                credited_index = idx
                break
        for idx, pending in enumerate(pending_online_credits):
            if idx == credited_index:
                delayed_rescue_bonus = _adaptive_online_delayed_rescue_bonus(
                    origin_attempt=pending.attempt,
                    resolved_attempt=success_attempt,
                    failure_signature_repeat=pending.failure_signature_repeat,
                    case_difficulty=pending.case_difficulty,
                    case_snapshot_dependency=pending.case_snapshot_dependency,
                )
                _resolve_pending_adaptive_online_credit(
                    pending=pending,
                    adaptive_online_policy_controller=adaptive_online_policy_controller,
                    delayed_rescue_bonus=delayed_rescue_bonus,
                    resolved_attempt=success_attempt,
                    resolution_reason="future_success_attempt",
                )
            else:
                _resolve_pending_adaptive_online_credit(
                    pending=pending,
                    adaptive_online_policy_controller=adaptive_online_policy_controller,
                    delayed_rescue_bonus=0.0,
                    resolved_attempt=0,
                    resolution_reason=(
                        "local_only_superseded_by_newer_helper"
                        if credited_index is not None
                        else "local_only_case_ended"
                    ),
                )
        pending_online_credits.clear()

    def _resolve_remaining_pending_online_credits() -> None:
        if not pending_online_credits:
            return
        for pending in pending_online_credits:
            _resolve_pending_adaptive_online_credit(
                pending=pending,
                adaptive_online_policy_controller=adaptive_online_policy_controller,
                delayed_rescue_bonus=0.0,
                resolved_attempt=0,
                resolution_reason="local_only_case_ended",
            )
        pending_online_credits.clear()

    def _refresh_adaptive_progress_for_note(
        *,
        note: str,
        success: bool,
        signature_repeated: bool,
        after_run: RunResult | None = None,
        recomputed_after_patch: bool = False,
    ) -> tuple[bool, str]:
        nonlocal adaptive_no_progress_streak
        nonlocal adaptive_no_progress_streak_current
        nonlocal adaptive_progress_made
        nonlocal adaptive_progress_reason
        nonlocal adaptive_progress_score
        nonlocal adaptive_progress_stop_candidate
        nonlocal adaptive_progress_stop_reason

        if not is_adaptive_condition:
            return adaptive_progress_stop_candidate, adaptive_progress_stop_reason

        progress_state, adaptive_no_progress_streak_current = (
            _recompute_adaptive_progress_for_attempt_outcome(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                note=note,
                success=success,
                signature_repeated=signature_repeated,
                new_evidence_count=len(new_evidence_ids),
                before_run=current,
                after_run=after_run,
                previous_failure_context=previous_failure_context,
                current_no_progress_streak=adaptive_no_progress_streak_current,
                recomputed_after_patch=recomputed_after_patch,
            )
        )
        adaptive_progress_made = bool(progress_state.made)
        adaptive_progress_reason = str(progress_state.reason)
        adaptive_progress_score = float(progress_state.score)
        adaptive_no_progress_streak = (
            0 if adaptive_progress_made else adaptive_no_progress_streak_current
        )
        (
            adaptive_progress_stop_candidate,
            adaptive_progress_stop_reason,
        ) = _adaptive_should_stop_for_no_progress(
            no_progress_streak=adaptive_no_progress_streak_current,
            stop_window=adaptive_no_progress_stop_window,
            helper_budget_exhausted=helper_budget_exhausted,
            helper_saturation_guarded=helper_saturation_guarded,
            helper_decision_reason=helper_decision_reason,
        )
        return adaptive_progress_stop_candidate, adaptive_progress_stop_reason

    default_context_protocol = (
        "staged"
        if is_structured_condition
        else ("monolithic" if is_adaptive_condition else normalized_context_protocol)
    )
    normalized_retry_prompt_mode = (
        retry_prompt_mode if retry_prompt_mode in RETRY_PROMPT_MODES else "auto"
    )
    if normalized_retry_prompt_mode == "auto":
        base_retry_prompt_mode = "delta" if is_structured_condition else "full"
    else:
        base_retry_prompt_mode = normalized_retry_prompt_mode
    normalized_retry_context_mode = (
        retry_context_mode if retry_context_mode in RETRY_CONTEXT_MODES else "history"
    )
    effective_retry_context_mode = normalized_retry_context_mode
    base_retry_history_max_chars = max(0, int(retry_history_max_chars))
    effective_retry_history_max_chars = base_retry_history_max_chars
    normalized_pytest_output_tail_lines = max(0, int(pytest_output_tail_lines))
    normalized_snapshot_artifact_max_string_chars = max(0, int(snapshot_artifact_max_string_chars))
    base_stage_a_max_requests = max(1, int(context_stage_a_max_requests))
    base_stage_a_max_chars = max(1, int(context_stage_a_max_chars))
    stage_a_max_requests = base_stage_a_max_requests
    stage_a_max_chars = base_stage_a_max_chars
    prompt_budget = {
        "total_chars": max(0, int(prompt_budget_total_chars)),
        "retry_delta_chars": max(0, int(prompt_budget_retry_delta_chars)),
        "stdout_chars": max(0, int(prompt_budget_stdout_chars)),
        "snapshot_chars": max(0, int(prompt_budget_snapshot_chars)),
        "evidence_chars": max(0, int(prompt_budget_evidence_chars)),
        "retry_history_chars": max(0, int(prompt_budget_retry_history_chars)),
    }
    normalized_context_compaction_mode = (
        context_compaction_mode
        if context_compaction_mode in CONTEXT_COMPACTION_MODES
        else "auto_overflow"
    )
    normalized_context_compaction_scope = {
        token.strip().lower()
        for token in str(context_compaction_scope or "").split(",")
        if token.strip() and token.strip().lower() in CONTEXT_COMPACTION_SCOPE_TOKENS
    }
    if not normalized_context_compaction_scope:
        normalized_context_compaction_scope = {"snapshot", "pytest", "files"}
    normalized_context_compaction_max_input_chars = max(0, int(context_compaction_max_input_chars))
    normalized_context_compaction_target_chars = max(1, int(context_compaction_target_chars))
    base_context_compaction_max_calls = max(0, int(context_compaction_max_calls))
    normalized_openai_context_window_tokens = max(0, int(openai_context_window_tokens))
    normalized_openai_context_window_source = str(openai_context_window_source or "")
    normalized_openai_context_window_probe_provider = str(
        openai_context_window_probe_provider or ""
    )
    normalized_openai_context_window_probe_error = str(openai_context_window_probe_error or "")
    normalized_openai_response_mode_autoswitch = (
        openai_response_mode_autoswitch
        if openai_response_mode_autoswitch in OPENAI_RESPONSE_MODE_AUTOSWITCH_MODES
        else "off"
    )
    normalized_adaptive_gate_policy = (
        adaptive_gate_policy if adaptive_gate_policy in ADAPTIVE_GATE_POLICIES else "balanced"
    )
    normalized_adaptive_max_helper_calls = max(0, int(adaptive_max_helper_calls))
    normalized_adaptive_budget_topup_max_calls = max(0, int(adaptive_budget_topup_max_calls))
    normalized_adaptive_stagnation_window = max(1, int(adaptive_stagnation_window))
    adaptive_no_progress_stop_window = max(2, normalized_adaptive_stagnation_window + 1)
    normalized_adaptive_saturation_threshold = min(
        1.0, max(0.0, float(adaptive_saturation_threshold))
    )
    normalized_adaptive_no_progress_token_cap = max(0, int(adaptive_no_progress_token_cap))
    normalized_adaptive_helper_budget_profile = (
        adaptive_helper_budget_profile
        if adaptive_helper_budget_profile in ADAPTIVE_HELPER_BUDGET_PROFILES
        else "default"
    )
    normalized_adaptive_openai_helper_response_mode = (
        adaptive_openai_helper_response_mode
        if adaptive_openai_helper_response_mode in ADAPTIVE_OPENAI_HELPER_RESPONSE_MODES
        else "inherit"
    )
    adaptive_mode_label = (
        "gated"
        if is_adaptive_gated
        else ("llm_discretion" if is_adaptive_llm_discretion else "none")
    )
    adaptive_policy_label = normalized_adaptive_gate_policy if is_adaptive_condition else "disabled"
    normalized_adaptive_online_policy_mode = (
        adaptive_online_policy_mode
        if adaptive_online_policy_mode in ADAPTIVE_ONLINE_POLICY_MODES
        else "off"
    )
    normalized_adaptive_online_policy_algo = (
        adaptive_online_policy_algo
        if adaptive_online_policy_algo in ADAPTIVE_ONLINE_POLICY_ALGOS
        else "linucb"
    )
    normalized_adaptive_online_feature_scaling_profile = normalize_feature_scaling_profile(
        adaptive_online_feature_scaling_profile
    )
    normalized_adaptive_budget_policy_mode = (
        adaptive_budget_policy_mode
        if adaptive_budget_policy_mode in ADAPTIVE_BUDGET_POLICY_MODES
        else "on"
    )
    normalized_adaptive_budget_policy_algo = (
        adaptive_budget_policy_algo
        if adaptive_budget_policy_algo in ADAPTIVE_BUDGET_POLICY_ALGOS
        else "linucb"
    )
    normalized_adaptive_budget_feature_scaling_profile = normalize_feature_scaling_profile(
        adaptive_budget_feature_scaling_profile
    )
    try:
        normalized_adaptive_budget_policy_tiers = _parse_adaptive_budget_policy_tiers(
            adaptive_budget_policy_tiers
        )
    except (TypeError, ValueError):
        normalized_adaptive_budget_policy_tiers = [1.0, 1.5, 2.0]
    normalized_adaptive_budget_policy_max_ups_per_case = max(
        0, int(adaptive_budget_policy_max_ups_per_case)
    )
    normalized_adaptive_budget_reward_rho_budget_exceeded = max(
        0.0, float(adaptive_budget_reward_rho_budget_exceeded)
    )
    normalized_syntax_preflight_enabled = bool(syntax_preflight_enabled)
    normalized_syntax_preflight_max_cycles = max(1, int(syntax_preflight_max_cycles))
    normalized_patch_sanitize_enabled = bool(patch_sanitize_enabled)
    normalized_patch_verifier_infer_test_source_scope = bool(patch_verifier_infer_test_source_scope)
    normalized_case_fail_fast_on_request_budget_exceeded = bool(
        case_fail_fast_on_request_budget_exceeded
    )
    if adaptive_online_policy_controller is not None:
        normalized_adaptive_online_policy_mode = adaptive_online_policy_controller.mode
        normalized_adaptive_online_policy_algo = adaptive_online_policy_controller.algorithm
        normalized_adaptive_online_feature_scaling_profile = normalize_feature_scaling_profile(
            getattr(adaptive_online_policy_controller.config, "feature_scaling_profile", "v1")
        )
    if adaptive_budget_policy_controller is not None:
        normalized_adaptive_budget_policy_mode = adaptive_budget_policy_controller.mode
        normalized_adaptive_budget_policy_algo = adaptive_budget_policy_controller.algorithm
        normalized_adaptive_budget_feature_scaling_profile = normalize_feature_scaling_profile(
            getattr(adaptive_budget_policy_controller.config, "feature_scaling_profile", "v1")
        )
    normalized_adaptive_reward_lambda_tokens = max(0.0, float(adaptive_reward_lambda_tokens))
    normalized_adaptive_reward_mu_latency = max(0.0, float(adaptive_reward_mu_latency))
    normalized_adaptive_reward_token_scale = max(1.0, float(adaptive_reward_token_scale))
    normalized_adaptive_reward_latency_scale = max(1.0, float(adaptive_reward_latency_scale))
    normalized_adaptive_reward_eta_progress = max(0.0, float(adaptive_reward_eta_progress))
    normalized_adaptive_reward_kappa_stagnation = max(0.0, float(adaptive_reward_kappa_stagnation))
    effective_run_metadata = (
        dict(run_metadata)
        if isinstance(run_metadata, dict)
        else _default_run_metadata(
            run_id=run_id,
            patcher_name=str(getattr(patcher, "name", "unknown")),
        )
    )
    patcher_name = str(getattr(patcher, "name", "unknown"))
    is_openai_patcher = patcher_name == "openai"
    request_context_fn = getattr(patcher, "request_context", None)
    supports_context_request = callable(request_context_fn)
    context_compaction_fn = getattr(patcher, "compact_context", None)
    supports_context_compaction = callable(context_compaction_fn)
    raw_budget_policy_base_propose_budget_sec = getattr(patcher, "max_propose_sec", 0.0)
    try:
        budget_policy_base_propose_budget_sec = max(
            0.0, float(raw_budget_policy_base_propose_budget_sec)
        )
    except (TypeError, ValueError):
        budget_policy_base_propose_budget_sec = 0.0
    raw_base_openai_max_tokens = getattr(patcher, "max_tokens", 0)
    try:
        base_openai_max_tokens = max(0, int(raw_base_openai_max_tokens))
    except (TypeError, ValueError):
        base_openai_max_tokens = 0
    base_openai_response_mode = str(getattr(patcher, "response_mode", "") or "")
    context_compaction_calls_used = 0
    last_context_compaction_requested = False
    last_context_compaction_invoked = False
    last_context_compaction_success = False
    last_context_compaction_ratio = 0.0
    last_context_compaction_trigger = ""

    _strip_eval_only_case_artifacts(workdir)
    llmdebug_dir = workdir / ".llmdebug"
    previous_failure_context: dict[str, Any] | None = None
    seen_evidence_ids: set[str] = set()
    retry_history_turns: list[str] = []
    runner_force_patch_attempt_enabled = bool(force_patch_attempt)
    helper_calls_used = 0
    helper_calls_without_context = 0
    adaptive_budget_topup_calls_used = 0
    last_helper_need_more_context: bool | None = None
    last_helper_decision_reason = ""
    last_helper_action_executed = ACTION_SKIP_HELPER
    stagnation_streak = 0
    adaptive_no_progress_streak = 0
    adaptive_no_progress_token_sum = 0
    adaptive_budget_policy_base_tier_index = next(
        (
            idx
            for idx, value in enumerate(normalized_adaptive_budget_policy_tiers)
            if abs(float(value) - 1.0) <= 1e-9
        ),
        0,
    )
    adaptive_budget_policy_current_tier_index = adaptive_budget_policy_base_tier_index
    adaptive_budget_policy_ups_used = 0
    base_budget_control_profile = _build_adaptive_budget_control_profile(
        tier_index=adaptive_budget_policy_base_tier_index,
        base_tier_index=adaptive_budget_policy_base_tier_index,
        tier_multipliers=normalized_adaptive_budget_policy_tiers,
        base_propose_budget_sec=budget_policy_base_propose_budget_sec,
        base_openai_max_tokens=base_openai_max_tokens,
        base_helper_max_calls=normalized_adaptive_max_helper_calls,
        base_stage_a_max_requests=base_stage_a_max_requests,
        base_stage_a_max_chars=base_stage_a_max_chars,
        base_retry_history_max_chars=base_retry_history_max_chars,
        base_context_compaction_max_calls=base_context_compaction_max_calls,
        context_compaction_enabled=normalized_context_compaction_mode != "off",
        base_response_mode=base_openai_response_mode,
        is_openai_patcher=is_openai_patcher,
    )
    file_index_cache: str | None = None
    file_index_lines_cache: tuple[str, ...] | None = None
    monolithic_context_cache: str | None = None

    def _file_index_lines_for_attempt() -> tuple[str, ...]:
        nonlocal file_index_lines_cache, file_index_cache
        if file_index_lines_cache is None:
            file_index_lines_cache = tuple(_collect_file_index_lines(workdir))
            file_index_cache = "\n".join(file_index_lines_cache)
        return file_index_lines_cache

    def _file_index_text_for_attempt() -> str:
        nonlocal file_index_cache
        if file_index_cache is None:
            file_index_cache = "\n".join(_file_index_lines_for_attempt())
        return file_index_cache

    def _monolithic_context_for_attempt() -> str:
        nonlocal monolithic_context_cache
        if monolithic_context_cache is None:
            monolithic_context_cache = collect_text_context(
                workdir,
                max_total_chars=max_context_chars,
                file_index_lines=_file_index_lines_for_attempt(),
            )
        return monolithic_context_cache

    def _invalidate_file_catalog_cache() -> None:
        nonlocal file_index_cache, file_index_lines_cache, monolithic_context_cache
        file_index_cache = None
        file_index_lines_cache = None
        monolithic_context_cache = None

    # Initial run (attempt 0, no patch yet).
    shutil.rmtree(llmdebug_dir, ignore_errors=True)
    _emit_progress(
        "pytest_before_initial",
        {
            "attempt": 0,
            "max_attempts": int(k),
        },
    )
    current = run_pytest(workdir, case.python_args, condition, case.timeout_sec, output_format)
    cumulative_runtime_sec = current.duration_sec
    if current.returncode == 0:
        attempt_dir = context_dir_base / "attempt_00"
        attempt_dir.mkdir(parents=True, exist_ok=True)
        _write_run_outputs(
            attempt_dir,
            current,
            prefix="pytest_before",
            tail_lines=normalized_pytest_output_tail_lines,
        )
        record = _build_base_record(
            case=case,
            condition=condition,
            run_id=run_id,
            attempt=0,
            patcher_name=patcher_name,
            run_metadata=effective_run_metadata,
            attempt_metadata={
                **_empty_attempt_metadata(attempt=0),
                "max_attempts": int(k),
                "attempt_ordinal_label": f"0/{int(k)}",
                "context_protocol": default_context_protocol,
                "retry_prompt_mode": base_retry_prompt_mode,
                "retry_context_mode": effective_retry_context_mode,
                "retry_history_budget_chars": int(prompt_budget.get("retry_history_chars", 0)),
                "openai_context_window_source": normalized_openai_context_window_source,
                "openai_context_window_probe_provider": normalized_openai_context_window_probe_provider,
                "openai_context_window_probe_error": normalized_openai_context_window_probe_error,
                "openai_response_mode_autoswitch_policy": normalized_openai_response_mode_autoswitch,
                "adaptive_mode": adaptive_mode_label,
                "adaptive_policy": adaptive_policy_label,
                "adaptive_helper_budget_max_calls": base_budget_control_profile.helper_max_calls,
                "adaptive_helper_budget_unlimited": (
                    base_budget_control_profile.helper_max_calls <= 0
                ),
                "adaptive_helper_budget_profile": normalized_adaptive_helper_budget_profile,
                "adaptive_budget_topup_granted": False,
                "adaptive_budget_topup_reason": "",
                "adaptive_online_policy_mode": normalized_adaptive_online_policy_mode,
                "adaptive_online_policy_algo": normalized_adaptive_online_policy_algo,
                "adaptive_online_reward_lambda_tokens": normalized_adaptive_reward_lambda_tokens,
                "adaptive_online_reward_mu_latency": normalized_adaptive_reward_mu_latency,
                "adaptive_online_reward_token_scale": normalized_adaptive_reward_token_scale,
                "adaptive_online_reward_latency_scale": normalized_adaptive_reward_latency_scale,
                "adaptive_budget_policy_mode": normalized_adaptive_budget_policy_mode,
                "adaptive_budget_policy_algo": normalized_adaptive_budget_policy_algo,
                "adaptive_budget_tiers": [
                    float(value) for value in normalized_adaptive_budget_policy_tiers
                ],
                "adaptive_budget_tier_index_before": adaptive_budget_policy_base_tier_index,
                "adaptive_budget_tier_index_after": adaptive_budget_policy_base_tier_index,
                "adaptive_budget_selected_multiplier": float(
                    normalized_adaptive_budget_policy_tiers[adaptive_budget_policy_base_tier_index]
                ),
                "adaptive_budget_applied_multiplier": float(
                    normalized_adaptive_budget_policy_tiers[adaptive_budget_policy_base_tier_index]
                ),
                "adaptive_budget_default_multiplier": float(
                    normalized_adaptive_budget_policy_tiers[adaptive_budget_policy_base_tier_index]
                ),
                "adaptive_budget_max_ups_per_case": normalized_adaptive_budget_policy_max_ups_per_case,
                "adaptive_budget_ups_used": 0,
                "adaptive_budget_reward_rho_budget_exceeded": (
                    normalized_adaptive_budget_reward_rho_budget_exceeded
                ),
                "syntax_preflight_enabled": normalized_syntax_preflight_enabled,
                "syntax_preflight_cycles_used": 0,
                "syntax_preflight_failures": 0,
                "syntax_preflight_last_error_type": "",
                "syntax_preflight_last_error_msg": "",
            },
            model_metadata=_build_model_metadata(patcher=patcher, patch_meta=None),
            context_protocol=default_context_protocol,
        )
        record.update(
            {
                "success": True,
                "before_returncode": current.returncode,
                "before_duration_sec": current.duration_sec,
                "cumulative_runtime_sec": cumulative_runtime_sec,
                "attempt_wall_clock_sec": float(current.duration_sec),
                "note": "no_patch_needed",
            }
        )
        record.update(_timeout_fields(current))
        _append_record(record)
        return records

    # ── Retry loop: up to k attempts per (case, condition) unit ──────────
    #
    # Each iteration generates a patch and re-runs tests.  Two flags at the
    # bottom of the loop control early exit:
    #   attempt_terminated_early  - this attempt ended without reaching test re-run
    #   should_break_after_attempt - also abandon remaining attempts (stop case)
    #
    # When attempt_terminated_early=True and should_break_after_attempt=False,
    # the loop `continue`s to the next attempt.  When both are True, it `break`s.
    #
    # Six triggers can stop the loop before all k attempts complete:
    #   1. request_budget_exceeded + case_fail_fast_on_request_budget_exceeded
    #      (non-adaptive; sets should_break_after_attempt via fail_fast_on_budget_exceeded)
    #   2. patch_verifier_rejected  (adaptive only; should_break_after_attempt = adaptive_stop_triggered)
    #   3. syntax_preflight_failed - cycle exit  (adaptive only; same mechanism)
    #   4. patch_apply_failed  (adaptive only; same mechanism)
    #   5. syntax_preflight_failed - post-apply  (adaptive only; same mechanism)
    #   6. tests_still_failing + no-progress token cap hit  (adaptive only;
    #      breaks directly via end-of-loop `if adaptive_stop_triggered: break`,
    #      NOT through should_break_after_attempt)
    #
    # Triggers 2-5 require adaptive_stop_triggered=True (from
    # _mark_adaptive_progress_stop), which only fires when
    # is_adaptive_condition=True and no progress is detected.
    # Trigger 6 also requires adaptive_stop_triggered but uses a separate
    # break path at the bottom of the loop body.
    # ───────────────────────────────────────────────────────────────────────
    for attempt in range(1, k + 1):
        _resolve_expired_pending_online_credits(current_attempt=attempt)
        attempt_start = time.perf_counter()
        _emit_progress(
            "attempt_start",
            {
                "attempt": int(attempt),
                "max_attempts": int(k),
            },
        )
        attempt_dir = context_dir_base / f"attempt_{attempt:02d}"
        attempt_dir.mkdir(parents=True, exist_ok=True)

        _write_run_outputs(
            attempt_dir,
            current,
            prefix="pytest_before",
            tail_lines=normalized_pytest_output_tail_lines,
        )

        snapshot = None
        snapshot_error = None
        snapshot_full_text: str | None = None
        snapshot_artifact_text: str | None = None
        if _is_snapshot_condition(condition):
            snapshot, snapshot_error = read_latest_snapshot(llmdebug_dir)
            if snapshot is not None:
                snapshot_full_text = json.dumps(snapshot, indent=2, ensure_ascii=False)
                snapshot_artifact = _truncate_snapshot_for_artifact(
                    snapshot,
                    max_string_chars=normalized_snapshot_artifact_max_string_chars,
                )
                snapshot_artifact_text = json.dumps(snapshot_artifact, indent=2, ensure_ascii=False)
                (attempt_dir / "snapshot.json").write_text(snapshot_artifact_text, encoding="utf-8")

        failure_signature = _build_failure_signature_for_attempt(run=current, snapshot=snapshot)
        previous_failure_signature = (
            previous_failure_context.get("failure_signature")
            if isinstance(previous_failure_context, dict)
            else None
        )
        failure_signature_repeat = bool(
            previous_failure_signature and previous_failure_signature == failure_signature
        )
        stagnation_streak = (stagnation_streak + 1) if failure_signature_repeat else 0
        budget_policy_tier_index_before = adaptive_budget_policy_current_tier_index
        budget_policy_default_profile = _build_adaptive_budget_control_profile(
            tier_index=budget_policy_tier_index_before,
            base_tier_index=adaptive_budget_policy_base_tier_index,
            tier_multipliers=normalized_adaptive_budget_policy_tiers,
            base_propose_budget_sec=budget_policy_base_propose_budget_sec,
            base_openai_max_tokens=base_openai_max_tokens,
            base_helper_max_calls=normalized_adaptive_max_helper_calls,
            base_stage_a_max_requests=base_stage_a_max_requests,
            base_stage_a_max_chars=base_stage_a_max_chars,
            base_retry_history_max_chars=base_retry_history_max_chars,
            base_context_compaction_max_calls=base_context_compaction_max_calls,
            context_compaction_enabled=normalized_context_compaction_mode != "off",
            base_response_mode=base_openai_response_mode,
            is_openai_patcher=is_openai_patcher,
        )
        budget_policy_selected_profile = budget_policy_default_profile
        budget_policy_applied_profile = budget_policy_default_profile
        budget_policy_default_controls = _adaptive_budget_control_profile_to_dict(
            budget_policy_default_profile
        )
        budget_policy_selected_controls = dict(budget_policy_default_controls)
        budget_policy_applied_controls = dict(budget_policy_default_controls)
        budget_policy_selected_controls_changed = _adaptive_budget_control_changes(
            current=budget_policy_default_profile,
            target=budget_policy_selected_profile,
        )
        budget_policy_applied_controls_changed = dict(budget_policy_selected_controls_changed)
        budget_policy_partial_override = False
        budget_policy_partial_override_errors: dict[str, str] = {}
        budget_policy_tier_index_after = budget_policy_tier_index_before
        budget_policy_default_multiplier = float(
            budget_policy_default_profile.propose_budget_multiplier
        )
        budget_policy_selected_multiplier = budget_policy_default_multiplier
        budget_policy_applied_multiplier = budget_policy_default_multiplier
        stage_a_max_requests = int(budget_policy_default_profile.stage_a_max_requests)
        stage_a_max_chars = int(budget_policy_default_profile.stage_a_max_chars)
        effective_retry_history_max_chars = int(
            budget_policy_default_profile.retry_history_max_chars
        )
        effective_context_compaction_max_calls = int(
            budget_policy_default_profile.context_compaction_max_calls
        )
        adaptive_budget_unlimited = budget_policy_default_profile.helper_max_calls <= 0
        effective_helper_budget_limit = (
            0
            if adaptive_budget_unlimited
            else max(
                0,
                int(budget_policy_default_profile.helper_max_calls)
                + adaptive_budget_topup_calls_used,
            )
        )
        helper_budget_exhausted = bool(
            is_adaptive_condition
            and (not adaptive_budget_unlimited)
            and helper_calls_used >= effective_helper_budget_limit
        )
        helper_saturation_guarded = False
        if (
            is_adaptive_condition
            and helper_calls_used >= max(2, normalized_adaptive_stagnation_window)
            and helper_calls_used > 0
        ):
            low_yield_ratio = helper_calls_without_context / float(helper_calls_used)
            helper_saturation_guarded = low_yield_ratio >= normalized_adaptive_saturation_threshold

        helper_decision_mode = adaptive_mode_label
        helper_should_consult = False
        helper_invoked = False
        helper_action_default = _default_helper_action()
        helper_action_selected = ACTION_SKIP_HELPER
        helper_action_executed = ACTION_SKIP_HELPER
        helper_action_fallback_reason = ""
        helper_materialized_context = False
        helper_outcome = ADAPTIVE_HELPER_OUTCOME_NOT_INVOKED
        helper_failure_kind = ""
        helper_retry_count = 0
        helper_transport_retry_count = 0
        helper_need_more_context: bool | None = None
        helper_decision_reason = "not_adaptive_condition"
        adaptive_context_requested = False
        adaptive_snapshot_included = False
        adaptive_snapshot_lite_included = False
        adaptive_snapshot_attempt1_denied = False
        adaptive_snapshot_attempt1_denied_reason = ""
        adaptive_budget_topup_granted = False
        adaptive_budget_topup_reason = ""
        patcher_for_attempt = patcher
        request_context_fn = getattr(patcher_for_attempt, "request_context", None)
        supports_context_request = callable(request_context_fn)
        context_compaction_fn = getattr(patcher_for_attempt, "compact_context", None)
        supports_context_compaction = callable(context_compaction_fn)
        online_policy_decision: PolicyDecision | None = None
        online_policy_features_raw: dict[str, float] = {}
        online_policy_features_scaled: dict[str, float] = {}
        online_policy_eligible = False
        online_policy_override_reason = ""
        online_policy_mode_for_attempt = (
            normalized_adaptive_online_policy_mode if is_adaptive_llm_discretion else "off"
        )
        online_policy_algo_for_attempt = (
            normalized_adaptive_online_policy_algo if is_adaptive_llm_discretion else ""
        )
        budget_policy_decision: BudgetPolicyDecision | None = None
        budget_policy_features_raw: dict[str, float] = {}
        budget_policy_features_scaled: dict[str, float] = {}
        budget_policy_eligible = False
        budget_policy_override_reason = ""
        budget_policy_mode_for_attempt = (
            normalized_adaptive_budget_policy_mode if is_adaptive_condition else "off"
        )
        budget_policy_algo_for_attempt = (
            normalized_adaptive_budget_policy_algo if is_adaptive_condition else ""
        )
        adaptive_stagnation_override_applied = False
        adaptive_stagnation_override_reason = ""
        adaptive_force_helper_stage_a = False

        # --- Pre-gate: traceback informativeness ---
        pregate_result = PreGateResult(
            should_skip=False,
            score=0.0,
            exception_type="",
            reason="",
            f_type_specificity=0.0,
            f_message_diagnostic=0.0,
            f_crash_in_user_code=0.0,
            f_assertion_quality=0.0,
            f_frame_depth=0.0,
        )
        if is_adaptive_condition and adaptive_informativeness_pregate:
            pregate_result = _traceback_informativeness_pre_gate(
                snapshot=snapshot,
                stdout=current.stdout,
                stderr=current.stderr,
                threshold=adaptive_informativeness_pregate_threshold,
            )
        if (
            is_adaptive_condition
            and (not adaptive_budget_unlimited)
            and helper_budget_exhausted
            and normalized_adaptive_budget_topup_max_calls > 0
            and adaptive_budget_topup_calls_used < normalized_adaptive_budget_topup_max_calls
            and last_helper_need_more_context is True
            and failure_signature_repeat
            and adaptive_no_progress_streak > 0
            and (not helper_saturation_guarded)
            and (not pregate_result.should_skip)
        ):
            adaptive_budget_topup_calls_used += 1
            adaptive_budget_topup_granted = True
            adaptive_budget_topup_reason = "need_more_context_recovery_once"
            helper_budget_exhausted = False

        if is_adaptive_condition:
            budget_policy_max_tier_index = max(0, len(normalized_adaptive_budget_policy_tiers) - 1)
            can_increase_tier = (
                adaptive_budget_policy_current_tier_index < budget_policy_max_tier_index
            )
            can_apply_more_ups = (
                normalized_adaptive_budget_policy_max_ups_per_case > 0
                and adaptive_budget_policy_ups_used
                < normalized_adaptive_budget_policy_max_ups_per_case
            )
            next_budget_tier_index = min(
                budget_policy_tier_index_before + 1,
                budget_policy_max_tier_index,
            )
            budget_policy_next_profile = _build_adaptive_budget_control_profile(
                tier_index=next_budget_tier_index,
                base_tier_index=adaptive_budget_policy_base_tier_index,
                tier_multipliers=normalized_adaptive_budget_policy_tiers,
                base_propose_budget_sec=budget_policy_base_propose_budget_sec,
                base_openai_max_tokens=base_openai_max_tokens,
                base_helper_max_calls=normalized_adaptive_max_helper_calls,
                base_stage_a_max_requests=base_stage_a_max_requests,
                base_stage_a_max_chars=base_stage_a_max_chars,
                base_retry_history_max_chars=base_retry_history_max_chars,
                base_context_compaction_max_calls=base_context_compaction_max_calls,
                context_compaction_enabled=normalized_context_compaction_mode != "off",
                base_response_mode=base_openai_response_mode,
                is_openai_patcher=is_openai_patcher,
            )
            next_budget_tier_changes = _adaptive_budget_control_changes(
                current=budget_policy_default_profile,
                target=budget_policy_next_profile,
            )
            next_budget_tier_changes_any = _adaptive_budget_controls_changed(
                next_budget_tier_changes
            )
            budget_policy_features_raw = _adaptive_budget_feature_vector(
                attempt=attempt,
                max_attempts=k,
                timed_out=bool(current.timed_out),
                failure_signature_repeat=failure_signature_repeat,
                stagnation_streak=stagnation_streak,
                helper_calls_used=helper_calls_used,
                helper_calls_without_context=helper_calls_without_context,
                adaptive_no_progress_streak=adaptive_no_progress_streak,
                adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
                helper_budget_exhausted=helper_budget_exhausted,
                helper_saturation_guarded=helper_saturation_guarded,
                supports_context_request=supports_context_request,
                snapshot_available=snapshot is not None,
                pregate_result=pregate_result,
                context_compaction_enabled=normalized_context_compaction_mode != "off",
                supports_context_compaction=supports_context_compaction,
                context_compaction_calls_used=context_compaction_calls_used,
                context_compaction_max_calls=effective_context_compaction_max_calls,
                last_context_compaction_requested=last_context_compaction_requested,
                last_context_compaction_invoked=last_context_compaction_invoked,
                last_context_compaction_success=last_context_compaction_success,
                last_context_compaction_ratio=last_context_compaction_ratio,
                last_context_compaction_trigger=last_context_compaction_trigger,
                current_tier_index=adaptive_budget_policy_current_tier_index,
                max_tier_index=budget_policy_max_tier_index,
                ups_used=adaptive_budget_policy_ups_used,
                max_ups_per_case=normalized_adaptive_budget_policy_max_ups_per_case,
                base_propose_budget_sec=budget_policy_base_propose_budget_sec,
                next_tier_changes=next_budget_tier_changes,
                next_tier_multiplier_delta=(
                    budget_policy_next_profile.propose_budget_multiplier
                    - budget_policy_default_profile.propose_budget_multiplier
                ),
                last_helper_need_more_context=last_helper_need_more_context,
                last_helper_decision_reason=last_helper_decision_reason,
            )
            budget_policy_features_scaled = scale_feature_mapping(
                budget_policy_features_raw,
                profile=normalized_adaptive_budget_feature_scaling_profile,
            )
            budget_policy_eligible = bool(
                normalized_adaptive_budget_policy_mode in {"shadow", "on"}
                and adaptive_budget_policy_controller is not None
                and can_increase_tier
                and can_apply_more_ups
                and next_budget_tier_changes_any
            )
            if budget_policy_eligible:
                budget_policy_decision = adaptive_budget_policy_controller.decide(
                    feature_values=budget_policy_features_raw,
                    default_action=ACTION_KEEP_BUDGET,
                )
                budget_policy_mode_for_attempt = budget_policy_decision.mode
                budget_policy_algo_for_attempt = budget_policy_decision.algorithm
                if budget_policy_decision.selected_action == ACTION_INCREASE_BUDGET:
                    budget_policy_selected_profile = budget_policy_next_profile
                    budget_policy_selected_controls = _adaptive_budget_control_profile_to_dict(
                        budget_policy_selected_profile
                    )
                    budget_policy_selected_controls_changed = _adaptive_budget_control_changes(
                        current=budget_policy_default_profile,
                        target=budget_policy_selected_profile,
                    )
                    budget_policy_selected_multiplier = float(
                        budget_policy_selected_profile.propose_budget_multiplier
                    )
                if budget_policy_decision.applied_action == ACTION_INCREASE_BUDGET:
                    budget_policy_tier_index_after = next_budget_tier_index
                    budget_policy_applied_profile = budget_policy_next_profile
                    budget_policy_applied_controls = _adaptive_budget_control_profile_to_dict(
                        budget_policy_applied_profile
                    )
                    budget_policy_applied_controls_changed = _adaptive_budget_control_changes(
                        current=budget_policy_default_profile,
                        target=budget_policy_applied_profile,
                    )
                    budget_policy_applied_multiplier = float(
                        budget_policy_applied_profile.propose_budget_multiplier
                    )
                if (
                    budget_policy_decision.applied_action != budget_policy_decision.selected_action
                    and not budget_policy_override_reason
                ):
                    budget_policy_override_reason = "shadow_mode_baseline"
            else:
                if normalized_adaptive_budget_policy_mode == "off":
                    budget_policy_override_reason = "policy_off"
                elif adaptive_budget_policy_controller is None:
                    budget_policy_override_reason = "policy_controller_unavailable"
                elif normalized_adaptive_budget_policy_max_ups_per_case <= 0:
                    budget_policy_override_reason = "constraint_max_ups_disabled"
                elif (
                    adaptive_budget_policy_ups_used
                    >= normalized_adaptive_budget_policy_max_ups_per_case
                ):
                    budget_policy_override_reason = "constraint_max_ups_reached"
                elif not can_increase_tier:
                    budget_policy_override_reason = "constraint_tier_ceiling"
                elif not next_budget_tier_changes_any:
                    budget_policy_override_reason = "constraint_next_tier_no_control_change"
                else:
                    budget_policy_override_reason = "not_eligible"

            if (
                budget_policy_decision is not None
                and budget_policy_decision.applied_action == ACTION_INCREASE_BUDGET
                and budget_policy_tier_index_after > budget_policy_tier_index_before
            ):
                (
                    patcher_for_attempt,
                    budget_policy_applied_profile,
                    budget_policy_partial_override_errors,
                ) = _apply_adaptive_budget_profile_to_patcher(
                    patcher=patcher_for_attempt,
                    applied_profile=budget_policy_applied_profile,
                    base_propose_budget_sec=budget_policy_base_propose_budget_sec,
                    base_openai_max_tokens=base_openai_max_tokens,
                    base_response_mode=base_openai_response_mode,
                    is_openai_patcher=is_openai_patcher,
                )
                budget_policy_partial_override = bool(budget_policy_partial_override_errors)
                budget_policy_applied_controls = _adaptive_budget_control_profile_to_dict(
                    budget_policy_applied_profile
                )
                budget_policy_applied_controls_changed = _adaptive_budget_control_changes(
                    current=budget_policy_default_profile,
                    target=budget_policy_applied_profile,
                )
                budget_policy_applied_multiplier = float(
                    budget_policy_applied_profile.propose_budget_multiplier
                )
                if not _adaptive_budget_controls_changed(budget_policy_applied_controls_changed):
                    budget_policy_tier_index_after = budget_policy_tier_index_before
                    budget_policy_applied_profile = budget_policy_default_profile
                    budget_policy_applied_controls = dict(budget_policy_default_controls)
                    budget_policy_applied_controls_changed = _adaptive_budget_control_changes(
                        current=budget_policy_default_profile,
                        target=budget_policy_applied_profile,
                    )
                    budget_policy_applied_multiplier = budget_policy_default_multiplier
                    if budget_policy_decision is not None:
                        budget_policy_decision = replace(
                            budget_policy_decision,
                            applied_action=ACTION_KEEP_BUDGET,
                        )
                    if not budget_policy_override_reason:
                        budget_policy_override_reason = (
                            "constraint_budget_profile_no_effective_change"
                        )
                else:
                    adaptive_budget_policy_current_tier_index = budget_policy_tier_index_after
                    adaptive_budget_policy_ups_used += 1

        request_context_fn = getattr(patcher_for_attempt, "request_context", None)
        supports_context_request = callable(request_context_fn)
        context_compaction_fn = getattr(patcher_for_attempt, "compact_context", None)
        supports_context_compaction = callable(context_compaction_fn)
        stage_a_max_requests = int(budget_policy_applied_profile.stage_a_max_requests)
        stage_a_max_chars = int(budget_policy_applied_profile.stage_a_max_chars)
        effective_retry_history_max_chars = int(
            budget_policy_applied_profile.retry_history_max_chars
        )
        effective_context_compaction_max_calls = int(
            budget_policy_applied_profile.context_compaction_max_calls
        )
        adaptive_budget_unlimited = budget_policy_applied_profile.helper_max_calls <= 0
        effective_helper_budget_limit = (
            0
            if adaptive_budget_unlimited
            else max(
                0,
                int(budget_policy_applied_profile.helper_max_calls)
                + adaptive_budget_topup_calls_used,
            )
        )
        helper_budget_exhausted = bool(
            is_adaptive_condition
            and (not adaptive_budget_unlimited)
            and helper_calls_used >= effective_helper_budget_limit
        )

        if is_adaptive_llm_discretion:
            online_policy_features_raw = _adaptive_online_feature_vector(
                attempt=attempt,
                max_attempts=k,
                timed_out=bool(current.timed_out),
                failure_signature_repeat=failure_signature_repeat,
                stagnation_streak=stagnation_streak,
                helper_calls_used=helper_calls_used,
                helper_calls_without_context=helper_calls_without_context,
                adaptive_no_progress_streak=adaptive_no_progress_streak,
                helper_budget_exhausted=helper_budget_exhausted,
                helper_saturation_guarded=helper_saturation_guarded,
                supports_context_request=supports_context_request,
                snapshot_available=snapshot is not None,
                pregate_result=pregate_result,
                effective_helper_budget_limit=effective_helper_budget_limit,
                adaptive_budget_unlimited=adaptive_budget_unlimited,
                adaptive_budget_topup_calls_used=adaptive_budget_topup_calls_used,
                adaptive_budget_topup_max_calls=normalized_adaptive_budget_topup_max_calls,
                last_helper_need_more_context=last_helper_need_more_context,
                last_helper_decision_reason=last_helper_decision_reason,
                last_helper_action_executed=last_helper_action_executed,
                context_compaction_enabled=normalized_context_compaction_mode != "off",
                supports_context_compaction=supports_context_compaction,
                context_compaction_calls_used=context_compaction_calls_used,
                context_compaction_max_calls=effective_context_compaction_max_calls,
                last_context_compaction_requested=last_context_compaction_requested,
                last_context_compaction_invoked=last_context_compaction_invoked,
                last_context_compaction_success=last_context_compaction_success,
                last_context_compaction_ratio=last_context_compaction_ratio,
                last_context_compaction_trigger=last_context_compaction_trigger,
                case_difficulty=case.difficulty,
                case_snapshot_dependency=case.snapshot_dependency,
                case_bug_source=case.bug_source,
            )
            online_policy_features_scaled = scale_feature_mapping(
                online_policy_features_raw,
                profile=normalized_adaptive_online_feature_scaling_profile,
            )

        if is_adaptive_condition:
            last_helper_invalid_response = (
                str(last_helper_decision_reason).strip() == "llm_invalid_response_no_helper"
            )
            if pregate_result.should_skip:
                helper_decision_reason = "informativeness_pregate_skip"
                online_policy_override_reason = "constraint_informativeness_pregate_skip"
                helper_action_selected = ACTION_SKIP_HELPER
                helper_action_executed = ACTION_SKIP_HELPER
            elif helper_budget_exhausted:
                helper_decision_reason = "helper_budget_exhausted"
                online_policy_override_reason = "constraint_helper_budget_exhausted"
                helper_action_selected = ACTION_SKIP_HELPER
                helper_action_executed = ACTION_SKIP_HELPER
            elif helper_saturation_guarded:
                helper_decision_reason = "helper_saturation_guard"
                online_policy_override_reason = "constraint_helper_saturation_guard"
                helper_action_selected = ACTION_SKIP_HELPER
                helper_action_executed = ACTION_SKIP_HELPER
            elif is_adaptive_gated:
                helper_should_consult, helper_decision_reason = _adaptive_gate_should_invoke_helper(
                    policy=normalized_adaptive_gate_policy,
                    attempt=attempt,
                    timed_out=bool(current.timed_out),
                    signature_repeated=failure_signature_repeat,
                    stagnation_streak=stagnation_streak,
                    stagnation_window=normalized_adaptive_stagnation_window,
                )
                helper_action_selected = (
                    ACTION_INVOKE_HELPER if helper_should_consult else ACTION_SKIP_HELPER
                )
                helper_action_executed = helper_action_selected
            else:
                helper_decision_reason = "llm_discretion_pending"
                online_policy_eligible = bool(
                    adaptive_online_policy_controller is not None
                    and normalized_adaptive_online_policy_mode in {"shadow", "on"}
                )
                if (
                    adaptive_online_policy_controller is not None
                    and normalized_adaptive_online_policy_mode in {"shadow", "on"}
                ):
                    online_policy_decision = adaptive_online_policy_controller.decide(
                        feature_values=online_policy_features_raw,
                        default_action=helper_action_default,
                    )
                    online_policy_mode_for_attempt = online_policy_decision.mode
                    online_policy_algo_for_attempt = online_policy_decision.algorithm
                    helper_action_selected = str(
                        online_policy_decision.selected_action or helper_action_default
                    )
                    helper_action_executed = str(
                        online_policy_decision.applied_action or helper_action_default
                    )
                    if (
                        online_policy_decision.applied_action
                        != online_policy_decision.selected_action
                    ):
                        online_policy_override_reason = "shadow_mode_baseline"
                elif normalized_adaptive_online_policy_mode == "off":
                    online_policy_override_reason = "policy_off"
                    helper_action_selected = helper_action_default
                    helper_action_executed = helper_action_default
                else:
                    online_policy_override_reason = "policy_controller_unavailable"
                    helper_action_selected = helper_action_default
                    helper_action_executed = helper_action_default
                helper_action_fallback_reason = _helper_action_fallback_reason(
                    action=helper_action_executed,
                    supports_context_request=supports_context_request,
                    snapshot_available=snapshot is not None,
                    context_compaction_mode=normalized_context_compaction_mode,
                    supports_context_compaction=supports_context_compaction,
                    context_compaction_calls_used=context_compaction_calls_used,
                    context_compaction_max_calls=effective_context_compaction_max_calls,
                    is_openai_patcher=is_openai_patcher,
                    last_helper_invalid_response=last_helper_invalid_response,
                )
                if helper_action_fallback_reason:
                    helper_action_executed = ACTION_INVOKE_HELPER
                helper_should_consult = helper_action_executed in HELPER_STAGE_A_ACTIONS
                if helper_action_executed == ACTION_SKIP_HELPER:
                    helper_decision_reason = "online_policy_skip_helper"
                elif helper_action_executed == ACTION_SNAPSHOT_LITE:
                    helper_decision_reason = "policy_action_snapshot_lite"
                elif helper_action_executed == ACTION_COMPACT_THEN_PATCH:
                    helper_decision_reason = "policy_action_compact_then_patch"
            if adaptive_budget_topup_granted and not online_policy_override_reason:
                online_policy_override_reason = "constraint_helper_budget_topup"

            previous_note_for_override = (
                str(previous_failure_context.get("note") or "")
                if isinstance(previous_failure_context, dict)
                else ""
            )
            previous_stagnation_signal = previous_note_for_override in {
                "no_patch_proposed",
                "patcher_request_failed",
                "forced_noop_patch",
                "patch_verifier_rejected",
                "syntax_preflight_failed",
                "patch_apply_failed",
                "tests_still_failing",
                "retry_gate_no_new_evidence",
                "hard_stop_no_progress",
            }
            policy_skip_signal = bool(
                online_policy_decision is not None
                and str(online_policy_decision.applied_action or "") not in HELPER_STAGE_A_ACTIONS
            )
            historical_skip_signal = last_helper_decision_reason in {
                "online_policy_skip_helper",
                "llm_declined_helper",
                "llm_invalid_response_no_helper",
                "llm_snapshot_request_denied_attempt1",
                "policy_action_snapshot_lite",
                "policy_action_compact_then_patch",
            }
            if (
                is_adaptive_llm_discretion
                and (not helper_should_consult)
                and failure_signature_repeat
                and stagnation_streak > 0
                and previous_stagnation_signal
                and (policy_skip_signal or historical_skip_signal)
                and (not pregate_result.should_skip)
                and supports_context_request
            ):
                adaptive_stagnation_override_reason = "repeated_no_progress_after_stagnation_note"
                if (
                    helper_budget_exhausted
                    and (not adaptive_budget_unlimited)
                    and normalized_adaptive_budget_topup_max_calls > 0
                    and adaptive_budget_topup_calls_used
                    < normalized_adaptive_budget_topup_max_calls
                ):
                    adaptive_budget_topup_calls_used += 1
                    adaptive_budget_topup_granted = True
                    adaptive_budget_topup_reason = "stagnation_override_recovery_once"
                    helper_budget_exhausted = False
                if not helper_budget_exhausted and not helper_saturation_guarded:
                    adaptive_stagnation_override_applied = True
                    adaptive_force_helper_stage_a = True
                    helper_action_executed = ACTION_INVOKE_HELPER
                    helper_should_consult = True
                    helper_decision_reason = "stagnation_override_force_helper"
                    online_policy_override_reason = "constraint_stagnation_override_force_helper"

        current_failure_context: dict[str, Any] = {
            "attempt": attempt,
            "failure_signature": failure_signature,
            "stdout": current.stdout,
            "stderr": current.stderr,
            "snapshot": snapshot,
        }
        previous_attempt_diff = _build_structured_previous_attempt_diff(
            previous_failure_context,
            current_failure_context=current_failure_context,
        )
        previous_note = (
            str(previous_failure_context.get("note") or "")
            if isinstance(previous_failure_context, dict)
            else ""
        )
        previous_patch_verifier_rejected = bool(
            isinstance(previous_failure_context, dict)
            and previous_failure_context.get("patch_verifier_rejected")
        )
        previous_patch_verifier_reasons_raw = (
            previous_failure_context.get("patch_verifier_reasons")
            if isinstance(previous_failure_context, dict)
            else []
        )
        previous_patch_verifier_reasons = (
            [str(item) for item in previous_patch_verifier_reasons_raw]
            if isinstance(previous_patch_verifier_reasons_raw, list)
            else []
        )
        previous_patch_verifier_scope = (
            str(previous_failure_context.get("patch_verifier_scope") or "")
            if isinstance(previous_failure_context, dict)
            else ""
        )
        previous_timed_out = bool(
            isinstance(previous_failure_context, dict) and previous_failure_context.get("timed_out")
        )
        previous_timeout_sec_raw = (
            previous_failure_context.get("timeout_sec")
            if isinstance(previous_failure_context, dict)
            else None
        )
        previous_timeout_sec = (
            int(previous_timeout_sec_raw) if isinstance(previous_timeout_sec_raw, int) else None
        )

        file_index_lines = _file_index_lines_for_attempt()
        file_index = _file_index_text_for_attempt()
        attempt_context_protocol = default_context_protocol
        evidence = {
            "case_id": case.case_id,
            "condition": condition.value,
            "attempt": attempt,
            "repro": [sys.executable, *case.python_args],
            "stdout": current.stdout,
            "stderr": current.stderr,
            "failure_signature": failure_signature,
            "previous_failure_signature": previous_failure_signature,
            "failure_signature_repeat": failure_signature_repeat,
            "previous_attempt_diff": previous_attempt_diff,
            "timed_out": bool(current.timed_out),
            "timeout_sec": int(current.timeout_sec)
            if isinstance(current.timeout_sec, int)
            else None,
            "previous_note": previous_note,
            "previous_patch_verifier_rejected": previous_patch_verifier_rejected,
            "previous_patch_verifier_reasons": previous_patch_verifier_reasons,
            "previous_patch_verifier_scope": previous_patch_verifier_scope,
            "previous_timed_out": previous_timed_out,
            "previous_timeout_sec": previous_timeout_sec,
            "file_index": file_index,
            "files": "",
            "snapshot_available": snapshot is not None,
            "snapshot_error": snapshot_error,
        }
        snapshot_for_prompt = snapshot if not is_adaptive_condition else None
        stage_a_prompt: str | None = None
        stage_a_data = _empty_stage_a_context(reason="not_used")
        stage_a_meta: dict[str, Any] = {}
        stage_a_request_invoked = False
        stage_a_request_payload: ContextRequestPayload | None = None
        stage_a_raw_response: str | None = None
        stage_a_need_snapshot = False
        stage_a_snapshot_justification = ""
        stage_a_need_compact_context = False
        stage_a_compact_context_reason = ""
        snapshot_lite_context = ""
        snapshot_lite_requested = False
        context_action_materialized = False
        helper_local_wall_sec = 0.0
        run_stage_a = False
        if is_structured_condition:
            run_stage_a = True
        elif is_adaptive_gated:
            run_stage_a = helper_should_consult
        elif is_adaptive_llm_discretion:
            can_run_stage_a = (
                not pregate_result.should_skip
                and not helper_budget_exhausted
                and not helper_saturation_guarded
                and supports_context_request
            )
            if adaptive_force_helper_stage_a:
                run_stage_a = can_run_stage_a
                if not run_stage_a and not adaptive_stagnation_override_reason:
                    adaptive_stagnation_override_reason = "stagnation_override_constraints_blocked"
            elif not can_run_stage_a:
                run_stage_a = False
            else:
                run_stage_a = helper_action_executed in HELPER_STAGE_A_ACTIONS
                if not run_stage_a and helper_action_executed == ACTION_SKIP_HELPER:
                    helper_decision_reason = "online_policy_skip_helper"
        elif default_context_protocol == "staged":
            run_stage_a = True

        helper_local_started_at = time.perf_counter() if run_stage_a else None
        if run_stage_a:
            stage_a_prompt = render_stage_a_request_prompt(
                evidence=evidence,
                snapshot=snapshot,
                helper_budget_total=(
                    effective_helper_budget_limit
                    if is_adaptive_condition and not adaptive_budget_unlimited
                    else 0
                ),
                helper_budget_remaining=(
                    max(0, effective_helper_budget_limit - helper_calls_used)
                    if is_adaptive_condition and not adaptive_budget_unlimited
                    else 0
                ),
                helper_action=helper_action_executed,
            )
            if supports_context_request:
                stage_a_request_invoked = True
                if is_adaptive_condition:
                    helper_invoked = True
                if helper_action_executed == ACTION_RETRY_HELPER_STRICT_JSON:
                    context_result = request_context_fn(
                        prompt=stage_a_prompt,
                        context_dir=attempt_dir,
                        cwd=workdir,
                        strict_json_first=True,
                    )
                else:
                    context_result = request_context_fn(
                        prompt=stage_a_prompt,
                        context_dir=attempt_dir,
                        cwd=workdir,
                    )
            else:
                context_result = None
            if context_result is not None:
                if hasattr(context_result, "meta") and isinstance(context_result.meta, dict):
                    stage_a_meta.update(context_result.meta)
                request_candidate = (
                    context_result.request_json if hasattr(context_result, "request_json") else None
                )
                request_payload = _coerce_context_request_payload(request_candidate)
                if request_payload is not None:
                    stage_a_request_payload = _filter_context_request_payload_for_action(
                        action=helper_action_executed,
                        request_payload=request_payload,
                    )
                if hasattr(context_result, "raw_response"):
                    raw_candidate = context_result.raw_response
                    if isinstance(raw_candidate, str):
                        stage_a_raw_response = raw_candidate

            if stage_a_request_payload is not None:
                stage_a_need_snapshot = bool(stage_a_request_payload.get("need_snapshot"))
                stage_a_snapshot_justification = str(
                    stage_a_request_payload.get("snapshot_justification") or ""
                )
                stage_a_need_compact_context = bool(
                    stage_a_request_payload.get("need_compact_context")
                )
                stage_a_compact_context_reason = str(
                    stage_a_request_payload.get("compact_context_reason") or ""
                )
                stage_a_data = execute_context_requests(
                    request_payload=stage_a_request_payload,
                    root=workdir,
                    snapshot=snapshot,
                    file_index=file_index_lines,
                    stdout=current.stdout,
                    stderr=current.stderr,
                    max_requests=stage_a_max_requests,
                    max_total_chars=stage_a_max_chars,
                )
                if is_adaptive_condition:
                    helper_need_more_context = bool(
                        stage_a_request_payload.get("need_more_context")
                    )
                    if is_adaptive_llm_discretion:
                        helper_decision_reason = (
                            "llm_requested_helper"
                            if (helper_need_more_context or stage_a_need_snapshot)
                            else "llm_declined_helper"
                        )
                    elif (
                        is_adaptive_gated and not helper_should_consult and helper_need_more_context
                    ):
                        helper_should_consult = True
                        helper_decision_reason = "gate_requested_helper"
            else:
                request_context_failure_kind = _request_context_failure_kind_from_meta(stage_a_meta)
                stage_a_failure_reason = {
                    "transport": "helper_transport_failed",
                    "backend_unavailable": "helper_unavailable_backend",
                    "request_budget_exceeded": "helper_request_budget_exceeded",
                    "invalid_payload": "helper_invalid_payload",
                }.get(request_context_failure_kind, "invalid_or_missing_stage_a_response")
                if is_adaptive_llm_discretion:
                    if request_context_failure_kind in {"", "invalid_payload"}:
                        helper_decision_reason = "llm_invalid_response_no_helper"
                    stage_a_data = StageAContextData(
                        blocks=[],
                        text="",
                        used_fallback=False,
                        requests_count=0,
                        chars_returned=0,
                        request_success=False,
                        request_payload=None,
                        request_raw=stage_a_raw_response,
                        reason=stage_a_failure_reason,
                        evidence_items=[],
                    )
                else:
                    stage_a_data = build_fallback_context_bundle(
                        root=workdir,
                        snapshot=snapshot,
                        stdout=current.stdout,
                        stderr=current.stderr,
                        max_total_chars=stage_a_max_chars,
                        reason=stage_a_failure_reason,
                        file_index_lines=file_index_lines,
                        request_raw=stage_a_raw_response,
                    )

            if stage_a_data.request_payload is None:
                stage_a_data = StageAContextData(
                    blocks=stage_a_data.blocks,
                    text=stage_a_data.text,
                    used_fallback=stage_a_data.used_fallback,
                    requests_count=stage_a_data.requests_count,
                    chars_returned=stage_a_data.chars_returned,
                    request_success=stage_a_data.request_success,
                    request_payload=stage_a_request_payload,
                    request_raw=stage_a_raw_response,
                    reason=stage_a_data.reason,
                    evidence_items=stage_a_data.evidence_items,
                )
        else:
            if is_adaptive_llm_discretion:
                if pregate_result.should_skip:
                    pass  # Keep "informativeness_pregate_skip" from earlier
                elif helper_budget_exhausted:
                    helper_decision_reason = "helper_budget_exhausted"
                elif helper_saturation_guarded:
                    helper_decision_reason = "helper_saturation_guard"
                elif helper_action_executed == ACTION_SNAPSHOT_LITE and snapshot is not None:
                    snapshot_lite_requested = True
                    snapshot_lite_context = _format_snapshot_index_section(snapshot)
                    helper_decision_reason = "policy_action_snapshot_lite"
                elif helper_action_executed == ACTION_COMPACT_THEN_PATCH:
                    helper_decision_reason = "policy_action_compact_then_patch"
                    stage_a_need_compact_context = True
                    stage_a_compact_context_reason = "policy_action"
                elif helper_action_executed == ACTION_SKIP_HELPER:
                    helper_decision_reason = "online_policy_skip_helper"
                elif not supports_context_request:
                    helper_decision_reason = "request_context_unavailable"
                stage_a_data = _empty_stage_a_context(reason=helper_decision_reason)
            elif is_adaptive_gated:
                stage_a_data = _empty_stage_a_context(reason=helper_decision_reason)
        if helper_local_started_at is not None:
            helper_local_wall_sec = time.perf_counter() - helper_local_started_at

        if is_adaptive_condition:
            adaptive_context_requested = stage_a_data.requests_count > 0

            # Attempt-1 snapshot restriction: require justification to include snapshot in Stage-B.
            if (
                attempt == 1
                and stage_a_need_snapshot
                and _is_generic_snapshot_justification(stage_a_snapshot_justification)
            ):
                adaptive_snapshot_attempt1_denied = True
                adaptive_snapshot_attempt1_denied_reason = "missing_or_generic_justification"
                stage_a_need_snapshot = False

            adaptive_snapshot_included = bool(stage_a_need_snapshot and snapshot is not None)
            helper_materialized_context = bool(
                adaptive_context_requested or adaptive_snapshot_included
            )
            context_action_materialized = bool(helper_materialized_context)
            helper_retry_count, helper_transport_retry_count = (
                _request_context_retry_counts_from_meta(stage_a_meta)
            )
            if helper_action_executed == ACTION_COMPACT_THEN_PATCH:
                helper_outcome = ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT
                helper_failure_kind = ""
            else:
                helper_outcome, helper_failure_kind = _adaptive_helper_outcome_for_attempt(
                    helper_invoked=helper_invoked,
                    helper_materialized_context=helper_materialized_context,
                    helper_decision_reason=helper_decision_reason,
                    stage_a_meta=stage_a_meta,
                )
                helper_reason_overrides = {
                    ADAPTIVE_HELPER_OUTCOME_INVALID_PAYLOAD: "helper_invalid_payload",
                    ADAPTIVE_HELPER_OUTCOME_TRANSPORT_FAILED: "helper_transport_failed",
                    ADAPTIVE_HELPER_OUTCOME_UNAVAILABLE_BACKEND: "helper_unavailable_backend",
                    ADAPTIVE_HELPER_OUTCOME_REQUEST_BUDGET_EXCEEDED: (
                        "helper_request_budget_exceeded"
                    ),
                }
                helper_reason_override = helper_reason_overrides.get(helper_outcome)
                if helper_reason_override:
                    stage_a_data = replace(stage_a_data, reason=helper_reason_override)
            attempt_context_protocol = "staged" if helper_materialized_context else "monolithic"
            snapshot_for_prompt = snapshot if adaptive_snapshot_included else None
            if (
                is_adaptive_llm_discretion
                and not helper_materialized_context
                and helper_decision_reason == "llm_requested_helper"
            ):
                helper_decision_reason = (
                    "llm_snapshot_request_denied_attempt1"
                    if adaptive_snapshot_attempt1_denied
                    else "llm_declined_helper"
                )
                helper_outcome = ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT
            if helper_invoked:
                if helper_outcome not in {
                    ADAPTIVE_HELPER_OUTCOME_TRANSPORT_FAILED,
                    ADAPTIVE_HELPER_OUTCOME_UNAVAILABLE_BACKEND,
                }:
                    helper_calls_used += 1
                if helper_outcome == ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT:
                    helper_calls_without_context += 1
            if isinstance(helper_need_more_context, bool):
                last_helper_need_more_context = helper_need_more_context
        elif default_context_protocol == "staged":
            attempt_context_protocol = "staged"
            snapshot_for_prompt = snapshot
        else:
            attempt_context_protocol = "monolithic"
            snapshot_for_prompt = snapshot
        if is_adaptive_condition:
            last_helper_decision_reason = str(helper_decision_reason)
            last_helper_action_executed = str(helper_action_executed or ACTION_SKIP_HELPER)
            if helper_outcome == ADAPTIVE_HELPER_OUTCOME_NOT_INVOKED:
                helper_outcome, helper_failure_kind = _adaptive_helper_outcome_for_attempt(
                    helper_invoked=helper_invoked,
                    helper_materialized_context=helper_materialized_context,
                    helper_decision_reason=helper_decision_reason,
                    stage_a_meta=stage_a_meta,
                )

        project_files_context = (
            _monolithic_context_for_attempt()
            if include_files_context and attempt_context_protocol == "monolithic"
            else ""
        )
        evidence["files"] = project_files_context
        evidence["snapshot_available"] = snapshot_for_prompt is not None

        stage_a_default_llm_calls = 1 if (is_openai_patcher and stage_a_request_invoked) else 0
        (
            helper_local_llm_calls,
            helper_local_tokens_prompt,
            helper_local_tokens_completion,
            helper_local_tokens_total,
        ) = _aggregate_attempt_llm_usage(
            stage_a_meta=stage_a_meta if stage_a_request_invoked else None,
            patch_meta=None,
            stage_a_default_llm_calls=stage_a_default_llm_calls if stage_a_request_invoked else 0,
            patch_default_llm_calls=0,
        )
        helper_local_accounting = _adaptive_policy_local_accounting(
            llm_call_count=helper_local_llm_calls,
            tokens_prompt=helper_local_tokens_prompt,
            tokens_completion=helper_local_tokens_completion,
            tokens_total=helper_local_tokens_total,
            wall_sec=helper_local_wall_sec if stage_a_request_invoked else 0.0,
        )

        structured_seed_data = _empty_stage_a_context()
        structured_seed_active = is_structured_condition or (
            is_adaptive_condition and helper_materialized_context
        )
        if structured_seed_active:
            structured_seed_data = build_fallback_context_bundle(
                root=workdir,
                snapshot=snapshot_for_prompt,
                stdout=current.stdout,
                stderr=current.stderr,
                max_total_chars=stage_a_max_chars,
                reason="structured_seed_bundle"
                if is_structured_condition
                else "adaptive_helper_seed_bundle",
                file_index_lines=file_index_lines,
            )

        combined_evidence_items = _merge_evidence_items(
            structured_seed_data.evidence_items if structured_seed_active else [],
            stage_a_data.evidence_items,
        )
        combined_evidence_ids = _evidence_ids_from_items(combined_evidence_items)
        new_evidence_items = [
            item
            for item in combined_evidence_items
            if str(item.get("id") or "") not in seen_evidence_ids
        ]
        new_evidence_ids = _evidence_ids_from_items(new_evidence_items)
        reused_evidence_ids = [
            evidence_id for evidence_id in combined_evidence_ids if evidence_id in seen_evidence_ids
        ]
        seen_evidence_ids.update(combined_evidence_ids)

        attempt_retry_prompt_mode = base_retry_prompt_mode
        is_delta_retry = attempt_retry_prompt_mode == "delta" and attempt > 1
        evidence_items_for_prompt = (
            new_evidence_items if is_delta_retry else combined_evidence_items
        )
        stage_a_context_full = _render_evidence_items(combined_evidence_items)
        stage_a_context_text = (
            _render_evidence_items(new_evidence_items) if is_delta_retry else stage_a_context_full
        )
        stage_a_evidence_index_text = _render_evidence_index(evidence_items_for_prompt)
        retry_delta_chars = len(stage_a_context_text) if is_delta_retry else 0
        adaptive_progress_made = False
        adaptive_progress_reason = "not_adaptive_condition"
        adaptive_progress_score = 0.0
        adaptive_no_progress_streak_current = 0
        adaptive_progress_stop_candidate = False
        adaptive_progress_stop_reason = "not_adaptive_condition"

        retry_packet = ""
        if structured_seed_active or is_delta_retry:
            retry_packet = _render_retry_packet(
                failure_signature=failure_signature,
                previous_failure_signature=evidence.get("previous_failure_signature"),
                failure_signature_repeat=bool(evidence.get("failure_signature_repeat")),
                previous_attempt_diff=previous_attempt_diff,
                timed_out=bool(evidence.get("timed_out")),
                timeout_sec=(
                    int(evidence.get("timeout_sec"))
                    if isinstance(evidence.get("timeout_sec"), int)
                    else None
                ),
                previous_note=str(evidence.get("previous_note") or ""),
                previous_patch_verifier_rejected=bool(
                    evidence.get("previous_patch_verifier_rejected")
                ),
                previous_patch_verifier_reasons=(
                    list(evidence.get("previous_patch_verifier_reasons"))
                    if isinstance(evidence.get("previous_patch_verifier_reasons"), list)
                    else []
                ),
                previous_patch_verifier_scope=str(
                    evidence.get("previous_patch_verifier_scope") or ""
                ),
            )
        evidence_fingerprint = _compute_evidence_fingerprint(
            failure_signature=failure_signature,
            stage_a_blocks=stage_a_data.blocks,
            stage_a_context=stage_a_context_full,
            structured_seed_blocks=structured_seed_data.blocks if structured_seed_active else [],
        )
        current_failure_context["evidence_fingerprint"] = evidence_fingerprint

        (
            retry_history_text,
            retry_history_chars,
            retry_history_turns_included,
            retry_history_truncated,
        ) = (
            _build_retry_history_section(
                prior_turns=retry_history_turns,
                max_chars=effective_retry_history_max_chars,
            )
            if effective_retry_context_mode == "history" and attempt > 1
            else ("", 0, 0, False)
        )
        attempt_prompt_budget = _adaptive_prompt_budget_for_attempt(
            base_budget=prompt_budget,
            helper_materialized_context=(is_adaptive_condition and helper_materialized_context),
            profile=normalized_adaptive_helper_budget_profile,
        )
        openai_preflight_prompt_cap_chars: int | None = None
        openai_preflight_trimmed = False
        if is_openai_patcher and normalized_openai_context_window_tokens > 0:
            openai_max_tokens_for_attempt = int(getattr(patcher_for_attempt, "max_tokens", 0) or 0)
            openai_preflight_prompt_cap_chars = _openai_preflight_total_char_cap(
                context_window_tokens=normalized_openai_context_window_tokens,
                max_tokens=openai_max_tokens_for_attempt,
            )
            previous_total_chars = int(attempt_prompt_budget.get("total_chars", 0))
            next_total_chars = (
                openai_preflight_prompt_cap_chars
                if previous_total_chars <= 0
                else min(previous_total_chars, openai_preflight_prompt_cap_chars)
            )
            openai_preflight_trimmed = (previous_total_chars <= 0 and next_total_chars > 0) or (
                previous_total_chars > 0 and next_total_chars < previous_total_chars
            )
            attempt_prompt_budget["total_chars"] = max(0, int(next_total_chars))
        include_stage_a_evidence_index = bool(
            stage_a_evidence_index_text and (is_delta_retry or not stage_a_context_text.strip())
        )
        stage_a_index_reason = (
            "delta_id_map"
            if (include_stage_a_evidence_index and is_delta_retry)
            else ("context_missing" if include_stage_a_evidence_index else "redundant_skipped")
        )
        include_snapshot_in_prompt = True
        include_pytest_output_in_prompt = True
        include_stage_a_context_in_prompt = True
        include_project_files_in_prompt = attempt_context_protocol == "monolithic"
        compacted_context_for_prompt = ""
        context_compaction_requested = bool(stage_a_need_compact_context)
        context_compaction_request_reason = str(stage_a_compact_context_reason or "")
        policy_compact_then_patch_requested = bool(
            is_adaptive_llm_discretion and helper_action_executed == ACTION_COMPACT_THEN_PATCH
        )
        context_compaction_invoked = False
        context_compaction_success = False
        context_compaction_trigger = "not_needed"
        context_compaction_error = ""
        context_compaction_auto_overflow_triggered = False
        context_compaction_replaced_sections: list[str] = []
        context_compaction_source_sections: list[str] = []
        context_compaction_source_chars: dict[str, int] = {}
        context_compaction_input_chars = 0
        context_compaction_output_chars = 0
        context_compaction_prompt_chars = 0
        context_compaction_meta: dict[str, Any] = {}

        prompt_sections = _build_stage_b_prompt_sections(
            evidence=evidence,
            snapshot=snapshot_for_prompt,
            stage_a_context=stage_a_context_text,
            stage_a_evidence_index=stage_a_evidence_index_text,
            include_stage_a_evidence_index=include_stage_a_evidence_index,
            compacted_context=compacted_context_for_prompt,
            include_snapshot=include_snapshot_in_prompt,
            include_pytest_output=include_pytest_output_in_prompt,
            include_stage_a_context=include_stage_a_context_in_prompt,
            include_project_files=include_project_files_in_prompt,
            retry_packet=retry_packet,
            retry_prompt_mode=attempt_retry_prompt_mode,
            retry_history=retry_history_text,
            attempt=attempt,
            known_evidence_refs=reused_evidence_ids,
            new_evidence_ids=new_evidence_ids,
            snapshot_lite_context=snapshot_lite_context,
        )
        prompt_budget_result = _apply_prompt_budget(
            sections=prompt_sections,
            budget=attempt_prompt_budget,
            is_delta_retry=is_delta_retry,
        )
        # If Stage-A context had to be truncated/dropped, re-introduce compact ID index.
        if (
            stage_a_evidence_index_text
            and not include_stage_a_evidence_index
            and prompt_budget_result.section_status.get("stage_a_context")
            in {"truncated", "dropped"}
        ):
            prompt_sections = _build_stage_b_prompt_sections(
                evidence=evidence,
                snapshot=snapshot_for_prompt,
                stage_a_context=stage_a_context_text,
                stage_a_evidence_index=stage_a_evidence_index_text,
                include_stage_a_evidence_index=True,
                compacted_context=compacted_context_for_prompt,
                include_snapshot=include_snapshot_in_prompt,
                include_pytest_output=include_pytest_output_in_prompt,
                include_stage_a_context=include_stage_a_context_in_prompt,
                include_project_files=include_project_files_in_prompt,
                retry_packet=retry_packet,
                retry_prompt_mode=attempt_retry_prompt_mode,
                retry_history=retry_history_text,
                attempt=attempt,
                known_evidence_refs=reused_evidence_ids,
                new_evidence_ids=new_evidence_ids,
                snapshot_lite_context=snapshot_lite_context,
            )
            prompt_budget_result = _apply_prompt_budget(
                sections=prompt_sections,
                budget=attempt_prompt_budget,
                is_delta_retry=is_delta_retry,
            )
            include_stage_a_evidence_index = True
            stage_a_index_reason = "context_truncated_fallback"

        context_compaction_auto_overflow_triggered = bool(
            prompt_budget_result.overflow_chars > 0
            or prompt_budget_result.section_status.get("snapshot") in {"truncated", "dropped"}
            or prompt_budget_result.section_status.get("pytest_output") in {"truncated", "dropped"}
            or prompt_budget_result.section_status.get("project_files") in {"truncated", "dropped"}
            or prompt_budget_result.section_status.get("stage_a_context")
            in {"truncated", "dropped"}
        )
        context_compaction_bundle = _build_context_compaction_bundle(
            scope=normalized_context_compaction_scope,
            snapshot=snapshot_for_prompt,
            stdout=current.stdout,
            stderr=current.stderr,
            stage_a_context=stage_a_context_text,
            project_files_context=project_files_context if include_project_files_in_prompt else "",
            max_input_chars=normalized_context_compaction_max_input_chars,
        )
        context_compaction_source_sections = list(context_compaction_bundle.source_sections)
        context_compaction_source_chars = dict(context_compaction_bundle.source_chars)
        context_compaction_input_chars = len(context_compaction_bundle.text)
        should_compact_context, context_compaction_trigger = _should_invoke_context_compaction(
            mode=normalized_context_compaction_mode,
            supports_context_compaction=supports_context_compaction,
            stage_a_requested=context_compaction_requested,
            policy_action_requested=policy_compact_then_patch_requested,
            auto_overflow_triggered=context_compaction_auto_overflow_triggered,
            calls_used=context_compaction_calls_used,
            max_calls=effective_context_compaction_max_calls,
            bundle_input_chars=context_compaction_input_chars,
        )
        if should_compact_context:
            context_compaction_invoked = True
            context_compaction_calls_used += 1
            context_compaction_prompt = render_context_compaction_prompt(
                evidence=evidence,
                attempt=attempt,
                reason=context_compaction_request_reason,
                source_sections=context_compaction_source_sections,
                target_chars=normalized_context_compaction_target_chars,
            )
            context_compaction_prompt_chars = len(context_compaction_prompt)
            try:
                compaction_result = context_compaction_fn(
                    prompt=context_compaction_prompt,
                    context_text=context_compaction_bundle.text,
                    context_dir=attempt_dir,
                    cwd=workdir,
                    target_chars=normalized_context_compaction_target_chars,
                )
            except Exception as exc:
                compaction_result = None
                context_compaction_error = f"{type(exc).__name__}: {exc}"
            if compaction_result is not None:
                if hasattr(compaction_result, "meta") and isinstance(compaction_result.meta, dict):
                    context_compaction_meta = dict(compaction_result.meta)
                compacted_text_candidate = (
                    compaction_result.compacted_text
                    if hasattr(compaction_result, "compacted_text")
                    else None
                )
                if isinstance(compacted_text_candidate, str) and compacted_text_candidate.strip():
                    compacted_context_for_prompt = compacted_text_candidate.strip()
                    context_compaction_output_chars = len(compacted_context_for_prompt)
                    context_compaction_success = True
                elif not context_compaction_error:
                    context_compaction_error = str(context_compaction_meta.get("error") or "")
            elif not context_compaction_error:
                context_compaction_error = "compact_context_unavailable"

            if context_compaction_success:
                include_snapshot_in_prompt = not context_compaction_bundle.replace_snapshot
                include_pytest_output_in_prompt = (
                    not context_compaction_bundle.replace_pytest_output
                )
                include_stage_a_context_in_prompt = (
                    not context_compaction_bundle.replace_stage_a_context
                )
                include_project_files_in_prompt = include_project_files_in_prompt and (
                    not context_compaction_bundle.replace_project_files
                )
                if context_compaction_bundle.replace_snapshot:
                    context_compaction_replaced_sections.append("snapshot")
                if context_compaction_bundle.replace_pytest_output:
                    context_compaction_replaced_sections.append("pytest_output")
                if context_compaction_bundle.replace_stage_a_context:
                    context_compaction_replaced_sections.append("stage_a_context")
                if context_compaction_bundle.replace_project_files:
                    context_compaction_replaced_sections.append("project_files")
                prompt_sections = _build_stage_b_prompt_sections(
                    evidence=evidence,
                    snapshot=snapshot_for_prompt,
                    stage_a_context=stage_a_context_text,
                    stage_a_evidence_index=stage_a_evidence_index_text,
                    include_stage_a_evidence_index=include_stage_a_evidence_index,
                    compacted_context=compacted_context_for_prompt,
                    include_snapshot=include_snapshot_in_prompt,
                    include_pytest_output=include_pytest_output_in_prompt,
                    include_stage_a_context=include_stage_a_context_in_prompt,
                    include_project_files=include_project_files_in_prompt,
                    retry_packet=retry_packet,
                    retry_prompt_mode=attempt_retry_prompt_mode,
                    retry_history=retry_history_text,
                    attempt=attempt,
                    known_evidence_refs=reused_evidence_ids,
                    new_evidence_ids=new_evidence_ids,
                    snapshot_lite_context=snapshot_lite_context,
                )
                prompt_budget_result = _apply_prompt_budget(
                    sections=prompt_sections,
                    budget=attempt_prompt_budget,
                    is_delta_retry=is_delta_retry,
                )
                if (
                    stage_a_evidence_index_text
                    and not include_stage_a_evidence_index
                    and (
                        prompt_budget_result.section_status.get("stage_a_context")
                        in {"truncated", "dropped"}
                        or prompt_budget_result.section_status.get("compacted_context")
                        in {"truncated", "dropped"}
                    )
                ):
                    prompt_sections = _build_stage_b_prompt_sections(
                        evidence=evidence,
                        snapshot=snapshot_for_prompt,
                        stage_a_context=stage_a_context_text,
                        stage_a_evidence_index=stage_a_evidence_index_text,
                        include_stage_a_evidence_index=True,
                        compacted_context=compacted_context_for_prompt,
                        include_snapshot=include_snapshot_in_prompt,
                        include_pytest_output=include_pytest_output_in_prompt,
                        include_stage_a_context=include_stage_a_context_in_prompt,
                        include_project_files=include_project_files_in_prompt,
                        retry_packet=retry_packet,
                        retry_prompt_mode=attempt_retry_prompt_mode,
                        retry_history=retry_history_text,
                        attempt=attempt,
                        known_evidence_refs=reused_evidence_ids,
                        new_evidence_ids=new_evidence_ids,
                        snapshot_lite_context=snapshot_lite_context,
                    )
                    prompt_budget_result = _apply_prompt_budget(
                        sections=prompt_sections,
                        budget=attempt_prompt_budget,
                        is_delta_retry=is_delta_retry,
                    )
                    include_stage_a_evidence_index = True
                    stage_a_index_reason = "context_truncated_fallback"

        # Snapshot-lite only counts if the final model prompt still carries that section.
        adaptive_snapshot_lite_included = bool(
            snapshot_lite_requested and "Compact snapshot index:\n" in prompt_budget_result.prompt
        )
        if helper_action_executed == ACTION_SNAPSHOT_LITE:
            context_action_materialized = adaptive_snapshot_lite_included
            helper_outcome = (
                ADAPTIVE_HELPER_OUTCOME_MATERIALIZED
                if adaptive_snapshot_lite_included
                else ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT
            )
            helper_failure_kind = ""

        last_context_compaction_requested = context_compaction_requested
        last_context_compaction_invoked = context_compaction_invoked
        if context_compaction_invoked:
            last_context_compaction_success = context_compaction_success
            last_context_compaction_trigger = context_compaction_trigger
            if context_compaction_input_chars > 0 and context_compaction_output_chars > 0:
                last_context_compaction_ratio = min(
                    1.0,
                    context_compaction_output_chars / float(max(1, context_compaction_input_chars)),
                )
            else:
                last_context_compaction_ratio = 0.0
        elif context_compaction_requested:
            last_context_compaction_success = False
            last_context_compaction_trigger = context_compaction_trigger
        if policy_compact_then_patch_requested:
            context_action_materialized = bool(context_compaction_success)
            helper_outcome = (
                ADAPTIVE_HELPER_OUTCOME_MATERIALIZED
                if context_compaction_success
                else ADAPTIVE_HELPER_OUTCOME_SEMANTIC_NO_CONTEXT
            )
            helper_failure_kind = ""
            helper_local_accounting = _adaptive_policy_local_accounting(
                llm_call_count=_llm_call_count_from_meta(
                    context_compaction_meta if context_compaction_invoked else None,
                    default=1 if (is_openai_patcher and context_compaction_invoked) else 0,
                ),
                tokens_prompt=_usage_token_counts_from_patch_meta(
                    context_compaction_meta if context_compaction_invoked else None
                )[0],
                tokens_completion=_usage_token_counts_from_patch_meta(
                    context_compaction_meta if context_compaction_invoked else None
                )[1],
                tokens_total=_usage_token_counts_from_patch_meta(
                    context_compaction_meta if context_compaction_invoked else None
                )[2],
                wall_sec=float(context_compaction_meta.get("compact_context_duration_sec") or 0.0)
                if context_compaction_invoked
                else 0.0,
            )
        elif helper_action_executed in {ACTION_SKIP_HELPER, ACTION_SNAPSHOT_LITE}:
            helper_local_accounting = _adaptive_policy_local_accounting()

        prompt = prompt_budget_result.prompt
        retry_history_budget_chars = int(attempt_prompt_budget.get("retry_history_chars", 0))
        retry_history_budget_applied = bool(
            retry_history_text
            and (
                retry_history_text not in prompt
                or (
                    retry_history_budget_chars > 0
                    and retry_history_chars > retry_history_budget_chars
                )
            )
        )
        attempt_metadata = _materialize_attempt_metadata(
            attempt_dir=attempt_dir,
            attempt=attempt,
            max_attempts=k,
            evidence=evidence,
            prompt=prompt,
            record_prompt=record_prompt,
            record_evidence=record_evidence,
            context_protocol=attempt_context_protocol,
            stage_a_prompt=stage_a_prompt,
            stage_a_raw_response=stage_a_data.request_raw,
            stage_a_request_payload=stage_a_data.request_payload,
            stage_a_requests_count=stage_a_data.requests_count,
            stage_a_chars_returned=stage_a_data.chars_returned,
            stage_a_request_success=stage_a_data.request_success,
            retry_prompt_mode=attempt_retry_prompt_mode,
            retry_context_mode=effective_retry_context_mode,
            retry_history_chars=retry_history_chars,
            retry_history_truncated=retry_history_truncated,
            retry_history_turns_included=retry_history_turns_included,
            retry_history_budget_chars=retry_history_budget_chars,
            retry_history_budget_applied=retry_history_budget_applied,
            retry_delta_prompt=is_delta_retry,
            retry_delta_chars=retry_delta_chars,
            adaptive_mode=helper_decision_mode,
            adaptive_policy=adaptive_policy_label,
            adaptive_signature_repeat=failure_signature_repeat,
            adaptive_stagnation_streak=stagnation_streak,
            adaptive_helper_invoked=helper_invoked,
            adaptive_helper_action_default=helper_action_default,
            adaptive_helper_action_selected=helper_action_selected,
            adaptive_helper_action_executed=helper_action_executed,
            adaptive_helper_action_fallback_reason=helper_action_fallback_reason,
            adaptive_helper_materialized_context=helper_materialized_context,
            adaptive_helper_outcome=helper_outcome,
            adaptive_helper_failure_kind=helper_failure_kind,
            adaptive_helper_retry_count=helper_retry_count,
            adaptive_helper_transport_retry_count=helper_transport_retry_count,
            adaptive_helper_decision_reason=helper_decision_reason,
            adaptive_stagnation_override_applied=adaptive_stagnation_override_applied,
            adaptive_stagnation_override_reason=adaptive_stagnation_override_reason,
            adaptive_helper_need_more_context=helper_need_more_context,
            adaptive_helper_calls_used_before_attempt=max(
                0,
                helper_calls_used - (1 if (is_adaptive_condition and helper_invoked) else 0),
            ),
            adaptive_helper_calls_used_after_attempt=helper_calls_used,
            adaptive_helper_budget_max_calls=budget_policy_applied_profile.helper_max_calls,
            adaptive_helper_budget_unlimited=adaptive_budget_unlimited,
            adaptive_helper_budget_exhausted=helper_budget_exhausted,
            adaptive_helper_saturation_guarded=helper_saturation_guarded,
            adaptive_helper_budget_profile=normalized_adaptive_helper_budget_profile,
            adaptive_budget_topup_granted=adaptive_budget_topup_granted,
            adaptive_budget_topup_reason=adaptive_budget_topup_reason,
            adaptive_context_requested=adaptive_context_requested,
            adaptive_snapshot_included=adaptive_snapshot_included,
            adaptive_snapshot_lite_included=adaptive_snapshot_lite_included,
            adaptive_snapshot_attempt1_denied=adaptive_snapshot_attempt1_denied,
            adaptive_snapshot_attempt1_denied_reason=adaptive_snapshot_attempt1_denied_reason,
            adaptive_progress_made=adaptive_progress_made,
            adaptive_progress_reason=adaptive_progress_reason,
            adaptive_no_progress_streak=adaptive_no_progress_streak_current,
            adaptive_progress_stop_candidate=adaptive_progress_stop_candidate,
            adaptive_progress_stop_triggered=False,
            adaptive_progress_stop_reason=adaptive_progress_stop_reason,
            llm_call_count=0,
            tokens_prompt=0,
            tokens_completion=0,
            tokens_total=0,
            evidence_ids_new_count=len(new_evidence_ids),
            evidence_ids_reused_count=len(reused_evidence_ids),
            evidence_ids_sent_count=len(_evidence_ids_from_items(evidence_items_for_prompt)),
            prompt_budget_used_chars=prompt_budget_result.used_chars,
            prompt_budget_overflow_chars=prompt_budget_result.overflow_chars,
            prompt_budget_dropped_sections=prompt_budget_result.dropped_sections,
            stage_a_index_included=bool(include_stage_a_evidence_index),
            stage_a_index_reason=stage_a_index_reason,
            context_compaction_mode=normalized_context_compaction_mode,
            context_compaction_scope=sorted(normalized_context_compaction_scope),
            context_compaction_supported=supports_context_compaction,
            context_compaction_requested=context_compaction_requested,
            context_compaction_request_reason=context_compaction_request_reason,
            context_compaction_trigger=context_compaction_trigger,
            context_compaction_auto_overflow_triggered=context_compaction_auto_overflow_triggered,
            context_compaction_invoked=context_compaction_invoked,
            context_compaction_success=context_compaction_success,
            context_compaction_error=context_compaction_error,
            context_compaction_calls_used=context_compaction_calls_used,
            context_compaction_max_calls=effective_context_compaction_max_calls,
            context_compaction_source_sections=context_compaction_source_sections,
            context_compaction_source_chars=context_compaction_source_chars,
            context_compaction_replaced_sections=context_compaction_replaced_sections,
            context_compaction_input_chars=context_compaction_input_chars,
            context_compaction_target_chars=normalized_context_compaction_target_chars,
            context_compaction_output_chars=context_compaction_output_chars,
            context_compaction_prompt_chars=context_compaction_prompt_chars,
            openai_preflight_prompt_cap_chars=openai_preflight_prompt_cap_chars,
            openai_preflight_trimmed=openai_preflight_trimmed,
            openai_context_window_source=normalized_openai_context_window_source,
            openai_context_window_probe_provider=normalized_openai_context_window_probe_provider,
            openai_context_window_probe_error=normalized_openai_context_window_probe_error,
            openai_response_mode_autoswitch_policy=normalized_openai_response_mode_autoswitch,
            openai_response_mode_autoswitched=False,
            openai_response_mode_autoswitch_from="",
            openai_response_mode_autoswitch_to="",
            openai_response_mode_autoswitch_cycle=0,
            openai_response_mode_autoswitch_reason="",
            adaptive_informativeness_pregate_fired=pregate_result.should_skip,
            adaptive_informativeness_pregate_score=pregate_result.score,
            adaptive_informativeness_pregate_exception_type=pregate_result.exception_type,
            adaptive_informativeness_pregate_reason=pregate_result.reason,
            adaptive_informativeness_pregate_features={
                "type_specificity": pregate_result.f_type_specificity,
                "message_diagnostic": pregate_result.f_message_diagnostic,
                "crash_in_user_code": pregate_result.f_crash_in_user_code,
                "assertion_quality": pregate_result.f_assertion_quality,
                "frame_depth": pregate_result.f_frame_depth,
            },
            syntax_preflight_enabled=normalized_syntax_preflight_enabled,
            syntax_preflight_cycles_used=0,
            syntax_preflight_failures=0,
            syntax_preflight_last_error_type="",
            syntax_preflight_last_error_msg="",
        )

        patch_meta: dict[str, Any] = {}
        patch_meta["context_protocol"] = attempt_context_protocol
        patch_meta["evidence_fingerprint"] = evidence_fingerprint
        patch_meta["retry_prompt_mode"] = attempt_retry_prompt_mode
        patch_meta["retry_context_mode"] = effective_retry_context_mode
        patch_meta["retry_history_chars"] = retry_history_chars
        patch_meta["retry_history_truncated"] = retry_history_truncated
        patch_meta["retry_history_turns_included"] = retry_history_turns_included
        patch_meta["retry_history_budget_chars"] = retry_history_budget_chars
        patch_meta["retry_history_budget_applied"] = retry_history_budget_applied
        patch_meta["retry_delta_prompt"] = is_delta_retry
        patch_meta["retry_delta_chars"] = retry_delta_chars
        patch_meta["evidence_ids_new_count"] = len(new_evidence_ids)
        patch_meta["evidence_ids_reused_count"] = len(reused_evidence_ids)
        patch_meta["evidence_ids_sent_count"] = len(
            _evidence_ids_from_items(evidence_items_for_prompt)
        )
        patch_meta["evidence_ids_new"] = new_evidence_ids[:20]
        patch_meta["evidence_ids_reused"] = reused_evidence_ids[:20]
        patch_meta["prompt_budget_used_chars"] = prompt_budget_result.used_chars
        patch_meta["prompt_budget_overflow_chars"] = prompt_budget_result.overflow_chars
        patch_meta["prompt_budget_dropped_sections"] = list(prompt_budget_result.dropped_sections)
        patch_meta["stage_a_index_included"] = bool(include_stage_a_evidence_index)
        patch_meta["stage_a_index_reason"] = stage_a_index_reason
        patch_meta["context_compaction_mode"] = normalized_context_compaction_mode
        patch_meta["context_compaction_scope"] = sorted(normalized_context_compaction_scope)
        patch_meta["context_compaction_supported"] = supports_context_compaction
        patch_meta["context_compaction_requested"] = context_compaction_requested
        patch_meta["context_compaction_request_reason"] = context_compaction_request_reason
        patch_meta["context_compaction_trigger"] = context_compaction_trigger
        patch_meta["context_compaction_auto_overflow_triggered"] = (
            context_compaction_auto_overflow_triggered
        )
        patch_meta["context_compaction_invoked"] = context_compaction_invoked
        patch_meta["context_compaction_success"] = context_compaction_success
        patch_meta["context_compaction_error"] = context_compaction_error
        patch_meta["context_compaction_calls_used"] = context_compaction_calls_used
        patch_meta["context_compaction_max_calls"] = effective_context_compaction_max_calls
        patch_meta["context_compaction_source_sections"] = list(context_compaction_source_sections)
        patch_meta["context_compaction_source_chars"] = dict(context_compaction_source_chars)
        patch_meta["context_compaction_replaced_sections"] = list(
            context_compaction_replaced_sections
        )
        patch_meta["context_compaction_input_chars"] = context_compaction_input_chars
        patch_meta["context_compaction_target_chars"] = normalized_context_compaction_target_chars
        patch_meta["context_compaction_output_chars"] = context_compaction_output_chars
        patch_meta["context_compaction_prompt_chars"] = context_compaction_prompt_chars
        if context_compaction_meta:
            patch_meta["context_compaction_meta"] = _summarize_context_compaction_meta(
                context_compaction_meta
            )
        patch_meta["openai_preflight_prompt_cap_chars"] = openai_preflight_prompt_cap_chars
        patch_meta["openai_preflight_trimmed"] = openai_preflight_trimmed
        patch_meta["openai_context_window_source"] = normalized_openai_context_window_source
        patch_meta["openai_context_window_probe_provider"] = (
            normalized_openai_context_window_probe_provider
        )
        patch_meta["openai_context_window_probe_error"] = (
            normalized_openai_context_window_probe_error
        )
        patch_meta["adaptive_mode"] = helper_decision_mode
        patch_meta["adaptive_policy"] = adaptive_policy_label
        patch_meta["adaptive_signature_repeat"] = failure_signature_repeat
        patch_meta["adaptive_stagnation_streak"] = stagnation_streak
        patch_meta["adaptive_helper_invoked"] = helper_invoked
        patch_meta["adaptive_helper_action_default"] = helper_action_default
        patch_meta["adaptive_helper_action_selected"] = helper_action_selected
        patch_meta["adaptive_helper_action_executed"] = helper_action_executed
        patch_meta["adaptive_helper_action_fallback_reason"] = helper_action_fallback_reason
        patch_meta["adaptive_helper_materialized_context"] = helper_materialized_context
        patch_meta["adaptive_helper_outcome"] = helper_outcome
        patch_meta["adaptive_helper_failure_kind"] = helper_failure_kind
        patch_meta["adaptive_helper_retry_count"] = helper_retry_count
        patch_meta["adaptive_helper_transport_retry_count"] = helper_transport_retry_count
        patch_meta["adaptive_helper_decision_reason"] = helper_decision_reason
        patch_meta["adaptive_stagnation_override_applied"] = adaptive_stagnation_override_applied
        patch_meta["adaptive_stagnation_override_reason"] = adaptive_stagnation_override_reason
        patch_meta["adaptive_helper_need_more_context"] = helper_need_more_context
        patch_meta["adaptive_helper_calls_used_before_attempt"] = max(
            0,
            helper_calls_used - (1 if (is_adaptive_condition and helper_invoked) else 0),
        )
        patch_meta["adaptive_helper_calls_used_after_attempt"] = helper_calls_used
        patch_meta["adaptive_helper_budget_max_calls"] = (
            budget_policy_applied_profile.helper_max_calls
        )
        patch_meta["adaptive_helper_budget_unlimited"] = adaptive_budget_unlimited
        patch_meta["adaptive_helper_budget_exhausted"] = helper_budget_exhausted
        patch_meta["adaptive_helper_saturation_guarded"] = helper_saturation_guarded
        patch_meta["adaptive_saturation_threshold"] = normalized_adaptive_saturation_threshold
        patch_meta["adaptive_helper_budget_profile"] = normalized_adaptive_helper_budget_profile
        patch_meta["adaptive_budget_topup_granted"] = adaptive_budget_topup_granted
        patch_meta["adaptive_budget_topup_reason"] = adaptive_budget_topup_reason
        patch_meta["adaptive_informativeness_pregate_fired"] = pregate_result.should_skip
        patch_meta["adaptive_informativeness_pregate_score"] = pregate_result.score
        patch_meta["adaptive_informativeness_pregate_exception_type"] = (
            pregate_result.exception_type
        )
        patch_meta["adaptive_informativeness_pregate_reason"] = pregate_result.reason
        patch_meta["adaptive_informativeness_pregate_features"] = {
            "type_specificity": pregate_result.f_type_specificity,
            "message_diagnostic": pregate_result.f_message_diagnostic,
            "crash_in_user_code": pregate_result.f_crash_in_user_code,
            "assertion_quality": pregate_result.f_assertion_quality,
            "frame_depth": pregate_result.f_frame_depth,
        }
        patch_meta["adaptive_context_requested"] = adaptive_context_requested
        patch_meta["adaptive_snapshot_included"] = adaptive_snapshot_included
        patch_meta["adaptive_snapshot_lite_included"] = adaptive_snapshot_lite_included
        patch_meta["adaptive_snapshot_attempt1_denied"] = adaptive_snapshot_attempt1_denied
        patch_meta["adaptive_snapshot_attempt1_denied_reason"] = (
            adaptive_snapshot_attempt1_denied_reason
        )
        if is_adaptive_condition:
            pre_attempt_progress_note = (
                str(previous_failure_context.get("note") or "tests_still_failing")
                if isinstance(previous_failure_context, dict)
                else "tests_still_failing"
            )
            progress_state, adaptive_no_progress_streak_current = (
                _recompute_adaptive_progress_for_attempt_outcome(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    note=pre_attempt_progress_note,
                    success=False,
                    signature_repeated=True,
                    new_evidence_count=len(new_evidence_ids),
                    before_run=current,
                    after_run=None,
                    previous_failure_context=previous_failure_context,
                    current_no_progress_streak=adaptive_no_progress_streak + 1,
                    recomputed_after_patch=False,
                )
            )
            adaptive_progress_made = bool(progress_state.made)
            adaptive_progress_reason = str(progress_state.reason)
            adaptive_progress_score = float(progress_state.score)
            adaptive_no_progress_streak = (
                0 if adaptive_progress_made else adaptive_no_progress_streak_current
            )
            (
                adaptive_progress_stop_candidate,
                adaptive_progress_stop_reason,
            ) = _adaptive_should_stop_for_no_progress(
                no_progress_streak=adaptive_no_progress_streak_current,
                stop_window=adaptive_no_progress_stop_window,
                helper_budget_exhausted=helper_budget_exhausted,
                helper_saturation_guarded=helper_saturation_guarded,
                helper_decision_reason=helper_decision_reason,
            )
        else:
            _sync_adaptive_progress_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                progress_state=AdaptiveProgressState(
                    made=False,
                    reason="not_adaptive_condition",
                    score=0.0,
                    component_signature=0.0,
                    component_new_evidence=0.0,
                    component_pytest_fail_reduction=0.0,
                    component_stage_advance=0.0,
                    component_verifier_score=0.0,
                    component_scope_alignment=0.0,
                    pytest_before_failed_total=None,
                    pytest_after_failed_total=None,
                    stage_before="",
                    stage_after="",
                    stage_before_ord=0,
                    stage_after_ord=0,
                ),
                no_progress_streak=0,
                recomputed_after_patch=False,
            )
        patch_meta["adaptive_progress_made"] = adaptive_progress_made
        patch_meta["adaptive_progress_reason"] = adaptive_progress_reason
        patch_meta["adaptive_no_progress_streak"] = adaptive_no_progress_streak_current
        patch_meta["adaptive_progress_stop_candidate"] = adaptive_progress_stop_candidate
        patch_meta["adaptive_progress_stop_triggered"] = False
        patch_meta["adaptive_progress_stop_reason"] = adaptive_progress_stop_reason
        patch_meta["patch_file_count"] = 0
        patch_meta["patch_test_file_count"] = 0
        patch_meta["patch_non_test_file_count"] = 0
        patch_meta["patch_touches_tests"] = False
        patch_meta["patch_file_paths"] = []
        patch_meta["patch_test_file_paths"] = []
        patch_meta["patch_verifier_enabled"] = False
        patch_meta["patch_verifier_scope"] = PATCH_VERIFIER_SCOPE_DISABLED
        patch_meta["patch_verifier_passed"] = None
        patch_meta["patch_verifier_score"] = None
        patch_meta["patch_verifier_reasons"] = []
        patch_meta["patch_verifier_inferred_source_scope"] = []
        patch_meta["patch_verifier_source_scope_matched"] = False
        patch_meta["patch_verifier_recovery_eligible"] = False
        patch_meta["patch_verifier_recovery_invoked"] = False
        patch_meta["patch_verifier_recovery_result"] = PATCH_VERIFIER_RECOVERY_RESULT_NOT_ELIGIBLE
        patch_meta["patch_verifier_recovery_prompt_chars_before"] = 0
        patch_meta["patch_verifier_recovery_prompt_chars_after"] = 0
        patch_meta["patch_verifier_recovery_duration_sec"] = 0.0
        patch_meta["patch_verifier_recovery_terminal_note"] = ""
        patch_meta["patch_verifier_recovery_score_before"] = None
        patch_meta["patch_verifier_recovery_reasons_before"] = []
        patch_meta["patch_verifier_recovery_patch_files_before"] = []
        patch_meta["patch_verifier_recovery_source_scope_matched_before"] = False
        patch_meta["patch_verifier_recovery_raw_meta"] = None
        patch_meta["syntax_preflight_enabled"] = normalized_syntax_preflight_enabled
        patch_meta["syntax_preflight_cycles_used"] = 0
        patch_meta["syntax_preflight_failures"] = 0
        patch_meta["syntax_preflight_last_error_type"] = ""
        patch_meta["syntax_preflight_last_error_msg"] = ""
        patch_meta["syntax_preflight_events"] = []
        _sync_openai_response_mode_autoswitch_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            policy=normalized_openai_response_mode_autoswitch,
            switched=False,
            from_mode="",
            to_mode="",
            cycle=0,
            reason="",
        )
        _sync_patcher_request_failure_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            failed=False,
        )
        _sync_patch_sanitize_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            enabled=normalized_patch_sanitize_enabled,
            passed=False,
            rejected=False,
            reasons=[],
            error_msg="",
        )
        if attempt_context_protocol == "staged" or stage_a_request_invoked:
            patch_meta["stage_a_request_success"] = stage_a_data.request_success
            patch_meta["stage_a_used_fallback"] = stage_a_data.used_fallback
            patch_meta["stage_a_reason"] = stage_a_data.reason
            patch_meta["stage_a_requests_count"] = stage_a_data.requests_count
            patch_meta["stage_a_chars_returned"] = stage_a_data.chars_returned
            if stage_a_request_payload:
                patch_meta["stage_a_diagnosis"] = str(stage_a_request_payload.get("diagnosis", ""))
                patch_meta["stage_a_missing_fact"] = stage_a_request_payload.get("missing_fact")
                patch_meta["stage_a_need_compact_context"] = bool(
                    stage_a_request_payload.get("need_compact_context")
                )
                patch_meta["stage_a_compact_context_reason"] = str(
                    stage_a_request_payload.get("compact_context_reason") or ""
                )
            if stage_a_meta:
                patch_meta["stage_a_meta"] = stage_a_meta
        (
            attempt_llm_calls,
            attempt_tokens_prompt,
            attempt_tokens_completion,
            attempt_tokens_total,
        ) = _aggregate_attempt_llm_usage(
            stage_a_meta=stage_a_meta,
            patch_meta=None,
            stage_a_default_llm_calls=stage_a_default_llm_calls,
            patch_default_llm_calls=0,
        )
        if context_compaction_invoked:
            (
                compaction_llm_calls,
                compaction_tokens_prompt,
                compaction_tokens_completion,
                compaction_tokens_total,
            ) = _aggregate_attempt_llm_usage(
                stage_a_meta=None,
                patch_meta=context_compaction_meta if context_compaction_meta else None,
                stage_a_default_llm_calls=0,
                patch_default_llm_calls=1 if is_openai_patcher else 0,
            )
            attempt_llm_calls += compaction_llm_calls
            attempt_tokens_prompt += compaction_tokens_prompt
            attempt_tokens_completion += compaction_tokens_completion
            attempt_tokens_total += compaction_tokens_total
        _apply_usage_totals_to_patch_meta(
            patch_meta=patch_meta,
            llm_call_count=attempt_llm_calls,
            tokens_prompt=attempt_tokens_prompt,
            tokens_completion=attempt_tokens_completion,
            tokens_total=attempt_tokens_total,
        )
        _sync_attempt_usage_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            llm_call_count=attempt_llm_calls,
        )
        _sync_adaptive_online_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            mode=online_policy_mode_for_attempt,
            algo=online_policy_algo_for_attempt,
            decision=online_policy_decision,
            eligible=online_policy_eligible,
            override_reason=online_policy_override_reason,
            feature_scaling_profile=normalized_adaptive_online_feature_scaling_profile,
            feature_values_raw=online_policy_features_raw,
            feature_values_scaled=online_policy_features_scaled,
            reward_lambda_tokens=normalized_adaptive_reward_lambda_tokens,
            reward_mu_latency=normalized_adaptive_reward_mu_latency,
            reward_token_scale=normalized_adaptive_reward_token_scale,
            reward_latency_scale=normalized_adaptive_reward_latency_scale,
            reward_eta_progress=normalized_adaptive_reward_eta_progress,
            reward_kappa_stagnation=normalized_adaptive_reward_kappa_stagnation,
            local_accounting=helper_local_accounting,
            behavior_action=str(attempt_metadata.get("adaptive_helper_action_executed") or "none"),
        )
        _sync_adaptive_budget_policy_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            mode=budget_policy_mode_for_attempt,
            algo=budget_policy_algo_for_attempt,
            decision=budget_policy_decision,
            eligible=budget_policy_eligible,
            override_reason=budget_policy_override_reason,
            feature_scaling_profile=normalized_adaptive_budget_feature_scaling_profile,
            feature_values_raw=budget_policy_features_raw,
            feature_values_scaled=budget_policy_features_scaled,
            tiers=normalized_adaptive_budget_policy_tiers,
            tier_index_before=budget_policy_tier_index_before,
            tier_index_after=budget_policy_tier_index_after,
            selected_multiplier=budget_policy_selected_multiplier,
            applied_multiplier=budget_policy_applied_multiplier,
            default_multiplier=budget_policy_default_multiplier,
            default_controls=budget_policy_default_controls,
            selected_controls=budget_policy_selected_controls,
            applied_controls=budget_policy_applied_controls,
            selected_controls_changed=budget_policy_selected_controls_changed,
            applied_controls_changed=budget_policy_applied_controls_changed,
            partial_override=budget_policy_partial_override,
            partial_override_errors=budget_policy_partial_override_errors,
            max_ups_per_case=normalized_adaptive_budget_policy_max_ups_per_case,
            ups_used=adaptive_budget_policy_ups_used,
            reward_lambda_tokens=normalized_adaptive_reward_lambda_tokens,
            reward_mu_latency=normalized_adaptive_reward_mu_latency,
            reward_token_scale=normalized_adaptive_reward_token_scale,
            reward_latency_scale=normalized_adaptive_reward_latency_scale,
            reward_rho_budget_exceeded=normalized_adaptive_budget_reward_rho_budget_exceeded,
            reward_eta_progress=normalized_adaptive_reward_eta_progress,
            reward_kappa_stagnation=normalized_adaptive_reward_kappa_stagnation,
            local_accounting=_adaptive_policy_local_accounting(),
        )
        online_finalize_kwargs = {
            "is_adaptive_llm_discretion": is_adaptive_llm_discretion,
            "online_policy_decision": online_policy_decision,
            "adaptive_online_policy_controller": adaptive_online_policy_controller,
            "run_id": run_id,
            "case_id": case.case_id,
            "condition_value": condition.value,
            "attempt": attempt,
            "attempt_metadata": attempt_metadata,
            "patch_meta": patch_meta,
            "online_policy_mode_for_attempt": online_policy_mode_for_attempt,
            "online_policy_algo_for_attempt": online_policy_algo_for_attempt,
            "online_policy_eligible": online_policy_eligible,
            "online_policy_override_reason": online_policy_override_reason,
            "online_policy_feature_scaling_profile": (
                normalized_adaptive_online_feature_scaling_profile
            ),
            "online_policy_features_raw": online_policy_features_raw,
            "online_policy_features_scaled": online_policy_features_scaled,
            "helper_decision_reason": helper_decision_reason,
            "behavior_action": str(
                attempt_metadata.get("adaptive_helper_action_executed") or "none"
            ),
            "normalized_adaptive_online_policy_mode": normalized_adaptive_online_policy_mode,
            "case_difficulty": case.difficulty,
            "case_snapshot_dependency": case.snapshot_dependency,
            "adaptive_progress_made": adaptive_progress_made,
            "adaptive_progress_score": adaptive_progress_score,
            "adaptive_no_progress_streak_current": adaptive_no_progress_streak_current,
            "reward_lambda_tokens": normalized_adaptive_reward_lambda_tokens,
            "reward_mu_latency": normalized_adaptive_reward_mu_latency,
            "reward_token_scale": normalized_adaptive_reward_token_scale,
            "reward_latency_scale": normalized_adaptive_reward_latency_scale,
            "reward_eta_progress": normalized_adaptive_reward_eta_progress,
            "reward_kappa_stagnation": normalized_adaptive_reward_kappa_stagnation,
            "local_accounting": helper_local_accounting,
            "context_materialized": context_action_materialized,
            "helper_outcome": helper_outcome,
        }
        budget_finalize_kwargs = {
            "is_adaptive_condition": is_adaptive_condition,
            "budget_policy_decision": budget_policy_decision,
            "adaptive_budget_policy_controller": adaptive_budget_policy_controller,
            "run_id": run_id,
            "case_id": case.case_id,
            "condition_value": condition.value,
            "attempt": attempt,
            "attempt_metadata": attempt_metadata,
            "patch_meta": patch_meta,
            "budget_policy_mode_for_attempt": budget_policy_mode_for_attempt,
            "budget_policy_algo_for_attempt": budget_policy_algo_for_attempt,
            "budget_policy_eligible": budget_policy_eligible,
            "budget_policy_override_reason": budget_policy_override_reason,
            "budget_policy_feature_scaling_profile": (
                normalized_adaptive_budget_feature_scaling_profile
            ),
            "budget_policy_features_raw": budget_policy_features_raw,
            "budget_policy_features_scaled": budget_policy_features_scaled,
            "normalized_adaptive_budget_policy_mode": normalized_adaptive_budget_policy_mode,
            "adaptive_progress_made": adaptive_progress_made,
            "adaptive_progress_score": adaptive_progress_score,
            "adaptive_no_progress_streak_current": adaptive_no_progress_streak_current,
            "reward_lambda_tokens": normalized_adaptive_reward_lambda_tokens,
            "reward_mu_latency": normalized_adaptive_reward_mu_latency,
            "reward_token_scale": normalized_adaptive_reward_token_scale,
            "reward_latency_scale": normalized_adaptive_reward_latency_scale,
            "reward_eta_progress": normalized_adaptive_reward_eta_progress,
            "reward_kappa_stagnation": normalized_adaptive_reward_kappa_stagnation,
            "reward_rho_budget_exceeded": normalized_adaptive_budget_reward_rho_budget_exceeded,
            "local_accounting": _adaptive_policy_local_accounting(),
            "tiers": normalized_adaptive_budget_policy_tiers,
            "tier_index_before": budget_policy_tier_index_before,
            "tier_index_after": budget_policy_tier_index_after,
            "selected_multiplier": budget_policy_selected_multiplier,
            "applied_multiplier": budget_policy_applied_multiplier,
            "default_multiplier": budget_policy_default_multiplier,
            "default_controls": budget_policy_default_controls,
            "selected_controls": budget_policy_selected_controls,
            "applied_controls": budget_policy_applied_controls,
            "selected_controls_changed": budget_policy_selected_controls_changed,
            "applied_controls_changed": budget_policy_applied_controls_changed,
            "partial_override": budget_policy_partial_override,
            "partial_override_errors": budget_policy_partial_override_errors,
            "max_ups_per_case": normalized_adaptive_budget_policy_max_ups_per_case,
            "ups_used": adaptive_budget_policy_ups_used,
        }

        if (
            is_openai_patcher
            and is_adaptive_condition
            and helper_materialized_context
            and normalized_adaptive_openai_helper_response_mode != "inherit"
        ):
            target_mode = normalized_adaptive_openai_helper_response_mode
            current_mode = str(getattr(patcher_for_attempt, "response_mode", "")).strip()
            if target_mode and target_mode != current_mode:
                try:
                    patcher_for_attempt = replace(patcher_for_attempt, response_mode=target_mode)
                    patch_meta["adaptive_openai_response_mode_override"] = target_mode
                except Exception as exc:
                    patch_meta["adaptive_openai_response_mode_override_error"] = (
                        f"{type(exc).__name__}: {exc}"
                    )
        if budget_policy_partial_override_errors.get("max_propose_sec"):
            patch_meta["adaptive_budget_propose_budget_override_error"] = (
                budget_policy_partial_override_errors["max_propose_sec"]
            )
        if is_openai_patcher:
            attempt_metadata["attempt_shared_propose_budget_base_sec"] = float(
                budget_policy_base_propose_budget_sec
            )
            patch_meta["attempt_shared_propose_budget_base_sec"] = float(
                budget_policy_base_propose_budget_sec
            )
            attempt_metadata["attempt_shared_propose_budget_multiplier"] = float(
                budget_policy_applied_multiplier
            )
            patch_meta["attempt_shared_propose_budget_multiplier"] = float(
                budget_policy_applied_multiplier
            )
        else:
            attempt_metadata["attempt_shared_propose_budget_base_sec"] = 0.0
            patch_meta["attempt_shared_propose_budget_base_sec"] = 0.0
            attempt_metadata["attempt_shared_propose_budget_multiplier"] = 1.0
            patch_meta["attempt_shared_propose_budget_multiplier"] = 1.0

        budget_finalize_kwargs["budget_policy_decision"] = budget_policy_decision
        budget_finalize_kwargs["budget_policy_override_reason"] = budget_policy_override_reason
        budget_finalize_kwargs["tier_index_after"] = budget_policy_tier_index_after
        budget_finalize_kwargs["applied_multiplier"] = budget_policy_applied_multiplier
        budget_finalize_kwargs["default_controls"] = budget_policy_default_controls
        budget_finalize_kwargs["selected_controls"] = budget_policy_selected_controls
        budget_finalize_kwargs["applied_controls"] = budget_policy_applied_controls
        budget_finalize_kwargs["selected_controls_changed"] = (
            budget_policy_selected_controls_changed
        )
        budget_finalize_kwargs["applied_controls_changed"] = budget_policy_applied_controls_changed
        budget_finalize_kwargs["partial_override"] = budget_policy_partial_override
        budget_finalize_kwargs["partial_override_errors"] = budget_policy_partial_override_errors
        budget_finalize_kwargs["ups_used"] = adaptive_budget_policy_ups_used

        if is_structured_condition and bool(evidence.get("failure_signature_repeat")):
            patch_meta["retry_gate_evaluated"] = True
            patch_meta["retry_gate_blocked"] = False
            patch_meta["retry_gate_reason"] = "new_evidence_present"
            prev_evidence_fingerprint = ""
            if isinstance(previous_failure_context, dict):
                prev_evidence_fingerprint = str(
                    previous_failure_context.get("evidence_fingerprint") or ""
                )
            if prev_evidence_fingerprint and prev_evidence_fingerprint == evidence_fingerprint:
                patch_meta["retry_gate_blocked"] = True
                patch_meta["retry_gate_reason"] = "same_signature_without_new_evidence"
                patch_meta["retry_gate_previous_evidence_fingerprint"] = prev_evidence_fingerprint
                attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
                cumulative_runtime_sec += attempt_wall_sec
                _refresh_adaptive_progress_for_note(
                    note="retry_gate_no_new_evidence",
                    success=False,
                    signature_repeated=True,
                )
                pending_credit = _finalize_adaptive_policy_attempt(
                    note="retry_gate_no_new_evidence",
                    success=False,
                    attempt_wall_sec=attempt_wall_sec,
                    online_finalize_kwargs=online_finalize_kwargs,
                    budget_finalize_kwargs=budget_finalize_kwargs,
                )
                record = _build_attempt_outcome_record(
                    case=case,
                    condition=condition,
                    run_id=run_id,
                    attempt=attempt,
                    patcher_name=patcher_name,
                    run_metadata=effective_run_metadata,
                    attempt_metadata=attempt_metadata,
                    patcher=patcher_for_attempt,
                    patch_meta=patch_meta,
                    context_protocol=attempt_context_protocol,
                    success=False,
                    note="retry_gate_no_new_evidence",
                    before=current,
                    cumulative_runtime_sec=cumulative_runtime_sec,
                    attempt_wall_clock_sec=attempt_wall_sec,
                )
                _queue_pending_online_credit(pending_credit=pending_credit, record=record)
                _append_record(record)
                _append_retry_history_turn_if_enabled(
                    retry_context_mode=effective_retry_context_mode,
                    retry_history_turns=retry_history_turns,
                    attempt=attempt,
                    note="retry_gate_no_new_evidence",
                    failure_signature=failure_signature,
                    patch_meta=patch_meta,
                )
                previous_failure_context = _enrich_failure_context(
                    base=current_failure_context,
                    note="retry_gate_no_new_evidence",
                    patch_meta=patch_meta,
                    run=current,
                )
                continue

        syntax_preflight_cycles_used = 0
        syntax_preflight_failures = 0
        syntax_preflight_last_error_type = ""
        syntax_preflight_last_error_msg = ""
        syntax_preflight_events: list[dict[str, Any]] = []
        syntax_preflight_max_cycles_for_attempt = (
            normalized_syntax_preflight_max_cycles if normalized_syntax_preflight_enabled else 1
        )
        syntax_retry_feedback = ""
        attempt_shared_propose_budget_sec = 0.0
        attempt_shared_propose_deadline_monotonic: float | None = None
        if is_openai_patcher:
            raw_attempt_budget_sec = getattr(patcher_for_attempt, "max_propose_sec", 0.0)
            try:
                attempt_shared_propose_budget_sec = max(0.0, float(raw_attempt_budget_sec))
            except (TypeError, ValueError):
                attempt_shared_propose_budget_sec = 0.0
            if attempt_shared_propose_budget_sec > 0:
                attempt_shared_propose_deadline_monotonic = (
                    time.monotonic() + attempt_shared_propose_budget_sec
                )
        attempt_metadata["attempt_shared_propose_budget_sec"] = attempt_shared_propose_budget_sec
        patch_meta["attempt_shared_propose_budget_sec"] = attempt_shared_propose_budget_sec
        attempt_llm_calls_cumulative = _nonnegative_int(
            attempt_metadata.get("llm_call_count"), default=0
        )
        attempt_tokens_prompt_cumulative = _nonnegative_int(
            attempt_metadata.get("tokens_prompt"), default=0
        )
        attempt_tokens_completion_cumulative = _nonnegative_int(
            attempt_metadata.get("tokens_completion"), default=0
        )
        attempt_tokens_total_cumulative = _nonnegative_int(
            attempt_metadata.get("tokens_total"), default=0
        )
        budget_local_llm_calls = 0
        budget_local_tokens_prompt = 0
        budget_local_tokens_completion = 0
        budget_local_tokens_total = 0
        budget_local_wall_sec = 0.0
        diff_to_apply = ""
        context_recovery_invoked = False
        attempt_terminated_early = False
        should_break_after_attempt = False
        patch_applied_and_ready = False

        _sync_syntax_preflight_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            enabled=normalized_syntax_preflight_enabled,
            cycles_used=syntax_preflight_cycles_used,
            failures=syntax_preflight_failures,
            last_error_type=syntax_preflight_last_error_type,
            last_error_msg=syntax_preflight_last_error_msg,
            events=syntax_preflight_events,
        )
        for syntax_cycle in range(1, syntax_preflight_max_cycles_for_attempt + 1):
            syntax_preflight_cycles_used = syntax_cycle
            _emit_progress(
                "attempt_propose_patch",
                {
                    "attempt": int(attempt),
                    "max_attempts": int(k),
                    "syntax_cycle": int(syntax_cycle),
                    "syntax_max_cycles": int(syntax_preflight_max_cycles_for_attempt),
                },
            )
            cycle_prompt = prompt + syntax_retry_feedback
            patch_start = time.perf_counter()
            patch = _propose_patch_with_snapshot_context(
                patcher=patcher_for_attempt,
                prompt=cycle_prompt,
                context_dir=attempt_dir,
                cwd=workdir,
                snapshot_full_text=snapshot_full_text,
                snapshot_artifact_text=snapshot_artifact_text,
                attempt_deadline_monotonic=attempt_shared_propose_deadline_monotonic,
            )
            patch_duration_sec = time.perf_counter() - patch_start
            patch_usage_meta: dict[str, Any] | None = None
            for key in (
                "llm_call_count",
                "tokens_prompt",
                "tokens_completion",
                "tokens_total",
                "usage",
            ):
                patch_meta.pop(key, None)
            for key in (
                "patch_diff_sha256",
                "patch_diff_chars",
                "patch_file_count",
                "patch_test_file_count",
                "patch_non_test_file_count",
                "patch_touches_tests",
                "patch_file_paths",
                "patch_test_file_paths",
                "patch_verifier_passed",
                "patch_verifier_score",
                "patch_verifier_reasons",
                "patch_verifier_patch_files",
                "patch_verifier_crash_file",
                "patch_verifier_crash_file_matched",
                "patch_verifier_crash_neighbor_matched",
                "patch_verifier_crash_neighbors",
                "patch_verifier_inferred_source_scope",
                "patch_verifier_source_scope_matched",
                "patch_verifier_failing_test_file",
                "patch_verifier_touches_test_file",
                "patch_verifier_touches_failing_test_file",
                "patch_verifier_substantive_change",
                "patch_verifier_recovery_eligible",
                "patch_verifier_recovery_invoked",
                "patch_verifier_recovery_result",
                "patch_verifier_recovery_prompt_chars_before",
                "patch_verifier_recovery_prompt_chars_after",
                "patch_verifier_recovery_duration_sec",
                "patch_verifier_recovery_terminal_note",
                "patch_verifier_recovery_score_before",
                "patch_verifier_recovery_reasons_before",
                "patch_verifier_recovery_patch_files_before",
                "patch_verifier_recovery_source_scope_matched_before",
                "patch_verifier_recovery_raw_meta",
                "apply_duration_sec",
                "initial_apply_error",
                "runner_forced_patch_attempt",
                "runner_forced_patch_target",
                "runner_forced_patch_reason",
                "runner_forced_patch_build_sec",
                "runner_forced_patch_recovery",
                "runner_forced_patch_recovery_build_sec",
                "runner_forced_patch_recovery_apply_sec",
                "runner_forced_patch_recovery_target",
                "runner_forced_patch_recovery_reason",
                "runner_forced_patch_recovery_touches_tests",
                "runner_forced_patch_recovery_file_count",
                "runner_forced_patch_recovery_test_file_count",
                "runner_forced_patch_recovery_file_paths",
                "runner_forced_patch_recovery_error",
                "runner_forced_patch_skipped",
                "runner_forced_patch_skip_reason",
                "runner_forced_patch_marker_only",
                "runner_forced_patch_disabled_after_noop",
                "context_recovery_invoked",
                "context_recovery_kind",
                "context_recovery_prompt_chars_before",
                "context_recovery_prompt_chars_after",
                "context_recovery_duration_sec",
                "context_recovery_retry_success",
                "context_recovery_raw_meta",
                "no_diff_recovery_invoked",
                "no_diff_recovery_prompt_chars_before",
                "no_diff_recovery_prompt_chars_after",
                "no_diff_recovery_duration_sec",
                "no_diff_recovery_retry_success",
                "no_diff_recovery_raw_meta",
                "patcher_request_failed",
                "patcher_request_failure_kind",
                "patcher_request_failure_error",
                "patcher_request_failure_status",
                "patcher_request_failure_exc",
                "patch_sanitize_enabled",
                "patch_sanitize_passed",
                "patch_sanitize_rejected",
                "patch_sanitize_reasons",
                "patch_sanitize_error_msg",
                "adaptive_no_progress_token_sum",
                "adaptive_no_progress_token_cap",
                "adaptive_no_progress_token_cap_hit",
            ):
                patch_meta.pop(key, None)
            if isinstance(patch.meta, dict):
                patch_usage_meta = dict(patch.meta)
                patch_meta.update(patch_usage_meta)
            elif patch.meta is not None:
                patch_meta["raw_meta"] = patch.meta
            patch_meta["propose_duration_sec"] = patch_duration_sec
            patch_meta["syntax_preflight_cycle"] = syntax_cycle
            patch_meta["syntax_preflight_prompt_chars"] = len(cycle_prompt)
            _ensure_deterministic_seed_ack(
                deterministic=deterministic,
                seed=seed,
                patcher_name=patcher_name,
                patch_meta=patch_meta,
                case_id=case.case_id,
                condition=condition.value,
                attempt=attempt,
            )
            (
                cycle_llm_calls,
                cycle_tokens_prompt,
                cycle_tokens_completion,
                cycle_tokens_total,
            ) = _aggregate_attempt_llm_usage(
                stage_a_meta=None,
                patch_meta=patch_usage_meta,
                stage_a_default_llm_calls=0,
                patch_default_llm_calls=1 if is_openai_patcher else 0,
            )
            attempt_llm_calls_cumulative += cycle_llm_calls
            attempt_tokens_prompt_cumulative += cycle_tokens_prompt
            attempt_tokens_completion_cumulative += cycle_tokens_completion
            attempt_tokens_total_cumulative += cycle_tokens_total
            budget_local_llm_calls += cycle_llm_calls
            budget_local_tokens_prompt += cycle_tokens_prompt
            budget_local_tokens_completion += cycle_tokens_completion
            budget_local_tokens_total += cycle_tokens_total
            budget_local_wall_sec += patch_duration_sec
            budget_finalize_kwargs["local_accounting"] = _adaptive_policy_local_accounting(
                llm_call_count=budget_local_llm_calls,
                tokens_prompt=budget_local_tokens_prompt,
                tokens_completion=budget_local_tokens_completion,
                tokens_total=budget_local_tokens_total,
                wall_sec=budget_local_wall_sec,
            )
            _apply_usage_totals_to_patch_meta(
                patch_meta=patch_meta,
                llm_call_count=attempt_llm_calls_cumulative,
                tokens_prompt=attempt_tokens_prompt_cumulative,
                tokens_completion=attempt_tokens_completion_cumulative,
                tokens_total=attempt_tokens_total_cumulative,
            )
            _sync_attempt_usage_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                llm_call_count=attempt_llm_calls_cumulative,
            )
            _sync_syntax_preflight_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                enabled=normalized_syntax_preflight_enabled,
                cycles_used=syntax_preflight_cycles_used,
                failures=syntax_preflight_failures,
                last_error_type=syntax_preflight_last_error_type,
                last_error_msg=syntax_preflight_last_error_msg,
                events=syntax_preflight_events,
            )
            _sync_patcher_request_failure_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                failed=False,
            )
            _sync_patch_sanitize_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                enabled=normalized_patch_sanitize_enabled,
                passed=False,
                rejected=False,
                reasons=[],
                error_msg="",
            )
            _sync_patch_verifier_recovery_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                eligible=False,
                invoked=False,
                result=PATCH_VERIFIER_RECOVERY_RESULT_NOT_ELIGIBLE,
            )

            diff_to_apply = patch.diff
            request_failed, request_failure_kind = _classify_patcher_request_failure(patch_meta)
            request_failure_error = str(patch_meta.get("error") or "")
            request_failure_status = patch_meta.get("status")
            request_failure_status_int = (
                int(request_failure_status) if isinstance(request_failure_status, int) else None
            )
            request_failure_exc = str(patch_meta.get("exc") or "")
            if request_failed:
                _sync_patcher_request_failure_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    failed=True,
                    kind=request_failure_kind,
                    error=request_failure_error,
                    status=request_failure_status_int,
                    exc=request_failure_exc,
                )
            if (
                diff_to_apply is None
                and request_failed
                and not context_recovery_invoked
                and request_failure_kind in CONTEXT_RECOVERY_FAILURE_KINDS
                and not (
                    normalized_case_fail_fast_on_request_budget_exceeded
                    and request_failure_error == "request_budget_exceeded"
                )
            ):
                context_recovery_invoked = True
                recovery_sections = _build_stage_b_prompt_sections(
                    evidence=evidence,
                    snapshot=snapshot_for_prompt,
                    stage_a_context=stage_a_context_text,
                    stage_a_evidence_index=stage_a_evidence_index_text,
                    include_stage_a_evidence_index=True,
                    compacted_context=compacted_context_for_prompt,
                    include_snapshot=include_snapshot_in_prompt,
                    include_pytest_output=include_pytest_output_in_prompt,
                    include_stage_a_context=include_stage_a_context_in_prompt,
                    include_project_files=False,
                    retry_packet=retry_packet,
                    retry_prompt_mode=attempt_retry_prompt_mode,
                    retry_history=retry_history_text,
                    attempt=attempt,
                    known_evidence_refs=reused_evidence_ids,
                    new_evidence_ids=new_evidence_ids,
                    snapshot_lite_context=snapshot_lite_context,
                )
                recovery_budget_result = _apply_prompt_budget(
                    sections=recovery_sections,
                    budget=CONTEXT_RECOVERY_PROMPT_BUDGET,
                    is_delta_retry=is_delta_retry,
                )
                recovery_prompt = recovery_budget_result.prompt + syntax_retry_feedback
                recovery_patch_start = time.perf_counter()
                recovery_patch = _propose_patch_with_snapshot_context(
                    patcher=patcher_for_attempt,
                    prompt=recovery_prompt,
                    context_dir=attempt_dir,
                    cwd=workdir,
                    snapshot_full_text=snapshot_full_text,
                    snapshot_artifact_text=snapshot_artifact_text,
                    attempt_deadline_monotonic=attempt_shared_propose_deadline_monotonic,
                )
                recovery_patch_duration_sec = time.perf_counter() - recovery_patch_start

                recovery_usage_meta: dict[str, Any] | None = None
                for key in ("error", "status", "body", "exc"):
                    patch_meta.pop(key, None)
                if isinstance(recovery_patch.meta, dict):
                    recovery_usage_meta = dict(recovery_patch.meta)
                    patch_meta.update(recovery_usage_meta)
                elif recovery_patch.meta is not None:
                    patch_meta["context_recovery_raw_meta"] = recovery_patch.meta
                patch_meta["propose_duration_sec"] = (
                    patch_duration_sec + recovery_patch_duration_sec
                )
                patch_meta["context_recovery_invoked"] = True
                patch_meta["context_recovery_kind"] = request_failure_kind
                patch_meta["context_recovery_prompt_chars_before"] = len(cycle_prompt)
                patch_meta["context_recovery_prompt_chars_after"] = len(recovery_prompt)
                patch_meta["context_recovery_duration_sec"] = recovery_patch_duration_sec
                (
                    recovery_llm_calls,
                    recovery_tokens_prompt,
                    recovery_tokens_completion,
                    recovery_tokens_total,
                ) = _aggregate_attempt_llm_usage(
                    stage_a_meta=None,
                    patch_meta=recovery_usage_meta,
                    stage_a_default_llm_calls=0,
                    patch_default_llm_calls=1 if is_openai_patcher else 0,
                )
                attempt_llm_calls_cumulative += recovery_llm_calls
                attempt_tokens_prompt_cumulative += recovery_tokens_prompt
                attempt_tokens_completion_cumulative += recovery_tokens_completion
                attempt_tokens_total_cumulative += recovery_tokens_total
                budget_local_llm_calls += recovery_llm_calls
                budget_local_tokens_prompt += recovery_tokens_prompt
                budget_local_tokens_completion += recovery_tokens_completion
                budget_local_tokens_total += recovery_tokens_total
                budget_local_wall_sec += recovery_patch_duration_sec
                budget_finalize_kwargs["local_accounting"] = _adaptive_policy_local_accounting(
                    llm_call_count=budget_local_llm_calls,
                    tokens_prompt=budget_local_tokens_prompt,
                    tokens_completion=budget_local_tokens_completion,
                    tokens_total=budget_local_tokens_total,
                    wall_sec=budget_local_wall_sec,
                )
                _apply_usage_totals_to_patch_meta(
                    patch_meta=patch_meta,
                    llm_call_count=attempt_llm_calls_cumulative,
                    tokens_prompt=attempt_tokens_prompt_cumulative,
                    tokens_completion=attempt_tokens_completion_cumulative,
                    tokens_total=attempt_tokens_total_cumulative,
                )
                _sync_attempt_usage_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    llm_call_count=attempt_llm_calls_cumulative,
                )
                _ensure_deterministic_seed_ack(
                    deterministic=deterministic,
                    seed=seed,
                    patcher_name=patcher_name,
                    patch_meta=patch_meta,
                    case_id=case.case_id,
                    condition=condition.value,
                    attempt=attempt,
                )
                diff_to_apply = recovery_patch.diff
                patch_meta["context_recovery_retry_success"] = diff_to_apply is not None
                request_failed, request_failure_kind = _classify_patcher_request_failure(patch_meta)
                request_failure_error = str(patch_meta.get("error") or "")
                request_failure_status = patch_meta.get("status")
                request_failure_status_int = (
                    int(request_failure_status) if isinstance(request_failure_status, int) else None
                )
                request_failure_exc = str(patch_meta.get("exc") or "")
                _sync_patcher_request_failure_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    failed=request_failed,
                    kind=request_failure_kind if request_failed else "",
                    error=request_failure_error if request_failed else "",
                    status=request_failure_status_int if request_failed else None,
                    exc=request_failure_exc if request_failed else "",
                )
            if (
                diff_to_apply is None
                and is_openai_patcher
                and (not request_failed)
                and str(patch_meta.get("error") or "").strip()
            ):
                no_diff_recovery_sections = _build_stage_b_prompt_sections(
                    evidence=evidence,
                    snapshot=snapshot_for_prompt,
                    stage_a_context=stage_a_context_text,
                    stage_a_evidence_index=stage_a_evidence_index_text,
                    include_stage_a_evidence_index=True,
                    compacted_context=compacted_context_for_prompt,
                    include_snapshot=include_snapshot_in_prompt,
                    include_pytest_output=include_pytest_output_in_prompt,
                    include_stage_a_context=include_stage_a_context_in_prompt,
                    include_project_files=False,
                    retry_packet=retry_packet,
                    retry_prompt_mode=attempt_retry_prompt_mode,
                    retry_history=retry_history_text,
                    attempt=attempt,
                    known_evidence_refs=reused_evidence_ids,
                    new_evidence_ids=new_evidence_ids,
                    snapshot_lite_context=snapshot_lite_context,
                )
                no_diff_recovery_sections.append(
                    {
                        "key": "no_diff_recovery",
                        "priority": 3,
                        "text": (
                            "Retry directive: the previous response did not produce an applicable "
                            "patch. Return one concrete minimal diff that changes source behavior "
                            "in traceback-linked non-test files whenever possible.\n"
                        ),
                    }
                )
                no_diff_recovery_budget_result = _apply_prompt_budget(
                    sections=no_diff_recovery_sections,
                    budget=CONTEXT_RECOVERY_PROMPT_BUDGET,
                    is_delta_retry=is_delta_retry,
                )
                no_diff_recovery_prompt = (
                    no_diff_recovery_budget_result.prompt + syntax_retry_feedback
                )
                no_diff_recovery_start = time.perf_counter()
                no_diff_recovery_patch = _propose_patch_with_snapshot_context(
                    patcher=patcher_for_attempt,
                    prompt=no_diff_recovery_prompt,
                    context_dir=attempt_dir,
                    cwd=workdir,
                    snapshot_full_text=snapshot_full_text,
                    snapshot_artifact_text=snapshot_artifact_text,
                    attempt_deadline_monotonic=attempt_shared_propose_deadline_monotonic,
                )
                no_diff_recovery_duration_sec = time.perf_counter() - no_diff_recovery_start

                no_diff_recovery_usage_meta: dict[str, Any] | None = None
                for key in ("error", "status", "body", "exc"):
                    patch_meta.pop(key, None)
                if isinstance(no_diff_recovery_patch.meta, dict):
                    no_diff_recovery_usage_meta = dict(no_diff_recovery_patch.meta)
                    patch_meta.update(no_diff_recovery_usage_meta)
                elif no_diff_recovery_patch.meta is not None:
                    patch_meta["no_diff_recovery_raw_meta"] = no_diff_recovery_patch.meta
                existing_propose_duration = float(
                    patch_meta.get("propose_duration_sec", patch_duration_sec) or patch_duration_sec
                )
                patch_meta["propose_duration_sec"] = (
                    existing_propose_duration + no_diff_recovery_duration_sec
                )
                patch_meta["no_diff_recovery_invoked"] = True
                patch_meta["no_diff_recovery_prompt_chars_before"] = len(cycle_prompt)
                patch_meta["no_diff_recovery_prompt_chars_after"] = len(no_diff_recovery_prompt)
                patch_meta["no_diff_recovery_duration_sec"] = no_diff_recovery_duration_sec
                (
                    no_diff_recovery_llm_calls,
                    no_diff_recovery_tokens_prompt,
                    no_diff_recovery_tokens_completion,
                    no_diff_recovery_tokens_total,
                ) = _aggregate_attempt_llm_usage(
                    stage_a_meta=None,
                    patch_meta=no_diff_recovery_usage_meta,
                    stage_a_default_llm_calls=0,
                    patch_default_llm_calls=1 if is_openai_patcher else 0,
                )
                attempt_llm_calls_cumulative += no_diff_recovery_llm_calls
                attempt_tokens_prompt_cumulative += no_diff_recovery_tokens_prompt
                attempt_tokens_completion_cumulative += no_diff_recovery_tokens_completion
                attempt_tokens_total_cumulative += no_diff_recovery_tokens_total
                budget_local_llm_calls += no_diff_recovery_llm_calls
                budget_local_tokens_prompt += no_diff_recovery_tokens_prompt
                budget_local_tokens_completion += no_diff_recovery_tokens_completion
                budget_local_tokens_total += no_diff_recovery_tokens_total
                budget_local_wall_sec += no_diff_recovery_duration_sec
                budget_finalize_kwargs["local_accounting"] = _adaptive_policy_local_accounting(
                    llm_call_count=budget_local_llm_calls,
                    tokens_prompt=budget_local_tokens_prompt,
                    tokens_completion=budget_local_tokens_completion,
                    tokens_total=budget_local_tokens_total,
                    wall_sec=budget_local_wall_sec,
                )
                _apply_usage_totals_to_patch_meta(
                    patch_meta=patch_meta,
                    llm_call_count=attempt_llm_calls_cumulative,
                    tokens_prompt=attempt_tokens_prompt_cumulative,
                    tokens_completion=attempt_tokens_completion_cumulative,
                    tokens_total=attempt_tokens_total_cumulative,
                )
                _sync_attempt_usage_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    llm_call_count=attempt_llm_calls_cumulative,
                )
                _ensure_deterministic_seed_ack(
                    deterministic=deterministic,
                    seed=seed,
                    patcher_name=patcher_name,
                    patch_meta=patch_meta,
                    case_id=case.case_id,
                    condition=condition.value,
                    attempt=attempt,
                )
                diff_to_apply = no_diff_recovery_patch.diff
                patch_meta["no_diff_recovery_retry_success"] = diff_to_apply is not None
                request_failed, request_failure_kind = _classify_patcher_request_failure(patch_meta)
                request_failure_error = str(patch_meta.get("error") or "")
                request_failure_status = patch_meta.get("status")
                request_failure_status_int = (
                    int(request_failure_status) if isinstance(request_failure_status, int) else None
                )
                request_failure_exc = str(patch_meta.get("exc") or "")
                _sync_patcher_request_failure_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    failed=request_failed,
                    kind=request_failure_kind if request_failed else "",
                    error=request_failure_error if request_failed else "",
                    status=request_failure_status_int if request_failed else None,
                    exc=request_failure_exc if request_failed else "",
                )
            if diff_to_apply is None and runner_force_patch_attempt_enabled:
                if request_failed:
                    _sync_patcher_request_failure_metadata(
                        attempt_metadata=attempt_metadata,
                        patch_meta=patch_meta,
                        failed=True,
                        kind=request_failure_kind,
                        error=request_failure_error,
                        status=request_failure_status_int,
                        exc=request_failure_exc,
                        runner_forced_patch_skipped=True,
                        runner_forced_patch_skip_reason="transport_or_context_failure",
                    )
                else:
                    force_start = time.perf_counter()
                    forced = _build_runner_forced_attempt_diff(
                        cwd=workdir, file_index=file_index, attempt=attempt
                    )
                    patch_meta["runner_forced_patch_build_sec"] = time.perf_counter() - force_start
                    if forced is not None:
                        forced_target, diff_to_apply = forced
                        patch_meta["runner_forced_patch_attempt"] = True
                        patch_meta["runner_forced_patch_target"] = forced_target
                        patch_meta["runner_forced_patch_reason"] = "no_patch_proposed"
            no_diff_note_override = ""
            if diff_to_apply is not None and _is_forced_noop_marker_diff(diff_to_apply):
                patch_meta["runner_forced_patch_marker_only"] = True
                patch_meta["runner_forced_patch_disabled_after_noop"] = True
                attempt_metadata["runner_forced_patch_marker_only"] = True
                attempt_metadata["runner_forced_patch_disabled_after_noop"] = True
                runner_force_patch_attempt_enabled = False
                diff_to_apply = None
                no_diff_note_override = "forced_noop_patch"
            if diff_to_apply is None:
                no_diff_note = (
                    no_diff_note_override
                    if no_diff_note_override
                    else ("patcher_request_failed" if request_failed else "no_patch_proposed")
                )
                fail_fast_on_budget_exceeded = bool(
                    normalized_case_fail_fast_on_request_budget_exceeded
                    and request_failed
                    and request_failure_error == "request_budget_exceeded"
                )
                if fail_fast_on_budget_exceeded:
                    patch_meta["case_fail_fast_triggered"] = True
                    patch_meta["case_fail_fast_reason"] = "request_budget_exceeded"
                    attempt_metadata["case_fail_fast_triggered"] = True
                    attempt_metadata["case_fail_fast_reason"] = "request_budget_exceeded"
                _refresh_adaptive_progress_for_note(
                    note=no_diff_note,
                    success=False,
                    signature_repeated=True,
                )
                (
                    adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason_for_note,
                    adaptive_no_progress_token_sum,
                    adaptive_no_progress_token_cap_hit,
                ) = _apply_no_progress_token_cap_candidate(
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_made=adaptive_progress_made,
                    adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
                    attempt_tokens_total=_nonnegative_int(
                        attempt_metadata.get("tokens_total"), default=0
                    ),
                    adaptive_no_progress_token_cap=normalized_adaptive_no_progress_token_cap,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason,
                )
                patch_meta["adaptive_no_progress_token_sum"] = adaptive_no_progress_token_sum
                patch_meta["adaptive_no_progress_token_cap"] = (
                    normalized_adaptive_no_progress_token_cap
                )
                patch_meta["adaptive_no_progress_token_cap_hit"] = (
                    adaptive_no_progress_token_cap_hit
                )
                adaptive_stop_triggered = _mark_adaptive_progress_stop(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
                    note=no_diff_note,
                )
                _sync_syntax_preflight_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    enabled=normalized_syntax_preflight_enabled,
                    cycles_used=syntax_preflight_cycles_used,
                    failures=syntax_preflight_failures,
                    last_error_type=syntax_preflight_last_error_type,
                    last_error_msg=syntax_preflight_last_error_msg,
                    events=syntax_preflight_events,
                )
                attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
                cumulative_runtime_sec += attempt_wall_sec
                pending_credit = _finalize_adaptive_policy_attempt(
                    note=no_diff_note,
                    success=False,
                    attempt_wall_sec=attempt_wall_sec,
                    online_finalize_kwargs=online_finalize_kwargs,
                    budget_finalize_kwargs=budget_finalize_kwargs,
                )
                record = _build_attempt_outcome_record(
                    case=case,
                    condition=condition,
                    run_id=run_id,
                    attempt=attempt,
                    patcher_name=patcher_name,
                    run_metadata=effective_run_metadata,
                    attempt_metadata=attempt_metadata,
                    patcher=patcher_for_attempt,
                    patch_meta=patch_meta,
                    context_protocol=attempt_context_protocol,
                    success=False,
                    note=no_diff_note,
                    before=current,
                    cumulative_runtime_sec=cumulative_runtime_sec,
                    attempt_wall_clock_sec=attempt_wall_sec,
                )
                _queue_pending_online_credit(pending_credit=pending_credit, record=record)
                _append_record(record)
                _append_retry_history_turn_if_enabled(
                    retry_context_mode=effective_retry_context_mode,
                    retry_history_turns=retry_history_turns,
                    attempt=attempt,
                    note=no_diff_note,
                    failure_signature=failure_signature,
                    patch_meta=patch_meta,
                )
                previous_failure_context = _enrich_failure_context(
                    base=current_failure_context,
                    note=no_diff_note,
                    patch_meta=patch_meta,
                    run=current,
                )
                attempt_terminated_early = True
                should_break_after_attempt = adaptive_stop_triggered or fail_fast_on_budget_exceeded
                break

            _sync_patch_diff_metadata(patch_meta=patch_meta, diff=diff_to_apply)
            patch_path = attempt_dir / "patch.diff"
            if syntax_cycle > 1:
                patch_path = attempt_dir / f"patch_cycle_{syntax_cycle:02d}.diff"
            patch_path.write_text(diff_to_apply, encoding="utf-8")

            patch_verifier_scope = (
                PATCH_VERIFIER_SCOPE_ADAPTIVE_ONLY
                if is_adaptive_condition
                else PATCH_VERIFIER_SCOPE_DISABLED
            )
            patch_verifier_enabled = patch_verifier_scope == PATCH_VERIFIER_SCOPE_ADAPTIVE_ONLY
            patch_verifier_passed: bool | None = None
            patch_verifier_score: int | None = None
            patch_verifier_reasons: list[str] = []
            patch_verifier_meta: dict[str, Any] = {}
            if patch_verifier_enabled:
                patch_verifier_passed, patch_verifier_meta = _score_patch_relevance(
                    diff=diff_to_apply,
                    cwd=workdir,
                    snapshot=snapshot,
                    stdout=current.stdout,
                    stderr=current.stderr,
                    infer_test_source_scope=normalized_patch_verifier_infer_test_source_scope,
                    file_index_lines=file_index_lines,
                )
                patch_meta.update(patch_verifier_meta)
                patch_verifier_score = int(patch_verifier_meta.get("patch_verifier_score", 0))
                raw_reasons = patch_verifier_meta.get("patch_verifier_reasons")
                if isinstance(raw_reasons, list):
                    patch_verifier_reasons = [str(item) for item in raw_reasons]
            _sync_patch_verifier_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                enabled=patch_verifier_enabled,
                scope=patch_verifier_scope,
                passed=patch_verifier_passed,
                score=patch_verifier_score,
                reasons=patch_verifier_reasons,
            )
            if patch_verifier_enabled and patch_verifier_passed is False:
                initial_patch_verifier_score = patch_verifier_score
                initial_patch_verifier_reasons = list(patch_verifier_reasons)
                patch_verifier_patch_files_before_raw = patch_meta.get("patch_verifier_patch_files")
                patch_verifier_patch_files_before = (
                    [str(item) for item in patch_verifier_patch_files_before_raw]
                    if isinstance(patch_verifier_patch_files_before_raw, list)
                    else []
                )
                patch_verifier_scope_before = bool(
                    patch_meta.get("patch_verifier_source_scope_matched")
                )
                patch_verifier_recovery_eligible = bool(
                    patch_meta.get("patch_verifier_substantive_change")
                )
                _sync_patch_verifier_recovery_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    eligible=patch_verifier_recovery_eligible,
                    invoked=False,
                    result=PATCH_VERIFIER_RECOVERY_RESULT_NOT_ELIGIBLE,
                    score_before=initial_patch_verifier_score,
                    reasons_before=initial_patch_verifier_reasons,
                    patch_files_before=patch_verifier_patch_files_before,
                    source_scope_matched_before=patch_verifier_scope_before,
                )
                if patch_verifier_recovery_eligible:
                    remaining_recovery_budget_sec = _remaining_attempt_propose_budget_sec(
                        attempt_shared_propose_deadline_monotonic
                    )
                    if (
                        remaining_recovery_budget_sec is not None
                        and remaining_recovery_budget_sec <= 0
                    ):
                        _sync_patch_verifier_recovery_metadata(
                            attempt_metadata=attempt_metadata,
                            patch_meta=patch_meta,
                            eligible=True,
                            invoked=False,
                            result=PATCH_VERIFIER_RECOVERY_RESULT_SKIPPED_DEADLINE_EXHAUSTED,
                            prompt_chars_before=len(cycle_prompt),
                            score_before=initial_patch_verifier_score,
                            reasons_before=initial_patch_verifier_reasons,
                            patch_files_before=patch_verifier_patch_files_before,
                            source_scope_matched_before=patch_verifier_scope_before,
                        )
                    else:
                        verifier_recovery_sections = _build_stage_b_prompt_sections(
                            evidence=evidence,
                            snapshot=snapshot_for_prompt,
                            stage_a_context=stage_a_context_text,
                            stage_a_evidence_index=stage_a_evidence_index_text,
                            include_stage_a_evidence_index=True,
                            compacted_context=compacted_context_for_prompt,
                            include_snapshot=include_snapshot_in_prompt,
                            include_pytest_output=include_pytest_output_in_prompt,
                            include_stage_a_context=include_stage_a_context_in_prompt,
                            include_project_files=False,
                            retry_packet=retry_packet,
                            retry_prompt_mode=attempt_retry_prompt_mode,
                            retry_history=retry_history_text,
                            attempt=attempt,
                            known_evidence_refs=reused_evidence_ids,
                            new_evidence_ids=new_evidence_ids,
                            snapshot_lite_context=snapshot_lite_context,
                        )
                        inferred_source_scope_raw = patch_meta.get(
                            "patch_verifier_inferred_source_scope"
                        )
                        inferred_source_scope = (
                            [str(item) for item in inferred_source_scope_raw]
                            if isinstance(inferred_source_scope_raw, list)
                            else []
                        )
                        verifier_recovery_sections.append(
                            {
                                "key": "patch_verifier_recovery",
                                "priority": 3,
                                "text": _render_patch_verifier_recovery_feedback(
                                    score=patch_verifier_score,
                                    min_score=PATCH_VERIFIER_MIN_SCORE,
                                    reasons=patch_verifier_reasons,
                                    patch_files=patch_verifier_patch_files_before,
                                    crash_file=str(
                                        patch_meta.get("patch_verifier_crash_file") or ""
                                    ),
                                    inferred_source_scope=inferred_source_scope,
                                ),
                            }
                        )
                        verifier_recovery_budget_result = _apply_prompt_budget(
                            sections=verifier_recovery_sections,
                            budget=CONTEXT_RECOVERY_PROMPT_BUDGET,
                            is_delta_retry=is_delta_retry,
                        )
                        verifier_recovery_prompt = (
                            verifier_recovery_budget_result.prompt + syntax_retry_feedback
                        )
                        verifier_recovery_start = time.perf_counter()
                        verifier_recovery_patch = _propose_patch_with_snapshot_context(
                            patcher=patcher_for_attempt,
                            prompt=verifier_recovery_prompt,
                            context_dir=attempt_dir,
                            cwd=workdir,
                            snapshot_full_text=snapshot_full_text,
                            snapshot_artifact_text=snapshot_artifact_text,
                            attempt_deadline_monotonic=attempt_shared_propose_deadline_monotonic,
                        )
                        verifier_recovery_duration_sec = (
                            time.perf_counter() - verifier_recovery_start
                        )

                        verifier_recovery_usage_meta: dict[str, Any] | None = None
                        verifier_recovery_raw_meta: Any = verifier_recovery_patch.meta
                        if isinstance(verifier_recovery_patch.meta, dict):
                            verifier_recovery_usage_meta = dict(verifier_recovery_patch.meta)
                            _ensure_deterministic_seed_ack(
                                deterministic=deterministic,
                                seed=seed,
                                patcher_name=patcher_name,
                                patch_meta=verifier_recovery_usage_meta,
                                case_id=case.case_id,
                                condition=condition.value,
                                attempt=attempt,
                            )
                            verifier_recovery_raw_meta = dict(verifier_recovery_usage_meta)
                        (
                            verifier_recovery_llm_calls,
                            verifier_recovery_tokens_prompt,
                            verifier_recovery_tokens_completion,
                            verifier_recovery_tokens_total,
                        ) = _aggregate_attempt_llm_usage(
                            stage_a_meta=None,
                            patch_meta=verifier_recovery_usage_meta,
                            stage_a_default_llm_calls=0,
                            patch_default_llm_calls=1 if is_openai_patcher else 0,
                        )
                        attempt_llm_calls_cumulative += verifier_recovery_llm_calls
                        attempt_tokens_prompt_cumulative += verifier_recovery_tokens_prompt
                        attempt_tokens_completion_cumulative += verifier_recovery_tokens_completion
                        attempt_tokens_total_cumulative += verifier_recovery_tokens_total
                        budget_local_llm_calls += verifier_recovery_llm_calls
                        budget_local_tokens_prompt += verifier_recovery_tokens_prompt
                        budget_local_tokens_completion += verifier_recovery_tokens_completion
                        budget_local_tokens_total += verifier_recovery_tokens_total
                        budget_local_wall_sec += verifier_recovery_duration_sec
                        budget_finalize_kwargs["local_accounting"] = (
                            _adaptive_policy_local_accounting(
                                llm_call_count=budget_local_llm_calls,
                                tokens_prompt=budget_local_tokens_prompt,
                                tokens_completion=budget_local_tokens_completion,
                                tokens_total=budget_local_tokens_total,
                                wall_sec=budget_local_wall_sec,
                            )
                        )
                        existing_propose_duration = float(
                            patch_meta.get("propose_duration_sec", patch_duration_sec)
                            or patch_duration_sec
                        )
                        patch_meta["propose_duration_sec"] = (
                            existing_propose_duration + verifier_recovery_duration_sec
                        )
                        verifier_recovery_request_failed, _ = _classify_patcher_request_failure(
                            verifier_recovery_usage_meta or {}
                        )
                        verifier_recovery_result = (
                            PATCH_VERIFIER_RECOVERY_RESULT_REQUEST_FAILED
                            if verifier_recovery_request_failed
                            else PATCH_VERIFIER_RECOVERY_RESULT_NO_DIFF
                        )
                        verifier_recovery_diff = verifier_recovery_patch.diff
                        if verifier_recovery_diff is not None:
                            patch_meta.update(verifier_recovery_usage_meta or {})
                            patch_meta["propose_duration_sec"] = (
                                existing_propose_duration + verifier_recovery_duration_sec
                            )
                            _sync_patch_diff_metadata(
                                patch_meta=patch_meta,
                                diff=verifier_recovery_diff,
                            )
                            patch_path.write_text(verifier_recovery_diff, encoding="utf-8")
                            patch_verifier_passed, patch_verifier_meta = _score_patch_relevance(
                                diff=verifier_recovery_diff,
                                cwd=workdir,
                                snapshot=snapshot,
                                stdout=current.stdout,
                                stderr=current.stderr,
                                infer_test_source_scope=(
                                    normalized_patch_verifier_infer_test_source_scope
                                ),
                                file_index_lines=file_index_lines,
                            )
                            patch_meta.update(patch_verifier_meta)
                            patch_verifier_score = int(
                                patch_verifier_meta.get("patch_verifier_score", 0)
                            )
                            raw_reasons = patch_verifier_meta.get("patch_verifier_reasons")
                            patch_verifier_reasons = (
                                [str(item) for item in raw_reasons]
                                if isinstance(raw_reasons, list)
                                else []
                            )
                            _sync_patch_verifier_metadata(
                                attempt_metadata=attempt_metadata,
                                patch_meta=patch_meta,
                                enabled=patch_verifier_enabled,
                                scope=patch_verifier_scope,
                                passed=patch_verifier_passed,
                                score=patch_verifier_score,
                                reasons=patch_verifier_reasons,
                            )
                            _sync_patcher_request_failure_metadata(
                                attempt_metadata=attempt_metadata,
                                patch_meta=patch_meta,
                                failed=False,
                            )
                            diff_to_apply = verifier_recovery_diff
                            verifier_recovery_result = (
                                PATCH_VERIFIER_RECOVERY_RESULT_PASSED_VERIFIER
                                if patch_verifier_passed
                                else PATCH_VERIFIER_RECOVERY_RESULT_STILL_REJECTED
                            )
                        _sync_patch_verifier_recovery_metadata(
                            attempt_metadata=attempt_metadata,
                            patch_meta=patch_meta,
                            eligible=True,
                            invoked=True,
                            result=verifier_recovery_result,
                            prompt_chars_before=len(cycle_prompt),
                            prompt_chars_after=len(verifier_recovery_prompt),
                            duration_sec=verifier_recovery_duration_sec,
                            score_before=initial_patch_verifier_score,
                            reasons_before=initial_patch_verifier_reasons,
                            patch_files_before=patch_verifier_patch_files_before,
                            source_scope_matched_before=patch_verifier_scope_before,
                            raw_meta=verifier_recovery_raw_meta,
                        )
                        _apply_usage_totals_to_patch_meta(
                            patch_meta=patch_meta,
                            llm_call_count=attempt_llm_calls_cumulative,
                            tokens_prompt=attempt_tokens_prompt_cumulative,
                            tokens_completion=attempt_tokens_completion_cumulative,
                            tokens_total=attempt_tokens_total_cumulative,
                        )
                        _sync_attempt_usage_metadata(
                            attempt_metadata=attempt_metadata,
                            patch_meta=patch_meta,
                            llm_call_count=attempt_llm_calls_cumulative,
                        )
            _sync_syntax_preflight_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                enabled=normalized_syntax_preflight_enabled,
                cycles_used=syntax_preflight_cycles_used,
                failures=syntax_preflight_failures,
                last_error_type=syntax_preflight_last_error_type,
                last_error_msg=syntax_preflight_last_error_msg,
                events=syntax_preflight_events,
            )
            if patch_verifier_enabled and patch_verifier_passed is False:
                _refresh_adaptive_progress_for_note(
                    note="patch_verifier_rejected",
                    success=False,
                    signature_repeated=True,
                )
                (
                    adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason_for_note,
                    adaptive_no_progress_token_sum,
                    adaptive_no_progress_token_cap_hit,
                ) = _apply_no_progress_token_cap_candidate(
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_made=adaptive_progress_made,
                    adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
                    attempt_tokens_total=_nonnegative_int(
                        attempt_metadata.get("tokens_total"), default=0
                    ),
                    adaptive_no_progress_token_cap=normalized_adaptive_no_progress_token_cap,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason,
                )
                patch_meta["adaptive_no_progress_token_sum"] = adaptive_no_progress_token_sum
                patch_meta["adaptive_no_progress_token_cap"] = (
                    normalized_adaptive_no_progress_token_cap
                )
                patch_meta["adaptive_no_progress_token_cap_hit"] = (
                    adaptive_no_progress_token_cap_hit
                )
                adaptive_stop_triggered = _mark_adaptive_progress_stop(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
                    note="patch_verifier_rejected",
                )
                attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
                cumulative_runtime_sec += attempt_wall_sec
                pending_credit = _finalize_adaptive_policy_attempt(
                    note="patch_verifier_rejected",
                    success=False,
                    attempt_wall_sec=attempt_wall_sec,
                    online_finalize_kwargs=online_finalize_kwargs,
                    budget_finalize_kwargs=budget_finalize_kwargs,
                )
                record = _build_attempt_outcome_record(
                    case=case,
                    condition=condition,
                    run_id=run_id,
                    attempt=attempt,
                    patcher_name=patcher_name,
                    run_metadata=effective_run_metadata,
                    attempt_metadata=attempt_metadata,
                    patcher=patcher_for_attempt,
                    patch_meta=patch_meta,
                    context_protocol=attempt_context_protocol,
                    success=False,
                    note="patch_verifier_rejected",
                    before=current,
                    cumulative_runtime_sec=cumulative_runtime_sec,
                    attempt_wall_clock_sec=attempt_wall_sec,
                )
                _queue_pending_online_credit(pending_credit=pending_credit, record=record)
                _append_record(record)
                _append_retry_history_turn_if_enabled(
                    retry_context_mode=effective_retry_context_mode,
                    retry_history_turns=retry_history_turns,
                    attempt=attempt,
                    note="patch_verifier_rejected",
                    failure_signature=failure_signature,
                    patch_meta=patch_meta,
                )
                previous_failure_context = _enrich_failure_context(
                    base=current_failure_context,
                    note="patch_verifier_rejected",
                    patch_meta=patch_meta,
                    run=current,
                )
                attempt_terminated_early = True
                should_break_after_attempt = adaptive_stop_triggered
                break

            if normalized_patch_sanitize_enabled:
                sanitize_ok, sanitize_meta = _run_patch_sanitization(diff=diff_to_apply)
                sanitize_reasons_raw = sanitize_meta.get("patch_sanitize_reasons")
                sanitize_reasons = (
                    [str(item) for item in sanitize_reasons_raw]
                    if isinstance(sanitize_reasons_raw, list)
                    else []
                )
                sanitize_error_msg = str(sanitize_meta.get("patch_sanitize_error_msg") or "")
                _sync_patch_sanitize_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    enabled=True,
                    passed=bool(sanitize_ok),
                    rejected=not bool(sanitize_ok),
                    reasons=sanitize_reasons,
                    error_msg=sanitize_error_msg,
                )
                if not sanitize_ok:
                    sanitize_checked_files_raw = sanitize_meta.get("patch_sanitize_checked_files")
                    sanitize_checked_files = (
                        [str(item) for item in sanitize_checked_files_raw]
                        if isinstance(sanitize_checked_files_raw, list)
                        else []
                    )
                    sanitize_error_file = (
                        sanitize_checked_files[0] if sanitize_checked_files else ""
                    )
                    syntax_event = {
                        "cycle": syntax_cycle,
                        "passed": False,
                        "syntax_preflight_checked_files": sanitize_checked_files,
                        "syntax_preflight_checked_count": int(
                            sanitize_meta.get("patch_sanitize_checked_count") or 0
                        ),
                        "syntax_preflight_error_file": sanitize_error_file,
                        "syntax_preflight_error_type": "PatchSanitizationError",
                        "syntax_preflight_error_msg": sanitize_error_msg,
                        "patch_sanitize_rejected": True,
                        "patch_sanitize_reasons": sanitize_reasons,
                    }
                    syntax_preflight_events.append(syntax_event)
                    syntax_preflight_failures += 1
                    syntax_preflight_last_error_type = "PatchSanitizationError"
                    syntax_preflight_last_error_msg = sanitize_error_msg
                    _sync_syntax_preflight_metadata(
                        attempt_metadata=attempt_metadata,
                        patch_meta=patch_meta,
                        enabled=normalized_syntax_preflight_enabled,
                        cycles_used=syntax_preflight_cycles_used,
                        failures=syntax_preflight_failures,
                        last_error_type=syntax_preflight_last_error_type,
                        last_error_msg=syntax_preflight_last_error_msg,
                        events=syntax_preflight_events,
                    )
                    if syntax_cycle < syntax_preflight_max_cycles_for_attempt:
                        if is_openai_patcher:
                            patcher_for_attempt, _ = _maybe_autoswitch_openai_response_mode(
                                patcher_for_attempt=patcher_for_attempt,
                                policy=normalized_openai_response_mode_autoswitch,
                                reason="patch_sanitize_rejected",
                                syntax_cycle=syntax_cycle,
                                patch_meta=patch_meta,
                                attempt_metadata=attempt_metadata,
                            )
                        syntax_retry_feedback = _render_patch_sanitize_retry_feedback(
                            cycle=syntax_cycle,
                            max_cycles=syntax_preflight_max_cycles_for_attempt,
                            sanitize_error=sanitize_error_msg,
                        )
                        continue

                    _refresh_adaptive_progress_for_note(
                        note="syntax_preflight_failed",
                        success=False,
                        signature_repeated=True,
                    )
                    (
                        adaptive_progress_stop_candidate_for_note,
                        adaptive_progress_stop_reason_for_note,
                        adaptive_no_progress_token_sum,
                        adaptive_no_progress_token_cap_hit,
                    ) = _apply_no_progress_token_cap_candidate(
                        is_adaptive_condition=is_adaptive_condition,
                        adaptive_progress_made=adaptive_progress_made,
                        adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
                        attempt_tokens_total=_nonnegative_int(
                            attempt_metadata.get("tokens_total"), default=0
                        ),
                        adaptive_no_progress_token_cap=normalized_adaptive_no_progress_token_cap,
                        adaptive_progress_stop_candidate=adaptive_progress_stop_candidate,
                        adaptive_progress_stop_reason=adaptive_progress_stop_reason,
                    )
                    patch_meta["adaptive_no_progress_token_sum"] = adaptive_no_progress_token_sum
                    patch_meta["adaptive_no_progress_token_cap"] = (
                        normalized_adaptive_no_progress_token_cap
                    )
                    patch_meta["adaptive_no_progress_token_cap_hit"] = (
                        adaptive_no_progress_token_cap_hit
                    )
                    adaptive_stop_triggered = _mark_adaptive_progress_stop(
                        attempt_metadata=attempt_metadata,
                        patch_meta=patch_meta,
                        is_adaptive_condition=is_adaptive_condition,
                        adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
                        adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
                        note="syntax_preflight_failed",
                    )
                    attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
                    cumulative_runtime_sec += attempt_wall_sec
                    pending_credit = _finalize_adaptive_policy_attempt(
                        note="syntax_preflight_failed",
                        success=False,
                        attempt_wall_sec=attempt_wall_sec,
                        online_finalize_kwargs=online_finalize_kwargs,
                        budget_finalize_kwargs=budget_finalize_kwargs,
                    )
                    record = _build_attempt_outcome_record(
                        case=case,
                        condition=condition,
                        run_id=run_id,
                        attempt=attempt,
                        patcher_name=patcher_name,
                        run_metadata=effective_run_metadata,
                        attempt_metadata=attempt_metadata,
                        patcher=patcher_for_attempt,
                        patch_meta=patch_meta,
                        context_protocol=attempt_context_protocol,
                        success=False,
                        note="syntax_preflight_failed",
                        before=current,
                        cumulative_runtime_sec=cumulative_runtime_sec,
                        attempt_wall_clock_sec=attempt_wall_sec,
                    )
                    _queue_pending_online_credit(pending_credit=pending_credit, record=record)
                    _append_record(record)
                    _append_retry_history_turn_if_enabled(
                        retry_context_mode=effective_retry_context_mode,
                        retry_history_turns=retry_history_turns,
                        attempt=attempt,
                        note="syntax_preflight_failed",
                        failure_signature=failure_signature,
                        patch_meta=patch_meta,
                    )
                    previous_failure_context = _enrich_failure_context(
                        base=current_failure_context,
                        note="syntax_preflight_failed",
                        patch_meta=patch_meta,
                        run=current,
                    )
                    attempt_terminated_early = True
                    should_break_after_attempt = adaptive_stop_triggered
                    break

            file_backups = _backup_diff_target_files(cwd=workdir, diff=diff_to_apply)
            applied_diff_for_preflight = diff_to_apply
            apply_start = time.perf_counter()
            apply_ok, apply_msg = apply_diff(workdir, diff_to_apply)
            patch_meta["apply_duration_sec"] = time.perf_counter() - apply_start
            if not apply_ok and runner_force_patch_attempt_enabled:
                patch_meta["initial_apply_error"] = apply_msg
                recovery_build_start = time.perf_counter()
                recovery_patch = _build_runner_forced_attempt_diff(
                    cwd=workdir,
                    file_index=file_index,
                    attempt=attempt,
                )
                patch_meta["runner_forced_patch_recovery_build_sec"] = (
                    time.perf_counter() - recovery_build_start
                )
                if recovery_patch is not None:
                    recovery_target, recovery_diff = recovery_patch
                    (attempt_dir / "patch_recovery.diff").write_text(
                        recovery_diff, encoding="utf-8"
                    )
                    recovery_metrics = _compute_patch_path_metrics(recovery_diff)
                    recovery_backups = _backup_diff_target_files(cwd=workdir, diff=recovery_diff)
                    for rel, payload in recovery_backups.items():
                        file_backups.setdefault(rel, payload)
                    recovery_apply_start = time.perf_counter()
                    recovery_ok, recovery_msg = apply_diff(workdir, recovery_diff)
                    patch_meta["runner_forced_patch_recovery_apply_sec"] = (
                        time.perf_counter() - recovery_apply_start
                    )
                    patch_meta["runner_forced_patch_recovery"] = True
                    patch_meta["runner_forced_patch_recovery_target"] = recovery_target
                    patch_meta["runner_forced_patch_recovery_reason"] = "patch_apply_failed"
                    patch_meta["runner_forced_patch_recovery_touches_tests"] = bool(
                        recovery_metrics["patch_touches_tests"]
                    )
                    patch_meta["runner_forced_patch_recovery_file_count"] = int(
                        recovery_metrics["patch_file_count"]
                    )
                    patch_meta["runner_forced_patch_recovery_test_file_count"] = int(
                        recovery_metrics["patch_test_file_count"]
                    )
                    patch_meta["runner_forced_patch_recovery_file_paths"] = list(
                        recovery_metrics["patch_file_paths"]
                    )
                    if recovery_ok:
                        apply_ok = True
                        apply_msg = recovery_msg
                        applied_diff_for_preflight = recovery_diff
                    else:
                        patch_meta["runner_forced_patch_recovery_error"] = recovery_msg
            if not apply_ok:
                _refresh_adaptive_progress_for_note(
                    note="patch_apply_failed",
                    success=False,
                    signature_repeated=True,
                )
                (
                    adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason_for_note,
                    adaptive_no_progress_token_sum,
                    adaptive_no_progress_token_cap_hit,
                ) = _apply_no_progress_token_cap_candidate(
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_made=adaptive_progress_made,
                    adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
                    attempt_tokens_total=_nonnegative_int(
                        attempt_metadata.get("tokens_total"), default=0
                    ),
                    adaptive_no_progress_token_cap=normalized_adaptive_no_progress_token_cap,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason,
                )
                patch_meta["adaptive_no_progress_token_sum"] = adaptive_no_progress_token_sum
                patch_meta["adaptive_no_progress_token_cap"] = (
                    normalized_adaptive_no_progress_token_cap
                )
                patch_meta["adaptive_no_progress_token_cap_hit"] = (
                    adaptive_no_progress_token_cap_hit
                )
                adaptive_stop_triggered = _mark_adaptive_progress_stop(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
                    note="patch_apply_failed",
                )
                _sync_syntax_preflight_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    enabled=normalized_syntax_preflight_enabled,
                    cycles_used=syntax_preflight_cycles_used,
                    failures=syntax_preflight_failures,
                    last_error_type=syntax_preflight_last_error_type,
                    last_error_msg=syntax_preflight_last_error_msg,
                    events=syntax_preflight_events,
                )
                (attempt_dir / "apply_error.txt").write_text(apply_msg, encoding="utf-8")
                attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
                cumulative_runtime_sec += attempt_wall_sec
                pending_credit = _finalize_adaptive_policy_attempt(
                    note="patch_apply_failed",
                    success=False,
                    attempt_wall_sec=attempt_wall_sec,
                    online_finalize_kwargs=online_finalize_kwargs,
                    budget_finalize_kwargs=budget_finalize_kwargs,
                )
                record = _build_attempt_outcome_record(
                    case=case,
                    condition=condition,
                    run_id=run_id,
                    attempt=attempt,
                    patcher_name=patcher_name,
                    run_metadata=effective_run_metadata,
                    attempt_metadata=attempt_metadata,
                    patcher=patcher_for_attempt,
                    patch_meta=patch_meta,
                    context_protocol=attempt_context_protocol,
                    success=False,
                    note="patch_apply_failed",
                    before=current,
                    cumulative_runtime_sec=cumulative_runtime_sec,
                    attempt_wall_clock_sec=attempt_wall_sec,
                )
                _queue_pending_online_credit(pending_credit=pending_credit, record=record)
                _append_record(record)
                _append_retry_history_turn_if_enabled(
                    retry_context_mode=effective_retry_context_mode,
                    retry_history_turns=retry_history_turns,
                    attempt=attempt,
                    note="patch_apply_failed",
                    failure_signature=failure_signature,
                    patch_meta=patch_meta,
                )
                previous_failure_context = _enrich_failure_context(
                    base=current_failure_context,
                    note="patch_apply_failed",
                    patch_meta=patch_meta,
                    run=current,
                )
                attempt_terminated_early = True
                should_break_after_attempt = adaptive_stop_triggered
                break

            _invalidate_file_catalog_cache()

            if normalized_syntax_preflight_enabled:
                syntax_preflight_passed, syntax_preflight_meta = _run_syntax_preflight(
                    cwd=workdir,
                    diff=applied_diff_for_preflight,
                )
                syntax_event = {
                    "cycle": syntax_cycle,
                    "passed": bool(syntax_preflight_passed),
                    **syntax_preflight_meta,
                }
                syntax_preflight_events.append(syntax_event)
                if syntax_preflight_passed:
                    _sync_syntax_preflight_metadata(
                        attempt_metadata=attempt_metadata,
                        patch_meta=patch_meta,
                        enabled=normalized_syntax_preflight_enabled,
                        cycles_used=syntax_preflight_cycles_used,
                        failures=syntax_preflight_failures,
                        last_error_type=syntax_preflight_last_error_type,
                        last_error_msg=syntax_preflight_last_error_msg,
                        events=syntax_preflight_events,
                    )
                    patch_applied_and_ready = True
                    break

                syntax_preflight_failures += 1
                syntax_preflight_last_error_type = str(
                    syntax_preflight_meta.get("syntax_preflight_error_type") or ""
                )
                syntax_preflight_last_error_msg = str(
                    syntax_preflight_meta.get("syntax_preflight_error_msg") or ""
                )
                _restore_diff_target_files(cwd=workdir, backups=file_backups)
                _sync_syntax_preflight_metadata(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    enabled=normalized_syntax_preflight_enabled,
                    cycles_used=syntax_preflight_cycles_used,
                    failures=syntax_preflight_failures,
                    last_error_type=syntax_preflight_last_error_type,
                    last_error_msg=syntax_preflight_last_error_msg,
                    events=syntax_preflight_events,
                )
                if syntax_cycle < syntax_preflight_max_cycles_for_attempt:
                    if is_openai_patcher:
                        patcher_for_attempt, _ = _maybe_autoswitch_openai_response_mode(
                            patcher_for_attempt=patcher_for_attempt,
                            policy=normalized_openai_response_mode_autoswitch,
                            reason="syntax_preflight_failed",
                            syntax_cycle=syntax_cycle,
                            patch_meta=patch_meta,
                            attempt_metadata=attempt_metadata,
                        )
                    syntax_retry_feedback = _render_syntax_preflight_retry_feedback(
                        cycle=syntax_cycle,
                        max_cycles=syntax_preflight_max_cycles_for_attempt,
                        error_file=str(
                            syntax_preflight_meta.get("syntax_preflight_error_file") or ""
                        ),
                        error_type=syntax_preflight_last_error_type,
                        error_msg=syntax_preflight_last_error_msg,
                    )
                    continue

                _refresh_adaptive_progress_for_note(
                    note="syntax_preflight_failed",
                    success=False,
                    signature_repeated=True,
                )
                (
                    adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason_for_note,
                    adaptive_no_progress_token_sum,
                    adaptive_no_progress_token_cap_hit,
                ) = _apply_no_progress_token_cap_candidate(
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_made=adaptive_progress_made,
                    adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
                    attempt_tokens_total=_nonnegative_int(
                        attempt_metadata.get("tokens_total"), default=0
                    ),
                    adaptive_no_progress_token_cap=normalized_adaptive_no_progress_token_cap,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason,
                )
                patch_meta["adaptive_no_progress_token_sum"] = adaptive_no_progress_token_sum
                patch_meta["adaptive_no_progress_token_cap"] = (
                    normalized_adaptive_no_progress_token_cap
                )
                patch_meta["adaptive_no_progress_token_cap_hit"] = (
                    adaptive_no_progress_token_cap_hit
                )
                adaptive_stop_triggered = _mark_adaptive_progress_stop(
                    attempt_metadata=attempt_metadata,
                    patch_meta=patch_meta,
                    is_adaptive_condition=is_adaptive_condition,
                    adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
                    adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
                    note="syntax_preflight_failed",
                )
                attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
                cumulative_runtime_sec += attempt_wall_sec
                pending_credit = _finalize_adaptive_policy_attempt(
                    note="syntax_preflight_failed",
                    success=False,
                    attempt_wall_sec=attempt_wall_sec,
                    online_finalize_kwargs=online_finalize_kwargs,
                    budget_finalize_kwargs=budget_finalize_kwargs,
                )
                record = _build_attempt_outcome_record(
                    case=case,
                    condition=condition,
                    run_id=run_id,
                    attempt=attempt,
                    patcher_name=patcher_name,
                    run_metadata=effective_run_metadata,
                    attempt_metadata=attempt_metadata,
                    patcher=patcher_for_attempt,
                    patch_meta=patch_meta,
                    context_protocol=attempt_context_protocol,
                    success=False,
                    note="syntax_preflight_failed",
                    before=current,
                    cumulative_runtime_sec=cumulative_runtime_sec,
                    attempt_wall_clock_sec=attempt_wall_sec,
                )
                _queue_pending_online_credit(pending_credit=pending_credit, record=record)
                _append_record(record)
                _append_retry_history_turn_if_enabled(
                    retry_context_mode=effective_retry_context_mode,
                    retry_history_turns=retry_history_turns,
                    attempt=attempt,
                    note="syntax_preflight_failed",
                    failure_signature=failure_signature,
                    patch_meta=patch_meta,
                )
                previous_failure_context = _enrich_failure_context(
                    base=current_failure_context,
                    note="syntax_preflight_failed",
                    patch_meta=patch_meta,
                    run=current,
                )
                attempt_terminated_early = True
                should_break_after_attempt = adaptive_stop_triggered
                break

            syntax_preflight_events.append(
                {
                    "cycle": syntax_cycle,
                    "passed": True,
                    "syntax_preflight_checked_files": [],
                    "syntax_preflight_checked_count": 0,
                    "syntax_preflight_error_file": "",
                    "syntax_preflight_error_type": "",
                    "syntax_preflight_error_msg": "",
                }
            )
            _sync_syntax_preflight_metadata(
                attempt_metadata=attempt_metadata,
                patch_meta=patch_meta,
                enabled=normalized_syntax_preflight_enabled,
                cycles_used=syntax_preflight_cycles_used,
                failures=syntax_preflight_failures,
                last_error_type=syntax_preflight_last_error_type,
                last_error_msg=syntax_preflight_last_error_msg,
                events=syntax_preflight_events,
            )
            patch_applied_and_ready = True
            break

        _sync_syntax_preflight_metadata(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            enabled=normalized_syntax_preflight_enabled,
            cycles_used=syntax_preflight_cycles_used,
            failures=syntax_preflight_failures,
            last_error_type=syntax_preflight_last_error_type,
            last_error_msg=syntax_preflight_last_error_msg,
            events=syntax_preflight_events,
        )
        if attempt_terminated_early:
            if should_break_after_attempt:
                break
            continue
        if not patch_applied_and_ready:
            continue

        # Re-run after patch.
        shutil.rmtree(llmdebug_dir, ignore_errors=True)
        _emit_progress(
            "attempt_pytest_after",
            {
                "attempt": int(attempt),
                "max_attempts": int(k),
            },
        )
        after = run_pytest(workdir, case.python_args, condition, case.timeout_sec, output_format)
        _write_run_outputs(
            attempt_dir,
            after,
            prefix="pytest_after",
            tail_lines=normalized_pytest_output_tail_lines,
        )
        success = after.returncode == 0
        note = "success" if success else "tests_still_failing"
        adaptive_progress_stop_candidate_for_note = adaptive_progress_stop_candidate
        adaptive_progress_stop_reason_for_note = adaptive_progress_stop_reason
        after_failure_signature = ""
        after_signature_repeated = False
        if is_adaptive_condition and not success:
            after_snapshot, _ = read_latest_snapshot(llmdebug_dir)
            after_failure_signature = _build_failure_signature_for_attempt(
                run=after,
                snapshot=after_snapshot,
            )
            after_signature_repeated = bool(after_failure_signature == failure_signature)
        if is_adaptive_condition:
            adaptive_progress_stop_candidate_for_note, adaptive_progress_stop_reason_for_note = (
                _refresh_adaptive_progress_for_note(
                    note=note,
                    success=success,
                    signature_repeated=(False if success else after_signature_repeated),
                    after_run=after,
                    recomputed_after_patch=True,
                )
            )
            patch_meta["adaptive_postpatch_failure_signature"] = after_failure_signature
            patch_meta["adaptive_postpatch_signature_repeat"] = after_signature_repeated
        (
            adaptive_progress_stop_candidate_for_note,
            adaptive_progress_stop_reason_for_note,
            adaptive_no_progress_token_sum,
            adaptive_no_progress_token_cap_hit,
        ) = _apply_no_progress_token_cap_candidate(
            is_adaptive_condition=is_adaptive_condition,
            adaptive_progress_made=bool(attempt_metadata.get("adaptive_progress_made")),
            adaptive_no_progress_token_sum=adaptive_no_progress_token_sum,
            attempt_tokens_total=_nonnegative_int(attempt_metadata.get("tokens_total"), default=0),
            adaptive_no_progress_token_cap=normalized_adaptive_no_progress_token_cap,
            adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
            adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
        )
        patch_meta["adaptive_no_progress_token_sum"] = adaptive_no_progress_token_sum
        patch_meta["adaptive_no_progress_token_cap"] = normalized_adaptive_no_progress_token_cap
        patch_meta["adaptive_no_progress_token_cap_hit"] = adaptive_no_progress_token_cap_hit
        adaptive_stop_triggered = _mark_adaptive_progress_stop(
            attempt_metadata=attempt_metadata,
            patch_meta=patch_meta,
            is_adaptive_condition=is_adaptive_condition,
            adaptive_progress_stop_candidate=adaptive_progress_stop_candidate_for_note,
            adaptive_progress_stop_reason=adaptive_progress_stop_reason_for_note,
            note=note,
        )
        attempt_wall_sec = _measure_attempt_wall_clock_sec(attempt_start)
        cumulative_runtime_sec += attempt_wall_sec
        pending_credit = _finalize_adaptive_policy_attempt(
            note=note,
            success=success,
            attempt_wall_sec=attempt_wall_sec,
            online_finalize_kwargs=online_finalize_kwargs,
            budget_finalize_kwargs=budget_finalize_kwargs,
        )
        record = _build_attempt_outcome_record(
            case=case,
            condition=condition,
            run_id=run_id,
            attempt=attempt,
            patcher_name=patcher_name,
            run_metadata=effective_run_metadata,
            attempt_metadata=attempt_metadata,
            patcher=patcher_for_attempt,
            patch_meta=patch_meta,
            context_protocol=attempt_context_protocol,
            success=success,
            note=note,
            before=current,
            cumulative_runtime_sec=cumulative_runtime_sec,
            after=after,
            attempt_wall_clock_sec=attempt_wall_sec,
        )
        _queue_pending_online_credit(pending_credit=pending_credit, record=record)
        _append_record(record)
        _append_retry_history_turn_if_enabled(
            retry_context_mode=effective_retry_context_mode,
            retry_history_turns=retry_history_turns,
            attempt=attempt,
            note=note,
            failure_signature=failure_signature,
            patch_meta=patch_meta,
        )

        if success:
            _resolve_pending_online_credits_for_success(success_attempt=attempt)
            break
        if adaptive_stop_triggered:
            break

        # Continue: next attempt uses this failure as evidence.
        previous_failure_context = _enrich_failure_context(
            base=current_failure_context,
            note=note,
            patch_meta=patch_meta,
            run=after,
        )
        current = after

    _resolve_remaining_pending_online_credits()
    return records


def _run_pytest_pooled(
    *,
    cwd: Path,
    python_args: list[str],
    condition: Condition,
    timeout_sec: int,
    output_format: str,
    execution: ExecutionConfig,
    pool: _EvalContainerPool,
) -> RunResult:
    """Run pytest in a pooled container via ``exec_run``."""
    env = _base_pytest_env(
        output_format=output_format,
        snapshot_condition=_is_snapshot_condition(condition),
    )
    if execution.container_mount_repo_src:
        env["PYTHONPATH"] = "/repo_src"
    env.update(execution.container_env)

    command = _build_pytest_args(
        python_cmd="python",
        python_args=python_args,
        condition=condition,
    )

    container = pool.acquire()
    try:
        return container.exec_pytest(
            host_cwd=cwd,
            command=command,
            env=env,
            timeout_sec=timeout_sec,
        )
    finally:
        pool.release(container)


def run_pytest(
    cwd: Path,
    python_args: list[str],
    condition: Condition,
    timeout_sec: int,
    output_format: str,
    *,
    execution: ExecutionConfig | None = None,
) -> RunResult:
    """Run pytest for *condition* using the active execution backend.

    Delegates to ``_run_pytest_local`` or ``_run_pytest_testcontainers``
    depending on the ``ExecutionConfig.executor`` setting.
    """
    active_execution = execution if execution is not None else get_active_execution_config()
    if active_execution.executor == "local":
        return _run_pytest_local(
            cwd=cwd,
            python_args=python_args,
            condition=condition,
            timeout_sec=timeout_sec,
            output_format=output_format,
        )
    if active_execution.executor == "testcontainers":
        pool = _ACTIVE_CONTAINER_POOL
        if pool is not None:
            return _run_pytest_pooled(
                cwd=cwd,
                python_args=python_args,
                condition=condition,
                timeout_sec=timeout_sec,
                output_format=output_format,
                execution=active_execution,
                pool=pool,
            )
        return _run_pytest_testcontainers(
            cwd=cwd,
            python_args=python_args,
            condition=condition,
            timeout_sec=timeout_sec,
            output_format=output_format,
            execution=active_execution,
        )
    raise RuntimeError(f"unsupported executor: {active_execution.executor!r}")


def _build_pytest_args(
    *, python_cmd: str, python_args: list[str], condition: Condition
) -> list[str]:
    args = [python_cmd, *python_args]
    if _is_snapshot_condition(condition):
        if "-m" in python_args and "pytest" in python_args:
            idx = python_args.index("pytest")
            args = [
                python_cmd,
                *python_args[: idx + 1],
                "-p",
                "llmdebug.pytest_plugin",
                *python_args[idx + 1 :],
            ]
        else:
            args = [python_cmd, *python_args, "-p", "llmdebug.pytest_plugin"]
    return args


def _base_pytest_env(*, output_format: str, snapshot_condition: bool) -> dict[str, str]:
    env = {
        "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1",
        "PYTHONDONTWRITEBYTECODE": "1",
        "LLMDEBUG_OUTPUT_FORMAT": output_format,
    }
    if _ACTIVE_RUN_SETTINGS.deterministic and _ACTIVE_RUN_SETTINGS.seed is not None:
        env["PYTHONHASHSEED"] = str(_ACTIVE_RUN_SETTINGS.seed)
        env.setdefault("TZ", "UTC")
    if snapshot_condition:
        env.setdefault("LLMDEBUG_DEBUG", "0")
        env.setdefault("LLMDEBUG_INCLUDE_GIT", "false")
        # Disable built-in hypothesis/category hints: the model should infer diagnosis itself.
        env["LLMDEBUG_CATEGORIZE_ERRORS"] = "0"
    return env


def _run_pytest_local(
    *,
    cwd: Path,
    python_args: list[str],
    condition: Condition,
    timeout_sec: int,
    output_format: str,
) -> RunResult:
    """Run pytest as a local subprocess with timeout handling."""
    env = os.environ.copy()
    env["PYTHONPATH"] = os.pathsep.join([str(_SRC_ROOT), env.get("PYTHONPATH", "")]).strip(
        os.pathsep
    )
    env.update(
        _base_pytest_env(
            output_format=output_format,
            snapshot_condition=_is_snapshot_condition(condition),
        )
    )
    args = _build_pytest_args(
        python_cmd=sys.executable,
        python_args=python_args,
        condition=condition,
    )

    start = time.perf_counter()
    try:
        proc = subprocess.run(
            args,
            cwd=cwd,
            env=env,
            capture_output=True,
            text=True,
            timeout=timeout_sec,
        )
    except subprocess.TimeoutExpired as exc:
        end = time.perf_counter()
        partial_stdout = _coerce_subprocess_output(exc.stdout)
        partial_stderr = _coerce_subprocess_output(exc.stderr)
        timeout_message = f"pytest timed out after {timeout_sec}s"
        stderr = timeout_message if not partial_stderr else f"{partial_stderr}\n{timeout_message}"
        return RunResult(
            returncode=PYTEST_TIMEOUT_RETURNCODE,
            duration_sec=float(end - start),
            stdout=partial_stdout,
            stderr=stderr,
            timed_out=True,
            timeout_sec=timeout_sec,
        )
    end = time.perf_counter()
    return RunResult(
        returncode=int(proc.returncode),
        duration_sec=float(end - start),
        stdout=proc.stdout,
        stderr=proc.stderr,
        timed_out=False,
        timeout_sec=None,
    )


def _status_code_from_wait_result(result: Any) -> int:
    if isinstance(result, int):
        return result
    if isinstance(result, dict):
        raw = result.get("StatusCode")
        if isinstance(raw, int):
            return raw
        try:
            return int(raw)
        except Exception:
            return 1
    return 1


def _is_container_wait_timeout(exc: Exception) -> bool:
    name = type(exc).__name__
    if "ReadTimeout" in name:
        return True
    text = str(exc).lower()
    return "read timed out" in text or "timed out" in text


def _run_pytest_testcontainers(
    *,
    cwd: Path,
    python_args: list[str],
    condition: Condition,
    timeout_sec: int,
    output_format: str,
    execution: ExecutionConfig,
) -> RunResult:
    """Run pytest inside a one-shot Docker container via testcontainers."""
    try:
        from testcontainers.core.container import DockerContainer
    except Exception as exc:
        raise RuntimeError(
            "testcontainers execution requested but dependency is missing "
            f"({type(exc).__name__}: {exc}). Install llmdebug[evals]."
        ) from exc

    container_env = _base_pytest_env(
        output_format=output_format,
        snapshot_condition=_is_snapshot_condition(condition),
    )
    if execution.container_mount_repo_src:
        container_env["PYTHONPATH"] = "/repo_src"
    container_env.update(execution.container_env)

    command = _build_pytest_args(
        python_cmd="python",
        python_args=python_args,
        condition=condition,
    )
    tmpfs_size = max(1, int(execution.container_tmpfs_size_mb))
    kwargs: dict[str, Any] = {
        "working_dir": "/workspace",
        "network_mode": execution.container_network,
        "read_only": True,
        "tmpfs": {"/tmp": f"rw,noexec,nosuid,nodev,size={tmpfs_size}m"},
        "cap_drop": ["ALL"],
        "security_opt": ["no-new-privileges:true"],
        "pids_limit": int(execution.container_pids_limit),
        "mem_limit": execution.container_memory,
        "nano_cpus": int(float(execution.container_cpus) * 1_000_000_000),
        "user": execution.container_user,
        "labels": {
            EVAL_CONTAINER_LABEL_KEY: EVAL_CONTAINER_LABEL_VALUE,
        },
    }

    container = (
        DockerContainer(execution.container_image)
        .with_volume_mapping(str(cwd), "/workspace", mode="rw")
        .with_command(command)
        .with_kwargs(**kwargs)
    )
    if execution.container_mount_repo_src:
        container = container.with_volume_mapping(str(_SRC_ROOT), "/repo_src", mode="ro")
    for key, value in sorted(container_env.items()):
        container = container.with_env(key, value)

    start = time.perf_counter()
    stdout = ""
    stderr = ""
    timed_out = False
    returncode = 1
    try:
        container.start()
        wrapped = container.get_wrapped_container()
        if wrapped is None:
            raise RuntimeError("testcontainer started without wrapped container handle")
        try:
            wait_result = wrapped.wait(timeout=timeout_sec)
            returncode = _status_code_from_wait_result(wait_result)
        except Exception as exc:
            if _is_container_wait_timeout(exc):
                timed_out = True
                returncode = PYTEST_TIMEOUT_RETURNCODE
                try:
                    wrapped.kill()
                except Exception:
                    pass
            else:
                raise RuntimeError(f"container wait failed: {type(exc).__name__}: {exc}") from exc

        out_raw, err_raw = container.get_logs()
        stdout = _coerce_subprocess_output(out_raw)
        stderr = _coerce_subprocess_output(err_raw)
    except RuntimeError:
        raise
    except Exception as exc:
        raise RuntimeError(
            "testcontainers execution failed: "
            f"{type(exc).__name__}: {exc} "
            f"(image={execution.container_image!r}, network={execution.container_network!r})"
        ) from exc
    finally:
        try:
            container.stop()
        except Exception:
            pass

    end = time.perf_counter()
    if timed_out:
        timeout_message = f"pytest timed out after {timeout_sec}s"
        stderr = timeout_message if not stderr else f"{stderr}\n{timeout_message}"
        return RunResult(
            returncode=PYTEST_TIMEOUT_RETURNCODE,
            duration_sec=float(end - start),
            stdout=stdout,
            stderr=stderr,
            timed_out=True,
            timeout_sec=timeout_sec,
        )
    return RunResult(
        returncode=int(returncode),
        duration_sec=float(end - start),
        stdout=stdout,
        stderr=stderr,
        timed_out=False,
        timeout_sec=None,
    )


def _truncate_persisted_pytest_output(
    text: str,
    *,
    tail_lines: int,
    hard_char_cap: int | None = None,
) -> str:
    normalized = text or ""
    normalized_tail_lines = max(0, int(tail_lines))
    if normalized_tail_lines > 0 and normalized:
        had_trailing_newline = normalized.endswith("\n")
        lines = normalized.splitlines()
        total_lines = len(lines)
        if total_lines > normalized_tail_lines:
            dropped_lines = total_lines - normalized_tail_lines
            normalized = "\n".join(lines[-normalized_tail_lines:])
            if had_trailing_newline:
                normalized += "\n"
            normalized = (
                f"...[llmdebug: truncated {dropped_lines} earlier line(s)]...\n{normalized}"
            )

    resolved_hard_char_cap = (
        PERSISTED_PYTEST_OUTPUT_HARD_CHAR_CAP if hard_char_cap is None else hard_char_cap
    )
    normalized_hard_char_cap = max(0, int(resolved_hard_char_cap))
    if normalized_hard_char_cap > 0 and len(normalized) > normalized_hard_char_cap:
        dropped_chars = len(normalized) - normalized_hard_char_cap
        normalized = (
            f"...[llmdebug: truncated {dropped_chars} earlier character(s)]...\n"
            f"{normalized[-normalized_hard_char_cap:]}"
        )
    return normalized


def _truncate_snapshot_string_for_artifact(value: str, *, max_chars: int) -> str:
    normalized_max_chars = max(0, int(max_chars))
    if normalized_max_chars <= 0 or len(value) <= normalized_max_chars:
        return value
    dropped_chars = len(value) - normalized_max_chars
    marker = f"...[llmdebug: snapshot string truncated {dropped_chars} chars]..."
    if normalized_max_chars <= len(marker):
        return marker[:normalized_max_chars]
    keep_chars = normalized_max_chars - len(marker)
    head_chars = keep_chars // 2
    tail_chars = keep_chars - head_chars
    return value[:head_chars] + marker + value[-tail_chars:]


def _truncate_snapshot_for_artifact(
    snapshot: dict[str, Any], *, max_string_chars: int
) -> dict[str, Any]:
    normalized_max_string_chars = max(0, int(max_string_chars))

    def _rewrite(value: Any) -> Any:
        if isinstance(value, str):
            return _truncate_snapshot_string_for_artifact(
                value, max_chars=normalized_max_string_chars
            )
        if isinstance(value, dict):
            return {key: _rewrite(item) for key, item in value.items()}
        if isinstance(value, list):
            return [_rewrite(item) for item in value]
        return value

    rewritten = _rewrite(snapshot)
    return rewritten if isinstance(rewritten, dict) else dict(snapshot)


def _write_run_outputs(
    out_dir: Path,
    run: RunResult,
    *,
    prefix: str,
    tail_lines: int = DEFAULT_PYTEST_OUTPUT_TAIL_LINES,
) -> None:
    stdout = _truncate_persisted_pytest_output(run.stdout or "", tail_lines=tail_lines)
    stderr = _truncate_persisted_pytest_output(run.stderr or "", tail_lines=tail_lines)
    (out_dir / f"{prefix}_stdout.txt").write_text(stdout, encoding="utf-8")
    (out_dir / f"{prefix}_stderr.txt").write_text(stderr, encoding="utf-8")
    combined = stdout + ("\n" if stdout and stderr else "") + stderr
    combined = _truncate_persisted_pytest_output(combined, tail_lines=0)
    (out_dir / f"{prefix}_combined.txt").write_text(combined, encoding="utf-8")


def _remaining_attempt_propose_budget_sec(deadline_monotonic: float | None) -> float | None:
    if deadline_monotonic is None:
        return None
    return max(0.0, float(deadline_monotonic) - time.monotonic())


def _build_attempt_budget_exhausted_patch_result(
    *,
    patcher: Any,
    remaining_budget_sec: float,
) -> Any:
    from evals.patchers.base import PatchResult

    meta: dict[str, Any] = {
        "error": "request_budget_exceeded",
        "effective_timeout_sec": 0.0,
        "remaining_budget_sec": max(0.0, float(remaining_budget_sec)),
        "llm_call_count": 0,
        "tokens_prompt": 0,
        "tokens_completion": 0,
        "tokens_total": 0,
        "usage": {
            "prompt_tokens": 0,
            "completion_tokens": 0,
            "total_tokens": 0,
        },
        "transport_retry_count": 0,
        "attempt_shared_propose_deadline_enabled": True,
        "attempt_shared_propose_budget_exhausted": True,
        "attempt_shared_propose_budget_remaining_sec": max(0.0, float(remaining_budget_sec)),
    }
    patcher_name = str(getattr(patcher, "name", "") or "")
    if patcher_name:
        meta["patcher"] = patcher_name
    model_name = str(getattr(patcher, "model", "") or "")
    if model_name:
        meta["model"] = model_name
    base_url = str(getattr(patcher, "base_url", "") or "")
    if base_url:
        meta["base_url"] = base_url
    response_mode = str(getattr(patcher, "response_mode", "") or "")
    if response_mode:
        meta["response_mode"] = response_mode
    return PatchResult(diff=None, meta=meta)


def _patcher_with_shared_attempt_budget(
    *,
    patcher: Any,
    attempt_deadline_monotonic: float | None,
) -> tuple[Any, float | None, float | None, bool]:
    remaining_budget_sec = _remaining_attempt_propose_budget_sec(attempt_deadline_monotonic)
    if remaining_budget_sec is None:
        return patcher, None, None, False

    if str(getattr(patcher, "name", "")).strip() != "openai":
        return patcher, remaining_budget_sec, None, False

    raw_max_propose_sec = getattr(patcher, "max_propose_sec", 0.0)
    try:
        configured_max_propose_sec = float(raw_max_propose_sec)
    except (TypeError, ValueError):
        configured_max_propose_sec = 0.0
    if configured_max_propose_sec <= 0:
        return patcher, remaining_budget_sec, None, False

    clamped_max_propose_sec = max(0.0, min(configured_max_propose_sec, remaining_budget_sec))
    if abs(clamped_max_propose_sec - configured_max_propose_sec) < 1e-6:
        return patcher, remaining_budget_sec, clamped_max_propose_sec, False

    try:
        clamped_patcher = replace(patcher, max_propose_sec=clamped_max_propose_sec)
    except Exception:
        return patcher, remaining_budget_sec, configured_max_propose_sec, False
    return clamped_patcher, remaining_budget_sec, clamped_max_propose_sec, True


def _propose_patch_with_snapshot_context(
    *,
    patcher: Any,
    prompt: str,
    context_dir: Path,
    cwd: Path,
    snapshot_full_text: str | None,
    snapshot_artifact_text: str | None,
    attempt_deadline_monotonic: float | None = None,
) -> Any:
    """Provide full snapshot context during propose, then restore artifact-capped file."""
    from evals.patchers.base import PatchResult

    (
        effective_patcher,
        remaining_budget_sec,
        call_budget_sec,
        budget_clamped,
    ) = _patcher_with_shared_attempt_budget(
        patcher=patcher,
        attempt_deadline_monotonic=attempt_deadline_monotonic,
    )
    if remaining_budget_sec is not None and remaining_budget_sec <= 0:
        return _build_attempt_budget_exhausted_patch_result(
            patcher=patcher,
            remaining_budget_sec=remaining_budget_sec,
        )

    def _call_propose_patch() -> Any:
        return effective_patcher.propose_patch(prompt=prompt, context_dir=context_dir, cwd=cwd)

    if (
        snapshot_full_text is None
        or snapshot_artifact_text is None
        or snapshot_full_text == snapshot_artifact_text
    ):
        result = _call_propose_patch()
    else:
        snapshot_path = context_dir / "snapshot.json"
        snapshot_path.write_text(snapshot_full_text, encoding="utf-8")
        try:
            result = _call_propose_patch()
        finally:
            snapshot_path.write_text(snapshot_artifact_text, encoding="utf-8")

    if not hasattr(result, "diff") or not hasattr(result, "meta"):
        return result

    result_meta = result.meta
    if not isinstance(result_meta, dict):
        return result
    enriched_meta = dict(result_meta)
    if remaining_budget_sec is not None:
        enriched_meta["attempt_shared_propose_deadline_enabled"] = True
        enriched_meta["attempt_shared_propose_budget_remaining_sec"] = remaining_budget_sec
        if call_budget_sec is not None:
            enriched_meta["attempt_shared_propose_call_budget_sec"] = call_budget_sec
        if budget_clamped:
            enriched_meta["attempt_shared_propose_budget_clamped"] = True
    return PatchResult(diff=result.diff, meta=enriched_meta)


def _coerce_subprocess_output(value: str | bytes | None) -> str:
    if value is None:
        return ""
    if isinstance(value, bytes):
        return value.decode("utf-8", errors="replace")
    return value


def _timeout_fields(run: RunResult) -> dict[str, Any]:
    if run.timed_out:
        return {
            "timed_out": True,
            "timeout_sec": run.timeout_sec,
        }
    return {}


def _enrich_failure_context(
    *,
    base: dict[str, Any],
    note: str,
    patch_meta: dict[str, Any] | None,
    run: RunResult,
) -> dict[str, Any]:
    enriched = dict(base)
    enriched["note"] = str(note or "")
    enriched["timed_out"] = bool(run.timed_out)
    enriched["timeout_sec"] = int(run.timeout_sec) if isinstance(run.timeout_sec, int) else None
    meta = patch_meta if isinstance(patch_meta, dict) else {}
    verifier_reasons_raw = meta.get("patch_verifier_reasons")
    verifier_reasons = (
        [str(item) for item in verifier_reasons_raw]
        if isinstance(verifier_reasons_raw, list)
        else []
    )
    enriched["patch_verifier_rejected"] = bool(meta.get("patch_verifier_enabled")) and (
        meta.get("patch_verifier_passed") is False
    )
    enriched["patch_verifier_reasons"] = verifier_reasons
    enriched["patch_verifier_scope"] = str(meta.get("patch_verifier_scope") or "")
    enriched["patch_verifier_score"] = _adaptive_optional_int(meta.get("patch_verifier_score"))
    enriched["patch_verifier_source_scope_matched"] = _adaptive_optional_bool(
        meta.get("patch_verifier_source_scope_matched")
    )
    return enriched


def read_latest_snapshot(out_dir: Path) -> tuple[dict[str, Any] | None, str | None]:
    """Read the most recent llmdebug snapshot from *out_dir*.

    Returns:
        Tuple of (snapshot_dict_or_None, error_string_or_None).
    """
    try:
        from llmdebug.output import get_latest_snapshot

        snap = get_latest_snapshot(str(out_dir))
        return snap, None
    except Exception as e:
        return None, f"{type(e).__name__}: {e}"


def _build_failure_signature_for_attempt(*, run: RunResult, snapshot: dict[str, Any] | None) -> str:
    """Compute a stable failure signature for an attempt outcome."""
    try:
        from llmdebug.rca_state import build_failure_signature

        return build_failure_signature(snapshot, stdout=run.stdout, stderr=run.stderr)
    except Exception as e:
        sys.stderr.write(
            f"llmdebug eval: build_failure_signature failed, using text fallback: "
            f"{type(e).__name__}: {e}\n"
        )
        text = (run.stderr or "").strip() or (run.stdout or "").strip() or "unknown_failure"
        head = "\n".join([line for line in text.splitlines() if line.strip()][-3:])
        digest = hashlib.sha256(head.encode("utf-8")).hexdigest()[:16]
        return f"TextFailure:{digest}"


def _build_structured_previous_attempt_diff(
    previous_failure_context: dict[str, Any] | None,
    *,
    current_failure_context: dict[str, Any],
) -> dict[str, Any] | None:
    """Build a structured diff between consecutive attempt failures.

    Compares snapshots (when available) or falls back to text-based
    comparison of pytest output.
    """
    if not previous_failure_context:
        return None

    prev_attempt = int(previous_failure_context.get("attempt", 0))
    curr_attempt = int(current_failure_context.get("attempt", 0))
    prev_signature = str(previous_failure_context.get("failure_signature") or "")
    curr_signature = str(current_failure_context.get("failure_signature") or "")

    prev_snapshot = previous_failure_context.get("snapshot")
    curr_snapshot = current_failure_context.get("snapshot")
    if isinstance(prev_snapshot, dict) and isinstance(curr_snapshot, dict):
        try:
            from llmdebug.snapshot_diff import diff_snapshots, structured_diff_summary

            diff = diff_snapshots(prev_snapshot, curr_snapshot)
            structured = structured_diff_summary(diff)
            structured["attempt_prev"] = f"attempt_{prev_attempt:02d}"
            structured["attempt_curr"] = f"attempt_{curr_attempt:02d}"
            return structured
        except Exception as e:
            sys.stderr.write(
                f"llmdebug eval: structured diff failed, using text fallback: "
                f"{type(e).__name__}: {e}\n"
            )
            pass

    prev_output = (
        str(previous_failure_context.get("stderr") or "")
        + "\n"
        + str(previous_failure_context.get("stdout") or "")
    ).strip()
    curr_output = (
        str(current_failure_context.get("stderr") or "")
        + "\n"
        + str(current_failure_context.get("stdout") or "")
    ).strip()

    return {
        "attempt_prev": f"attempt_{prev_attempt:02d}",
        "attempt_curr": f"attempt_{curr_attempt:02d}",
        "failure_signature_same": prev_signature == curr_signature,
        "evidence_delta": {
            "new_frames": [],
            "dropped_frames": [],
            "message_changed": prev_output != curr_output,
        },
        "change_delta": {
            "touched_files": [],
            "touched_functions": [],
        },
        "hypothesis_delta": "unknown",
        "verification_delta": {
            "old_status": "unknown",
            "new_status": "unknown",
            "status_changed": False,
        },
    }


from evals.eval_patches import (  # noqa: E402
    _backup_diff_target_files,
    _build_runner_forced_attempt_diff,
    _compute_patch_path_metrics,
    _extract_candidate_patch_files,
    _is_forced_noop_marker_diff,
    _is_test_file_path,
    _patch_contains_substantive_change,
    _patch_mentions_crash_file,
    _patch_mentions_failing_test_context,
    _patch_mentions_scope_files,
    _render_patch_sanitize_retry_feedback,
    _render_patch_verifier_recovery_feedback,
    _render_syntax_preflight_retry_feedback,
    _restore_diff_target_files,
    _run_patch_sanitization,
    _run_syntax_preflight,
    apply_diff,
)
from evals.eval_prompts import (  # noqa: E402
    _apply_prompt_budget,
    _build_context_compaction_bundle,
    _build_retry_history_section,
    _build_stage_b_prompt_sections,
    _format_snapshot_index_section,
    _format_snapshot_section,  # noqa: F401  # re-exported for tests
    _is_generic_snapshot_justification,
    _render_retry_history_turn,
    _render_retry_packet,
    _should_invoke_context_compaction,
    render_context_compaction_prompt,
    render_prompt,  # noqa: F401  # re-exported for tests
    render_stage_a_request_prompt,
    render_stage_b_patch_prompt,  # noqa: F401  # re-exported for tests
)


def _infer_test_source_scope(
    *,
    cwd: Path,
    crash_file: str | None,
    failing_test_file: str | None,
    enabled: bool,
    file_index_lines: Sequence[str] | None = None,
) -> list[str]:
    if not enabled:
        return []

    candidates: list[str] = []
    if isinstance(crash_file, str) and crash_file and _is_test_file_path(crash_file):
        candidates.append(crash_file)
    if (
        isinstance(failing_test_file, str)
        and failing_test_file
        and _is_test_file_path(failing_test_file)
        and failing_test_file not in candidates
    ):
        candidates.append(failing_test_file)

    inferred: list[str] = []
    for rel in candidates:
        for neighbor in _import_neighbors(root=cwd, rel_path=rel, limit=12):
            normalized = str(PurePosixPath(neighbor.replace("\\", "/")))
            if normalized not in inferred:
                inferred.append(normalized)
        for neighbor in _extract_subprocess_script_neighbors(
            root=cwd,
            rel_path=rel,
            limit=12,
            file_index_lines=file_index_lines,
        ):
            normalized = str(PurePosixPath(neighbor.replace("\\", "/")))
            if normalized not in inferred:
                inferred.append(normalized)
    return inferred


def _score_patch_relevance(
    *,
    diff: str,
    cwd: Path,
    snapshot: dict[str, Any] | None,
    stdout: str,
    stderr: str,
    infer_test_source_scope: bool = True,
    file_index_lines: Sequence[str] | None = None,
) -> tuple[bool, dict[str, Any]]:
    """Heuristic relevance score for a proposed diff.

    Checks whether the patch touches the crash file, its import
    neighbors, the inferred source scope, or test files.  Returns
    (passed, details_dict) with ``passed=True`` when the score
    meets ``PATCH_VERIFIER_MIN_SCORE``.
    """
    patch_files = _extract_candidate_patch_files(diff)
    crash_file = _crash_file_from_snapshot(snapshot)
    crash_neighbors = (
        _import_neighbors(root=cwd, rel_path=crash_file, limit=12) if crash_file else []
    )
    crash_neighbor_set = {str(PurePosixPath(path.replace("\\", "/"))) for path in crash_neighbors}
    failing_test_file = _detect_failing_test_file(
        stdout=stdout,
        stderr=stderr,
        root=cwd,
        file_index_lines=file_index_lines,
    )
    inferred_source_scope = _infer_test_source_scope(
        cwd=cwd,
        crash_file=crash_file,
        failing_test_file=failing_test_file,
        enabled=infer_test_source_scope,
        file_index_lines=file_index_lines,
    )
    inferred_source_scope_set = {
        str(PurePosixPath(path.replace("\\", "/"))) for path in inferred_source_scope
    }

    substantive_change = _patch_contains_substantive_change(diff)
    matches_crash_file = _patch_mentions_crash_file(patch_files=patch_files, crash_file=crash_file)
    matches_crash_neighbor = any(path in crash_neighbor_set for path in patch_files)
    matches_inferred_source_scope = _patch_mentions_scope_files(
        patch_files=patch_files,
        scope_files=inferred_source_scope_set,
    )
    touches_test_file = any(_is_test_file_path(path) for path in patch_files)
    touches_failing_test_file = _patch_mentions_failing_test_context(
        patch_files=patch_files,
        failing_test_file=failing_test_file,
    )

    score = 0
    reasons: list[str] = []
    if substantive_change:
        score += 1
        reasons.append("substantive_change")
    else:
        score -= 2
        reasons.append("no_substantive_change")

    if crash_file:
        if matches_crash_file:
            score += 2
            reasons.append("matches_crash_file")
        elif matches_crash_neighbor:
            score += 1
            reasons.append("matches_crash_neighbor")
        elif matches_inferred_source_scope:
            score += 1
            reasons.append("matches_inferred_source_scope")
        else:
            score -= 1
            reasons.append("misses_crash_scope")
    else:
        if matches_inferred_source_scope:
            score += 1
            reasons.append("matches_inferred_source_scope")
        else:
            reasons.append("no_crash_file_available")

    if touches_failing_test_file:
        score -= 2
        reasons.append("touches_failing_test_file")
    elif touches_test_file:
        score -= 1
        reasons.append("touches_test_file")
    else:
        score += 1
        reasons.append("non_test_edit")

    passed = bool(score >= PATCH_VERIFIER_MIN_SCORE)

    return passed, {
        "patch_verifier_passed": passed,
        "patch_verifier_score": int(score),
        "patch_verifier_min_score": int(PATCH_VERIFIER_MIN_SCORE),
        "patch_verifier_reasons": reasons,
        "patch_verifier_patch_files": patch_files,
        "patch_verifier_crash_file": crash_file,
        "patch_verifier_crash_file_matched": matches_crash_file,
        "patch_verifier_crash_neighbor_matched": matches_crash_neighbor,
        "patch_verifier_crash_neighbors": sorted(crash_neighbor_set),
        "patch_verifier_inferred_source_scope": sorted(inferred_source_scope_set),
        "patch_verifier_source_scope_matched": matches_inferred_source_scope,
        "patch_verifier_failing_test_file": failing_test_file,
        "patch_verifier_touches_test_file": touches_test_file,
        "patch_verifier_touches_failing_test_file": touches_failing_test_file,
        "patch_verifier_substantive_change": substantive_change,
    }


if __name__ == "__main__":
    raise SystemExit(main())
