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).
| Method | Path | Purpose |
|---|---|---|
GET / POST | /api/scheduled-tasks | List / create |
GET / PUT / DELETE | /{id} | CRUD (update re-bases schedule) |
POST | /{id}/pause | /resume | /run-now | Control |
GET | /{id}/occurrences?take&skip | History |
GET | /occurrences/{occurrenceId} | Occurrence + report |
POST | /occurrences/{occurrenceId}/cancel | Cancel live run |
DELETE | /occurrences/{occurrenceId} | Delete finished history |
GET | /preview-schedule?… | Dry-run next N slots |
Targets:
target | Behaviour |
|---|---|
Session (default) | Each firing creates a fresh AgentSession with the task’s mode, provider, allow-lists, budgets |
AgentRun | Queues 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
| Entity | Role |
|---|---|
| IntegrationConnection | Linked external account + webhook URL + signing secret |
| Automation | Project-scoped rule: agent + connection + event type + instructions + execution mode |
| InboundEvent | Signed webhook delivery (deduped by external delivery id) |
| Invocation | One agent run for an event (Pending → Running → WaitingForApproval / terminal) |
| Approval | Human gate for write/send tool calls |
| Grant | Allow-listed tool with optional resource pattern + approval flag |
Execution modes (AutomationExecutionMode):
| Mode | Behaviour |
|---|---|
Observe | Read-only observation of events |
Draft | Agent may draft; nothing leaves the system |
ApproveWrites | Reads run immediately; mutating/external actions require approval |
ScopedAutonomous | Allowed granted actions may execute within grant limits without per-action approval |
Interactive | Reserved 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
| Method | Path | Purpose |
|---|---|---|
POST | /connect-token | { "projectId" } → Connect token (external user id from principal, never body) |
GET | /apps?query= | Search apps |
GET | /apps/{appKey}/actions | List actions |
POST | /connections | Record account; webhookSecret returned once |
GET | /accounts?projectId&appKey | Pipedream accounts for user |
GET | /connections | Local IntegrationConnections |
POST | /connections/{id}/rotate-secret | Rotate 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-signatureover 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)
| Method | Path | Purpose |
|---|---|---|
GET / POST | /api/automations | List / create |
GET / PUT / DELETE | /{id} | CRUD |
POST | /{id}/toggle | { "isActive": true } — new automations start paused |
GET | /{id}/invocations | Invocation 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
| Method | Path | Purpose |
|---|---|---|
GET | /api/automation-events?projectId&status&take | Inbox |
GET | /dead-letter | Dead letter |
GET | /{id} | Event + invocations chain |
POST | /{id}/replay | New attempt (bumps attempt count) |
POST | /{id}/ignore | Ignore |
POST | /test | Development 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
| Method | Path | Purpose |
|---|---|---|
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).