mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 17:10:50 -10:00
fix(tool-search): name the deferred tools a search activation evicted
The conversation toolbox (ToolActivationCache: 8 names, 16 KiB of serialized schemas, LRU) evicts earlier activations when a search admits new ones. execute_tool_search_inner removed the evicted names from the active set, but the search result carried only `tool_references` and `unavailable_tool_references`. The model was never told. Its next request simply lacked the tool, and #6969 describes a model calling tools that had vanished from its catalog. The direct-call hydration path already says what it loaded ("Tool ... was deferred and has now been loaded"). Search was the silent one. The result now adds `evicted_tool_references`, as typed entries with a reason, plus the same names in metadata, but only when something was evicted. Ordinary search payloads are byte-identical, so existing consumers and the history of unaffected sessions do not change. Only names that actually left the active set are listed: a tool that became eager is filtered out by remove_evicted_cache_activations, stays callable, and is not reported. The reason text holds for every cause activate() can evict for: LRU, or revalidation of a removed, denied or newly-eager tool. docs/TOOL_SURFACE.md and the zh_hans copy describe it, and record the limit: a tool dropped by per-turn revalidation outside a search (turn_loop.rs:1467) is still dropped silently, because no tool result exists to carry the notice. KV-cache effect: none on the prefix. The notice rides the search's own tool result, which is append-only history. Evidence: - A throwaway scenario test (not committed) ran ten searches over ten deferred tools. Searches 1-8 return the old payload unchanged. Search 9 reports alpha0 evicted, and search 10 reports alpha1. The active set ends as alpha2..alpha9, matching the cache. - scripts/dev-test.sh tui tool_search tool_catalog deferred toolbox activation: 88 tests run, 88 passed. - CI-form clippy (-p codewhale-tui --all-targets --all-features --locked) exit 0. cargo fmt --check clean. sync-changelog and check-versions OK. The cost questions in #6969 (hysteresis, wire-byte budget, append-in-context injection) are not addressed here. Refs #6969 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NBKbhx19BRbZtvWK8rVKjz
This commit is contained in:
1 parent
f6583c3ffe
commit
3f0bcb4166
5 files changed
+53
-6
No files matched your search
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- When a `tool_search` activation pushes an earlier deferred tool out of the
|
||||
bounded conversation toolbox (8 tools, 16 KiB of schemas), the search
|
||||
result now names it under `evicted_tool_references`. Before, the tool
|
||||
silently disappeared from the next request, and a model could go on
|
||||
calling it. Thanks @asto18089 for the cost and correctness analysis
|
||||
(Refs #6969).
|
||||
- An ACP client that asks how to turn on Full Access is now told the
|
||||
routes that work: start the server with `codewhale --yolo serve --acp`,
|
||||
or set `yolo = true` in config.toml. The hint used to recommend
|
||||
|
||||
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- When a `tool_search` activation pushes an earlier deferred tool out of the
|
||||
bounded conversation toolbox (8 tools, 16 KiB of schemas), the search
|
||||
result now names it under `evicted_tool_references`. Before, the tool
|
||||
silently disappeared from the next request, and a model could go on
|
||||
calling it. Thanks @asto18089 for the cost and correctness analysis
|
||||
(Refs #6969).
|
||||
- An ACP client that asks how to turn on Full Access is now told the
|
||||
routes that work: start the server with `codewhale --yolo serve --acp`,
|
||||
or set `yolo = true` in config.toml. The hint used to recommend
|
||||
|
||||
@@ -1543,9 +1543,15 @@ fn execute_tool_search_inner(
|
||||
};
|
||||
|
||||
let mut cache_rejected = Vec::new();
|
||||
let mut evicted = Vec::new();
|
||||
if let Some(cache) = cache {
|
||||
let delta = cache.activate(catalog, &discovered);
|
||||
remove_evicted_cache_activations(catalog, active_tools, delta.evicted);
|
||||
remove_evicted_cache_activations(catalog, active_tools, delta.evicted.iter().cloned());
|
||||
evicted = delta
|
||||
.evicted
|
||||
.into_iter()
|
||||
.filter(|name| !active_tools.contains(name))
|
||||
.collect();
|
||||
cache_rejected = delta.rejected;
|
||||
discovered = delta.admitted;
|
||||
}
|
||||
@@ -1575,19 +1581,33 @@ fn execute_tool_search_inner(
|
||||
})
|
||||
}));
|
||||
|
||||
let payload = json!({
|
||||
let mut payload = json!({
|
||||
"type": "tool_search_tool_search_result",
|
||||
"tool_references": references,
|
||||
"unavailable_tool_references": unavailable_references.clone(),
|
||||
});
|
||||
let mut metadata = json!({
|
||||
"tool_references": discovered,
|
||||
"unavailable_tool_references": unavailable_references,
|
||||
});
|
||||
if !evicted.is_empty() {
|
||||
payload["evicted_tool_references"] = evicted
|
||||
.iter()
|
||||
.map(|name| {
|
||||
json!({
|
||||
"type": "evicted_tool_reference",
|
||||
"tool_name": name,
|
||||
"reason": "No longer in this conversation's bounded toolbox (8 cached tools, 16KiB of added serialized schemas), so it is not callable now. Search for it again before using it."
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["evicted_tool_references"] = json!(evicted);
|
||||
}
|
||||
|
||||
Ok(ToolResult {
|
||||
content: serde_json::to_string(&payload).unwrap_or_else(|_| payload.to_string()),
|
||||
success: true,
|
||||
metadata: Some(json!({
|
||||
"tool_references": discovered,
|
||||
"unavailable_tool_references": unavailable_references,
|
||||
})),
|
||||
metadata: Some(metadata),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,15 @@ entry against the current catalog and policy before advertising it again. A
|
||||
session sync clears it. The cache cannot resurrect a removed, denied, or
|
||||
newly-eager tool.
|
||||
|
||||
When a search's activation pushes a previously activated tool out of the
|
||||
cache, that search's result lists it under `evicted_tool_references`, so the
|
||||
model is told the tool is no longer callable rather than finding it missing
|
||||
from the next request. Only tools that actually left the active set are listed;
|
||||
a tool that became eager stays callable and is not reported. Known limit: a
|
||||
tool dropped by the per-turn revalidation (removed, denied, or no longer
|
||||
deferred) outside a search is still dropped silently, because there is no tool
|
||||
result to carry the notice.
|
||||
|
||||
Each subagent gets its own policy-filtered deferred catalog, always-present
|
||||
`tool_search`, and bounded activation cache. Forked messages and instructions
|
||||
remain in context, but the child cache starts empty and discovers tools locally;
|
||||
|
||||
@@ -151,6 +151,12 @@ MCP 工具是动态的。连接成功的服务器会从 `~/.codewhale/mcp.json`
|
||||
让每个条目对照当前目录与策略重新校验。会话同步会清空它。
|
||||
缓存无法让已移除、已被拒绝或刚刚变为预加载的工具复活。
|
||||
|
||||
如果某次搜索的激活把之前激活的工具挤出了缓存,这次搜索的结果会在
|
||||
`evicted_tool_references` 中列出它,告诉模型该工具已不可调用,而不是让它在下一次请求里
|
||||
悄悄消失。只列出真正离开激活集合的工具;刚刚变为预加载的工具仍可调用,不会列出。
|
||||
已知限制:在搜索之外,按回合重新校验时被丢弃的工具(已移除、已被拒绝或不再延迟加载)
|
||||
仍然会被静默丢弃,因为没有工具结果可以携带这条通知。
|
||||
|
||||
每个子智能体都有自己的、经策略过滤的延迟目录,始终存在的 `tool_search`,
|
||||
以及有界的激活缓存。分叉出来的消息和指令仍留在上下文(context)中,
|
||||
但子智能体的缓存从空开始,并在本地发现工具;分叉的上下文和缓存都不能变成发现白名单。
|
||||
|
||||
Reference in new issue
Block a user