Skip to content

feat(cli): add /loop command for periodic task execution#2643

Closed
wenshao wants to merge 24 commits intoQwenLM:mainfrom
wenshao:feat/loop-command-clean
Closed

feat(cli): add /loop command for periodic task execution#2643
wenshao wants to merge 24 commits intoQwenLM:mainfrom
wenshao:feat/loop-command-clean

Conversation

@wenshao
Copy link
Copy Markdown
Collaborator

@wenshao wenshao commented Mar 24, 2026

Summary

  • Add /loop slash command that periodically executes a prompt at a configurable interval
  • Add LoopManager in core for framework-agnostic loop state management
  • Integrate with AppContainer for automatic iteration scheduling on streaming completion

Closes #2638

Features

Command Description
/loop 5m check if CI passed Run every 5 minutes
/loop 30s --max 10 is server healthy? Run every 30s, max 10 times
/loop status Show active loop info
/loop stop Cancel the active loop
  • Interval support: 10s to 24h, default 10m
  • Auto-pause after 3 consecutive failures
  • Tab completion for subcommands and common intervals
  • i18n for all user-facing strings
  • waitingForResponse flag ensures normal user prompts don't interfere with loop scheduling

Test plan

  • 24 unit tests covering interval parsing/formatting, loop lifecycle, failure handling, pause/resume, skipFirstIteration, timer cleanup, singleton management
  • Manual: /loop 10s check date — verify iterations appear with headers
  • Manual: /loop stop during active loop — verify clean stop
  • Manual: /loop status — verify status display
  • Manual: type a normal prompt during loop interval wait — verify it doesn't disrupt loop

🤖 Generated with Claude Code

Implement a /loop slash command that periodically executes a prompt
at a configurable interval, enabling hands-off monitoring and
automation workflows.

Core module (packages/core/src/loop/):
- LoopManager: framework-agnostic state management with start/stop/resume
- Interval parsing (30s, 5m, 1.5h) and formatting utilities
- Configurable max iterations and auto-pause after 3 consecutive failures
- Minimum interval 10s, maximum 24h to prevent API abuse and timer overflow
- waitingForResponse flag to distinguish loop iterations from user prompts

CLI command (packages/cli/src/ui/commands/loopCommand.ts):
- /loop [interval] <prompt> — start a loop (default: 10m)
- /loop [interval] --max N <prompt> — limit iterations
- /loop status — show active loop info
- /loop stop — cancel the active loop
- Tab completion for subcommands and common intervals

AppContainer integration:
- Registers loop iteration callback via useRef to avoid stale closures
- Notifies loop manager on streaming completion for scheduling
- Only processes completions when loop is waiting (not user prompts)
- Displays iteration headers and completion/pause messages with i18n
- Stops loop on component unmount

Tests: 24 tests covering interval parsing, formatting, loop lifecycle,
failure handling, pause/resume, skipFirstIteration, waitingForResponse
guard, timer cleanup, and singleton management.

Closes QwenLM#2638

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR introduces a /loop command that enables periodic task execution at configurable intervals, along with a LoopManager class for framework-agnostic state management. The implementation is well-structured with comprehensive test coverage (24 unit tests), proper i18n support, and thoughtful UX features like auto-pause on failures and tab completion. Overall, this is a solid implementation that addresses the feature requirements effectively.

🔍 General Feedback

  • Architecture: Clean separation between the CLI command layer (loopCommand.ts) and core logic (LoopManager), following the project's established patterns
  • Test Coverage: Excellent unit test coverage for interval parsing/formatting, loop lifecycle, failure handling, pause/resume, and timer cleanup
  • User Experience: Well-considered features including auto-pause after 3 consecutive failures, waitingForResponse flag to prevent interference from normal prompts, and clear status/stop subcommands
  • Internationalization: All user-facing strings properly wrapped with t() for i18n support
  • Type Safety: Strong TypeScript typing throughout with proper interfaces for LoopConfig and LoopState

🎯 Specific Feedback

