mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-13 17:07:40 +00:00
refactor(config): promote planTool to canonical tools.updatePlan (#113958)
The update_plan checklist tool has been default-on with no model gating for a while, so keeping it under tools.experimental misrepresented it and kept a container key alive for a single boolean. Rename it to a plain boolean tools.updatePlan (camel-cased tool id, matching tools.agentToAgent and tools.toolSearch), keeping the !== false default-on semantics. The strict schema rejects the retired key, so openclaw doctor --fix now lifts tools.experimental.planTool to tools.updatePlan and deletes the emptied container. Registration also drops five parameters that were never read (agentSessionKey, agentId, modelProvider, modelId, pluginToolAllowlist). Docs: config-tools.md dropped a stale claim that the tool defaults off behind a strict-agentic GPT-5 rule that no longer exists, experimental-features.md no longer lists the tool and now cites the real Codex app-server floor (0.143.0, per MIN_CODEX_APP_SERVER_VERSION). Config baseline core count drops 2307 -> 2306.
This commit is contained in:
committed by
GitHub
parent
9349a0e880
commit
d7ef7fc5a6
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"core": 2307,
|
||||
"core": 2306,
|
||||
"channel": 3630,
|
||||
"plugin": 3556
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
f1956566f629dc6e6d7f0b2ce268e25dede588e352faf3d9c1009d3856eb4d53 config-baseline.json
|
||||
8824942e998e77a20892b58f27bf739e5566da71fc81c101c73bc37d99c13a47 config-baseline.core.json
|
||||
b5a60d7a0cfb860835eb4f38acb6421db736c76121a891311f37dede4c26e5a4 config-baseline.json
|
||||
e51de41a04b71b26f3ed5edd8e663982f2dee84026b7ecfab6ef75ecb2e7eb4d config-baseline.core.json
|
||||
af6ca0e70007113462270d46fa14ef0551e577e2fa157d2b9f6d0a93d36f96f1 config-baseline.channel.json
|
||||
d47f7eb4268480e99ab85b6c1b5ee07f6c570ce0555e3db682ec26037014b47e config-baseline.plugin.json
|
||||
|
||||
@@ -16,13 +16,12 @@ Experimental features are preview surfaces behind explicit flags. They need more
|
||||
|
||||
## Currently documented flags
|
||||
|
||||
| Surface | Key | Use it when | More |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| Local model runtime | `agents.defaults.experimental.localModelLean`, `agents.entries.*.experimental.localModelLean` | A smaller or stricter local backend chokes on OpenClaw's full default tool surface | [Local Models](/gateway/local-models) |
|
||||
| Codex harness | `plugins.entries.codex.config.appServer.experimental.sandboxExecServer` | You want native Codex app-server 0.132.0 or newer to target an OpenClaw sandbox-backed exec-server instead of disabling Code Mode | [Codex harness reference](/plugins/codex-harness-reference#sandboxed-native-execution) |
|
||||
| Structured planning tool | `tools.experimental.planTool` | You want the structured `update_plan` tool exposed for multi-step work tracking in compatible runtimes and UIs | [Gateway configuration reference](/gateway/config-tools#toolsexperimental) |
|
||||
| Code Mode | `tools.codeMode.enabled` | You want compact code-orchestrated access to a hidden OpenClaw tool catalog | [Code Mode](/tools/code-mode) |
|
||||
| Swarm | `tools.swarm.enabled` | You want Code Mode scripts to orchestrate bounded groups of sub-agents in parallel | [Swarm](/tools/swarm) |
|
||||
| Surface | Key | Use it when | More |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| Local model runtime | `agents.defaults.experimental.localModelLean`, `agents.entries.*.experimental.localModelLean` | A smaller or stricter local backend chokes on OpenClaw's full default tool surface | [Local Models](/gateway/local-models) |
|
||||
| Codex harness | `plugins.entries.codex.config.appServer.experimental.sandboxExecServer` | You want native Codex app-server 0.143.0 or newer to target an OpenClaw sandbox-backed exec-server instead of disabling Code Mode | [Codex harness reference](/plugins/codex-harness-reference#sandboxed-native-execution) |
|
||||
| Code Mode | `tools.codeMode.enabled` | You want compact code-orchestrated access to a hidden OpenClaw tool catalog | [Code Mode](/tools/code-mode) |
|
||||
| Swarm | `tools.swarm.enabled` | You want Code Mode scripts to orchestrate bounded groups of sub-agents in parallel | [Swarm](/tools/swarm) |
|
||||
|
||||
## Control UI Labs
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ The bundled OpenAI/Codex GPT-5-family overlay (`resolveGpt5SystemPromptContribut
|
||||
|
||||
The prompt is compact, with fixed sections:
|
||||
|
||||
- **Tooling**: structured-tool source-of-truth reminder plus runtime tool-use guidance. When the experimental `update_plan` tool is enabled (`tools.experimental.planTool`), its own tool description adds: use it only for non-trivial multi-step work, keep at most one step `in_progress`, and skip it for simple one-step work.
|
||||
- **Tooling**: structured-tool source-of-truth reminder plus runtime tool-use guidance. When the `update_plan` tool is enabled (`tools.updatePlan`, on by default), its own tool description adds: use it only for non-trivial multi-step work, keep at most one step `in_progress`, and skip it for simple one-step work.
|
||||
- **Execution Bias**: act in-turn on actionable requests, continue until done or blocked, recover from weak tool results, check mutable state live, and verify before finalizing.
|
||||
- **Promised Work**: promising future, background, delegated, or continued work creates follow-through ownership: arrange a push-based completion or watch path before ending the turn, proactively return with the result or a concrete blocker, and never treat progress (like `running`) as completion.
|
||||
- **Safety**: short guardrail reminder against power-seeking behavior or bypassing oversight.
|
||||
|
||||
@@ -3404,7 +3404,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
|
||||
- H3: tools.agentToAgent
|
||||
- H3: tools.sessions
|
||||
- H3: `tools.sessions_spawn`
|
||||
- H3: tools.experimental
|
||||
- H3: tools.updatePlan
|
||||
- H3: agents.defaults.subagents
|
||||
- H2: Custom providers and base URLs
|
||||
- H3: Provider field details
|
||||
|
||||
@@ -403,25 +403,25 @@ Controls inline attachment support for `sessions_spawn`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<a id="toolsexperimental"></a>
|
||||
<a id="toolsupdateplan"></a>
|
||||
|
||||
### `tools.experimental`
|
||||
### `tools.updatePlan`
|
||||
|
||||
Experimental built-in tool flags. Default off unless a strict-agentic GPT-5 auto-enable rule applies.
|
||||
Kill switch for the structured `update_plan` checklist tool used for non-trivial multi-step work tracking.
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: true, // enable experimental update_plan
|
||||
},
|
||||
updatePlan: false, // hide update_plan from every run
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `planTool`: enables the structured `update_plan` tool for non-trivial multi-step work tracking.
|
||||
- Default: `false` unless `agents.defaults.embeddedAgent.executionContract` (or a per-agent override) is set to `"strict-agentic"` for an `openai` provider run against a GPT-5-family model id (this covers OpenAI Codex CLI runs too, since Codex auth/model routing lives under the `openai` provider). Set `true` to force the tool on outside that scope, or `false` to keep it off even for strict-agentic GPT-5 runs.
|
||||
- When enabled, the system prompt also adds usage guidance so the model only uses it for substantial work and keeps at most one step `in_progress`.
|
||||
- Default: `true` for every provider and model. Set `false` to keep the tool off; there is no model-specific auto-enable rule.
|
||||
- The tool description adds usage guidance so the model only uses it for substantial work and keeps at most one step `in_progress`.
|
||||
- `tools.deny: ["update_plan"]` also removes the tool, so use whichever surface already carries your tool policy.
|
||||
|
||||
Older configs used `tools.experimental.planTool`. Run `openclaw doctor --fix` to move the value to `tools.updatePlan`.
|
||||
|
||||
### `agents.defaults.subagents`
|
||||
|
||||
|
||||
@@ -243,9 +243,7 @@ describe("createOpenClawTools browser plugin integration", () => {
|
||||
allow: ["browser"],
|
||||
},
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: true,
|
||||
},
|
||||
updatePlan: true,
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
let capturedRuntimeConfig: OpenClawConfig | undefined;
|
||||
@@ -292,9 +290,7 @@ describe("createOpenClawTools browser plugin integration", () => {
|
||||
allow: ["browser"],
|
||||
},
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: true,
|
||||
},
|
||||
updatePlan: true,
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
let capturedRuntimeConfig: OpenClawConfig | undefined;
|
||||
|
||||
@@ -22,35 +22,20 @@ export function collectPresentOpenClawTools(
|
||||
return candidates.filter((tool): tool is AnyAgentTool => tool !== null && tool !== undefined);
|
||||
}
|
||||
|
||||
/** Resolves the default-on update_plan switch with an explicit kill switch. */
|
||||
function isUpdatePlanToolEnabledForOpenClawTools(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentSessionKey?: string;
|
||||
agentId?: string | null;
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
}): boolean {
|
||||
return params.config?.tools?.experimental?.planTool !== false;
|
||||
}
|
||||
|
||||
/** Decides whether update_plan should be included in the assembled OpenClaw tool set. */
|
||||
export function shouldIncludeUpdatePlanToolForOpenClawTools(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentSessionKey?: string;
|
||||
agentId?: string | null;
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
pluginToolAllowlist?: string[];
|
||||
pluginToolDenylist?: string[];
|
||||
}): boolean {
|
||||
// Default-on with an explicit kill switch: only `false` opts out.
|
||||
if (params.config?.tools?.updatePlan === false) {
|
||||
return false;
|
||||
}
|
||||
const deny = uniqueStrings([
|
||||
...(params.config?.tools?.deny ?? []),
|
||||
...(params.pluginToolDenylist ?? []),
|
||||
]);
|
||||
return (
|
||||
isUpdatePlanToolEnabledForOpenClawTools(params) &&
|
||||
isToolAllowedByPolicyName("update_plan", { deny })
|
||||
);
|
||||
return isToolAllowedByPolicyName("update_plan", { deny });
|
||||
}
|
||||
|
||||
/** Includes ask_user only on a primary session and when normal deny policy permits it. */
|
||||
|
||||
@@ -452,11 +452,6 @@ export function createOpenClawTools(
|
||||
const effectiveCallGateway = embedded ? createEmbeddedCallGateway() : callGateway;
|
||||
const includeUpdatePlanTool = shouldIncludeUpdatePlanToolForOpenClawTools({
|
||||
config: resolvedConfig,
|
||||
agentSessionKey: options?.agentSessionKey,
|
||||
agentId: options?.requesterAgentIdOverride,
|
||||
modelProvider: options?.modelProvider,
|
||||
modelId: options?.modelId,
|
||||
pluginToolAllowlist: options?.pluginToolAllowlist,
|
||||
pluginToolDenylist: options?.pluginToolDenylist,
|
||||
});
|
||||
const includeAskUserTool = shouldIncludeAskUserToolForOpenClawTools({
|
||||
|
||||
@@ -96,16 +96,9 @@ describe("openclaw-tools update_plan gating", () => {
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
});
|
||||
const emptyAllowlistParams = {
|
||||
config: {} as OpenClawConfig,
|
||||
pluginToolAllowlist: [],
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
};
|
||||
|
||||
expect(defaultTools).toContain("update_plan");
|
||||
expect(defaultTools).not.toContain("ask_user");
|
||||
expect(shouldIncludeUpdatePlanToolForOpenClawTools(emptyAllowlistParams)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps ask_user on primary sessions and excludes spawned worker sessions", () => {
|
||||
@@ -280,13 +273,7 @@ describe("openclaw-tools update_plan gating", () => {
|
||||
});
|
||||
|
||||
it("registers update_plan when explicitly enabled", () => {
|
||||
const config = {
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const config = { tools: { updatePlan: true } } as OpenClawConfig;
|
||||
|
||||
expectUpdatePlanEnabled({ config }, true);
|
||||
expect(createUpdatePlanTool().displaySummary).toBe("Track short work plan.");
|
||||
@@ -306,19 +293,6 @@ describe("openclaw-tools update_plan gating", () => {
|
||||
it("includes update_plan when a config allowlist group includes it", () => {
|
||||
const includeUpdatePlan = shouldIncludeUpdatePlanToolForOpenClawTools({
|
||||
config: { tools: { allow: ["group:agents"] } } as OpenClawConfig,
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
expect(includeUpdatePlan).toBe(true);
|
||||
});
|
||||
|
||||
it("includes update_plan when a runtime allowlist group includes it", () => {
|
||||
const includeUpdatePlan = shouldIncludeUpdatePlanToolForOpenClawTools({
|
||||
config: {} as OpenClawConfig,
|
||||
pluginToolAllowlist: ["group:agents"],
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
expect(includeUpdatePlan).toBe(true);
|
||||
@@ -336,23 +310,9 @@ describe("openclaw-tools update_plan gating", () => {
|
||||
expect(tools).not.toContain("update_plan");
|
||||
});
|
||||
|
||||
it("lets explicit planTool false disable every model and override allowlists", () => {
|
||||
const cfg = {
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: false,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expectUpdatePlanEnabled({ config: cfg, modelProvider: "openai", modelId: "gpt-5.4" }, false);
|
||||
it("lets an explicit updatePlan false override an allowlist that includes the tool", () => {
|
||||
expectUpdatePlanEnabled(
|
||||
{
|
||||
config: cfg,
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
pluginToolAllowlist: ["update_plan"],
|
||||
},
|
||||
{ config: { tools: { updatePlan: false, allow: ["update_plan"] } } as OpenClawConfig },
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -184,9 +184,7 @@ describe("resolveQueuedReplyExecutionConfig channel scope", () => {
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: true,
|
||||
},
|
||||
updatePlan: true,
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
setRuntimeConfigSnapshot(staleRuntimeConfig, sourceConfig);
|
||||
|
||||
@@ -530,6 +530,28 @@ describe("retired runtime config migrations", () => {
|
||||
expect(result.changes.length).toBeGreaterThan(8);
|
||||
});
|
||||
|
||||
it("lifts the retired plan-tool switch out of the tools.experimental container", () => {
|
||||
const result = applyAll({ tools: { experimental: { planTool: false } } });
|
||||
|
||||
expect(result.raw).toHaveProperty("tools.updatePlan", false);
|
||||
expect(result.raw).not.toHaveProperty("tools.experimental");
|
||||
expect(result.changes).toContain("Moved tools.experimental.planTool → tools.updatePlan.");
|
||||
});
|
||||
|
||||
it("drops the tools.experimental container when the canonical plan-tool switch wins", () => {
|
||||
const canonicalWins = applyAll({
|
||||
tools: { updatePlan: true, experimental: { planTool: false } },
|
||||
});
|
||||
const emptyContainer = applyAll({ tools: { experimental: {} } });
|
||||
|
||||
expect(canonicalWins.raw).toHaveProperty("tools.updatePlan", true);
|
||||
expect(canonicalWins.raw).not.toHaveProperty("tools.experimental");
|
||||
expect(emptyContainer.raw).not.toHaveProperty("tools.experimental");
|
||||
expect(emptyContainer.changes).toContain(
|
||||
"Removed tools.experimental; tools.updatePlan now owns the switch.",
|
||||
);
|
||||
});
|
||||
|
||||
it("consolidates the approved tier-eval tranche with canonical values winning", () => {
|
||||
const result = applyAll({
|
||||
mcp: { servers: { docs: { cwd: "/canonical", workingDirectory: "/legacy" } } },
|
||||
|
||||
@@ -500,6 +500,7 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME_RETIRED: LegacyConfigMigrationSpec
|
||||
["tools", "message", "allowCrossContextSend"],
|
||||
"tools.message.allowCrossContextSend moved to tools.message.crossContext.",
|
||||
),
|
||||
rule(["tools", "experimental"], "tools.experimental.planTool moved to tools.updatePlan."),
|
||||
rule(
|
||||
["talk", "realtime", "voice"],
|
||||
"talk.realtime.voice moved to talk.realtime.speakerVoice.",
|
||||
@@ -561,6 +562,19 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME_RETIRED: LegacyConfigMigrationSpec
|
||||
}
|
||||
delete messageTool.allowCrossContextSend;
|
||||
}
|
||||
// planTool was the only tools.experimental member, so the strict schema now
|
||||
// rejects the whole container; lift the value, then drop the empty parent.
|
||||
const tools = getRecord(raw.tools);
|
||||
const experimentalTools = getRecord(tools?.experimental);
|
||||
if (tools && experimentalTools) {
|
||||
if (Object.hasOwn(experimentalTools, "planTool") && tools.updatePlan === undefined) {
|
||||
tools.updatePlan = experimentalTools.planTool;
|
||||
changes.push("Moved tools.experimental.planTool → tools.updatePlan.");
|
||||
} else {
|
||||
changes.push("Removed tools.experimental; tools.updatePlan now owns the switch.");
|
||||
}
|
||||
delete tools.experimental;
|
||||
}
|
||||
const talkRealtime = getRecord(getRecord(raw.talk)?.realtime);
|
||||
if (talkRealtime) {
|
||||
moveVoice(talkRealtime, "talk.realtime", changes);
|
||||
|
||||
@@ -145,9 +145,7 @@ describe("runtime snapshot state", () => {
|
||||
const scopedResolvedConfig: OpenClawConfig = {
|
||||
...runtimeConfig,
|
||||
tools: {
|
||||
experimental: {
|
||||
planTool: true,
|
||||
},
|
||||
updatePlan: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -95,10 +95,8 @@ export const RUNTIME_FIELD_HELP: Record<string, string> = {
|
||||
"Enables the agent_to_agent tool surface so one agent can invoke another agent at runtime. Keep off in simple deployments and enable only when orchestration value outweighs complexity.",
|
||||
"tools.agentToAgent.allow":
|
||||
"Allowlist of target agent IDs permitted for agent_to_agent calls when orchestration is enabled. Use explicit allowlists to avoid uncontrolled cross-agent call graphs.",
|
||||
"tools.experimental":
|
||||
"Experimental built-in tool flags. Use each tool's switch to opt in or out of its documented default.",
|
||||
"tools.experimental.planTool":
|
||||
"Structured `update_plan` checklist tool for non-trivial multi-step work. Enabled by default for embedded models; set false to opt out.",
|
||||
"tools.updatePlan":
|
||||
"Structured `update_plan` checklist tool for non-trivial multi-step work. Enabled by default; set false to opt out.",
|
||||
"tools.toolSearch":
|
||||
"Compact large OpenClaw, MCP, and client tool catalogs. Set to true for the default code bridge or use the object form to choose structured controls or a compact visible tool directory.",
|
||||
"tools.toolSearch.enabled":
|
||||
|
||||
@@ -236,8 +236,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"tools.agentToAgent": "Agent-to-Agent Tool Access",
|
||||
"tools.agentToAgent.enabled": "Enable Agent-to-Agent Tool",
|
||||
"tools.agentToAgent.allow": "Agent-to-Agent Target Allowlist",
|
||||
"tools.experimental": "Experimental Tools",
|
||||
"tools.experimental.planTool": "Enable Structured Plan Tool",
|
||||
"tools.updatePlan": "Enable Structured Plan Tool",
|
||||
"tools.toolSearch": "Tool Search",
|
||||
"tools.toolSearch.enabled": "Enable Tool Search",
|
||||
"tools.toolSearch.mode": "Tool Search Surface",
|
||||
|
||||
@@ -785,17 +785,13 @@ describe("config schema", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts experimental tool flags in the runtime zod schema", () => {
|
||||
const parsed = ToolsSchema.parse({
|
||||
experimental: {
|
||||
planTool: true,
|
||||
},
|
||||
});
|
||||
it("accepts the update_plan tool switch in the runtime zod schema", () => {
|
||||
const parsed = ToolsSchema.parse({ updatePlan: false });
|
||||
if (!parsed) {
|
||||
throw new Error("expected parsed tools config");
|
||||
}
|
||||
|
||||
expect(parsed?.experimental?.planTool).toBe(true);
|
||||
expect(parsed?.updatePlan).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts simplified Tool Search config in the runtime zod schema", () => {
|
||||
|
||||
@@ -529,11 +529,8 @@ export type ToolsConfig = {
|
||||
sandbox?: {
|
||||
tools?: ToolAllowDenyPolicyConfig;
|
||||
};
|
||||
/** Experimental tool flags. */
|
||||
experimental?: {
|
||||
/** Structured checklist tool; enabled by default. Set false to opt out. */
|
||||
planTool?: boolean;
|
||||
};
|
||||
/** Structured update_plan checklist tool; enabled by default. Set false to opt out. */
|
||||
updatePlan?: boolean;
|
||||
};
|
||||
|
||||
export type MessageToolsConfig = {
|
||||
|
||||
@@ -1023,12 +1023,7 @@ export const ToolsSchema = z
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
experimental: z
|
||||
.object({
|
||||
planTool: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
updatePlan: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.superRefine((value, ctx) => {
|
||||
|
||||
Reference in New Issue
Block a user