6.3 KiB
Tasks
Autonomous agent scheduling. A task is a cron-driven or one-shot prompt 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) or workflow (instantiate
a workflow in the service channel).
schedule: cron expression or once. Supported patterns: @hourly,
@daily, @weekly, */N * * * * (every N minutes), full 5-field cron
via robfig/cron/v3.
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.
Get Task
GET /tasks/:id
Auth: Owner or admin.
Update Task
PUT /tasks/:id
Partial update. All fields from create are accepted.
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.
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.
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.
Task Object
{
"id": "uuid",
"owner_id": "uuid",
"team_id": "uuid|null",
"name": "Morning News Digest",
"description": "...",
"scope": "personal|team|global",
"task_type": "prompt|workflow",
"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",
"timezone": "America/New_York",
"is_active": true,
"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": "running|completed|failed|budget_exceeded|cancelled",
"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) - Creates or reuses a
servicechannel (output_channel_id) - Persists the
user_promptas a message in the service channel - Runs
CoreToolLoop(headless completion — same as streaming but no SSE) - Enforces budgets:
max_tokens,max_tool_calls,max_wall_clock - On budget breach: status =
budget_exceeded, owner notified - On completion: updates
last_run_at, calculatesnext_run_at
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 5.
Webhooks
Tasks with webhook_url fire a POST on completion:
{
"task_id": "uuid",
"run_id": "uuid",
"status": "completed|failed|budget_exceeded",
"output": "...",
"tokens_used": 1234,
"timestamp": "..."
}
Signed with HMAC-SHA256 using webhook_secret in the
X-Switchboard-Signature header. Retry: 3 attempts, exponential backoff.