fix(agents): reject empty sessions_search queries in the tool schema (#129120)

Fixes #129054.

The `sessions_search` tool schema now requires a non-empty `query` (`minLength: 1`) with a concise description, so models are told up front instead of discovering it from an error, and a blank or whitespace query returns an actionable retry message. main already rejected blank queries at execution ("query must not be empty"); this adds the schema constraint and clearer guidance. Keyword searches are unchanged. The system prompt is unchanged; the tool schema grows by about 100 compact bytes. Time-range guidance from the original PR was deferred because live runs did not show the model using it.

Proof: baseline schema accepts an empty query, this change rejects it; an isolated Gateway rejects blank queries with the retry guidance and returns the same keyword results. Live gpt-5-mini runs emitted a non-empty query. Regression tests fail on main and pass here.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
ruel225 2026-10-03 05:07:26 +08:00 • committed by GitHub
parent 4aa24e3a13
commit 54db277b5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 54 additions and 26 deletions

View file

@ -7,6 +7,7 @@ import { replaceSessionEntrySync } from "../../config/sessions/session-accessor.
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { callGateway as gatewayCall } from "../../gateway/call.js";
import { createSessionVisibilityChecker } from "../../plugin-sdk/session-visibility.js";
import { normalizeToolParameters } from "../agent-tools.schema.js";
import { describeSessionLinkRule } from "../tool-description-presets.js";
import { compactToolOutputHint } from "../tool-schema-hints.js";
import { createSessionsSearchTool } from "./sessions-search-tool.js";
@ -202,6 +203,25 @@ describe("sessions_search tool", () => {
);
});
it("rejects an empty query in the schema while accepting keywords", () => {
const tool = createTool({});
expect(Value.Check(tool.parameters, { query: "" })).toBe(false);
expect(Value.Check(tool.parameters, { query: "loan" })).toBe(true);
expect(Value.Check(tool.parameters, { query: " loan " })).toBe(true);
});
it.each(["openai", "google"])(
"rejects blank execution with retry guidance after %s schema normalization",
async (modelProvider) => {
const tool = normalizeToolParameters(createTool({}), { modelProvider });
for (const query of ["", " "]) {
await expect(tool.execute!("blank-query", { query })).rejects.toThrow(
/query must not be empty; retry with non-empty keywords/,
);
}
},
);
it("filters invisible hits before applying the limit", async () => {
const requests: CallGatewayRequest[] = [];
const tool = createTool({

View file

@ -50,7 +50,11 @@ const SESSIONS_SEARCH_INDEXING_WARNING =
const SessionsSearchToolSchema = Type.Object({
user: requesterProfileSchema(),
query: Type.String({ maxLength: SESSIONS_SEARCH_MAX_QUERY_CHARS }),
query: Type.String({
minLength: 1,
maxLength: SESSIONS_SEARCH_MAX_QUERY_CHARS,
description: "Required non-empty keywords to match in past user and assistant text.",
}),
sessionKey: Type.Optional(Type.String()),
limit: optionalPositiveIntegerSchema({
maximum: SESSIONS_SEARCH_MAX_LIMIT,
@ -310,7 +314,9 @@ export function createSessionsSearchTool(opts?: {
const params = args as Record<string, unknown>;
const query = readToolStringParam(params, "query") ?? "";
if (!query) {
throw new ToolInputError("query must not be empty");
throw new ToolInputError(
"query must not be empty; retry with non-empty keywords to match in past session text",
);
}
if (query.length > SESSIONS_SEARCH_MAX_QUERY_CHARS) {
throw new ToolInputError(

View file

@ -1332,7 +1332,9 @@
"type": "integer"
},
"query": {
"description": "Required non-empty keywords to match in past user and assistant text.",
"maxLength": 4096,
"minLength": 1,
"type": "string"
},
"sessionKey": {

View file

@ -1,5 +1,5 @@
--- telegram-direct-codex-message-tool.md sha256=d7dfb9a21011bee9b1cd8b769cae0d29d9ebbbf1e8d96a3a84873bfa8711242e
+++ discord-group-codex-message-tool.md sha256=71b5d8e9471f38e06145340b0c186246822fad3484100924cd3e607a0d7e14bc
--- telegram-direct-codex-message-tool.md sha256=5c0befbd287efb1bba2f0a88b440cdfadaf5da175afb9b219dff96928564e79e
+++ discord-group-codex-message-tool.md sha256=b5294f195059c7243fab65032ce6fb353189d5a0ea32bb08bdb30f6f2a9a96b1
@@ -1,1 +1,1 @@
-# Telegram Direct Codex Message Tool Turn
+# Discord Group Codex Message Tool Turn
@ -32,10 +32,10 @@
- "chars": 1348,
+ "chars": 1347,
@@ -261,2 +261,2 @@
- "chars": 72263,
- "roughTokens": 18066
+ "chars": 72861,
+ "roughTokens": 18216
- "chars": 72395,
- "roughTokens": 18099
+ "chars": 72993,
+ "roughTokens": 18249
@@ -265,2 +265,2 @@
- "chars": 2992,
- "roughTokens": 748
@ -47,10 +47,10 @@
+ "chars": 28695,
+ "roughTokens": 7174
@@ -277,2 +277,2 @@
- "chars": 99643,
- "roughTokens": 24911
+ "chars": 101558,
+ "roughTokens": 25390
- "chars": 99775,
- "roughTokens": 24944
+ "chars": 101690,
+ "roughTokens": 25423
@@ -281,2 +281,2 @@
- "chars": 879,
- "roughTokens": 220

View file

@ -258,8 +258,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
"chars": 72263,
"roughTokens": 18066
"chars": 72395,
"roughTokens": 18099
},
"openClawDeveloperInstructions": {
"chars": 2992,
@ -274,8 +274,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 6845
},
"totalWithDynamicToolsJson": {
"chars": 99643,
"roughTokens": 24911
"chars": 99775,
"roughTokens": 24944
},
"userInputText": {
"chars": 879,

View file

@ -1,5 +1,5 @@
--- telegram-direct-codex-message-tool.md sha256=d7dfb9a21011bee9b1cd8b769cae0d29d9ebbbf1e8d96a3a84873bfa8711242e
+++ telegram-heartbeat-codex-tool.md sha256=046f19e66869e2dfb9bacc08a63a493637cc2993cda89fda643e4edd8854269d
--- telegram-direct-codex-message-tool.md sha256=5c0befbd287efb1bba2f0a88b440cdfadaf5da175afb9b219dff96928564e79e
+++ telegram-heartbeat-codex-tool.md sha256=4483ad37d42f6fb1a62d148f1ccc7528c79b045fe34379fa6cf83954f151e2ef
@@ -1,1 +1,1 @@
-# Telegram Direct Codex Message Tool Turn
+# Telegram Direct Codex Heartbeat Tool Turn
@ -37,20 +37,20 @@
+ "chars": 1218,
+ "roughTokens": 305
@@ -261,2 +258,2 @@
- "chars": 72263,
- "roughTokens": 18066
+ "chars": 73756,
+ "roughTokens": 18439
- "chars": 72395,
- "roughTokens": 18099
+ "chars": 73888,
+ "roughTokens": 18472
@@ -273,2 +270,2 @@
- "chars": 27378,
- "roughTokens": 6845
+ "chars": 27715,
+ "roughTokens": 6929
@@ -277,2 +274,2 @@
- "chars": 99643,
- "roughTokens": 24911
+ "chars": 101473,
+ "roughTokens": 25369
- "chars": 99775,
- "roughTokens": 24944
+ "chars": 101605,
+ "roughTokens": 25402
@@ -281,2 +278,2 @@
- "chars": 879,
- "roughTokens": 220