diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index 9daf3e0ea7a..3c21a1cdb90 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -636,9 +636,39 @@ describe("buildAgentSystemPrompt", () => { expect(prompt).toContain("message: Send messages and channel actions"); expect(prompt).toContain("### message tool"); + expect(prompt).toContain("Use `message` for proactive sends + channel actions"); + expect(prompt).toContain("For `action=send`, include `target` and `message`."); expect(prompt).toContain(`respond with ONLY: ${SILENT_REPLY_TOKEN}`); }); + it("gates sub-agent orchestration guidance on available tools", () => { + const messagingPrompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/openclaw", + toolNames: ["message", "sessions_send"], + }); + const spawnOnlyPrompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/openclaw", + toolNames: ["sessions_spawn"], + }); + const orchestrationPrompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/openclaw", + toolNames: ["sessions_spawn", "subagents"], + }); + + expect(messagingPrompt).not.toContain("Sub-agent orchestration"); + expect(messagingPrompt).not.toContain("sessions_spawn(...)"); + expect(messagingPrompt).not.toContain("subagents(action=list|steer|kill)"); + + expect(spawnOnlyPrompt).toContain( + "- Sub-agent orchestration → use `sessions_spawn(...)` to start delegated work.", + ); + expect(spawnOnlyPrompt).not.toContain("manage already-spawned children"); + + expect(orchestrationPrompt).toContain( + "- Sub-agent orchestration → use `sessions_spawn(...)` to start delegated work; use `subagents(action=list|steer|kill)` to manage already-spawned children.", + ); + }); + it("reapplies provider prompt contributions", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/openclaw", diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 4fa55a68e6c..12cd4a70bd7 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -343,11 +343,20 @@ function buildMessagingSection(params: { if (params.isMinimal) { return []; } + const hasSessionsSpawn = params.availableTools.has("sessions_spawn"); + const hasSubagents = params.availableTools.has("subagents"); + const subagentOrchestrationGuidance = hasSessionsSpawn + ? hasSubagents + ? "- Sub-agent orchestration → use `sessions_spawn(...)` to start delegated work; use `subagents(action=list|steer|kill)` to manage already-spawned children." + : "- Sub-agent orchestration → use `sessions_spawn(...)` to start delegated work." + : hasSubagents + ? "- Sub-agent orchestration → use `subagents(action=list|steer|kill)` to manage already-spawned children." + : ""; return [ "## Messaging", "- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)", "- Cross-session messaging → use sessions_send(sessionKey, message)", - "- Sub-agent orchestration → use subagents(action=list|steer|kill)", + subagentOrchestrationGuidance, `- Runtime-generated completion events may ask for a user update. Rewrite those in your normal assistant voice and send the update (do not forward raw internal metadata or default to ${SILENT_REPLY_TOKEN}).`, "- Never use exec/curl for provider messaging; OpenClaw handles all routing internally.", params.availableTools.has("message") @@ -355,7 +364,7 @@ function buildMessagingSection(params: { "", "### message tool", "- Use `message` for proactive sends + channel actions (polls, reactions, etc.).", - "- For `action=send`, include `to` and `message`.", + "- For `action=send`, include `target` and `message`.", `- If multiple channels are configured, pass \`channel\` (${params.messageChannelOptions}).`, `- If you use \`message\` (\`action=send\`) to deliver your user-visible reply, respond with ONLY: ${SILENT_REPLY_TOKEN} (avoid duplicate replies).`, params.inlineButtonsEnabled