ac06c2ff8b
Every empty-response retry re-sends the full conversation input at full price. On large contexts a single turn that produces no visible output could bill the user several dollars across the 3-retry + fallback-chain walk (reported: ~$2.33 for one empty answer on a ~26K-token session). Signaled refusals (finish_reason=content_filter, Anthropic refusal stop_reason, guardrail interventions) are already terminal today and never reach this loop. The uncovered class is *unsignaled* refusals: the provider returns 200 with zero output tokens and a generic finish reason. Those are deterministic — resending the identical prompt reproduces the same empty — so burning the remaining retry budget only multiplies the charge. New agent/empty_response_guard.py, two independent guards, both failing OPEN to today's behaviour: - Deterministic-empty detection: two consecutive empty attempts with usage present, output_tokens == 0 (reasoning tokens count as output), and identical (model, provider, finish_reason) skip the remaining retries and go straight to the fallback chain — a different model may well answer. Missing usage, nonzero output, or any signature change keeps the full budget. - Cost-aware retry budget: when one attempt's estimated input cost exceeds HERMES_EMPTY_RETRY_COST_THRESHOLD_USD (default $0.25), the empty-retry budget drops 3 -> 1 for that streak. Unknown pricing or included/subscription routes are untouched. At exhaustion the status trace now includes the estimated cost of the empty attempts so the charge is at least explained in-session. Streak state lives on the agent and self-clears whenever _empty_content_retries resets to 0, transparently honouring every existing reset site (turn start, tool success, compaction, fallback activation) without touching them. Set HERMES_DETERMINISTIC_EMPTY_GUARD=0 to disable both guards. Tests: tests/agent/test_empty_response_guard.py (26 unit tests) plus two loop-level integration tests in tests/run_agent/test_run_agent.py proving the api_call reduction and the fail-open path. Refs NS-503.