Skip to main content

Scheduled tasks & automations

Scheduled tasks

Controller: ScheduledTasksController (api/scheduled-tasks) — JWT, user-scoped (personal, not admin-global)

Requires ScheduledTasks:Enabled && AgentSessions:Enabled (create/update → 503 otherwise). Feature bit also needs Kubernetes because tasks execute as sessions (or agent runs).

MethodPathPurpose
GET / POST/api/scheduled-tasksList / create
GET / PUT / DELETE/{id}CRUD (update re-bases schedule)
POST/{id}/pause | /resume | /run-nowControl
GET/{id}/occurrences?take&skipHistory
GET/occurrences/{occurrenceId}Occurrence + report
POST/occurrences/{occurrenceId}/cancelCancel live run
DELETE/occurrences/{occurrenceId}Delete finished history
GET/preview-schedule?…Dry-run next N slots

Targets:

targetBehaviour
Session (default)Each firing creates a fresh AgentSession with the task’s mode, provider, allow-lists, budgets
AgentRunQueues an AgentRun against an existing AgentDefinition

Recurrence: exactly one of intervalMinutes (anchored on startAtUtc) or cronExpression + timeZoneId (5-field cron).

Create body (selected fields):

{
"name": "Nightly TODO sweep",
"instructions": "Collect TODOs and propose a cleanup plan.",
"projectId": 1,
"description": null,
"isActive": true,
"target": "Session",
"providerId": 12,
"model": null,
"mode": "Plan",
"knowledgeBaseIds": [3],
"mcpServerIds": [12],
"repositoryFullName": "org/repo",
"repositoryUrl": "https://github.com/org/repo",
"branch": "main",
"gitHubInstallationId": 1,
"intervalMinutes": 1440,
"cronExpression": null,
"timeZoneId": "Asia/Jerusalem",
"startAtUtc": "2026-08-09T02:00:00Z",
"endAtUtc": null,
"overlapPolicy": "Skip",
"maxConsecutiveFailures": 3,
"carryOverPreviousReport": true,
"maxTokens": 200000,
"maxEstimatedCostUsd": 5,
"maxToolCallsPerTurn": 40,
"maxModelCallsPerTurn": 40,
"reportEnabled": true
}

Safety rails: overlap Skip/Queue, unique idempotency per slot, auto-pause after consecutive failures, per-occurrence budgets, per-user concurrency caps, workspace teardown after run, DST-aware cron.

Reports: every occurrence ends with ReportMarkdown (narrative + trusted metrics footer), streamed as a report event and stored on the occurrence. Failed/cancelled runs still get a report.

Empty knowledgeBaseIds / mcpServerIds means no restriction (same as interactive sessions).


Automations, connections & approvals

Event automations wake an agent on an external event (Slack message, Shopify order, …) rather than on a schedule or interactive user. They run on an in-process Service runtime and do not require Kubernetes.

Enable with:

export Automations__Enabled=true
export Automations__PublicWebhookBaseUrl=https://your-kroov-host
# Optional Pipedream Connect
export Automations__Pipedream__ProjectId=proj_...
export Automations__Pipedream__ClientId=...
export Automations__Pipedream__ClientSecret=...

When Automations:Enabled=false, automation controllers return 503.
Use Development POST /api/automation-events/test to exercise the gateway with a connection's own signing secret.

Concepts

EntityRole
IntegrationConnectionLinked external account + webhook URL + signing secret
AutomationProject-scoped rule: agent + connection + event type + instructions + execution mode
InboundEventSigned webhook delivery (deduped by external delivery id)
InvocationOne agent run for an event (PendingRunningWaitingForApproval / terminal)
ApprovalHuman gate for write/send tool calls
GrantAllow-listed tool with optional resource pattern + approval flag

Execution modes (AutomationExecutionMode):

