Repository navigation
feat(daemon): Support current-session scheduled tasks #9838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
doudouOUC
merged 10 commits into
QwenLM:main
from
doudouOUC:docs/scheduled-task-current-session-entrypoints
Aug 26, 2026
Merged
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1075c65
docs(scheduled-tasks): design current-session creation entrypoints
doudouOUC 7c8a4b3
docs: clarify scheduled task session semantics
doudouOUC 4693c7a
feat(daemon): Support current-session scheduled tasks
doudouOUC 1180fcd
codex: fix CI failure on PR #9838
doudouOUC 98d15a5
codex: fix CI failure on PR #9838
doudouOUC 8291f12
codex: fix CI failure on PR #9838
doudouOUC 2391398
codex: address PR review feedback (#9838)
doudouOUC 8ca008d
codex: fix CI failure on PR #9838
doudouOUC 16a5786
codex: address PR review feedback (#9838)
doudouOUC cf63c6e
codex: address PR review feedback (#9838)
doudouOUC File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next
Next commit
docs(scheduled-tasks): design current-session creation entrypoints
Co-authored-by: Qwen-Coder <[email protected]>
- Loading branch information
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
284 changes: 284 additions & 0 deletions
284
docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,284 @@ | ||
| # Current-session entrypoints for daemon scheduled tasks | ||
|
|
||
| Status: Draft | ||
|
|
||
| Related: #8906, #9361, #9415 | ||
|
|
||
| ## Summary | ||
|
|
||
| PR #9361 added the daemon primitive this feature needs: scheduled-task create | ||
| requests may reuse an existing session by sending `sessionId`. The daemon | ||
| validates the live session, records it as caller-owned, keeps it resident, and | ||
| restores it after restart. A task bound this way continues to run in that | ||
| session even when the Web Shell selects a different conversation. | ||
|
|
||
| Two user entrypoints still cannot request that behavior. The Scheduled Tasks | ||
| form never sends the current session id, and `cron_create` has no current-session | ||
| mode. This design adds those entrypoints without changing the scheduler, | ||
| persisted ownership model, or the default dedicated-session behavior. | ||
|
|
||
| ## Existing baseline | ||
|
|
||
| The merged #9361 contract is the source of truth: | ||
|
|
||
| - Omitting or passing `null` for `sessionId` creates a dedicated task-owned | ||
| session. | ||
| - Passing `sessionId` reuses a live, idle session in the selected workspace and | ||
| persists `sessionOwnedByTask: false`. | ||
| - A caller-owned session is not renamed or closed when its task is renamed or | ||
| deleted. | ||
| - Archiving the bound session disables the task, unarchiving resumes it, and | ||
| deleting the session removes the task. | ||
| - Enabled bound sessions are kept resident and rehydrated after daemon restart. | ||
| - A session may be bound to at most one scheduled task. | ||
|
|
||
| The scheduler already maps `task.sessionId` to `boundSessionId` and fires the | ||
| task only from the matching session. Session execution already serializes cron | ||
| turns behind active user turns. | ||
|
|
||
| ## Goals | ||
|
|
||
| - Let the Scheduled Tasks form bind a new task to the currently selected | ||
| ordinary conversation. | ||
| - Let a user explicitly request current-session binding through `cron_create`. | ||
| - Preserve dedicated sessions as the default for every existing caller. | ||
| - Preserve the #9361 ownership, workspace, capacity, lifecycle, and unique | ||
| binding checks. | ||
| - Fail clearly when the host cannot guarantee daemon-managed restoration. | ||
|
|
||
| ## Non-goals | ||
|
|
||
| - Rebinding an existing task through PATCH. | ||
| - Binding more than one task to a session. | ||
| - Migrating task history between sessions. | ||
| - Supporting Channel, side-task, Live, standalone, archived, or non-live | ||
| sessions. | ||
| - Changing the Scheduled Tasks page's existing "Create via chat" action, which | ||
| intentionally starts a fresh conversation. | ||
| - Solving the remaining legacy teardown-versus-reuse race tracked by #9415. | ||
| - Changing token-limit or missed-fire policy. | ||
|
|
||
| ## Public behavior | ||
|
|
||
| ### Scheduled Tasks form | ||
|
|
||
| The create form gains a two-option session selector: | ||
|
|
||
| - **Dedicated task conversation** — default; omit `sessionId`, preserving the | ||
| current behavior. | ||
| - **Current conversation** — send the active session id in the existing | ||
| `DaemonCreateScheduledTaskRequest.sessionId` field. | ||
|
|
||
| The current-conversation option is shown only when the daemon advertises a new | ||
| `scheduled_task_session_reuse` capability. It is disabled with a reason when: | ||
|
|
||
| - there is no active session; | ||
|
doudouOUC marked this conversation as resolved.
Outdated
|
||
| - the active session still has a running turn or pending interaction; | ||
| - the active session is not an eligible top-level ordinary conversation; | ||
| - the form's selected workspace differs from the active session's workspace; | ||
| or | ||
| - the loaded task list already contains a task with that session id. | ||
|
|
||
| These checks are advisory. The daemon remains authoritative and the form | ||
| surfaces its existing `session_busy`, `session_already_bound`, | ||
| `session_workspace_mismatch`, `session_not_live`, and related errors. | ||
|
|
||
| Binding is selectable only during creation. Edit mode does not display or send | ||
| `sessionId`. Task cards keep the existing generic "View conversation" action, | ||
| which is correct for both dedicated and caller-owned sessions. | ||
|
|
||
| ### `cron_create` | ||
|
|
||
| `CronCreateParams` gains: | ||
|
|
||
| ```ts | ||
| sessionMode?: 'dedicated' | 'current'; | ||
| ``` | ||
|
|
||
| The default is `dedicated`. `sessionMode: 'current'` is valid only with | ||
| `durable: true`, and the tool description instructs the model to use it only | ||
| when the user explicitly asks to keep scheduled work in the current | ||
| conversation. The permission-classifier projection includes `sessionMode`. | ||
|
|
||
| Outside a daemon-managed ACP session, current mode returns a clear | ||
| `current_session_scheduling_unavailable` error. Dedicated durable and | ||
| session-only jobs retain their existing paths. | ||
|
doudouOUC marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Architecture | ||
|
|
||
| ### Why the REST path cannot be called directly from `cron_create` | ||
|
|
||
| The public #9361 endpoint requires a supplied session to be idle. A | ||
| `cron_create` tool call runs inside an active prompt, so its own session is | ||
| necessarily busy and a direct REST-equivalent call would return | ||
| `session_busy`. | ||
|
|
||
| The busy rule must remain unchanged for ordinary clients: an arbitrary caller | ||
| must not bind a session while a different turn is mutating it. Current-mode | ||
| tool creation therefore uses a daemon-only control path that can attest that | ||
| the active turn is the caller requesting the binding. | ||
|
doudouOUC marked this conversation as resolved.
Outdated
|
||
|
|
||
| ### Daemon control path | ||
|
|
||
| Core Config receives an optional `CurrentSessionScheduledTaskCreator` | ||
| capability, following the existing injected daemon-capability pattern. The ACP | ||
| Session implementation wires it to a new control request: | ||
|
|
||
| ```text | ||
| qwen/control/scheduled-task/create-current | ||
| ``` | ||
|
|
||
| The child request carries `cron`, `prompt`, `recurring`, and its own | ||
| `callerSessionId`. It does not accept a separate target session id. | ||
|
|
||
| The bridge handler: | ||
|
|
||
| 1. validates payload types and the same prompt bounds as the REST route; | ||
| 2. verifies that the bridge client owns `callerSessionId`; | ||
| 3. resolves that live session in the bridge that received the request; | ||
| 4. requires a top-level session and rejects `channel`, `side_task`, | ||
| `scheduled_task`, `standalone`, and the reserved Live source marker; | ||
| unreserved top-level source types remain compatible with the existing API; | ||
| and | ||
| 5. delegates to a host callback installed only by `qwen serve` runtimes that | ||
| manage scheduled-task sessions. | ||
|
|
||
| No host callback returns method-not-found, which the tool maps to | ||
| `current_session_scheduling_unavailable`. | ||
|
|
||
| ### Shared daemon creation command | ||
|
|
||
| The host callback and the REST route share a focused | ||
| `createScheduledTaskWithExistingSession` command extracted from the #9361 | ||
| provided-session branch. The command accepts an internal binding context: | ||
|
|
||
| ```ts | ||
| type ExistingSessionBindingContext = | ||
| | { source: 'rest'; allowActiveCaller: false } | ||
| | { | ||
| source: 'cron-tool'; | ||
| allowActiveCaller: true; | ||
| callerSessionId: string; | ||
| }; | ||
| ``` | ||
|
|
||
| Both paths apply the same session-id normalization, selected-runtime and | ||
| workspace ownership, archive state, scheduled-task-source, capacity, | ||
| generation, and unique-binding checks. Only the authenticated cron-tool path | ||
| may skip the idle rejection, and only when the resolved session id equals its | ||
| caller session id. | ||
|
|
||
| The final write-lock check remains authoritative. It revalidates that the | ||
| session is live and not task-reserved, rejects a concurrent binding, and writes | ||
| the task with the existing fields: | ||
|
|
||
| ```ts | ||
| { | ||
| sessionId: callerSessionId, | ||
| sessionOwnedByTask: false, | ||
| } | ||
| ``` | ||
|
|
||
| No new durable schema or migration is introduced. The task creation timestamp | ||
| and `lastFiredAt` use the same creation-minute anchor as the REST route, so the | ||
| task cannot fire from the turn that is still creating it. | ||
|
|
||
| After the host commits the task, the control response returns its id and cron | ||
| expression. The creating session's file watcher loads the bound task; a | ||
| subsequent `cron_list` remains immediately consistent because durable listing | ||
| is file-first. | ||
|
|
||
| ### Execution and session switching | ||
|
|
||
| There is no scheduler change. Once the task is on disk, only the scheduler whose | ||
| session id equals the task's `boundSessionId` may fire it. If a user turn is | ||
| active, the cron prompt waits in that session's existing serial queue. | ||
|
|
||
| Selecting another Web Shell conversation detaches the previous UI client but | ||
| does not close the session. Keepalive continues to heartbeat the bound session, | ||
| and boot rehydration restores it after daemon restart. Restore failures keep the | ||
| task bound and retry through the existing policy; they never move work into a | ||
| different conversation. | ||
|
|
||
| ## Compatibility and rollout | ||
|
|
||
| - `sessionMode` is optional and defaults to the existing behavior. | ||
| - Existing REST and SDK callers do not change. | ||
| - Existing task files require no rewrite. | ||
| - The daemon advertises `scheduled_task_session_reuse` only when | ||
| `manageScheduledTaskSessions` is enabled, so minimal embeds do not promise a | ||
| lifecycle they cannot keep alive. | ||
|
doudouOUC marked this conversation as resolved.
Outdated
|
||
| - Web clients without `scheduled_task_session_reuse` do not render the new | ||
| selector, preventing an older daemon from silently ignoring the intent. | ||
| - Non-daemon tool callers receive an explicit error rather than creating a | ||
| durable task whose bound session cannot be restored. | ||
| - The feature can ship in one implementation PR because capability advertising, | ||
| UI use, and daemon control support are versioned together. | ||
|
|
||
| ## Test plan | ||
|
|
||
| ### Core tool | ||
|
|
||
| - Omitted mode preserves session-only and dedicated durable creation. | ||
| - Current mode requires `durable: true` and an injected host capability. | ||
| - Current mode forwards the exact schedule and returns the committed task id. | ||
| - The permission-classifier input includes `sessionMode`. | ||
| - Host failure and method-not-found are surfaced without creating an unbound | ||
| fallback task. | ||
|
|
||
| ### Bridge and daemon | ||
|
|
||
| - The control method rejects malformed payloads, an unknown caller, and a | ||
| caller session not owned by the bridge client. | ||
| - The authenticated active caller succeeds despite `hasActivePrompt: true`. | ||
| - REST creation with the same busy session still returns `session_busy`. | ||
| - Workspace mismatch, archived/non-live sessions, task-created sessions, | ||
| capacity, generation closure, and an existing binding preserve #9361 errors. | ||
| - A concurrent REST/tool create commits exactly one task. | ||
| - The committed task is caller-owned; task rename and deletion do not rename or | ||
| close the conversation. | ||
|
|
||
| ### Web Shell | ||
|
|
||
| - Dedicated mode is the default and omits `sessionId`. | ||
| - Current mode sends the active session id. | ||
| - Capability absence, no active session, an active turn, an ineligible session | ||
| source, workspace mismatch, and an existing binding disable the option with | ||
| the expected explanation. | ||
| - Edit requests never mutate binding. | ||
| - "Create via chat" continues to start a fresh conversation. | ||
|
|
||
| ### End to end | ||
|
|
||
| 1. In conversation A, create a durable current-session task through | ||
| `cron_create`; confirm creation succeeds while the tool turn is active. | ||
| 2. Switch the Web Shell to conversation B and confirm the scheduled turn appears | ||
| in A, not B. | ||
| 3. Restart the daemon without opening A and confirm A is rehydrated and the next | ||
| fire still appears there. | ||
| 4. Delete the task and confirm A remains open and usable. | ||
| 5. Repeat creation through the Scheduled Tasks form while A is idle and confirm | ||
| it uses the same session without minting a new one. | ||
|
|
||
| ## Alternatives rejected | ||
|
|
||
| ### Relax `session_busy` for the public endpoint | ||
|
|
||
| This cannot prove that the active turn belongs to the caller requesting the | ||
| binding and weakens #9361 for every API client. | ||
|
|
||
| ### Write the task file directly from `cron_create` | ||
|
|
||
| This bypasses daemon runtime ownership, capacity and generation checks, and | ||
| cannot safely promise keepalive outside `qwen serve`. | ||
|
|
||
| ### Defer creation until the tool turn ends | ||
|
|
||
| The tool would have to report success before persistence, or keep a | ||
| process-local deferred operation whose failure cannot be returned to the user. | ||
| The authenticated control path commits before the tool returns. | ||
|
|
||
| ### Create a dedicated session and later migrate it | ||
|
|
||
| Migration splits transcript history and adds rollback and ownership transitions | ||
| that are unnecessary now that #9361 can bind the intended session directly. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.