Repository navigation
fix(tools): persist tool output reference labels and keep keys unique across runs - #629
Open
Shabonasar wants to merge 4 commits into
Open
Shabonasar wants to merge 4 commits into
Shabonasar wants to merge 4 commits into
Conversation
… across runs
[ref: toolNturnM] labels were added only when the key was live in the current
run's registry, and every run restarted its turn counter at 0. A restored
history therefore had no labels (the first labelled result of each later turn
changed the prefix and missed the provider cache), and keys repeated between
turns, so {{tool0turn0}} from an old turn silently resolved to the current one.
- ON_RUN_STEP_COMPLETED tool_call now carries `ref` and `unresolved_refs`
(direct, early, eager and batch completions); the content aggregator keeps them
- formatAgentMessages restores them as `_refKey` / `_unresolvedRefs`
- annotateMessagesForLLM labels by `_refKey` without a registry lookup
- Run seeds the registry turn counter past the highest restored key, so a
placeholder naming an older turn is reported as unresolved
Fixes LibreChat-AI#627
This was referenced Oct 20, 2026
… across runs
[ref: toolNturnM] labels were added only when the key was live in the current
run's registry, and every run restarted its turn counter at 0. A restored
history therefore had no labels (the first labelled result of each later turn
changed the prefix and missed the provider cache), and keys repeated between
turns, so {{tool0turn0}} from an old turn silently resolved to the current one.
- ON_RUN_STEP_COMPLETED tool_call now carries `ref` and `unresolved_refs`
(direct, early, eager and batch completions); the content aggregator keeps them
- formatAgentMessages restores them as `_refKey` / `_unresolvedRefs`
- annotateMessagesForLLM labels by `_refKey` without a registry lookup
- Run seeds the registry turn counter past the highest restored key, so a
placeholder naming an older turn is reported as unresolved
Fixes LibreChat-AI#627
This was referenced Oct 27, 2026
Author
|
Updated to current
|
Shabonasar
added a commit
to Shabonasar/agents
that referenced
this pull request
Nov 2, 2026
Convenience build for trying the prompt-cache series together; not for review. Tree = main 708c911 + the following PRs (they merge cleanly with each other): - LibreChat-AI#613 fix/prompt-cache-stable-dynamic-placement b38670e - LibreChat-AI#614 feat/tool-dispatcher 83298af - LibreChat-AI#615 fix/responses-tool-round-restore 35020f0 - LibreChat-AI#619 feat/reasoning-replay c618501 (on LibreChat-AI#615) - LibreChat-AI#629 fix/stable-tool-output-ref-labels 4575818 Used by LibreChat branch integration/prompt-cache (LibreChat-AI/LibreChat#17102).
A tool result restored with its stored _refKey was labelled only while the run had a registry. With code execution switched off since the round ran, the run has none, the label disappeared and the request stopped being a prefix continuation of the earlier turns at that tool result.
Shabonasar
added a commit
to Shabonasar/agents
that referenced
this pull request
Nov 6, 2026
Convenience build for trying the prompt-cache series together; not for review. Tree = main 708c911 + the following PRs and fixes: - LibreChat-AI#613 fix/prompt-cache-stable-dynamic-placement b38670e - LibreChat-AI#614 feat/tool-dispatcher 83298af - LibreChat-AI#615 fix/responses-tool-round-restore 35020f0 - LibreChat-AI#619 feat/reasoning-replay c618501 (on LibreChat-AI#615) - LibreChat-AI#629 fix/stable-tool-output-ref-labels 4575818 - fix/prompt-cache-dynamic-only-tail c087c4f - LibreChat-AI#629 follow-up 42c7cbc: keep restored tool output labels when references are off in the run Resolved: the dynamic-tail condition in AgentContext (LibreChat-AI#613 and the fix both change it): the tail moves whenever it is set, for Anthropic, OpenRouter and Bedrock with prompt caching; the system message is omitted when nothing is left in it. Used by LibreChat branch integration/prompt-cache (LibreChat-AI/LibreChat#17102).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of LibreChat-AI/LibreChat#17102
Summary
With tool output references on,
ToolNodelabels each tool result with[ref: tool<i>turn<n>]in the request it sends live. The label was shown only while the key was in the current run's registry, and every run restarted atturn0. As a result, a restored history lost the labels: the first request of each later turn missed the prompt cache, and Claude dropped thinking blocks bound to the old prefix. Keys also repeated between turns, so{{tool0turn0}}from an old turn silently resolved to the current turn's output. With this PR the label is the stored name of the output: it is saved with the result, restored with the history, and turn numbers continue across runs. A restored label is kept in a run with the references off too (in LibreChat: the code interpreter switched off since the round ran), so switching the code interpreter off or on does not rewrite earlier tool results.Fixes #627
Fixes #669
Related to LibreChat-AI/LibreChat#17102. Base:
main, no dependency on other PRs. LibreChat needs no change: the fields ride in the storedtool_callpart.How it works
ref/unresolved_refsfor direct tools, event-driven tools (batch and early completion) and eager executions.[unresolved refs: ...]warning.annotateMessagesForLLMlabels a restored_refKeyalso without a registry (references off in this run). Without any ref metadata in the history it still returns the input array unchanged; the early return without a registry is gone.Changes from the previous version
execute_codeswitched off mid-chat, 57.1% cached, the cache stopped at the first tool round). +64 -40 in 3 files, of which source +5 -8; the PR againstmainis now +544 -158.annotateMessagesForLLM.test.ts"does not iterate any message when the feature is disabled (registry undefined)" is replaced by "labels a restored _refKey without a registry (feature off in this run)", and "returns the input array reference when registry is undefined" now uses a message without_refKey(renamed "... without a registry when no ToolMessage carries metadata");invoke.test.ts"passes messages unchanged when no registry is exposed on context (e.g. summarization)" now expects the label (renamed "labels a restored _refKey when no registry ...").integration/prompt-cache(stableContext: true), custom endpoint withprovider: anthropic, Qwen3.8-27B (vLLM behind NVIDIA Dynamo, through LiteLLM); 7 turns,bash_toolround on turn 2, code interpreter off on turns 4-5, on again on turn 6; two runs each:integration/prompt-cacheRe-enabling on turn 6 costs little before the change because the provider still holds the labelled prefix of turns 1-3. Not run: the Anthropic API itself;
web_searchwas loaded but not called.Type of change
Testing
Tested environments/configuration:
The table comes from an earlier run, before the last change; with the references on in every turn, as there, the requests are the same. Real Anthropic API through LibreChat, 6 turns, adaptive thinking, code interpreter running code, chained
bash_toolcalls, MCP tool, restart after turn 3, turn 5 references a key from turn 1. "series" is the LibreChat series plus agents #613, #614, #615, #619 (LibreChat-AI/LibreChat#17102);devis LibreChatdevwith agentsmain. "before" used a 1h cache TTL, "after" 5m.devdevWith the series, keys run
tool0turn0...tool0turn8without repeats, and the turn-5 probe returns[unresolved refs: tool0turn0]instead of a wrong output. Ondevthe labels are now stable too; its misses come from other restore differences that the series fixes.Automated tests:
maintoo: 3 need API keys (anthropic,google,vertexaillm.spec.ts),LocalExecutionTools.test.tsandlocalToolResultCaps.test.tsfail in the Alpine container.ToolNode.outputReferences.test.ts: key and unresolved refs on the completedtool_call(direct and event-driven), none when the feature is off, an earlier run's key reported as unresolved, a run seeded from the keys in its input history.invoke.test.ts: hydrated run 1 messages labelled, run 2 keys not repeating them.formatAgentMessages.tools.test.ts: stored key and unresolved refs restored and labelled as live, including the parts stored by the content aggregator.toolOutputReferences.test.ts,annotateMessagesForLLM.test.ts: turn counter continuity, labelling by_refKeywithout a registry entry and without a registry.invoke.test.ts"sends a restored tool round with the same bytes when tool output references are off in this run": the OpenAI request built from a restored round is byte-equal with and without the registry (fails without the change at the tool message).durability-checkpoint.integration.test.tsandpersistent-handoffs.test.ts, which need MongoDB and fail onmaintoo. LibreChat agent suites against all five:packages/apisrc/agents6913 passed (1 failure inhandlers.shutdown.spec.ts, a shutdown timing test);apiserver/controllers/agents1371 passed.Screenshots / recordings
No user-facing change.
Risk / compatibility
refand stays unlabelled, as it is today.Checklist