Constraints

A Constraint is a reusable scheduling rule that you attach to tasks. When the plan breaks it, Clockwork raises a constraint_violated Conflict. A constraint has a free-form type string, but only three types are interpreted; any other type is stored and returned as you sent it and never evaluated.

Planning v2 availability. Merged to main and deployed to production (platform.clockwork-co.com; F1 to F7 and F9 to F11 on 2026-10-09, F12 pool assignment and the security fixes on 2026-10-12). The platform flag CLOCKWORK_PLANNING_V2 is currently on globally, in production and in preview, as of 2026-10-12: every organization gets planning-v2 behavior, and with the flag off the planning-v2 endpoints answer 404. Per-organization gating (CLOCKWORK_PLANNING_V2=orgs) is merged but not enabled in production, so the global flag is the only access control in effect. Exact dates and commits: Changelog.

When to use client.constraints

  • Keeping a task inside a window measured from a job's required finish time (anchor_window)
  • Giving tasks a deadline measured from the job's anchor or from when the job was created (deadline)
  • Limiting how many of a set of tasks may run at the same time (max_concurrent), for example "at most two press runs at once"
  • Reusing one rule across many tasks or jobs

Methods

MethodDescription
create(input)Define a constraint
list(opts?)List constraints in a project, newest first
attach(constraintId, input)Attach to a task (or, see below, a job)
setStatus(constraintId, status)'active' or 'disabled'

constraints.create(input)

Parameters

ParameterTypeRequiredDescription
namestringyesHuman-readable constraint name
typestringyes'anchor_window', 'deadline' and 'max_concurrent' are interpreted; anything else is only stored
definitionRecord<string, unknown>noType-specific configuration (below)
projectIdstringnoDefaults to your personal project

Which types do something

typeInterpreted byDefinitionResult when broken
anchor_windowthe planner, for jobs that use it{ relativeTo: 'instance_anchor', minOffsetMinutes?, maxOffsetMinutes?, applyTo?: 'finish' | 'start' }constraint_violated ("placed N minute(s) outside its allowed window")
deadline with a relativeTothe planner, for jobs that use it{ relativeTo: 'instance_anchor' | 'instance_start', offsetMinutes?, offsetHours?, offsetDays? }constraint_violated, same wording
max_concurrentthe scheduling engine, on every recompute{ maxConcurrent: n } with an integer n >= 1constraint_violated on each task that pushes the concurrent count over n
deadline without relativeTo, time_window, capacity, anything elsenothingwhatever you likenone: stored, listed, never evaluated

"The planner, for jobs that use it" means a job created with schedulingDirection: 'backward' or resourceLeveling: 'delay' (see Workflow Instances). On a plain forward job with leveling off, anchor_window and deadline constraints are not applied when the job is recomputed. max_concurrent does not depend on that.

anchor_window

Keeps a task's finish (default) or start inside a window around the job's anchor, the required finish time set with instance.anchor. Offsets are in minutes; negative means before the anchor.

typescript
const { data: shipWindow } = await client.constraints.create({
  name: 'Ship window',
  type: 'anchor_window',
  definition: { relativeTo: 'instance_anchor', minOffsetMinutes: -30, maxOffsetMinutes: -5 }, // finish 30 to 5 minutes before the anchor
})

The definition is checked when you create the constraint, and is strict: relativeTo must be 'instance_anchor', at least one of minOffsetMinutes / maxOffsetMinutes is required, minOffsetMinutes <= maxOffsetMinutes, applyTo is 'finish' or 'start', and any other key is rejected. A bad definition is 422 VALIDATION_ERROR with a message such as Invalid anchor_window definition: Provide minOffsetMinutes and/or maxOffsetMinutes. If the job has no anchor, the constraint does not apply.

deadline

The task must finish by a time measured from a reference. The offsets add up (offsetDays: 1, offsetHours: 2 is 26 hours); each is an integer and defaults to 0.

typescript
// Done within 90 minutes of the job being created
const { data: within } = await client.constraints.create({
  name: 'Done within 90 minutes',
  type: 'deadline',
  definition: { relativeTo: 'instance_start', offsetMinutes: 90 },
})
// Done 60 minutes before the anchor
await client.constraints.create({ name: 'Prep done', type: 'deadline', definition: { relativeTo: 'instance_anchor', offsetMinutes: -60 } })
  • 'instance_anchor' is the job's required finish time; without an anchor the constraint does not apply.
  • 'instance_start' is the moment the job was created.
  • A definition that names a relativeTo is validated (422 VALIDATION_ERROR for another value, e.g. Invalid enum value. Expected 'instance_anchor' | 'instance_start', received 'instance_end'). Other keys are tolerated. A deadline with no relativeTo (the earlier free-form style, { offsetDays: 5 }) is stored as given and is not interpreted.
  • To put a deadline on a single task, use the task's own deadline field: it raises a deadline_violated conflict instead.

max_concurrent

typescript
const { data: cap } = await client.constraints.create({
  name: 'At most 2 press runs at once',
  type: 'max_concurrent',
  definition: { maxConcurrent: 2 },
})
for (const taskId of [runA, runB, runC]) await client.constraints.attach(cap.id, { targetType: 'task', targetId: taskId })

Every time the schedule is recomputed, the engine sweeps the attached tasks (skipping completed and cancelled ones). A task that starts while maxConcurrent others are running gets a constraint_violated conflict: Task "Run a" pushes this constraint's concurrent count to 3, over its cap of 2. Nothing is moved: it is reported, not resolved. A task that finishes at the exact moment another starts does not overlap it. A definition without a valid integer maxConcurrent is ignored.

constraints.attach(constraintId, input)

typescript
await client.constraints.attach(shipWindow.id, { targetType: 'task', targetId: 'tsk_xyz' }) // 201
targetTypeEffect
'task'The constraint applies to that task. This is the target to use
'instance'Documented to apply to every task of a job. Known issue: on a database built from the migrations the call currently fails with 500 INTERNAL_ERROR, because a database CHECK on constraint_attachments.target_type only allows execution, task and workflow_definition. Attach to the job's tasks instead
'execution', 'workflow_definition'Accepted and stored, never evaluated

An unknown targetType is 400 VALIDATION_ERROR; an unknown constraint is 404 NOT_FOUND. targetId is not checked against any table. Attaching an anchor_window or deadline to a task of a planner-managed job recomputes that job straight away; for max_concurrent the violation appears on the next recompute (any task change). A task can carry several constraints: their windows are combined into the tightest one.

Graph tasks can carry the anchor window directly: anchorWindow on a task in createWithGraph creates an anchor_window constraint attached to that task for you.

constraints.setStatus(constraintId, status)

typescript
await client.constraints.setStatus(cap.id, 'disabled')

A disabled constraint is no longer evaluated. Conflicts it already raised stay until the job's next recompute, which no longer produces them. Both changes are written to the event log (constraint.disabled, constraint.enabled), along with constraint.created and constraint.attached; these have no webhook.

Violations

A broken constraint produces a constraint_violated conflict on the task concerned, listed by client.conflicts.list() and, for planner jobs, returned in schedule.conflicts. The conflict.detected webhook announces it without the conflict itself; read it from the API.

Type reference

typescript
type ConstraintStatus = 'active' | 'disabled'
type ConstraintTargetType = 'execution' | 'task' | 'workflow_definition' | 'instance'
 
interface Constraint {
  id: string
  projectId: string | null
  name: string
  type: string
  definition?: Record<string, unknown> | null
  status?: ConstraintStatus
  createdAt: string
}
 
interface CreateConstraintInput {
  projectId?: string
  name: string
  type: string
  definition?: Record<string, unknown>
}
 
interface AttachConstraintInput {
  targetType: ConstraintTargetType
  targetId: string
}

Runnable: docs-examples/constraints-types.mts (every validation message above, a deadline violation, a max_concurrent violation, disabling, and the instance known issue).