mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 05:53:55 +00:00
fix(agents): keep exec visible for lean local models (#101607)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
committed by
GitHub
parent
bd7da9decd
commit
7c0a7c8be2
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Lean local model shell access:** keep `exec` directly visible beside the default structured Tool Search controls so coding-tuned local models can use their shell fallback instead of searching for missing domain tools. (#87587) Thanks @vincentkoc.
|
||||
- **OAuth refresh contention diagnostics:** keep local lock paths out of user-facing refresh failures and avoid duplicate failure prefixes while preserving structured provider and profile classification. (#83383) Thanks @vincentkoc.
|
||||
- **Exec approval prompts:** keep background-disabled fallback warnings out of pending gateway/node approvals and show them only after a command actually runs in the foreground. (#78184) Thanks @vincentkoc.
|
||||
- **Direct poll delivery:** route direct and hybrid channel polls through the owning outbound adapter while preserving gateway-mode routing and channel option checks. (#99950) Thanks @NianJiuZst.
|
||||
|
||||
@@ -29,6 +29,8 @@ Experimental features are opt-in preview surfaces behind explicit flags. They ne
|
||||
|
||||
If you already tune Tool Search globally, OpenClaw leaves that config alone. Set `tools.toolSearch: false` to opt out of the lean-mode Tool Search default.
|
||||
|
||||
In structured `tools` mode, lean runs keep `exec` directly visible beside the Tool Search controls so coding-tuned local models can still choose their familiar shell path. This changes schema visibility only: normal tool policy, sandboxing, and exec approvals still apply. Explicit `code` and `directory` modes keep their normal compaction behavior.
|
||||
|
||||
### Why these tools
|
||||
|
||||
These tools have the largest descriptions, broadest parameter shapes, or highest chance of distracting a small model from the normal coding and conversation path. On a small-context or stricter OpenAI-compatible backend that is the difference between:
|
||||
|
||||
@@ -263,7 +263,7 @@ If the model loads cleanly but full agent turns misbehave, work top-down: confir
|
||||
openclaw infer model run --gateway --model <provider/model> --prompt "Reply with exactly: pong" --json
|
||||
```
|
||||
|
||||
3. **Try lean mode** if both probes pass but real agent turns fail with malformed tool calls or oversized prompts: set `agents.defaults.experimental.localModelLean: true`. It drops heavyweight browser, cron, message, media-generation, voice, and PDF tools unless explicitly required, and defaults larger tool catalogs behind structured Tool Search controls. See [Experimental Features -> Local model lean mode](/concepts/experimental-features#local-model-lean-mode) for details and how to confirm it's on.
|
||||
3. **Try lean mode** if both probes pass but real agent turns fail with malformed tool calls or oversized prompts: set `agents.defaults.experimental.localModelLean: true`. It drops heavyweight browser, cron, message, media-generation, voice, and PDF tools unless explicitly required, and defaults larger tool catalogs behind structured Tool Search controls while keeping `exec` directly visible. See [Experimental Features -> Local model lean mode](/concepts/experimental-features#local-model-lean-mode) for details and how to confirm it's on.
|
||||
|
||||
4. **Disable tools entirely as a last resort** by setting `models.providers.<provider>.models[].compat.supportsTools: false` for that model - the agent then runs without tool calls.
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ import {
|
||||
filterLocalModelLeanTools,
|
||||
isLocalModelLeanEnabled,
|
||||
resolveLocalModelLeanPreserveToolNames,
|
||||
shouldCatalogToolForLocalModelLean,
|
||||
} from "../../local-model-lean.js";
|
||||
import { resolveModelAuthMode } from "../../model-auth.js";
|
||||
import { resolveDefaultModelForAgent } from "../../model-selection.js";
|
||||
@@ -1319,6 +1320,11 @@ export async function runEmbeddedAttempt(
|
||||
sandboxToolPolicy: sandbox?.tools,
|
||||
runtimeToolAllowlist: effectiveToolsAllow,
|
||||
});
|
||||
const localModelLeanEnabled = isLocalModelLeanEnabled({
|
||||
config: params.config,
|
||||
agentId: sessionAgentId,
|
||||
sessionKey: params.sessionKey,
|
||||
});
|
||||
const localModelLeanPreserveToolNames = resolveLocalModelLeanPreserveToolNames({
|
||||
toolNames: runtimeCapabilityProfile.policy.explicitToolOverrideAllowlist,
|
||||
forceMessageTool: params.forceMessageTool,
|
||||
@@ -1813,6 +1819,10 @@ export async function runEmbeddedAttempt(
|
||||
runId: params.runId,
|
||||
catalogRef: toolSearchCatalogRef,
|
||||
toolHookContext: catalogToolHookContext,
|
||||
shouldCatalogTool:
|
||||
localModelLeanEnabled && toolSearchConfig.mode === "tools"
|
||||
? shouldCatalogToolForLocalModelLean
|
||||
: undefined,
|
||||
});
|
||||
const projectedToolSearchTools = filterLocalModelLeanTools({
|
||||
tools: toolSearch.tools,
|
||||
@@ -2841,10 +2851,7 @@ export async function runEmbeddedAttempt(
|
||||
agentId: sessionAgentId,
|
||||
messageProvider: params.messageProvider,
|
||||
messageChannel: params.messageChannel,
|
||||
localModelLean: isLocalModelLeanEnabled({
|
||||
config: params.config,
|
||||
agentId: sessionAgentId,
|
||||
}),
|
||||
localModelLean: localModelLeanEnabled,
|
||||
toolCount: effectiveTools.length,
|
||||
clientToolCount: clientToolDefs.length,
|
||||
});
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { createStubTool } from "../test-helpers/agent-tool-stubs.js";
|
||||
import {
|
||||
testing,
|
||||
TOOL_CALL_RAW_TOOL_NAME,
|
||||
TOOL_DESCRIBE_RAW_TOOL_NAME,
|
||||
TOOL_SEARCH_CODE_MODE_TOOL_NAME,
|
||||
TOOL_SEARCH_RAW_TOOL_NAME,
|
||||
} from "../tool-search.js";
|
||||
import { createAgentHarnessToolSurfaceRuntime } from "./tool-surface-bridge.js";
|
||||
|
||||
function tools(names: string[]) {
|
||||
return names.map(createStubTool);
|
||||
}
|
||||
|
||||
function createRuntime(config: OpenClawConfig) {
|
||||
return createAgentHarnessToolSurfaceRuntime({
|
||||
config,
|
||||
executeTool: async () => ({ content: [], details: {} }),
|
||||
modelToolsEnabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
describe("createAgentHarnessToolSurfaceRuntime", () => {
|
||||
it("filters raw SDK tools but does not refilter prepared constructor output", () => {
|
||||
const config: OpenClawConfig = {
|
||||
agents: { defaults: { experimental: { localModelLean: true } } },
|
||||
tools: { alsoAllow: ["image_generate"], toolSearch: { enabled: false } },
|
||||
};
|
||||
const runtime = createAgentHarnessToolSurfaceRuntime({
|
||||
config,
|
||||
executeTool: async () => ({ content: [], details: {} }),
|
||||
modelToolsEnabled: true,
|
||||
});
|
||||
const runtime = createRuntime(config);
|
||||
|
||||
expect(
|
||||
runtime
|
||||
@@ -31,4 +42,51 @@ describe("createAgentHarnessToolSurfaceRuntime", () => {
|
||||
).toEqual(["read", "browser"]);
|
||||
runtime.cleanup();
|
||||
});
|
||||
|
||||
it("keeps exec direct in lean structured Tool Search mode", () => {
|
||||
const config: OpenClawConfig = {
|
||||
agents: { defaults: { experimental: { localModelLean: true } } },
|
||||
};
|
||||
const runtime = createRuntime(config);
|
||||
|
||||
expect(
|
||||
runtime
|
||||
.compactTools(
|
||||
tools([
|
||||
TOOL_SEARCH_RAW_TOOL_NAME,
|
||||
TOOL_DESCRIBE_RAW_TOOL_NAME,
|
||||
TOOL_CALL_RAW_TOOL_NAME,
|
||||
"exec",
|
||||
"read",
|
||||
]),
|
||||
)
|
||||
.tools.map((tool) => tool.name),
|
||||
).toEqual([
|
||||
TOOL_SEARCH_RAW_TOOL_NAME,
|
||||
TOOL_DESCRIBE_RAW_TOOL_NAME,
|
||||
TOOL_CALL_RAW_TOOL_NAME,
|
||||
"exec",
|
||||
]);
|
||||
runtime.cleanup();
|
||||
});
|
||||
|
||||
it("preserves explicit code-mode compaction for lean runs", () => {
|
||||
testing.setToolSearchCodeModeSupportedForTest(true);
|
||||
try {
|
||||
const config: OpenClawConfig = {
|
||||
agents: { defaults: { experimental: { localModelLean: true } } },
|
||||
tools: { toolSearch: { mode: "code" } },
|
||||
};
|
||||
const runtime = createRuntime(config);
|
||||
|
||||
expect(
|
||||
runtime
|
||||
.compactTools(tools([TOOL_SEARCH_CODE_MODE_TOOL_NAME, "exec", "read"]))
|
||||
.tools.map((tool) => tool.name),
|
||||
).toEqual([TOOL_SEARCH_CODE_MODE_TOOL_NAME]);
|
||||
runtime.cleanup();
|
||||
} finally {
|
||||
testing.setToolSearchCodeModeSupportedForTest(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,9 @@ import { resolveConversationCapabilityProfile } from "../conversation-capability
|
||||
import {
|
||||
applyLocalModelLeanToolSearchDefaults,
|
||||
filterLocalModelLeanTools,
|
||||
isLocalModelLeanEnabled,
|
||||
resolveLocalModelLeanPreserveToolNames,
|
||||
shouldCatalogToolForLocalModelLean,
|
||||
} from "../local-model-lean.js";
|
||||
import { filterRuntimeCompatibleTools } from "../tool-schema-projection.js";
|
||||
import {
|
||||
@@ -76,6 +78,11 @@ export function createAgentHarnessToolSurfaceRuntime(params: {
|
||||
}): AgentHarnessToolSurfaceRuntime {
|
||||
const forceDirectMessageTool =
|
||||
params.forceMessageTool === true || params.sourceReplyDeliveryMode === "message_tool_only";
|
||||
const localModelLeanEnabled = isLocalModelLeanEnabled({
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
sessionKey: params.sessionKey,
|
||||
});
|
||||
const codeModeConfig = resolveCodeModeConfig(params.config, params.agentId);
|
||||
const toolSearchRuntimeConfig = forceDirectMessageTool
|
||||
? params.config
|
||||
@@ -200,6 +207,10 @@ export function createAgentHarnessToolSurfaceRuntime(params: {
|
||||
runId: params.runId,
|
||||
catalogRef: toolSearchCatalogRef,
|
||||
toolHookContext: options.hookContext,
|
||||
shouldCatalogTool:
|
||||
localModelLeanEnabled && toolSearchConfig.mode === "tools"
|
||||
? shouldCatalogToolForLocalModelLean
|
||||
: undefined,
|
||||
});
|
||||
const projectedCompactedTools = options.localModelLeanApplied
|
||||
? compacted.tools
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
filterLocalModelLeanTools,
|
||||
isLocalModelLeanEnabled,
|
||||
resolveLocalModelLeanPreserveToolNames,
|
||||
shouldCatalogToolForLocalModelLean,
|
||||
} from "./local-model-lean.js";
|
||||
|
||||
function tools(names: string[]): AnyAgentTool[] {
|
||||
@@ -335,4 +336,9 @@ describe("local model lean tool filtering", () => {
|
||||
|
||||
expect(applyLocalModelLeanToolSearchDefaults({ config: cfg, agentId: "main" })).toBe(cfg);
|
||||
});
|
||||
|
||||
it("keeps exec outside the lean Tool Search catalog", () => {
|
||||
expect(shouldCatalogToolForLocalModelLean({ name: "exec" } as AnyAgentTool)).toBe(false);
|
||||
expect(shouldCatalogToolForLocalModelLean({ name: "read" } as AnyAgentTool)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ const LOCAL_MODEL_LEAN_DENY_TOOL_NAMES = new Set([
|
||||
"tts",
|
||||
"video_generate",
|
||||
]);
|
||||
const LOCAL_MODEL_LEAN_DIRECT_TOOL_NAMES = new Set(["exec"]);
|
||||
const LOCAL_MODEL_LEAN_TOOL_SEARCH_DEFAULTS = {
|
||||
enabled: true,
|
||||
mode: "tools",
|
||||
@@ -107,6 +108,12 @@ export function filterLocalModelLeanTools(params: {
|
||||
});
|
||||
}
|
||||
|
||||
// Lean mode targets coding-tuned local models; keep their familiar shell
|
||||
// primitive visible instead of requiring a catalog search to rediscover it.
|
||||
export function shouldCatalogToolForLocalModelLean(tool: AnyAgentTool): boolean {
|
||||
return !LOCAL_MODEL_LEAN_DIRECT_TOOL_NAMES.has(normalizeToolName(tool.name));
|
||||
}
|
||||
|
||||
export function applyLocalModelLeanToolSearchDefaults(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentId?: string;
|
||||
|
||||
@@ -897,6 +897,7 @@ export function applyToolSearchCatalog(params: {
|
||||
runId?: string;
|
||||
catalogRef?: ToolSearchCatalogRef;
|
||||
toolHookContext?: HookContext;
|
||||
shouldCatalogTool?: (tool: AnyAgentTool) => boolean;
|
||||
}): {
|
||||
tools: AnyAgentTool[];
|
||||
compacted: boolean;
|
||||
|
||||
Reference in New Issue
Block a user