Skip to content

fix(agent): honor prompt_caching.cache_ttl=off on MoA and fallback stub paths - #76113

Closed
686f6c61 wants to merge 4 commits into
NousResearch:mainfrom
686f6c61:fix/76085-honor-cache-ttl-off-on-stubs
Closed

fix(agent): honor prompt_caching.cache_ttl=off on MoA and fallback stub paths#76113
686f6c61 wants to merge 4 commits into
NousResearch:mainfrom
686f6c61:fix/76085-honor-cache-ttl-off-on-stubs

Conversation

@686f6c61

@686f6c61 686f6c61 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

prompt_caching.cache_ttl: off sets _cache_disabled on the real AIAgent in init_agent. Main-loop requests honor that flag. MoA advisor/aggregator decoration and the shared plan_cache_sections_for_destination() helper do not: they resolve policy against a blank SimpleNamespace stub, so getattr(stub, "_cache_disabled", False) is always false and cache_control markers still get injected.

That breaks the documented contract from #33555 that the config disable is global. Operators who turn caching off (providers that mishandle markers, OAuth extra-usage concerns, debugging) still saw markers on:

  • MoA aggregator requests
  • MoA advisor requests on cache-honoring slots
  • Auxiliary / MoA destinations planned through plan_cache_sections_for_destination

Fix

  1. Add prompt_caching_disabled_from_config() with the same TTL disable detection as init_agent.
  2. Stamp _cache_disabled onto the policy stub in:
    • plan_cache_sections_for_destination() (optional cache_disabled param; defaults to config)
    • _maybe_apply_moa_cache_control() (same)
  3. MoA acting-aggregator path passes getattr(self._agent, "_cache_disabled", False) so a live agent disable wins when available.

Caching-enabled routes are unchanged: with cache_disabled=False / default TTL, native Anthropic destinations still receive breakpoints.

Related Issue

Fixes #76085

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Tests (adding or improving test coverage)

Changes Made

  • agent/agent_runtime_helpers.py — config helper + stub flag in plan_cache_sections_for_destination
  • agent/moa_loop.py — stub flag in _maybe_apply_moa_cache_control; pass agent flag from prepared aggregator
  • tests/agent/test_cache_disabled_on_stubs.py — config detection, plan-path strip, MoA decoration strip, enabled path still marks

How to Test

pytest tests/agent/test_cache_disabled_on_stubs.py -v

Docker (ghcr.io/astral-sh/uv:python3.12-bookworm-slim + uv pip install -e ".[dev]"): 7/7 passed.

Manual:

  1. Set in config:
    prompt_caching:
      cache_ttl: false
  2. Run an MoA turn or a path that hits auxiliary fallback replan against a Claude destination
  3. Confirm request payloads no longer carry cache_control markers

Checklist

Code

Documentation & Housekeeping

  • Docs — N/A (restores existing documented disable)
  • Config example — N/A
  • Cross-platform — N/A
  • Tool schemas — N/A

Notes

I defaulted the stub paths to read config when the caller does not pass an explicit flag so auxiliary fallback does not need a full agent object. When MoA has self._agent, the agent flag is still threaded for consistency with runtime state after init.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API area/config Config system, migrations, profiles P0 Critical — data loss, security, crash loop sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) labels Aug 1, 2026
Blank SimpleNamespace stubs used by MoA decoration and
plan_cache_sections_for_destination never set _cache_disabled, so
anthropic_prompt_cache_policy re-injected cache_control markers after
operators turned caching off. Stamp the disable onto those stubs from
an explicit flag or the live config, and pass the agent flag from the
MoA aggregator path.

Fixes NousResearch#76085
Avoid F401 from ruff/pyflakes on the NousResearch#76085 regression file.
@686f6c61
686f6c61 force-pushed the fix/76085-honor-cache-ttl-off-on-stubs branch from cc71231 to e16781f Compare August 1, 2026 11:41
@686f6c61

686f6c61 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and force-pushed.

Follow-up from review hygiene elsewhere:

  • dropped an unused pytest import (F401 risk)
  • tests call production helpers (plan_cache_sections_for_destination, _maybe_apply_moa_cache_control) with config/stubs — no source-text pins

Tip is e16781f. Happy for review.

@686f6c61

686f6c61 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Note for maintainers: #76121 opened later against the same issue (#76085) with a similar approach (stamp _cache_disabled onto the blank policy stubs for MoA + fallback replan).

