Skip to content

Conditional useTool() changes can invalidate the provider prompt cache #545

Description

@zackleman

Hope this is helpful

The conditional tools guide says that Flue announces tool changes through a resources signal, “keeping the transcript coherent while preserving the provider's prompt cache.”

I don't think that guarantee holds for custom tools mounted with conditional useTool().

At Flue v2.0.0, a rerender does append the resource delta, but it also rebuilds and replaces the native tools array passed to the provider. The appended signal preserves the history of when the tool changed; replacing the provider tool array changes the prompt prefix itself.

This matters because provider-native tool definitions precede the conversation in the cached prompt. Anthropic documents the cache hierarchy as toolssystemmessages and says that modifying tool definitions invalidates the entire cache. OpenAI likewise requires an exact prefix match, including identical tools.

This report is specifically about conditional custom tools. Skills and subagents generally use stable dispatcher tools such as activate_skill and task, so they have a different model-facing shape.

Expected Behavior

Either:

  1. conditional useTool() changes preserve the provider-visible tool prefix through a cache-safe mechanism; or
  2. the documentation explains that resources signals preserve transcript coherence, while native custom-tool changes may still invalidate the prompt cache depending on the provider and model.

Steps to Reproduce

The ReleaseManager pattern from the docs is enough to expose the issue:

'use agent';
import { useModel, usePersistentState, useTool } from '@flue/runtime';
import * as v from 'valibot';

export function ReleaseManager() {
  useModel('anthropic/claude-sonnet-4-6');
  const [approved, setApproved] = usePersistentState('approved', false);

  useTool({
    name: 'record_approval',
    description: 'Record an operator approval code.',
    input: v.object({ code: v.string() }),
    async run() {
      setApproved(true);
      return 'Approval recorded.';
    },
  });

  if (approved) {
    useTool({
      name: 'publish_release',
      description: 'Publish the approved release.',
      input: v.object({}),
      async run() {
        return 'Published.';
      },
    });
  }

  return 'Prepare the release.';
}
  1. Capture the serialized provider request before record_approval completes.
  2. Complete record_approval, causing the agent to rerender with approved === true.
  3. Capture the next provider request.
  4. Compare the two native tools arrays.

The second request should include publish_release, so the native tool prefix is no longer identical. Under Anthropic's normal Messages API cache rules, that invalidates the cached tools/system/messages prefix.

Version Inspected

Relevant Runtime Path

In prepareRerenderTurn(), Flue:

  1. evaluates the agent again;
  2. assigns the newly declared custom tools to this.agentTools;
  3. rebuilds the complete tool collection;
  4. replaces this.agentLoop.state.tools;
  5. returns that array as the next Pi Context.tools value; and
  6. separately appends the resource delta narration.

The resource implementation also notes that a changed schema reaches the model through the native request tool array.

The resources signal is useful, but it is appended after the existing history. It cannot make an earlier, changed tool prefix identical again.

Provider references:

Suggested First Step

I would start by qualifying the documentation. The current runtime can keep the transcript coherent, but it cannot make a general cache-preservation guarantee for conditional custom tools while it replaces the native provider tool array.

For a runtime fix, the existing resource reconciliation around prepareRerenderTurn() / narrateResourceDelta() looks like the natural place to persist a chronological tool-availability delta and let each provider adapter choose the strongest mapping it supports.

Acceptance Criteria

  • The docs distinguish transcript coherence from provider prompt-cache preservation.
  • Conditional tool behavior is documented for additions, removals, schema updates, and unsupported provider/model fallbacks.
  • Cache-safe provider paths keep the original native tool prefix stable and express availability changes later in the conversation.
  • The runtime rejects calls to tools that are no longer live, even if the model saw them earlier.
  • Tool-availability changes survive persistence, recovery, replay, join/resume, and compaction.
  • Tests compare raw serialized provider requests across add, remove, and update transitions and assert provider cache metrics where available.

Related: #456 covered dynamic tool discovery. This is narrower: it concerns the documented cache-preservation guarantee for deterministic conditional useTool() changes.

Verification Note

This is based on source inspection of the Flue and Pi paths plus the providers' documented caching contracts. A regression test that records the outgoing payload and provider-reported cache tokens before and after the transition would provide the final end-to-end confirmation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions