Changelog

User-facing changes to the Clockwork platform API, @clockwork/sdk and @clockwork/mcp. Every entry says where the change stands, because "merged" and "available to you" are not the same thing.

Status of planning v2

As of 2026-10-12. All times UTC. "Merged" means on main; "deployed" means a Vercel production deployment of the platform (platform.clockwork-co.com) reached READY. Merging to main triggers a production deployment automatically.

ChangeMergedDeployed to production
Planning v2 core: F1 to F7 and F9 to F11 (batch graphs, requirements, anchors, leveling, max lag, simulation, events, shared state, availability windows, guest tokens)PR #268, 2026-10-07 01:25 (df6384e). Build fix PR #271, 2026-10-08 16:31 (95c8e75). Docs PR #270, 2026-10-09 17:28 (e14fe9e)Yes, 2026-10-09. First READY deployment dpl_J6GQT4sQ… (commit 95c8e751), migrations 0072 to 0078 applied; dpl_44ERFeiu… (commit e14fe9e1, the docs PR) followed
F12: pool assignmentPR #273, 2026-10-12 01:26 (a6397a2)Yes, 2026-10-12 (commit a6397a25, READY), migration 0079 applied
Security and correctness fixesPR #274, 2026-10-12 09:46 (a98d121)Yes, 2026-10-12 (commit a98d1218, READY). Production serves this build
F8: template overrides, excludes, role bindingsPR #275, 2026-10-12 13:42 (3329a9e)Yes, 2026-10-12. The first production deployment (dpl_7VmaPm…) failed with Vercel's git_info_fail (no build log); the retry dpl_5tFFsmnW… reached READY. The F8 fields have not been exercised against production by us
FlagCLOCKWORK_PLANNING_V2=1 is set on the Production and Preview environments of the platform (set 2026-10-08/09) and is therefore on for every organization. In code the default is still off: with it off, each new endpoint answers 404 NOT_FOUND, the new fields are inert, guest tokens do not authenticate and webhooks and the event log behave exactly as before.
Access control todayThat single global flag. Per-organization gating is planned (an allow-list so the flag can be limited to chosen organizations); it is not built, and nothing on this page describes it as available.
What has been checked in productionThe served OpenAPI document is version 1.1.0 with 107 paths, and a request with a spoofed x-user-id header answers 401 (both checked 2026-10-12). One authenticated smoke test ran once with a throwaway key (job created, scheduled and cancelled; tasks deleted; resources retired). The features have not otherwise been verified in production; they were verified on a preview deployment and by local tests.

Package versions did not change for these features (@clockwork/sdk 1.0.0, @clockwork/mcp 0.1.0): identify what you have by the commit SHAs above.

The documentation pages describe the code on main. Their code samples are run against a local platform built from it with the flag on (docs-examples/), not against production.

