Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aa49391
feat(cron): add in-session loop scheduling with cron tools
tanzhenxin Mar 28, 2026
c4ae7bf
test(cli): add cron config mocks to test fixtures
tanzhenxin Mar 29, 2026
99e5a9f
test(integration): add cron tools integration tests
tanzhenxin Mar 29, 2026
439a1a4
feat(cron): make cron tools opt-in via experimental settings
tanzhenxin Mar 29, 2026
57cf2b0
refactor(cron): rename cron_expression param to cron and enhance docu…
tanzhenxin Mar 29, 2026
023638f
docs(loop): improve /loop skill documentation with clearer parsing ru…
tanzhenxin Mar 29, 2026
f063e7c
refactor(cron): simplify and improve cron tool output messages
tanzhenxin Mar 29, 2026
a3623fd
feat(cron): add interactive E2E tests and fix cron trigger reactivity
tanzhenxin Mar 29, 2026
69fff43
feat(loop): add subcommands and improve cron display output
tanzhenxin Mar 29, 2026
42e7e54
fix(cron): improve jitter strategy and fix vixie-cron day matching se…
tanzhenxin Mar 29, 2026
bc674ca
docs(scheduled-tasks): add documentation for /loop and cron schedulin…
tanzhenxin Mar 29, 2026
707b06c
fix(cron): replace "Claude" with "Qwen Code" in tool messages
tanzhenxin Mar 29, 2026
ded8961
refactor(tests): reorganize integration tests by execution mode
tanzhenxin Mar 29, 2026
314a9de
refactor(tests): replace Playwright with @xterm/headless in cron tests
tanzhenxin Mar 29, 2026
aa454a5
feat(cron): add distinct Cron message type and exit summary
tanzhenxin Mar 30, 2026
9dd22eb
feat(cron): integrate cron scheduling into ACP session lifecycle
tanzhenxin Mar 30, 2026
9a8829c
merge: resolve conflict in client.ts hook condition
tanzhenxin Mar 30, 2026
1af0ef9
Merge remote-tracking branch 'origin/main' into feat/in-session-cron-…
tanzhenxin Mar 30, 2026
dbc86e7
test(session): add isCronEnabled mock to test config
tanzhenxin Mar 30, 2026
05f3854
fix(cron): prevent concurrent cron execution and queue properly
tanzhenxin Mar 30, 2026
1e44fc8
fix(cron): correct jitter handling for delayed and advanced fire times
tanzhenxin Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ jobs:
run: |
npm run preflight
npm run test:integration:cli:sandbox:none
npm run test:integration:interactive:sandbox:none
npm run test:integration:cli:sandbox:docker
npm run test:integration:interactive:sandbox:docker
env:
OPENAI_API_KEY: '${{ secrets.OPENAI_API_KEY }}'
OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}'
Expand Down
1 change: 1 addition & 0 deletions docs/users/features/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export default {
sandbox: 'Sandboxing',
language: 'i18n',
hooks: 'Hooks',
'scheduled-tasks': 'Scheduled Tasks',
};
139 changes: 139 additions & 0 deletions docs/users/features/scheduled-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Run Prompts on a Schedule

> Use `/loop` and the cron scheduling tools to run prompts repeatedly, poll for status, or set one-time reminders within a Qwen Code session.

Scheduled tasks let Qwen Code re-run a prompt automatically on an interval. Use them to poll a deployment, babysit a PR, check back on a long-running build, or remind yourself to do something later in the session.

Tasks are session-scoped: they live in the current Qwen Code process and are gone when you exit. Nothing is written to disk.

> **Note:** Scheduled tasks are an experimental feature. Enable them with `experimental.cron: true` in your [settings](../configuration/settings.md), or set `QWEN_CODE_ENABLE_CRON=1` in your environment.

## Schedule a recurring prompt with /loop

The `/loop` [bundled skill](skills.md) is the quickest way to schedule a recurring prompt. Pass an optional interval and a prompt, and Qwen Code sets up a cron job that fires in the background while the session stays open.

```text
/loop 5m check if the deployment finished and tell me what happened
```

Qwen Code parses the interval, converts it to a cron expression, schedules the job, and confirms the cadence and job ID. It then immediately executes the prompt once — you don't have to wait for the first cron fire.

### Interval syntax

Intervals are optional. You can lead with them, trail with them, or leave them out entirely.

| Form | Example | Parsed interval |
| :---------------------- | :------------------------------------ | :--------------------------- |
| Leading token | `/loop 30m check the build` | every 30 minutes |
| Trailing `every` clause | `/loop check the build every 2 hours` | every 2 hours |
| No interval | `/loop check the build` | defaults to every 10 minutes |

