Changeset 0.28.0.2 (#174)

This commit is contained in:
2026-03-11 19:41:04 +00:00
parent 58313f7e31
commit 8e08f3e4b0
19 changed files with 1734 additions and 384 deletions

View File

@@ -1,7 +1,8 @@
# 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.
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
@@ -63,17 +64,20 @@ POST /tasks
}
```
`task_type`: `prompt` (direct LLM execution) or `workflow` (instantiate
a workflow in the service channel).
`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 or `once`. Supported patterns: `@hourly`,
`@daily`, `@weekly`, `*/N * * * *` (every N minutes), full 5-field cron
via `robfig/cron/v3`.
`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.
**Auth:** `tasks.create` permission required. Additionally, `task.action`
permission is required for `task_type: "action"`.
### Get Task
@@ -89,7 +93,11 @@ GET /tasks/:id
PUT /tasks/:id
```
Partial update. All fields from create are accepted.
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.
@@ -108,7 +116,7 @@ POST /tasks/:id/run
```
Immediately executes the task regardless of schedule. Creates a new
task run. Returns 409 if a run is already active.
task run. Returns 409 if a run is already active or queued.
**Auth:** `tasks.create` permission, must be owner.
@@ -132,6 +140,39 @@ 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.
@@ -209,16 +250,17 @@ DELETE /admin/tasks/:id
"name": "Morning News Digest",
"description": "...",
"scope": "personal|team|global",
"task_type": "prompt|workflow",
"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",
"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,
@@ -244,7 +286,8 @@ DELETE /admin/tasks/:id
"id": "uuid",
"task_id": "uuid",
"channel_id": "uuid|null",
"status": "running|completed|failed|budget_exceeded|cancelled",
"status": "queued|running|completed|failed|budget_exceeded|cancelled",
"trigger_payload": "...|null",
"started_at": "...",
"completed_at": "...|null",
"tokens_used": 1234,
@@ -260,18 +303,29 @@ 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`
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 5.
don't have a BYOK provider fail at step 7.
## Webhooks
**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:
@@ -279,12 +333,28 @@ 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,
"timestamp": "..."
"error": "...|null"
}
```
Signed with HMAC-SHA256 using `webhook_secret` in the
`X-Switchboard-Signature` header. Retry: 3 attempts, exponential backoff.
`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.