This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ICD/tasks.md
2026-03-11 19:41:04 +00:00

361 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 69. 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.