Files
deepseek-harness/docs/subsystems/schedule.md
T

7.8 KiB

Session-local Schedule

English | 中文

Schedule owns durable reminders that return to the original live Session as ordinary later conversation turns. The durable Schedule Agent Note owns the persistence and lifecycle decisions, conversational delivery owns the no-receipt boundary, and the explicit time-zone boundary owns browser-local interpretation. This page records the durable and model-facing shapes from packages/schedule/tool-schedule/src/types.ts; the package README owns composition, tool behavior, and the exact reminder framing.

Durable records

ScheduleId is a branded id, unique and never reused within one Session. Version 1 supports either a positive safe-integer after_seconds delay or an explicit absolute at target. Creation canonicalizes either selector into a four-digit-year RFC 3339 UTC scheduledAt; an after record retains its submitted delay, while an at record stores only the resulting instant.

/** Durable one-shot reminder created from a positive delay. */
interface AfterScheduleRecord {
  /** Session-local stable identity. */
  readonly id: ScheduleId
  /** Rule discriminator for a delayed one-shot reminder. */
  readonly kind: 'after'
  /** Trimmed reminder content supplied at creation. */
  readonly prompt: string
  /** Positive safe-integer delay accepted at creation. */
  readonly afterSeconds: number
  /** Four-digit-year RFC 3339 UTC target. */
  readonly scheduledAt: string
}
/** Durable one-shot reminder created from an absolute instant. */
interface AtScheduleRecord {
  /** Session-local stable identity. */
  readonly id: ScheduleId
  /** Rule discriminator for an absolute one-shot reminder. */
  readonly kind: 'at'
  /** Trimmed reminder content supplied at creation. */
  readonly prompt: string
  /** Four-digit-year RFC 3339 UTC target. */
  readonly scheduledAt: string
}
/** The v1 durable reminder record union. */
type ScheduleRecord = AfterScheduleRecord | AtScheduleRecord

Absolute-time input

The at selector is either a strict offset-bearing RFC 3339 string or an exact local-calendar object. The local form keeps its interpretation explicit at the tool boundary:

/** Structured local-calendar input accepted by `schedule_create`. */
interface LocalAtInput {
  /** Four-digit ISO calendar date. */
  readonly date: string
  /** Local wall-clock time with optional one-to-three digit milliseconds. */
  readonly time: string
  /** Explicit UTC or IANA Area/Location zone. */
  readonly time_zone: string
}
/** Absolute selector accepted by `schedule_create`. */
type AtInput = string | LocalAtInput

The official Web overlay samples the browser's IANA zone for every prompt. Time-context tells the model to interpret otherwise-unqualified natural-language dates and times in that request-local zone when the open turn has one unambiguous browser zone; mixed or missing provenance tells the model to ask. That guidance is not a durable Session default: the model must still pass an offset in the string form or time_zone in the local form, and Schedule never reads browser, Session, process, or model context.

Schedule rejects invalid offsets and zones, offset-free strings, non-future targets, and local times inside daylight-saving gaps. A daylight-saving overlap chooses its first, earlier instant. Successful creation stores only canonical UTC scheduledAt, so replay never depends on ambient time-zone state.

Durable changes and replay

The version-1 schedule/change Session event is the only durable Schedule authority. Create stores the complete record. Delete and dispatch are terminal id-only transitions for one-shot reminders; dispatch means the follow-up was synchronously queued, not that a model answer succeeded or the user read it.

/** Creates one durable reminder record. */
interface ScheduleCreateChange {
  readonly version: 1
  readonly operation: 'create'
  readonly schedule: ScheduleRecord
}
/** Deletes one currently active reminder. */
interface ScheduleDeleteChange {
  readonly version: 1
  readonly operation: 'delete'
  readonly id: ScheduleId
}
/** Records that one active one-shot reminder entered the durable dispatch history. */
interface ScheduleDispatchChange {
  readonly version: 1
  readonly operation: 'dispatch'
  readonly id: ScheduleId
}
/** Strict version-1 durable Schedule mutation union. */
type ScheduleChange = ScheduleCreateChange | ScheduleDeleteChange | ScheduleDispatchChange

The strict decoder and fold reject unknown versions, extra fields, reused ids, and delete or dispatch transitions against inactive records. A normal Session folds its complete event stream. A fork folds only events at or after SessionHeader.seedLength, so it retains history without adopting the parent Session's active reminders. The schedule/change declaration and source location are also indexed in the persistence catalog.

Active views and management

Tool values combine the durable record with delivery state derived from the current wall clock. session-local means the original Session must be live: no external notification channel or cold-session scheduler exists.

/** Current delivery timing derived from the durable record and wall clock. */
type ScheduleState = 'scheduled' | 'overdue'
/** Fixed v1 delivery boundary: the original session must be live. */
type ScheduleDeliveryMode = 'session-local'
/** Complete model-facing view of one active reminder. */
type ScheduleView = ScheduleRecord & {
  /** Whether the target remains in the future. */
  readonly state: ScheduleState
  /** Reminder delivery never leaves the owning session. */
  readonly deliveryMode: ScheduleDeliveryMode
}

The generated tool catalog owns the argument and result schemas for schedule_create, schedule_list, and schedule_delete. Management calls serialize with due work in one Agent-scoped queue. Every read or decision first waits for the shared Session persistence barrier; create and an actual delete wait again after appending. A barrier failure reports persistence_uncertain instead of guessing whether an eager write committed. The other stable error codes are invalid_prompt, invalid_selector, invalid_rule, invalid_time_zone, not_future, time_out_of_range, corrupt_schedule_log, and internal_error.

Live delivery

The process-local owner derives its earliest timer from the durable fold and rereads the wall clock after every bounded wait. Cold Sessions do no work; reopening one reconstructs timers and makes a past target overdue. An overdue reminder waits for the Agent to become fully idle and claims the maintenance phase before it refolds state, queues followup(), and appends dispatch. It never calls steer() and never interrupts a current turn.

The admitted follow-up starts one normal later turn and appears only through the ordinary conversation transcript; Schedule has no independent durable Web receipt or browser renderer. If framing or synchronous queue admission fails, no dispatch is recorded and the reminder stays active. The narrow crash interval after admission but before durable dispatch can repeat the reminder after recovery, so the boundary is best-effort at-least-once rather than exactly-once delivery.