ModeBehaviour
ObserveRead-only observation of events
DraftAgent may draft; nothing leaves the system
ApproveWritesReads run immediately; mutating/external actions require approval
ScopedAutonomousAllowed granted actions may execute within grant limits without per-action approval
InteractiveReserved for human-watched agent chat invocations (not used by webhook dispatch)

Action classes (AgentActionClass): ReadOnly, ReversibleWrite, ExternalCommunication, Financial, Destructive, CredentialOrPermissionChange.

Runtime kinds (AgentRuntimeKind): Service (in-process automations — default for event automations), Workspace, Workflow.

Pipedream Connect — PipedreamConnectController (api/integrations/pipedream) — JWT

MethodPathPurpose
POST/connect-token{ "projectId" } → Connect token (external user id from principal, never body)
GET/apps?query=Search apps
GET/apps/{appKey}/actionsList actions
POST/connectionsRecord account; webhookSecret returned once
GET/accounts?projectId&appKeyPipedream accounts for user
GET/connectionsLocal IntegrationConnections
POST/connections/{id}/rotate-secretRotate signing secret
DELETE/connections/{id}Revoke
curl -s -X POST https://your-kroov-host/api/integrations/pipedream/connect-token \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"projectId":1}'

curl -s -X POST https://your-kroov-host/api/integrations/pipedream/connections \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"projectId":1,"appKey":"slack","accountId":"apn_abc123"}'
{
"connection": {
"id": "…",
"webhookUrl": "https://…/api/integration-events/pipedream/…"
},
"webhookSecret": "…"
}

Store webhookSecret immediately — it is encrypted at rest and never returned again (except via rotate).

Webhook gateway — IntegrationEventsController — AllowAnonymous

POST /api/integration-events/{provider}/{connectionId}
  • Connection id is in the path (routing key); signing secret belongs to that connection alone.
  • Pipedream: header x-pd-signature over the raw body.
  • Success: 202 Accepted.
  • Unknown connection or bad signature → identical 401 (no connection-id oracle).
  • Duplicate delivery id → { "duplicate": true, "deliveryId": "…" } with no second side effects.

Automations CRUD — AutomationsController (api/automations) — JWT (project-scoped)

MethodPathPurpose
GET / POST/api/automationsList / create
GET / PUT / DELETE/{id}CRUD
POST/{id}/toggle{ "isActive": true } — new automations start paused
GET/{id}/invocationsInvocation history
GET / POST / DELETE/{id}/grants…Tool grants
{
"projectId": 1,
"agentDefinitionId": 1,
"integrationConnectionId": "<guid>",
"name": "Support inbox",
"eventType": "message",
"instructions": "Read the customer message and draft a reply.",
"runtimeKind": "Service",
"executionMode": "ApproveWrites",
"maxEventsPerHour": 60,
"maxConcurrentRuns": 1
}
{
"toolName": "pipedream__slack__send_message",
"actionClass": "ExternalCommunication",
"allowedResourcePattern": "#support",
"maxCallsPerRun": 1,
"requiresApproval": true
}

An automation must reference an existing agent (POST /api/agents first). Service runtime agents do not need a repository or K8s.

Events inbox — AutomationEventsController (api/automation-events) — JWT

MethodPathPurpose
GET/api/automation-events?projectId&status&takeInbox
GET/dead-letterDead letter
GET/{id}Event + invocations chain
POST/{id}/replayNew attempt (bumps attempt count)
POST/{id}/ignoreIgnore
POST/testDevelopment only — sign a test delivery with the connection’s secret
{
"connectionId": "…",
"eventType": "message",
"payloadJson": "{\"event_id\":\"evt_1\",\"text\":\"where is my order?\"}"
}

/test returns the URL, headers, and body to POST to the real gateway.

Approvals — AutomationApprovalsController (api/automation-approvals) — JWT

MethodPathPurpose
GET/api/automation-approvals?status=List
GET/{id}Detail
POST/{id}/approve{ "comment": "…" } — records decision; ActionWorker executes
POST/{id}/reject{ "comment": "…" }

A second approve returns 409 (exactly-once action execution).