2026-10-12 — Security and correctness fixes (merged #274, deployed)

Merged 2026-10-12 09:46 UTC, deployed to production the same day (a98d1218). Some of these change behavior for callers that relied on the old, wrong behavior.

  • Spoofable identity headers (critical). The platform trusted x-user-id / x-org-id / x-actor-id / x-actor-role / x-org-slug request headers as session context, but nothing in the platform sets them, so any caller who knew a platform-user id could act as that user without credentials. The middleware now drops these headers from every /api/v1/* request. Authenticate with an API key or a Supabase session JWT.
  • POST /tasks/{id}/assign-resource and POST /tasks/{id}/complete now apply the same task-visibility rule as delay, update and assign (403 for a task you cannot see, 404 unknown task). Guest tokens stay refused on assign-resource.
  • Webhook URL validation. POST /webhooks and POST /events/subscriptions return 400 INVALID_WEBHOOK_URL for non-http(s) URLs and URLs with embedded credentials, and, on the production platform, for localhost, *.localhost, *.local, *.internal, single-label hosts and IP literals in loopback, private, link-local, metadata or reserved ranges. The check also runs at delivery time and deliveries no longer follow redirects. A hostname that resolves to a private address (DNS rebinding) is not covered.
  • GET /conflicts?resolved=false, GET /opportunities?dismissed=false and GET /workflow-instances?withSummary=false now honour false/0 (they were read as true). Accepted values: true, false, 1, 0; anything else is 400 VALIDATION_ERROR.
  • @clockwork/sdk: ConflictType now includes anchor_infeasible and max_lag_exceeded. Code with an exhaustive switch over the old union needs the two new cases.
  • Guest tokens. An open event stream ends within one poll of the token being revoked or expiring; guest event payloads no longer carry the assignee's platform-user id; a guest delay cannot move a task earlier (422) and truncates reason to 500 characters; guest conversation titles are cut to 200; guests are rate limited by an in-process counter when Redis is not configured; lastUsedAt is updated at most once a minute; a delay with an unrepresentable date is 400 (was 500). A project-bound API key can only mint, list and revoke tokens in its own project, and revoking a token you did not mint needs organization admin.

2026-10-12 — Template overrides and scheduling fields on create (F8: merged, NOT deployed)

Merged by PR #275 on 2026-10-12 13:42 UTC. Deployed to production on 2026-10-12 (the first deployment failed with git_info_fail; the retry dpl_5tFFsmnW… reached READY; not exercised against production by us). Additive, needs CLOCKWORK_PLANNING_V2. No migration. With the flag off nothing changes, byte for byte.

Added

  • Template instantiation with overrides, excludes and role bindings. workflowInstances.create({ templateId, ... }) takes overrides (per manifest node key: title/name, description, durationMinutes, bufferAfterMinutes, isFixed, offsetFromAnchorMin; per "from->to": lagMinutes), exclude with excludeMode: 'bridge' | 'drop' (bridge connects an excluded node's predecessors to its successors with the lags summed; drop removes its dependencies) and roleBindings (role slot -> resource id, bound with the existing role machinery). The job is built through the same atomic path as createWithGraph (one transaction, one schedule computation) and the response adds applied and schedule. Unknown keys or slots, excluding every node, and a mixed-type bridge are 422s that write nothing. MCP: the create_workflow_instance tool takes the same fields. Workflow Instances

Fixed

  • POST /workflow-instances silently dropped anchor, schedulingDirection and resourceLeveling. With the flag on they are now validated (same rules as createWithGraph; backward without anchor is a 422 ANCHOR_REQUIRED) and stored. With the flag off they are still dropped without an error, as before. The schema remains non-strict, so other unknown fields are still ignored. The SDK's CreateWorkflowInstanceInput now declares the fields.

2026-10-12 — Pool assignment (F12: merged, deployed)

Written 2026-10-10; merged by PR #273 on 2026-10-12 01:26 UTC; deployed to production 2026-10-12 (a6397a25) with migration 0079 (two nullable columns, reversible). Needs CLOCKWORK_PLANNING_V2 and is inert unless you set the new field.

Added

  • Automatic assignment from a pool. instance.assignmentPolicy ('balance_load' or 'earliest_finish', needs resourceLeveling: 'delay') on createWithGraph makes the planner pick one member of a capability:<id> pool for each pool requirement. The choice is returned in schedule.assignments, stored on the requirement (assignedResourceId from tasks.listRequirements) and is sticky: a started or finished task never changes member, an unstarted task keeps its member while it stays in the pool. Deterministic and idempotent. Without the field nothing changes. MCP: assignmentPolicy on create_workflow_graph. Resource Requirements

2026-10-07 — Planning v2 (merged 2026-10-07, deployed to production 2026-10-09)

The features below were authored on 2026-10-06 and merged together by PR #268 on 2026-10-07 (F8 and F12 followed, above). They are additive: nothing changes for an existing integration until the flag is on and you use a new field or endpoint. Migrations 0072–0078 shipped with them (all nullable or defaulted columns, each with a reversible down script) and were applied by the production deployment on 2026-10-09. The flag is now on in production, so the endpoints below exist there.

Added

  • Atomic batch graph creation (ca2c08d). POST /workflow-instances/graph (workflowInstances.createWithGraph) creates a job with its resources, tasks and dependencies in one transaction with one schedule computation, and returns the instance, a ref -> id map and the computed schedule. Unknown refs, duplicate refs and dependency cycles are rejected with a 422 naming the offending refs, and nothing is written. Limits per request: 500 tasks, 2,000 dependencies, 100 resources. Also POST /tasks/batch (tasks.createBatch) and POST /dependencies/batch (dependencies.createBatch). MCP: create_workflow_graph. Counts as one request toward rate limits. Migration 0072 (planned start/end and latest start on tasks; required start and makespan on jobs). Batch Graphs
  • Multi-unit and partial resource requirements (9321836). A task can claim units of a resource or pool with an occupancy of full, bookends (first and last minutes only) or none. GET/PUT /tasks/{id}/requirements (tasks.listRequirements / setRequirements; PUT replaces the list). Units above capacity are a 422 REQUIREMENT_EXCEEDS_CAPACITY; over-allocation is reported as a resource_capacity_breach conflict. MCP: get_task_requirements, set_task_requirements. Migration 0073. Resource Requirements
  • Anchored backward scheduling (350e7cc). Jobs take anchor: { type: 'finish', at } and schedulingDirection: 'forward' | 'backward' (default forward). Backward places every unstarted task at its latest start that still meets the anchor. New constraint types anchor_window and deadline with relativeTo, a per-task anchorWindow shorthand, and an anchor_infeasible conflict (with shortfallMinutes) when the anchor cannot be met. Tasks gain plannedStart, plannedEnd, latestStart; jobs gain requiredStart and makespanMinutes. Migration 0074. Anchored Scheduling
  • Maximum lag on dependencies (9cef3d2). maxLagMinutes (at least lagMinutes; null clears it) on dependency create/update, batch graphs and batch dependencies. Planner-managed jobs keep the gap within the limit; other jobs get a max_lag_exceeded conflict. Migration 0075. Dependencies
  • Resource leveling (4249291). resourceLeveling: 'off' | 'delay' on jobs (default off, which only reports over-allocation). delay resolves it by delaying unstarted tasks, lowest slack first, without moving started or completed tasks. Migration 0076. Resource Leveling
  • Dry-run simulation (2652544). POST /schedule/simulate (scheduling.simulate; MCP simulate_schedule): a hypothetical graph, or { baseInstanceId, mutations } against a live job with delay, complete, add_resource and remove_resource. Persists nothing and emits no events. Has its own rate-limit bucket at 5× the key's budget. Schedule Simulation
  • Schedule-change events and one event vocabulary (a3d4c70). schedule.changed is emitted once per recompute, listing every task whose planned window moved. Canonical dot-notation event names everywhere, with the original underscore names and the names earlier releases delivered under still accepted. A shared envelope that is byte-identical on webhooks and on the new server route GET /events/stream (Server-Sent Events: polling-backed, about one second of lag, heartbeat, resume with Last-Event-ID). The SDK's events.stream() existed before, but the server route it called did not. Events, Webhooks, Event-Driven Patterns
  • Shared state and setup time on resources (47a9db1). Resources declare stateKey, stateTolerance and stateSetupMinutes; requirements take a state. Claims overlap only within the tolerance and a larger change inserts the setup time. 422 STATE_NOT_SUPPORTED for a state on a pool or on a resource that declares none. Migration 0077. Resources
  • Resource availability windows (4c4919e). resources.availability entries shaped { from, to } or { start, end } (ISO 8601) are absolute windows the planner keeps occupied time inside. No migration. With the flag on, unusable window entries are rejected with 400. Resources
  • Scoped guest tokens and filtered streams (01b6fa8). POST /tokens, GET /tokens, DELETE /tokens/{id} (tokens.create / list / revoke; MCP create_guest_token, list_guest_tokens, revoke_guest_token) mint short-lived, revocable cg_live_… tokens for a person with no account, bound to one job and to either one resource's tasks or a task list, with a fixed action list, a per-token rate limit and a lifetime of at most 30 days. Webhook subscriptions take workflowInstanceId and resourceId delivery filters. New management scopes tokens:read and tokens:write. Migration 0078. A security review of this feature is recommended before enabling it in production. Guest Access

Changed

  • With the flag on, the webhook body is the shared event envelope. event carries the canonical name (task.delayed, not task.updated; conflict.detected, not constraint.violated; task.ready, not task.created) and legacy_event keeps the earlier name. A handler that switched on the earlier name should switch on type. X-Clockwork-Event carries the canonical name. A subscription registered under an earlier name keeps receiving the event. Webhooks
  • criticalPath in plan output is now a single driving chain through tight dependencies, not every zero-slack task.
  • Ids generated for a batch request are handed out in ref order, so the same request body produces the same schedule.
  • ClockworkError.details in the SDK now carries structured error fields such as refs on a 422.

Documentation

  • Rate limits are documented: 1,000 requests per 60-second window per API key by default, 5× for simulation, 120 per guest token by default; RateLimit-Reset is the seconds until the window ends, not a Unix timestamp as the page previously said. Authentication
  • The Webhooks guide and the Events page now use one set of event names (the guide used to subscribe to task_delayed but switch on task.updated), with a table of how each event appears on SSE and on webhooks, a handler that works with the payload shape from before and after the flag is on, and an account of what delivery guarantees exist and do not exist (see Webhooks).
  • The OpenAPI document (/api/v1/openapi.json) is now version 1.2.0 (1.1.0 added the planning-v2 surface; 1.2.0 corrects the POST /dependencies/batch response shape and the task/job response unions): it documents the paths the SDK calls (/api/v1/<resource>, with the older /runtime/... forms noted as equivalent), has request and response schemas for each operation, the error envelope, rate-limit headers, and the scope each operation requires. Removed the /templates path, which does not exist.

2026-10-07 — Scheduled jobs in the web app (merged)

  • Vercel cron schedules in apps/web are daily (b116328, PR #269 merged 2026-10-07 03:36 UTC, merge 2275046; the web app's deployment is not tracked on this page). /api/cron/auto-triage-sweep runs at 06:00 UTC and /api/cron/email-scan-sweep at 07:00 UTC (previously every 30 and 15 minutes) so the project deploys on Vercel's Hobby plan. This affects the Clockwork web app only, not the platform API.

Known issues

Found while checking this documentation against the code; each was reproduced on a local platform built from main with a fresh database, not on production. They are listed so that you do not meet them by surprise; none is fixed by the documentation change.

  • Attaching a constraint to a job fails. constraints.attach with targetType: 'instance' is accepted by the API and SDK but answers 500 INTERNAL_ERROR: the table has a check allowing only execution, task and workflow_definition (migration 0042). Attach to the job's tasks. Constraints
  • On forward jobs with leveling off, plannedStart, plannedEnd, latestStart, requiredStart and makespanMinutes keep their creation values after a delay; earliestStart is the live start there. Tasks
  • conflicts.snooze records snoozedUntil and does nothing else: snoozed conflicts still appear in list(). Conflicts
  • Webhooks are one attempt with no retries and no ordering guarantee, and whether a delivery always completes after the request that caused it has answered is not verified on the hosted runtime. Webhooks
  • Editing a resource or a capability pool's membership does not re-plan jobs; the next task change in the job does. Resources, Capability Pools

Earlier

Changes before 2026-10-07 are not itemised here. The last production build of the platform before planning v2 was 117b998 (2026-08-02).