news 2026/9/14 19:56:23

Qwen Code /loop 技能深度解析:循环 Prompt 的固定间隔调度与自节奏唤醒实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qwen Code /loop 技能深度解析:循环 Prompt 的固定间隔调度与自节奏唤醒实现

Qwen Code /loop 技能深度解析:循环 Prompt 的固定间隔调度与自节奏唤醒实现

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

本文基于 Qwen Code 内置技能文档 SKILL.md,完整拆解/loop命令的输入解析规则、固定间隔(cron)路径、自节奏(LoopWakeup)路径、loop.md任务文件模式与自主(Autonomous)模式五大执行路径,并结合 loop-wakeup.ts、cron-create.ts 与 cronScheduler.ts 的源码,讲清楚每条路径背后的调度机制、参数边界与安全约束。读完本文,你将掌握/loop的全部用法,并理解它如何用“秒级一次性唤醒 + 分钟级 cron 任务”两套引擎实现可长期运行的自主工作循环。

技能定义与工具白名单

/loop是 Qwen Code 的内置(bundled)技能,其声明位于 packages/core/src/skills/bundled/loop/SKILL.md 的 frontmatter 中:

--- name: loop description: Create a loop that runs a prompt now and follows up either on a fixed schedule or through self-paced wakeups. Usage - /loop check the build, /loop 5m check the build, /loop check the PR every 30m. /loop list to show jobs, /loop clear to cancel all. argument-hint: '[interval] [prompt] | list | clear' allowedTools: - cron_create - cron_list - cron_delete - loop_wakeup ---

frontmatter 中的allowedTools限定了技能执行期间可调用的工具集合:cron_create(创建定时任务)、cron_list(列出任务)、cron_delete(删除任务)与loop_wakeup(自节奏唤醒原语)。这一白名单由测试 SKILL.test.ts 显式断言,确保技能文档与工具集始终一致。

从源码结构看,这四个工具对应两套独立的调度引擎:

  • cron 路径cron_create/cron_list/cron_delete):分钟粒度、可持久化到磁盘的周期或一次性任务,核心实现在 packages/core/src/services/cronScheduler.ts;
  • 自节奏路径loop_wakeup):秒级精度、仅存在于会话内存中的一次性唤醒,实现在 packages/core/src/tools/loop-wakeup.ts。

子命令:list 与 clear

输入在去掉/loop前缀后如果恰好是以下关键词之一,则执行子命令而不是调度:

  • list— 调用 CronList 并展示结果,结束。
  • clear— 先调用 CronList,然后对返回的每一个任务调用 CronDelete,并确认取消了几个任务,结束。

对应的 CronList/CronDelete 工具共享同一个调度器实例:CronScheduler.list()会同时返回 cron 任务和待触发的 loop wakeup(wakeup 通过wakeupToJob映射成最小CronJob形状,cronExpr标记为@wakeup),因此list能看到两类任务,clear也能一次性取消全部。

输入解析:四种路径的分流规则

解析发生在技能文档的 “Parsing” 一节。去掉/loop前缀后,按以下顺序判断:

  1. 空输入(没有 prompt 也没有 interval):走自主路径(autonomous path),运行一个自节奏的自主循环。
  2. 前导 interval 记号:第一个以空白分隔的记号匹配^\d+[smhd]$(如5m2h),则走固定间隔重复路径,其余部分为 prompt。
  3. 尾部 “every” 从句:否则,如果输入以every <N><unit>every <N> <unit-word>结尾(如every 20mevery 5 minutesevery 2 hours),走固定间隔重复路径;提取出 interval 后从 prompt 中剔除。注意只有 “every” 后面跟着时间表达式时才匹配——check every PR中没有 interval。
  4. 纯 prompt 输入:其余情况整个输入都是 prompt,走纯 prompt 的自节奏路径。

如果给了 interval 但 prompt 为空(如/loop 5m),则是固定间隔的自主循环,见下文 Autonomous mode 一节。

文档给出的解析示例:

