# 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 ``` ```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) 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 ```json { "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 ```json { "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. 1. Finds tasks where `next_run_at <= now` and `is_active = true` 2. Skips if an active run exists (`GetActiveRun` returns non-nil) 3. Creates or reuses a `service` channel (`output_channel_id`) 4. Persists the `user_prompt` as a message in the service channel 5. Runs `CoreToolLoop` (headless completion — same as streaming but no SSE) 6. Enforces budgets: `max_tokens`, `max_tool_calls`, `max_wall_clock` 7. On budget breach: status = `budget_exceeded`, owner notified 8. On completion: updates `last_run_at`, calculates `next_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: ```json { "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.