Skip to main content

Schedule recurring work

A cron sends a saved prompt to the projects you pick in a workspace, or to none, on a schedule. Each time it fires, Sensei hands the prompt, word for word, to the Cron Agent in a disposable sandbox that clones the selected repositories, if any. A run opens no chat session of its own. Every workspace cron run spends the workspace's Credentials, never a member's Subscription. To manage crons in the web app, see Crons. A Personal Cron has its own endpoints under /api/user/crons; see Personal crons.

Create a cron​

curl -s -X POST "https://app.kosuke.ai/api/organizations/$WORKSPACE_ID/crons" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly dependency check",
"projectIds": ["'"$PROJECT_ID"'"],
"prompt": "Check for outdated dependencies and update the patch versions.",
"cronExpression": "0 6 * * 1"
}' > cron.json

CRON_ID=$(jq -r '.data.id' cron.json)
FieldRequiredValue
nameYesUp to 100 characters.
projectIdsYesThe projects it fires in, all of this workspace. A project created later joins only when you edit the cron to add it. Send an empty list for a cron that runs with no repository: its run has no clones, files no session, and sends no Sensei message and raises no Sensei Event.
promptYesWhat the Cron Agent is asked to do, handed to it word for word. It can run a skill from the project's checkout.
cronExpressionYesFive fields, in order minute, hour, day of month, month and day of week, read in the Workspace Timezone. The schedule may fire at most once every 15 minutes.
modelNoA model id such as anthropic:opus__xhigh. Leave it out, or send null, for the workspace default.

The response answers 201 with the cron, enabled:

{
"data": {
"id": "0e6b2d91-…",
"name": "Weekly dependency check",
"projects": [{ "id": "9b1d4e27-…", "name": "storefront", "baseBranch": "main", "lastRunStatus": null, "running": false }],
"cronExpression": "0 6 * * 1",
"timezone": "UTC",
"model": null,
"enabled": true,
"nextRunAt": "2026-10-05T06:00:00.000Z",
"createdByName": "Ada Lovelace"
}
}

With the CLI:

kosuke crons create "$WORKSPACE_ID" --project "$PROJECT_ID" --name "Weekly dependency check" \
--prompt "Check for outdated dependencies and update the patch versions." \
--cron-expression "0 6 * * 1"

Repeat --project to add more projects. The CLI requires --project, so for a cron with none, send the whole body: kosuke crons create "$WORKSPACE_ID" --body '{"name":"Weekly digest","projectIds":[],"prompt":"Summarize the week.","cronExpression":"0 6 * * 1"}'. kosuke workspaces list gives the workspace id.

A cron has no timezone of its own. Every cron of a workspace is read in the Workspace Timezone, UTC until an owner or admin sets another. The cron's timezone field tells you which zone that is, and you cannot send it. To change it for every cron at once:

kosuke workspaces timezone set "$WORKSPACE_ID" --timezone Europe/Zurich

Creating a cron answers 400 for a body that sends timezone, a schedule that fires more often than every 15 minutes, or a project of another workspace. It also answers 400 when the workspace has no usable AI provider Credentials.

Read its runs​

Every fire leaves one run covering its selected projects, even a fire that started nothing. A cron with no project leaves one run with no project. Projects are not paused one by one. projects lists every project a run covers:

curl -s "https://app.kosuke.ai/api/organizations/$WORKSPACE_ID/crons/$CRON_ID/runs" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
| jq '.data.runs[] | {projects, status, summary, error, startedAt}'

The response holds runs, newest first, with total and failed counts. It takes page and pageSize, and projectId and status filters that repeat for several values, such as ?status=failed&status=skipped.

Run statusMeaning
runningThe Cron Agent is working.
succeededThe Cron Agent finished. summary holds the first line of its reply.
failedThe fire was refused, or the Cron Agent failed. error says why.
cancelledThe cron's creator or a workspace admin stopped the run.
skippedThe previous run of this cron was still working, or another fire was already starting, so nothing started.

kosuke crons runs get reads one run with its reply and the sessions it filed. kosuke crons runs messages list reads its transcript, and kosuke crons runs cancel stops a run that is still going. With the CLI, list the runs with kosuke crons runs list <workspaceId> <cronId>, adding --project or --status to filter them.

Run it now​

Fire the cron once, whether it is paused or not, covering all its projects:

curl -s -X POST "https://app.kosuke.ai/api/organizations/$WORKSPACE_ID/crons/$CRON_ID/run" \
-H "Authorization: Bearer $KOSUKE_API_KEY"

The request answers { "data": { "runs": [{ "runId": "…", "status": "running", "projectIds": ["…"], "projectId": "…" }] } }. The one run in runs covers the projects with projectIds. projectId names only the first of them and is kept for older clients. status is the run's status when the request answers, so it can be any status in the table above. A run that ended quickly is already succeeded, failed or cancelled. It is queued when the fire had not opened the run within about ten seconds. The run fires exactly as a scheduled one does, on the workspace's Credentials, and the sessions the Cron Agent files are Sensei's, not yours. Read a run with kosuke crons runs get <workspaceId> <cronId> <runId> to see how it went. A cron with no project runs once, with no project. Run now is refused for a cron disabled with "No projects left" until a save picks a project. Send { "projectIds": [...] } to run it in only some of its projects. With the CLI, run kosuke crons run <workspaceId> <cronId>, with --project for each project to pick.

Pause and resume​

In the web app, the cron's Enabled switch in Settings pauses or resumes it at once. The Crons list's row menu still has Pause and Resume.

Send enabled: false to pause the cron, and enabled: true to resume it:

curl -s -X PATCH "https://app.kosuke.ai/api/organizations/$WORKSPACE_ID/crons/$CRON_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'

With the CLI, run kosuke crons update <workspaceId> <cronId> --no-enabled to pause and --enabled to resume.

The same request changes any other field, and only the fields you send change. projectIds replaces the cron's projects. A cron disabled with "No projects left" stays disabled, and enabled: true is refused, until a save sends at least one project; a save that sends none, such as a rename, keeps the reason. "model": null goes back to the workspace default. The CLI sends it with --body '{"model": null}'.

Five failed runs in a row disable the whole cron: it reads enabled: false, and disabledReason says why. A run that failed because Sensei was updated while it worked does not count, and only a run that succeeds sets the count back to zero. A cancelled or skipped run leaves the count as it was. Resume it with kosuke crons update <workspaceId> <cronId> --enabled. Resuming or saving any edit sets the count back to zero, and resuming clears the reason. A cron disabled with "No projects left" stays disabled until a save picks a project.

Who can do what​

ActionWho
Create a cronAny member of the workspace
Read crons and their runsAny member of the workspace
Change, pause, resume, run now, stop a run or deleteThe cron's creator, or a workspace owner or admin. Anyone else gets 403.

The agent inside a chat session acts as the session's Assignee, when the Assignee sent the turn. It can create, change, run, stop and delete crons as they could. When anyone else sent the turn, it acts as the workspace, as a cron's run does.

Delete a cron​

curl -s -X DELETE "https://app.kosuke.ai/api/organizations/$WORKSPACE_ID/crons/$CRON_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY"

Deleting a cron also deletes its runs, their transcripts and their Artifacts. The sessions its runs filed stay. Pause the cron instead if you want to keep them. With the CLI, run kosuke crons delete <workspaceId> <cronId>.

Next steps​