# Workflow Schedules

<!-- BEGIN GENERATED: guide-map -->
## Guide map

Read only the section needed for the next decision. Line ranges are exact
for this generated runtime target.

| Section | What it covers | Lines |
| --- | --- | --- |
| [Purpose](#purpose) | Scope, ownership, and the central operating question. | 26-32 |
| [Use This When](#use-this-when) | Tasks and conditions that belong in this guide. | 33-39 |
| [Do Not Use This For](#do-not-use-this-for) | Boundaries and the correct routes for excluded work. | 40-46 |
| [Mental Model](#mental-model) | Concepts and state boundaries needed for correct operation. | 47-61 |
| [Preflight](#preflight) | State, authority, inputs, and readiness to verify before execution. | 62-71 |
| [Risk And Side Effects](#risk-and-side-effects) | Effects, approvals, cost, privacy, and retry boundaries. | 72-87 |
| [Canonical Operating Pattern](#canonical-operating-pattern) | The normal end-to-end execution sequence. | 88-124 |
| [Data Contracts](#data-contracts) | Required identities, fields, evidence, handles, and durable shapes. | 125-153 |
| [Examples](#examples) | Representative usage patterns and code. | 154-184 |
| [Core SDK Surfaces](#core-sdk-surfaces) | Exact source-backed callable contracts owned by this guide. | 185-211 |
| [Verification](#verification) | Evidence required before reporting success. | 212-232 |
| [Troubleshooting](#troubleshooting) | Failure diagnosis and corrective routes. | 233-247 |
| [Related References](#related-references) | Adjacent boundaries and when to cross them. | 248-255 |
<!-- END GENERATED: guide-map -->

## Purpose

Use this guide when a Workflow Play should run later, recur, be triggered now
through an existing server schedule, or deliver its completed result to Slack.
The public SDK owns schedule definition and observation. Runtime-specific agent
orchestration belongs to the active host, not this installed Skill.

## Use This When

- A Play should run once later or on a recurrence.
- A user explicitly asks to run an existing server schedule now.
- An operator needs schedule health, tick progress, authority, or Slack delivery
  state.

## Do Not Use This For

- Use `workflow_operations.md` for immediate local Play runs and snapshots.
- Use `workflow_sources.md` for source windows and checkpoint freshness.
- Use `workflow_reports.md` for report and output-bundle design.
- Do not use this public guide for host infrastructure or agent-loop policy.

## Mental Model

Keep these lanes distinct.

| Lane | Use it for | Preserve |
| --- | --- | --- |
| Local or external | A user machine, CI, cron, LaunchAgent, or another scheduler invokes the generated Play. | Scheduler identity, command, workflow root, local run id, logs, and Play sync receipt. |
| Server agent | A server-owned schedule runs an agent task. The public API literal is `hosted_mochi`. | Schedule id, tick id, agent run id, thread id, and health. |
| Server deterministic | A server-owned schedule executes the exact synced Play revision. The public API literal is `hosted_workflow`. | Schedule id, tick id, code revision id, workflow progress id, Play run id, and output ids. |

Local scheduling has no SDK CRUD surface. Configure the external scheduler to
run the generated Play with the SDK environment, then keep Play sync enabled if
the result should appear in workspace history. Server schedules use
`vf.plays.schedules.*`.

## Preflight

- Resolve one canonical `play_id` and stable `schedule_slug`.
- For deterministic server execution, sync the Play and preserve its exact ready
  revision id.
- Read an existing schedule before changing it. Preserve its recurrence, status,
  delivery target, metadata, workflow scope, thread policy, and next due time
  unless the user asked to change them.
- Confirm whether the request means schedule only, run now, or both.

## Risk And Side Effects

- `list`, `get`, `ticks`, and `live` are reads.
- `upsert`, `create_agent_schedule`, `set_status`, `delete`, `reauthorize`,
  `revoke_authority`, and `run_now` are writes.
- `run_now` can spend credits or trigger the Play's declared effects.
- A code sync or material schedule edit invalidates previous execution
  authority. Reload the schedule, review its current definition and
  `authority_grant_version`, then reauthorize that exact version.
- Use `revoke_authority(...)` for an immediate authority stop. Use
  `set_status(..., "disabled")` to stop future automatic claims without
  deleting the schedule.
- Slack delivery is terminal-only. Preserve the returned delivery target and
  thread handles. Failed, blocked, skipped, and cancelled ticks do not deliver
  a successful result.

## Canonical Operating Pattern

1. Resolve the canonical `play_id`. Register the Play first if you only have a
   Local root.
2. For deterministic server execution, declare final outputs with
   `workflow.declare_output(...)`, then call `vf.workflows.sync(...)` and
   preserve its exact ready revision. Sync never executes the Play.
   In Local SDK operation, sync publishes changed code and, when
   `include_outputs=True`, eligible completed Local run output bytes. It may
   therefore return both a ready code revision and already-published Local run
   outputs.
3. Build the recurrence with `interval`, timezone-aware `calendar`, `once`, or
   `manual`. Use `calendar` for local time, weekdays, business days, holidays,
   and DST stability.
4. Call `vf.plays.schedules.upsert(...)` with a stable `schedule_slug`, the
   intended execution runtime, and the exact schedule definition. Read the
   returned `health` object.
5. Stop after `upsert` when the user asked only to schedule future work.
   Scheduling does not imply an immediate run.
6. Call `run_now(...)` only for an explicit immediate request. Reuse one
   idempotency key only when retrying the same definition and revision. Use a
   new key after changing recurrence, run template, or code revision.
7. Poll `get_tick(...)` or `ticks(...)` until the queued tick has progress
   handles or is terminal. Stream with `live(...)` using exactly the handle
   documented by the function contract.
8. For deterministic completed runs, verify the terminal Play run and every
   artifact published from its declarations, then download the declared
   workspace artifact with
   `vf.plays.outputs.download(...)` and verify the returned digest receipt.
   Slack and browser links must resolve those same artifact ids and committed
   bytes.

One Play and one `schedule_slug` are stable identities. Repair and update those
identities instead of creating proof schedules. `409 workflow_play_schedule_busy`
means another tick owns the schedule. Wait for that tick rather than creating a
duplicate schedule.

## Data Contracts

```python
hourly = {"type": "interval", "cadence_seconds": 3600}

weekday_morning = {
    "type": "calendar",
    "timezone": "America/Los_Angeles",
    "local_time": "08:00",
    "business_days": True,
    "excluded_dates": ["2026-07-03"],
    "starts_at": "2026-07-01T00:00:00-07:00",
    "ends_at": "2026-12-31T23:59:59-08:00",
}

once = {"type": "once", "at": "2026-07-14T15:00:00Z"}
manual = {"type": "manual"}
```

Every explicit timestamp needs a timezone offset. Raw cron is rejected because
it does not encode the timezone and DST contract. Disable recurrence with
`set_status(..., "disabled")`.

The schedule response owns `schedule_id`, `schedule_slug`, `status`,
`schedule_spec`, `run_template`, `delivery_target`, `reuse_thread`,
`next_run_at`, `authority_grant_version`, and `health`. A tick owns `tick_id`,
`status`, trigger type, scheduled time, run handles, terminal summary, and error.
Preserve these values instead of reconstructing them from local inputs.

## Examples

```python
from pathlib import Path

import verbiflow as vf

root = Path("/absolute/path/play")
synced = vf.workflows.sync(root)
play_id = synced["play_id"]
revision_id = synced["code"]["code_revision_id"]

schedule = vf.plays.schedules.upsert(
    play_id,
    schedule_slug="weekday-review",
    schedule_spec={
        "type": "calendar",
        "timezone": "America/Los_Angeles",
        "local_time": "08:00",
        "business_days": True,
    },
    execution_runtime="hosted_workflow",
    expected_revision_id=revision_id,
    run_template={"task": "Run the pinned Play entrypoint."},
)

print(schedule["schedule"]["schedule_id"], schedule["schedule"]["health"])
```

Do not call `run_now` in this example unless the user also asked to run it now.

## Core SDK Surfaces

<!-- BEGIN GENERATED: core-sdk-surfaces -->
Workspace server schedule and Play sync SDK functions:

| Function | Use When | Inputs | Output And Handles To Preserve | Side Effect | Source |
| --- | --- | --- | --- | --- | --- |
| `vf.plays.get` | Fetch one canonical server Play and its latest run/output metadata. | <strong>Required</strong>: `play_id` (str; required): Canonical uploaded or Workflow Play id returned by plays.list.<br><strong>Optional</strong>: none | `workflow_play` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data.<br><strong>Common next calls</strong>: `vf.plays.schedules.list` | read | installed SDK: `verbiflow/plays.py:239-265` |
| `vf.plays.list` | List the workspace's uploaded and Workflow Plays from the canonical server catalog, including local-only Plays by default. Filter by exact title when known. | <strong>Required</strong>: none<br><strong>Optional</strong>: `include_hidden` (bool; optional; default `True`): Include local-only Workflow Plays in the canonical catalog.; `q` (str \| None; optional; default `None`): Optional bounded title/slug/root-label search for a colloquial Play reference.; `title` (str \| None; optional; default `None`): Optional exact title, matched case-insensitively before the server limit. | `workflow_play_list` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data.<br><strong>Common next calls</strong>: `vf.plays.get`, `vf.play_scaffold.init` | read | installed SDK: `verbiflow/plays.py:182-237` |
| `vf.plays.outputs.download` | Download a hosted Workflow Play workspace artifact, verify its size and SHA-256, and atomically write it locally. | <strong>Required</strong>: `output_id` (str; required): terminal_summary.artifact_output_id from the completed tick.; `output_path` (str \| Path; required): Absolute local path where the verified workspace zip is written.; `play_id` (str; required): Canonical Workflow Play id that owns the output.; `run_id` (str; required): Workflow Play run id returned by a hosted workflow tick.<br><strong>Optional</strong>: none | `workflow_play_output_download` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | local_file_write | installed SDK: `verbiflow/plays.py:344-463` |
| `vf.plays.outputs.get` | Fetch one durable Workflow Play run output and its content handle. | <strong>Required</strong>: `output_id` (str; required): Durable output id. Hosted workflow ticks return this as terminal_summary.artifact_output_id.; `play_id` (str; required): Canonical Workflow Play id that owns the output.; `run_id` (str; required): Workflow Play run id, workflow progress run id, hosted agent run id, or local run id returned by the server.<br><strong>Optional</strong>: none | `workflow_play_output` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data.<br><strong>Common next calls</strong>: `vf.plays.outputs.download` | read | installed SDK: `verbiflow/plays.py:301-342` |
| `vf.plays.schedules.create_agent_schedule` | Create a lightweight auto Play plus hosted agent schedule in one call. | <strong>Required</strong>: `schedule_spec` (JsonObject; required): Server recurrence object. Supports interval, timezone-aware calendar, once, and manual specs. Use calendar for DST-stable local time, weekdays, business days, and start/end windows.; `task` (str; required): Hosted agent task prompt to run on every scheduled tick.; `workflow_slug` (str; required): Stable Workflow Play slug for the auto-created agent schedule anchor.<br><strong>Optional</strong>: `delivery_target` (JsonObject \| None; optional; default `None`): Optional proactive delivery target. V1 supports {'kind': 'slack_channel', 'team_id': 'T...', 'channel_id': 'C...'}.; `description` (str \| None; optional; default `None`): Optional display description for the auto-created Play.; `metadata` (JsonObject \| None; optional; default `None`): Optional operator/debug metadata for the schedule.; `next_run_at` (str \| None; optional; default `None`): Optional first due time for interval or once schedules as an ISO timestamp with timezone offset. Calendar schedules derive it and use schedule_spec.starts_at as the optional lower bound.; `reuse_thread` (bool; optional; default `False`): Reuse the schedule's last hosted agent thread for recurring ticks.; `schedule_slug` (str \| None; optional; default `None`): Stable human-readable schedule key for idempotent edits.; `status` (enabled \| disabled; optional; default `'enabled'`): Initial schedule status.; `title` (str \| None; optional; default `None`): Optional display title for the auto-created Play.; `workflow_name` (str \| None; optional; default `None`): Optional display workflow name for the auto-created Play.; `workflow_scope` (str \| None; optional; default `None`): Optional advanced workflow runtime scope override. Omit it to use the server deployment scope; set it only when intentionally separating schedule lanes. | `dict` with `play` and `schedule`; preserve the auto-created `play.id`, `workflow_slug`, `schedule_id`, `delivery_target`, `reuse_thread`, all `health` fields, and returned run/thread handles. | platform_write | installed SDK: `verbiflow/plays.py:693-837` |
| `vf.plays.schedules.delete` | Soft-delete a Workflow Play schedule so future ticks are not claimed. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Server schedule id to soft-delete.<br><strong>Optional</strong>: none | `dict`/empty success response for a soft delete. Preserve the requested `schedule_id` in your own notes; no future ticks should be claimed after success. | platform_write | installed SDK: `verbiflow/plays.py:1021-1047` |
| `vf.plays.schedules.get` | Fetch one Workflow Play schedule by id. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Server schedule id returned by plays.schedules.list or upsert.<br><strong>Optional</strong>: none | `dict` with one `schedule`; preserve `schedule_id`, `schedule_slug`, `status`, `schedule_spec`, `next_run_at`, all `health` fields, and last tick/run/thread handles. | read | installed SDK: `verbiflow/plays.py:506-532` |
| `vf.plays.schedules.get_tick` | Fetch one Workflow Play schedule tick by id. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id that owns the tick.; `tick_id` (str; required): Server schedule tick id returned by run_now or ticks.<br><strong>Optional</strong>: none | `dict` with one `tick`; preserve `tick_id`, `status`, `trigger_type`, `runId`, `threadId`, `workflowProgressRunId`, `workflowPlayRunId`, `terminal_summary`, and `error_message`. | read | installed SDK: `verbiflow/plays.py:1141-1167` |
| `vf.plays.schedules.list` | List current non-deleted Workflow Play schedules for a Play. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id returned by registration or Play sync.<br><strong>Optional</strong>: `schedule_slug` (str \| None; optional; default `None`): Optional exact schedule slug filter. | `dict` with `schedules`; preserve each `schedule_id`, `schedule_slug`, `status`, `next_run_at`, `health.status`, `health.reason`, `health.changed_at`, `health.attention_required`, and last tick/run/thread handles. | read | installed SDK: `verbiflow/plays.py:470-504` |
| `vf.plays.schedules.live` | Stream structured progress pages for a started scheduled run. | <strong>Required</strong>: none<br><strong>Optional</strong>: `after_seq` (int \| None; optional; default `None`): Optional durable event cursor for reconnect/resume. Pass the last seen seq from a previous live page.; `run_id` (str \| None; optional; default `None`): Hosted agent run id returned by plays.schedules.run_now or a hosted_mochi schedule tick.; `workflow_progress_run_id` (str \| None; optional; default `None`): Workflow progress run id returned by a hosted_workflow schedule tick. Use this instead of run_id for deterministic hosted workflow runs.<br><strong>Note</strong>: Pass exactly one of `run_id` or `workflow_progress_run_id`. Poll `get_tick(...)` or `ticks(...)` first if the queued tick has no hosted run handle yet. | `Iterator[dict]` of durable SSE pages for a scheduled run. Use `run_id` for hosted agent ticks and `workflow_progress_run_id` for deterministic hosted workflow ticks. Preserve the latest event sequence for reconnect and stop when the page reports terminal state. | read | installed SDK: `verbiflow/plays.py:1474-1535` |
| `vf.plays.schedules.reauthorize` | Authorize the current code and definition of a changed Workflow Play schedule. | <strong>Required</strong>: `expected_authority_grant_version` (int; required): Current authority_grant_version from plays.schedules.get. The server rejects stale versions so a changed schedule cannot be authorized by accident.; `idempotency_key` (str; required): Stable retry key for this exact reauthorization decision. Reuse it only when retrying the same reviewed schedule definition.; `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Schedule whose current definition and code should be authorized.<br><strong>Optional</strong>: `expected_revision_id` (str \| None; optional; default `None`): Optional exact ready code revision that this authority decision reviewed. | `workflow_play_schedule` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | platform_write | installed SDK: `verbiflow/plays.py:883-959` |
| `vf.plays.schedules.revoke_authority` | Revoke a Workflow Play schedule's active execution authority. | <strong>Required</strong>: `expected_authority_grant_version` (int; required): Current authority_grant_version from plays.schedules.get. The server rejects stale versions.; `idempotency_key` (str; required): Stable retry key for this exact revocation decision.; `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Schedule whose authority should be revoked immediately.<br><strong>Optional</strong>: none | `workflow_play_schedule` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | platform_write | installed SDK: `verbiflow/plays.py:961-1019` |
| `vf.plays.schedules.run_hosted_workflow` | Use after workflows.sync returned the canonical play id and exact ready revision, when the user explicitly requested a deterministic hosted run and optional verified workspace download. | <strong>Required</strong>: `expected_revision_id` (str; required): Exact ready code revision returned by workflows.sync; execution fails before tick creation if the server revision changed.; `idempotency_key` (str; required): Stable retry key bound to this exact revision and schedule definition. Use a new key after changing recurrence or run_template.; `play_id` (str; required): Canonical Play id returned by workflows.sync.; `schedule_slug` (str; required): Stable key used to idempotently upsert the schedule.<br><strong>Optional</strong>: `output_path` (str \| Path \| None; optional; default `None`): Optional local path for the verified workspace ZIP.; `poll_interval` (float; optional; default `2.0`): Seconds between durable tick reads.; `run_template` (JsonObject \| None; optional; default `None`): Small hosted-workflow run template; credentials are forbidden.; `schedule_spec` (JsonObject \| None; optional; default `None`): Exact manual, once, interval, or timezone-aware calendar schedule. Defaults to {'type': 'manual'} for an immediate-only proof.; `timeout` (float; optional; default `900.0`): Maximum seconds to wait for terminal tick state. | `dict` normalized receipt with `status`, `play_id`, exact `revision_id`, `schedule_id`, `tick_id`, `workflow_play_run_id`, `workflow_progress_run_id`, `attempt_run_id`, `artifact_output_id`, optional verified `artifact`, and `error_message`. | platform_write | installed SDK: `verbiflow/plays.py:1169-1472` |
| `vf.plays.schedules.run_now` | Run an immediate manual tick for a Workflow Play schedule. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Server schedule id to queue immediately.<br><strong>Optional</strong>: `expected_revision_id` (str \| None; optional; default `None`): Optional code revision guard. New ticks require this exact latest ready revision. Same-key retries require the revision pinned to the stored tick.; `idempotency_key` (str \| None; optional; default `None`): Stable retry key for this manual trigger. Reuse the same key when retrying the same user request. | `dict` with `tick`; preserve `tick_id`, `status`, `trigger_type`, `runId`, `threadId`, `workflowProgressRunId`, `workflowPlayRunId`, `liveUrl`, and `scheduled_for`. | platform_write | installed SDK: `verbiflow/plays.py:1049-1104` |
| `vf.plays.schedules.set_status` | Enable or disable a Workflow Play schedule. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Server schedule id to enable or disable.; `status` (enabled \| disabled; required): New schedule status: enabled resumes future claims; disabled stops future claims and skips queued ticks.<br><strong>Optional</strong>: none | `dict` with updated `schedule`; preserve `schedule_id`, `status`, `next_run_at`, all `health` fields, and last tick/run/thread handles for audit. | platform_write | installed SDK: `verbiflow/plays.py:839-881` |
| `vf.plays.schedules.ticks` | List recent ticks for a Workflow Play schedule. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id that owns the schedule.; `schedule_id` (str; required): Server schedule id whose recent ticks should be listed.<br><strong>Optional</strong>: `limit` (int; optional; default `50`): Maximum recent schedule ticks to return for this schedule. | `dict` with recent `ticks`; preserve `tick_id`, `status`, `trigger_type`, `runId`, `threadId`, `workflowProgressRunId`, `workflowPlayRunId`, `terminal_summary`, and `error_message`. | read | installed SDK: `verbiflow/plays.py:1106-1139` |
| `vf.plays.schedules.upsert` | Create or update a server-owned Workflow Play schedule. | <strong>Required</strong>: `play_id` (str; required): Canonical backend Workflow Play id to schedule.; `schedule_spec` (JsonObject; required): Server recurrence object. Supports interval, timezone-aware calendar, once, and manual specs. Use calendar for DST-stable local time, weekdays, business days, and start/end windows.<br><strong>Optional</strong>: `delivery_target` (JsonObject \| None; optional; default `None`): Optional proactive delivery target. V1 supports {'kind': 'slack_channel', 'team_id': 'T...', 'channel_id': 'C...'}.; `execution_runtime` (hosted_mochi \| hosted_workflow; optional; default `'hosted_mochi'`): Execution lane for ticks. Use hosted_mochi for agentic scheduled work and hosted_workflow for deterministic server execution of the latest synced Play code.; `expected_revision_id` (str \| None; optional; default `None`): Optional ready code revision compare-and-set guard for schedule definition and authority changes.; `metadata` (JsonObject \| None; optional; default `None`): Optional operator/debug metadata for the schedule. Do not put secrets, auth tokens, local absolute paths, or runtime-selection fields here.; `next_run_at` (str \| None; optional; default `None`): Optional first due time for interval or once schedules as an ISO timestamp with timezone offset. Calendar schedules derive it and use schedule_spec.starts_at as the optional lower bound.; `reuse_thread` (bool; optional; default `False`): When true, recurring hosted_mochi ticks reuse the schedule's last agent thread unless run_template pins an explicit thread_id.; `run_template` (JsonObject \| None; optional; default `None`): Hosted run template object. For hosted_mochi include the scheduled task/context. For hosted_workflow keep it small; the server runs the latest synced Play code. Do not include auth credentials or local file paths.; `schedule_slug` (str \| None; optional; default `None`): Stable human-readable schedule key for idempotent upsert, for example daily-review. Keep it stable when editing the same schedule.; `status` (enabled \| disabled; optional; default `'enabled'`): Initial schedule status. Use enabled for active schedules or disabled to save without running.; `workflow_scope` (str \| None; optional; default `None`): Optional advanced workflow runtime scope override. Omit it to use the server deployment scope; set it only when intentionally separating schedule lanes. | `dict` with `schedule`; preserve `schedule_id`, stable `schedule_slug`, `next_run_at`, `run_template_hash`, `delivery_target`, `reuse_thread`, all `health` fields, and any returned last tick/run/thread handles. | platform_write | installed SDK: `verbiflow/plays.py:534-691` |
| `vf.workflows.sync` | Keep a Workflow Play current for hosted use by syncing changed executable code and eligible completed run outputs. This never starts a workflow run. | <strong>Required</strong>: `path_or_id` (str \| Path; required): Workflow project directory, catalog id, or generated workflow path to sync.<br><strong>Optional</strong>: `dry_run` (bool; optional; default `False`): Plan the sync and compute hashes without uploading code or outputs.; `include_drafts` (bool; optional; default `False`): Whether to sync blocked/interrupted run state for review.; `include_failed` (bool; optional; default `False`): Whether to sync failed run diagnostics.; `include_outputs` (bool; optional; default `True`): Whether to sync eligible completed run outputs in addition to code.; `run_id` (str \| None; optional; default `None`): Optional local run id whose output should be synced.; `visibility` (visible \| local_only; optional; default `None`): Optional workspace visibility change applied with this sync. Omit it to preserve the Play's current visibility. | `dict` with `play_id`, `code` status, `code.source_hash`, `code.archive_sha256`, `outputs` status, uploaded artifact ids, `schedule_ready`, skipped reasons, and errors. Preserve both code hashes when debugging hosted schedules. This is the one public manual repair path for keeping a Workflow Play current; the daemon uses the same sync engine. | platform_write | installed SDK: `verbiflow/workflows.py:1390-1497` |
<!-- END GENERATED: core-sdk-surfaces -->

## Verification

- After `upsert`, call `get` and compare `schedule_spec`, `run_template`,
  `status`, `next_run_at`, `delivery_target`, `reuse_thread`,
  `authority_grant_version`, and every `health` field.
- After `run_now`, preserve the returned tick id. Poll that tick rather than
  issuing another trigger.
- Treat `needs_reauthorization` and `needs_sync` as first-class unhealthy
  states. Report the health reason and transition time.
- A same-key retry must return the same tick and pinned definition. A changed
  definition with the same key must fail instead of silently replaying stale
  work.
- A retry after artifact publication must adopt the existing immutable
  publication receipt. It must not rerun the Play or create replacement output
  bytes.
- For a completed deterministic tick, verify the pinned code revision, Play run,
  declared output ids, canonical artifact ids, blob digests, and delivery
  projection before calling the schedule healthy.
- For Local or external schedules, verify scheduler logs, the Local run record,
  and the Play sync receipt.

## Troubleshooting

- A schedule 404 usually means the wrong Play, workspace, or a deleted row.
- `schedule_authority_version_conflict` means the schedule changed after the
  read. Reload and review the new definition before making another decision.
- `workflow_play_needs_sync` means deterministic execution has no ready synced
  revision. Run `vf.workflows.sync(...)` from the Play root and retry with the
  returned revision.
- A queued tick without progress handles has not been prepared yet. Poll the
  same tick.
- `workflow_play_schedule_busy` identifies the active tick. Wait for it instead
  of creating another schedule.
- Reusing an idempotency key after changing code or schedule definition is an
  error. Generate a new key for the new exact operation.

## Related References

| If you are doing this | Read next |
| --- | --- |
| Running or inspecting a Play in its runtime filesystem | `workflow_operations.md` |
| Changing source windows or checkpointing | `workflow_sources.md` |
| Building reports and output bundles | `workflow_reports.md` |
| Reviewing confirmation and effect rules | `safety.md` |