🟡 High

  • packages/cli/src/ui/AppContainer.tsx:934-967 - The useEffect that sets up the loop iteration callback has an empty dependency array but captures addMessage and historyManager via refs. While the ref pattern is correct, the t function used inside the callback could become stale if the locale changes. Consider adding t to the dependencies or using a ref for it as well.

  • packages/cli/src/ui/AppContainer.tsx:969-1008 - The streaming state effect checks loopManager.isActive() and loopManager.isWaitingForResponse() but these values could change between renders. The effect only depends on streamingState, which means if the loop manager state changes, this effect won't re-run. Consider whether this could lead to missed notifications or stale state reads.

  • packages/core/src/loop/loopManager.ts:183-186 - The onIterationComplete method keeps state accessible after loop completion but doesn't clear the timer. If stop() is called after completion, it will try to clear a potentially already-executed timer. While clearTimeout is idempotent, consider explicitly clearing timerId when the loop completes naturally.

🟢 Medium

  • packages/cli/src/ui/commands/loopCommand.ts:58-71 - The parseLoopArgs function returns default values when no tokens are provided, but this results in an empty prompt which then fails validation in the action. Consider returning an error indicator earlier or documenting this flow more clearly.

  • packages/core/src/loop/loopManager.ts:15 - The MAX_CONSECUTIVE_FAILURES constant is hardcoded as a module constant. Consider making this configurable via the LoopConfig interface to allow callers to customize the failure threshold.

  • packages/core/src/loop/loopManager.ts:203-205 - The comment states "Keep state accessible briefly so caller can read final iteration count" but there's no mechanism to clean up this state. The state remains accessible until stop() is called or a new loop starts. Consider documenting this behavior or adding a cleanup timeout.

  • packages/cli/src/ui/commands/loopCommand.ts:236-245 - The completion function only provides completions for subcommands and preset intervals when the partial argument is empty or matches 'status'/'stop'. It doesn't provide any completion suggestions when the user is typing an interval or prompt, which could be a missed opportunity for UX improvement.

🔵 Low

  • packages/core/src/loop/loopManager.ts:127-128 - The getState() method returns Readonly<LoopState> but the actual returned object is not frozen. Consider using Object.freeze() or creating a readonly copy to prevent accidental mutations from outside the class.

  • packages/cli/src/ui/commands/loopCommand.ts:1 - The license header mentions "Qwen Team" and "Apache-2.0" which is consistent with the project. However, the JSDoc comment at the top of the file could benefit from an @module tag for better documentation generation.

  • packages/core/src/loop/loopManager.test.ts:113-115 - The test "ignores onIterationComplete when not waiting for response" only tests one edge case. Consider adding a test for calling onIterationComplete when no loop is active (state is null) to ensure it doesn't throw.

  • packages/cli/src/ui/AppContainer.tsx:44-45 - The imports getLoopManager and formatInterval are added to an existing import statement from @qwen-code/qwen-code-core. Consider grouping these with related imports for better readability.

  • packages/core/src/loop/loopManager.ts:91-92 - The parseInterval function uses Math.round() for conversion but doesn't validate that the result fits within JavaScript's safe integer range. While practically not an issue for the allowed interval range (10s to 24h), adding a comment or validation could improve robustness.

✅ Highlights

  • Excellent Test Coverage: The 24 unit tests comprehensively cover interval parsing/formatting edge cases, loop lifecycle, failure handling, pause/resume functionality, and singleton management
  • Clean Architecture: The separation between LoopManager (core logic) and loopCommand (UI layer) follows the project's established patterns and makes the code highly maintainable
  • Thoughtful UX Features: The auto-pause after 3 consecutive failures, waitingForResponse flag to prevent prompt interference, and clear status messages demonstrate careful consideration of real-world usage scenarios
  • Proper Resource Cleanup: The stop() method properly clears timers, and the useEffect cleanup in AppContainer ensures the loop is stopped when the component unmounts
  • Good Error Handling: Clear error messages for invalid intervals, missing prompts, and usage instructions with examples

… null state test

- Explicitly clear timerId when loop completes via maxIterations,
  even though clearTimeout is idempotent, for defensive cleanup
- Add test for onIterationComplete when no loop is active (null state)
  to verify it doesn't throw

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wenshao
Copy link
Copy Markdown
Collaborator Author

wenshao commented Mar 24, 2026

Thanks for the thorough review! Here's how I addressed each point:

High

  1. t function stale in callbackt is a module-level import (not React state/prop), so it stays current even when locale changes trigger a re-render. No ref needed.

  2. Streaming state effect reading stale loopManager stategetLoopManager() returns a singleton with mutable internal state. Each effect invocation calls the methods directly on the live instance, so isActive() / isWaitingForResponse() always reflect current state. The effect only needs streamingState as trigger — it's intentional.

  3. timerId not cleared on natural completion — ✅ Fixed in a1ab9df. Now explicitly clears timerId when loop completes via maxIterations, even though no pending timer should exist at that point (defensive cleanup).

