361 lines
8.8 KiB
Markdown
361 lines
8.8 KiB
Markdown
# 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
|
||
```
|
||
|
||
```json
|
||
{
|
||
"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), or `action`
|
||
(no LLM — relay/webhook only; requires `task.action` permission).
|
||
|
||
`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`:
|
||
```json
|
||
{
|
||
"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.
|
||
|
||
## 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
|
||
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"owner_id": "uuid",
|
||
"team_id": "uuid|null",
|
||
"name": "Morning News Digest",
|
||
"description": "...",
|
||
"scope": "personal|team|global",
|
||
"task_type": "prompt|workflow|action",
|
||
"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
|
||
|
||
```json
|
||
{
|
||
"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.
|
||
|
||
1. Finds tasks where `next_run_at <= now` and `is_active = true`
|
||
2. Skips if an active run exists (`GetActiveRun` returns non-nil)
|
||
3. Adopts a queued run if one exists (from webhook trigger), otherwise
|
||
creates a new run record
|
||
4. Marks `last_run_at` (records when execution started, not when it finished)
|
||
5. Creates or reuses a `service` channel (`output_channel_id`)
|
||
6. Persists the `user_prompt` as a message in the service channel
|
||
(with trigger payload prepended if present)
|
||
7. For **prompt** tasks: runs `CoreToolLoop` (headless completion)
|
||
8. For **action** tasks: skips LLM, relays trigger payload to webhook
|
||
9. Enforces budgets: `max_tokens`, `max_tool_calls`, `max_wall_clock`
|
||
10. On budget breach: status = `budget_exceeded`, owner notified
|
||
11. 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:
|
||
|
||
```json
|
||
{
|
||
"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.
|