Supported units are `s` for seconds, `m` for minutes, `h` for hours, and `d` for days. Seconds are rounded up to the nearest minute since cron has one-minute granularity. Intervals that don't divide evenly into their unit, such as `7m` or `90m`, are rounded to the nearest clean interval and Qwen Code tells you what it picked.

### Loop over another command

The scheduled prompt can itself be a command or skill invocation. This is useful for re-running a workflow you've already packaged.

```text
/loop 20m /review-pr 1234
```

Each time the job fires, Qwen Code runs `/review-pr 1234` as if you had typed it.

### Manage loops

`/loop` also supports two subcommands for managing existing jobs:

```text
/loop list
```

Lists all scheduled jobs with their IDs and cron expressions.

```text
/loop clear
```

Cancels all scheduled jobs at once.

## Set a one-time reminder

For one-shot reminders, describe what you want in natural language instead of using `/loop`. Qwen Code schedules a single-fire task that deletes itself after running.

```text
remind me at 3pm to push the release branch
```

```text
in 45 minutes, check whether the integration tests passed
```

Qwen Code pins the fire time to a specific minute and hour using a cron expression and confirms when it will fire.

## Manage scheduled tasks

Ask Qwen Code in natural language to list or cancel tasks, or reference the underlying tools directly.

```text
what scheduled tasks do I have?
```

```text
cancel the deploy check job
```

Under the hood, Qwen Code uses these tools:

| Tool | Purpose |
| :----------- | :-------------------------------------------------------------------------------------------------------------- |
| `CronCreate` | Schedule a new task. Accepts a 5-field cron expression, the prompt to run, and whether it recurs or fires once. |
| `CronList` | List all scheduled tasks with their IDs, schedules, and prompts. |
| `CronDelete` | Cancel a task by ID. |

Each scheduled task has an 8-character ID you can pass to `CronDelete`. A session can hold up to 50 scheduled tasks at once.

## How scheduled tasks run

The scheduler checks every second for due tasks and enqueues them when the session is idle. A scheduled prompt fires between your turns, not while Qwen Code is mid-response. If Qwen Code is busy when a task comes due, the prompt waits until the current turn ends.

All times are interpreted in your local timezone. A cron expression like `0 9 * * *` means 9am wherever you're running Qwen Code, not UTC.

### Jitter

To avoid every session hitting the API at the same wall-clock moment, the scheduler adds a small deterministic offset to fire times:

- **Recurring tasks** fire up to 10% of their period late, capped at 15 minutes. An hourly job might fire anywhere from `:00` to `:06`.
- **One-shot tasks** scheduled for the top or bottom of the hour (minute `:00` or `:30`) fire up to 90 seconds early.

The offset is derived from the task ID, so the same task always gets the same offset. If exact timing matters, pick a minute that is not `:00` or `:30`, for example `3 9 * * *` instead of `0 9 * * *`, and the one-shot jitter will not apply.

### Three-day expiry

Recurring tasks automatically expire 3 days after creation. The task fires one final time, then deletes itself. This bounds how long a forgotten loop can run. If you need a recurring task to last longer, cancel and recreate it before it expires.

One-shot tasks do not expire on a timer — they simply delete themselves after firing once.

## Cron expression reference

`CronCreate` accepts standard 5-field cron expressions: `minute hour day-of-month month day-of-week`. All fields support wildcards (`*`), single values (`5`), steps (`*/15`), ranges (`1-5`), and comma-separated lists (`1,15,30`).

| Example | Meaning |
| :------------- | :--------------------------- |
| `*/5 * * * *` | Every 5 minutes |
| `0 * * * *` | Every hour on the hour |
| `7 * * * *` | Every hour at 7 minutes past |
| `0 9 * * *` | Every day at 9am local |
| `0 9 * * 1-5` | Weekdays at 9am local |
| `30 14 15 3 *` | March 15 at 2:30pm local |

Day-of-week uses `0` or `7` for Sunday through `6` for Saturday. When both day-of-month and day-of-week are constrained (neither is `*`), a date matches if either field matches — this follows standard vixie-cron semantics.

Extended syntax like `L`, `W`, `?`, and name aliases such as `MON` or `JAN` is not supported.

## Limitations

Session-scoped scheduling has inherent constraints:

- Tasks only fire while Qwen Code is running and idle. Closing the terminal or letting the session exit cancels everything.
- No catch-up for missed fires. If a task's scheduled time passes while Qwen Code is busy on a long-running request, it fires once when Qwen Code becomes idle, not once per missed interval.
- No persistence across restarts. Restarting Qwen Code clears all session-scoped tasks.
Loading
Loading