Medium

  1. parseLoopArgs empty prompt flow — This is intentional: empty prompt → action shows usage help. Standard pattern used by other commands in the project.

  2. MAX_CONSECUTIVE_FAILURES configurable — Good suggestion for a follow-up. Keeping it simple for v1 to avoid over-engineering the config surface.

  3. State remains after completion — The state is only read once by AppContainer (to show "Loop completed" message) and then becomes inaccessible via /loop status (which checks isActive). Next /loop start or /loop stop call will clean it up.

  4. Richer tab completion — Agreed this could be improved later. Current completions cover the most common use cases.

Low

8-9. Readonly / @module — Accepted as-is. Readonly<> is sufficient for the current consumer pattern.

  1. Null state test — ✅ Added in a1ab9df. New test verifies onIterationComplete doesn't throw when no loop is active.

11-12. Import grouping / safe integer — Imports follow existing project conventions. MAX_INTERVAL_MS (24h) keeps values well within safe integer range.

…nsistency

- Fix race condition: loop prompts now use a dedicated submission path
  (loopSubmitRef + submitQueryRef) instead of sharing the user message
  queue. Added loopInitiatedStreamRef to distinguish loop-initiated
  streams from user-initiated ones.
- Fix error detection: scan pending items (API/streaming errors) and
  committed history backward for ERROR and tool_group errors, instead
  of only checking the last history item.
- Fix slash command consistency: all loop iterations submit as Part[]
  to bypass isSlashCommand() parsing, matching the first-iteration
  submit_prompt path.
- Fix /btw interaction: don't clear loop flag for btw commands that
  run alongside the current stream.
- Fix /loop stop with queued prompt: discard queued prompt if loop
  was stopped while waiting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wenshao
Copy link
Copy Markdown
Collaborator Author

wenshao commented Mar 25, 2026

Code Review & Fix Summary

经过多轮代码审查,发现以下问题并在 commit 26fbe265 中修复:

1. Race Condition — 用户消息与 loop 迭代竞态(中)

问题:loop 迭代通过 addMessage 和用户消息共享同一个 useMessageQueue。当 loop timer 在用户 AI 响应期间触发时,executeIterationwaitingForResponse=true 并通过 addMessage 排队,用户响应完成后 completion effect 误将用户响应当作 loop 迭代完成处理,loop prompt 的实际响应被忽略,导致 loop 行为错乱。

修复:loop 迭代使用独立的提交路径(loopSubmitRef + submitQueryRef),不再经过 useMessageQueue。新增 loopInitiatedStreamRef 区分 loop 和用户发起的 stream。

2. 嵌套 Slash 命令不一致(中)

问题:首次迭代通过 submit_prompt 返回 Part[],绕过 isSlashCommand() 检查;后续迭代通过 addMessage 传入 string,会触发 slash 命令解析。如果 prompt 是 /review 等命令,首次作为纯文本发送,后续会被当作命令执行。

修复:所有迭代统一通过 Part[] 提交,与首次迭代路径一致。

3. 错误检测不可靠(中)

问题:仅检查 history 最后一条是否为 MessageType.ERROR。但 API 流式错误存在 pendingRetryErrorItem 中(不进入 history),tool 执行错误存为 tool_group 类型。大多数实际错误被漏判,连续失败自动暂停机制基本不生效。

修复:新增 pendingGeminiHistoryItems 检查 + 向后扫描 history 直到 MessageType.USER,捕获 ERROR 和 tool_group 中的 ToolCallStatus.Error

4. 其他修复

  • /btw 命令不再误清 loop flag(btw 是旁路命令,不影响主 stream)
  • /loop stop 时丢弃 loopSubmitRef 中残留的排队 prompt
  • 首次迭代 completion 通过 loopSubmitRef === null 判断为 loop 响应

Remaining Items (Low Priority)

  • resume() 方法已实现但无 /loop resume UI 入口
  • 8 个 i18n key 未添加到翻译文件(fallback 英文正常工作)
  • getState() 返回 Readonly<LoopState> 但暴露可变引用(编译时约束足够)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new /loop CLI slash command backed by a framework-agnostic LoopManager in core, with CLI integration that schedules subsequent iterations when streaming completes.

