ServicesTasks

Submit and manage Tasks

Submit a durable Task, select its resources, follow its status, and cancel it.

In development

Submit an operation as a Task when it must keep going after your program exits, retry after a Worker failure, or run on more compute than your computer has. This guide submits Table maintenance, which compacts the frames Table on the main branch of the demo Project. The same requests work for every built-in Task.

Prerequisites

  • You have the Project editor role, and the permissions that the specification needs on its Resources.
  • The Project has Worker capacity for the specification. See Worker Pools.

Find the branch and Table IDs

Table maintenance names the branch and the Table by ID, not by name. Look both up in the Catalog. First read the branch:

curl "https://api.spiraldb.dev/api/catalog/projects/demo/branches/by-name?name=main" \
  -H "Authorization: Bearer $SPIRAL_TOKEN"

Save branchId as $BRANCH_ID and headCommitId as $COMMIT_ID. Then resolve the Table's path at that commit:

curl -X POST "https://api.spiraldb.dev/api/catalog/projects/demo/commits/$COMMIT_ID/namespace/resolve" \
  -H "Authorization: Bearer $SPIRAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path": ["frames"]}'

Save resource.descriptor.resourceId as $TABLE_RESOURCE_ID. The IDs do not change when the branch moves or the Table is written, so you can store them.

Submit a Task

A submission carries a definition of the Task, optional resource options, and an idempotency key. The definition's type names the kind of Task, and its fields name the Resources it works on. The key makes the request safe to retry: if you send the same request again with the same key, Spiral returns the same Task instead of creating a second one. Reusing a key with a different definition fails with a conflict, because it most likely means two different operations got the same key. Each SDK's submit_task (submitTask in TypeScript) returns a handle that reads the Task's progress and waits for its result, and picks a new key for each call.

definition = TasksTaskDefinition(
    table_maintenance=TasksTableMaintenanceDefinition(
        read_target=TasksTaskReadTarget(latest_on_branch=TasksTaskReadTargetLatestOnBranch(branch_id=branch_id)),
        table_resource_id=table_resource_id,
    )
)
options = TasksTaskOptions(resource_profile="cpu_4")
task = client.project("demo").submit_task(definition, options=options)
if task is None:
    print("skipped: another maintenance Task already waits for this Table")
    return
print(f"accepted {task.id}")
progress = task.progress()
if progress is not None:
    print(f"{progress.percentage}% done")
print(f"published {len(task.wait().versions)} versions")

The response has a type and a matching payload:

OutcomeMeaning
acceptedSpiral created the Task, or returned the Task for this idempotency key. The response contains accepted.task with its taskId and status.
skippedAnother Task for the same Table is already waiting, so there is no need for this one. The response has no Task. See When two Tasks overlap.

Select resources

The options object controls execution:

OptionValue
resourceProfilecpu_1, cpu_2, cpu_4, cpu_8, or cpu_16. It must be in the specification's allowed list. Without it, the Task uses the default profile.
maxAttemptsThe largest number of Attempts.
deadlineAtMicrosA deadline in microseconds since the Unix epoch. If the Task has not finished by then, Spiral stops it and marks it failed with deadline_exceeded.

Read each specification's default and allowed profiles:

curl https://api.spiraldb.dev/api/tasks/projects/demo/task-specs \
  -H "Authorization: Bearer $SPIRAL_TOKEN"

You cannot ask for custom CPU or memory values; select a profile instead. See Choosing a Worker size.

Follow the status

Read the Task, its Attempts, and the Resources that it references:

details = client.tasks.read_task(task_id)
print(f"{details.task.task_spec_id.name} ran {details.task.attempt_count} attempts")
if details.task.status in ("succeeded", "failed", "cancelled"):
    print("already finished")
else:
    task = client.tasks.cancel_task(task_id)
    print(f"cancellation requested for {task.task_id}")

To find Tasks, list them with filters such as status, taskSpecName, resourceId, origin, and pageSize. Pass nextPageToken from a response as pageToken to read the next page:

curl "https://api.spiraldb.dev/api/tasks/projects/demo/tasks?status=running&pageSize=20" \
  -H "Authorization: Bearer $SPIRAL_TOKEN"

GET /api/tasks/projects/demo/task-stats returns the count of ready and running Tasks.

Cancel a Task

curl -X POST https://api.spiraldb.dev/api/tasks/$TASK_ID/cancel \
  -H "Authorization: Bearer $SPIRAL_TOKEN"

In an SDK, call cancel_task (cancelTask in TypeScript), as the example in Follow the status shows.

Cancelling asks the Worker to stop; it does not undo work. If the Worker already published its change, the Task ends succeeded instead of cancelled. Either way, no new Attempt starts after the request. See What happens when things fail.

Troubleshooting

HTTP errors use the shared error envelope.

ErrorCauseFix
Tasks:InvalidArgumentThe definition is invalid, or the profile is not allowed.Read parameters.reason.
Default:PermissionDeniedYou do not have a permission that the Task needs on a Resource.Read operation and resource, then ask a Project admin for a role.
Tasks:ConflictThe idempotency key was used with another input.Use a new key for a new submission.
The Task stays readyNo compatible Worker can claim it.Turn on managed Workers, or start a Worker that runs the specification. See Worker Pools.

Where next: automate the Task on a schedule or after each Table change.

Found a problem? Edit this page or report an issue.

On this page