This PR was first (opened ~11:07 UTC; #76121 ~11:35 UTC). Happy to fold any extra coverage from #76121 (e.g. auxiliary client tests) into this branch if useful, or to stand down if you prefer consolidating there — just say which to keep.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tracing the blank-stub policy paths; the current-main premise is real: agent/agent_runtime_helpers.py:1889 and agent/moa_loop.py:409 both pass a stub without _cache_disabled to the policy.

Problems

  • agent/moa_loop.py:1698 accesses self._agent directly and coerces an unavailable agent to False. The existing prepared-aggregator test builds the facade via MoAChatCompletions.__new__ without setting _agent (tests/agent/test_moa_aggregator_cache_control.py:133-153), so this raises inside the planner try, skips planning, and breaks its expected tool marker. An explicitly None agent would also suppress the new planner's config fallback.

Suggested changes

  • Preserve a tri-state value here: use getattr(self, "_agent", None) and pass None when no live _cache_disabled snapshot exists, allowing plan_cache_sections_for_destination() to read config as intended.
  • Add a regression for the prepared-aggregator no-agent/config-off path.

Automated hermes-sweeper review.

Comment thread agent/moa_loop.py
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Aug 1, 2026
Prepared-aggregator facades built via __new__ lack _agent. Accessing
self._agent raised inside the planner try and bool-coercion of a missing
snapshot forced False, suppressing config fallback for cache_ttl=off.
Pass a tri-state value and add a no-agent/config-off regression.
@686f6c61

686f6c61 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — addressed on the latest tip.

  • Prepared-aggregator path now uses getattr(self, "_agent", None) and passes a tri-state cache_disabled (None when no live agent / no _cache_disabled snapshot) so plan_cache_sections_for_destination() can still read config instead of raising or forcing False.
  • Added a regression: MoAChatCompletions.__new__ facade with no _agent + prompt_caching.cache_ttl=off must not inject cache_control.

Docker: test_cache_disabled_on_stubs.py + test_moa_aggregator_cache_control.py 11/11 passed.

@686f6c61

686f6c61 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@JoaoMarcos44 thanks for the careful side-by-side — that is exactly the coordination I was hoping for.

I ported the pieces from #76121 that were not yet here onto this tip:

  1. blank_cache_policy_stub() — single factory used by both plan_cache_sections_for_destination and _maybe_apply_moa_cache_control, so _cache_disabled cannot be left off a hand-rolled stub again.
  2. Advisor + one-shot synthesis threading_run_references_parallel / _run_reference and aggregate_moa_context now pin the live agent disable onto decoration instead of only re-reading config.
  3. Kept our earlier tri-state no-agent prepared-aggregator fix and its regression.
  4. Added factory / synthesis / advisor-runtime regressions in tests/agent/test_cache_disabled_on_stubs.py.

Docker: test_cache_disabled_on_stubs.py + test_moa_aggregator_cache_control.py 15/15 passed.

If maintainers are happy merging this branch as the single path, you can close #76121 as superseded; if they prefer yours as the merge target I am fine either way. Thanks again for the comparison.

@JoaoMarcos44

Copy link
Copy Markdown
Contributor

Closed #76121 as superseded — verified your ported changes match (pulled the branch, diffed, reran the tests locally, 15/15 + 193 passed on the broader suite). Appreciate you folding those in cleanly.

One ask: since 15599816 absorbs the blank_cache_policy_stub factory and the advisor/one-shot-synthesis threading from #76121, could you add a Co-authored-by: trailer to that commit (or a follow-up commit) crediting the source?

Co-authored-by: JoaoMarcos44 <87440198+JoaoMarcos44@users.noreply.github.com>

That's the standard way GitHub attributes a merged commit to both authors. Thanks again for coordinating on this.

Absorb the useful deltas from the parallel NousResearch#76121 approach: a single
blank_cache_policy_stub factory so _cache_disabled cannot be left off
hand-rolled SimpleNamespaces, and pin the live agent disable onto MoA
advisor fan-out and one-shot aggregate_moa_context decoration so those
paths track conversation state rather than a fresh config re-read.

Keeps the earlier tri-state prepared-aggregator no-agent fix. Adds
factory and synthesis/advisor regressions.

Coordinates with NousResearch#76121 / NousResearch#76085.

Co-authored-by: JoaoMarcos44 <87440198+JoaoMarcos44@users.noreply.github.com>
@686f6c61
686f6c61 force-pushed the fix/76085-honor-cache-ttl-off-on-stubs branch from 1559981 to ebf54b1 Compare August 1, 2026 12:58
@686f6c61

686f6c61 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@JoaoMarcos44 done — amended the consolidate commit with your trailer and force-pushed.

Tip is now ebf54b1 (same tree as 15599816, with):

Co-authored-by: JoaoMarcos44 <87440198+JoaoMarcos44@users.noreply.github.com>

Thanks again for closing #76121 as superseded and for the careful verification.

kshitijk4poor added a commit that referenced this pull request Aug 2, 2026
Attribution entry for PR #76113 salvage (github@00b.tech -> 686f6c61).
kshitijk4poor added a commit to kshitijk4poor/hermes-agent that referenced this pull request Aug 2, 2026
…d stub paths

Follow-ups from review of NousResearch#76113:
- Extract cache_ttl_means_disabled() as the single disable-synonym
  predicate; agent_init and prompt_caching_disabled_from_config both use
  it so the two detection sites can no longer drift (drift would recreate
  the NousResearch#76085 bug class).
- Mirror _run_reference's not-None injection guard in
  aggregate_moa_context (stamping None was a harmless no-op copy).
- Replace a vacuous trailing test assertion with the intended
  input-non-mutation check; drop a stray blank line.
- Add a predicate-parity regression test (unknown TTL values keep
  caching enabled, matching historical agent_init semantics).
kshitijk4poor added a commit that referenced this pull request Aug 2, 2026
…d stub paths

Follow-ups from review of #76113:
- Extract cache_ttl_means_disabled() as the single disable-synonym
  predicate; agent_init and prompt_caching_disabled_from_config both use
  it so the two detection sites can no longer drift (drift would recreate
  the #76085 bug class).
- Mirror _run_reference's not-None injection guard in
  aggregate_moa_context (stamping None was a harmless no-op copy).
- Replace a vacuous trailing test assertion with the intended
  input-non-mutation check; drop a stray blank line.
- Add a predicate-parity regression test (unknown TTL values keep
  caching enabled, matching historical agent_init semantics).
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #76622 — your four commits were cherry-picked onto current main with authorship preserved (rebase merge, no squash), including the Co-authored-by credit for @JoaoMarcos44 from the #76121 consolidation.

Commits on main: 8a29703, 67db870, 8e1a351, 8ee5174 (+ 88a629b, a small follow-up from review: shared cache_ttl_means_disabled() predicate so agent_init and the stub paths can't drift, consistent injection guard in aggregate_moa_context, and a strengthened test assertion).

Review verification before merge: caching-enabled output verified byte-identical to main across native Anthropic / OpenRouter Claude / non-caching routes (the disable now strips markers on all stub paths), plus a mutation check proving the new tests fail when the stub flag is defeated. Solid fix and excellent coordination with the duplicate PR — thanks!

@686f6c61

686f6c61 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @kshitijk4poor — glad the four commits landed via #76622 with authorship preserved, and the follow-up (cache_ttl_means_disabled() shared predicate + stronger tests) is a solid hardening of the disable path. Appreciate the careful verification.

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Attribution entry for PR NousResearch#76113 salvage (github@00b.tech -> 686f6c61).
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…d stub paths

Follow-ups from review of NousResearch#76113:
- Extract cache_ttl_means_disabled() as the single disable-synonym
  predicate; agent_init and prompt_caching_disabled_from_config both use
  it so the two detection sites can no longer drift (drift would recreate
  the NousResearch#76085 bug class).
- Mirror _run_reference's not-None injection guard in
  aggregate_moa_context (stamping None was a harmless no-op copy).
- Replace a vacuous trailing test assertion with the intended
  input-non-mutation check; drop a stray blank line.
- Add a predicate-parity regression test (unknown TTL values keep
  caching enabled, matching historical agent_init semantics).
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
Attribution entry for PR NousResearch#76113 salvage (github@00b.tech -> 686f6c61).
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…d stub paths

Follow-ups from review of NousResearch#76113:
- Extract cache_ttl_means_disabled() as the single disable-synonym
  predicate; agent_init and prompt_caching_disabled_from_config both use
  it so the two detection sites can no longer drift (drift would recreate
  the NousResearch#76085 bug class).
- Mirror _run_reference's not-None injection guard in
  aggregate_moa_context (stamping None was a harmless no-op copy).
- Replace a vacuous trailing test assertion with the intended
  input-non-mutation check; drop a stray blank line.
- Add a predicate-parity regression test (unknown TTL values keep
  caching enabled, matching historical agent_init semantics).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P0 Critical — data loss, security, crash loop provider/anthropic Anthropic native Messages API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

prompt_caching.cache_ttl disable is bypassed on stub-resolved cache-plan paths (MoA aggregator/advisors, auxiliary fallbacks)

5 participants