Changes:

  • Introduces LoopManager (+ interval parse/format helpers, singleton accessors) and unit tests in packages/core.
  • Adds /loop command implementation in the CLI and registers it in BuiltinCommandLoader.
  • Integrates loop iteration triggering/completion handling into AppContainer.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/core/src/loop/loopManager.ts Implements core loop state machine, interval helpers, and singleton accessors.
packages/core/src/loop/loopManager.test.ts Adds Vitest coverage for parsing/formatting and loop lifecycle behavior.
packages/core/src/loop/index.ts Exposes loop module exports.
packages/core/src/index.ts Re-exports loop module from core public API.
packages/cli/src/ui/commands/loopCommand.ts Implements /loop start/status/stop + interval bounds and completions.
packages/cli/src/ui/AppContainer.tsx Wires loop iteration callback to prompt submission and marks completion on stream idle.
packages/cli/src/services/BuiltinCommandLoader.ts Registers the new loop built-in command.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

… interval

- Nested slash commands: loop iterations now submit as string (not
  Part[]) so `/loop 5m /review` executes /review on each iteration.
  First iteration also goes through the callback+state drain path
  instead of submit_prompt, ensuring consistent behavior.
- Day interval unit: parseInterval/formatInterval now support `d`
  (e.g., `/loop 1d check status`). MAX_INTERVAL_MS already covers 24h.
- Natural language interval: `/loop check deploy every 5m` extracts
  the interval from the prompt tail (regex: `every <interval>$`).
- Changed pendingLoopPrompt from ref to React state to trigger
  re-renders when the timer callback queues a prompt during Idle.
- Added tests for day parsing/formatting (27 total).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…validation

- Fix client-side slash command hang: after submitQuery resolves, if
  no streaming was started (e.g. /help, /clear), yield to React's
  render cycle then manually call onIterationComplete so the loop
  can advance to the next iteration.
- Fix parseLoopArgs: use hasExplicitInterval flag instead of comparing
  against DEFAULT_INTERVAL_MS for trailing "every" parsing, so
  `/loop 10m X every 5m` doesn't silently override the explicit 10m.
- Add config validation in LoopManager.start(): reject intervalMs
  outside MIN/MAX bounds and non-integer maxIterations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix drain/messageQueue race: drain effect now checks
  messageQueue.length > 0 and skips if user messages are pending,
  preventing submitQuery from early-returning and dropping the
  loop prompt.
- Fix --max parsing: `/loop --max` or `/loop --max foo` now
  returns empty prompt to trigger usage error, instead of silently
  including "--max" in the prompt text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- isValidTask: validate iteration is non-negative integer
