fix(moa): make reference_timeout default inherit auxiliary config; filter recursion-guard skips

Follow-ups for salvaged #53784:

- reference_timeout now defaults to None = no per-preset override, so the
  reference fan-out inherits auxiliary.moa_reference.timeout (900s default)
  via call_llm's own per-task timeout resolution. The PR's 30.0s default
  would have cut off long-thinking advisors mid-response, and its 300s max
  cap capped legitimate explicit values — both removed. Explicit per-preset
  values are still honored as-is.
- _is_failed_reference also treats '[skipped: …]' recursion-guard notes as
  internal sentinels, keeping them out of both aggregator prompts.
- Dashboard/desktop TS types updated to number | null; web_server validator
  accepts null/empty as 'inherit'.
This commit is contained in:
Teknium 2026-07-23 12:13:10 -07:00
parent 223881e492
commit d3fc27bbf8
7 changed files with 73 additions and 21 deletions

View file

@ -428,7 +428,7 @@ def _run_reference(
*,
temperature: float | None = None,
max_tokens: int | None = None,
reference_timeout: float = 30.0,
reference_timeout: float | None = None,
) -> tuple[str, str, Any]:
"""Call one reference model and return ``(label, text, accounting)``.
@ -573,7 +573,7 @@ def _run_references_parallel(
temperature: float | None = None,
max_tokens: int | None = None,
progress_callback: Any = None,
reference_timeout: float = 30.0,
reference_timeout: float | None = None,
) -> list[tuple[str, str, Any]]:
"""Fan out all reference models in parallel, returning outputs in order.
@ -867,8 +867,15 @@ def _preset_temperature(preset: dict[str, Any], key: str) -> float | None:
def _is_failed_reference(text: str) -> bool:
"""Return whether a reference output is the internal failure sentinel."""
return text.lstrip().lower().startswith("[failed:")
"""Return whether a reference output is an internal failure/skip sentinel.
Covers both the ``[failed: ]`` notes produced when a reference call
raises (which may embed raw provider error text) and the
``[skipped: ]`` recursion-guard notes neither is real advice, so
neither belongs in the aggregator prompt.
"""
sentinel = text.lstrip().lower()
return sentinel.startswith("[failed:") or sentinel.startswith("[skipped:")
def _successful_references(
@ -899,7 +906,7 @@ def aggregate_moa_context(
temperature: float | None = None,
aggregator_temperature: float | None = None,
reference_max_tokens: int | None = None,
reference_timeout: float = 30.0,
reference_timeout: float | None = None,
degraded_reference_policy: str = "loud",
) -> str:
"""Run configured reference models and synthesize their advice.
@ -1346,7 +1353,13 @@ class MoAChatCompletions:
# explicit values. See _preset_temperature.
temperature = _preset_temperature(preset, "reference_temperature")
aggregator_temperature = _preset_temperature(preset, "aggregator_temperature")
reference_timeout = float(preset.get("reference_timeout") or 30.0)
# None (the default) = no per-preset override; the fan-out inherits
# auxiliary.moa_reference.timeout (900s default) via call_llm's own
# per-task timeout resolution. Explicit per-preset values are honored.
raw_reference_timeout = preset.get("reference_timeout")
reference_timeout = (
float(raw_reference_timeout) if raw_reference_timeout else None
)
degraded_reference_policy = str(
preset.get("degraded_reference_policy") or "loud"
)

View file

@ -1019,7 +1019,7 @@ export interface MoaConfigResponse {
reference_max_tokens?: number | null
/** Fan-out cadence (per_iteration | user_turn) — round-tripped. */
fanout?: string
reference_timeout: number
reference_timeout: number | null
}
>
aggregator: MoaModelSlot
@ -1029,7 +1029,7 @@ export interface MoaConfigResponse {
max_tokens: number
reference_models: MoaModelSlot[]
reference_temperature: number
reference_timeout: number
reference_timeout: number | null
}
export interface ModelAssignmentRequest {

View file

@ -21,8 +21,7 @@ DEFAULT_MOA_AGGREGATOR: dict[str, str] = {
"model": "anthropic/claude-opus-4.8",
}
DEFAULT_MOA_REFERENCE_TIMEOUT = 30.0
MAX_MOA_REFERENCE_TIMEOUT = 300.0
DEFAULT_MOA_REFERENCE_TIMEOUT: float | None = None
def _default_reference_models() -> list[dict[str, Any]]:
@ -45,9 +44,17 @@ def _coerce_float_or_none(value: Any) -> float | None:
return None
def _coerce_reference_timeout(value: Any) -> float:
"""Return a finite positive advisor timeout capped at five minutes."""
if isinstance(value, bool):
def _coerce_reference_timeout(value: Any) -> float | None:
"""Return a finite positive advisor timeout, or None to inherit.
``None`` (the default) means "no per-preset override": the reference
fan-out inherits the ``auxiliary.moa_reference.timeout`` config value
(900s by default) via ``call_llm``'s own resolution, exactly like every
other auxiliary task. An explicit finite positive per-preset value is
honored as-is no artificial cap, since long-thinking advisor models
legitimately run far beyond five minutes.
"""
if value is None or value == "" or isinstance(value, bool):
return DEFAULT_MOA_REFERENCE_TIMEOUT
try:
timeout = float(value)
@ -55,7 +62,7 @@ def _coerce_reference_timeout(value: Any) -> float:
return DEFAULT_MOA_REFERENCE_TIMEOUT
if not math.isfinite(timeout) or timeout <= 0:
return DEFAULT_MOA_REFERENCE_TIMEOUT
return min(timeout, MAX_MOA_REFERENCE_TIMEOUT)
return timeout
def _coerce_degraded_reference_policy(value: Any) -> str:

View file

@ -1365,13 +1365,17 @@ class MoaModelSlot(BaseModel):
class _MoaReferenceControls(BaseModel):
reference_timeout: float = 30.0
# None = no per-preset override; the fan-out inherits
# auxiliary.moa_reference.timeout (900s default).
reference_timeout: Optional[float] = None
degraded_reference_policy: Literal["loud", "silent"] = "loud"
@field_validator("reference_timeout", mode="before")
@classmethod
def _validate_reference_timeout(cls, value: Any) -> float:
def _validate_reference_timeout(cls, value: Any) -> Optional[float]:
"""Reject JSON booleans/non-finite values before float coercion."""
if value is None or value == "":
return None
if isinstance(value, bool):
raise ValueError("reference_timeout must be a finite positive number")
try:

View file

@ -754,13 +754,16 @@ def test_reference_failure_controls_are_normalized_per_preset_and_flattened():
@pytest.mark.parametrize("value", [None, "", 0, -1, "bad"])
def test_reference_timeout_invalid_values_fall_back_to_default(value):
assert resolve_moa_preset(_preset(reference_timeout=value), "p")["reference_timeout"] == 30.0
# None = inherit auxiliary.moa_reference.timeout (no per-preset override).
assert resolve_moa_preset(_preset(reference_timeout=value), "p")["reference_timeout"] is None
def test_reference_timeout_is_capped_and_unknown_policy_is_loud():
def test_reference_timeout_is_uncapped_and_unknown_policy_is_loud():
preset = resolve_moa_preset(
_preset(reference_timeout=9999, degraded_reference_policy="wat"), "p"
)
assert preset["reference_timeout"] == 300.0
# Explicit per-preset values are honored as-is — long-thinking advisor
# models legitimately run beyond any fixed cap.
assert preset["reference_timeout"] == 9999.0
assert preset["degraded_reference_policy"] == "loud"

View file

@ -1889,6 +1889,31 @@ def test_reference_filtering_preserves_accounting_triples():
assert _failed_reference_labels(outputs) == ["bad-model"]
def test_reference_filtering_excludes_recursion_guard_skips():
"""[skipped: …] recursion-guard notes are internal sentinels, not advice —
they must be filtered out of the aggregator prompt like [failed: ]."""
from agent.moa_loop import (
_RefAccounting,
_failed_reference_labels,
_is_failed_reference,
_successful_references,
)
from agent.usage_pricing import CanonicalUsage
outputs = [
("good-model", "useful advice", _RefAccounting(CanonicalUsage())),
(
"moa:nested",
"[skipped: MoA presets cannot recursively reference MoA]",
_RefAccounting(CanonicalUsage()),
),
]
assert _is_failed_reference(outputs[1][1])
assert _successful_references(outputs) == [outputs[0]]
assert _failed_reference_labels(outputs) == ["moa:nested"]
def test_aggregate_moa_context_sanitizes_failed_reference_and_forwards_timeout(monkeypatch):
from agent import moa_loop
from agent.usage_pricing import CanonicalUsage

View file

@ -2344,7 +2344,7 @@ export interface MoaConfigResponse {
aggregator: MoaModelSlot;
reference_temperature: number;
aggregator_temperature: number;
reference_timeout: number;
reference_timeout: number | null;
degraded_reference_policy: "loud" | "silent";
max_tokens: number;
/** Optional advisor output cap — round-tripped, not edited here. */
@ -2357,7 +2357,7 @@ export interface MoaConfigResponse {
aggregator: MoaModelSlot;
reference_temperature: number;
aggregator_temperature: number;
reference_timeout: number;
reference_timeout: number | null;
degraded_reference_policy: "loud" | "silent";
max_tokens: number;
enabled: boolean;