diff --git a/CHANGELOG.md b/CHANGELOG.md index 965525c2350..e534730c919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai - Build: enable stricter Vitest lint rules for focused, disabled, conditional, hook, matcher, and expectation hazards. - Build: pin explicit oxfmt defaults in the shared formatter config to keep formatting behavior stable across upgrades. - TypeScript: enable stricter compiler checks for implicit returns, side-effect imports, overrides, and unused production code. +- Agents: allow `session.agentToAgent.maxPingPongTurns` up to 20 while keeping the default at 5 for longer agent-to-agent reply chains. Fixes #52382. (#52400) Thanks @thirumaleshp. - Build: upgrade workspace package management to pnpm 11 and keep Docker, install, update, and release workflows on the pnpm 11 config surface. (#79414) Thanks @altaywtf. - Models: add provider-level `localService` startup for on-demand local model servers before OpenAI-compatible requests, including one-shot model probes. - Agents: trim default system prompt guidance and send-only message tool schemas to reduce prompt tokens while preserving GPT-5 personality guidance. diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 5e3eeff55aa..9c92d3caf40 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -c9e88800854b697cb3c9721d0087eb2bc7bcf6ae7239cb51d9849c49ef3d48e3 config-baseline.json -67c58457ed2b525975cdb053489f92a5f840c8cf982666393e111fd327dd132e config-baseline.core.json +1cc2cabc41c2261492dea4d927b48864c6992dcfb5b81fa97f2171848d037b1e config-baseline.json +bfa974d070e5ef26fab023506b050cb3a582d1e54a096dbf4dddbc59535de29c config-baseline.core.json f90c9d96ccc4c0c703d6c489f86d89fde208cd7f697b396aeee96ff3ee087956 config-baseline.channel.json 18f71e9d4a62fe68fbd5bf18d5833a4e380fc705ad641769e1cf05794286344c config-baseline.plugin.json diff --git a/docs/concepts/session-tool.md b/docs/concepts/session-tool.md index 21d5fe1980c..d9a90cd6401 100644 --- a/docs/concepts/session-tool.md +++ b/docs/concepts/session-tool.md @@ -104,7 +104,8 @@ provenance. The receiving agent should treat them as tool-routed data, not as a direct end-user-authored instruction. After the target responds, OpenClaw can run a **reply-back loop** where the -agents alternate messages (up to 5 turns). The target agent can reply +agents alternate messages (up to `session.agentToAgent.maxPingPongTurns`, range +0-20, default 5). The target agent can reply `REPLY_SKIP` to stop early. ## Status and orchestration helpers diff --git a/docs/gateway/config-agents.md b/docs/gateway/config-agents.md index 46fe6d2185b..cf661162728 100644 --- a/docs/gateway/config-agents.md +++ b/docs/gateway/config-agents.md @@ -1219,7 +1219,7 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden - **`reset`**: primary reset policy. `daily` resets at `atHour` local time; `idle` resets after `idleMinutes`. When both configured, whichever expires first wins. Daily reset freshness uses the session row's `sessionStartedAt`; idle reset freshness uses `lastInteractionAt`. Background/system-event writes such as heartbeat, cron wakeups, exec notifications, and gateway bookkeeping can update `updatedAt`, but they do not keep daily/idle sessions fresh. - **`resetByType`**: per-type overrides (`direct`, `group`, `thread`). Legacy `dm` accepted as alias for `direct`. - **`mainKey`**: legacy field. Runtime always uses `"main"` for the main direct-chat bucket. -- **`agentToAgent.maxPingPongTurns`**: maximum reply-back turns between agents during agent-to-agent exchanges (integer, range: `0`–`5`). `0` disables ping-pong chaining. +- **`agentToAgent.maxPingPongTurns`**: maximum reply-back turns between agents during agent-to-agent exchanges (integer, range: `0`-`20`, default: `5`). `0` disables ping-pong chaining. - **`sendPolicy`**: match by `channel`, `chatType` (`direct|group|channel`, with legacy `dm` alias), `keyPrefix`, or `rawKeyPrefix`. First deny wins. - **`maintenance`**: session-store cleanup + retention controls. - `mode`: `warn` emits warnings only; `enforce` applies cleanup. diff --git a/src/agents/tools/sessions-send-helpers.test.ts b/src/agents/tools/sessions-send-helpers.test.ts index 7e3eedfb9a7..32b03bcc2fa 100644 --- a/src/agents/tools/sessions-send-helpers.test.ts +++ b/src/agents/tools/sessions-send-helpers.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { setActivePluginRegistry } from "../../plugins/runtime.js"; import { createSessionConversationTestRegistry } from "../../test-utils/session-conversation-registry.js"; -import { resolveAnnounceTargetFromKey } from "./sessions-send-helpers.js"; +import { resolveAnnounceTargetFromKey, resolvePingPongTurns } from "./sessions-send-helpers.js"; describe("resolveAnnounceTargetFromKey", () => { beforeEach(() => { @@ -63,3 +63,28 @@ describe("resolveAnnounceTargetFromKey", () => { }); }); }); + +describe("resolvePingPongTurns", () => { + it("defaults to 5 when unset", () => { + expect(resolvePingPongTurns(undefined)).toBe(5); + expect(resolvePingPongTurns({ session: {} } as never)).toBe(5); + }); + + it("uses configured values through the 20-turn ceiling", () => { + expect( + resolvePingPongTurns({ session: { agentToAgent: { maxPingPongTurns: 10 } } } as never), + ).toBe(10); + expect( + resolvePingPongTurns({ session: { agentToAgent: { maxPingPongTurns: 20 } } } as never), + ).toBe(20); + }); + + it("keeps defensive floor and ceiling clamps", () => { + expect( + resolvePingPongTurns({ session: { agentToAgent: { maxPingPongTurns: -1 } } } as never), + ).toBe(0); + expect( + resolvePingPongTurns({ session: { agentToAgent: { maxPingPongTurns: 50 } } } as never), + ).toBe(20); + }); +}); diff --git a/src/agents/tools/sessions-send-helpers.ts b/src/agents/tools/sessions-send-helpers.ts index 5d03539804a..7e439278b46 100644 --- a/src/agents/tools/sessions-send-helpers.ts +++ b/src/agents/tools/sessions-send-helpers.ts @@ -13,7 +13,7 @@ export { } from "./sessions-send-tokens.js"; const DEFAULT_PING_PONG_TURNS = 5; -const MAX_PING_PONG_TURNS = 5; +const MAX_PING_PONG_TURNS = 20; export type AnnounceTarget = { channel: string; diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index b4b02c25d4e..ced4096be5d 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -1537,7 +1537,7 @@ export const FIELD_HELP: Record = { "session.agentToAgent": "Groups controls for inter-agent session exchanges, including loop prevention limits on reply chaining. Keep defaults unless you run advanced agent-to-agent automation with strict turn caps.", "session.agentToAgent.maxPingPongTurns": - "Max reply-back turns between requester and target agents during agent-to-agent exchanges (0-5). Use lower values to hard-limit chatter loops and preserve predictable run completion.", + "Max reply-back turns between requester and target agents during agent-to-agent exchanges (0-20, default 5). Use lower values to hard-limit chatter loops and preserve predictable run completion.", "session.threadBindings": "Shared defaults for thread-bound session routing behavior across providers that support thread focus workflows. Configure global defaults here and override per channel only when behavior differs.", "session.threadBindings.enabled": diff --git a/src/config/types.base.ts b/src/config/types.base.ts index c441b6a4582..cd613022b10 100644 --- a/src/config/types.base.ts +++ b/src/config/types.base.ts @@ -205,7 +205,7 @@ export type SessionConfig = { /** Session transcript write-lock acquisition policy. */ writeLock?: SessionWriteLockConfig; agentToAgent?: { - /** Max ping-pong turns between requester/target (0–5). Default: 5. */ + /** Max ping-pong turns between requester/target (0-20). Default: 5. */ maxPingPongTurns?: number; }; /** Shared defaults for thread-bound session routing across channels/providers. */ diff --git a/src/config/zod-schema.session.ts b/src/config/zod-schema.session.ts index e9bb3befa91..54d69d82554 100644 --- a/src/config/zod-schema.session.ts +++ b/src/config/zod-schema.session.ts @@ -64,7 +64,7 @@ export const SessionSchema = z .optional(), agentToAgent: z .object({ - maxPingPongTurns: z.number().int().min(0).max(5).optional(), + maxPingPongTurns: z.number().int().min(0).max(20).optional(), }) .strict() .optional(),