Agents: add lean local model mode (#66495)

Merged via squash.

Prepared head SHA: d88da6082c1ce2a9b497e19075540c384d7bf1e3
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Reviewed-by: @ImLukeF
This commit is contained in:
Luke
2026-04-14 23:45:49 +10:00
committed by GitHub
parent dae060390b
commit 4015138df9
9 changed files with 93 additions and 2 deletions
+1
View File
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Docs/showcase: add a scannable hero, complete section jump links, and a responsive video grid for community examples. (#48493) Thanks @jchopard69.
- Agents/local models: add `agents.defaults.localModelMode: "lean"` to drop heavyweight default tools like `browser`, `cron`, and `message`, reducing prompt size for weaker local-model setups without changing the normal path. Thanks @ImLukeF.
### Fixes
+4 -2
View File
@@ -164,8 +164,10 @@ Compatibility notes for stricter OpenAI-compatible backends:
- Some smaller or stricter local backends are unstable with OpenClaw's full
agent-runtime prompt shape, especially when tool schemas are included. If the
backend works for tiny direct `/v1/chat/completions` calls but fails on normal
OpenClaw agent turns, try
`models.providers.<provider>.models[].compat.supportsTools: false` first.
OpenClaw agent turns, first try
`agents.defaults.localModelMode: "lean"` to drop heavyweight default tools
like `browser`, `cron`, and `message`; if that still fails, try
`models.providers.<provider>.models[].compat.supportsTools: false`.
- If the backend still fails only on larger OpenClaw runs, the remaining issue
is usually upstream model/server capacity or a backend bug, not OpenClaw's
transport layer.
@@ -115,4 +115,56 @@ describe("applyModelProviderToolPolicy", () => {
expect(toolNames(filtered)).toEqual(["read", "web_search", "exec"]);
});
it("drops heavyweight tools when lean local-model mode is enabled", () => {
const filtered = __testing.applyModelProviderToolPolicy(
[
{ name: "read" },
{ name: "browser" },
{ name: "cron" },
{ name: "message" },
{ name: "exec" },
] as unknown as AnyAgentTool[],
{
config: {
agents: {
defaults: {
localModelMode: "lean",
},
},
},
modelProvider: "openai",
modelApi: "openai-responses",
modelId: "gpt-5.4",
},
);
expect(toolNames(filtered)).toEqual(["read", "exec"]);
});
it("keeps heavyweight tools when lean local-model mode is not enabled", () => {
const filtered = __testing.applyModelProviderToolPolicy(
[
{ name: "read" },
{ name: "browser" },
{ name: "cron" },
{ name: "message" },
{ name: "exec" },
] as unknown as AnyAgentTool[],
{
config: {
agents: {
defaults: {
localModelMode: "default",
},
},
},
modelProvider: "openai",
modelApi: "openai-responses",
modelId: "gpt-5.4",
},
);
expect(toolNames(filtered)).toEqual(["read", "browser", "cron", "message", "exec"]);
});
});
+5
View File
@@ -131,6 +131,11 @@ function applyModelProviderToolPolicy(
modelCompat?: ModelCompatConfig;
},
): AnyAgentTool[] {
if (params?.config?.agents?.defaults?.localModelMode === "lean") {
const leanDeny = new Set(["browser", "cron", "message"]);
tools = tools.filter((tool) => !leanDeny.has(tool.name));
}
if (
shouldSuppressManagedWebSearchTool({
config: params?.config,
+20
View File
@@ -3243,6 +3243,21 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
description:
"Max total characters across all injected workspace bootstrap files (default: 150000).",
},
localModelMode: {
anyOf: [
{
type: "string",
const: "default",
},
{
type: "string",
const: "lean",
},
],
title: "Local Model Mode",
description:
'Local-model prompt profile: "default" keeps the standard tool surface, while "lean" drops heavyweight non-essential tools for smaller or weaker models.',
},
bootstrapPromptTruncationWarning: {
anyOf: [
{
@@ -24513,6 +24528,11 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
help: "Max total characters across all injected workspace bootstrap files (default: 150000).",
tags: ["performance"],
},
"agents.defaults.localModelMode": {
label: "Local Model Mode",
help: 'Local-model prompt profile: "default" keeps the standard tool surface, while "lean" drops heavyweight non-essential tools for smaller or weaker models.',
tags: ["advanced"],
},
"agents.defaults.bootstrapPromptTruncationWarning": {
label: "Bootstrap Prompt Truncation Warning",
help: 'Inject agent-visible warning text when bootstrap files are truncated: "off", "once" (default), or "always".',
+2
View File
@@ -848,6 +848,8 @@ export const FIELD_HELP: Record<string, string> = {
"Max characters of each workspace bootstrap file injected into the system prompt before truncation (default: 20000).",
"agents.defaults.bootstrapTotalMaxChars":
"Max total characters across all injected workspace bootstrap files (default: 150000).",
"agents.defaults.localModelMode":
'Local-model prompt profile: "default" keeps the standard tool surface, while "lean" drops heavyweight non-essential tools for smaller or weaker models.',
"agents.defaults.bootstrapPromptTruncationWarning":
'Inject agent-visible warning text when bootstrap files are truncated: "off", "once" (default), or "always".',
"agents.defaults.startupContext":
+1
View File
@@ -343,6 +343,7 @@ export const FIELD_LABELS: Record<string, string> = {
"agents.defaults.contextInjection": "Context Injection",
"agents.defaults.bootstrapMaxChars": "Bootstrap Max Chars",
"agents.defaults.bootstrapTotalMaxChars": "Bootstrap Total Max Chars",
"agents.defaults.localModelMode": "Local Model Mode",
"agents.defaults.bootstrapPromptTruncationWarning": "Bootstrap Prompt Truncation Warning",
"agents.defaults.startupContext": "Startup Context",
"agents.defaults.startupContext.enabled": "Enable Startup Context",
+7
View File
@@ -13,6 +13,7 @@ import type { MemorySearchConfig } from "./types.tools.js";
export type AgentContextInjection = "always" | "continuation-skip";
export type EmbeddedPiExecutionContract = "default" | "strict-agentic";
export type LocalModelMode = "default" | "lean";
export type AgentModelEntryConfig = {
alias?: string;
@@ -198,6 +199,12 @@ export type AgentDefaultsConfig = {
bootstrapMaxChars?: number;
/** Max total chars across all injected bootstrap files (default: 150000). */
bootstrapTotalMaxChars?: number;
/**
* Optional local-model prompt profile:
* - default: keep the standard tool surface
* - lean: drop heavyweight non-essential tools for smaller or weaker models
*/
localModelMode?: LocalModelMode;
/**
* Agent-visible bootstrap truncation warning mode:
* - off: do not inject warning text
+1
View File
@@ -52,6 +52,7 @@ export const AgentDefaultsSchema = z
contextInjection: z.union([z.literal("always"), z.literal("continuation-skip")]).optional(),
bootstrapMaxChars: z.number().int().positive().optional(),
bootstrapTotalMaxChars: z.number().int().positive().optional(),
localModelMode: z.union([z.literal("default"), z.literal("lean")]).optional(),
bootstrapPromptTruncationWarning: z
.union([z.literal("off"), z.literal("once"), z.literal("always")])
.optional(),