Skip to content

fix(tools): availability-gated tools appear when their credential/daemon does (salvage #92693, part 2) - #95053

Closed
teknium1 wants to merge 1 commit into
mainfrom
salv/ts-deferral-b
Closed

teknium1 wants to merge 1 commit into
mainfrom
salv/ts-deferral-b

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

Availability-gated tools (Docker-gated, credential-gated, OAuth-gated) now appear/disappear when their check_fn verdict actually flips, instead of the tool list staying stale for the process lifetime. Split out of #92693 as its own PR because it is the one fix in that set with a caching consequence worth reviewing separately: when the environment genuinely changes mid-conversation, the tool list changes with it — a one-time prompt-prefix bust for that conversation. Stable environments see zero change; the memo keeps hitting and the tool list stays byte-identical.

Root cause: both memo sites keyed on the registry _generation counter, which only moves on registry mutations — a check_fn verdict can flip (credential lands, daemon starts) without any mutation, so the memo kept serving the stale list forever.

Salvaged from #92693 by @alt-glitch, authorship preserved.

Changes

  • tools/registry.py: check_fn_verdict_snapshot() — aggregate snapshot of every cached probe verdict, with its own short-TTL memo (keyed on registry scope, probe scope, generation; cleared by invalidate_check_fn_cache()). Hot-path hit is one dict lookup; a flaky probe coalesces to at most one live re-probe per window.
  • model_tools.py: get_tool_definitions memo key includes the verdict snapshot; TOCTOU guard re-checks the snapshot after compute and skips the store on mismatch (a flip mid-compute can't park a fresh result under a stale key); generation re-take handles probes that lazily register tools.
  • agent/tool_executor.py: the executor's bridge-scope cache (which tools a session may reach through tool_call) gains the same snapshot + config-fingerprint key members and the same TOCTOU guard — the second site of the identical staleness class.
  • tests/tools/test_availability_cache_staleness.py: 5 behavior-level tests (verdict flip changes tool definitions, scope cache observes flips and config changes, snapshot coalesces probes, memo still hits within TTL), red-verified in the original PR.

Validation

Before After
Credential lands mid-session tool never appears until restart appears within ~35s worst case (probe TTL + snapshot TTL), immediately on explicit invalidation
Stable environment memo hits memo hits (verdict tuple unchanged, key identical)
tests — 58 passed (test_availability_cache_staleness.py, test_get_tool_definitions_cache_isolation.py, test_terminal_tool_requirements.py, test_mcp_reload_refreshes_cached_agents.py, test_tool_search.py)

Infographic

availability cache staleness fix

@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 comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists labels Aug 25, 2026
@teknium1
teknium1 force-pushed the salv/ts-deferral-b branch from 63ebda1 to 0383602 Compare August 25, 2026 21:43
…salvage #92693, part 2)

A check_fn verdict flip (credential lands, Docker daemon starts, OAuth
login completes) never invalidated the get_tool_definitions memo or the
executor's bridge-scope cache — the registry generation only moves on
registry MUTATIONS, so the stale tool list survived for the process
lifetime.

Both cache sites now key on an aggregate TTL-cached snapshot of every
probe's verdict (memoized in the registry; hot-path hit is one dict
lookup). A flip propagates within probe TTL + snapshot TTL (~35s worst
case) or immediately on invalidate_check_fn_cache(). Both sites re-check
the snapshot after compute and skip the store on mismatch, so a verdict
flipping mid-compute can't park a fresh result under a stale key.

Note: when a tool's availability genuinely changes mid-conversation, the
tool list changes with it — a one-time prompt-prefix bust for that
conversation. That is the intended behavior of availability-gated tools
(the alternative is the tool never appearing until restart); stable
environments see zero change.

Salvaged from #92693 by @alt-glitch with authorship preserved.
@teknium1
teknium1 force-pushed the salv/ts-deferral-b branch from 0383602 to 96ed052 Compare August 25, 2026 21:43
@github-actions

github-actions Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 96ed052 — fix(tools): stale tool list no longer survives an availabili

⚠️ Warnings

OSV vulnerability scan · View job

7 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 4m25s vs 4m17s (+3.1%). 8 job(s) slower, 3 faster, 1 unchanged.

  • Check contributors / check-attribution: -30.0s
  • OSV scan / Scan lockfiles / osv-scan: +28.0s
  • Python tests / e2e: +27.0s
  • Python lints / ruff enforcement (blocking): +26.0s
  • Check no committed infographics / check-no-committed-infographics: +17.0s

@teknium1

teknium1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Closing on a policy decision (Teknium, Sep 2 2026): availability-gated tools are FROZEN for the life of a session; the tool list only changes on user-consented refresh (/reload-mcp, /new) or at compaction.

Why not this direction: the change removes the memo's shielding rather than adding damping — every check_fn verdict flip (visible within ~35s) becomes a fresh memo key, so every MCP session's next turn republishes tools[]. Main's 60s failure-grace hides fast flapping, but a daemon that is down >60s and back per cycle flips tools twice per cycle (a 2-min outage every 10 min ≈ 12 full-prefix forks/hour on every active session), with no one-bust-per-session cap. The consumer this serves (a credential landing mid-session) is already covered by /reload-mcp, which is explicitly consented cache invalidation; extending it to also call invalidate_check_fn_cache() is ~10 LOC and lands with the freeze work.

Plan: reopen #100638 (@JoaoMarcos44, _merge_preserving_prefix in the shared refresh path) as step 1, persist the resolved tool-name set per session so the gateway agent-cache eviction rebuild doesn't re-probe (step 2), and add the /reload-mcp check_fn hatch. @alt-glitch — 4 of #92693's 5 fixes are on main; this 5th one is superseded by the freeze. Thanks both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants