Skip to content

Scheduled prompts and command queue

This document explains how the extracted Copilot CLI bundle implements scheduled prompts and queued command dispatch. In the analyzed app.js, /every and /after are user-visible slash commands backed by an in-session ScheduleRegistry, while command.queued, command.execute, and command.completed are ephemeral client-dispatch events used to route slash commands to the correct interactive/protocol owner.

The key distinction is:

  • Scheduled prompts enqueue plain user messages on a timer.
  • Queued commands ask a UI/protocol client to execute or route slash-command text.

Because app.js is bundled/minified, symbol names are unstable. Line references below are searchable anchors in the extracted bundle and will shift across releases.

Source anchors

Semantic aliasMinified anchorApprox. app.js lineRole
Slash commands/every, /after, YBn(...)1303, 1340User-visible recurring and one-shot scheduled prompt commands.
Schedule validationInvalid interval, Minimum interval, Maximum interval, J7n=1e4, Z7n=864e54210Intervals are parsed and bounded between 10 seconds and 1 day.
Slash-command restrictiononly schedules plain messages — slash commands are not supported1303Scheduled entries cannot be slash commands.
Registry classScheduleRegistry, minified Sbt4210Stores entries, hydrates from events, schedules timers, and disposes on shutdown.
Create/cancel eventssession.schedule_created, session.schedule_cancelled4210, 4361Durable session events define schedule state.
Session accessgetScheduleRegistry()4471, 7344Registry is lazily created and reused by TUI dialogs/tools.
Tool API bridgeenableManageScheduleTool, scheduleApi4471, 4481Schedule management can be exposed to tools when enabled.
Command request queuecommand.queued, command.execute, command.completed4210, 4361, 4481, 6100Ephemeral client command routing and completion lifecycle.
Queue mutationpending_messages.modified4479Prompt queue changes notify the UI.
Cleanupsession.shutdown4210, 4361Schedule timers are disposed when the session shuts down.

User-visible scheduled prompts

The main user-facing commands are:

CommandDescriptionRuntime mode
/every <interval> <prompt>Schedule a recurring prompt for the current session.Re-arms after each tick.
/after <delay> <prompt>Schedule a one-shot prompt for the current session.Fires once, then cancels itself.

Both commands are marked experimental in the slash-command table, can run during agent execution, and do not stop queue processing.

Parsing and validation

The shared parser (YBn(...) in the minified bundle) expects:

/<every|after> <interval-or-delay> <prompt>

It performs these checks:

  1. Argument text must not be empty.
  2. The first non-space token is parsed as an interval/delay.
  3. The rest of the input becomes the prompt text.
  4. Prompt text must not be empty.
  5. Prompt text must not start with /.
  6. The parsed interval must be within bounds.

The error text makes the key design choice explicit:

/every only schedules plain messages — slash commands are not supported.

This prevents scheduled prompts from becoming a hidden automation channel for arbitrary slash commands such as /mcp, /plugin, or /reset-allowed-tools.

Interval limits

The interval parser accepts human-readable values such as 30s, 5m, 2h, and 1d. The ScheduleRegistry helper validates:

ConstantValueMeaning
J7n10,000 msMinimum interval: 10 seconds.
Z7n86,400,000 msMaximum interval: 1 day.

Invalid values produce guidance like “Try 30s, 5m, 2h, or 1d.”

ScheduleRegistry state

Each scheduled entry has this shape:

FieldMeaning
idSequential schedule ID within the session.
intervalMsDelay between ticks in milliseconds.
promptPlain prompt text to enqueue.
recurringtrue for /every, false for /after.
runtime-only timerActive timeout handle.
runtime-only cancelledPrevents future ticks.
runtime-only inFlightCleanupCleanup callback for any active queued work.

The public/listed entry returned by X7n(...) omits runtime-only timer fields.

Event-sourced hydration

The registry is event-sourced. On construction it calls hydrate() and replays prior session events:

EventHydration effect
session.schedule_createdAdd or update an entry in the in-memory schedule map.
session.schedule_cancelledRemove an entry from the map.

The registry also tracks the highest seen ID and sets nextId = maxId + 1. After replay, it schedules timers for all remaining entries.