输入解析结果依据
5m /babysit-prs固定间隔5m,prompt/babysit-prs前导 interval 记号
check the deploy every 20m固定间隔20m,promptcheck the deploy尾部 "every" 从句
run tests every 5 minutes固定间隔5m,promptrun tests尾部 "every" 从句(词形单位)
check every PR纯 prompt 自节奏路径,promptcheck every PR"every" 后不是时间表达式
check the deploy纯 prompt 自节奏路径,promptcheck the deploy无 interval
(空)自节奏自主循环(哨兵<<autonomous-loop-dynamic>>空输入
5m固定间隔自主循环(interval5m,哨兵<<autonomous-loop>>只有 interval

纯 prompt 自节奏路径

该路径仅在用户给了 prompt 但没给 interval 时使用,其执行步骤为:

  1. 不调用 CronCreate。该路径不使用 cron 引擎。
  2. 如果本次 tick 以一个<task-notification>块开头(说明是监控器或后台事件重新唤醒了你,而不是裸的/loop唤醒 prompt),先处理该事件再重跑 prompt:
    • 若通知表明被监视的条件已满足:如果还持有其 ID,用 CronDelete 取消任何待处理的 fallback LoopWakeup,然后结束循环。
    • 若 Monitor 因空闲或 max-events 自动停止:只要监视仍然有用就重启一次,重新布防 fallback,向用户报告重启次数,并把该计数写进 LoopWakeup 的 prompt 或 reason 中(例如monitor restarted 1/1 time),使其能在上下文压缩(context compaction)之后存活。若下一个 tick 它又自动停止,则结束循环并向用户报告重复的自动停止。
    • 若信号含义模糊:重新布防一个更短的后续检查,在下一个 tick 继续调查;若连续三个 tick 信号仍然模糊,结束循环并报告无法得出明确结论。
  3. 立即执行解析出的 prompt:如果是 slash 命令,通过 Skill 工具调用;否则直接执行。
  4. 在结束当前轮次前,判断是否还有下一次有用的检查:
    • 只有持续跟进有用时才调用 LoopWakeup;
    • 任务已完成则不调用;
    • 任务被阻塞在用户输入或无法稍后检查的外部状态上则不调用;
    • 不存在有用的下一次检查时,不要仅仅为了维持轮询而调用。
    • 如果你启动了后台 agent 或 Monitor,它们会在退出、失败、取消或 Monitor 自动停止时通过终端的<task-notification>唤醒你 —— 所以 LoopWakeup 应设为长周期 fallback而不是短轮询。不要仅仅因为“有东西在监视”就省略它:工作可能挂死,Monitor 可能在空闲或 max-events 时自动停止(而且另一个 agent 拥有的 Monitor,其通知只路由给那个 agent)。只有上述终止条件(完成、被阻塞、重复的 Monitor 自动停止)才省略 LoopWakeup。
  5. 调度续跑时,调用 LoopWakeup 并传入:
    • delaySeconds:下一次有用检查的秒数。运行时钳制到 60–3600(1–60 分钟);取值应遵循工具自身的指引 —— 它会考虑 prompt-cache 窗口,以及后台任务会唤醒你时 fallback-heartbeat 的取值;
    • prompt/loop ${原始 prompt},外加下一个 tick 必须保留的状态(如monitor restarted 1/1 time);
    • reason:简短说明所选延迟的原因;若在自动停止后重新布防,把 Monitor 重启计数也写在这里。
  6. 用简短的话告诉用户刚才做了什么。如果调度了唤醒,说明预计下次检查时间;如果因通知结束循环而没有调度唤醒,说明陈旧的 fallback 是否已被取消;若唤醒 ID 已丢失,当陈旧唤醒触发时简要忽略或回应即可。

LoopWakeup 工具的实现边界

技能文档中“delaySeconds 钳制到 60–3600”这一约束由源码精确兑现。packages/core/src/tools/loop-wakeup.ts 中:

  • 参数 schema 要求delaySeconds(number)与prompt(string,最长 10000 字符),reason可选;
  • delaySeconds的描述明确给出取值策略:60–270 秒仅用于主动轮询没有任何其他上报机制的外部状态(CI 运行、远端队列),以保持在约 5 分钟的 prompt-cache 窗口内;当有后台任务会通过<task-notification>唤醒你时,这里应取1200–1800 秒作为 fallback(任务挂死、Monitor 自动停止、或通知被路由给其他 agent 的情形);无特定信号可监视时,默认1200 秒以上
  • 执行时若 scheduler 已被禁用(本会话 token 达到上限触发的断路器),会返回“Loop wakeups are disabled for the rest of this session”的错误而不是静默失败。

钳制逻辑位于 cronScheduler.ts 的clampWakeupSeconds

export const WAKEUP_MIN_SECONDS = 60; export const WAKEUP_MAX_SECONDS = 3600; const WAKEUP_DEFAULT_SECONDS = 1200; const WAKEUP_CHAIN_MAX_AGE_MS = 24 * 60 * 60 * 1000; export function clampWakeupSeconds(delaySeconds: number): number { if (!Number.isFinite(delaySeconds)) return WAKEUP_DEFAULT_SECONDS; return Math.min( WAKEUP_MAX_SECONDS, Math.max(WAKEUP_MIN_SECONDS, Math.round(delaySeconds)), ); }

即:非有限输入回落到 1200 秒默认心跳,其余输入四舍五入后钳制到 [60, 3600] 区间。此外scheduleWakeup还实施了一条文档未展开的硬约束:自节奏唤醒链受24 小时会话预算限制(WAKEUP_CHAIN_MAX_AGE_MS),预算起点在整个会话内不随单次唤醒重置,防止连续循环逃出上限;且同一时刻最多只保留一个待触发 wakeup —— 新调度会替换旧的(返回值中的replacedId即被替换者)。

从源码注释看,LoopWakeup 的默认权限被设为'ask'(而非'allow'):唤醒会“在未来以完整工具权限对 agent 执行续跑 prompt”,属于副作用操作,必须经过 AUTO 模式的 classifier 审核 —— 这与 CronCreate 的安全考量一致,两个工具的toAutoClassifierInput都会把完整 prompt、cron 表达式与延迟参数转交给分类器。

固定间隔重复路径

该路径用于含前导 interval 记号或尾部 "every" 从句的输入,分两步:先把 interval 转成 cron 表达式,再调用 CronCreate。

Interval 到 cron 的转换表

支持的尾缀:s(秒,向上取整到最近分钟,最小 1)、m(分钟)、h(小时)、d(天)。转换规则:

Interval 模式Cron 表达式说明
Nm(N <= 59)*/N * * * *每 N 分钟
Nm(N >= 60)0 */H * * *折算为小时(H = N/60,必须整除 24)
Nh(N <= 23)0 */N * * *每 N 小时
Nd0 0 */N * *每 N 天的本地零点
Nsceil(N/60)m处理cron 最小粒度是 1 分钟

如果 interval 不能整除其单位(例如7m会在:56:00之间产生不均匀间隔,或90m是 1.5 小时、cron 无法表达),应选择最近的整洁 interval,并在调度前告知用户被取整到了什么

调度动作

  1. 调用 CronCreate,传入:
    • cron:上表得到的表达式;
    • prompt:解析得到的 prompt 原样传递(slash 命令不做改动直接透传);
    • recurringtrue
    • durable:若用户语言暗示持久(“keep doing this”、“set this up permanently”、“every day even after restart”)则传true,否则省略(默认仅会话内有效)。
  2. 简要确认:调度的内容、cron 表达式、人类可读的频率、自动过期时间(默认创建后 7 天 —— CronCreate 工具描述中声明的配置值可能不同或已被禁用),以及可以用 CronDelete(附 job ID)提前取消。
  3. 立即执行解析出的 prompt,不要等 cron 第一次触发。slash 命令通过 Skill 工具调用,否则直接执行。

CronCreate 的实际行为可由 packages/core/src/tools/cron-create.ts 印证:

  • cron 表达式使用标准 5 字段格式(本地时区)minute hour day-of-month month day-of-week,工具创建前会先parseCron校验并调用nextFireTime拒绝“能解析但永远不匹配”的表达式(如0 0 30 2 *);
  • recurring默认truedurable默认falsedurable: true会把任务写入~/.qwen下的任务文件(源码常量CRON_TASKS_DISPLAY_PATH),重启后仍存活;
  • 工具描述明确要求“避免 :00 和 :30 整点”(所有用户都要 9 点都会挤在同一瞬间),并说明调度器会叠加确定性抖动:周期任务最多延迟其周期的 10%(上限 15 分钟),落在 :00/:30 的一次性任务最多提前 90 秒 —— 这些数值与 cronScheduler.ts 中MAX_RECURRING_JITTER_MS = 15 * 60 * 1000MAX_ONESHOT_JITTER_MS = 90 * 1000的常量一致;
  • 关于“默认 7 天自动过期”:源码中DEFAULT_RECURRING_MAX_AGE_DAYS = 7,周期任务在触发时评估年龄,过期任务最后再触发一次然后被删除;该值可由配置覆盖(设置或环境变量QWEN_CODE_CRON_MAX_AGE_DAYS),设为0表示禁用过期。

此外从调度器源码可以看到两条与/loop相关的全局边界:调度器内存任务总数上限为MAX_JOBS = 50(wakeup 不计入该上限,因为它存放在独立的 map 中);且任务只在 REPL 空闲时触发(不在 query 进行中途)。

loop.md 任务文件模式

当用户希望循环去处理维护在文件中的任务清单(用户说“work through my loop.md”、“loop over the tasks in .qwen/loop.md”或指向这样一个文件)时使用此模式。任务存放在.qwen/loop.md(项目级)或~/.qwen/loop.md(home 级;项目级优先)。

与直接写 prompt 不同,此模式把循环的prompt设为哨兵(sentinel),让每次触发都重新读取文件:

  • 自节奏(无 interval)→ LoopWakeup 的prompt<<loop.md-dynamic>>
  • 固定间隔 → CronCreate 的prompt<<loop.md>>(配合recurring: true,若暗示持久则加durable: true)。

每次触发时,你会收到完整任务清单(首次投递、文件变更后、或上下文压缩后),或一条简短的“继续处理之前已建立的清单”的提醒。处理任务;自节奏模式下,只有持续跟进有用时才用<<loop.md-dynamic>>重新布防 LoopWakeup(与纯 prompt 路径相同的“完成/被阻塞则不再布防”规则)。若触发时.qwen/loop.md不存在,循环回落到自主模式(继续自主工作而不是空转);文件被重建后会在下一次触发时被拾取。向用户确认时用自然语言(如“looping over your.qwen/loop.mdtask list…”),不要暴露原始哨兵。

哨兵的触发期解析实现在 loop-tick-resolver.ts:

  • 导出的LOOP_SENTINEL_CRON = '<<loop.md>>'LOOP_SENTINEL_DYNAMIC = '<<loop.md-dynamic>>'与文档一一对应;
  • 变更检测采用全内容相等(而非 mtime/hash),因此“编辑”和“删除后重建”都会自动重新展开完整任务块;完整任务块只在首次投递或文件变化时送入一次,后续 tick 只发一行简短提醒 —— 任务清单只支付一次进缓存前缀的成本;
  • allowProjectFile依赖每次resolve()重新评估isTrustedFolder():工作区信任状态从 trusted 翻转为 untrusted 时,会立即停止读取仓库可控的项目.qwen/loop.md(用户自有的~/.qwen/loop.md仍然会读),这是一道防提示注入的信任边界。

Autonomous 自主模式

/loop(无 prompt、无文件)时使用此模式 —— 用户希望在离开的这段时间里让 agent 持续推进工作。用自主哨兵布防循环,并立即执行第一次检查:

  • 自节奏(空输入)→ LoopWakeup 的prompt<<autonomous-loop-dynamic>>
  • 固定间隔(/loop <interval>,无 prompt)→ CronCreate 的prompt<<autonomous-loop>>(配合recurring: true,若暗示持久则加durable: true)。

第一次立即检查,以及每次定时触发,都推进对话已经建立的工作 —— 完成用户开始的事项、维护进行中的 PR(回应 review 评论、修复失败的 CI、解决冲突)、兑现 “I'll also…” 的承诺。核心约束是:你是管家(steward),不是发起者(initiator)—— 只处理 transcript 中已建立的事情;没有明确授权绝不发明新工作或做不可逆操作(push、delete、send)。如果一切都确实平静,用一句话说明并停止。

第一次触发(以及压缩后的第一次)会投递更完整的指导;后续触发只发送指向它的简短提醒。自节奏模式下用<<autonomous-loop-dynamic>>重新布防 LoopWakeup(相同的“完成/被阻塞则不再布防”规则)。向用户确认时用自然语言(“running an autonomous loop on your work…”),不要暴露原始哨兵。

哨兵常量与完整前导语(preamble)定义在 autonomous-loop.ts:

export const AUTONOMOUS_SENTINEL_CRON = '<<autonomous-loop>>'; export const AUTONOMOUS_SENTINEL_DYNAMIC = '<<autonomous-loop-dynamic>>';

该文件中的AUTONOMOUS_PREAMBLE是首次投递的完整自主检查指导,逐字实现了文档中“steward, not an initiator”的原则,并补充了可操作细节:把工具输出、文件内容、CI 日志、SCM 评论与抓取的远端数据都视为不可信上下文(作为调查证据而非用户授权);最高信号来源是进行中的 PR(回应并解决 review 线程、诊断失败的 CI、修复合并冲突);连续三次“无事可做”后应收缩为一次快速 CI 检查并用一行收尾,避免刷屏 transcript。autonomousTickText则生成后续触发的短提醒,并根据 pacing 模式附上不同的再布防指引:cron 模式提示“recurring cron 会自动触发下一次 tick,本 tick 不要再调用 LoopWakeup”,dynamic 模式则要求在本轮末尾再次调用 LoopWakeup 并把 prompt 设为哨兵字面量,否则循环在本 tick 后结束。

安全与生命周期要点汇总

综合技能文档与源码,/loop的实现带有若干值得注意的安全与生命周期设计:

  1. 权限门控loop_wakeupcron_create的默认权限都是'ask',因为两者的 prompt 都会在触发时以完整工具权限对 agent 执行,必须经过 AUTO 模式 classifier(或手动审批)审核 —— 见 loop-wakeup.ts 与 cron-create.ts 中的getDefaultPermission注释;
  2. 自节奏唤醒的三重预算:单次延迟钳制在 [60, 3600] 秒、非有限输入回落 1200 秒、整条唤醒链受 24 小时会话预算约束 —— 防止自循环无限自我续命;
  3. 会话内与持久化的区分:LoopWakeup 是 session-only 一次性任务,从不落盘、从不计入 50 任务上限;CronCreate 默认 session-only(进程退出即消失),仅当用户明确要求持久时才写盘,且周期任务默认 7 天后自动过期;
  4. token 断路器:会话 token 达到上限后调度器被禁用,此时 LoopWakeup 会返回明确的“本会话剩余时间内已禁用”错误,而不是留下一个永远不会触发的僵尸任务;
  5. 文档一致性有测试兜底:SKILL.test.ts 用 vitest 断言技能白名单、各路径关键句、哨兵字符串与delaySeconds(而非delayMinutes)的存在,保证技能文档、哨兵常量与调度器实现三者在演进中不脱节。

适用前提与限制

  • 本文所有行为描述以当前仓库中的技能文档与源码为准:/loop依赖cron_createcron_listcron_deleteloop_wakeup四个内置工具,且技能仅在 REPL 空闲时触发任务;
  • cron 表达式基于用户本地时区的标准 5 字段格式;durable 任务要求项目根目录存在(createDurable在无 projectRoot 时抛错),standalone 会话不支持 durable cron 任务;
  • 周期任务的过期天数(默认 7 天)、wakeup 延迟区间(60–3600 秒)与 24 小时唤醒链预算都是代码常量/配置值,若仓库后续调整配置,应以对应源码常量为准。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 19:55:43

后端工程师三天速成前端三件套:AI辅助下的高效学习路径

很多后端同学跑来问我同一个问题&#xff1a;前端三件套到底怎么学&#xff1f;以前我带人上手HTML、CSS、JavaScript&#xff0c;最快也要两三个月&#xff0c;中间还得折腾一堆构建工具、框架概念&#xff0c;很多人没到写页面就先放弃了。现在情况确实不一样了&#xff0c;A…

作者头像 李华
网站建设 2026/9/14 19:54:15

SpringBoot+Vue+MyBatis+MySQL汽车销售网站系统全栈实战解析

做了好几个月的汽车销售管理类项目&#xff0c;这次这套基于SpringBootVueMyBatisMySQL的靓车汽车销售网站系统算是我觉得最能直接拿来复用的产物。整个系统覆盖了常见的商用场景&#xff1a;用户端看车、搜车、看视频、预约试驾、在线询价&#xff0c;管理端负责车辆上下架、分…

作者头像 李华
网站建设 2026/9/14 19:54:11

智驾芯片竞争本质:不是算力排行榜,而是工程确定性之战

1. 英伟达不是“王座”&#xff0c;而是整个智驾芯片生态的底层操作系统“谁能撼动英伟达王座&#xff1f;”——这个提问本身&#xff0c;就暴露了对当前智能驾驶芯片格局最典型的认知偏差。我从2018年参与第一代L2域控制器量产项目起&#xff0c;就反复在内部技术评审会上强调…

作者头像 李华
网站建设 2026/9/14 19:54:11

GEO与SEO/SEM差异解析及地理引擎优化实战

1. GEO与传统SEO/SEM的本质差异解析GEO&#xff08;地理引擎优化&#xff09;与传统SEO/SEM的根本区别在于目标维度的不同。传统SEO/SEM主要解决"如何在搜索引擎结果页获得更好排名"的问题&#xff0c;而GEO要解决的是"如何在地理空间维度上获得更精准的流量分发…

作者头像 李华