11 KiB
Tasks
Autonomous agent scheduling. A task is a cron-driven, one-shot, or webhook-triggered execution in a headless service channel — no human participant in the loop.
Configuration
Global config keys (set via PUT /admin/settings/:key):
| Key | Default | Description |
|---|---|---|
tasks.enabled |
true |
Master kill switch for task scheduler |
tasks.allow_personal |
true |
Allow non-admin users to create tasks |
tasks.max_concurrent |
5 |
Max tasks running simultaneously |
tasks.personal_require_byok |
false |
Personal tasks must use BYOK provider |
Default budget ceilings (overridable per task):
| Key | Default |
|---|---|
tasks.default_max_tokens |
4096 |
tasks.default_max_tool_calls |
10 |
tasks.default_max_wall_clock |
300 (seconds) |
Personal Tasks
List My Tasks
GET /tasks
Returns tasks owned by the current user.
Auth: Authenticated.
Create Task
POST /tasks
{
"name": "Morning News Digest",
"description": "Summarize top tech news",
"task_type": "prompt",
"persona_id": "uuid|null",
"model_id": "claude-sonnet-4-20250514",
"system_prompt": "You are a news summarizer.",
"user_prompt": "Summarize the top 5 tech news stories from today.",
"schedule": "@daily",
"timezone": "America/New_York",
"max_tokens": 4096,
"max_tool_calls": 10,
"max_wall_clock": 300,
"output_mode": "channel",
"webhook_url": "https://...",
"provider_config_id": "uuid|null",
"notify_on_complete": false,
"notify_on_failure": true,
"tool_grants": ["web_search", "url_fetch"]
}
task_type: prompt (direct LLM execution), workflow (deferred —
not yet implemented; creation is rejected at the API), action
(no LLM — relay/webhook only; requires task.action permission),
or system (v0.28.6 — built-in Go function, admin-only).
System tasks require system_function — a registered function name
from the platform's Go function registry. No LLM, no provider
resolution, no channel needed. The function receives the store set
and returns a result string. Designed for core platform ops
(session_cleanup, staleness_check, retention_sweep, health_prune)
that must not break from bad user code. Permanent track — not replaced
by Starlark in v0.29.0.
schedule: cron expression, once, or webhook. Supported cron
patterns: @hourly, @daily, @weekly, */N * * * * (every N
minutes), full 5-field cron via robfig/cron/v3. When schedule is
webhook, the task fires only on inbound POST to its trigger URL.
output_mode: channel (default — output stays in service channel),
note (creates a note from the output), webhook (POST result to URL).
Auth: tasks.create permission required. Additionally, task.action
permission is required for task_type: "action".
Get Task
GET /tasks/:id
Auth: Owner or admin.
Update Task
PUT /tasks/:id
Partial update. Accepted fields: name, description, persona_id,
model_id, system_prompt, user_prompt, workflow_id, tool_grants,
schedule, timezone, is_active, max_tokens, max_tool_calls,
max_wall_clock, output_mode, output_channel_id, webhook_url,
provider_config_id, notify_on_complete, notify_on_failure.
Auth: tasks.create permission, must be owner.
Delete Task
DELETE /tasks/:id
Auth: tasks.create permission, must be owner.
Run Now (Manual Trigger)
POST /tasks/:id/run
Immediately executes the task regardless of schedule. Creates a new task run. Returns 409 if a run is already active or queued.
Auth: tasks.create permission, must be owner.
Kill Active Run
POST /tasks/:id/kill
Cancels the active run. Sets status to cancelled.
Auth: tasks.create permission, must be owner.
List Runs
GET /tasks/:id/runs
Returns run history for the task, most recent first.
Auth: Owner or admin.
Webhook Trigger (Inbound)
External systems fire a task by POSTing to its trigger URL.
POST /api/v1/hooks/t/:token
The :token is a per-task secret generated at creation time (returned
in the trigger_token field). The request body is stored as
trigger_payload on the run record and forwarded to the executor:
- Prompt tasks: payload is prepended to the user prompt as context.
- Action tasks: payload is relayed to the outbound webhook as-is.
- Workflow tasks: (deferred) payload becomes initial stage data.
Returns 202 Accepted:
{
"triggered": true,
"run_id": "uuid",
"task_id": "uuid"
}
Returns 409 Conflict if a run is already active or queued.
Returns 410 Gone if the task is inactive.
Auth: None (token-based). The trigger token is the credential.
Rate limiting: Governed by global request rate limits. No per-task rate limiting in v0.28 — defer to a future version if needed.
Task-to-Task Chaining (v0.28.6)
Tasks can trigger other tasks by setting the webhook_url of Task A
to the trigger URL of Task B:
- Create Task B with
schedule: "webhook". Note the trigger URL from the response (/api/v1/hooks/t/{trigger_token}). - Create Task A with
output_mode: "webhook"and setwebhook_urlto Task B's trigger URL. - When Task A completes, its output is POSTed to Task B's trigger URL.
Task B starts with Task A's output as its
trigger_payload.
This enables multi-step pipelines without external orchestration:
- Task A: "Summarize today's news" (prompt, cron: 6am)
- Task B: "Format and post to Slack" (action, webhook-triggered)
The webhook_secret on Task A signs outbound payloads with HMAC-SHA256.
Task B's trigger endpoint does not currently verify signatures (the
trigger token itself is the auth mechanism).
Team Tasks
Team-scoped tasks visible to all team members, manageable by team admins.
List Team Tasks (Member)
GET /teams/:teamId/tasks
Returns tasks scoped to the team. Read-only for members.
Auth: Team member.
List Team Task Runs (Member)
GET /teams/:teamId/tasks/:id/runs
Auth: Team member.
Create Team Task (Admin)
POST /teams/:teamId/tasks
Same shape as personal task creation. Team scope is injected automatically.
Auth: tasks.create permission, team admin.
Update/Delete/Run/Kill Team Task
PUT /teams/:teamId/tasks/:id
DELETE /teams/:teamId/tasks/:id
POST /teams/:teamId/tasks/:id/run
POST /teams/:teamId/tasks/:id/kill
Auth: tasks.create permission, team admin.
Admin Tasks
Platform-wide task management.
List All Tasks
GET /admin/tasks
Returns all tasks across all users and teams.
Auth: Platform admin.
Admin Run/Kill/Delete
POST /admin/tasks/:id/run
POST /admin/tasks/:id/kill
DELETE /admin/tasks/:id
Auth: Platform admin.
System Functions (v0.28.6)
GET /admin/system-functions → { "data": [SystemFuncInfo, ...] }
Returns all registered built-in system function names and descriptions. Used by the admin UI to populate the function dropdown when creating system tasks.
SystemFuncInfo:
{
"name": "session_cleanup",
"description": "Remove expired anonymous visitor sessions"
}
Built-in functions (v0.28.6): session_cleanup, staleness_check,
retention_sweep, health_prune.
Auth: Platform admin.
Task Object
{
"id": "uuid",
"owner_id": "uuid",
"team_id": "uuid|null",
"name": "Morning News Digest",
"description": "...",
"scope": "personal|team|global",
"task_type": "prompt|workflow|action|system",
"system_function": "session_cleanup",
"persona_id": "uuid|null",
"model_id": "claude-sonnet-4-20250514",
"system_prompt": "...",
"user_prompt": "...",
"workflow_id": "uuid|null",
"tool_grants": ["web_search", "url_fetch"],
"schedule": "@daily|once|webhook",
"timezone": "America/New_York",
"is_active": true,
"trigger_token": "hex-string|null",
"max_tokens": 4096,
"max_tool_calls": 10,
"max_wall_clock": 300,
"output_mode": "channel|note|webhook",
"output_channel_id": "uuid|null",
"webhook_url": "https://...",
"webhook_secret": "...",
"provider_config_id": "uuid|null",
"notify_on_complete": false,
"notify_on_failure": true,
"last_run_at": "...|null",
"next_run_at": "...|null",
"run_count": 42,
"created_at": "...",
"updated_at": "..."
}
Task Run Object
{
"id": "uuid",
"task_id": "uuid",
"channel_id": "uuid|null",
"status": "queued|running|completed|failed|budget_exceeded|cancelled",
"trigger_payload": "...|null",
"started_at": "...",
"completed_at": "...|null",
"tokens_used": 1234,
"tool_calls": 3,
"wall_clock": 45,
"error": "...|null"
}
Execution
The TaskScheduler is a background goroutine polling every 30 seconds.
- Finds tasks where
next_run_at <= nowandis_active = true - Skips if an active run exists (
GetActiveRunreturns non-nil) - Adopts a queued run if one exists (from webhook trigger), otherwise creates a new run record
- Marks
last_run_at(records when execution started, not when it finished) - Creates or reuses a
servicechannel (output_channel_id) - Persists the
user_promptas a message in the service channel (with trigger payload prepended if present) - For prompt tasks: runs
CoreToolLoop(headless completion) - For action tasks: skips LLM, relays trigger payload to webhook
- Enforces budgets:
max_tokens,max_tool_calls,max_wall_clock - On budget breach: status =
budget_exceeded, owner notified - On completion: calculates
next_run_at
Workflow tasks: Not yet implemented. The API rejects
task_type: "workflow" at creation time. Reserved for a future version.
Provider resolution: BYOK → team provider → global provider → routing
policy. If tasks.personal_require_byok is true, personal tasks that
don't have a BYOK provider fail at step 7.
Action tasks skip steps 6–9. They create the service channel, fire the outbound webhook with the trigger payload, and complete.
Webhooks (Outbound)
Tasks with webhook_url fire a POST on completion:
{
"task_id": "uuid",
"run_id": "uuid",
"task_name": "Morning News Digest",
"channel_id": "uuid",
"status": "completed|failed|budget_exceeded",
"completed_at": "2025-03-11T12:00:00Z",
"output": "...",
"tokens_used": 1234,
"error": "...|null"
}
Signed with HMAC-SHA256 using webhook_secret in the
X-Switchboard-Signature header. Retry: 3 attempts, exponential
backoff (1s, 5s, 25s).
Task Chaining
Task A's outbound webhook_url can be Task B's trigger URL:
Task A (webhook_url) → POST /api/v1/hooks/t/<task-B-token> → Task B runs
This enables multi-step pipelines: CI failure → summarize with LLM → post to Slack. Each link is a separate task with its own type, budget, and notification preferences.