This design makes scheduled prompts survive session replay/resume within the event log model without needing a separate schedules file.

Creating and cancelling schedules

sequenceDiagram
participant User
participant Slash as /every or /after
participant Registry as ScheduleRegistry
participant Session
User->>Slash: /every 5m check status
Slash->>Registry: add("5m", "check status", recurring=true)
Registry->>Session: emit session.schedule_created
Registry->>Registry: scheduleNextTick(entry)
Registry-->>Slash: entry
Slash-->>User: Scheduled #id every 5m -> prompt

Cancellation uses stop(id), which:

  1. finds the entry;
  2. cancels its timer and in-flight cleanup;
  3. deletes it from the entries map;
  4. emits session.schedule_cancelled;
  5. returns the public entry snapshot.

cancelAll() is called by dispose().

Timer execution

At each tick, the registry enqueues the scheduled prompt into the session as a normal prompt/message. For recurring entries, it schedules the next tick unless the entry was cancelled. For one-shot entries, it cancels/removes the entry after firing.

Because scheduled prompts become ordinary queued user prompts, they inherit the same model/tool/permission behavior as manually submitted prompts.

Shutdown behavior

The registry subscribes to session.shutdown in its constructor. On shutdown it calls dispose(), which:

  • marks the registry disposed;
  • cancels all timers;
  • clears all entries;
  • unsubscribes from the shutdown listener.

If code attempts to add an entry after disposal, it throws ScheduleRegistry has been disposed.

Manage-schedule tool bridge

The session has an enableManageScheduleTool option. When set, the tool initialization context includes:

scheduleApi: this.getScheduleRegistry()

This allows a tool or UI component to list/cancel/manage scheduled prompts through the registry, without duplicating schedule state.

The TUI also has a schedule manager dialog path that receives registry: session.getScheduleRegistry().

Command queue events

The command queue is related but separate from scheduled prompts. It is used when slash commands need to be executed by a UI/protocol client or routed to a command owner.

EventPersistenceMeaning
command.queuedEphemeralA queued slash command text should be handled by a client.
command.executeEphemeralA registered command should be dispatched to its owning connection.
command.completedEphemeralThe pending command request has been resolved; clients can dismiss UI.

The session pending-request manager stores request resolvers keyed by request ID and emits these events.

Queued command lifecycle

sequenceDiagram
participant Session
participant Pending as PendingRequests
participant Client as TUI / ACP / extension connection
Session->>Pending: requestQueuedCommand(command)
Pending-->>Client: command.queued(requestId, command)
Client->>Client: handle slash command
Client->>Pending: respondToQueuedCommand(requestId, result)
Pending-->>Client: command.completed(requestId)
Pending-->>Session: result

The command.execute path is similar but includes commandName and args, and the embedded server can route the event to the connection that owns a registered command.

Interaction with the prompt queue

The session prompt queue emits pending_messages.modified whenever queued messages change. Scheduled prompts and manually submitted prompts both flow through this queue. Commands are handled specially:

  • if a queued item is kind: "command", the session checks whether there are command.queued listeners;
  • if a client handles the command and requests queue processing to stop, the session clears remaining queued items;
  • otherwise command execution errors are logged and queue processing continues.

This makes commands client-mediated while keeping ordinary scheduled messages inside the agent queue.

Why scheduled slash commands are blocked

Blocking scheduled slash commands avoids several edge cases:

  • recurring /mcp or /plugin management changes;
  • recurring permission resets;
  • recursive /every creation;
  • background command execution without clear user intent;
  • ambiguous command ownership in ACP/embedded-server sessions.

By scheduling only plain prompts, the feature acts like a timed user reminder/request rather than a general cron system.

Relationship to other docs

  • tui-and-slash-commands.md explains general slash-command registration and TUI dialogs.
  • session-manager-and-event-replay.md explains event replay and session persistence.
  • built-in-tools-execution-events.md explains tool events that scheduled prompts may later trigger.
  • autopilot-and-no-ask-user.md explains autonomous continuation modes that scheduled prompts can interact with.

Created and maintained by Yingting Huang.