molecule-core/workspace-template/tests
rabbitblood 7d732cec3c feat(hermes): escalation ladder — promote to stronger models on transient failure
Ships scoped Phase 3 of the Hermes multi-provider work. Every workspace
can now declare an ordered list of (provider, model) rungs; when the
pinned model hits rate-limit / 5xx / context-length / overload, the
executor advances to the next rung before raising.

## Why

3× Claude Max saturation is a routine occurrence now — the "first 429 on
a batch delegation" is the common path, not the exception. A workspace
pinned to Haiku that hits a context-length limit has no recovery today;
same for Sonnet hitting rate-limit mid-synthesis. Escalation promotes
to the next tier for that single call, preserves coordination, avoids
restart cascades.

## New module: adapters/hermes/escalation.py

- ``LadderRung(provider, model)`` — one config entry.
- ``parse_ladder(raw)`` — tolerant config parser; skips malformed rungs
  with a warning rather than raising so boot stays resilient.
- ``should_escalate(exc) -> bool`` — truth table over 15+ error shapes:
  - Typed classes (RateLimitError, OverloadedError, APITimeoutError,
    APIConnectionError, InternalServerError)
  - Context-length markers (each provider uses different phrasing)
  - Gateway markers (502/503/504, overloaded, temporarily unavailable)
  - Status-code substrings (429, 529, 5xx)
  - Hard-rejects auth failures (401/403/invalid_api_key) even if the
    outer exception class is RateLimitError — wrapping case matters.

## Executor wiring

``HermesA2AExecutor`` now accepts ``escalation_ladder`` in its
constructor + ``create_executor()`` factory. ``_do_inference()`` walks
the ladder:

  1. First attempt = pinned provider:model (matches pre-ladder behaviour)
  2. On escalatable error, try each rung in order
  3. On non-escalatable error, raise immediately (auth, malformed payload)
  4. On exhaustion, raise the last error

Rung switches temporarily rebind ``self.provider_cfg`` / ``self.model``
/ ``self.api_key`` / ``self.base_url`` in a try/finally, so any raised
error leaves the executor in its original state for the next call. Key
resolution for non-pinned rungs goes through ``resolve_provider`` which
reads the rung-provider's env vars fresh.

## Config shape

``config.yaml`` (rendered from ``org.yaml`` → workspace secrets):

    runtime_config:
      escalation_ladder:
        - provider: gemini
          model: gemini-2.5-flash
        - provider: anthropic
          model: claude-sonnet-4-5-20250929
        - provider: anthropic
          model: claude-opus-4-1-20250805

Empty / absent = single-shot behaviour, full backwards-compat with
every existing workspace.

## Tests

34 passing, all isolated (no network):

- ``test_hermes_escalation.py`` (28): parser + truth-table across
  rate-limit, overload, context-length, gateway, auth-reject, unrelated
  exceptions, and case-insensitivity.
- ``test_hermes_ladder_integration.py`` (6): no-ladder single call,
  ladder-not-triggered on success, escalate-on-rate-limit-then-succeed,
  stop-on-non-escalatable, raise-last-error-when-exhausted, skip-
  unknown-provider-in-rung.

## Not in this PR

- Uncertainty-driven escalation (judge pass after successful reply).
- Per-workspace budget tracking (#305 covers this separately).
- Live streaming reuse across rungs (ladder retries the whole call).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 02:27:27 -07:00
..
__init__.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
conftest.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_a2a_cli.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_a2a_client.py fix(security): complete Phase 30.6 auth headers in a2a_client get_peers and discover_peer 2026-04-14 13:23:44 +00:00
test_a2a_executor.py fix(a2a): cancel() event, stateTransitionHistory capability, wire push store (#173 #174 #175) 2026-04-15 17:58:10 +00:00
test_a2a_mcp_server.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_a2a_tools_impl.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_a2a_tools_module.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_adapters.py feat(adapters): add gemini-cli runtime adapter (closes #332) (#379) 2026-04-15 23:30:00 -07:00
test_agent_base_urls.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_agent.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_approval.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_audit.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_awareness_client_full.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_claude_sdk_executor.py fix(claude-sdk): #160 — probe CLI directly when SDK swallowed the real stderr 2026-04-15 11:50:55 -07:00
test_cli_executor.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_common_setup.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_compliance.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_config.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_consolidation.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_coordinator_parent.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_coordinator_routing.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_delegation.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_events.py fix(security): Cycle 5 — auth middleware, injection hardening, skill sandbox 2026-04-14 04:44:42 +00:00
test_executor_helpers.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_first_party_plugins.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_governance.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_heartbeat.py fix(security): Cycle 5 — auth middleware, injection hardening, skill sandbox 2026-04-14 04:44:42 +00:00
test_hermes_adapter.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_hermes_escalation.py feat(hermes): escalation ladder — promote to stronger models on transient failure 2026-04-16 02:27:27 -07:00
test_hermes_ladder_integration.py feat(hermes): escalation ladder — promote to stronger models on transient failure 2026-04-16 02:27:27 -07:00
test_hermes_phase2_dispatch.py feat(hermes): Phase 2d-i — system-prompt.md injection on all 3 dispatch paths 2026-04-15 16:21:47 -07:00
test_hermes_providers.py fix(tests): hermes provider env-var leak broke test_hermes_smoke 2026-04-15 13:59:48 -07:00
test_hermes_smoke.py feat: implement Hermes adapter create_executor() with OpenRouter fallback 2026-04-13 16:47:29 -07:00
test_hitl.py fix(security): hitl task-id ownership + wire fail_open_if_no_scanner in loader (closes #265, #268) 2026-04-15 21:18:52 -07:00
test_main_initial_prompt.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_mcp_memory.py fix(a2a-tools): auth_headers on recall_memory + commit_memory (#304) 2026-04-15 19:12:18 -07:00
test_medo.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_memory.py fix(tests): update memory fakes for auth_headers kwarg + activity overwrite 2026-04-15 17:29:15 -07:00
test_molecule_ai_status.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_namespaces.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_openclaw_adapter.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_platform_auth.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_plugins_builtins_drift.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_plugins_builtins.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_plugins_registry.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_plugins.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_preflight.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_prompt.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_qianfan_provider.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_routing_policy.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_sandbox.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_security_scan.py fix(security): hitl task-id ownership + wire fail_open_if_no_scanner in loader (closes #265, #268) 2026-04-15 21:18:52 -07:00
test_shared_runtime.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_skills_loader.py fix(security): hitl task-id ownership + wire fail_open_if_no_scanner in loader (closes #265, #268) 2026-04-15 21:18:52 -07:00
test_skills_watcher.py fix(security): H1 — replace MD5 with SHA-256 in config/skill watchers 2026-04-14 07:52:07 +00:00
test_telemetry.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_temporal_workflow.py initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
test_transcript_auth.py fix(security): /transcript endpoint fails closed when auth token missing (#328) 2026-04-15 21:17:37 -07:00
test_transcript_lines.py feat: GET /workspaces/:id/transcript — live agent session log 2026-04-15 14:29:43 -07:00
test_watcher.py fix(gate-3): update watcher test to expect SHA-256 hash 2026-04-14 01:21:35 -07:00