Skip to content

feat(schedule): add HTTP reactive polling for scheduled tasks #304

Description

@max-cyq

Problem / Motivation

Related to #274, specifically Reactive Polling — Phase 1.

Open Cowork's current scheduler is time-driven and unconditional:

timer fires → executeTask() → startSession(prompt, cwd)

Every scheduled trigger starts a new Agent session, even when the external state being monitored has not changed. This makes recurring monitoring unnecessarily expensive and prevents scheduled tasks from acting as lightweight watchers.

We need to separate condition checking from Agent execution:

timer fires → check condition → persist observed state
            → changed: start Agent session
            → unchanged: reschedule without starting a session

The implementation should extend the existing scheduler rather than introduce a parallel monitoring system.

Proposed Solution

Add an HTTP-only WatchTask capability on top of the existing scheduled-task infrastructure.

Scope

Reuse the existing:

  • ScheduledTaskManager timer engine
  • scheduled_tasks SQLite table
  • Schedule IPC CRUD APIs
  • executeTask() → sessionManager.startSession() execution path

A scheduled task becomes a WatchTask when it has a valid watchConfig. Tasks without watchConfig must retain their existing behavior.

HTTP checker

Support the following comparison modes:

  • status: compare the HTTP response status
  • bodyHash: compare a hash of the HTTP response body

Suggested configuration shape:

type WatchConfig = {
  checkType: 'http';
  compareMode: 'status' | 'bodyHash';
  checkConfig: {
    url: string;
    method?: 'GET' | 'HEAD';
    timeoutMs?: number;
  };
};

HEAD + bodyHash must be rejected because a HEAD response does not provide a meaningful response body.

Persisted state

Persist the following state for each WatchTask:

  • watchConfig
  • lastState
  • lastCheckedAt
  • consecutiveUnchanged

The SQLite migration must remain backward-compatible with existing databases and records.

Execution semantics

The scheduler pipeline becomes:

timer
  → checkCondition()
  → persist check state
  → changed?
      ├─ yes → executeTask() → startSession()
      └─ no  → skip Agent session
  → reschedule

Behavior requirements:

  1. The first successful check establishes the baseline and must not start an Agent session.
  2. An unchanged state increments consecutiveUnchanged and skips Agent execution.
  3. A changed state starts the existing Agent execution path.
  4. A checker failure records lastError, does not disable the task, and allows future checks to continue.
  5. runNow() on a WatchTask runs the same check → persist → act pipeline.
  6. WatchTasks must use a repeating interval, daily, or weekly schedule.
  7. The minimum HTTP check interval is 60 seconds.
  8. Existing non-watch scheduled tasks must behave exactly as before.

HTTP safety boundaries

The checker must:

  • Allow only http: and https: URLs
  • Enforce a request timeout
  • Limit response body size
  • Limit redirects and revalidate the protocol after each redirect
  • Never persist the complete response body
  • Handle malformed persisted watchConfig without crashing the scheduler

Custom request headers are not required for this phase.

Out of Scope

This issue intentionally does not include:

  • Command checker or host shell execution
  • File checker
  • Agent-based checker
  • JSONPath or regex comparison
  • Multiple-condition composition
  • Notifications
  • WatchTask UI forms

The original #274 Phase 1 proposal included a command checker. That capability should be handled separately as Phase 1.1 because periodic command execution introduces a new security-sensitive path that may bypass the existing Agent permission flow.

A future command checker should use SandboxAdapter.executeCommand() and define isolation, timeout, output-size, and user-confirmation requirements before implementation.

Acceptance Criteria

  • Existing scheduled tasks retain their current behavior
  • Existing databases migrate without data loss
  • Old scheduled-task rows without watch fields remain readable
  • The first HTTP check persists a baseline without starting an Agent session
  • An unchanged HTTP status does not start an Agent session
  • A changed HTTP status starts an Agent session
  • An unchanged body hash does not start an Agent session
  • A changed body hash starts an Agent session
  • lastState, lastCheckedAt, and consecutiveUnchanged are persisted correctly
  • Checker errors update lastError without disabling future checks
  • Malformed persisted watch configuration does not crash the scheduler
  • One-time WatchTasks are rejected
  • HTTP WatchTasks enforce a minimum 60-second interval
  • runNow() follows the WatchTask check pipeline
  • HTTP timeout, body-size, protocol, and redirect limits are tested
  • npm run lint passes
  • npx tsc --noEmit passes
  • npx vitest run passes
  • npm run build passes

Alternatives Considered

Build a separate polling service

Rejected because the existing scheduler already provides timers, persistence, IPC CRUD, and Agent execution. A parallel system would duplicate infrastructure and complicate lifecycle management.

Include command checking in this phase

Deferred because directly executing commands from the scheduler could bypass SessionManager, Agent tool permissions, and user confirmation. This requires a separately reviewed sandbox and permission design.

Start an Agent session for every check

Rejected because the purpose of reactive polling is to keep condition checks lightweight and invoke an Agent only when external state changes.

Additional Context

Parent roadmap: #274

Platform: macOS and Windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions