fix(agents): prefer background completion wake over polling (#60877)

* fix(agents): prefer completion wake over polling

* fix(changelog): note completion wake guidance

* fix(agents): qualify quiet exec completion wake

* fix(agents): qualify disabled exec completion wake

* fix(agents): split process polling from control actions
This commit is contained in:
Vincent Koc
2026-04-05 03:17:10 +09:00
committed by GitHub
parent 0c3ec064f1
commit 6f2e804182
6 changed files with 26 additions and 2 deletions

View File

@@ -97,6 +97,7 @@ Docs: https://docs.openclaw.ai
- Gateway/auth: serialize async shared-secret auth attempts per client so concurrent Tailscale-capable failures cannot overrun the intended auth rate-limit budget. Thanks @Telecaster2147.
- Doctor/config: compare normalized `talk` configs by deep structural equality instead of key-order-sensitive serialization so `openclaw doctor --fix` stops repeatedly reporting/applying no-op `talk.provider/providers` normalization. (#59911) Thanks @ejames-dev.
- Gateway/device auth: reuse cached device-token scopes only for cached-token reconnects, while keeping explicit `deviceToken` scope requests and empty-cache fallbacks intact so reconnects preserve `operator.read` without breaking explicit auth flows. (#46032) Thanks @caicongyang.
- Agents/scheduling: steer background-now work toward automatic completion wake and treat `process` polling as on-demand inspection or intervention instead of default completion handling. (#60877) Thanks @vincentkoc.
- Google Gemini CLI auth: improve OAuth credential discovery across Windows nvm and Homebrew libexec installs, and align Code Assist metadata so Gemini login stops failing on packaged CLI layouts. (#40729) Thanks @hughcube.
- Mattermost/config schema: accept `groups.*.requireMention` again so existing Mattermost configs no longer fail strict validation after upgrade. (#58271) Thanks @MoerAI.
- Agents/failover: scope Anthropic `An unknown error occurred` failover matching by provider so generic internal unknown-error text no longer triggers retryable timeout fallback. (#59325) Thanks @aaron-he-zhu.

View File

@@ -1119,6 +1119,7 @@ export function describeExecTool(params?: { agentId?: string; hasCronTool?: bool
const base = [
"Execute shell commands with background continuation for work that starts now.",
"Use yieldMs/background to continue later via process tool.",
"For long-running work started now, rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use process to confirm completion. Use process whenever you need logs, status, input, or intervention.",
params?.hasCronTool
? "Do not use exec sleep or delay loops for reminders or deferred follow-ups; use cron instead."
: undefined,

View File

@@ -120,6 +120,7 @@ function resetPollRetrySuggestion(sessionId: string): void {
export function describeProcessTool(params?: { hasCronTool?: boolean }): string {
return [
"Manage running exec sessions for commands already started: list, poll, log, write, send-keys, submit, paste, kill.",
"Use poll/log when you need status, logs, quiet-success confirmation, or completion confirmation when automatic completion wake is unavailable. Use write/send-keys/submit/paste/kill for input or intervention.",
params?.hasCronTool
? "Do not use process polling to emulate timers or reminders; use cron for scheduled follow-ups."
: undefined,

View File

@@ -415,6 +415,15 @@ describe("tool descriptions", () => {
const execWithCron = createTestExecTool({ hasCronTool: true });
const processWithCron = createProcessTool({ hasCronTool: true });
expect(execWithCron.description).toContain(
"rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use process to confirm completion. Use process whenever you need logs, status, input, or intervention.",
);
expect(processWithCron.description).toContain(
"completion confirmation when automatic completion wake is unavailable.",
);
expect(processWithCron.description).toContain(
"Use write/send-keys/submit/paste/kill for input or intervention.",
);
expect(execWithCron.description).toContain(
"Do not use exec sleep or delay loops for reminders or deferred follow-ups; use cron instead.",
);
@@ -423,6 +432,9 @@ describe("tool descriptions", () => {
);
expect(execTool.description).not.toContain("use cron instead");
expect(processTool.description).not.toContain("scheduled follow-ups");
expect(execTool.description).toContain("otherwise use process to confirm completion");
expect(processTool.description).toContain("completion confirmation when automatic completion wake is unavailable");
expect(processTool.description).toContain("Use write/send-keys/submit/paste/kill for input or intervention.");
});
});

View File

@@ -124,6 +124,9 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain(
"Use exec/process only for commands that start now and continue running in the background.",
);
expect(prompt).toContain(
"For long-running work that starts now, start it once and rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use process to confirm completion, and use it for logs, status, input, or intervention.",
);
expect(prompt).toContain(
"Do not emulate scheduling with sleep loops, timeout loops, or repeated polling.",
);
@@ -295,6 +298,9 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain(
"Use exec/process only for commands that start now and continue running in the background.",
);
expect(prompt).toContain(
"For long-running work that starts now, start it once and rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use process to confirm completion, and use it for logs, status, input, or intervention.",
);
expect(prompt).toContain("Completion is push-based: it will auto-announce when done.");
expect(prompt).toContain("Do not poll `subagents list` / `sessions_list` in a loop");
expect(prompt).toContain(

View File

@@ -244,8 +244,9 @@ export function buildAgentSystemPrompt(params: {
const sandboxedRuntime = params.sandboxInfo?.enabled === true;
const acpSpawnRuntimeEnabled = acpEnabled && !sandboxedRuntime;
const execToolSummary =
"Run shell commands (pty available for TTY-required CLIs; use for work that starts now, not delayed follow-ups)";
const processToolSummary = "Manage background exec sessions for commands already started";
"Run shell commands (pty available for TTY-required CLIs; use for work that starts now, not delayed follow-ups; background completion may wake automatically when enabled)";
const processToolSummary =
"Manage background exec sessions for commands already started (poll/log for inspection, debugging, input, intervention, or completion confirmation when auto-wake is unavailable)";
const cronToolSummary =
"Manage cron jobs and wake events (use for reminders, delayed follow-ups, and recurring tasks; for requests like 'check back in 10 minutes' or 'remind me later', use cron instead of exec sleep, yieldMs delays, or process polling; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)";
const coreToolSummaries: Record<string, string> = {
@@ -477,10 +478,12 @@ export function buildAgentSystemPrompt(params: {
? [
`For follow-up at a future time (for example "check back in 10 minutes", reminders, run-later work, or recurring tasks), use cron instead of ${execToolName} sleep, yieldMs delays, or ${processToolName} polling.`,
`Use ${execToolName}/${processToolName} only for commands that start now and continue running in the background.`,
`For long-running work that starts now, start it once and rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use ${processToolName} to confirm completion, and use it for logs, status, input, or intervention.`,
"Do not emulate scheduling with sleep loops, timeout loops, or repeated polling.",
]
: [
`For long waits, avoid rapid poll loops: use ${execToolName} with enough yieldMs or ${processToolName}(action=poll, timeout=<ms>).`,
`For long-running work that starts now, start it once and rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use ${processToolName} to confirm completion, and use it for logs, status, input, or intervention.`,
]),
"If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done.",
...(acpHarnessSpawnAllowed