- Slot-busy path preserves resumeIteration instead of resetting to 0
- Settings read from correct path (merged.model.loopEnabled etc.)
- Sanitize loopMaxConcurrent and loopExpiryDays with bounds checking
- Doc: clarify file lock scope (restore only, not persistence writes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Persist state after pause/resume (all 4 paths) to prevent stale file on crash
- Docs: settings use model.loopEnabled prefix (both user doc and design doc)
- Design doc: settings JSON example shows correct nested model section
- resumeIteration semantics and PID 999999 test addressed in replies

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Error message uses model.loopEnabled (fully qualified setting path)
- Doc: loopMaxConcurrent reference uses model. prefix
- Doc: drift protection wording corrected (fires on wake, not guaranteed <1s)
- isValidTask optional fields: safe defaults handle corruption (reply only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Export parseLoopArgs for testing. Cover:
- 6 subcommands (status/stop/pause/resume/restore/list)
- Target ID and --all flag parsing
- Leading interval (s/m/h/d)
- --id flag (with/without interval, missing value)
- --max flag (valid, missing, invalid, 0, negative)
- --id + --max combined
- Trailing "every" syntax (short units, full words, non-match)
- Leading interval precedence over trailing every
- Edge cases (whitespace, multi-space, subcommand-like prompt)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wenshao
Copy link
Copy Markdown
Collaborator Author

wenshao commented Apr 3, 2026

PR vs Claude Code 功能对比

功能矩阵

功能 PR Claude Code
多任务并发(最多 50)
命名循环 --id
迭代限制 --max N
/loop pause / resume
/loop status(含倒计时)
/loop stop(原子操作) ❌(需 CronList→CronDelete)
/loop list ❌(需 CronList 工具调用)
/loop restore ❌(自动恢复)
指数退避(2x→4x)
连续失败自动暂停(3 次)
Safety timer(3s)
漂移保护(动态收敛) ❌(固定 1s 轮询)
文件持久化 ✅ v2 + v1 迁移
多会话锁(PID+heartbeat)
Jitter 负载分散 ✅ 10%/30s cap ✅ 10%/15min cap
自动过期 ✅ 7 天(可配置,可禁用) ✅ 7 天(GrowthBook)
错过任务检测
Feature gate ✅ 4 项 settings ✅ GrowthBook+env
Trailing every 语法
亚分钟间隔(10s) ❌(cron 最小 1 分钟)
--all 批量操作
Tab 补全
i18n 国际化
单元测试 119 tests 0 tests

PR 独有(Claude Code 没有的 12 项)

  1. --id 命名循环 + --all 批量操作
  2. --max N 迭代限制
  3. pause / resume(手动 + 失败自动暂停)
  4. 指数退避(2x/4x cap)
  5. Safety timer(非 AI 命令防挂起)
  6. /loop status(含 next fire 倒计时、过期时间)
  7. /loop stop(原子操作,无需 job ID)
  8. 漂移保护(动态收敛 vs 固定 1s 轮询)
  9. 亚分钟间隔(10 秒 vs 1 分钟)
  10. Tab 补全(子命令 + loop ID)
  11. 119 个单元测试(5 个文件)
  12. 设计文档 + 用户文档

Claude Code 独有(PR 没有的 3 项)

  1. 自动恢复 — 启动时自动恢复,无需手动 /loop restore
  2. GrowthBook 远程调控 — 运维秒级生效的 jitter/expiry 参数调整
  3. One-shot 反向 jitter — 一次性任务提前触发(避免 :00/:30 峰值)

架构差异

PR Claude Code
调度引擎 setTimeout + 动态漂移保护 setInterval(1s) 轮询
用户接口 /loop 子命令(确定性解析) LLM prompt → CronCreate 工具
最小粒度 10 秒 1 分钟(cron 限制)
空闲 CPU 零(timer 挂起) 每秒遍历所有任务

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@wenshao
Copy link
Copy Markdown
Collaborator Author

wenshao commented Apr 3, 2026

image

All 4 clear-file call sites now go through persistLoopStates([]) which
awaits any in-flight write before deleting the file, preventing a race
where a concurrent write recreates loop-state.json after unlink.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@tanzhenxin
Copy link
Copy Markdown
Collaborator

Closing in favor of #2731

Hi @wenshao — thank you for the thorough work on this PR. The implementation is well-engineered, with solid persistence, backoff, and multi-session coordination. However, after reviewing both approaches, we're going with #2731 and closing this one. Here's why:

1. Tool-based interface vs. slash-command-only
The most significant divergence. The target architecture exposes scheduling to the LLM via declarative tools (cron_create, cron_list, cron_delete), meaning the model can autonomously schedule, inspect, and cancel jobs as part of agentic workflows — not just when the user explicitly types /loop. The /loop skill is intentionally just syntactic sugar over these tools. Your PR keeps scheduling entirely in slash-command space, which fundamentally limits the model's ability to use scheduling as a capability during multi-step reasoning.

2. Integration breadth
PR #2731 integrates at all three execution levels (interactive CLI, non-interactive CLI, ACP session), whereas this PR focuses primarily on the interactive CLI path with a LoopManager singleton that would need additional work to support the other modes. The tool-based design in #2731 makes this multi-surface integration natural, since tools are already available across all execution contexts.

3. Scope and surface area
This PR adds features not present in the target design — pause/resume/restore lifecycle, named loops with --id, exponential backoff, --max iteration caps. While individually useful, they increase the API surface and maintenance burden without matching what the system expects. The simpler model in #2731 (create/list/delete, auto-expiry, fire-and-forget) aligns with the principle of keeping the scheduling layer thin and letting the LLM handle higher-level logic.

4. Scheduling primitive: cron expressions vs. setTimeout
The upstream design uses 5-field cron expressions as the scheduling primitive, which naturally maps to standard scheduling semantics and is what the storage format, tool interfaces, and human-readable display are all built around. The setTimeout approach enables sub-minute granularity but introduces drift-protection complexity and diverges from the cron model the rest of the system assumes.

Again, the engineering quality here is genuinely strong — the drift protection, FNV-1a jitter, and backoff logic are well thought out. Some of those ideas (particularly the backoff and persistence format) may be worth revisiting as enhancements to the merged approach. Thank you for the contribution.

@wenshao wenshao closed this Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add /loop command for periodic task execution

4 participants